lib/review/epubmaker.rb in review-5.0.0 vs lib/review/epubmaker.rb in review-5.1.0
- old
+ new
@@ -1,6 +1,6 @@
-# Copyright (c) 2010-2020 Kenshi Muto and Masayoshi Takahashi
+# Copyright (c) 2010-2021 Kenshi Muto and Masayoshi Takahashi
#
# This program is free software.
# You can distribute or modify this program under the terms of
# the GNU LGPL, Lesser General Public License version 2.1.
# For details of the GNU LGPL, see the file "COPYING".
@@ -11,32 +11,37 @@
require 'review/i18n'
require 'review/book'
require 'review/configure'
require 'review/converter'
require 'review/latexbuilder'
-require 'review/yamlloader'
require 'review/version'
require 'review/htmltoc'
require 'review/htmlbuilder'
+require 'review/img_math'
require 'rexml/document'
require 'rexml/streamlistener'
-require 'epubmaker'
+require 'review/call_hook'
+require 'review/epubmaker/producer'
+require 'review/epubmaker/content'
+require 'review/epubmaker/epubv2'
+require 'review/epubmaker/epubv3'
require 'review/epubmaker/reviewheaderlistener'
require 'review/makerhelper'
module ReVIEW
class EPUBMaker
- include ::EPUBMaker
- include REXML
include MakerHelper
+ include ReVIEW::CallHook
def initialize
@producer = nil
@htmltoc = nil
@buildlogtxt = 'build-log.txt'
@logger = ReVIEW.logger
+ @img_math = nil
+ @basedir = nil
end
def error(msg)
@logger.error msg
exit 1
@@ -48,16 +53,10 @@
def log(msg)
@logger.debug(msg)
end
- def load_yaml(yamlfile)
- @producer = Producer.new(@config)
- @producer.load(yamlfile)
- @config = @producer.config
- end
-
def self.execute(*args)
self.new.execute(*args)
end
def parse_opts(args)
@@ -88,21 +87,27 @@
error "#{yamlfile} not found." unless File.exist?(yamlfile)
@config = ReVIEW::Configure.create(maker: 'epubmaker',
yamlfile: yamlfile,
config: cmd_config)
- load_yaml(yamlfile)
+ @producer = ReVIEW::EPUBMaker::Producer.new(@config)
update_log_level
log("Loaded yaml file (#{yamlfile}).")
+ @basedir = File.absolute_path(File.dirname(yamlfile))
produce(yamlfile, exportfile)
end
def update_log_level
if @config['debug']
- @logger.level = Logger::DEBUG
- else
+ if @logger.ttylogger?
+ ReVIEW.logger = nil
+ @logger = ReVIEW.logger(level: 'debug')
+ else
+ @logger.level = Logger::DEBUG
+ end
+ elsif !@logger.ttylogger?
@logger.level = Logger::INFO
end
end
def build_path
@@ -121,10 +126,11 @@
def produce(yamlfile, bookname = nil)
I18n.setup(@config['language'])
bookname ||= @config['bookname']
booktmpname = "#{bookname}-epub"
+ @img_math = ReVIEW::ImgMath.new(@config)
begin
@config.check_version(ReVIEW::VERSION)
rescue ReVIEW::ConfigError => e
warn e.message
end
@@ -133,35 +139,34 @@
FileUtils.rm_f("#{bookname}.epub")
if @config['debug']
FileUtils.rm_rf(booktmpname)
end
- cleanup_mathimg
+ @img_math.cleanup_mathimg
basetmpdir = build_path
begin
log("Created first temporary directory as #{basetmpdir}.")
- call_hook('hook_beforeprocess', basetmpdir)
+ call_hook('hook_beforeprocess', basetmpdir, base_dir: @basedir)
@htmltoc = ReVIEW::HTMLToc.new(basetmpdir)
## copy all files into basetmpdir
copy_stylesheet(basetmpdir)
copy_frontmatter(basetmpdir)
- call_hook('hook_afterfrontmatter', basetmpdir)
+ call_hook('hook_afterfrontmatter', basetmpdir, base_dir: @basedir)
build_body(basetmpdir, yamlfile)
- call_hook('hook_afterbody', basetmpdir)
+ call_hook('hook_afterbody', basetmpdir, base_dir: @basedir)
copy_backmatter(basetmpdir)
- math_dir = "./#{@config['imagedir']}/_review_math"
- if @config['imgmath'] && File.exist?(File.join(math_dir, '__IMGMATH_BODY__.map'))
- make_math_images(math_dir)
+ if @config['math_format'] == 'imgmath'
+ @img_math.make_math_images
end
- call_hook('hook_afterbackmatter', basetmpdir)
+ call_hook('hook_afterbackmatter', basetmpdir, base_dir: @basedir)
## push contents in basetmpdir into @producer
push_contents(basetmpdir)
if @config['epubmaker']['verify_target_images'].present?
@@ -173,11 +178,11 @@
copy_resources('covers', File.join(basetmpdir, @config['imagedir']))
copy_resources('adv', File.join(basetmpdir, @config['imagedir']))
copy_resources(@config['fontdir'], File.join(basetmpdir, 'fonts'), @config['font_ext'])
- call_hook('hook_aftercopyimage', basetmpdir)
+ call_hook('hook_aftercopyimage', basetmpdir, base_dir: @basedir)
@producer.import_imageinfo(File.join(basetmpdir, @config['imagedir']), basetmpdir)
@producer.import_imageinfo(File.join(basetmpdir, 'fonts'), basetmpdir, @config['font_ext'])
check_image_size(basetmpdir, @config['image_maxpixels'], @config['image_ext'])
@@ -186,38 +191,28 @@
if @config['debug'].present?
epubtmpdir = File.join(basetmpdir, booktmpname)
Dir.mkdir(epubtmpdir)
end
log('Call ePUB producer.')
- @producer.produce("#{bookname}.epub", basetmpdir, epubtmpdir)
+ @producer.produce("#{bookname}.epub", basetmpdir, epubtmpdir, base_dir: @basedir)
log('Finished.')
+ @logger.success("built #{bookname}.epub")
rescue ApplicationError => e
raise if @config['debug']
+
error(e.message)
ensure
FileUtils.remove_entry_secure(basetmpdir) unless @config['debug']
end
end
- def call_hook(hook_name, *params)
- filename = @config['epubmaker'][hook_name]
- log("Call #{hook_name}. (#{filename})")
- if filename.present? && File.exist?(filename) && FileTest.executable?(filename)
- if ENV['REVIEW_SAFE_MODE'].to_i & 1 > 0
- warn 'hook is prohibited in safe mode. ignored.'
- else
- system(filename, *params)
- end
- end
- end
-
def verify_target_images(basetmpdir)
@producer.contents.each do |content|
case content.media
when 'application/xhtml+xml'
File.open("#{basetmpdir}/#{content.file}") do |f|
- Document.new(File.new(f)).each_element('//img') do |e|
+ REXML::Document.new(File.new(f)).each_element('//img') do |e|
@config['epubmaker']['force_include_images'].push(e.attributes['src'])
if e.attributes['src'] =~ /svg\Z/i
content.properties.push('svg')
end
end
@@ -235,10 +230,11 @@
@config['epubmaker']['force_include_images'] = @config['epubmaker']['force_include_images'].compact.sort.uniq
end
def copy_images(resdir, destdir, allow_exts = nil)
return nil unless File.exist?(resdir)
+
allow_exts ||= @config['image_ext']
FileUtils.mkdir_p(destdir)
if @config['epubmaker']['verify_target_images'].present?
@config['epubmaker']['force_include_images'].each do |file|
unless File.exist?(file)
@@ -257,19 +253,21 @@
end
end
def copy_resources(resdir, destdir, allow_exts = nil)
return nil unless File.exist?(resdir)
+
allow_exts ||= @config['image_ext']
FileUtils.mkdir_p(destdir)
recursive_copy_files(resdir, destdir, allow_exts)
end
def recursive_copy_files(resdir, destdir, allow_exts)
Dir.open(resdir) do |dir|
dir.each do |fname|
next if fname.start_with?('.')
+
if FileTest.directory?(File.join(resdir, fname))
recursive_copy_files(File.join(resdir, fname), File.join(destdir, fname), allow_exts)
elsif fname =~ /\.(#{allow_exts.join('|')})\Z/i
FileUtils.mkdir_p(destdir)
log("Copy #{resdir}/#{fname} to the temporary directory.")
@@ -296,11 +294,11 @@
@tocdesc = []
basedir = File.dirname(yamlfile)
base_path = Pathname.new(basedir)
book = ReVIEW::Book::Base.new(basedir, config: @config)
- @converter = ReVIEW::Converter.new(book, ReVIEW::HTMLBuilder.new)
+ @converter = ReVIEW::Converter.new(book, ReVIEW::HTMLBuilder.new(img_math: @img_math))
@compile_errors = nil
book.parts.each do |part|
if part.name.present?
if part.file?
@@ -325,23 +323,17 @@
end
def build_part(part, basetmpdir, htmlfile)
log("Create #{htmlfile} from a template.")
File.open(File.join(basetmpdir, htmlfile), 'w') do |f|
- @body = ''
- @body << %Q(<div class="part">\n)
- @body << %Q(<h1 class="part-number">#{h(ReVIEW::I18n.t('part', part.number))}</h1>\n)
- if part.name.strip.present?
- @body << %Q(<h2 class="part-title">#{h(part.name.strip)}</h2>\n)
- end
- @body << %Q(</div>\n)
+ @part_number = part.number
+ @part_title = part.name.strip
+ @body = ReVIEW::Template.generate(path: 'html/_part_body.html.erb', binding: binding)
@language = @producer.config['language']
@stylesheets = @producer.config['stylesheet']
- tmplfile = File.expand_path(template_name, ReVIEW::Template::TEMPLATE_DIR)
- tmpl = ReVIEW::Template.load(tmplfile)
- f.write tmpl.result(binding)
+ f.write ReVIEW::Template.generate(path: template_name, binding: binding)
end
end
def template_name
if @producer.config['htmlversion'].to_i == 5
@@ -434,16 +426,23 @@
end
end
properties
end
- def write_info_body(basetmpdir, _id, filename, ispart = nil, chaptype = nil)
+ def parse_headlines(path)
headlines = []
+
+ File.open(path) do |htmlio|
+ REXML::Document.parse_stream(htmlio, ReVIEWHeaderListener.new(headlines))
+ end
+
+ headlines
+ end
+
+ def write_info_body(basetmpdir, _id, filename, ispart = nil, chaptype = nil)
path = File.join(basetmpdir, filename)
- htmlio = File.new(path)
- Document.parse_stream(htmlio, ReVIEWHeaderListener.new(headlines))
- htmlio.close
+ headlines = parse_headlines(path)
if headlines.empty?
warn "#{filename} is discarded because there is no heading. Use `=[notoc]' or `=[nodisp]' to exclude headlines from the table of contents."
return
end
@@ -478,74 +477,81 @@
end
def push_contents(_basetmpdir)
@htmltoc.each_item do |level, file, title, args|
next if level.to_i > @config['toclevel'] && args[:force_include].nil?
+
log("Push #{file} to ePUB contents.")
- hash = { 'file' => file,
- 'level' => level.to_i,
- 'title' => title,
- 'chaptype' => args[:chaptype] }
+ params = { file: file,
+ level: level.to_i,
+ title: title,
+ chaptype: args[:chaptype] }
if args[:id].present?
- hash['id'] = args[:id]
+ params[:id] = args[:id]
end
if args[:properties].present?
- hash['properties'] = args[:properties].split(' ')
+ params[:properties] = args[:properties].split(' ') # rubocop:disable Style/RedundantArgument
end
if args[:notoc].present?
- hash['notoc'] = args[:notoc]
+ params[:notoc] = args[:notoc]
end
- @producer.contents.push(Content.new(hash))
+ @producer.contents.push(ReVIEW::EPUBMaker::Content.new(**params))
end
end
def copy_stylesheet(basetmpdir)
return if @config['stylesheet'].empty?
+
@config['stylesheet'].each do |sfile|
unless File.exist?(sfile)
- error "#{sfile} is not found."
+ error "stylesheet: #{sfile} is not found."
end
FileUtils.cp(sfile, basetmpdir)
- @producer.contents.push(Content.new('file' => sfile))
+ @producer.contents.push(ReVIEW::EPUBMaker::Content.new(file: sfile))
end
end
+ def copy_static_file(configname, destdir, destfilename: nil)
+ destfilename ||= @config[configname]
+ unless File.exist?(@config[configname])
+ error "#{configname}: #{@config[configname]} is not found."
+ end
+ FileUtils.cp(@config[configname],
+ File.join(destdir, destfilename))
+ end
+
def copy_frontmatter(basetmpdir)
if @config['cover'].present? && File.exist?(@config['cover'])
- FileUtils.cp(@config['cover'],
- File.join(basetmpdir, File.basename(@config['cover'])))
+ copy_static_file('cover', basetmpdir)
end
if @config['titlepage']
if @config['titlefile'].nil?
build_titlepage(basetmpdir, "titlepage.#{@config['htmlext']}")
else
- FileUtils.cp(@config['titlefile'],
- File.join(basetmpdir, "titlepage.#{@config['htmlext']}"))
+ copy_static_file('titlefile', basetmpdir, destfilename: "titlepage.#{@config['htmlext']}")
end
@htmltoc.add_item(1,
"titlepage.#{@config['htmlext']}",
- @producer.res.v('titlepagetitle'),
+ ReVIEW::I18n.t('titlepagetitle'),
chaptype: 'pre')
end
- if @config['originaltitlefile'].present? && File.exist?(@config['originaltitlefile'])
- FileUtils.cp(@config['originaltitlefile'],
- File.join(basetmpdir, File.basename(@config['originaltitlefile'])))
+ if @config['originaltitlefile'].present?
+ copy_static_file('originaltitlefile', basetmpdir)
@htmltoc.add_item(1,
File.basename(@config['originaltitlefile']),
- @producer.res.v('originaltitle'),
+ ReVIEW::I18n.t('originaltitle'),
chaptype: 'pre')
end
- if @config['creditfile'].present? && File.exist?(@config['creditfile'])
- FileUtils.cp(@config['creditfile'],
- File.join(basetmpdir, File.basename(@config['creditfile'])))
+ if @config['creditfile'].present?
+ copy_static_file('creditfile', basetmpdir)
@htmltoc.add_item(1,
File.basename(@config['creditfile']),
- @producer.res.v('credittitle'),
+ ReVIEW::I18n.t('credittitle'),
chaptype: 'pre')
end
true
end
@@ -568,57 +574,46 @@
end
@body << '</div>'
@language = @producer.config['language']
@stylesheets = @producer.config['stylesheet']
- tmplfile = File.expand_path(template_name, ReVIEW::Template::TEMPLATE_DIR)
- tmpl = ReVIEW::Template.load(tmplfile)
- f.write tmpl.result(binding)
+ f.write ReVIEW::Template.generate(path: template_name, binding: binding)
end
end
def copy_backmatter(basetmpdir)
if @config['profile']
- FileUtils.cp(@config['profile'],
- File.join(basetmpdir, File.basename(@config['profile'])))
+ copy_static_file('profile', basetmpdir)
@htmltoc.add_item(1,
File.basename(@config['profile']),
- @producer.res.v('profiletitle'),
+ ReVIEW::I18n.t('profiletitle'),
chaptype: 'post')
end
if @config['advfile']
- FileUtils.cp(@config['advfile'],
- File.join(basetmpdir, File.basename(@config['advfile'])))
+ copy_static_file('advfile', basetmpdir)
@htmltoc.add_item(1,
File.basename(@config['advfile']),
- @producer.res.v('advtitle'),
+ ReVIEW::I18n.t('advtitle'),
chaptype: 'post')
end
if @config['colophon']
- if @config['colophon'].is_a?(String) # FIXME: should let obsolete this style?
- FileUtils.cp(@config['colophon'],
- File.join(basetmpdir, "colophon.#{@config['htmlext']}"))
- else
- filename = File.join(basetmpdir, "colophon.#{@config['htmlext']}")
- File.open(filename, 'w') do |f|
- @producer.colophon(f)
- end
+ if @config['colophon'].is_a?(String)
+ copy_static_file('colophon', basetmpdir, destfilename: "colophon.#{@config['htmlext']}") # override pre-built colophon
end
@htmltoc.add_item(1,
"colophon.#{@config['htmlext']}",
- @producer.res.v('colophontitle'),
+ ReVIEW::I18n.t('colophontitle'),
chaptype: 'post')
end
if @config['backcover']
- FileUtils.cp(@config['backcover'],
- File.join(basetmpdir, File.basename(@config['backcover'])))
+ copy_static_file('backcover', basetmpdir)
@htmltoc.add_item(1,
File.basename(@config['backcover']),
- @producer.res.v('backcovertitle'),
+ ReVIEW::I18n.t('backcovertitle'),
chaptype: 'post')
end
true
end
@@ -640,11 +635,13 @@
pat = '\\.(' + allow_exts.delete_if { |t| %w[ttf woff otf].member?(t.downcase) }.join('|') + ')'
extre = Regexp.new(pat, Regexp::IGNORECASE)
Find.find(basetmpdir) do |fname|
next unless fname.match(extre)
+
img = ImageSize.path(fname)
next if img.width.nil? || img.width * img.height <= maxpixels
+
h = Math.sqrt(img.height * maxpixels / img.width)
w = maxpixels / h
fname.sub!("#{basetmpdir}/", '')
warn "#{fname}: #{img.width}x#{img.height} exceeds a limit. suggeted value is #{w.to_i}x#{h.to_i}"
end