From 084a1868051c2c7136ea3160f027dbf3a31fff98 Mon Sep 17 00:00:00 2001 From: Kenshi Muto Date: Sun, 14 Oct 2018 00:25:19 +0900 Subject: [PATCH 1/8] 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) From b367ef7845af64a45548684db935374be92c37c6 Mon Sep 17 00:00:00 2001 From: Kenshi Muto Date: Sun, 14 Oct 2018 11:14:08 +0900 Subject: [PATCH 2/8] move mathimg logic to makerhelper. avoid making images multiply --- lib/review/configure.rb | 4 +- lib/review/epubmaker.rb | 147 ++----------------------------------- lib/review/makerhelper.rb | 151 +++++++++++++++++++++++++++++++++++++- lib/review/webmaker.rb | 13 ++-- 4 files changed, 166 insertions(+), 149 deletions(-) diff --git a/lib/review/configure.rb b/lib/review/configure.rb index 612f0b1aa..212faa57b 100644 --- a/lib/review/configure.rb +++ b/lib/review/configure.rb @@ -95,8 +95,8 @@ def self.values 'converter' => 'pdfcrop', # dvipng | pdfcrop 'pdfcrop_cmd' => 'pdfcrop --hires %i %o', 'preamble_file' => nil, - 'fontsize' => 12, - 'lineheight' => 12 * 1.2, + 'fontsize' => 10, + 'lineheight' => 10 * 1.2, 'pdfcrop_pixelize_cmd' => 'pdftocairo -png -f %p -l %p %i %o' } ] diff --git a/lib/review/epubmaker.rb b/lib/review/epubmaker.rb index 3143f6093..00f843dbd 100644 --- a/lib/review/epubmaker.rb +++ b/lib/review/epubmaker.rb @@ -23,12 +23,13 @@ require 'rexml/streamlistener' require 'epubmaker' require 'review/epubmaker/reviewheaderlistener' -require 'shellwords' +require 'review/makerhelper' module ReVIEW class EPUBMaker include ::EPUBMaker include REXML + include MakerHelper def initialize @producer = nil @@ -104,10 +105,8 @@ def produce(yamlfile, bookname = nil) if @config['debug'] FileUtils.rm_rf(booktmpname) end - math_dir = "./#{@config['imagedir']}/_review_math" - if @config['imgmath'] && Dir.exist?(math_dir) - FileUtils.rm_rf(math_dir) - end + + cleanup_mathimg basetmpdir = build_path begin @@ -126,6 +125,8 @@ def produce(yamlfile, bookname = nil) call_hook('hook_afterbody', basetmpdir) copy_backmatter(basetmpdir) + + math_dir = "./#{@config['imagedir']}/_review_math" if @config['imgmath'] && File.exist?("#{math_dir}/__IMGMATH_BODY__.tex") make_math_images(math_dir) end @@ -607,141 +608,5 @@ 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/makerhelper.rb b/lib/review/makerhelper.rb index 114346811..e4261d9c7 100644 --- a/lib/review/makerhelper.rb +++ b/lib/review/makerhelper.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2012-2017 Yuto HAYAMIZU, Kenshi Muto +# Copyright (c) 2012-2018 Yuto HAYAMIZU, Kenshi Muto # # This program is free software. # You can distribute or modify this program under the terms of @@ -8,6 +8,7 @@ require 'pathname' require 'fileutils' require 'yaml' +require 'shellwords' module ReVIEW module MakerHelper @@ -64,5 +65,153 @@ def copy_images_to_dir(from_dir, to_dir, options = {}) image_files end module_function :copy_images_to_dir + + def cleanup_mathimg + math_dir = "./#{@config['imagedir']}/_review_math" + if @config['imgmath'] && Dir.exist?(math_dir) + FileUtils.rm_rf(math_dir) + end + 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! + if File.exist?(File.join(math_dir, "_gen_#{key}.#{@config['imgmath_options']['format']}")) + # made already + next + end + + 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/webmaker.rb b/lib/review/webmaker.rb index 3839182c1..ee7b6afad 100644 --- a/lib/review/webmaker.rb +++ b/lib/review/webmaker.rb @@ -20,10 +20,12 @@ require 'review/tocprinter' require 'review/version' require 'erb' +require 'review/makerhelper' module ReVIEW class WEBMaker include ERB::Util + include MakerHelper attr_accessor :config, :basedir @@ -71,10 +73,7 @@ def build_path end def remove_old_files(path) - math_dir = "./#{@config['imagedir']}/_review_math" - if @config['imgmath'] && Dir.exist?(math_dir) - FileUtils.rm_rf(math_dir) - end + cleanup_mathimg FileUtils.rm_rf(path) end @@ -116,6 +115,11 @@ def generate_html_files(yamlfile) build_body(@path, yamlfile) copy_backmatter(@path) + math_dir = "./#{@config['imagedir']}/_review_math" + if @config['imgmath'] && File.exist?("#{math_dir}/__IMGMATH_BODY__.tex") + make_math_images(math_dir) + end + copy_images(@config['imagedir'], "#{@path}/#{@config['imagedir']}") copy_resources('covers', "#{@path}/#{@config['imagedir']}") @@ -133,7 +137,6 @@ 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? From b2fc13c9a0d217dc3f3e82e6211c85b32dbe4723 Mon Sep 17 00:00:00 2001 From: Kenshi Muto Date: Sun, 14 Oct 2018 11:58:17 +0900 Subject: [PATCH 3/8] add class attr to allow to customize. --- lib/review/configure.rb | 2 +- lib/review/htmlbuilder.rb | 4 ++-- lib/review/makerhelper.rb | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/review/configure.rb b/lib/review/configure.rb index 212faa57b..9c367782a 100644 --- a/lib/review/configure.rb +++ b/lib/review/configure.rb @@ -97,7 +97,7 @@ def self.values 'preamble_file' => nil, 'fontsize' => 10, 'lineheight' => 10 * 1.2, - 'pdfcrop_pixelize_cmd' => 'pdftocairo -png -f %p -l %p %i %o' + 'pdfcrop_pixelize_cmd' => 'pdftocairo -png -r 90 -f %p -l %p -singlefile %i %O' } ] conf.maker = nil diff --git a/lib/review/htmlbuilder.rb b/lib/review/htmlbuilder.rb index dffa0292a..222031a79 100644 --- a/lib/review/htmlbuilder.rb +++ b/lib/review/htmlbuilder.rb @@ -557,7 +557,7 @@ def texequation(lines) puts %Q() else defer_math_image(math_str, img_path, key) - puts %Q(#{escape(lines.join(' '))}) + puts %Q(#{escape(lines.join(' '))}) end else print '
'
@@ -937,7 +937,7 @@ def inline_m(str)
           %Q()
         else
           defer_math_image(math_str, img_path, key)
