From 084a1868051c2c7136ea3160f027dbf3a31fff98 Mon Sep 17 00:00:00 2001 From: Kenshi Muto Date: Sun, 14 Oct 2018 00:25:19 +0900 Subject: [PATCH] improve imgmath function. Closes #868 --- lib/review/configure.rb | 9 +++ lib/review/epubmaker.rb | 141 ++++++++++++++++++++++++++++++++++++++ lib/review/htmlbuilder.rb | 40 ++++++++--- lib/review/webmaker.rb | 7 ++ test/test_htmlbuilder.rb | 4 +- 5 files changed, 191 insertions(+), 10 deletions(-) diff --git a/lib/review/configure.rb b/lib/review/configure.rb index e1413c8e9..612f0b1aa 100644 --- a/lib/review/configure.rb +++ b/lib/review/configure.rb @@ -89,6 +89,15 @@ def self.values 'makeindex_dic' => nil, 'makeindex_mecab' => true, 'makeindex_mecab_opts' => '-Oyomi' + }, + 'imgmath_options' => { + 'format' => 'png', + 'converter' => 'pdfcrop', # dvipng | pdfcrop + 'pdfcrop_cmd' => 'pdfcrop --hires %i %o', + 'preamble_file' => nil, + 'fontsize' => 12, + 'lineheight' => 12 * 1.2, + 'pdfcrop_pixelize_cmd' => 'pdftocairo -png -f %p -l %p %i %o' } ] conf.maker = nil diff --git a/lib/review/epubmaker.rb b/lib/review/epubmaker.rb index 632c0d47a..3143f6093 100644 --- a/lib/review/epubmaker.rb +++ b/lib/review/epubmaker.rb @@ -23,6 +23,7 @@ require 'rexml/streamlistener' require 'epubmaker' require 'review/epubmaker/reviewheaderlistener' +require 'shellwords' module ReVIEW class EPUBMaker @@ -125,6 +126,9 @@ def produce(yamlfile, bookname = nil) call_hook('hook_afterbody', basetmpdir) copy_backmatter(basetmpdir) + if @config['imgmath'] && File.exist?("#{math_dir}/__IMGMATH_BODY__.tex") + make_math_images(math_dir) + end call_hook('hook_afterbackmatter', basetmpdir) ## push contents in basetmpdir into @producer @@ -267,6 +271,7 @@ def build_body(basetmpdir, yamlfile) book.config = @config @converter = ReVIEW::Converter.new(book, ReVIEW::HTMLBuilder.new) @compile_errors = nil + book.parts.each do |part| if part.name.present? if part.file? @@ -602,5 +607,141 @@ def check_image_size(basetmpdir, maxpixels, allow_exts = nil) true end + + def default_imgmath_preamble + <<-EOB +\\documentclass[uplatex]{jsarticle} +\\usepackage[deluxe,uplatex]{otf} +\\usepackage[T1]{fontenc} +\\usepackage{textcomp} +\\usepackage{lmodern} +\\usepackage[dvipdfmx]{graphicx} +\\usepackage[dvipdfmx,table]{xcolor} +\\usepackage[utf8]{inputenc} +\\usepackage{ascmac} +\\usepackage{float} +\\usepackage{alltt} +\\usepackage{amsmath} +\\usepackage{amssymb} +\\usepackage{amsfonts} +\\usepackage{anyfontsize} +\\usepackage{bm} +\\pagestyle{empty} + EOB + end + + def make_math_images(math_dir) + fontsize = @config['imgmath_options']['fontsize'].to_f + lineheight = @config['imgmath_options']['lineheight'].to_f + + texsrc = default_imgmath_preamble + if @config['imgmath_options']['preamble_file'] && File.readable?(@config['imgmath_options']['preamble_file']) + texsrc = File.read(@config['imgmath_options']['preamble_file']) + end + + texsrc << <<-EOB +\\begin{document} +\\fontsize{#{fontsize}}{#{lineheight}}\\selectfont +\\input{__IMGMATH_BODY__} +\\end{document} +EOB + + math_dir = File.realpath(math_dir) + Dir.mktmpdir do |tmpdir| + FileUtils.cp([File.join(math_dir, '__IMGMATH_BODY__.tex'), + File.join(math_dir, '__IMGMATH_BODY__.map')], + tmpdir) + tex_path = File.join(tmpdir, '__IMGMATH__.tex') + File.write(tex_path, texsrc) + + begin + case @config['imgmath_options']['converter'] + when 'pdfcrop' + make_math_images_pdfcrop(tmpdir, tex_path, math_dir) + when 'dvipng' + make_math_images_dvipng(tmpdir, tex_path, math_dir) + end + rescue CompileError + FileUtils.cp([tex_path, + File.join(File.dirname(tex_path), '__IMGMATH__.log')], + math_dir) + error "LaTeX math compile error. See #{math_dir}/__IMGMATH__.log for details." + end + end + FileUtils.rm_f([File.join(math_dir, '__IMGMATH_BODY__.tex'), + File.join(math_dir, '__IMGMATH_BODY__.map')]) + end + + def make_math_images_pdfcrop(dir, tex_path, math_dir) + Dir.chdir(dir) do + dvi_path = '__IMGMATH__.dvi' + pdf_path = '__IMGMATH__.pdf' + out, status = Open3.capture2e(*[@config['texcommand'], @config['texoptions'].shellsplit, tex_path].flatten.compact) + if !status.success? || (!File.exist?(dvi_path) && !File.exist?(pdf_path)) + raise CompileError + end + if File.exist?(dvi_path) + out, status = Open3.capture2e(*[@config['dvicommand'], @config['dvioptions'].shellsplit, dvi_path].flatten.compact) + if !status.success? || !File.exist?(pdf_path) + warn "error in #{@config['dvicommand']}. Error log:\n#{out}" + raise CompileError + end + end + + File.open('__IMGMATH_BODY__.map') do |f| + page = 0 + f.each_line do |key| + page += 1 + key.chomp! + args = @config['imgmath_options']['pdfcrop_cmd'].shellsplit + args.map! do |m| + m.sub('%i', pdf_path). + sub('%o', '__IMGMATH__pdfcrop.pdf') + end + out, status = Open3.capture2e(*args) + unless status.success? + warn "error in pdfcrop. Error log:\n#{out}" + raise CompileError + end + + args = @config['imgmath_options']['pdfcrop_pixelize_cmd'].shellsplit + args.map! do |m| + m.sub('%i', '__IMGMATH__pdfcrop.pdf'). + sub('%o', File.join(math_dir, "_gen_#{key}.#{@config['imgmath_options']['format']}")). + sub('%O', File.join(math_dir, "_gen_#{key}")). + sub('%p', page.to_s) + end + out, status = Open3.capture2e(*args) + unless status.success? + warn "error in pdf pixelizing. Error log:\n#{out}" + raise CompileError + end + end + end + end + end + + def make_math_images_dvipng(dir, tex_path, math_dir) + Dir.chdir(dir) do + dvi_path = '__IMGMATH__.dvi' + out, status = Open3.capture2e(*[@config['texcommand'], @config['texoptions'].shellsplit, tex_path].flatten.compact) + if !status.success? || !File.exist?(dvi_path) + raise CompileError + end + + File.open('__IMGMATH_BODY__.map') do |f| + page = 0 + f.each_line do |key| + page += 1 + key.chomp! + out, status = Open3.capture2e('dvipng', '-T', 'tight', '-z', '9', '-p', page.to_s, '-l', page.to_s, '-o', File.join(math_dir, "_gen_#{key}.png"), dvi_path) + unless status.success? + warn "error in dvipng. Error log:\n#{out}" + raise CompileError + end + end + end + end + end end end diff --git a/lib/review/htmlbuilder.rb b/lib/review/htmlbuilder.rb index 1a29f92cc..dffa0292a 100644 --- a/lib/review/htmlbuilder.rb +++ b/lib/review/htmlbuilder.rb @@ -549,14 +549,19 @@ def texequation(lines) elsif @book.config['imgmath'] math_str = "\\begin{equation*}\n" + unescape(lines.join("\n")) + "\n\\end{equation*}\n" key = Digest::SHA256.hexdigest(math_str) - math_dir = "./#{@book.config['imagedir']}/_review_math" + math_dir = File.join(@book.config['imagedir'], '_review_math') Dir.mkdir(math_dir) unless Dir.exist?(math_dir) - img_path = "./#{math_dir}/_gen_#{key}.png" - make_math_image(math_str, img_path) - puts %Q() + img_path = File.join(math_dir, "_gen_#{key}.#{@book.config['imgmath_options']['format']}") + if @book.config.check_version('2', exception: false) + make_math_image(math_str, img_path) + puts %Q() + else + defer_math_image(math_str, img_path, key) + puts %Q(#{escape(lines.join(' '))}) + end else print '
'
-        puts lines.join("\n")
+        puts escape(lines.join("\n"))
         puts '
