#!/usr/bin/perl -w # L2P - Convert LaTeX expressions to PNG $version = '1.1.1'; =head1 NAME l2p - create PNG images from LaTeX expressions =head1 SYNOPSIS B [options...] -i '$I$' or B [options...] [I] I contains an expression or expressions in (La)TeX format - one per line. If neither I nor an B<-i> option is given, the expression is read from standard input. =head1 DESCRIPTION Convert expressions in LaTeX format into PNGs =head1 EXAMPLES =over =item l2p -i '$4x^2-7=\cos{2 \pi x}$' -o 'eqn4.png' Produce a PNG image, named 'eqn4.png', of the equation described by the LaTeX expression '$4x^2 - 7 = \cos{2 \pi x}$'. =item l2p -o big_equation.png big_hairy_equation Produce a PNG image, called big_equation.png, from the LaTeX expression contained in the file big_hairy_equation (specifically, it contains '$x=2$'.) Note that this file is NOT a full LaTeX document - use the B<-F> option for that. =item l2p -d 250 -i '$\nabla \cdot \mathbf{D} = \rho$' Produce a PNG image from the LaTeX code given with the B<-i> argument (which happens to be one of Maxwell's equations), at 250 dots per inch. Since we did not specify an output file name with the B<-o> option, the image will be 'eqn.png' (the default). =item l2p -p amssymb -i '$\mho$' -o mho.png Produce a PNG image of the Mho symbol (an upside-down capital omega), saving the image in the file 'mho.png'. We include the amssymb package, which defines that symbol. =item l2p -B 20x30 -i '$\sum_{n=0}^{\infty}\frac{(-\phi^2)^n}{(2n)!}$' -o cosine.png Produce an image of the indicated infinite summation, padded with a border that is 20 pixels on each side horizontally, and 30 pixels each side vertically. The color of this border region will be the same as the rest of the image background. =back =head1 OPTIONS Many options have arguments that may contain characters, like '#' or spaces, that the shell considers special. Be sure to surround all such arguments with single or double quotes, so that the shell understands what is meant. (If unsure, it's always safe to use the quotes.) =over =item B<-i "$latex$"> Argument is an equation/expression in (La)TeX format. In most cases, you will want to enclose the argument in quotes to protect it from shell expansion. =item B<-b "rrggbb"> Background color. There are several ways to specify the color. See the section L, below, for details. =item B<-d dpi> Pixel density at which the equation is rendered, in dots per inch (default 300). An image with a DPI of 600 will have twice as many pixels in each of the x and y directions than an image with a DPI of 300. The effect is different in the normal context of printing, where a higher DPI will leave the text with the same physical size, but with a finer resolution. This is because the physical size of a pixel is not really variable; so to have double the resolution, a symbol in an image must be double the size. =item B<-f "rrggbb"> Foreground color. There are several ways to specify the color. See the section L, below, for details. =item B<-h> Show a help summary. =item B<-o output.png> Name of output file. Default is 'eqn.png'. =item B<-p packagename[,packagename2[,...]]]> Use additional LaTeX/TeX packages. You can specify several, separated by commas. =item B<-B "WIDTHxHEIGHT [color]"> or: B<-B "SIZE [color]"> Pad the resulting image with a border of the indicated size, in pixels. You can optionally specify a color for the border region. By default, the border will be the same color as the rest of the background. (See L below for the format.) =item B<-C> Suppress automatic removal (cleanup) of temporary files. This will be useful if something goes wrong, or if you want to use the intermediate DVI or Postscript renditions. B will tell you which directory contains these files. =item B<-F> Supplied expression is a full LaTeX document, rather than just an expression fragment. Negates the B<-f>, B<-b>, B<-p>, B<-B> and B<-T> options. B: B currently only converts full LaTeX documents that are relatively simple: only one page in length, and with no external dependencies (such as included graphics). If you need to convert a more complex document, you can generate a DVI file with latex like normal, then convert the DVI into a series of PNG images using B from the ImageMagick distribution. See L, or L for more information. =item B<-T> Create an image with a transparent background. =item B<-V> Show version information. =back =head1 COLORS Some options, such as B<-b> and B<-f>, take an argument specifying a color in RGB format. B will decipher most representations, such as: =over =item A hexidecimal triplet. For example, '-f "FF0000" -b "#ffffff"' gives a red foreground on a white background. Case is not important, and the "#" is optional. =item Three decimal whole numbers, in the range of 0 to 255. These must be separated by spaces or punctuation (comma, semicolon or colon). For example, '-b "0 127 255" -f "0,0,0"' is black on a nice bluish background. =item Three fractions between 0 and 1, inclusive. At least one of the three numbers must contain a decimal point (to distinguish this format from the others), and they are separated by space or punctuation. For example, "0.87 .78 .41" is the same as the hex triplet "DEC769", and "0, 1.0, 0" is the color green. (Remember that decimal point. "0, 1, 0" will give you a nearly black color.) =back Note that you may need to put single or double quotes around the color string, to ensure the shell interprets it correctly. =head1 BUGS Error handling is imperfect. Among other things, If a needed LaTeX package is not included, B will silently produce a broken image. On certain platforms, images produced with the B<-T> option (transparent background) may leave pixels at the edges of symbols a mixture of the text color and some background color. This may not look good if the resulting image is put on a differently colored background. A workaround is to give a background color hint with the B<-b> option; the edge pixels will then be a mixture of specified foreground and background colors. =head1 ACKS Thanks to Jesse Merriman (L) for providing a patch that improved transparent background support. Integrated in version 1.1. =head1 COPYRIGHT This software is in the public domain. =head1 AUTHOR Aaron Maxwell (amax@redsymbol.net). Comments, feature requests, and patches are welcome. =cut use File::Temp qw/tempfile tempdir/; use Getopt::Std; # Takes a string and extracts an RGB value from it. # Returns ($r,$g,$b), all values between 0 and 1 if parsing is successful, # otherwise returns undef. # Usage: ($r,$g,$b) = parsergb($string); sub parsergb { my $string=shift; $string =~s/^\s+//; $string =~s/\s+$//; $string =~s/^#//; my(@args,@vals,$arg,$val); # hex triplet format? if($string=~/^[0-9a-fA-F]{6}$/) { @args=(substr($string,0,2), substr($string,2,2), substr($string,4,2)); foreach $arg (@args) { $val=hex($arg)/255; push @vals, $val; } return @vals; } $string =~s/[,:;]/ /g; @args = split /\s+/, $string; if (@args != 3) { return undef; } # 0-255 decimal format? if ($string =~ /^[\d\s]+$/) { foreach $arg (@args) { if ($arg>=0 && $arg<=255) { $val=$arg/255; } else { return undef; } push @vals, $val; } # 0.0-1.0 range format? } elsif ($string =~ /^[\d\.\s]+$/) { foreach $arg (@args) { if($arg>=0 && $arg<=1) { $val = 0+$arg; } else { return undef; } push @vals, $val; } } else { return undef; # unrecognized format! } return @vals; } # norm2hex - convert an RGB color in the form 'r,g,b', 0<=[rgb]<=1, # to a hex triplet. Returns undef if invoked incorrectly. # usage: $hexrgb = norm2hex($normrgb); sub norm2hex { $_=shift; my @vals=split(/,/,$_); scalar(@vals)==3 or return undef; my($val,$hex); foreach $val (@vals) { unless($val>=0 and $val<=1) { return undef; } $hex .= sprintf('%02x',$val*255); } return $hex; } my($pre,$post,$dpi,$eqn,$outfile,$fg,$bg); our($opt_o, # output file name $opt_d, # dpi $opt_i, # in-command-line latex expression $opt_f, # foreground RGB triplet $opt_b, # background RGB triplet $opt_F, # set if input is a full LaTeX document $opt_T, # transparent background $opt_C, # suppress autocleaning of temp files $opt_h, # display help message $opt_p, # additional package(s) $opt_V, # print version info $opt_B, # border $opt_Z, # reserved for hacks ); # check to see if needed software is available my($latex,$dvips,$convert); $latex = `which latex`; chomp $latex; if($latex eq '' or not -X $latex) { print STDERR "Cannot find latex executable. Aborting.\n"; exit(2); } $dvips = `which dvips`; chomp $dvips; if($dvips eq '' or not -X $dvips) { print STDERR "Cannot find dvips executable. Aborting.\n"; exit(2); } $convert = `which convert`; chomp $convert; if($convert eq '' or not -X $convert) { print STDERR "Cannot find convert executable. Aborting.\n"; exit(2); } # process command line opts getopt('odifbpB'); if ($opt_V) { print $version, "\n"; exit(0); } if ($opt_h) { print <<'EOT'; Generate PNG images from LaTeX expressions usage: l2p [options] [file_containing_latex_expressions] or l2p [options] -i '$LaTeX-expression$' Note: Many options will require quotes around their arguments to ensure correct interpretation by the shell. Options: -o output.png Name of output file. Default is 'eqn.png'. -i '$latex$' equation/expression in (La)TeX format -f 'rrggbb' foreground color -b 'rrggbb' background color -d dpi Conversion resolution (default 300) -T Transparent background -p pkg[,pkg2...] use TeX/LaTeX package(s) -C Suppress removal (cleanup) of temporary files -F Input is full LaTeX document, not just fragment -V Show version -B 'geom [color]' Pad image with a border -h Show this help and exit Also see the full documentation (try typing 'perldoc l2p'). EOT exit(0); } $outfile = $opt_o || 'eqn.png'; $dpi = $opt_d || 300; # determine foreground color $fg='0,0,0'; if($opt_f) { my($r,$g,$b) = parsergb($opt_f); if (not defined $r) { print STDERR "Foreground color not in recognizeable format. Reverting to default.\n"; ($r,$g,$b) = (0,0,0); } $fg=join(',',$r,$g,$b); } $bg='1,1,1'; # determine background color if($opt_b) { my($r,$g,$b) = parsergb($opt_b); if (not defined $r) { print STDERR "Background color not in recognizeable format. Reverting to default.\n"; ($r,$g,$b) = (1,1,1); } $bg=join(',',$r,$g,$b); } # deal with transparent background $fuzz = 20; if ($opt_T) { if($opt_b) { # Workaround: with a BG hint, a nonzero fuzz can result in erased symbols $fuzz = 0; } # $bg and $fg must be different for transparency to work my($bR,$bG,$bB) = split(/,/, $bg); my($fR,$fG,$fB) = split(/,/, $fg); my($dR, $dG, $dB) = map { abs($_) } ($fR-$bR, $fG-$bG, $fB-$bB); if($dR<0.1 && $dG<0.1 && $dB<0.1) { $bg = (sqrt($fR**2+$fG**2+$fB**2)>0.5) ? '0,0,0' : '1,1,1'; } } my @packages = ('color'); if ($opt_p) { @packages = (@packages, split(/,/, $opt_p)); } # get expression to render $pre = join "\n", ( '\documentclass{article}', (map { '\usepackage{' . $_ . '}' } @packages), '\definecolor{bg}{rgb}{', $bg, '}', '\definecolor{fg}{rgb}{', $fg, '}', '\pagestyle{empty}', '\pagecolor{bg}', '\begin{document}', '\color{fg}', '\begin{center}', ""); $post = <<'EOT'; \end{center} \end{document} EOT # discover the LaTeX expression to render $eqn=''; if (defined $opt_i) { # expression from command line $eqn = $opt_i . "\n"; } elsif (not $opt_F) { # file/stdin contains LaTeX expression(s) # TODO: rewrite using an expression iterator subroutine while(<>) { next if /^\s*#/ or /^\s*$/; chomp; $eqn .= $_ . "\n"; # If this is line contains a single inline expression, add # an extra newline, so that it renders correctly if (/^\s*\$.*\$\s*$/) { $eqn .= "\n"; } } } if (not $opt_F and $eqn =~ /^\s*$/) { print STDERR <<'EOT'; Did not find a LaTeX expression to render. Perhaps the supplied expression or file is empty, or does not exist. EOT exit(3); } # create a temporary latex file to use my $tempdir = tempdir(CLEANUP=> $opt_C ? 0 : 1) or die "Cannot not make temp dir. Unable to proceed - aborting."; print "Temporary files stored in $tempdir\n" if $opt_C; my $latexfn = $tempdir . "/foo.latex"; if($opt_F) { $source = shift @ARGV; if(not -e $source) { die "LaTeX source ($source) does not exist!"; } elsif (not -r $source) { die "Cannot read file $source."; } if(substr($source,0,1) ne '/') { my $cwd = `pwd`; chomp $cwd; $source = $cwd . '/' . $source; } system("ln -s $source $latexfn"); -e "$latexfn" or die "Unable to create link to source file ($source)"; } else { my $latexfh; open($latexfh,">",$latexfn) or die "could not write to latex temp file"; print $latexfh $pre, $eqn, $post; close($latexfh); } # produce dvi output system("cd $tempdir; $latex -interaction=batchmode $latexfn >/dev/null"); unless (-e "${tempdir}/foo.dvi") { print STDERR <<'EOT'; latex run failed. Perhaps the input is invalid, or a specified package was not found. EOT exit(1); } # convert dvi to ps # the -E option prevents convert from freaking out later my $dvipscmd = "$dvips " . ($opt_F ? "": "-E") . " foo -o 2>/dev/null"; system("cd $tempdir; $dvipscmd"); unless (-e "${tempdir}/foo.ps") { print STDERR <<'EOT'; Conversion of DVI to PS, a needed intermediate step, has failed. This probably should not happen. Please send a bug report to amax@redsymbol.net. EOT exit(2); } # convert ps to png my @cargs; if($opt_F) { # make image of full latex document @cargs = (); } else { @cargs = ( '-units', 'PixelsPerInch', '-density', "$dpi", ); } if ($opt_T) { # transparent background @cargs = ( '-matte', '-fuzz', $fuzz . '%', '-transparent', '#' . norm2hex($bg), '-units', 'PixelsPerInch', '-density', "$dpi", ); } # Border if($opt_B) { my($geom, $color); if($opt_B =~ /\s/) { # user has defined a border color $opt_B =~ m|^(\S+)\s+(.+)$|; ($geom, $color) = ($1, $2); $color = join(',', parsergb($color)); } else { # no border color defined, so use regular background color ($geom, $color) = ($opt_B, $bg); } $color = '#' . norm2hex($color); unshift @cargs, ('-border', $geom, '-bordercolor', $color); } unshift @cargs, ("$convert"); push @cargs, ( "${tempdir}/foo.ps", "${tempdir}/foo.png", ); # The following system() call seems to be completely successful. # However, using the ``system(...) or die "died: $!"'' idiom results # in death with the error message 'Inappropriate ioctl for device'. I # never could discern why, so I've left it as is. If you know why, # please let me know (amax@redsymbol.net) system(@cargs); unless (-e "$tempdir/foo.png") { print STDERR <<'EOT'; Sorry, something went wrong. Final conversion to PNG format has failed. EOT exit(2); } # rename final png system("cp $tempdir/foo.png $outfile");