redsymbol / l2p

LaTeX to Pic

This URL has Read+Write access

redsymbol (author)
Fri Apr 04 17:12:19 -0700 2008
commit  9f3a7303145a4e7228dca58c02464e95b0ff8ee8
tree    07988338ead2529b1e0f4806c9b51b9833377bdc
parent  10fbcd1831d04297d9b38e50b17007bc1580007e
l2p / l2p
93517c80 » amax 2007-05-10 Reorganization to allow tag... 1 #!/usr/bin/perl -w
2 # L2P - Convert LaTeX expressions to PNG
3
b4307024 » redsymbol 2007-10-06 Fix for multi-line expressi... 4 $version = '1.1.1';
93517c80 » amax 2007-05-10 Reorganization to allow tag... 5
6 =head1 NAME
7
8 l2p - create PNG images from LaTeX expressions
9
10 =head1 SYNOPSIS
11
12 B<l2p> [options...] -i '$I<latex_expression>$'
13
14 or
15
16 B<l2p> [options...] [I<expression_file>]
17
18 I<expression_file> contains an expression or expressions in (La)TeX
19 format - one per line. If neither I<expression_file> nor an
20 B<-i> option is given, the expression is read from standard input.
21
22 =head1 DESCRIPTION
23
24 Convert expressions in LaTeX format into PNGs
25
26 =head1 EXAMPLES
27
28 =over
29
30 =item
31
32 l2p -i '$4x^2-7=\cos{2 \pi x}$' -o 'eqn4.png'
33
34 Produce a PNG image, named 'eqn4.png', of the equation described by the
35 LaTeX expression '$4x^2 - 7 = \cos{2 \pi x}$'.
36
37 =item
38
39 l2p -o big_equation.png big_hairy_equation
40
41 Produce a PNG image, called big_equation.png, from the LaTeX expression
42 contained in the file big_hairy_equation (specifically, it contains
43 '$x=2$'.) Note that this file is NOT a full LaTeX document - use the
44 B<-F> option for that.
45
46 =item
47
48 l2p -d 250 -i '$\nabla \cdot \mathbf{D} = \rho$'
49
50 Produce a PNG image from the LaTeX code given with the B<-i> argument
51 (which happens to be one of Maxwell's equations), at 250 dots per inch.
52 Since we did not specify an output file name with the B<-o> option, the
53 image will be 'eqn.png' (the default).
54
55 =item
56
57 l2p -p amssymb -i '$\mho$' -o mho.png
58
59 Produce a PNG image of the Mho symbol (an upside-down capital omega),
60 saving the image in the file 'mho.png'. We include the amssymb package,
61 which defines that symbol.
62
63 =item
64
65 l2p -B 20x30 -i '$\sum_{n=0}^{\infty}\frac{(-\phi^2)^n}{(2n)!}$' -o cosine.png
66
67 Produce an image of the indicated infinite summation, padded with a
68 border that is 20 pixels on each side horizontally, and 30 pixels each
69 side vertically. The color of this border region will be the same as
70 the rest of the image background.
71
72 =back
73
74 =head1 OPTIONS
75
76 Many options have arguments that may contain characters, like '#' or
77 spaces, that the shell considers special. Be sure to surround all
78 such arguments with single or double quotes, so that the shell
79 understands what is meant. (If unsure, it's always safe to use the
80 quotes.)
81
82 =over
83
84 =item
85 B<-i "$latex$">
86
87 Argument is an equation/expression in (La)TeX format. In most cases,
88 you will want to enclose the argument in quotes to protect it from shell
89 expansion.
90
91 =item
92 B<-b "rrggbb">
93
94 Background color. There are several ways to specify the color. See the
95 section L</COLORS>, below, for details.
96
97 =item
98 B<-d dpi>
99
100 Pixel density at which the equation is rendered, in dots per inch
101 (default 300).
102
103 An image with a DPI of 600 will have twice as many pixels in each of the
104 x and y directions than an image with a DPI of 300. The effect is
105 different in the normal context of printing, where a higher DPI will
106 leave the text with the same physical size, but with a finer resolution.
107 This is because the physical size of a pixel is not really variable; so
108 to have double the resolution, a symbol in an image must be double the
109 size.
110
111 =item
112 B<-f "rrggbb">
113
114 Foreground color. There are several ways to specify the color. See the
115 section L</COLORS>, below, for details.
116
117 =item
118 B<-h>
119
120 Show a help summary.
121
122 =item
123 B<-o output.png>
124
125 Name of output file. Default is 'eqn.png'.
126
127 =item
128 B<-p packagename[,packagename2[,...]]]>
129
130 Use additional LaTeX/TeX packages. You can specify several, separated
131 by commas.
132
133 =item
134 B<-B "WIDTHxHEIGHT [color]">
135
136 or: B<-B "SIZE [color]">
137
138 Pad the resulting image with a border of the indicated size, in pixels.
139
140 You can optionally specify a color for the border region. By default,
141 the border will be the same color as the rest of the background. (See
142 L</COLORS> below for the format.)
143
144 =item
145 B<-C>
146
147 Suppress automatic removal (cleanup) of temporary files. This will be
148 useful if something goes wrong, or if you want to use the intermediate
149 DVI or Postscript renditions. B<l2p> will tell you which directory
150 contains these files.
151
152 =item
153 B<-F>
154
155 Supplied expression is a full LaTeX document, rather than just an
156 expression fragment. Negates the B<-f>, B<-b>, B<-p>, B<-B> and B<-T>
157 options.
158
159 B<Note>: B<l2p> currently only converts full LaTeX documents
160 that are relatively simple: only one page in length, and with no external
161 dependencies (such as included graphics). If you need to convert a more
162 complex document, you can generate a DVI file with latex like normal,
163 then convert the DVI into a series of PNG images using B<convert> from
164 the ImageMagick distribution. See L<convert(1)>, or
165 L<http://imagemagick.org/script/convert.php> for more information.
166
167 =item
168 B<-T>
169
170 Create an image with a transparent background.
171
172 =item
173 B<-V>
174
175 Show version information.
176
177 =back
178
179 =head1 COLORS
180
181 Some options, such as B<-b> and B<-f>, take an argument specifying a
182 color in RGB format. B<l2p> will decipher most representations, such
183 as:
184
185 =over
186
187 =item
188
189 A hexidecimal triplet. For example, '-f "FF0000" -b "#ffffff"' gives a
190 red foreground on a white background. Case is not important, and the
191 "#" is optional.
192
193 =item
194
195 Three decimal whole numbers, in the range of 0 to 255. These must be
196 separated by spaces or punctuation (comma, semicolon or colon). For
197 example, '-b "0 127 255" -f "0,0,0"' is black on a nice bluish
198 background.
199
200 =item
201
202 Three fractions between 0 and 1, inclusive. At least one of the three
203 numbers must contain a decimal point (to distinguish this format from
204 the others), and they are separated by space or punctuation. For
205 example, "0.87 .78 .41" is the same as the hex triplet "DEC769", and "0,
206 1.0, 0" is the color green. (Remember that decimal point. "0, 1, 0"
207 will give you a nearly black color.)
208
209 =back
210
211 Note that you may need to put single or double quotes around the color
212 string, to ensure the shell interprets it correctly.
213
214 =head1 BUGS
215
216 Error handling is imperfect. Among other things, If a needed
217 LaTeX package is not included, B<l2p> will silently produce a
218 broken image.
219
220 On certain platforms, images produced with the B<-T> option (transparent
221 background) may leave pixels at the edges of symbols a mixture of the
222 text color and some background color. This may not look good if the
223 resulting image is put on a differently colored background. A
224 workaround is to give a background color hint with the B<-b> option;
225 the edge pixels will then be a mixture of specified foreground and
226 background colors.
227
379c84f7 » amax 2007-05-10 Build process tools 228 =head1 ACKS
93517c80 » amax 2007-05-10 Reorganization to allow tag... 229
379c84f7 » amax 2007-05-10 Build process tools 230 Thanks to Jesse Merriman (L<http://www.jessemerriman.com/>) for
231 providing a patch that improved transparent background support.
232 Integrated in version 1.1.
93517c80 » amax 2007-05-10 Reorganization to allow tag... 233
234 =head1 COPYRIGHT
235
236 This software is in the public domain.
237
379c84f7 » amax 2007-05-10 Build process tools 238 =head1 AUTHOR
239
240 Aaron Maxwell (amax@redsymbol.net). Comments, feature requests, and
241 patches are welcome.
242
93517c80 » amax 2007-05-10 Reorganization to allow tag... 243 =cut
244
245 use File::Temp qw/tempfile tempdir/;
246 use Getopt::Std;
247
248 # Takes a string and extracts an RGB value from it.
249 # Returns ($r,$g,$b), all values between 0 and 1 if parsing is successful,
250 # otherwise returns undef.
251 # Usage: ($r,$g,$b) = parsergb($string);
252 sub parsergb {
253 my $string=shift;
254 $string =~s/^\s+//;
255 $string =~s/\s+$//;
256 $string =~s/^#//;
257 my(@args,@vals,$arg,$val);
258
259 # hex triplet format?
260 if($string=~/^[0-9a-fA-F]{6}$/) {
261 @args=(substr($string,0,2), substr($string,2,2), substr($string,4,2));
262 foreach $arg (@args) {
263 $val=hex($arg)/255;
264 push @vals, $val;
265 }
266 return @vals;
267 }
268 $string =~s/[,:;]/ /g;
269 @args = split /\s+/, $string;
270 if (@args != 3) {
271 return undef;
272 }
273
274 # 0-255 decimal format?
275 if ($string =~ /^[\d\s]+$/) {
276 foreach $arg (@args) {
277 if ($arg>=0 && $arg<=255) {
278 $val=$arg/255;
279 } else { return undef; }
280 push @vals, $val;
281 }
282
283 # 0.0-1.0 range format?
284 } elsif ($string =~ /^[\d\.\s]+$/) {
285 foreach $arg (@args) {
286 if($arg>=0 && $arg<=1) {
287 $val = 0+$arg;
288 } else { return undef; }
289 push @vals, $val;
290 }
291
292 } else {
293 return undef; # unrecognized format!
294 }
295
296 return @vals;
297 }
298
299 # norm2hex - convert an RGB color in the form 'r,g,b', 0<=[rgb]<=1,
300 # to a hex triplet. Returns undef if invoked incorrectly.
301 # usage: $hexrgb = norm2hex($normrgb);
302 sub norm2hex {
303 $_=shift;
304 my @vals=split(/,/,$_);
305 scalar(@vals)==3 or return undef;
306 my($val,$hex);
307 foreach $val (@vals) {
308 unless($val>=0 and $val<=1) { return undef; }
309 $hex .= sprintf('%02x',$val*255);
310 }
311 return $hex;
312 }
313
314 my($pre,$post,$dpi,$eqn,$outfile,$fg,$bg);
315 our($opt_o, # output file name
316 $opt_d, # dpi
317 $opt_i, # in-command-line latex expression
318 $opt_f, # foreground RGB triplet
319 $opt_b, # background RGB triplet
320 $opt_F, # set if input is a full LaTeX document
321 $opt_T, # transparent background
322 $opt_C, # suppress autocleaning of temp files
323 $opt_h, # display help message
324 $opt_p, # additional package(s)
325 $opt_V, # print version info
326 $opt_B, # border
327 $opt_Z, # reserved for hacks
328 );
329
330 # check to see if needed software is available
331 my($latex,$dvips,$convert);
332 $latex = `which latex`; chomp $latex;
333 if($latex eq '' or not -X $latex) {
334 print STDERR "Cannot find latex executable. Aborting.\n";
335 exit(2);
336 }
337 $dvips = `which dvips`; chomp $dvips;
338 if($dvips eq '' or not -X $dvips) {
339 print STDERR "Cannot find dvips executable. Aborting.\n";
340 exit(2);
341 }
342 $convert = `which convert`; chomp $convert;
343 if($convert eq '' or not -X $convert) {
344 print STDERR "Cannot find convert executable. Aborting.\n";
345 exit(2);
346 }
347
348 # process command line opts
349 getopt('odifbpB');
350
351 if ($opt_V) {
352 print $version, "\n";
353 exit(0);
354 }
355
356 if ($opt_h) {
357 print <<'EOT';
358 Generate PNG images from LaTeX expressions
359 usage:
360 l2p [options] [file_containing_latex_expressions]
361 or
362 l2p [options] -i '$LaTeX-expression$'
363
364 Note: Many options will require quotes around their arguments to
365 ensure correct interpretation by the shell.
366
367 Options:
368 -o output.png Name of output file. Default is 'eqn.png'.
369 -i '$latex$' equation/expression in (La)TeX format
370 -f 'rrggbb' foreground color
371 -b 'rrggbb' background color
372 -d dpi Conversion resolution (default 300)
373 -T Transparent background
374 -p pkg[,pkg2...] use TeX/LaTeX package(s)
375 -C Suppress removal (cleanup) of temporary files
376 -F Input is full LaTeX document, not just fragment
377 -V Show version
378 -B 'geom [color]' Pad image with a border
379 -h Show this help and exit
380 Also see the full documentation (try typing 'perldoc l2p').
381 EOT
382 exit(0);
383 }
384
385 $outfile = $opt_o || 'eqn.png';
386 $dpi = $opt_d || 300;
387
388 # determine foreground color
389 $fg='0,0,0';
390 if($opt_f) {
391 my($r,$g,$b) = parsergb($opt_f);
392 if (not defined $r) {
393 print STDERR "Foreground color not in recognizeable format. Reverting to default.\n";
394 ($r,$g,$b) = (0,0,0);
395 }
396 $fg=join(',',$r,$g,$b);
397 }
398
399 $bg='1,1,1';
400 # determine background color
401 if($opt_b) {
402 my($r,$g,$b) = parsergb($opt_b);
403 if (not defined $r) {
404 print STDERR "Background color not in recognizeable format. Reverting to default.\n";
405 ($r,$g,$b) = (1,1,1);
406 }
407 $bg=join(',',$r,$g,$b);
408 }
409 # deal with transparent background
410 $fuzz = 20;
411 if ($opt_T) {
412 if($opt_b) {
413 # Workaround: with a BG hint, a nonzero fuzz can result in erased symbols
414 $fuzz = 0;
415 }
416 # $bg and $fg must be different for transparency to work
417 my($bR,$bG,$bB) = split(/,/, $bg);
418 my($fR,$fG,$fB) = split(/,/, $fg);
419 my($dR, $dG, $dB) = map { abs($_) } ($fR-$bR, $fG-$bG, $fB-$bB);
420 if($dR<0.1 && $dG<0.1 && $dB<0.1) {
421 $bg = (sqrt($fR**2+$fG**2+$fB**2)>0.5) ? '0,0,0' : '1,1,1';
422 }
423 }
424
425 my @packages = ('color');
426 if ($opt_p) {
427 @packages = (@packages, split(/,/, $opt_p));
428 }
429
430 # get expression to render
431 $pre = join "\n", (
432 '\documentclass{article}',
433 (map { '\usepackage{' . $_ . '}' } @packages),
434 '\definecolor{bg}{rgb}{', $bg, '}',
435 '\definecolor{fg}{rgb}{', $fg, '}',
436 '\pagestyle{empty}',
437 '\pagecolor{bg}',
438 '\begin{document}',
439 '\color{fg}',
440 '\begin{center}',
441 "");
442
443 $post = <<'EOT';
444 \end{center}
445 \end{document}
446 EOT
447
448 # discover the LaTeX expression to render
449 $eqn='';
450 if (defined $opt_i) {
d57771e6 » redsymbol 2007-10-06 Indentation changes 451 # expression from command line
452 $eqn = $opt_i . "\n";
93517c80 » amax 2007-05-10 Reorganization to allow tag... 453 } elsif (not $opt_F) {
d57771e6 » redsymbol 2007-10-06 Indentation changes 454 # file/stdin contains LaTeX expression(s)
455 # TODO: rewrite using an expression iterator subroutine
456 while(<>) {
457 next if /^\s*#/ or /^\s*$/;
458 chomp;
459 $eqn .= $_ . "\n";
460 # If this is line contains a single inline expression, add
461 # an extra newline, so that it renders correctly
462 if (/^\s*\$.*\$\s*$/) {
463 $eqn .= "\n";
464 }
465 }
93517c80 » amax 2007-05-10 Reorganization to allow tag... 466 }
467 if (not $opt_F and $eqn =~ /^\s*$/) {
d57771e6 » redsymbol 2007-10-06 Indentation changes 468 print STDERR <<'EOT';
93517c80 » amax 2007-05-10 Reorganization to allow tag... 469 Did not find a LaTeX expression to render. Perhaps the supplied
470 expression or file is empty, or does not exist.
471 EOT
d57771e6 » redsymbol 2007-10-06 Indentation changes 472 exit(3);
93517c80 » amax 2007-05-10 Reorganization to allow tag... 473 }
474
475 # create a temporary latex file to use
476 my $tempdir = tempdir(CLEANUP=> $opt_C ? 0 : 1)
477 or die "Cannot not make temp dir. Unable to proceed - aborting.";
478 print "Temporary files stored in $tempdir\n" if $opt_C;
479 my $latexfn = $tempdir . "/foo.latex";
480 if($opt_F) {
d57771e6 » redsymbol 2007-10-06 Indentation changes 481 $source = shift @ARGV;
482 if(not -e $source) {
483 die "LaTeX source ($source) does not exist!";
484 } elsif (not -r $source) {
485 die "Cannot read file $source.";
486 }
487 if(substr($source,0,1) ne '/') {
488 my $cwd = `pwd`; chomp $cwd;
489 $source = $cwd . '/' . $source;
490 }
491 system("ln -s $source $latexfn");
492 -e "$latexfn" or die "Unable to create link to source file ($source)";
93517c80 » amax 2007-05-10 Reorganization to allow tag... 493 } else {
d57771e6 » redsymbol 2007-10-06 Indentation changes 494 my $latexfh;
495 open($latexfh,">",$latexfn) or die "could not write to latex temp file";
496 print $latexfh $pre, $eqn, $post;
497 close($latexfh);
93517c80 » amax 2007-05-10 Reorganization to allow tag... 498 }
499
500 # produce dvi output
501 system("cd $tempdir; $latex -interaction=batchmode $latexfn >/dev/null");
502 unless (-e "${tempdir}/foo.dvi") {
503 print STDERR <<'EOT';
504 latex run failed. Perhaps the input is invalid, or a specified
505 package was not found.
506 EOT
507 exit(1);
508 }
509
510 # convert dvi to ps
511 # the -E option prevents convert from freaking out later
512 my $dvipscmd = "$dvips " . ($opt_F ? "": "-E") . " foo -o 2>/dev/null";
513 system("cd $tempdir; $dvipscmd");
514 unless (-e "${tempdir}/foo.ps") {
515 print STDERR <<'EOT';
516 Conversion of DVI to PS, a needed intermediate step, has failed.
517 This probably should not happen. Please send a bug report to
518 amax@redsymbol.net.
519 EOT
520 exit(2);
521 }
522
523 # convert ps to png
524 my @cargs;
525 if($opt_F) { # make image of full latex document
526 @cargs = ();
527 } else {
528 @cargs = (
529 '-units',
530 'PixelsPerInch',
531 '-density',
532 "$dpi",
533 );
534 }
535 if ($opt_T) { # transparent background
536 @cargs = (
537 '-matte',
538 '-fuzz',
539 $fuzz . '%',
540 '-transparent',
541 '#' . norm2hex($bg),
542 '-units',
543 'PixelsPerInch',
544 '-density',
545 "$dpi",
546 );
547 }
548 # Border
549 if($opt_B) {
550 my($geom, $color);
551 if($opt_B =~ /\s/) {
552 # user has defined a border color
553 $opt_B =~ m|^(\S+)\s+(.+)$|;
554 ($geom, $color) = ($1, $2);
555 $color = join(',', parsergb($color));
556 } else {
557 # no border color defined, so use regular background color
558 ($geom, $color) = ($opt_B, $bg);
559 }
560 $color = '#' . norm2hex($color);
561 unshift @cargs, ('-border', $geom, '-bordercolor', $color);
562 }
563 unshift @cargs, ("$convert");
564 push @cargs, (
565 "${tempdir}/foo.ps",
566 "${tempdir}/foo.png",
567 );
568 # The following system() call seems to be completely successful.
569 # However, using the ``system(...) or die "died: $!"'' idiom results
570 # in death with the error message 'Inappropriate ioctl for device'. I
571 # never could discern why, so I've left it as is. If you know why,
572 # please let me know (amax@redsymbol.net)
573 system(@cargs);
574 unless (-e "$tempdir/foo.png") {
575 print STDERR <<'EOT';
576 Sorry, something went wrong. Final conversion to PNG format has failed.
577 EOT
578 exit(2);
579 }
580
581 # rename final png
582 system("cp $tempdir/foo.png $outfile");