lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-2.0.1 vs lib/asciidoctor-epub3/converter.rb in asciidoctor-epub3-2.1.0

- old
+ new

@@ -5,48 +5,43 @@ require 'sass' require_relative 'font_icon_map' module Asciidoctor module Epub3 - # Public: The main converter for the epub3 backend that handles packaging the - # EPUB3 or KF8 publication file. + # Public: The main converter for the epub3 backend that handles packaging the EPUB3 publication file. class Converter include ::Asciidoctor::Converter include ::Asciidoctor::Logging include ::Asciidoctor::Writer register_for 'epub3' def write(output, target) - epub_file = @format == :kf8 ? %(#{::Asciidoctor::Helpers.rootname target}-kf8.epub) : target - output.generate_epub epub_file - logger.debug %(Wrote #{@format.upcase} to #{epub_file}) + output.generate_epub target + logger.debug %(Wrote to #{target}) if @extract - extract_dir = epub_file.sub EPUB_EXTENSION_RX, '' + extract_dir = target.sub EPUB_EXTENSION_RX, '' ::FileUtils.remove_dir extract_dir if ::File.directory? extract_dir ::Dir.mkdir extract_dir ::Dir.chdir extract_dir do - ::Zip::File.open epub_file do |entries| + ::Zip::File.open target do |entries| entries.each do |entry| next unless entry.file? unless (entry_dir = ::File.dirname entry.name) == '.' || (::File.directory? entry_dir) ::FileUtils.mkdir_p entry_dir end entry.extract entry.name end end end - logger.debug %(Extracted #{@format.upcase} to #{extract_dir}) + logger.debug %(Extracted to #{extract_dir}) end - if @format == :kf8 - # QUESTION: shouldn't we validate this epub file too? - distill_epub_to_mobi epub_file, target, @compress - elsif @validate - validate_epub epub_file - end + return unless @validate + + validate_epub target end CSV_DELIMITED_RX = /\s*,\s*/.freeze DATA_DIR = ::File.expand_path ::File.join(__dir__, '..', '..', 'data') @@ -78,18 +73,10 @@ }.freeze TO_HTML_SPECIAL_CHARS_RX = /[#{TO_HTML_SPECIAL_CHARS_MAP.keys.join}]/.freeze EPUB_EXTENSION_RX = /\.epub$/i.freeze - KINDLEGEN_COMPRESSION = { - '0' => '-c0', - '1' => '-c1', - '2' => '-c2', - 'none' => '-c0', - 'standard' => '-c1', - 'huffdic' => '-c2' - }.freeze QUOTE_TAGS = begin tags = { monospaced: ['<code>', '</code>', true], emphasis: ['<em>', '</em>', true], @@ -107,11 +94,11 @@ end def initialize(backend, opts = {}) super basebackend 'html' - outfilesuffix '.epub' # dummy outfilesuffix since it may be .mobi + outfilesuffix '.epub' htmlsyntax 'xml' end def convert(node, name = nil, _opts = {}) method_name = %(convert_#{name ||= node.node_name}) @@ -156,23 +143,20 @@ def icon_names @icon_names ||= [] end def convert_document(node) - @format = node.attr('ebook-format').to_sym - @validate = node.attr? 'ebook-validate' @extract = node.attr? 'ebook-extract' @compress = node.attr 'ebook-compress' - @kindlegen_path = node.attr 'ebook-kindlegen-path' @epubcheck_path = node.attr 'ebook-epubcheck-path' @xrefs_seen = ::Set.new @media_files = {} @footnotes = [] @book = GEPUB::Book.new 'EPUB/package.opf' - @book.epub_backward_compat = @format != :kf8 + @book.epub_backward_compat = true @book.language node.attr('lang', 'en'), id: 'pub-language' if node.attr? 'uuid' @book.primary_identifier node.attr('uuid'), 'pub-identifier', 'uuid' else @@ -237,11 +221,11 @@ # For list of supported landmark types see # https://idpf.github.io/epub-vocabs/structure/ landmarks = [] front_cover = add_cover_page node, 'front-cover' - if front_cover.nil? && @format != :kf8 && node.doctype == 'book' + if front_cover.nil? && node.doctype == 'book' # TODO(#352): add textual front cover similar to PDF end landmarks << { type: 'cover', href: front_cover.href, title: 'Front Cover' } unless front_cover.nil? @@ -287,13 +271,13 @@ href: %(#{get_chapter_filename item}.xhtml), title: item.title } end - nav_item.add_content postprocess_xhtml(nav_doc(node, toc_items, landmarks, outlinelevels)) + nav_item.add_content nav_doc(node, toc_items, landmarks, outlinelevels).to_ios # User is not supposed to see landmarks, so pass empty array here - toc_item&.add_content postprocess_xhtml(nav_doc(node, toc_items, [], toclevels)) + toc_item&.add_content nav_doc(node, toc_items, [], toclevels).to_ios # NOTE: gepub doesn't support building a ncx TOC with depth > 1, so do it ourselves toc_ncx = ncx_doc node, toc_items, outlinelevels @book.add_item 'toc.ncx', content: toc_ncx.to_ios, id: 'ncx' @@ -404,24 +388,21 @@ </style> ) end header = if title || subtitle - %(<header> -<div class="chapter-header"> + %(<header class="chapter-header"> #{byline}<h1 class="chapter-title">#{title}#{subtitle ? %(<small class="subtitle">#{subtitle}</small>) : ''}</h1> -</div> </header>) else '' end # We want highlighter CSS to be stored in a separate file # in order to avoid style duplication across chapter files linkcss = true - # NOTE: kindlegen seems to mangle the <header> element, so we wrap its content in a div lines = [%(<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xmlns:mml="http://www.w3.org/1998/Math/MathML" xml:lang="#{lang = node.document.attr 'lang', 'en'}" lang="#{lang}"> <head> @@ -453,21 +434,18 @@ #{content}) unless (fns = node.document.footnotes - @footnotes).empty? @footnotes += fns - # NOTE: kindlegen seems to mangle the <footer> element, so we wrap its content in a div - lines << '<footer> -<div class="chapter-footer"> + lines << '<footer class="chapter-footer"> <div class="footnotes">' fns.each do |footnote| lines << %(<aside id="note-#{footnote.index}" epub:type="footnote"> <p>#{footnote.text}</p> </aside>) end lines << '</div> -</div> </footer>' end lines << '</section>' @@ -477,11 +455,11 @@ end lines << '</body> </html>' - chapter_item.add_content postprocess_xhtml lines * LF + chapter_item.add_content((lines * LF).to_ios) epub_properties = node.attr 'epub-properties' chapter_item.add_property 'svg' if epub_properties&.include? 'svg' # # QUESTION reenable? # #linear 'yes' if i == 0 @@ -854,11 +832,11 @@ def convert_colist(node) lines = ['<div class="callout-list"> <ol>'] num = CALLOUT_START_NUM node.items.each_with_index do |item, i| - lines << %(<li><i class="conum" data-value="#{i + 1}">#{num}</i> #{item.text}</li>) + lines << %(<li><i class="conum" data-value="#{i + 1}">#{num}</i> #{item.text}#{item.content if item.blocks?}</li>) num = num.next end lines << '</ol> </div>' end @@ -1384,11 +1362,10 @@ def prepend_space(value) value ? %( #{value}) : '' end def add_theme_assets(doc) - format = @format workdir = if doc.attr? 'epub3-stylesdir' stylesdir = doc.attr 'epub3-stylesdir' # FIXME: make this work for Windows paths!! if stylesdir.start_with? '/' stylesdir @@ -1402,17 +1379,11 @@ end # TODO: improve design/UX of custom theme functionality, including custom fonts %w[epub3 epub3-css3-only].each do |f| css = load_css_file File.join(workdir, %(#{f}.scss)) - if format == :kf8 - # NOTE: add layer of indirection so Kindle Direct Publishing (KDP) doesn't strip font-related CSS rules - @book.add_item %(styles/#{f}.css), content: %(@import url("#{f}-proxied.css");).to_ios - @book.add_item %(styles/#{f}-proxied.css), content: css.to_ios - else - @book.add_item %(styles/#{f}.css), content: css.to_ios - end + @book.add_item %(styles/#{f}.css), content: css.to_ios end syntax_hl = doc.syntax_highlighter if syntax_hl&.write_stylesheet? doc Dir.mktmpdir do |dir| @@ -1431,19 +1402,16 @@ font_files, font_css = select_fonts load_css_file(File.join(DATA_DIR, 'styles/epub3-fonts.scss')), (doc.attr 'scripts', 'latin') @book.add_item 'styles/epub3-fonts.css', content: font_css.to_ios unless font_files.empty? # NOTE: metadata property in oepbs package manifest doesn't work; must use proprietary iBooks file instead - # (@book.metadata.add_metadata 'meta', 'true')['property'] = 'ibooks:specified-fonts' unless format == :kf8 - unless format == :kf8 - @book.add_optional_file 'META-INF/com.apple.ibooks.display-options.xml', '<?xml version="1.0" encoding="UTF-8"?> + @book.add_optional_file 'META-INF/com.apple.ibooks.display-options.xml', '<?xml version="1.0" encoding="UTF-8"?> <display_options> <platform name="*"> <option name="specified-fonts">true</option> </platform> </display_options>'.to_ios - end font_files.each do |font_file| @book.add_item font_file, content: File.join(DATA_DIR, font_file) end end @@ -1484,12 +1452,10 @@ rescue StandardError => e logger.error %(#{::File.basename doc.attr('docfile')}: error adding cover image. Make sure that :#{image_attr_name}: attribute points to a valid image file. #{e}) return nil end - return nil if @format == :kf8 - unless !image_attrs.empty? && (width = image_attrs['width']) && (height = image_attrs['height']) width = 1050 height = 1600 end @@ -1520,13 +1486,13 @@ </head> <body epub:type="cover"><svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="0 0 #{width} #{height}" preserveAspectRatio="xMidYMid meet"> <image width="#{width}" height="#{height}" xlink:href="#{image_href}"/> </svg></body> -</html>).to_ios +</html>) - @book.add_ordered_item %(#{name}.xhtml), content: content, id: name + @book.add_ordered_item %(#{name}.xhtml), content: content.to_ios, id: name end def get_frontmatter_files(doc, workdir) if doc.attr? 'epub3-frontmatterdir' fmdir = doc.attr 'epub3-frontmatterdir' @@ -1557,11 +1523,11 @@ result = nil get_frontmatter_files(doc, workdir).each do |front_matter| front_matter_content = ::File.read front_matter front_matter_file = File.basename front_matter, '.html' - item = @book.add_ordered_item "#{front_matter_file}.xhtml", content: (postprocess_xhtml front_matter_content) + item = @book.add_ordered_item "#{front_matter_file}.xhtml", content: front_matter_content.to_ios item.add_property 'svg' if SVG_IMG_SNIFF_RX =~ front_matter_content # Store link to first frontmatter page result = item if result.nil? front_matter_content.scan IMAGE_SRC_SCAN_RX do @@ -1613,12 +1579,12 @@ <link rel="stylesheet" type="text/css" href="styles/epub3.css"/> <link rel="stylesheet" type="text/css" href="styles/epub3-css3-only.css" media="(min-device-width: 0px)"/> </head> <body> <section class="chapter"> -<header> -<div class="chapter-header"><h1 class="chapter-title"><small class="subtitle">#{doc.attr 'toc-title'}</small></h1></div> +<header class="chapter-header"> +<h1 class="chapter-title"><small class="subtitle">#{doc.attr 'toc-title'}</small></h1> </header> <nav epub:type="toc" id="toc">)] lines << (nav_level items, [depth, 0].max) lines << '</nav>' @@ -1736,76 +1702,10 @@ load_paths = [File.dirname(filename)] sass_engine = Sass::Engine.new template, syntax: :scss, cache: false, load_paths: load_paths, style: :compressed sass_engine.render end - def postprocess_xhtml(content) - return content.to_ios unless @format == :kf8 - - # TODO: convert regular expressions to constants - content - .gsub(/<img([^>]+) style="width: (\d\d)%;"/, '<img\1 style="width: \2%; height: \2%;"') - .gsub(%r{<script type="text/javascript">.*?</script>\n?}m, '') - .to_ios - end - - def build_kindlegen_command - unless @kindlegen_path.nil? - logger.debug %(Using ebook-kindlegen-path attribute: #{@kindlegen_path}) - return [@kindlegen_path] - end - - unless (result = ENV.fetch('KINDLEGEN', nil)).nil? - logger.debug %(Using KINDLEGEN env variable: #{result}) - return [result] - end - - begin - require 'kindlegen' unless defined? ::Kindlegen - result = ::Kindlegen.command.to_s - logger.debug %(Using KindleGen from gem: #{result}) - [result] - rescue LoadError => e - logger.debug %(#{e}; Using KindleGen from PATH) - [%(kindlegen#{::Gem.win_platform? ? '.exe' : ''})] - end - end - - def distill_epub_to_mobi(epub_file, target, compress) - mobi_file = ::File.basename target.sub(EPUB_EXTENSION_RX, '.mobi') - compress_flag = KINDLEGEN_COMPRESSION[if compress - compress.empty? ? '1' : compress.to_s - else - '0' - end] - - argv = build_kindlegen_command + ['-dont_append_source', compress_flag, '-o', mobi_file, epub_file].compact - begin - # This duplicates Kindlegen.run, but we want to override executable - out, err, res = Open3.capture3(*argv) do |r| - r.force_encoding 'UTF-8' if ::Gem.win_platform? && r.respond_to?(:force_encoding) - end - rescue Errno::ENOENT => e - raise 'Unable to run KindleGen. Either install the kindlegen gem or place `kindlegen` executable on PATH or set KINDLEGEN environment variable with path to it', - cause: e - end - - out.each_line do |line| - log_line line - end - err.each_line do |line| - log_line line - end - - output_file = ::File.join ::File.dirname(epub_file), mobi_file - if res.success? - logger.debug %(Wrote MOBI to #{output_file}) - else - logger.error %(KindleGen failed to write MOBI to #{output_file}) - end - end - def build_epubcheck_command unless @epubcheck_path.nil? logger.debug %(Using ebook-epubcheck-path attribute: #{@epubcheck_path}) return [@epubcheck_path] end @@ -1873,65 +1773,33 @@ def role_valid_class?(role) role.is_a? String end end - class NumericIdGenerator - def initialize - @counter = 1 - end - - # @param node [Asciidoctor::AbstractNode] - # @return [void] - def generate_id(node) - if node.chapter? || node.is_a?(Asciidoctor::Document) - node.id = %(_generated_id_#{@counter}) - @counter += 1 - end - - # Recurse - node.blocks.each do |subnode| - # dlist contains array of *arrays* of blocks, so just skip them - generate_id subnode if subnode.is_a?(Asciidoctor::AbstractBlock) - end - end - end - Extensions.register do if (document = @document).backend == 'epub3' document.set_attribute 'listing-caption', 'Listing' # TODO: bw theme for CodeRay document.set_attribute 'pygments-style', 'bw' unless document.attr? 'pygments-style' document.set_attribute 'rouge-style', 'bw' unless document.attr? 'rouge-style' - case (ebook_format = document.attributes['ebook-format']) - when 'epub3', 'kf8' - # all good - when 'mobi' - ebook_format = document.attributes['ebook-format'] = 'kf8' - else - # QUESTION: should we display a warning? - ebook_format = document.attributes['ebook-format'] = 'epub3' - end - document.attributes[%(ebook-format-#{ebook_format})] = '' + # Backward compatibility for documents that were created before we dropped MOBI support + document.set_attribute 'ebook-format', 'epub3' + document.set_attribute 'ebook-format-epub3', '' # Enable generation of section ids because we use them for chapter filenames document.set_attribute 'sectids' treeprocessor do process do |doc| - if ebook_format == 'kf8' - # Kindlegen doesn't support unicode ids - NumericIdGenerator.new.generate_id doc - else - # :sectids: doesn't generate id for top-level section (why?), do it manually - doc.id = Section.generate_id(doc.first_section&.title || doc.attr('docname') || 'document', doc) if doc.id.nil_or_empty? + # :sectids: doesn't generate id for top-level section (why?), do it manually + doc.id = Section.generate_id(doc.first_section&.title || doc.attr('docname') || 'document', doc) if doc.id.nil_or_empty? - if (preamble = doc.blocks[0]) && preamble.context == :preamble && preamble.id.nil_or_empty? - # :sectids: doesn't generate id for preamble (because it is not a section), do it manually - preamble.id = Section.generate_id(preamble.title || 'preamble', doc) - end + if (preamble = doc.blocks[0]) && preamble.context == :preamble && preamble.id.nil_or_empty? + # :sectids: doesn't generate id for preamble (because it is not a section), do it manually + preamble.id = Section.generate_id(preamble.title || 'preamble', doc) end + nil end end end end