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