329 lines
12 KiB
PHP
329 lines
12 KiB
PHP
<?php
|
|
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
# ***** BEGIN LICENSE BLOCK *****
|
|
# This file is part of Plume Framework, a simple PHP Application Framework.
|
|
# Copyright (C) 2001-2007 Loic d'Anterroches and contributors.
|
|
#
|
|
# Plume Framework is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU Lesser General Public License as published by
|
|
# the Free Software Foundation; either version 2.1 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Plume Framework is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
#
|
|
# ***** END LICENSE BLOCK ***** */
|
|
|
|
/**
|
|
* Given a shunk of a latex equation code, render it and return a
|
|
* corresponding image file.
|
|
*
|
|
* Based on code by:
|
|
* Benjamin Zeiss
|
|
* Copyright (C) 2003 Benjamin Zeiss <zeiss@math.uni-goettingen.de>
|
|
* 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<sizeof($this->_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';
|
|
}
|
|
|
|
}
|