Parent Directory
|
Revision Log
Added error messages that should make it easier to tell if certain directories have not been created. Also checks that files are actually created and written at each step of the process. --Mike
1 ################################################################################ 2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project 3 # $Id$ 4 ################################################################################ 5 6 package WeBWorK::PG::ImageGenerator; 7 8 =head1 NAME 9 10 WeBWorK::PG::ImageGenerator - create an object for holding bits of math for 11 LaTeX, and then to process them all at once. 12 13 =head1 SYNPOSIS 14 15 FIXME: add this 16 17 =cut 18 19 use strict; 20 use warnings; 21 use WeBWorK::EquationCache; 22 #use WeBWorK::Utils qw(readDirectory makeTempDirectory removeTempDirectory); 23 # can't use WeBWorK::Utils from here :( 24 # so we define the needed functions here instead 25 sub readDirectory($) { 26 my $dirName = shift; 27 opendir my $dh, $dirName 28 or die "Failed to read directory $dirName: $!"; 29 my @result = readdir $dh; 30 close $dh; 31 return @result; 32 } 33 use constant MKDIR_ATTEMPTS => 10; 34 sub makeTempDirectory($$) { 35 my ($parent, $basename) = @_; 36 # Loop until we're able to create a directory, or it fails for some 37 # reason other than there already being something there. 38 my $triesRemaining = MKDIR_ATTEMPTS; 39 my ($fullPath, $success); 40 do { 41 my $suffix = join "", map { ('A'..'Z','a'..'z','0'..'9')[int rand 62] } 1 .. 8; 42 $fullPath = "$parent/$basename.$suffix"; 43 $success = mkdir $fullPath; 44 } until ($success or not $!{EEXIST}); 45 unless ($success) { 46 my $msg = ''; 47 $msg .= "Server does not have write access to the directory $parent" unless -w $parent; 48 die "$msg\r\nFailed to create directory $fullPath:\r\n $!" 49 } 50 51 return $fullPath; 52 } 53 use File::Path qw(rmtree); 54 sub removeTempDirectory($) { 55 my ($dir) = @_; 56 rmtree($dir, 0, 0); 57 } 58 # okay, all set. 59 60 use constant PREAMBLE => <<'EOF'; 61 \documentclass[12pt]{article} 62 \nonstopmode 63 \usepackage{amsmath,amsfonts,amssymb} 64 \def\gt{>} 65 \def\lt{<} 66 \usepackage[active,textmath,displaymath]{preview} 67 \begin{document} 68 EOF 69 use constant POSTAMBLE => <<'EOF'; 70 \end{document} 71 EOF 72 use constant DVIPNG_ARGS => join " ", qw( 73 -x4000.5 74 -bgTransparent 75 -Q6 76 -mode toshiba 77 -D180 78 ); 79 80 =head1 METHODS 81 82 =over 83 84 =item new 85 86 Returns a new ImageGenerator object. C<%options> must contain the following 87 entries: 88 89 tempDir => directory in which to create temporary processing directory 90 latex => path to latex binary 91 dvipng => path to dvipng binary 92 useCache => boolean, whether to use global image cache 93 94 If C<useCache> is false, C<%options> must also contain the following entries: 95 96 dir => directory for resulting files 97 url => url to directory for resulting files 98 basename => base name for image files (i.e. "eqn-$psvn-$probNum") 99 100 If C<useCache> is true, C<%options> must also contain the following entries: 101 102 cacheDir => directory for resulting files 103 cacheURL => url to cacheDir 104 cacheDB => path to cache database file 105 106 =cut 107 108 sub new { 109 my ($invocant, %options) = @_; 110 my $class = ref $invocant || $invocant; 111 my $self = { 112 names => [], 113 strings => [], 114 %options, 115 }; 116 117 if ($self->{useCache}) { 118 $self->{dir} = $self->{cacheDir}; 119 $self->{url} = $self->{cacheURL}; 120 $self->{basename} = ""; 121 $self->{equationCache} = WeBWorK::EquationCache->new(cacheDB => $self->{cacheDB}); 122 } 123 124 bless $self, $class; 125 } 126 127 =item add($string, $mode) 128 129 Adds the equation in C<$string> to the object. C<$mode> can be "display" or 130 "inline". If not specified, "inline" is assumed. Returns the proper HTML tag 131 for displaying the image. 132 133 =cut 134 135 sub add { 136 my ($self, $string, $mode) = @_; 137 138 my $names = $self->{names}; 139 my $strings = $self->{strings}; 140 my $dir = $self->{dir}; 141 my $url = $self->{url}; 142 my $basename = $self->{basename}; 143 my $useCache = $self->{useCache}; 144 145 # if the string came in with delimiters, chop them off and set the mode 146 # based on whether they were \[ .. \] or \( ... \). this means that if 147 # the string has delimiters, the mode *argument* is ignored. 148 if ($string =~ s/^\\\[(.*)\\\]$/$1/s) { 149 $mode = "display"; 150 } elsif ($string =~ s/^\\\((.*)\\\)$/$1/s) { 151 $mode = "inline"; 152 } 153 # otherwise, leave the string and the mode alone. 154 155 # assume that a bare string with no mode specified is inline 156 $mode ||= "inline"; 157 158 # now that we know what mode we're dealing with, we can generate a "real" 159 # string to pass to latex 160 my $realString = ($mode eq "display") 161 ? '\(\displaystyle{' . $string . '}\)' 162 : '\(' . $string . '\)'; 163 164 # determine what the image's "number" is 165 my $imageNum = ($useCache) 166 ? $self->{equationCache}->lookup($realString) 167 : @$strings + 1; 168 169 # get the full file name of the image 170 my $imageName = ($basename) 171 ? "$basename.$imageNum.png" 172 : "$imageNum.png"; 173 174 # store the full file name of the image, and the "real" tex string to the object 175 push @$names, $imageName; 176 push @$strings, $realString; 177 #warn "ImageGenerator: added string $realString with name $imageName\n"; 178 179 # ... and the full URL. 180 my $imageURL = "$url/$imageName"; 181 182 my $imageTag = ($mode eq "display") 183 ? " <div align=\"center\"><img src=\"$imageURL\" align=\"baseline\" alt=\"$string\"></div> " 184 : " <img src=\"$imageURL\" align=\"baseline\" alt=\"$string\"> "; 185 186 return $imageTag; 187 } 188 189 =item render(%options) 190 191 Uses LaTeX and dvipng to render the equations stored in the object. The 192 193 =for comment 194 195 If the key "mtime" in C<%options> is given, its value will be interpreted as a 196 unix date and compared with the modification date on any existing copy of the 197 first image to be generated. It is recommended that the modification time of the 198 source file from which the equations originate be used for this value. If the 199 key "refresh" in C<%options> is true, images will be regenerated regardless of 200 when they were last modified. If neither option is supplied, "refresh" is 201 assumed. 202 203 =cut 204 205 sub render { 206 my ($self, %options) = @_; 207 208 my $tempDir = $self->{tempDir}; 209 my $dir = $self->{dir}; 210 my $basename = $self->{basename}; 211 my $latex = $self->{latex}; 212 my $dvipng = $self->{dvipng}; 213 my $names = $self->{names}; 214 my $strings = $self->{strings}; 215 216 #my $mtime = $options{mtime}; 217 #my $refresh = $options{refresh} || ! defined $mtime; 218 # # must refresh if no mtime is given 219 # 220 #unless ($refresh) { 221 # #my $firstImage = "$dir/$basename.1.png"; 222 # my $firstImage = "$dir/" . $names->[0]; 223 # if (-e $firstImage) { 224 # # return if first image newer than $mtime 225 # return if (stat $firstImage)[9] >= $mtime; 226 # } 227 #} 228 229 # determine which images need to be generated 230 my (@newStrings, @newNames); 231 for (my $i = 0; $i < @$strings; $i++) { 232 my $string = $strings->[$i]; 233 my $name = $names->[$i]; 234 if (-e "$dir/$name") { 235 #warn "ImageGenerator: found a file named $name, skipping string $string\n"; 236 } else { 237 #warn "ImageGenerator: didn't find a file named $name, including string $string\n"; 238 push @newStrings, $string; 239 push @newNames, $name; 240 } 241 } 242 243 return unless @newStrings; # Don't run latex if there are no images to generate 244 245 # create temporary directory in which to do TeX processing 246 my $wd = makeTempDirectory($tempDir, "ImageGenerator"); 247 248 # store equations in a tex file 249 my $texFile = "$wd/equation.tex"; 250 open my $tex, ">", $texFile 251 or die "failed to open file $texFile for writing: $!"; 252 print $tex PREAMBLE; 253 print $tex "$_\n" foreach @newStrings; 254 print $tex POSTAMBLE; 255 close $tex; 256 warn "tex file $texFile was not written" unless -e $texFile; 257 # call LaTeX 258 my $latexCommand = "cd $wd && $latex equation > latex.out 2> latex.err"; 259 my $latexStatus = system $latexCommand; 260 if ($latexStatus) { 261 warn "$latexCommand returned non-zero status $latexStatus: $!"; 262 warn "cd $wd failed" if system "cd $wd"; 263 warn "Unable to write to directory $wd. " unless -w $wd; 264 warn "Unable to execute $latex " unless -e $latex ; 265 266 warn `ls -l $wd`; 267 my $errorMessage = ''; 268 if (-r "$wd/equation.log") { 269 local(*LOGFILE); 270 open LOGFILE, "<$wd/equation.log" or die "Unable to read $wd/equation.log"; 271 local($/) = undef; 272 $errorMessage = <LOGFILE>; 273 close(LOGFILE); 274 warn "<pre> Logfile contents:\n$errorMessage\n</pre>"; 275 } else { 276 warn "Unable to read logfile $wd/equation.log "; 277 } 278 } 279 warn "$latexCommand failed to generate a DVI file" 280 unless -e "$wd/equation.dvi"; 281 282 # call dvipng 283 my $dvipngCommand = "cd $wd && $dvipng " . DVIPNG_ARGS . " equation > dvipng.out 2> dvipng.err"; 284 my $dvipngStatus = system $dvipngCommand; 285 warn "$dvipngCommand returned non-zero status $dvipngStatus: $!" 286 if $dvipngStatus; 287 288 # move/rename images 289 foreach my $image (readDirectory($wd)) { 290 # only work on equation#.png files 291 next unless $image =~ m/^equation(\d+)\.png$/; 292 293 # get image number from above match 294 my $imageNum = $1; 295 296 #warn "ImageGenerator: found generated image $imageNum with name $newNames[$imageNum-1]\n"; 297 298 # move/rename image 299 #my $mvCommand = "cd $wd && /bin/mv $wd/$image $dir/$basename.$imageNum.png"; 300 my $mvCommand = "cd $wd && /bin/mv $wd/$image $dir/" . $newNames[$imageNum-1]; 301 my $mvStatus = system $mvCommand; 302 if ( $mvStatus) { 303 warn "$mvCommand returned non-zero status $mvStatus: $!"; 304 warn "Can't write to tmp/equations directory $dir" unless -w $dir; 305 } 306 307 } 308 309 # remove temporary directory (and its contents) 310 removeTempDirectory($wd); 311 } 312 313 1;
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |