* http://www.mayer.dial.pipex.com/tex.htm * Kjell Magne Fauske * http://www.fauskes.net/nb/htmleqII/ * * Example usage: * * $latex = new Pluf_Text_Latex_Equation(); * if (true === $latex->render('e = mc^2')) { * $png_file = $latex->output_file; * $png_full_path = $latex->output_path; * } else { * $error = $latex->error_code; * $msg = $latex->error_msg; * } * * Note that the class is not using exception to return the errors, * that way you can easily grab the error and depending on the error * code/message generate a special .png file to display. */ class Pluf_Text_Latex_Equation { public $tmp_dir = '/tmp'; public $output_dir = '/tmp'; public $encoding = 'utf-8'; public $fragment = ''; public $latex_path = '/usr/bin/latex'; public $dvipng_path = '/usr/bin/dvipng'; public $resolution = '120'; public $bg_color = 'ffffff'; public $debug = false; /** * Black listing of tags. * * this most certainly needs to be extended. in the long term it * is planned to use a positive list for more security. this is * hopefully enough for now. i'd be glad to receive more bad tags * ! */ public $tags_blacklist = array( 'include', 'def', 'command', 'loop', 'repeat', 'open', 'toks', 'output', 'input', 'catcode', 'name', '^^', '\\every', '\\errhelp', '\\errorstopmode', '\\scrollmode', '\\nonstopmode', '\\batchmode', '\\read', '\\write', 'csname', '\\newhelp', '\\uppercase', '\\lowercase', '\\relax', '\\aftergroup', '\\afterassignment', '\\expandafter', '\\noexpand', '\\special'); public $error_code = 0; public $error_msg = ''; /* var $_latex_path = "/usr/local/bin/latex"; var $_dvips_path = "/usr/local/bin/dvips"; var $_convert_path = "/usr/local/bin/convert"; var $_identify_path="/usr/local/bin/identify"; var $_formula_density = 100; // originally 110 var $_xsize_limit = 700; var $_ysize_limit = 1000; var $_string_length_limit = 1000; var $_font_size = 10; var $_latexclass = "article"; //install extarticle class if you wish to have smaller font sizes var $_tmp_filename; var $_image_format = "png"; //change to png if you prefer ); var $_errorcode = 0; var $_errorextra = ''; */ /** * Initializes the class * * @param string Output directory (null) * @param string Temp directory (null) */ public function __construct($output_dir=null, $tmp_dir=null) { if (!is_null($output_dir)) { $this->output_dir = $output_dir; } if (!is_null($tmp_dir)) { $this->tmp_dir = $tmp_dir; } } /** * Tries to match the LaTeX Formula given as argument against the * formula cache. If the picture has not been rendered before, it'll * try to render the formula and drop it in the picture cache directory. * * @param string formula in LaTeX format * @returns the webserver based URL to a picture which contains the * requested LaTeX formula. If anything fails, the resultvalue is false. */ function getFormulaURL($latex_formula) { // circumvent certain security functions of web-software which // is pretty pointless right here $latex_formula = preg_replace("/>/i", ">", $latex_formula); $latex_formula = preg_replace("/</i", "<", $latex_formula); $formula_hash = md5($latex_formula.$this->_font_size); $filename = $formula_hash.".".$this->_image_format; $full_path_filename = $this->getPicturePath()."/".$filename; if (is_file($full_path_filename)) { return $this->getPicturePathHTTPD()."/".$filename; } else { // security filter: reject too long formulas if (strlen($latex_formula) > $this->_string_length_limit) { $this->_errorcode = 1; return false; } // security filter: try to match against LaTeX-Tags Blacklist for ($i=0;$i_latex_tags_blacklist);$i++) { if (stristr($latex_formula,$this->_latex_tags_blacklist[$i])) { $this->_errorcode = 2; return false; } } // security checks assume correct formula, let's render it if ($this->renderLatex($latex_formula)) { return $this->getPicturePathHTTPD()."/".$filename; } else { // uncomment if required //$this->_errorcode = 3; return false; } } } /** * Get the Latex preamble. * * You can overwrite this function if you want. * * @return string Latex preamble. */ function getPreamble() { return '\documentclass{article}'."\n" .'\usepackage{amsmath}'."\n" .'\usepackage{amsthm}'."\n" .'\usepackage{amssymb}'."\n" .'\usepackage{bm}'."\n" .'% \newcommand{\mx}[1]{\mathbf{\bm{#1}}} % Matrix command'."\n" .'% \newcommand{\vc}[1]{\mathbf{\bm{#1}}} % Vector command'."\n" .'% \newcommand{\T}{\text{T}} % Transpose'."\n" .'\pagestyle{empty}'."\n" .'\begin{document}'."\n"; } /** * Renders a LaTeX formula by the using the following method: * - write the formula into a wrapped tex-file in a temporary directory * and change to it * - Create a DVI file using latex (tetex) * - Convert DVI file to png or gif using dvipng * - Save the resulting image to the picture cache directory using an * md5 hash as filename. Already rendered formulas can be found directly * this way. * * @param string LaTeX formula * @param bool Render an inline formulat (false) * @return true if the picture has been successfully saved to the picture * cache directory */ function render($latex_formula, $inline=false) { $this->output_file = ''; $this->output_path = ''; $this->error_code = 0; $this->error_msg = ''; $output_file = $this->getOutputFile($latex_formula, $inline); if (file_exists($this->output_dir.'/'.$output_file)) { $this->output_file = $output_file; $this->output_path = $this->output_dir.'/'.$output_file; return true; } if (!$this->isCleanLatex($latex_formula)) { // error code and message set by the method return false; } if ($inline) { $body = sprintf("$%s$ \n \\newpage \n", $latex_formula); } else { $body = sprintf("\\[\n%s \n\\] \n \\newpage \n", $latex_formula); } $latex_document = $this->getPreamble().$body; $latex_document .= '\end{document}'; $current_dir = getcwd(); chdir($this->tmp_dir); // create temporary latex file $tmp_filename = md5($latex_formula.rand()); $fp = fopen($this->tmp_dir.'/'.$tmp_filename.'.tex', 'a+'); fputs($fp, $latex_document); fclose($fp); // create temporary dvi file $command = $this->latex_path.' --interaction=nonstopmode '.$tmp_filename.'.tex'; exec($command); if (!is_file($tmp_filename.'.dvi')) { $this->clean($tmp_filename); chdir($current_dir); $this->error_code = 4; $this->error_msg = __('Unable to generate the dvi file.'); return false; } // convert dvi file to png using dvipng $bg = 'rgb '; $_r = sprintf('%01.2f', hexdec(substr($this->bg_color, 0, 2))/255); $_g = sprintf('%01.2f', hexdec(substr($this->bg_color, 2, 2))/255); $_b = sprintf('%01.2f', hexdec(substr($this->bg_color, 4, 2))/255); $command = $this->dvipng_path.' -q -T tight -D '.$this->resolution.' -z 9 -pp -1 -bg "rgb '.$_r.' '.$_g.' '.$_b.'" -bg transparent ' .'-o %s.png %s.dvi'; $command = sprintf($command, $tmp_filename, $tmp_filename); exec($command); if (!is_file($tmp_filename.'.png')) { $this->clean($tmp_filename); chdir($current_dir); $this->error_code = 5; $this->error_msg = __('Unable to generate the png file.'); return false; } $output_file = $this->getOutputFile($latex_formula, $inline); $output_path = $this->output_dir.'/'.$output_file; if (false == rename($tmp_filename.'.png', $output_path) or !is_file($output_path)) { $this->clean($tmp_filename); chdir($current_dir); $this->error_code = 6; $this->error_msg = __('Unable to move the png file.'); return false; } $this->clean($tmp_filename); $this->output_file = $output_file; $this->output_path = $output_path; chdir($current_dir); return true; } /** * Cleans the temporary directory */ public function clean($tmp_filename) { if ($this->debug) return; $current_dir = getcwd(); chdir($this->tmp_dir); $exts = array('tex', 'aux', 'log', 'dvi', 'png'); foreach ($exts as $ext) { if (file_exists($tmp_filename.'.'.$ext)) { @unlink($tmp_filename.'.'.$ext); } } chdir($current_dir); } /** * Check if the latex code is clean. * * @param string Latex code. * @return bool Is Clean. */ public function isCleanLatex($latex) { foreach ($this->tags_blacklist as $tag) { if (false !== stristr($latex, $tag)) { $this->error_code = 2; $this->error_msg = sprintf(__('The LaTeX tag "%s" is not acceptable.'), $tag); return false; } } return true; } /** * Get the output file based on the latex fragment. * * @param string Latex. * @param bool Is the equation an inline equation (false). * @param string Output file with extension without directory. */ public function getOutputFile($latex, $inline=false) { $inline = ($inline) ? 'inline' : 'normal'; return md5($this->bg_color.'###'.$inline.'###'.$this->resolution.'###'.$latex).'.png'; } }