lib/gollum/tex.rb in gollum-2.1.10 vs lib/gollum/tex.rb in gollum-2.2.0
- old
+ new
@@ -1,366 +1,14 @@
-require 'fileutils'
-require 'shellwords'
-require 'tmpdir'
-require 'posix/spawn'
-require 'base64'
+require 'escape_utils'
module Gollum
module Tex
- class Error < StandardError; end
+ TEX_URL = "http://www.mathtran.org/cgi-bin/toy/"
+ TEX_SIZES = { :inline => 2, :block => 4 }
- extend POSIX::Spawn
-
- Template = <<-EOS
-\\documentclass[11pt]{article}
-\\pagestyle{empty}
-\\setlength{\\topskip}{0pt}
-\\setlength{\\parindent}{0pt}
-\\setlength{\\abovedisplayskip}{0pt}
-\\setlength{\\belowdisplayskip}{0pt}
-
-\\usepackage{geometry}
-
-\\usepackage{amsfonts}
-\\usepackage{amsmath}
-
-\\newsavebox{\\snippetbox}
-\\newlength{\\snippetwidth}
-\\newlength{\\snippetheight}
-\\newlength{\\snippetdepth}
-\\newlength{\\pagewidth}
-\\newlength{\\pageheight}
-\\newlength{\\pagemargin}
-
-\\begin{lrbox}{\\snippetbox}%
-\$%s\$
-\\end{lrbox}
-
-\\settowidth{\\snippetwidth}{\\usebox{\\snippetbox}}
-\\settoheight{\\snippetheight}{\\usebox{\\snippetbox}}
-\\settodepth{\\snippetdepth}{\\usebox{\\snippetbox}}
-
-\\setlength\\pagemargin{4pt}
-
-\\setlength\\pagewidth\\snippetwidth
-\\addtolength\\pagewidth\\pagemargin
-\\addtolength\\pagewidth\\pagemargin
-
-\\setlength\\pageheight\\snippetheight
-\\addtolength{\\pageheight}{\\snippetdepth}
-\\addtolength\\pageheight\\pagemargin
-\\addtolength\\pageheight\\pagemargin
-
-\\newwrite\\foo
-\\immediate\\openout\\foo=\\jobname.dimensions
- \\immediate\\write\\foo{snippetdepth = \\the\\snippetdepth}
- \\immediate\\write\\foo{snippetheight = \\the\\snippetheight}
- \\immediate\\write\\foo{snippetwidth = \\the\\snippetwidth}
- \\immediate\\write\\foo{pagewidth = \\the\\pagewidth}
- \\immediate\\write\\foo{pageheight = \\the\\pageheight}
- \\immediate\\write\\foo{pagemargin = \\the\\pagemargin}
-\\closeout\\foo
-
-\\geometry{paperwidth=\\pagewidth,paperheight=\\pageheight,margin=\\pagemargin}
-
-\\begin{document}%
-\\usebox{\\snippetbox}%
-\\end{document}
- EOS
-
- class << self
- attr_accessor :latex_path
+ def self.to_html(tex, type = :inline)
+ tex_uri = EscapeUtils.escape_uri(tex)
+ tex_alt = EscapeUtils.escape_html(tex)
+ %{<img src="#{TEX_URL}?D=#{TEX_SIZES[type]};tex=#{tex}" alt="#{tex_alt}">}
end
-
- self.latex_path = 'pdflatex'
-
- def self.check_dependencies!
- return if @dependencies_available
-
- if `which pdflatex` == ""
- raise Error, "`pdflatex` command not found"
- end
-
- if `which gs` == ""
- raise Error, "`gs` command not found"
- end
-
- if `which pnmcrop` == ""
- raise Error, "`pnmcrop` command not found"
- end
-
- if `which pnmpad` == ""
- raise Error, "`pnmpad` command not found"
- end
-
- if `which pnmscale` == ""
- raise Error, "`pnmscale` command not found"
- end
-
- if `which ppmtopgm` == ""
- raise Error, "`ppmtopgm` command not found"
- end
-
- if `which pnmgamma` == ""
- raise Error, "`pnmgamma` command not found"
- end
-
- if `which pnmtopng` == ""
- raise Error, "`pnmtopng` command not found"
- end
-
- @dependencies_available = true
- end
-
- # Render the formula and calculate the correct alignment
- # for the image in the html.
- #
- # This is a ruby implementation of the Perl version described
- # at http://tex.stackexchange.com/questions/44486/pixel-perfect-vertical-alignment-of-image-rendered-tex-snippets
- #
- # The main caveat is that rendering takes quite a bit of processing power,
- # which can make the page load slowly if it has to render each time.
- # For this reason, the method caches the rendered formula in `/tmp` for reduced
- # loading time in subsequent loads.
- #
- # @param formula the tex formula to render
- # @param with_properties, if true it returns an array with a base64
- # string with the image, and the alignment values for the image.
- # Otherwise it returns the binary image.
- def self.render_formula(formula, with_properties=false)
- check_dependencies!
-
- render_antialias_bits = 4
- render_oversample = 4
- display_oversample = 4
- gamma = 0.3
- if !with_properties
- display_oversample = 1
- gamma = 0.5
- end
-
- oversample = render_oversample * display_oversample
- render_dpi = 96*1.2 * 72.27/72 * oversample # This is 1850.112 dpi.
-
-
- # Cache rendered formula and returned cached version if it exists
-
- # First look for the .cache directory in the home folder
- cache_dir = ::File.expand_path("~/.cache")
- if not ::File.exists?(cache_dir) or not ::File.directory?(cache_dir)
- ::Dir.mkdir(cache_dir)
- end
-
- # Check that the gollum directory exists inside the cache dir
- cache_dir = ::File.join(cache_dir, "gollum")
- if not ::File.exists?(cache_dir) or not ::File.directory?(cache_dir)
- ::Dir.mkdir(cache_dir)
- end
-
- # Check for the formula in the cache dir
- hash = Digest::SHA1.hexdigest(formula)
- cache_file = ::File.join(cache_dir, "tex-#{hash}")
-
- if ::File.exists?(cache_file)
- width, height, align, base64 = ::File.open(cache_file, 'rb') { |io| io.read }.split(",")
-
- if with_properties
- return width, height, align, base64
- else
- return Base64.decode64(base64)
- end
- end
-
- Dir.mktmpdir('tex') do |path|
- file = ::File.join(path, "formula")
-
- # --- Write TeX source and compile to PDF.Write snippet into template
- ::File.open(file + ".tex", 'w') { |f| f.write(Template % formula) }
-
- result = sh_chdir path, "pdflatex",
- "-halt-on-error",
- "-output-directory=#{path}",
- "-output-format=pdf",
- "#{file}.tex",
- ">#{file}.err 2>&1"
-
-
-
- # --- Convert PDF to PNM using Ghostscript.
- sh "gs",
- "-q -dNOPAUSE -dBATCH",
- "-dTextAlphaBits=#{render_antialias_bits}",
- "-dGraphicsAlphaBits=#{render_antialias_bits}",
- "-r#{render_dpi}",
- "-sDEVICE=pnmraw",
- "-sOutputFile=#{file}.pnm",
- "#{file}.pdf"
-
-
- img_width, img_height = pnm_width_height(file + ".pnm")
-
-
- # --- Read dimensions file written by TeX during processing.
- #
- # Example of file contents:
- # snippetdepth = 6.50009pt
- # snippetheight = 13.53899pt
- # snippetwidth = 145.4777pt
- # pagewidth = 153.4777pt
- # pageheight = 28.03908pt
- # pagemargin = 4.0pt
- dimensions = {}
- ::File.open(file + ".dimensions").readlines.each_with_index do |line, i|
- if line =~ /^(\S+)\s+=\s+(-?[0-9\.]+)pt$/
- dimensions[$1] = Float($2) / 72.27 * render_dpi
- else
- raise Error, "#{file}.dimensions: invalid line: #{i}"
- end
- end
-
- # --- Crop bottom, then measure how much was cropped.
- sh "pnmcrop -white -bottom #{file}.pnm >#{file}.bottomcrop.pnm"
- #raise Error, "`pnmcrop` command failed: #{result}" unless ::File.exist?(file + ".bottomcrop.pnm")
-
- img_width_bottomcrop, img_height_bottomcrop = pnm_width_height("#{file}.bottomcrop.pnm")
- bottomcrop = img_height - img_height_bottomcrop
-
- # --- Crop top and sides, then measure how much was cropped from the top.
- sh "pnmcrop -white #{file}.bottomcrop.pnm > #{file}.crop.pnm"
- #raise Error, "`pnmcrop` command failed: #{result}" unless ::File.exist?(file + ".crop.pnm")
-
- cropped_img_width, cropped_img_height = pnm_width_height("#{file}.crop.pnm")
- topcrop = img_height_bottomcrop - cropped_img_height
-
- # --- Pad image with specific values on all four sides, in preparation for
- # downsampling.
-
- # Calculate bottom padding.
- snippet_depth = Integer(dimensions["snippetdepth"] + dimensions["pagemargin"] + 0.5) - bottomcrop
- padded_snippet_depth = round_up(snippet_depth, oversample)
- increase_snippet_depth = padded_snippet_depth - snippet_depth
- bottom_padding = increase_snippet_depth
-
- # --- Next calculate top padding, which depends on bottom padding.
-
- padded_img_height = round_up(cropped_img_height + bottom_padding,
- oversample)
- top_padding = padded_img_height - (cropped_img_height + bottom_padding)
-
-
- # --- Calculate left and right side padding. Distribute padding evenly.
-
- padded_img_width = round_up(cropped_img_width, oversample)
- left_padding = Integer((padded_img_width - cropped_img_width) / 2.0)
- right_padding = (padded_img_width - cropped_img_width) - left_padding
-
-
- # --- Pad the final image.
- result = sh "pnmpad",
- "-white",
- "-bottom=#{bottom_padding}",
- "-top=#{top_padding}",
- "-left=#{left_padding}",
- "-right=#{right_padding}",
- "#{file}.crop.pnm",
- ">#{file}.pad.pnm"
-
- # --- Sanity check of final size.
- final_pnm_width, final_pnm_height = pnm_width_height(file + ".pad.pnm")
- raise Error, "#{final_pnm_width} is not a multiple of #{oversample}" unless final_pnm_width % oversample == 0
-
- raise "#{final_pnm_height} is not a multiple of #{oversample}" unless final_pnm_height % oversample == 0
-
- # --- Convert PNM to PNG.
-
- final_png_width = final_pnm_width / render_oversample
- final_png_height = final_pnm_height / render_oversample
-
- result = sh "cat #{file}.pad.pnm",
- "| ppmtopgm",
- "| pnmscale -reduce #{render_oversample}",
- "| pnmgamma #{gamma}",
- "| pnmtopng -compression 9",
- "> #{file}.png"
-
- raise Error, "Conversion to png failed: #{result}" unless ::File.exist?(file + ".png")
-
- # Calculate html properties
- html_img_width = final_png_width / display_oversample
- html_img_height = final_png_height / display_oversample
- html_img_vertical_align = sprintf("%.0f", -padded_snippet_depth / oversample)
- png_data_base64 = Base64.encode64(::File.open("#{file}.png") { |io| io.read }).chomp
-
- ::File.open(cache_file, 'w') { |f| f.write(%{#{html_img_width},#{html_img_height},#{html_img_vertical_align},#{png_data_base64}}) }
- if with_properties
- return html_img_width, html_img_height, html_img_vertical_align, png_data_base64
- else
- ::File.read(file + ".png")
- end
- end
- end
-
- private
- def self.sh_chdir(path, *args)
- origcommand = args * " "
- return if origcommand == ""
-
- command = origcommand
- command.gsub! /(["\\])/, "\\$1"
- command = %{/bin/sh -c "(#{command}) 2>&1"}
-
- pid = spawn command, :chdir => path
-
- result = Process::waitpid(pid)
- exit_value = Integer($? >> 8), signal_num = Integer($? & 127), dumped_core = Integer($? & 128)
- raise Error, "Failed #{result}: #{origcommand}. Exit value = #{exit_value}. Signal Num = #{signal_num}. Dumped core = #{dumped_core}" unless $?.success?
-
- return result
- end
-
- def self.sh(*args)
- origcommand = args * " "
- return if origcommand == ""
-
- command = origcommand
- command.gsub! /(["\\])/, "\\$1"
- command = %{/bin/sh -c "(#{command}) 2>&1"}
-
- pid = spawn command
- #pid = spawn *args
- result = Process::waitpid(pid)
- exit_value = $? >> 8, signal_num = $? & 127, dumped_core = $? & 128
- raise Error, "Failed #{result}: #{origcommand}. Exit value = #{exit_value}. Signal Num = #{signal_num}. Dumped core = #{dumped_core}" unless $?.success?
-
- return result
- end
-
- def self.round_up(num, mod)
- num + (num % mod == 0 ? 0 : (mod - (num % mod)))
- end
-
- def self.pnm_width_height(filename)
- raise Error, "#{filename} is not a .pnm file" if filename !~ /\.pnm$/
-
- width = nil, height = nil
- ::File.open(filename) do |file|
- # Read first line
- line = file.gets
- begin
- line = file.gets # Read next line, skipping comments
- end while line && line =~ /^#/
-
- if line =~ /^(\d+)\s+(\d+)$/
- width = Integer($1)
- height = Integer($2)
- else
- raise Error, "#{filename}: couldn't read image size"
- end
- end
-
- raise Error, "#{filename}: couldn't read image size" unless width && height
-
- return width, height
- end
-
end
end