-          %Q(#{escape(str)})
+          %Q(#{escape(str)})
         end
       else
         %Q(#{escape(str)})
diff --git a/lib/review/makerhelper.rb b/lib/review/makerhelper.rb
index e4261d9c7..8705f6e93 100644
--- a/lib/review/makerhelper.rb
+++ b/lib/review/makerhelper.rb
@@ -125,6 +125,8 @@ def make_math_images(math_dir)
             make_math_images_pdfcrop(tmpdir, tex_path, math_dir)
           when 'dvipng'
             make_math_images_dvipng(tmpdir, tex_path, math_dir)
+          else
+            error "unknown math converter error. imgmath_options/converter parameter should be 'pdfcrop' or 'dvipng'."
           end
         rescue CompileError
           FileUtils.cp([tex_path,

From 2c18072fffcd2a5545d5aa987df1ec11ff6200f4 Mon Sep 17 00:00:00 2001
From: Kenshi Muto 
Date: Sun, 14 Oct 2018 11:59:39 +0900
Subject: [PATCH 4/8] fix test

---
 test/test_htmlbuilder.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/test_htmlbuilder.rb b/test/test_htmlbuilder.rb
index 739e07ea7..efe0f9d5b 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 +p \\land \\bm{P} q
EOB tmpio = $stderr From 91543a4739507e05b5b7d7d8c3d1e0a39a3e1c5e Mon Sep 17 00:00:00 2001 From: Kenshi Muto Date: Sun, 14 Oct 2018 12:53:47 +0900 Subject: [PATCH 5/8] add note about imgmath --- doc/format.ja.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/doc/format.ja.md b/doc/format.ja.md index f2f98a376..a13de5fbf 100644 --- a/doc/format.ja.md +++ b/doc/format.ja.md @@ -575,7 +575,83 @@ LaTeX の式を挿入するには、`//texequation{ 〜 //}` を使います。 インライン命令では `@{〜}` を使います。インライン命令の式中に「}」を含む場合、`\}` とエスケープする必要があることに注意してください(`{` はエスケープ不要)。 -LaTeX の数式が正常に整形されるかどうかは処理系に依存します。たとえば TeX PDF であれば問題なく利用できるでしょうが、EPUB では MathML 変換を有効にしても妥当な結果にならないことがあります。確実を期すならば、画像で表現するほうが適切です。 +LaTeX の数式が正常に整形されるかどうかは処理系に依存します。LaTeX を利用する PDFMaker では問題なく利用できます。 + +EPUBMaker および WEBMaker では、MathML に変換する方法と、画像化する方法のどちらかを選べます。 + +### MathML の場合 +MathML ライブラリをインストールしておきます(`gem install math_ml`)。 + +さらに config.yml に以下のように指定します。 + +``` +mathml: true +``` + +なお、MathML で正常に表現されるかどうかは、ビューアやブラウザに依存します。 + +### 画像化の場合 + +LaTeX を内部で呼び出し、外部ツールを使って画像化する方法です。画像化された数式は、`images/_review_math` フォルダに配置されます。 + +TeXLive などの LaTeX 環境が必要です。必要に応じて config.yml の `texcommand`、`texoptions`、`dvicommand`、`dvioptions` のパラメータを調整します。 + +さらに、画像化するための外部ツールも用意します。現在、以下の2つのやり方をサポートしています。 + +- `pdfcrop`:TeXLive に収録されている `pdfcrop` コマンドを使用して数式部分を切り出し、さらに PDF から画像化します。デフォルトでは画像化には Poppler ライブラリに収録されている `pdftocairo` コマンドを使用します(コマンドラインで利用可能であれば、別のツールに変更することもできます)。 +- `dvipng`:[dvipng](https://ctan.org/pkg/dvipng) を使用します。OS のパッケージまたは `tlmgr install dvipng` でインストールできます。数式中に日本語は使えません。 + +config.yml で以下のように設定すると、 + +``` +imgmath: true +``` + +デフォルト値として以下が使われます。 + +``` +imgmath_options: + # 使用する画像拡張子。通常は「png」か「svg」(svgの場合は、pdfcrop_pixelize_cmdの-pngも-svgにする) + format: png + # 変換手法。pdfcrop または dvipng + converter: pdfcrop + # プリアンブルの内容を上書きするファイルを指定する(デフォルトはupLaTeX+jsarticle.clsを前提とした、lib/review/makerhelper.rbのdefault_imgmath_preambleメソッドの内容) + preamble_file: null + # 基準のフォントサイズ + fontsize: 10 + # 基準の行間 + lineheight: 12 + # pdfcropコマンドのコマンドライン。プレースホルダは + # %i: 入力ファイル、%o: 出力ファイル + pdfcrop_cmd: "pdfcrop --hires %i %o" + # PDFから画像化するコマンドのコマンドライン。プレースホルダは + # %i: 入力ファイル、%o: 出力ファイル、%O: 出力ファイルから拡張子を除いたもの + # %p: 対象ページ番号 + pdfcrop_pixelize_cmd: pdftocairo -png -r 90 -f %p -l %p -singlefile %i %O" +``` + +たとえば SVG を利用するには、次のようにします。 + +``` +imgmath: true +imgmath_options: + format: svg + pdfcrop_pixelize_cmd: pdftocairo -svg -r 90 -f %p -l %p -singlefile %i %o" +``` + +pdfcrop_pixelize_cmd に指定するコマンドは、1ページあたり1数式からなる複数ページの PDF のファイル名を `%i` プレースホルダで受け取り、`%p` プレースホルダのページ数に基づいて `%o`(拡張子あり)または `%O`(拡張子なし)の画像ファイルに書き出す、という仕組みになっています。 + +単一のページの処理を前提とする `sips` コマンドや `magick` コマンドを使う場合、入力 PDF から指定のページを抽出するラッパースクリプトを別途用意する必要があります。 + +Re:VIEW 2 以前の dvipng の設定に合わせるには、次のようにします。 + +``` +imgmath: true +imgmath_options: + converter: dvipng + fontsize: 12 + lineheight: 14.3 +``` ## 字下げの制御 From 31243de01222d58a29e02551e3afc69a4a46b558 Mon Sep 17 00:00:00 2001 From: Kenshi Muto Date: Sun, 14 Oct 2018 14:41:55 +0900 Subject: [PATCH 6/8] support single page converter also --- .rubocop.yml | 2 +- doc/format.ja.md | 24 +++++++++++++++---- lib/review/configure.rb | 5 +++- lib/review/makerhelper.rb | 50 ++++++++++++++++++++++++++++++--------- 4 files changed, 64 insertions(+), 17 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index fb5e000cd..32de86645 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -427,7 +427,7 @@ Metrics/AbcSize: ### shoud be < 25 Metrics/BlockLength: CountComments: false # count full line comments? - Max: 50 + Max: 62 Exclude: - 'Rakefile' - '**/*.rake' diff --git a/doc/format.ja.md b/doc/format.ja.md index a13de5fbf..653c399d7 100644 --- a/doc/format.ja.md +++ b/doc/format.ja.md @@ -627,7 +627,13 @@ imgmath_options: # PDFから画像化するコマンドのコマンドライン。プレースホルダは # %i: 入力ファイル、%o: 出力ファイル、%O: 出力ファイルから拡張子を除いたもの # %p: 対象ページ番号 - pdfcrop_pixelize_cmd: pdftocairo -png -r 90 -f %p -l %p -singlefile %i %O" + pdfcrop_pixelize_cmd: "pdftocairo -png -r 90 -f %p -l %p -singlefile %i %O" + # pdfcrop_pixelize_cmdが複数ページの処理に対応していない場合に単ページ化するか + extract_singlepage: null + # 単ページ化するコマンドのコマンドライン + pdfextract_cmd: "pdfjam -q --outfile %o %i %p" + # dvipngコマンドのコマンドライン + dvipng_cmd: "dvipng -T tight -z 9 -p %p -l %p -o %o %i" ``` たとえば SVG を利用するには、次のようにします。 @@ -636,12 +642,22 @@ imgmath_options: imgmath: true imgmath_options: format: svg - pdfcrop_pixelize_cmd: pdftocairo -svg -r 90 -f %p -l %p -singlefile %i %o" + pdfcrop_pixelize_cmd: "pdftocairo -svg -r 90 -f %p -l %p -singlefile %i %o" ``` -pdfcrop_pixelize_cmd に指定するコマンドは、1ページあたり1数式からなる複数ページの PDF のファイル名を `%i` プレースホルダで受け取り、`%p` プレースホルダのページ数に基づいて `%o`(拡張子あり)または `%O`(拡張子なし)の画像ファイルに書き出す、という仕組みになっています。 +デフォルトでは、pdfcrop_pixelize_cmd に指定するコマンドは、1ページあたり1数式からなる複数ページの PDF のファイル名を `%i` プレースホルダで受け取り、`%p` プレースホルダのページ数に基づいて `%o`(拡張子あり)または `%O`(拡張子なし)の画像ファイルに書き出す、という仕組みになっています。 -単一のページの処理を前提とする `sips` コマンドや `magick` コマンドを使う場合、入力 PDF から指定のページを抽出するラッパースクリプトを別途用意する必要があります。 +単一のページの処理を前提とする `sips` コマンドや `magick` コマンドを使う場合、入力 PDF から指定のページを抽出するように `extract_singlepage: true` として挙動を変更します。単一ページの抽出はデフォルトで TeXLive の `pdfjam` コマンドが使われます。 + +``` +imgmath: true +imgmath_options: + extract_singlepage: true + # ImageMagickを利用する例 + pdfcrop_pixelize_cmd: "magick -density 200x200 %i %o" + # sipsを利用する例 + pdfcrop_pixelize_cmd: "sips -s format png --out %o %i" +``` Re:VIEW 2 以前の dvipng の設定に合わせるには、次のようにします。 diff --git a/lib/review/configure.rb b/lib/review/configure.rb index 9c367782a..44f4c3ef8 100644 --- a/lib/review/configure.rb +++ b/lib/review/configure.rb @@ -94,10 +94,13 @@ def self.values 'format' => 'png', 'converter' => 'pdfcrop', # dvipng | pdfcrop 'pdfcrop_cmd' => 'pdfcrop --hires %i %o', + 'extract_singlepage' => nil, + 'pdfextract_cmd' => 'pdfjam -q --outfile %o %i %p', 'preamble_file' => nil, 'fontsize' => 10, 'lineheight' => 10 * 1.2, - 'pdfcrop_pixelize_cmd' => 'pdftocairo -png -r 90 -f %p -l %p -singlefile %i %O' + 'pdfcrop_pixelize_cmd' => 'pdftocairo -png -r 90 -f %p -l %p -singlefile %i %O', + 'dvipng_cmd' => 'dvipng -T tight -z 9 -p %p -l %p -o %o %i' } ] conf.maker = nil diff --git a/lib/review/makerhelper.rb b/lib/review/makerhelper.rb index 8705f6e93..dfa16c7be 100644 --- a/lib/review/makerhelper.rb +++ b/lib/review/makerhelper.rb @@ -155,6 +155,19 @@ def make_math_images_pdfcrop(dir, tex_path, math_dir) end end + 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 + pdf_path = '__IMGMATH__pdfcrop.pdf' + pdf_path2 = pdf_path + File.open('__IMGMATH_BODY__.map') do |f| page = 0 f.each_line do |key| @@ -165,20 +178,28 @@ def make_math_images_pdfcrop(dir, tex_path, math_dir) next end - 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 + if @config['imgmath_options']['extract_singlepage'] + # if extract_singlepage = true, split each page + args = @config['imgmath_options']['pdfextract_cmd'].shellsplit + + args.map! do |m| + m.sub('%i', pdf_path). + sub('%o', "__IMGMATH__pdfcrop_p#{page}.pdf"). + sub('%O', "__IMGMATH__pdfcrop_p#{page}"). + sub('%p', page.to_s) + end + out, status = Open3.capture2e(*args) + unless status.success? + warn "error in pdf extracting. Error log:\n#{out}" + raise CompileError + end + + pdf_path2 = "__IMGMATH__pdfcrop_p#{page}.pdf" end args = @config['imgmath_options']['pdfcrop_pixelize_cmd'].shellsplit args.map! do |m| - m.sub('%i', '__IMGMATH__pdfcrop.pdf'). + m.sub('%i', pdf_path2). 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) @@ -206,7 +227,14 @@ def make_math_images_dvipng(dir, tex_path, math_dir) 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) + args = @config['imgmath_options']['dvipng_cmd'].shellsplit + args.map! do |m| + m.sub('%i', dvi_path). + 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 dvipng. Error log:\n#{out}" raise CompileError From 2693e36264baa9b381934c3a0a1d00fcc4b78ba6 Mon Sep 17 00:00:00 2001 From: Kenshi Muto Date: Sun, 14 Oct 2018 18:45:42 +0900 Subject: [PATCH 7/8] add English document --- doc/format.ja.md | 2 +- doc/format.md | 97 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/doc/format.ja.md b/doc/format.ja.md index 653c399d7..1d6f49aaf 100644 --- a/doc/format.ja.md +++ b/doc/format.ja.md @@ -573,7 +573,7 @@ LaTeX の式を挿入するには、`//texequation{ 〜 //}` を使います。 //} ``` -インライン命令では `@{〜}` を使います。インライン命令の式中に「}」を含む場合、`\}` とエスケープする必要があることに注意してください(`{` はエスケープ不要)。 +インライン命令では `@{〜}` を使います。インライン命令の式中に「}」を含む場合、`\}` とエスケープする必要があることに注意してください(`{` はエスケープ不要)。「インライン命令のフェンス記法」も参照してください。 LaTeX の数式が正常に整形されるかどうかは処理系に依存します。LaTeX を利用する PDFMaker では問題なく利用できます。 diff --git a/doc/format.md b/doc/format.md index 965ccd54e..45d18d0e7 100644 --- a/doc/format.md +++ b/doc/format.md @@ -498,7 +498,6 @@ The rule of finding images is same as image command. //} ``` - ## Quoting Text You can use `//quote{ ... //}` as quotations. @@ -618,6 +617,102 @@ Usage: //} ``` +There is `@{ ... }` for inline (see "Fence notation for inline commands" section also). + +Whether LaTeX formula is correctly displayed or not depends on the processing system. PDFMaker uses LaTeX internally, so there is no problem. + +EPUBMaker and WEBMaker use either MathML transformation or imaging. + +### MathML case +Install MathML library (`gem install math_ml`). + +Specify in config.yml as follows: + +``` +mathml: true +``` + +Whether it is displayed properly in MathML depends on your viewer or browser. + +### imaging case + +This way calls LaTeX internally and images it with an external tool. Image files will be placed in `images/_review_math` folder. + +You need TeXLive or other LaTeX environment. Modify the parameters of `texcommand`,` texoptions`, `dvicommand`,` dvioptions` in config.yml as necessary. + +In addition, external tools for image conversion are also needed. Currently, it supports the following two methods. + +- `pdfcrop`: cut out the formula using `pdfcrop` command (included in TeXLive) and image it. By default, `pdftocairo` command is used (included in Poppler library). You can change it to another tool if available on the command line. +- `dvipng`: it uses [dvipng](https://ctan.org/pkg/dvipng) to cut out and to image. You can install with OS package or `tlmgr install dvipng`. + +By setting in config.yml, + +``` +imgmath: true +``` + +it is set as follows: + +``` +imgmath_options: + # format. png|svg + format: png + # conversion method. pdfcrop|dvipng + converter: pdfcrop + # custom preamble file (default: for upLaTeX+jsarticle.cls, see lib/review/makerhelper.rb#default_imgmath_preamble) + preamble_file: null + # default font size + fontsize: 10 + # default line height + lineheight: 12 + # pdfcrop command. + # %i: filename for input %o: filename for output + pdfcrop_cmd: "pdfcrop --hires %i %o" + # imaging command. + # %i: filename for input %o: filename for output %O: filename for output without the extension + # %p: page number + pdfcrop_pixelize_cmd: "pdftocairo -png -r 90 -f %p -l %p -singlefile %i %O" + # whether to generate a single PDF page for pdfcrop_pixelize_cmd. + extract_singlepage: null + # command line to generate a single PDF page file. + pdfextract_cmd: "pdfjam -q --outfile %o %i %p" + # dvipng command. + dvipng_cmd: "dvipng -T tight -z 9 -p %p -l %p -o %o %i" +``` + +For example, to make SVG: + +``` +imgmath: true +imgmath_options: + format: svg + pdfcrop_pixelize_cmd: "pdftocairo -svg -r 90 -f %p -l %p -singlefile %i %o" +``` + +By default, the command specified in `pdfcrop_pixelize_cmd` takes the filename of multi-page PDF consisting of one formula per page. + +If you want to use the `sips` command or the` magick` command, they can only process a single page, so you need to set `extract_singlepage: true` to extract the specified page from the input PDF. `pdfjam` command (in TeXLive) is used to extract pages. + +``` +imgmath: true +imgmath_options: + extract_singlepage: true + # use ImageMagick + pdfcrop_pixelize_cmd: "magick -density 200x200 %i %o" + # use sips + pdfcrop_pixelize_cmd: "sips -s format png --out %o %i" +``` + +To set the same setting as Re:VIEW 2: + +``` +imgmath: true +imgmath_options: + converter: dvipng + fontsize: 12 + lineheight: 14.3 +``` + ## Spacing `//noindent` is a tag for spacing. From c332f5737bcd77587da6a60ab9a332153b0c1588 Mon Sep 17 00:00:00 2001 From: Kenshi Muto Date: Sun, 14 Oct 2018 19:57:05 +0900 Subject: [PATCH 8/8] example of pdftk usecase, for Windows --- doc/format.ja.md | 2 ++ doc/format.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/doc/format.ja.md b/doc/format.ja.md index 1d6f49aaf..d9f7c7a0d 100644 --- a/doc/format.ja.md +++ b/doc/format.ja.md @@ -653,6 +653,8 @@ imgmath_options: imgmath: true imgmath_options: extract_singlepage: true + # pdfjamの代わりに外部ツールのpdftkを使う場合(Windowsなど) + pdfextract_cmd: "pdftk A=%i cat A%p output %o" # ImageMagickを利用する例 pdfcrop_pixelize_cmd: "magick -density 200x200 %i %o" # sipsを利用する例 diff --git a/doc/format.md b/doc/format.md index 45d18d0e7..d081a1e85 100644 --- a/doc/format.md +++ b/doc/format.md @@ -697,6 +697,8 @@ If you want to use the `sips` command or the` magick` command, they can only pro imgmath: true imgmath_options: extract_singlepage: true + # use pdftk instead of default pdfjam (for Windows) + pdfextract_cmd: "pdftk A=%i cat A%p output %o" # use ImageMagick pdfcrop_pixelize_cmd: "magick -density 200x200 %i %o" # use sips