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

View of /trunk/pg/lib/WeBWorK/PG/ImageGenerator.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1688 - (download) (as text) (annotate)
Sat Dec 27 21:34:04 2003 UTC (9 years, 4 months ago) by gage
File size: 9107 byte(s)
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