module Webgen::ContentProcessor::Tikz

Uses LaTeX and the TikZ library for creating images from LaTeX code.

Public Class Methods

call(context) click to toggle source

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

cache_data(context) click to toggle source

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
cache_file(tex_file) click to toggle source

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
cache_usable?(context, tex_file, image_file) click to toggle source

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(context, cwd, tex_file, basename, ext) click to toggle source

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(cmd, cwd, context) { |status, stdout, stderr| ... } click to toggle source

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
prepare_options(context) click to toggle source

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(context, tex_file) click to toggle source

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
use_cache_or_compile(context) click to toggle source

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