# Blosxom Plugin: latex_render
# Author(s): Carlos Falquez <1.054572@gmail.com>
# Based on code from the latexrender Perl Package by Benjamin Zeiss, Steve Mayer and Alex Gittens
# Version: 1.0
# Documentation: See the bottom of this file or type: perldoc latex_render
package latex_render;
## Configurable Variables ##
# IMPORTANT: You MUST set this variables right in order for the plugin to work correctly ------
# prepend this to the returned img url
my $url_picture_path = 'http://www.myserver.net/user/blog/img/latex';
# where to store the pictures
my $picture_path = '/path/to/www/dir/user/blog/img/latex';
# Change this according to your server paths
my $tmp_dir = '/tmp'; # tmp dir
my $latex_path = '/usr/bin/latex'; # path to latex
my $dvips_path = '/usr/bin/dvips'; # path to dvips
my $convert_path = '/usr/bin/convert'; # path to convert
my $identify_path = '/usr/bin/identify'; # path to identify
# you only need to set and uncomment this if your webserver has funny non-standard
# paths and it is causing you trouble, or if you would like to use your own binaries
#$ENV{"PATH"} = '/path/to/my/bindir:'.$ENV{"PATH"};
# Not so important: You can tinker with this if you like --------------------------------------
# define your tex markup, default is
my $math_tag = 'math';
# convert arguments, use to tweak your image output
my %def_math_args = ();
$def_math_args{'density'} = 200;
$def_math_args{'trim'} = '';
$def_math_args{'negate'} = '';
# install extarticle class if you wish to have smaller font sizes
my $latexclass = 'article';
# some more parameters for good measure
my $image_format = 'png'; # change to gif if you prefer
my $font_size = 24; # font size
my $xsize_limit = 800; # img x size limit
my $ysize_limit = 600; # img y size limit
my $string_length_limit = 800; # text limit
# Customize this if you want other fonts or whatever
my $latex_head = "\\documentclass[$font_size" . "pt]{$latexclass}\n"
. "\\usepackage[latin1]{inputenc}\n"
. "\\usepackage{amsmath}\n"
. "\\usepackage{amsfonts}\n"
. "\\usepackage{amssymb}\n"
. "\\pagestyle{empty}\n";
# ---------------------------------------------------------------------------------------------
## Plugin Code ##
use Cwd;
use HTML::Entities;
use File::Copy;
use Digest::MD5 qw(md5_hex);
my $RANDMAX = 1000;
# this most certainly needs to be extended. in the long term it is planned to use
# a positive list for more security. this is hopefully enough for now. i'd be glad
# to receive more bad tags !
my @latex_tags_blacklist = (
"include", "def",
"command", "loop",
"repeat", "open",
"toks", "output",
"input", "catcode",
"\\^\\^", #" name",
"\\every", "\\errhelp",
"\\errorstopmode", "\\scrollmode",
"\\nonstopmode", "\\batchmode",
"\\read", "\\write",
"csname", "\\newhelp",
"\\uppercase", "\\lowercase",
"\\relax", "\\aftergroup",
"\\afterassignment", "\\expandafter",
"\\noexpand", "\\special"
);
# Tries to match the LaTeX Formula given as argument against the
# formula cache. If the picture has not been rendered before, it'll
# renderLatex to try and render it after the security filters are passed
# @param string formula in LaTeX format and a reference to a hash of convert arguments
# @returns an html
tag that points to a picture which contains the requested
# LaTeX formula, or an error message in the format '[ Error : $errorStr ]'
sub getFormulaURL {
my ($latex_formula,$math_args) = @_;
my $convert_args = '';
my $errorStr = '';
for my $k ( keys %def_math_args ) {
$$math_args{$k} and $convert_args .= " -$k $$math_args{$k}"
or $convert_args .= " -$k $def_math_args{$k}";
}
my $formula_hash = md5_hex($latex_formula.$convert_args);
my $filename = $formula_hash.'.'.$image_format;
if ( -f $picture_path .'/'. $filename ) {
my $alt_latex_formula = encode_entities($latex_formula);
#$alt_latex_formula =~ s/\r/
/g; $alt_latex_formula =~ s/\n/
/g;
return "
";
} else {
$errorStr = checkLatexFormula($latex_formula) and return '[ Error : '.$errorStr.' ]';
# security checks assume correct formula, let's render it
unless ( $errorStr = renderLatex($latex_formula,$convert_args) ) {
my $alt_latex_formula = encode_entities($latex_formula);
#$alt_latex_formula =~ s/\r/
/g; $alt_latex_formula =~ s/\n/
/g;
return "
"; #align='middle'>";
}
return '[ Error : '.$errorStr.' ]';
}
}
# wraps a minimalistic LaTeX document around the formula and returns a string
# containing the whole document as string.
# @param string formula in LaTeX format
# @returns minimalistic LaTeX document containing the given formula
sub wrap_formula {
my $latex_formula = shift;
my $string = $latex_head;
$string .= "\\begin{document}\n";
$string .= "\$" . $latex_formula . "\$\n";
$string .= "\\end{document}\n";
return $string;
}
# returns the dimensions of a picture file using 'identify' of the
# imagemagick tools.
# @param string path to a picture
# @returns array containing the picture dimensions
sub getDimensions {
my $filename = shift;
my $output = `$identify_path $filename`;
my @result = split / /, $output;
my @dim = split /x/, $result[2];
$dim[1] = int( $dim[1] );
return @dim;
}
# perform various checks to the LaTeX formula before rendering it
# @param string formula in LaTeX format
# @returns '' when all tests where passed, else returns an error string
sub checkLatexFormula {
my $latex_formula = shift;
my $errorStr = '';
# security filter: reject too long formulas
length($latex_formula) > $string_length_limit and return 'This formula is too long';
# security filter: try to match against LaTeX-Tags Blacklist
foreach (@latex_tags_blacklist) {
$latex_formula =~ m/$_/ and $errorStr .= ' '.$_;
}
$errorStr and return 'Includes blacklisted tag(s)'.$errorStr;
return '';
}
# Renders a LaTeX formula by the using the following method:
# - write the formula into a wrapped tex-file in a temporary directory
# and change to it
# - Create a DVI file using latex (tetex)
# - Convert DVI file to Postscript (PS) using dvips (tetex)
# - convert, trim and add transparancy by using 'convert' from the
# imagemagick package.
# - Save the resulting image to the picture cache directory using an
# md5 hash of the formula plus arguments as filename. Already rendered
# formulas can be found directly this way.
# @param string LaTeX formula and arguments for convert
# @returns '' if the picture has been successfully saved to
# the picture cache directory, else returns an error string
sub renderLatex {
my ($latex_formula,$convert_args) = @_;
my $current_dir = getcwd();
my $tmp_filename = md5_hex( int( rand $RANDMAX ) );
chdir $tmp_dir;
open( my $fp, ">$tmp_filename.tex" );
print $fp (wrap_formula($latex_formula));
close $fp;
# create temporary dvi file
my $command = "$latex_path --interaction=nonstopmode $tmp_filename.tex";
my $status_code = system("$command > /dev/null 2>&1");
if ($status_code) {
cleanTemporaryDirectory($tmp_filename,$current_dir);
return 'Problem running latex (syntax error? file permisions?), so dvi file not created';
}
# convert dvi file to postscript using dvips
$command = "$dvips_path -E $tmp_filename.dvi -o $tmp_filename.ps";
$status_code = system("$command > /dev/null 2>&1");
# imagemagick convert ps to image and trim picture " -transparent \"#FFFFFF\" "
$command = "$convert_path ".$convert_args." $tmp_filename.ps $tmp_filename.$image_format";
$status_code = system("$command > /dev/null 2>&1");
# test picture for correct dimensions
my @dim = getDimensions("$tmp_filename.$image_format");
if ( ( $dim[0] > $xsize_limit ) or ( $dim[1] > $ysize_limit ) ) {
cleanTemporaryDirectory($tmp_filename,$current_dir);
return 'resulting image too large : ' . $dim[0] . 'x' . $dim[1];
}
# copy temporary formula file to cahed formula directory
my $latex_hash = md5_hex($latex_formula.$convert_args);
my $filename = $picture_path . "/$latex_hash.$image_format";
$status_code = copy( "$tmp_filename.$image_format", $filename );
cleanTemporaryDirectory($tmp_filename,$current_dir);
!$status_code and return 'Cannot copy image to pictures directory';
return '';
}
# Cleans the temporary directory
# @param the name of the temporary file and the original working directory
# @returns nothing
sub cleanTemporaryDirectory {
my ($tmp_filename,$dir) = @_;
chdir($tmp_dir);
unlink($tmp_dir.'/'.$tmp_filename.'.tex');
unlink($tmp_dir.'/'.$tmp_filename.'.aux');
unlink($tmp_dir.'/'.$tmp_filename.'.log');
unlink($tmp_dir.'/'.$tmp_filename.'.dvi');
unlink($tmp_dir.'/'.$tmp_filename.'.ps');
unlink($tmp_dir.'/'.$tmp_filename.'.'.$image_format);
chdir($dir);
}
sub replace_tex
{
my $text = shift;
my %math_formula;
study $text;
$text =~ s{ ( <$math_tag\b(.*?)> # save args in $2
\s*((?:.+?\n?)+?)\s*$math_tag> ) # save formula in $3
}{ my $key = md5_hex($1);
my %math_args = ();
if ( my $args = $2 ) {
for my $k ( keys %def_math_args ) {
$args =~ /$k=(\w+)/ and $math_args{$k} = $1;
}
}
$math_formula{$key} = getFormulaURL($3,\%math_args);
$key;
}egmx;
while ( my ($key, $value) = each(%math_formula) ) {
$text =~ s|$key|$value|g;
}
return $text;
}
sub start {
return 1;
}
sub story {
my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
$$body_ref = &replace_tex($$body_ref);
return 1;
}
return 1;
__END__
=head1 NAME
Blosxom Plug-in: latex_render
=head1 SYNOPSIS
Make Blosxom output image files from LaTeX code inside