1<?php
2/**
3 * LaTeX Rendering Class
4 * Copyright (C) 2003  Benjamin Zeiss <zeiss@math.uni-goettingen.de>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 * --------------------------------------------------------------------
20 * @author Benjamin Zeiss <zeiss@math.uni-goettingen.de>
21 * @version v0.8
22 * @package latexrender
23 *
24 */
25
26class LatexRender {
27
28    // ====================================================================================
29    // Variable Definitions
30    // ====================================================================================
31    var $_picture_path = "";
32    var $_picture_path_httpd = "";
33    // i was too lazy to write mutator functions for every single program used
34    // just access it outside the class or change it here if nescessary
35
36	/////////////////////////////////////////
37    ////// NOTE - DO NOT CHANGE THESE SETTINGS. USE THE CONFIG MANAGER
38    //////    IN DOKUWIKI INSTEAD; THESE ARE OVERWRITTEN.
39    var $_tmp_dir = "";
40    var $_keep_tmp = false;   // keep temporary files? (good for debug)
41    var $_latex_path = "latex";
42    var $_dvips_path = "dvips";
43    var $_convert_path = "convert";
44    var $_identify_path = "identify";
45    var $_xsize_limit = 1000;
46    var $_ysize_limit = 500;
47    var $_string_length_limit = 2000;
48    var $_image_format = "png"; //change to png if you prefer
49	////////////////////////////////////
50
51		var $_font_size = 10;
52		var $_latexclass = "article"; //install extarticle class if you wish to have smaller font sizes
53    var $_tmp_filename;
54    // this most certainly needs to be extended. in the long term it is planned to use
55    // a positive list for more security. this is hopefully enough for now. i'd be glad
56    // to receive more bad tags !
57    var $_latex_tags_blacklist = array(
58	   "include","def","command","loop","repeat","open","toks","output","input",
59	   "catcode","name","^^",
60	   "\\every","\\errhelp","\\errorstopmode","\\scrollmode","\\nonstopmode","\\batchmode",
61	   "\\read","\\write","csname","\\newhelp","\\uppercase", "\\lowercase","\\relax","\\aftergroup",
62	   "\\afterassignment","\\expandafter","\\noexpand","\\special"
63	    );
64    var $_errorcode = 0;
65		var $_errorextra = "";
66		var $_filename;
67
68
69    // ====================================================================================
70    // constructor
71    // ====================================================================================
72
73    /**
74	* Initializes the class
75	*
76	* @param string path where the rendered pictures should be stored
77	* @param string same path, but from the httpd chroot
78	*/
79    function LatexRender($picture_path,$picture_path_httpd,$tmp_dir) {
80	   $this->_picture_path = $picture_path;
81	   $this->_picture_path_httpd = $picture_path_httpd;
82	   $this->_tmp_dir = $tmp_dir;
83    }
84
85    // ====================================================================================
86    // public functions
87    // ====================================================================================
88
89    /**
90	* Picture path Mutator function
91	*
92	* @param string sets the current picture path to a new location
93	*/
94    function setPicturePath($name) {
95	   $this->_picture_path = $name;
96    }
97
98    /**
99	* Picture path Mutator function
100	*
101	* @returns the current picture path
102	*/
103    function getPicturePath() {
104	   return $this->_picture_path;
105    }
106
107    /**
108	* Picture path HTTPD Mutator function
109	*
110	* @param string sets the current httpd picture path to a new location
111	*/
112    function setPicturePathHTTPD($name) {
113	   $this->_picture_path_httpd = $name;
114    }
115
116    /**
117	* Picture path HTTPD Mutator function
118	*
119	* @returns the current picture path
120	*/
121    function getPicturePathHTTPD() {
122	   return $this->_picture_path_httpd;
123    }
124
125    /**
126	* Tries to match the LaTeX Formula given as argument against the
127	* formula cache. If the picture has not been rendered before, it'll
128	* try to render the formula and drop it in the picture cache directory.
129	*
130	* @param string formula in LaTeX format
131	* @returns the webserver based URL to a picture which contains the
132	* requested LaTeX formula. If anything fails, the resultvalue is false.
133	*/
134    function getFormulaURL($latex_formula) {
135	   // circumvent certain security functions of web-software which
136	   // is pretty pointless right here
137	   $latex_formula = preg_replace("/&gt;/i", ">", $latex_formula);
138	   $latex_formula = preg_replace("/&lt;/i", "<", $latex_formula);
139
140		 $this->latex_document = $this->_preamble."\n".trim($latex_formula)."\n".$this->_postamble;
141
142	   $formula_hash = md5($latex_formula);
143
144	   $filename = "img".$formula_hash.'.'.$this->_image_format;
145	   $full_path_filename = $this->getPicturePath()."/".$filename;
146		 $this->_filename = $full_path_filename;
147
148	   if (is_readable($full_path_filename)) {
149		  return $this->getPicturePathHTTPD()."/".$filename;
150	   } else {
151		  // security filter: reject too-long formulas
152		  if (strlen($latex_formula) > $this->_string_length_limit) {
153		  	$this->_errorcode = 1;
154							$this->_errorextra = ': '.strlen($latex_formula);
155		    return false;
156		  }
157
158		  // security filter: try to match against LaTeX-Tags Blacklist
159		  for ($i=0;$i<sizeof($this->_latex_tags_blacklist);$i++) {
160			 if (stristr($latex_formula,$this->_latex_tags_blacklist[$i])) {
161			 	$this->_errorcode = 2;
162			   return false;
163			 }
164		  }
165
166		  // security checks assume correct formula, let's render it
167		  if ($this->renderLatex($this->latex_document,$full_path_filename)) {
168			 return $this->getPicturePathHTTPD().$filename;
169		  } else {
170			 // uncomment if required
171			 // $this->_errorcode = 3;
172			 return false;
173		  }
174	   }
175    }
176
177    // ====================================================================================
178    // private functions
179    // ====================================================================================
180
181    /**
182	* returns the dimensions of a picture file using 'identify' of the
183	* imagemagick tools. The resulting array can be adressed with either
184	* $dim[0] / $dim[1] or $dim["x"] / $dim["y"]
185	*
186	* @param string path to a picture
187	* @returns array containing the picture dimensions
188	*/
189    function getDimensions($filename) {
190	   $output=$this->myexec($this->_identify_path." ".$filename, $status);
191	   $result=explode(" ",$output);
192	   $dim=explode("x",$result[2]);
193	   $dim["x"] = $dim[0];
194	   $dim["y"] = $dim[1];
195
196	   return $dim;
197    }
198
199    /**
200	* Renders a LaTeX formula by the using the following method:
201	*  - write the formula into a wrapped tex-file in a temporary directory
202	*    and change to it
203	*  - Create a DVI file using latex (tetex)
204	*  - Convert DVI file to Postscript (PS) using dvips (tetex)
205	*  - convert, trim and add transparancy by using 'convert' from the
206	*    imagemagick package.
207	*  - Save the resulting image to the picture cache directory using an
208	*    md5 hash as filename. Already rendered formulas can be found directly
209	*    this way.
210	*
211	* @param string LaTeX formula
212	* @returns true if the picture has been successfully saved to the picture
213	*		cache directory
214	*/
215    function renderLatex($latex_document,$destination) {
216
217	   $current_dir = getcwd();
218
219	   chdir($this->_tmp_dir);
220
221	   $this->_tmp_filename = md5(rand().$destination);
222
223		 $this->_cmdout = " >> ".$this->_tmp_filename.".cmd 2>&1";
224	   // create temporary latex file
225	   $fp = fopen($this->_tmp_dir."/".$this->_tmp_filename.".tex","w");
226	   fputs($fp,$latex_document);
227	   fclose($fp);
228
229	   // create temporary dvi file
230	   $command = $this->_latex_path." --interaction=nonstopmode ".$this->_tmp_filename.".tex";
231	   $this->myexec($command,$status_latex);
232
233		// LaTeXing only fails if DVI doesn't exist. - let's ignore some minor errors.
234	  if (!file_exists($this->_tmp_filename.".dvi"))
235		{
236			if( ! $this->_keep_tmp)
237				$this->cleanTemporaryDirectory();
238			chdir($current_dir);
239			$this->_errorcode = 4; /// Error 4: latexing failed
240			return false;
241		}
242
243	   // convert dvi file to postscript using dvips
244	   $command = $this->_dvips_path." -E ".$this->_tmp_filename.".dvi"." -o ".$this->_tmp_filename.".ps";
245	   $this->myexec($command,$status_dvips);
246
247
248	   // imagemagick convert ps to image and trim picture
249	   $command = $this->_convert_path." ".$this->_tmp_filename.".ps ".
250				$this->_tmp_filename.".".$this->_image_format;
251	   $this->myexec($command,$status_convert);
252
253
254		 if ($status_dvips || $status_convert) {
255			if( ! $this->_keep_tmp)
256				$this->cleanTemporaryDirectory();
257			chdir($current_dir);
258			$this->_errorcode = 6;
259			return false;
260		}
261
262	   // test picture for correct dimensions
263	   $dim = $this->getDimensions($this->_tmp_filename.".".$this->_image_format);
264
265	   if ( ($dim["x"] > $this->_xsize_limit) or ($dim["y"] > $this->_ysize_limit)) {
266		  if( ! $this->_keep_tmp)
267				$this->cleanTemporaryDirectory();
268			chdir($current_dir);
269		  $this->_errorcode = 5; // image too big.
270		  $this->_errorextra = ": " . $dim["x"] . "x" . $dim["y"];
271		  return false;
272	   }
273
274	   // copy temporary formula file to cahed formula directory
275	   $status_code = copy($this->_tmp_filename.".".$this->_image_format,$destination);
276		 chdir($current_dir);
277
278	   if( ! $this->_keep_tmp)
279				$this->cleanTemporaryDirectory();
280
281	   if (!$status_code) { $this->_errorcode = 7; return false; }
282
283	   return true;
284    }
285
286		//// Run command and append it to _cmdoutput if that variable exists. (for debug).
287		function myexec($cmd,&$status) {
288			$cmd = "$cmd 2>&1";
289			$lastline = exec($cmd,$output,$status);
290
291//			//strip trailing empty lines from output
292//			for($i = count($output)-1 ; $i > 0 ; $i -= 1)
293//				if($output[$i]) break;
294//			$lastline = $output[$i];
295
296			if(isset($this->_cmdoutput))
297				$this->_cmdoutput .= "\n>>>>> $cmd\n".trim(implode(PHP_EOL,$output)).PHP_EOL."  --- exit status ".$status;
298
299			return $lastline;
300		}
301
302   /**
303		* Cleans the temporary directory
304		*/
305    function cleanTemporaryDirectory() {
306//	   $current_dir = getcwd();
307//	   chdir($this->_tmp_dir);
308
309			@unlink($this->_tmp_dir."/".$this->_tmp_filename.".tex");
310			@unlink($this->_tmp_dir."/".$this->_tmp_filename.".aux");
311			@unlink($this->_tmp_dir."/".$this->_tmp_filename.".log");
312			@unlink($this->_tmp_dir."/".$this->_tmp_filename.".dvi");
313			@unlink($this->_tmp_dir."/".$this->_tmp_filename.".ps");
314			@unlink($this->_tmp_dir."/".$this->_tmp_filename.".".$this->_image_format);
315
316//	   chdir($current_dir);
317    }
318
319}
320