Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2      // latex.php
   3      // render TeX stuff using latex - this will not work on all platforms
   4      // or configurations. Only works on Linux and Mac with appropriate
   5      // software installed.
   6      // Much of this inspired/copied from Benjamin Zeiss' work
   7  
   8      class latex {
   9  
  10          var $temp_dir;
  11          var $error;
  12  
  13          /**
  14           * Constructor - create temporary directories and build paths to
  15           * external 'helper' binaries.
  16           * Other platforms could/should be added
  17           */
  18          public function __construct() {
  19              global $CFG;
  20  
  21              // construct directory structure
  22              $this->temp_dir = $CFG->tempdir . "/latex";
  23              make_temp_directory('latex');
  24          }
  25  
  26          /**
  27           * Old syntax of class constructor. Deprecated in PHP7.
  28           *
  29           * @deprecated since Moodle 3.1
  30           */
  31          public function latex() {
  32              debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
  33              self::__construct();
  34          }
  35  
  36          /**
  37           * Accessor function for support_platform field.
  38           * @return boolean value of supported_platform
  39           */
  40          function supported() {
  41              return $this->supported_platform;
  42          }
  43  
  44          /**
  45           * Turn the bit of TeX into a valid latex document
  46           * @param string $forumula the TeX formula
  47           * @param int $fontsize the font size
  48           * @return string the latex document
  49           */
  50          function construct_latex_document($formula, $fontsize = 12) {
  51              // $fontsize don't affects to formula's size. $density can change size
  52              $doc = "\\documentclass[{$fontsize}pt]{article}\n";
  53              $doc .= get_config('filter_tex', 'latexpreamble');
  54              $doc .= "\\pagestyle{empty}\n";
  55              $doc .= "\\begin{document}\n";
  56              if (preg_match("/^[[:space:]]*\\\\begin\\{(gather|align|alignat|multline).?\\}/i", $formula)) {
  57                 $doc .= "$formula\n";
  58              } else {
  59                 $doc .= "$ {$formula} $\n";
  60              }
  61              $doc .= "\\end{document}\n";
  62  
  63              // Sanitize the whole document (rather than just the formula) to make sure no one can bypass sanitization
  64              // by using \newcommand in preamble to give an alias to a blocked command.
  65              $doc = filter_tex_sanitize_formula($doc);
  66  
  67              return $doc;
  68          }
  69  
  70          /**
  71           * execute an external command, with optional logging
  72           * @param string $command command to execute
  73           * @param file $log valid open file handle - log info will be written to this file
  74           * @return return code from execution of command
  75           */
  76          function execute( $command, $log=null ) {
  77              $output = array();
  78              exec( $command, $output, $return_code );
  79              if ($log) {
  80                  fwrite( $log, "COMMAND: $command \n" );
  81                  $outputs = implode( "\n", $output );
  82                  fwrite( $log, "OUTPUT: $outputs \n" );
  83                  fwrite( $log, "RETURN_CODE: $return_code\n " );
  84              }
  85              return $return_code;
  86          }
  87  
  88          /**
  89           * Render TeX string into gif/png
  90           * @param string $formula TeX formula
  91           * @param string $filename filename for output (including extension)
  92           * @param int $fontsize font size
  93           * @param int $density density value for .ps to .gif/.png conversion
  94           * @param string $background background color (e.g, #FFFFFF).
  95           * @param file $log valid open file handle for optional logging (debugging only)
  96           * @return bool true if successful
  97           */
  98          function render( $formula, $filename, $fontsize=12, $density=240, $background='', $log=null ) {
  99  
 100              global $CFG;
 101  
 102              // quick check - will this work?
 103              $pathlatex = get_config('filter_tex', 'pathlatex');
 104              if (empty($pathlatex)) {
 105                  return false;
 106              }
 107              $pathlatex = escapeshellarg(trim($pathlatex, " '\""));
 108  
 109              $doc = $this->construct_latex_document( $formula, $fontsize );
 110  
 111              // construct some file paths
 112              $convertformat = get_config('filter_tex', 'convertformat');
 113              if (!strpos($filename, ".{$convertformat}")) {
 114                  $convertformat = 'png';
 115              }
 116              $filename = str_replace(".{$convertformat}", '', $filename);
 117              $tex = "$filename.tex"; // Absolute paths won't work with openin_any = p setting.
 118              $dvi = "{$this->temp_dir}/$filename.dvi";
 119              $ps  = "{$this->temp_dir}/$filename.ps";
 120              $img = "{$this->temp_dir}/$filename.{$convertformat}";
 121  
 122              // Change directory to temp dir so that we can work with relative paths.
 123              chdir($this->temp_dir);
 124  
 125              // turn the latex doc into a .tex file in the temp area
 126              $fh = fopen( $tex, 'w' );
 127              fputs( $fh, $doc );
 128              fclose( $fh );
 129  
 130              // run latex on document
 131              $command = "$pathlatex --interaction=nonstopmode --halt-on-error $tex";
 132  
 133              if ($this->execute($command, $log)) { // It allways False on Windows
 134  //                return false;
 135              }
 136  
 137              // run dvips (.dvi to .ps)
 138              $pathdvips = escapeshellarg(trim(get_config('filter_tex', 'pathdvips'), " '\""));
 139              $command = "$pathdvips -q -E $dvi -o $ps";
 140              if ($this->execute($command, $log )) {
 141                  return false;
 142              }
 143  
 144              // Run convert on document (.ps to .gif/.png) or run dvisvgm (.ps to .svg).
 145              if ($background) {
 146                  $bg_opt = "-transparent \"$background\""; // Makes transparent background
 147              } else {
 148                  $bg_opt = "";
 149              }
 150              if ($convertformat == 'svg') {
 151                  $pathdvisvgm = escapeshellarg(trim(get_config('filter_tex', 'pathdvisvgm'), " '\""));
 152                  $command = "$pathdvisvgm -E $ps -o $img";
 153              } else {
 154                  $pathconvert = escapeshellarg(trim(get_config('filter_tex', 'pathconvert'), " '\""));
 155                  $command = "$pathconvert -density $density -trim $bg_opt $ps $img";
 156              }
 157              if ($this->execute($command, $log )) {
 158                  return false;
 159              }
 160  
 161              return $img;
 162          }
 163  
 164          /**
 165           * Delete files created in temporary area
 166           * Don't forget to copy the final gif/png before calling this
 167           * @param string $filename file base (no extension)
 168           */
 169          function clean_up( $filename ) {
 170              global $CFG;
 171  
 172              unlink( "{$this->temp_dir}/$filename.tex" );
 173              unlink( "{$this->temp_dir}/$filename.dvi" );
 174              unlink( "{$this->temp_dir}/$filename.ps" );
 175              $convertformat = get_config('filter_tex', 'convertformat');
 176              unlink( "{$this->temp_dir}/$filename.{$convertformat}" );
 177              unlink( "{$this->temp_dir}/$filename.aux" );
 178              unlink( "{$this->temp_dir}/$filename.log" );
 179              return;
 180          }
 181  
 182      }
 183  
 184  
 185