00001 <?php
00016 class MathRenderer {
00017 var $mode = MW_MATH_MODERN;
00018 var $tex = '';
00019 var $inputhash = '';
00020 var $hash = '';
00021 var $html = '';
00022 var $mathml = '';
00023 var $conservativeness = 0;
00024
00025 function __construct( $tex, $params=array() ) {
00026 $this->tex = $tex;
00027 $this->params = $params;
00028 }
00029
00030 function setOutputMode( $mode ) {
00031 $this->mode = $mode;
00032 }
00033
00034 function render() {
00035 global $wgTmpDirectory, $wgInputEncoding;
00036 global $wgTexvc;
00037 $fname = 'MathRenderer::render';
00038
00039 if( $this->mode == MW_MATH_SOURCE ) {
00040 # No need to render or parse anything more!
00041 return ('$ '.htmlspecialchars( $this->tex ).' $');
00042 }
00043 if( $this->tex == '' ) {
00044 return; # bug 8372
00045 }
00046
00047 if( !$this->_recall() ) {
00048 # Ensure that the temp and output directories are available before continuing...
00049 if( !file_exists( $wgTmpDirectory ) ) {
00050 if( !wfMkdirParents( $wgTmpDirectory ) ) {
00051 return $this->_error( 'math_bad_tmpdir' );
00052 }
00053 } elseif( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) {
00054 return $this->_error( 'math_bad_tmpdir' );
00055 }
00056
00057 if( function_exists( 'is_executable' ) && !is_executable( $wgTexvc ) ) {
00058 return $this->_error( 'math_notexvc' );
00059 }
00060 $cmd = $wgTexvc . ' ' .
00061 escapeshellarg( $wgTmpDirectory ).' '.
00062 escapeshellarg( $wgTmpDirectory ).' '.
00063 escapeshellarg( $this->tex ).' '.
00064 escapeshellarg( $wgInputEncoding );
00065
00066 if ( wfIsWindows() ) {
00067 # Invoke it within cygwin sh, because texvc expects sh features in its default shell
00068 $cmd = 'sh -c ' . wfEscapeShellArg( $cmd );
00069 }
00070
00071 wfDebug( "TeX: $cmd\n" );
00072 $contents = `$cmd`;
00073 wfDebug( "TeX output:\n $contents\n---\n" );
00074
00075 if (strlen($contents) == 0) {
00076 return $this->_error( 'math_unknown_error' );
00077 }
00078
00079 $retval = substr ($contents, 0, 1);
00080 $errmsg = '';
00081 if (($retval == 'C') || ($retval == 'M') || ($retval == 'L')) {
00082 if ($retval == 'C') {
00083 $this->conservativeness = 2;
00084 } else if ($retval == 'M') {
00085 $this->conservativeness = 1;
00086 } else {
00087 $this->conservativeness = 0;
00088 }
00089 $outdata = substr ($contents, 33);
00090
00091 $i = strpos($outdata, "\000");
00092
00093 $this->html = substr($outdata, 0, $i);
00094 $this->mathml = substr($outdata, $i+1);
00095 } else if (($retval == 'c') || ($retval == 'm') || ($retval == 'l')) {
00096 $this->html = substr ($contents, 33);
00097 if ($retval == 'c') {
00098 $this->conservativeness = 2;
00099 } else if ($retval == 'm') {
00100 $this->conservativeness = 1;
00101 } else {
00102 $this->conservativeness = 0;
00103 }
00104 $this->mathml = NULL;
00105 } else if ($retval == 'X') {
00106 $this->html = NULL;
00107 $this->mathml = substr ($contents, 33);
00108 $this->conservativeness = 0;
00109 } else if ($retval == '+') {
00110 $this->html = NULL;
00111 $this->mathml = NULL;
00112 $this->conservativeness = 0;
00113 } else {
00114 $errbit = htmlspecialchars( substr($contents, 1) );
00115 switch( $retval ) {
00116 case 'E':
00117 $errmsg = $this->_error( 'math_lexing_error', $errbit );
00118 break;
00119 case 'S':
00120 $errmsg = $this->_error( 'math_syntax_error', $errbit );
00121 break;
00122 case 'F':
00123 $errmsg = $this->_error( 'math_unknown_function', $errbit );
00124 break;
00125 default:
00126 $errmsg = $this->_error( 'math_unknown_error', $errbit );
00127 }
00128 }
00129
00130 if ( !$errmsg ) {
00131 $this->hash = substr ($contents, 1, 32);
00132 }
00133
00134 wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) );
00135
00136 if ( $errmsg ) {
00137 return $errmsg;
00138 }
00139
00140 if (!preg_match("/^[a-f0-9]{32}$/", $this->hash)) {
00141 return $this->_error( 'math_unknown_error' );
00142 }
00143
00144 if( !file_exists( "$wgTmpDirectory/{$this->hash}.png" ) ) {
00145 return $this->_error( 'math_image_error' );
00146 }
00147
00148 if( filesize( "$wgTmpDirectory/{$this->hash}.png" ) == 0 ) {
00149 return $this->_error( 'math_image_error' );
00150 }
00151
00152 $hashpath = $this->_getHashPath();
00153 if( !file_exists( $hashpath ) ) {
00154 if( !@wfMkdirParents( $hashpath, 0755 ) ) {
00155 return $this->_error( 'math_bad_output' );
00156 }
00157 } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
00158 return $this->_error( 'math_bad_output' );
00159 }
00160
00161 if( !rename( "$wgTmpDirectory/{$this->hash}.png", "$hashpath/{$this->hash}.png" ) ) {
00162 return $this->_error( 'math_output_error' );
00163 }
00164
00165 # Now save it back to the DB:
00166 if ( !wfReadOnly() ) {
00167 $outmd5_sql = pack('H32', $this->hash);
00168
00169 $md5_sql = pack('H32', $this->md5); # Binary packed, not hex
00170
00171 $dbw = wfGetDB( DB_MASTER );
00172 $dbw->replace( 'math', array( 'math_inputhash' ),
00173 array(
00174 'math_inputhash' => $dbw->encodeBlob($md5_sql),
00175 'math_outputhash' => $dbw->encodeBlob($outmd5_sql),
00176 'math_html_conservativeness' => $this->conservativeness,
00177 'math_html' => $this->html,
00178 'math_mathml' => $this->mathml,
00179 ), $fname
00180 );
00181 }
00182
00183
00184 global $wgUseSquid;
00185 if ( $wgUseSquid ) {
00186 $urls = array( $this->_mathImageUrl() );
00187 $u = new SquidUpdate( $urls );
00188 $u->doUpdate();
00189 }
00190 }
00191
00192 return $this->_doRender();
00193 }
00194
00195 function _error( $msg, $append = '' ) {
00196 $mf = htmlspecialchars( wfMsg( 'math_failure' ) );
00197 $errmsg = htmlspecialchars( wfMsg( $msg ) );
00198 $source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) );
00199 return "<strong class='error'>$mf ($errmsg$append): $source</strong>\n";
00200 }
00201
00202 function _recall() {
00203 global $wgMathDirectory;
00204 $fname = 'MathRenderer::_recall';
00205
00206 $this->md5 = md5( $this->tex );
00207 $dbr = wfGetDB( DB_SLAVE );
00208 $rpage = $dbr->selectRow( 'math',
00209 array( 'math_outputhash','math_html_conservativeness','math_html','math_mathml' ),
00210 array( 'math_inputhash' => $dbr->encodeBlob(pack("H32", $this->md5))), # Binary packed, not hex
00211 $fname
00212 );
00213
00214 if( $rpage !== false ) {
00215 # Tailing 0x20s can get dropped by the database, add it back on if necessary:
00216 $xhash = unpack( 'H32md5', $dbr->decodeBlob($rpage->math_outputhash) . " " );
00217 $this->hash = $xhash ['md5'];
00218
00219 $this->conservativeness = $rpage->math_html_conservativeness;
00220 $this->html = $rpage->math_html;
00221 $this->mathml = $rpage->math_mathml;
00222
00223 $filename = $this->_getHashPath() . "/{$this->hash}.png";
00224 if( file_exists( $filename ) ) {
00225 if( filesize( $filename ) == 0 ) {
00226
00227 @unlink( $filename );
00228 } else {
00229 return true;
00230 }
00231 }
00232
00233 if( file_exists( $wgMathDirectory . "/{$this->hash}.png" ) ) {
00234 $hashpath = $this->_getHashPath();
00235
00236 if( !file_exists( $hashpath ) ) {
00237 if( !@wfMkdirParents( $hashpath, 0755 ) ) {
00238 return false;
00239 }
00240 } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
00241 return false;
00242 }
00243 if ( function_exists( "link" ) ) {
00244 return link ( $wgMathDirectory . "/{$this->hash}.png",
00245 $hashpath . "/{$this->hash}.png" );
00246 } else {
00247 return rename ( $wgMathDirectory . "/{$this->hash}.png",
00248 $hashpath . "/{$this->hash}.png" );
00249 }
00250 }
00251
00252 }
00253
00254 # Missing from the database and/or the render cache
00255 return false;
00256 }
00257
00261 function _doRender() {
00262 if( $this->mode == MW_MATH_MATHML && $this->mathml != '' ) {
00263 return Xml::tags( 'math',
00264 $this->_attribs( 'math',
00265 array( 'xmlns' => 'http://www.w3.org/1998/Math/MathML' ) ),
00266 $this->mathml );
00267 }
00268 if (($this->mode == MW_MATH_PNG) || ($this->html == '') ||
00269 (($this->mode == MW_MATH_SIMPLE) && ($this->conservativeness != 2)) ||
00270 (($this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML) && ($this->conservativeness == 0))) {
00271 return $this->_linkToMathImage();
00272 } else {
00273 return Xml::tags( 'span',
00274 $this->_attribs( 'span',
00275 array( 'class' => 'texhtml' ) ),
00276 $this->html );
00277 }
00278 }
00279
00280 function _attribs( $tag, $defaults=array(), $overrides=array() ) {
00281 $attribs = Sanitizer::validateTagAttributes( $this->params, $tag );
00282 $attribs = Sanitizer::mergeAttributes( $defaults, $attribs );
00283 $attribs = Sanitizer::mergeAttributes( $attribs, $overrides );
00284 return $attribs;
00285 }
00286
00287 function _linkToMathImage() {
00288 $url = $this->_mathImageUrl();
00289
00290 return Xml::element( 'img',
00291 $this->_attribs(
00292 'img',
00293 array(
00294 'class' => 'tex',
00295 'alt' => $this->tex ),
00296 array(
00297 'src' => $url ) ) );
00298 }
00299
00300 function _mathImageUrl() {
00301 global $wgMathPath;
00302 $dir = $this->_getHashSubPath();
00303 return "$wgMathPath/$dir/{$this->hash}.png";
00304 }
00305
00306 function _getHashPath() {
00307 global $wgMathDirectory;
00308 $path = $wgMathDirectory .'/' . $this->_getHashSubPath();
00309 wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" );
00310 return $path;
00311 }
00312
00313 function _getHashSubPath() {
00314 return substr($this->hash, 0, 1)
00315 .'/'. substr($this->hash, 1, 1)
00316 .'/'. substr($this->hash, 2, 1);
00317 }
00318
00319 public static function renderMath( $tex, $params=array() ) {
00320 global $wgUser;
00321 $math = new MathRenderer( $tex, $params );
00322 $math->setOutputMode( $wgUser->getOption('math'));
00323 return $math->render();
00324 }
00325 }