' end puts '' @@ -924,11 +929,16 @@ def inline_m(str) elsif @book.config['imgmath'] math_str = '$' + str + '$' key = Digest::SHA256.hexdigest(str) - math_dir = "./#{@book.config['imagedir']}/_review_math" + math_dir = File.join(@book.config['imagedir'], '_review_math') Dir.mkdir(math_dir) unless Dir.exist?(math_dir) - img_path = "./#{math_dir}/_gen_#{key}.png" - make_math_image(math_str, img_path) - %Q() + img_path = File.join(math_dir, "_gen_#{key}.#{@book.config['imgmath_options']['format']}") + if @book.config.check_version('2', exception: false) + make_math_image(math_str, img_path) + %Q() + else + defer_math_image(math_str, img_path, key) + %Q(#{escape(str)}) + end else %Q(#{escape(str)}) end @@ -1192,7 +1202,19 @@ def olnum(num) @ol_num = num.to_i end + def defer_math_image(str, path, key) + # for Re:VIEW >3 + File.open(File.join(File.dirname(path), '__IMGMATH_BODY__.tex'), 'a+') do |f| + f.puts str + f.puts '\\clearpage' + end + File.open(File.join(File.dirname(path), '__IMGMATH_BODY__.map'), 'a+') do |f| + f.puts key + end + end + def make_math_image(str, path, fontsize = 12) + # Re:VIEW 2 compatibility fontsize2 = (fontsize * 1.2).round.to_i texsrc = <<-EOB \\documentclass[12pt]{article} diff --git a/lib/review/webmaker.rb b/lib/review/webmaker.rb index 474b415d0..3839182c1 100644 --- a/lib/review/webmaker.rb +++ b/lib/review/webmaker.rb @@ -123,10 +123,17 @@ def generate_html_files(yamlfile) copy_resources(@config['fontdir'], "#{@path}/fonts", @config['font_ext']) end + def clean_mathdir + if @config['imgmath'] && File.exist?("#{@config['imagedir']}/_review_math") + FileUtils.rm_rf("#{@config['imagedir']}/_review_math") + end + end + def build_body(basetmpdir, _yamlfile) base_path = Pathname.new(@basedir) builder = ReVIEW::HTMLBuilder.new @converter = ReVIEW::Converter.new(@book, builder) + clean_mathdir @book.parts.each do |part| if part.name.present? if part.file? diff --git a/test/test_htmlbuilder.rb b/test/test_htmlbuilder.rb index 6f5c373bb..739e07ea7 100644 --- a/test/test_htmlbuilder.rb +++ b/test/test_htmlbuilder.rb @@ -1080,7 +1080,7 @@ def test_texequation FileUtils.mkdir_p(File.join(dir, 'images')) expected = <<-EOB
- +p \\land \\bm{P} q
EOB tmpio = $stderr @@ -1096,12 +1096,14 @@ def test_texequation end def test_texequation_fail + # Re:VIEW 3 never fail on defer mode. This test is only for Re:VIEW 2. return true if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM return true unless system('latex -version 1>/dev/null 2>/dev/null') mktmpbookdir('catalog.yml' => "CHAPS:\n - ch01.re\n", 'ch01.re' => "= test\n\n//texequation{\np \\land \\bm{P}} q\n//}\n") do |dir, book, _files| @book = book @book.config = @config + @config['review_version'] = 2 @config['imgmath'] = true @chapter = Book::Chapter.new(@book, 1, '-', nil, StringIO.new) location = Location.new(nil, nil)