[system] / trunk / webwork2 / lib / WeBWorK / PG / ImageGenerator.pm Repository:
ViewVC logotype

Annotation of /trunk/webwork2/lib/WeBWorK/PG/ImageGenerator.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1159 - (view) (download) (as text)

1 : gage 1022 ################################################################################
2 : sh002i 1131 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project
3 :     # $Id$
4 : gage 1022 ################################################################################
5 :    
6 : sh002i 1131 package WeBWorK::PG::ImageGenerator;
7 : gage 1022
8 :     =head1 NAME
9 :    
10 : sh002i 1154 WeBWorK::PG::ImageGenerator - create an object for holding bits of math for
11 :     LaTeX, and then to process them all at once.
12 : gage 1022
13 :     =head1 SYNPOSIS
14 :    
15 : sh002i 1154 FIXME: add this
16 : gage 1022
17 :     =cut
18 :    
19 : sh002i 1131 use strict;
20 :     use warnings;
21 : sh002i 1154 use WeBWorK::Utils qw(readDirectory makeTempDirectory removeTempDirectory);
22 : gage 1022
23 : sh002i 1154 use constant PREAMBLE => <<'EOF';
24 :     \documentclass[12pt]{article}
25 :     \nonstopmode
26 :     \usepackage{amsmath,amsfonts,amssymb}
27 :     \def\gt{>}
28 :     \def\lt{<}
29 :     \usepackage[active,textmath,displaymath]{preview}
30 :     \begin{document}
31 :     EOF
32 :     use constant POSTAMBLE => <<'EOF';
33 :     \end{document}
34 :     EOF
35 :     use constant DVIPNG_ARGS => join " ", qw(
36 :     -x4000.5
37 :     -bgTransparent
38 :     -Q6
39 :     -mode toshiba
40 :     -D180
41 :     );
42 :    
43 : sh002i 1131 =head1 METHODS
44 : gage 1022
45 : sh002i 1131 =over
46 : gage 1022
47 : sh002i 1131 =item new
48 :    
49 : sh002i 1154 Returns a new ImageGenerator object. <C%options> must contain the following entries:
50 : gage 1022
51 : sh002i 1154 tempDir => directory for storing temporary files (non-web accessible)
52 :     dir => directory for resulting files
53 :     url => url to directory for resulting files
54 :     basename => base name for image files
55 :     latex => path to latex binary
56 :     dvipng => path to dvipng binary
57 : sh002i 1131
58 : gage 1022 =cut
59 :    
60 :     sub new {
61 : sh002i 1154 my ($invocant, %options) = @_;
62 :     my $class = ref $invocant || $invocant;
63 : sh002i 1131 my $self = {
64 : sh002i 1154 strings => [],
65 :     %options,
66 : sh002i 1131 };
67 :    
68 :     bless $self, $class;
69 : gage 1022 }
70 :    
71 : sh002i 1154 =item add($string, $mode)
72 :    
73 :     Adds the equation in C<$string> to the object. C<$mode> can be "display" or "inline". If
74 :     not specified, "inline" is assumed. Returns the proper HTML tag for displaying the image.
75 :    
76 :     =cut
77 :    
78 :     sub add {
79 :     my ($self, $string, $mode) = @_;
80 :    
81 :     my $strings = $self->{strings};
82 :     my $dir = $self->{dir};
83 :     my $url = $self->{url};
84 :     my $basename = $self->{basename};
85 :    
86 : sh002i 1159 # if the string came in with delimiters, chop them off and set the mode
87 :     # based on whether they were \[ .. \] or \( ... \). this means that if
88 :     # the string has delimiters, the mode *argument* is ignored.
89 :     if ($string =~ s/^\\\[(.*)\\\]$/$1/) {
90 :     $mode = "display";
91 :     } elsif ($string =~ s/^\\\((.*)\\\)$/$1/) {
92 :     $mode = "inline";
93 :     }
94 :     # otherwise, leave the string and the mode alone.
95 :    
96 : sh002i 1154 my $imageNum = @$strings + 1;
97 :     my $imageURL = "$url/$basename.$imageNum.png";
98 :     my $imageTag = "<img src=\"$imageURL\" align=\"middle\" alt=\"$string\">";
99 :    
100 :     if ($mode eq "display") {
101 :     push @$strings, '\(\displaystyle{' . $string . '}\)';
102 :     return " <div align=\"center\">$imageTag</div> ";
103 :     } else {
104 :     push @$strings, '\(' . $string . '\)';
105 :     return " $imageTag ";
106 :     }
107 :     }
108 :    
109 :     =item render(%options)
110 :    
111 :     Uses LaTeX and dvipng to render the equations stored in the object. If the key "mtime" in
112 :     C<%options> is given, it will be interpreted as a unix date and compared with the
113 :     modification date on any existing copy of the first image to be generated. It is
114 :     recommended that the modification time of the source file from which the equations
115 :     originate be used for this value. If the key "refresh" in C<%options> is true, images
116 :     will be regenerated regardless of when they were last modified.
117 :    
118 :     =cut
119 :    
120 :     sub render {
121 :     my ($self, %options) = @_;
122 :    
123 :     my $tempDir = $self->{tempDir};
124 :     my $dir = $self->{dir};
125 :     my $basename = $self->{basename};
126 :     my $latex = $self->{latex};
127 :     my $dvipng = $self->{dvipng};
128 :     my $strings = $self->{strings};
129 :    
130 :     my $mtime = $options{mtime};
131 :     my $refresh = not defined $mtime || $options{refresh};
132 :     # must refresh if no mtime is given
133 :    
134 :     return unless @$strings; # Don't run latex if there are no images to generate
135 :    
136 :     unless ($refresh) {
137 :     my $firstImage = "$dir/$basename.1.png";
138 :     if (-e $firstImage) {
139 :     # return if first image newer than $mtime
140 :     return if (stat $firstImage)[9] >= $mtime;
141 :     }
142 :     }
143 :    
144 :     # create temporary directory in which to do TeX processing
145 :     my $wd = makeTempDirectory($tempDir, "ImageGenerator");
146 :    
147 :     # store equations in a tex file
148 :     my $texFile = "$wd/equation.tex";
149 :     open my $tex, ">", $texFile
150 :     or die "failed to open file $texFile for writing: $!";
151 :     print $tex PREAMBLE;
152 :     print $tex "$_\n" foreach @$strings;
153 :     print $tex POSTAMBLE;
154 :     close $tex;
155 :    
156 :     # call LaTeX
157 :     my $latexCommand = "cd $wd && $latex equation > latex.out 2> latex.err";
158 :     my $latexStatus = system $latexCommand;
159 :     warn "$latexCommand returned non-zero status $latexStatus: $!"
160 :     if $latexStatus;
161 :     warn "$latexCommand failed to generate a DVI file"
162 :     unless -e "$wd/equation.dvi";
163 :    
164 :     # call dvipng
165 :     my $dvipngCommand = "cd $wd && $dvipng " . DVIPNG_ARGS . " equation > dvipng.out 2> dvipng.err";
166 :     my $dvipngStatus = system $dvipngCommand;
167 :     #warn "$dvipngCommand returned non-zero status $dvipngStatus: $!"
168 :     # if $dvipngStatus;
169 :    
170 :     # move/rename images
171 :     foreach my $image (readDirectory($wd)) {
172 :     # only work on equation#.png files
173 :     next unless $image =~ m/^equation(\d+)\.png$/;
174 :    
175 :     # get image number from above match
176 :     my $imageNum = $1;
177 :    
178 :     # move/rename image
179 :     my $mvCommand = "cd $wd && /bin/mv $wd/$image $dir/$basename.$imageNum.png";
180 :     my $mvStatus = system $mvCommand;
181 :     warn "$mvCommand returned non-zero status $mvStatus: $!"
182 :     if $mvStatus;
183 :     }
184 :    
185 :     # remove temporary directory (and its contents)
186 :     removeTempDirectory($wd);
187 :     }
188 :    
189 :     1;
190 :    
191 :     __END__
192 :    
193 :     ################################################################################
194 :     # OLD VERSIONS OF SUBROUTINES
195 :     ################################################################################
196 :    
197 :    
198 :     sub getCount {
199 :     my $self = shift;
200 :     return $self->{count};
201 :     }
202 :    
203 :     sub tmpurl {
204 :     my $self = shift;
205 :     return "$self->{tmpURLstart}/$self->{filenamestart}";
206 :     }
207 :    
208 : gage 1022 sub initialize {
209 : sh002i 1154 my ($self, $envir) = @_;
210 : sh002i 1131
211 : sh002i 1154 my $problemnum = $envir->{'probNum'};
212 :     my $studname = $envir->{'studentLogin'};
213 :     my $psvn = $envir->{'psvn'};
214 :     my $setname = $envir->{'setNumber'};
215 : sh002i 1131 my $tmpURLstart = $envir->{'tempURL'};
216 :    
217 :     my $path=main::surePathToTmpFile(main::convertPath("png/$setname/$psvn/foo"));
218 :     $path =~ s/foo$//; # remove final foo
219 :    
220 :     $self->{sourceFile} = $envir->{templateDirectory} . "/" . $envir->{fileName};
221 :     $self->{tmpURLstart} = $tmpURLstart."png/$setname/$psvn";
222 :     $self->{tmppath}=$path;
223 :     $self->{filenamestart}="$studname-prob${problemnum}image";
224 : gage 1022 }
225 :    
226 :     # Add another string to list to be LaTeX'ed
227 :     # return the tag
228 :     sub add {
229 :     my $self = shift;
230 :     my $newstr = shift;
231 :     my $tag = $newstr;
232 :     $self->{count}++;
233 :     my $tempURL= $self->tmpurl()."$self->{count}.png";
234 :    
235 :     if ($tag =~ /^\\\(/) {
236 :     $tag =~ s|^\\\( *||;
237 :     $tag =~ s|\\\)$||;
238 :     $tag = qq!<img src="$tempURL" align="middle" alt="$tag">!;
239 :     } else {
240 :     # Displayed math comes in with \[ stuff \]. To get a good
241 :     # bounding box through preview, we change that to \( \displaystyle{
242 :     # stuff } \), and then center the resulting image
243 :     $tag =~ s|^\\\[ *||;
244 :     $tag =~ s|\\\]$||;
245 :     $newstr = '\(\displaystyle{'.$tag.'}\)';
246 :     $tag = qq!<div align="center"><img src="$tempURL" align="middle" alt="$tag"></div>!;
247 :     }
248 : sh002i 1131
249 : gage 1022 push @{$self->{latexlines}}, $newstr;
250 :     return $tag;
251 :     }
252 :    
253 :     sub render {
254 :     my $self = shift;
255 :     my %opts = @_;
256 : sh002i 1131
257 :     # Don't run latex if there are no images
258 :     if($self->{count}==0) {
259 :     return;
260 :     }
261 :    
262 : gage 1022 my $refreshMe = 0;
263 : sh002i 1131 if (defined($opts{refresh}) and (($opts{refresh} eq "yes") or ($opts{refresh} == 1))) {
264 : gage 1022 $refreshMe = 1;
265 :     }
266 :    
267 : sh002i 1131 #$refreshMe = 1; # Uncomment for testing
268 :     my $latexfilenamebase = $self->{tmppath} . $self->{filenamestart};
269 : gage 1022
270 :     my $sourcePath = $self->{sourceFile};
271 : sh002i 1131 my $tempFile = "${latexfilenamebase}" . $self->{count} . ".png"; # last image
272 : gage 1022
273 : sh002i 1131 if ($refreshMe or not -e $tempFile or (stat $sourcePath)[9] > (stat $tempFile)[9]) {
274 : gage 1022 # image file doesn't exist, or source file is newer then image file
275 :     # or we just want new images produced
276 :    
277 : sh002i 1131 #my $old_cdir = `pwd`; # cd for running latex
278 :     #chomp($old_cdir);
279 :     chdir($self->{tmppath})
280 :     || warn "Could not move into temporary directory $self->{tmppath}";
281 : gage 1022
282 :     if (-e "$latexfilenamebase.tex") {
283 :     unlink("$latexfilenamebase.tex") ||
284 :     warn "Could not delete old LaTeX file";
285 :     }
286 :    
287 :     local *LATEXME;
288 :     open(LATEXME,">$latexfilenamebase.tex") || warn "Cannot create temporary tex file";
289 :     print LATEXME <<'EOT';
290 :     \documentclass[12pt]{article}
291 :     \nonstopmode
292 :     \usepackage{amsmath,amsfonts,amssymb}
293 :     \def\gt{>}
294 :     \def\lt{<}
295 :    
296 :     \usepackage[active,textmath,displaymath]{preview}
297 :     \begin{document}
298 :     EOT
299 :    
300 :     my $j;
301 :     for $j (@{$self->{latexlines}}) {
302 :     print LATEXME "\n$j\n";
303 :     }
304 :    
305 :     print LATEXME '\end{document}'."\n";
306 :     close(LATEXME);
307 :    
308 :     chmod(0666, "$latexfilenamebase.tex") || warn "Could not change permissions on $latexfilenamebase.tex";
309 :    
310 :     my $error_log = '/dev/null'; ## by default do not log error messages
311 :     $error_log = &Global::getErrorLog if $Global::imageDebugMode;
312 :    
313 :     $ENV{PATH} .= "$Global::extendedPath";
314 :     my $dvipng_res = int($Global::dvipngScaling * 1000+0.5);
315 :     my $cmdout="";
316 :    
317 :     # remove any old files using this name
318 :     unlink("$self->{filenamestart}.dvi","$self->{filenamestart}.log",
319 :     "$self->{filenamestart}.aux","missfont.log");
320 :    
321 :     $cmdout=system("$Global::externalLatexPath $self->{filenamestart}.tex >>$error_log 2>>$error_log");
322 :     warn "$Global::externalLatexPath $self->{filenamestart}.tex >>$error_log 2>>$error_log -- FAILED in ImageGenerator returned $cmdout" if $cmdout;
323 :    
324 :     $cmdout=system("$Global::externalDvipngPath -x$dvipng_res -bgTransparent -Q$Global::dvipngShrinkFactor -mode $Global::dvipngMode -D$Global::dvipngDPI $self->{filenamestart}.dvi >>$error_log 2>>$error_log");
325 :     warn "$Global::externalDvipngPath -x$dvipng_res -bgTransparent -Q$Global::dvipngShrinkFactor -mode $Global::dvipngMode -D$Global::dvipngDPI $self->{filenamestart}.dvi >>$error_log 2>>$error_log -- FAILED in ImageGenerator.pm returned $cmdout" if $cmdout !=256;
326 :    
327 :     unless ($Global::imageDebugMode) {
328 :     unlink("$self->{filenamestart}.dvi","$self->{filenamestart}.log",
329 :     "$self->{filenamestart}.tex",
330 :     "$self->{filenamestart}.aux"
331 :     );
332 :     }
333 : sh002i 1131 #chdir($old_cdir);
334 : gage 1022 }
335 :     }
336 :    
337 :     1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9