/
l2p
executable file
·634 lines (517 loc) · 16.8 KB
/
l2p
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
#!/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<l2p> [options...] -I 'I<latex math expression>'
or
B<l2p> [options...] [I<expression_file>]
I<expression_file> contains an expression or expressions in (La)TeX
format - one per line. If neither I<expression_file>, nor the B<-I>
or B<-i> options are 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 -i '$4x^2-7=\cos{2 \pi x}$' -o 'eqn4.png'
Same as previous, except that the latex code argument is not
automatically rendered as an equation - we must explicity surround it
with '$' symbols. By omitting that, you can also render
images of non-math or mixed expressions.
=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 "math expression">
Argument is an equation/expression in (La)TeX format, interpreted in
math mode. In most cases, you will want to enclose the argument in
quotes to protect it from shell expansion.
=item
B<-i "latex code">
Argument is a snippet of (La)TeX code. If it is a math expression, be
sure to surround it with $ symbols: for example, B<-i> "$x+2$". In
most cases, you will want to enclose the argument in quotes to protect
it from shell expansion.
The only difference between the B<-i> and B<-I> options is that B<-I>
automatically surrounds its argument with "$" symbols, so that it is
rendered in LaTeX's math mode. Otherwise there is no difference.
=item
B<-b "rrggbb">
Background color. There are several ways to specify the color. See the
section L</COLORS>, 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</COLORS>, 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</COLORS> 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<l2p> 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<Note>: B<l2p> 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<convert> from
the ImageMagick distribution. See L<convert(1)>, or
L<http://imagemagick.org/script/convert.php> 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<l2p> 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<l2p> 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<http://www.jessemerriman.com/>) 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;
}
# extract_ext - guess an extension (and thus image type) from a file name
# Returns undef if unable to make a good guess.
sub extract_ext($) {
my $fname = shift;
if (-1 == index($fname, '.')) {
return undef;
}
@parts = split(/\./, $fname);
return pop @parts;
}
my($pre,$post,$dpi,$eqn,$outfile,$fg,$bg);
our($opt_o, # output file name
$opt_d, # dpi
$opt_i, # in-command-line latex code
$opt_x, # file type extension
$opt_I, # in-command-line math 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 %cmds = (
'latex' => '',
'dvips' => '',
'convert' => '',
);
my @cmds_notfound;
foreach $cmd (keys %cmds) {
$cmds{$cmd} = `which $cmd`; chomp $cmds{$cmd};
if($cmds{$cmd} eq '' or not -X $cmds{$cmd}) {
unshift @cmds_notfound, $cmd;
}
}
if(@cmds_notfound) {
printf STDERR "Cannot find executable(s) for %s. Aborting.\n", join(',', @cmds_notfound);
exit(2);
}
# process command line opts
getopt('odiIfbpBx');
if ($opt_V) {
print $version, "\n";
exit(0);
}
$outfile = $opt_o || 'eqn.png';
# Format to write image in
if($opt_x) {
$out_format = $opt_x;
} else {
$out_format = extract_ext($outfile);
if (not defined $out_format) {
$out_format = 'png';
}
}
print "out format: $out_format\n";
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 eqn>' (La)TeX equation or expression (rendered as math)
-i '<latex code>' Raw (La)TeX code (not rendered as math by default)
-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);
}
$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) {
# latex code from command line
$eqn = $opt_i . "\n";
} elsif (defined $opt_I) {
# expression from command line
$eqn = sprintf("\$%s\$\n",$opt_I);
} 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; $cmds{'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 = "$cmds{'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, ("$cmds{'convert'}");
$dest_img = "${tempdir}/dest_img";
push @cargs, (
"${tempdir}/foo.ps",
"${out_format}:${dest_img}",
);
# 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 $dest_img) {
print STDERR <<'EOT';
Sorry, something went wrong. Final conversion from postscript format has failed.
EOT
exit(2);
}
# rename final png
system("cp ${dest_img} $outfile");