module Webgen::ContentProcessor::Tikz
Uses LaTeX and the TikZ library for creating images from LaTeX code.
Public Class Methods
Process the content with LaTeX to generate a TikZ image.
# File lib/webgen/content_processor/tikz.rb 20 def self.call(context) 21 prepare_options(context) 22 context.content = context.render_block(:name => 'content', 23 :chain => [context.website.tree[context['content_processor.tikz.template']]]) 24 context.content = File.binread(use_cache_or_compile(context)) 25 context 26 end
Private Class Methods
The data that should be written to the cache file.
# File lib/webgen/content_processor/tikz.rb 114 def self.cache_data(context) 115 ("" << context.content << 116 "\n" << context['content_processor.tikz.resolution'].to_s << 117 "\n" << context['content_processor.tikz.transparent'].to_s << 118 "\n" << context['content_processor.tikz.libraries'].to_s << 119 "\n" << context['content_processor.tikz.opts'].to_s << 120 "\n" << context['content_processor.tikz.template'].to_s).force_encoding('BINARY') 121 end
Return the name of the cache file for the give LaTeX file.
# File lib/webgen/content_processor/tikz.rb 125 def self.cache_file(tex_file) 126 tex_file.sub(/\.tex$/, ".cache") 127 end
Check if the content of the LaTeX document or the used options have changed.
# File lib/webgen/content_processor/tikz.rb 107 def self.cache_usable?(context, tex_file, image_file) 108 cfile = cache_file(tex_file) 109 File.exist?(image_file) && File.exist?(cfile) && File.binread(cfile) == cache_data(context) 110 end
Compile the LaTeX document stored in the Context
and convert the resulting PDF to the correct output image format specified by context (the extension needs to include the dot).
Returns the path to the created image.
# File lib/webgen/content_processor/tikz.rb 64 def self.compile(context, cwd, tex_file, basename, ext) 65 render_res, output_res = context['content_processor.tikz.resolution'].split(' ') 66 67 engine = context['content_processor.tikz.engine'] 68 File.write(tex_file, context.content) 69 execute("#{engine} -shell-escape -interaction=nonstopmode -halt-on-error #{basename}.tex", cwd, context) do |_status, stdout, stderr| 70 errors = (stdout+stderr).scan(/^!(.*\n.*)/).join("\n") 71 raise Webgen::RenderError.new("Error while parsing TikZ picture commands with #{engine}: #{errors}", 72 'content_processor.tikz', context.dest_node, context.ref_node) 73 end 74 75 if context['content_processor.tikz.transparent'] && ext =~ /\.png/i 76 cmd = "gs -dSAFER -dBATCH -dNOPAUSE -r#{render_res} -sDEVICE=pngalpha -dGraphicsAlphaBits=4 " + 77 "-dTextAlphaBits=4 -sOutputFile=#{basename}#{ext} #{basename}.pdf" 78 elsif ext =~ /\.svg/i 79 # some installations of ghostscript (`gs`) also have a svg output device, but pdf2svg 80 # is a safer bet. 81 cmd = "pdf2svg #{basename}.pdf #{basename}.svg" 82 else 83 cmd = "convert -density #{render_res} #{basename}.pdf #{basename}#{ext}" 84 end 85 execute(cmd, cwd, context) 86 87 # resizing doesn't really make sense on a vector graphic. 88 unless ext =~ /\.svg/i 89 if render_res != output_res 90 _status, stdout, _stderr = execute("identify #{basename}#{ext}", cwd, context) 91 width, height = stdout.scan(/\s\d+x\d+\s/).first.strip.split('x').collect do |s| 92 s.to_f * output_res.to_f / render_res.to_f 93 end 94 execute("convert -resize #{width}x#{height} #{basename}#{ext} #{basename}#{ext}", cwd, context) 95 end 96 end 97 end
Execute the command cmd
in the working directory cwd
.
If the exit status is not zero, yields to the given block if one is given, or raises an error otherwise.
Returns [status, stdout, stderr]
# File lib/webgen/content_processor/tikz.rb 136 def self.execute(cmd, cwd, context) 137 status, stdout, stderr = systemu(cmd, :cwd => cwd) 138 if status.exitstatus != 0 139 if block_given? 140 yield(status, stdout, stderr) 141 else 142 raise Webgen::RenderError.new("Error while running a command for a TikZ picture: #{stdout + "\n" + stderr}", 143 'content_processor.tikz', context.dest_node, context.ref_node) 144 end 145 end 146 [status, stdout, stderr] 147 end
Collect the necessary options and save them in the context object.
# File lib/webgen/content_processor/tikz.rb 29 def self.prepare_options(context) 30 %w[content_processor.tikz.resolution content_processor.tikz.transparent 31 content_processor.tikz.libraries content_processor.tikz.opts 32 content_processor.tikz.template content_processor.tikz.engine].each do |opt| 33 context[opt] = context.content_node[opt] || context.website.config[opt] 34 end 35 context['data'] = context.content 36 end
Save cache data so that it is possible to use it the next time.
# File lib/webgen/content_processor/tikz.rb 101 def self.save_cache(context, tex_file) 102 File.write(cache_file(tex_file), cache_data(context)) 103 end
Checks whether a cached version exists and if it is usable. If not, the LaTeX document is compiled.
# File lib/webgen/content_processor/tikz.rb 41 def self.use_cache_or_compile(context) 42 cwd = context.website.tmpdir('content_processor.tikz') 43 FileUtils.mkdir_p(cwd) 44 45 tex_file = File.join(cwd, context.dest_node.dest_path.tr('/', '_').sub(/\..*?$/, '.tex')) 46 basename = File.basename(tex_file, '.tex') 47 ext = File.extname(context.dest_node.dest_path) 48 image_file = tex_file.sub(/\.tex$/, ext) 49 50 if !cache_usable?(context, tex_file, image_file) 51 compile(context, cwd, tex_file, basename, ext) 52 save_cache(context, tex_file) 53 end 54 55 image_file 56 end