# encoding: UTF-8 require_relative 'spine_item_processor' require_relative 'font_icon_map' module Asciidoctor module Epub3 #WordJoiner = [8288].pack 'U*' WordJoiner = [65279].pack 'U*' # Public: The main converter for the epub3 backend that handles packaging the # EPUB3 or KF8 publication file. class Converter include ::Asciidoctor::Converter include ::Asciidoctor::Writer register_for 'epub3' def initialize backend, opts super basebackend 'html' outfilesuffix '.epub' # dummy outfilesuffix since it may be .mobi htmlsyntax 'xml' @validate = false @extract = false end def convert spine_doc, name = nil @validate = true if spine_doc.attr? 'ebook-validate' @extract = true if spine_doc.attr? 'ebook-extract' Packager.new spine_doc, (spine_doc.references[:spine_items] || [spine_doc]), spine_doc.attributes['ebook-format'].to_sym end # FIXME we have to package in write because we don't have access to target before this point def write packager, target # NOTE we use dirname of target since filename is calculated automatically packager.package validate: @validate, extract: @extract, to_dir: (::File.dirname target) nil end end # Public: The converter for the epub3 backend that converts the individual # content documents in an EPUB3 publication. class ContentConverter include ::Asciidoctor::Converter register_for 'epub3-xhtml5' WordJoiner = Epub3::WordJoiner EOL = "\n" NoBreakSpace = ' ' ThinNoBreakSpace = ' ' RightAngleQuote = '›' XmlElementRx = /<\/?.+?>/ CharEntityRx = /&#(\d{2,5});/ NamedEntityRx = /&([A-Z]+);/ UppercaseTagRx = /<(\/)?([A-Z]+)>/ FromHtmlSpecialCharsMap = { '<' => '<', '>' => '>', '&' => '&' } FromHtmlSpecialCharsRx = /(?:#{FromHtmlSpecialCharsMap.keys * '|'})/ ToHtmlSpecialCharsMap = { '&' => '&', '<' => '<', '>' => '>' } ToHtmlSpecialCharsRx = /[#{ToHtmlSpecialCharsMap.keys.join}]/ OpenParagraphTagRx = /^

/ CloseParagraphTagRx = /<\/p>$/ def initialize backend, opts super basebackend 'html' outfilesuffix '.xhtml' htmlsyntax 'xml' @xrefs_used = ::Set.new @icon_names = [] end def convert node, name = nil if respond_to?(name ||= node.node_name) send name, node else warn %(conversion missing in epub3 backend for #{name}) end end # TODO aggregate authors of spine document into authors attribute(s) on main document def navigation_document node, spine doctitle_sanitized = ((node.doctitle sanitize: true) || (node.attr 'untitled-label')).gsub WordJoiner, '' lines = [%( #{doctitle_sanitized}

#{doctitle_sanitized}

) lines * EOL end def document node docid = node.id if (doctitle = node.doctitle) doctitle_sanitized = (node.doctitle sanitize: :sgml).gsub WordJoiner, '' if doctitle.include? ': ' title, _, subtitle = doctitle.rpartition ': ' else # HACK until we get proper handling of title-only in CSS title = '' subtitle = doctitle end else # HACK until we get proper handling of title-only in CSS title = '' subtitle = node.attr 'untitled-label' end subtitle_formatted = subtitle.gsub(WordJoiner, '').split(' ').map {|w| %(#{w}) } * ' ' title_upper = title.upcase # FIXME make this uppercase routine more intelligent, less fragile subtitle_formatted_upper = subtitle_formatted.upcase .gsub(UppercaseTagRx) { %(<#{$1}#{$2.downcase}>) } .gsub(NamedEntityRx) { %(&#{$1.downcase};) } author = node.attr 'author' username = node.attr 'username', 'default' # FIXME needs to resolve to the imagesdir of the spine document, not this document #imagesdir = (node.attr 'imagesdir', '.').chomp '/' #imagesdir = (imagesdir == '.' ? nil : %(#{imagesdir}/)) imagesdir = 'images/' mark_last_paragraph node content = node.content # NOTE must run after content is resolved # NOTE pubtree requires icon CSS to be repeated inside (or in a linked stylesheet); perhaps create dynamic CSS file? icon_css = unless @icon_names.empty? icon_defs = @icon_names.map {|name| %(.i-#{name}::before { content: "#{FontIconMap[name.tr('-', '_').to_sym]}"; }) } * EOL %( ) end # NOTE kindlegen seems to mangle the
element, so we wrap its content in a div lines = [%( #{doctitle_sanitized} #{icon_css}
#{icon_css && (icon_css.sub '