From 926db7c3bdcf8911b5314ffd5c1c7d0064d2734f Mon Sep 17 00:00:00 2001 From: Physikerwelt Date: Sat, 27 Oct 2012 16:30:50 +0200 Subject: [PATCH] Restructuring Math classes The Math.body file which contains the MathRender class was split in the following way: - Math.base contains the base class with the database related stuff and provides an abstract interface - Math.source and Math.MathJax handle the plain tex string output. There are two classes since they will differntiate in the future I think. - Math.texvc contains the "old" implementation of png generation with all the file handling related stuff - Other implementation of math renderer can be added in the same style. - Cleanup to better follow coding conventions. - Changed LockManager to 'fsLockManager' The first attempt restructure the class layout and introduce LaTeXML at the same time was dropped. Instead this was split up into two phases. This commit only deals about the restructuring of the math module design. Change-Id: I9b1d68c4faa8d177d8d0088fa1a5879caed4f1fe --- Math.hooks.php | 28 +-- Math.php | 5 +- MathMathJax.php | 36 ++++ MathRenderer.php | 444 +++++++++++++++----------------------------- MathSource.php | 42 +++++ MathTexvc.php | 317 +++++++++++++++++++++++++++++++ math/README | 5 + mathParserTests.txt | 2 +- 8 files changed, 565 insertions(+), 314 deletions(-) create mode 100644 MathMathJax.php create mode 100644 MathSource.php create mode 100644 MathTexvc.php diff --git a/Math.hooks.php b/Math.hooks.php index 635fefb..ab61f10 100644 --- a/Math.hooks.php +++ b/Math.hooks.php @@ -36,23 +36,27 @@ class MathHooks { /** * Callback function for the parser hook. * - * @param $content + * @param $content (the LaTeX input) * @param $attributes * @param $parser Parser * @return string */ static function mathTagHook( $content, $attributes, $parser ) { global $wgContLang, $wgUseMathJax; - $renderedMath = MathRenderer::renderMath( - $content, $attributes, $parser->getOptions() + if ( trim( $content ) === "" ) { // bug 8372 + return ""; + } + $mode = $parser->getOptions()->getMath(); + $renderer = MathRenderer::getRenderer( + $content, $attributes, $mode ); - - if ( $wgUseMathJax && $parser->getOptions()->getMath() == MW_MATH_MATHJAX ) { + $renderer->setAnchorID( $parser->nextLinkID() ); // Add an ID for referencing the equation later on only used by LaTeXML + $renderedMath = $renderer->render(); + if ( $wgUseMathJax && $mode == MW_MATH_MATHJAX ) { $parser->getOutput()->addModules( array( 'ext.math.mathjax.enabler' ) ); } - $output = $renderedMath; - - return $wgContLang->armourMath( $output ); + $renderer->writeCache(); + return $wgContLang->armourMath( $renderedMath ); } /** @@ -78,13 +82,12 @@ class MathHooks { * @return array of strings */ private static function getMathNames() { + global $wgUseMathJax; $names = array( MW_MATH_PNG => wfMessage( 'mw_math_png' )->escaped(), MW_MATH_SOURCE => wfMessage( 'mw_math_source' )->escaped(), ); - - global $wgUseMathJax; - if( $wgUseMathJax ) { + if ( $wgUseMathJax ) { $names[MW_MATH_MATHJAX] = wfMessage( 'mw_math_mathjax' )->escaped(); } @@ -102,7 +105,6 @@ class MathHooks { # Don't generate TeX PNGs (lack of a sensible current directory causes errors anyway) $wgUser->setOption( 'math', MW_MATH_SOURCE ); - return true; } @@ -114,7 +116,7 @@ class MathHooks { * @return bool */ static function onLoadExtensionSchemaUpdates( $updater = null ) { - if( is_null( $updater ) ) { + if ( is_null( $updater ) ) { throw new MWException( "Math extension is only necessary in 1.18 or above" ); } $map = array( diff --git a/Math.php b/Math.php index 4e6492e..2f6ea2b 100644 --- a/Math.php +++ b/Math.php @@ -7,6 +7,7 @@ * @version 1.0 * @author Tomasz Wegrzanowski * @author Brion Vibber + * @author Moritz Schubotz * @copyright © 2002-2012 various MediaWiki contributors * @license GPLv2 license; info in main package. * @link http://www.mediawiki.org/wiki/Extension:Math Documentation @@ -122,7 +123,9 @@ $wgHooks['ParserTestParser'][] = 'MathHooks::onParserTestParser'; $dir = dirname( __FILE__ ) . '/'; $wgAutoloadClasses['MathHooks'] = $dir . 'Math.hooks.php'; $wgAutoloadClasses['MathRenderer'] = $dir . 'MathRenderer.php'; - +$wgAutoloadClasses['MathTexvc'] = $dir . 'MathTexvc.php'; +$wgAutoloadClasses['MathSource'] = $dir . 'MathSource.php'; +$wgAutoloadClasses['MathMathJax'] = $dir . 'MathMathJax.php'; $wgExtensionMessagesFiles['Math'] = $dir . 'Math.i18n.php'; $wgParserTestFiles[] = $dir . 'mathParserTests.txt'; diff --git a/MathMathJax.php b/MathMathJax.php new file mode 100644 index 0000000..e0daa5d --- /dev/null +++ b/MathMathJax.php @@ -0,0 +1,36 @@ +getAttributes( + 'span', + array( + 'class' => 'tex', + 'dir' => 'ltr' + ) + ), + '$ ' . str_replace( "\n", " ", $this->tex ) . ' $' + ); + } +} diff --git a/MathRenderer.php b/MathRenderer.php index 3039a37..a0b09a9 100644 --- a/MathRenderer.php +++ b/MathRenderer.php @@ -2,34 +2,23 @@ /** * MediaWiki math extension * - * (c) 2002-2012 Tomasz Wegrzanowski, Brion Vibber, and other MediaWiki contributors + * (c) 2002-2012 Tomasz Wegrzanowski, Brion Vibber, Moritz Schubotz, and other MediaWiki contributors * GPLv2 license; info in main package. * - * Contains everything related to parsing * @file - * @ingroup Parser */ -if ( !function_exists('wfEscapeSingleQuotes') ) { - /** - * Escapes a string with single quotes for a UNIX shell. - * It's equivalent to escapeshellarg() in UNIX, but also - * working in Windows, where we need it for cygwin shell. - */ - function wfEscapeSingleQuotes( $str ) { - return "'" . str_replace( "'", "'\\''", $str ) . "'"; - } -} - /** - * Takes LaTeX fragments, sends them to a helper program (texvc) for rendering - * to rasterized PNG and HTML and MathML approximations. An appropriate - * rendering form is picked and returned. + * Abstract base class for math renderers using different technologies. * - * @author Tomasz Wegrzanowski, with additions by Brion Vibber (2003, 2004) - * @ingroup Parser + * @author Tomasz Wegrzanowski + * @author Brion Vibber + * @author Moritz Schubotz */ -class MathRenderer { +abstract class MathRenderer { + /** + * The following variables should made private, as soon it can be verified that they are not being directly accessed by other extensions. + */ var $mode = MW_MATH_PNG; var $tex = ''; var $inputhash = ''; @@ -37,222 +26,99 @@ class MathRenderer { var $html = ''; var $mathml = ''; var $conservativeness = 0; + var $params = ''; + protected $recall; + protected $anchorID = 0; + /** + * Constructs a base MathRenderer + * + * @param string $tex LaTeX markup + * @param array $params HTML attributes + */ public function __construct( $tex, $params = array() ) { $this->tex = $tex; $this->params = $params; } /** - * @return FileBackend + * Static method for rendering math tag + * + * @param string $tex LaTeX markup + * @param array $params HTML attributes + * @param int $mode constant indicating rendering mode + * @return string HTML for math tag */ - protected function getBackend() { - global $wgMathFileBackend, $wgMathDirectory; - - if ( $wgMathFileBackend ) { - return FileBackendGroup::singleton()->get( $wgMathFileBackend ); - } else { - static $backend = null; - if ( !$backend ) { - $backend = new FSFileBackend( array( - 'name' => 'math-backend', - 'lockManager' => 'nullLockManager', - 'containerPaths' => array( 'math-render' => $wgMathDirectory ), - 'fileMode' => 0777 - ) ); - } - return $backend; - } + public static function renderMath( $tex, $params = array(), $mode = MW_MATH_PNG ) { + $renderer = getRenderer( $tex, $params, $mode ); + return $renderer->render(); } - function setOutputMode( $mode ) { + /** + * Static factory method for getting a renderer based on mode + * + * @param string $tex LaTeX markup + * @param array $params HTML attributes + * @param int $mode constant indicating rendering mode + * @return MathRenderer appropriate renderer for mode + */ + public static function getRenderer( $tex, $params = array(), $mode = MW_MATH_PNG ) { + global $wgDefaultUserOptions; $validModes = array( MW_MATH_PNG, MW_MATH_SOURCE, MW_MATH_MATHJAX ); - if ( in_array( $mode, $validModes ) ) { - $this->mode = $mode; - } else { - // Several mixed modes have been phased out. - $this->mode = MW_MATH_PNG; + if ( !in_array( $mode, $validModes ) ) + $mode = $wgDefaultUserOptions['math']; + switch ( $mode ) { + case MW_MATH_SOURCE: + $renderer = new MathSource( $tex, $params ); + break; + case MW_MATH_MATHJAX: + $renderer = new MathMathJax( $tex, $params ); + break; + case MW_MATH_PNG: + default: + $renderer = new MathTexvc( $tex, $params ); } + return $renderer; } - function render() { - global $wgTexvc, $wgTexvcBackgroundColor, $wgUseSquid; + /** + * Returns TeX to HTML + * + * @return string of rendered HTML + */ + abstract public function render(); - if( $this->mode == MW_MATH_SOURCE || $this->mode == MW_MATH_MATHJAX ) { - # No need to render or parse anything more! - # New lines are replaced with spaces, which avoids confusing our parser (bugs 23190, 22818) - return Xml::element( 'span', - $this->_attribs( - 'span', - array( - 'class' => 'tex', - 'dir' => 'ltr' - ) - ), - '$ ' . str_replace( "\n", " ", $this->tex ) . ' $' - ); - } - if( $this->tex == '' ) { - return; # bug 8372 - } - - $tmpDir = wfTempDir(); - if( !$this->_recall() ) { // cache miss - if( !is_executable( $wgTexvc ) ) { - return $this->_error( 'math_notexvc' ); - } - $cmd = $wgTexvc . ' ' . - wfEscapeSingleQuotes( $tmpDir ) . ' '. - wfEscapeSingleQuotes( $tmpDir ) . ' '. - wfEscapeSingleQuotes( $this->tex ) . ' '. - wfEscapeSingleQuotes( 'UTF-8' ) . ' '. - wfEscapeSingleQuotes( $wgTexvcBackgroundColor ); - - if ( wfIsWindows() ) { - # Invoke it within cygwin sh, because texvc expects sh features in its default shell - $cmd = 'sh -c ' . wfEscapeShellArg( $cmd ); - } - - wfDebug( "TeX: $cmd\n" ); - $contents = wfShellExec( $cmd ); - wfDebug( "TeX output:\n $contents\n---\n" ); - - if ( strlen( $contents ) == 0 ) { - if ( !file_exists( $tmpDir ) || !is_writable( $tmpDir ) ) { - return $this->_error( 'math_bad_tmpdir' ); - } else { - return $this->_error( 'math_unknown_error' ); - } - } - - $tempFsFile = new TempFSFile( "$tmpDir/{$this->hash}.png" ); - $tempFsFile->autocollect(); // destroy file when $tempFsFile leaves scope - - $retval = substr( $contents, 0, 1 ); - $errmsg = ''; - if ( ( $retval == 'C' ) || ( $retval == 'M' ) || ( $retval == 'L' ) ) { - if ( $retval == 'C' ) { - $this->conservativeness = 2; - } elseif ( $retval == 'M' ) { - $this->conservativeness = 1; - } else { - $this->conservativeness = 0; - } - $outdata = substr( $contents, 33 ); - - $i = strpos( $outdata, "\000" ); - - $this->html = substr( $outdata, 0, $i ); - $this->mathml = substr( $outdata, $i + 1 ); - } elseif ( ( $retval == 'c' ) || ( $retval == 'm' ) || ( $retval == 'l' ) ) { - $this->html = substr( $contents, 33 ); - if ( $retval == 'c' ) { - $this->conservativeness = 2; - } elseif ( $retval == 'm' ) { - $this->conservativeness = 1; - } else { - $this->conservativeness = 0; - } - $this->mathml = null; - } elseif ( $retval == 'X' ) { - $this->html = null; - $this->mathml = substr( $contents, 33 ); - $this->conservativeness = 0; - } elseif ( $retval == '+' ) { - $this->html = null; - $this->mathml = null; - $this->conservativeness = 0; - } else { - $errbit = htmlspecialchars( substr( $contents, 1 ) ); - switch( $retval ) { - case 'E': - $errmsg = $this->_error( 'math_lexing_error', $errbit ); - break; - case 'S': - $errmsg = $this->_error( 'math_syntax_error', $errbit ); - break; - case 'F': - $errmsg = $this->_error( 'math_unknown_function', $errbit ); - break; - default: - $errmsg = $this->_error( 'math_unknown_error', $errbit ); - } - } - - if ( !$errmsg ) { - $this->hash = substr( $contents, 1, 32 ); - } - - wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) ); - - if ( $errmsg ) { - return $errmsg; - } elseif ( !preg_match( "/^[a-f0-9]{32}$/", $this->hash ) ) { - return $this->_error( 'math_unknown_error' ); - } elseif( !file_exists( "$tmpDir/{$this->hash}.png" ) ) { - return $this->_error( 'math_image_error' ); - } elseif( filesize( "$tmpDir/{$this->hash}.png" ) == 0 ) { - return $this->_error( 'math_image_error' ); - } - - $hashpath = $this->_getHashPath(); // final storage directory - - $backend = $this->getBackend(); - # Create any containers/directories as needed... - if ( !$backend->prepare( array( 'dir' => $hashpath ) )->isOK() ) { - return $this->_error( 'math_output_error' ); - } - // Store the file at the final storage path... - if ( !$backend->quickStore( array( - 'src' => "$tmpDir/{$this->hash}.png", 'dst' => "$hashpath/{$this->hash}.png" - ) )->isOK() - ) { - return $this->_error( 'math_output_error' ); - } - - # Now save it back to the DB: - if ( !wfReadOnly() ) { - $outmd5_sql = pack( 'H32', $this->hash ); - - $md5_sql = pack( 'H32', $this->md5 ); # Binary packed, not hex - - $dbw = wfGetDB( DB_MASTER ); - $dbw->replace( - 'math', - array( 'math_inputhash' ), - array( - 'math_inputhash' => $dbw->encodeBlob( $md5_sql ), - 'math_outputhash' => $dbw->encodeBlob( $outmd5_sql ), - 'math_html_conservativeness' => $this->conservativeness, - 'math_html' => $this->html, - 'math_mathml' => $this->mathml, - ), - __METHOD__ - ); - } - - // If we're replacing an older version of the image, make sure it's current. - if ( $wgUseSquid ) { - $urls = array( $this->_mathImageUrl() ); - $u = new SquidUpdate( $urls ); - $u->doUpdate(); - } - } - - return $this->_doRender(); - } - - function _error( $msg, $append = '' ) { + /** + * Returns an internationalized HTML error string + * + * @param string $msg message key for specific error + * @param string $append string to append after error + * @return string HTML error string + */ + protected function getError( $msg, $append = '' ) { $mf = wfMessage( 'math_failure' )->inContentLanguage()->escaped(); $errmsg = wfMessage( $msg )->inContentLanguage()->escaped(); $source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) ); return "$mf ($errmsg$append): $source\n"; } - function _recall() { - global $wgMathCheckFiles; + /** + * Return hash of input + * + * @return string hash + */ + public function getInputHash() { + // TODO: What happens if $tex is empty? + $dbr = wfGetDB( DB_SLAVE ); + return $dbr->encodeBlob( pack( "H32", md5( $this->tex ) ) ); # Binary packed, not hex + } - $this->md5 = md5( $this->tex ); + /** + * Reads rendering data from database + * + * @return boolean true if read successfully, false otherwise + */ + protected function readFromDB() { $dbr = wfGetDB( DB_SLAVE ); $rpage = $dbr->selectRow( 'math', @@ -261,127 +127,107 @@ class MathRenderer { 'math_mathml' ), array( - 'math_inputhash' => $dbr->encodeBlob( pack( "H32", $this->md5 ) ) # Binary packed, not hex + 'math_inputhash' => $this->getInputHash() ), __METHOD__ ); - - if( $rpage !== false ) { + if ( $rpage !== false ) { # Trailing 0x20s can get dropped by the database, add it back on if necessary: $xhash = unpack( 'H32md5', $dbr->decodeBlob( $rpage->math_outputhash ) . " " ); $this->hash = $xhash['md5']; - $this->conservativeness = $rpage->math_html_conservativeness; $this->html = $rpage->math_html; $this->mathml = $rpage->math_mathml; - - if( !$wgMathCheckFiles ) { - // Short-circuit the file existence & migration checks - return true; - } - - $filename = $this->_getHashPath() . "/{$this->hash}.png"; // final storage path - - $backend = $this->getBackend(); - if( $backend->fileExists( array( 'src' => $filename ) ) ) { - if( $backend->getFileSize( array( 'src' => $filename ) ) == 0 ) { - // Some horrible error corrupted stuff :( - $backend->quickDelete( array( 'src' => $filename ) ); - } else { - return true; // cache hit - } - } + $this->recall = true; + return true; } # Missing from the database and/or the render cache + $this->recall = false; return false; } /** - * Select among PNG, HTML, or MathML output depending on + * Writes rendering entry to database */ - function _doRender() { - if( $this->mode == MW_MATH_MATHML && $this->mathml != '' ) { - return Xml::tags( 'math', - $this->_attribs( 'math', - array( 'xmlns' => 'http://www.w3.org/1998/Math/MathML' ) ), - $this->mathml ); - } - if ( ( $this->mode == MW_MATH_PNG ) || ( $this->html == '' ) || - ( ( $this->mode == MW_MATH_SIMPLE ) && ( $this->conservativeness != 2 ) ) || - ( ( $this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML ) && ( $this->conservativeness == 0 ) ) - ) - { - return $this->_linkToMathImage(); - } else { - return Xml::tags( 'span', - $this->_attribs( 'span', - array( 'class' => 'texhtml', - 'dir' => 'ltr' - ) ), - $this->html + protected function writeDBEntry() { + # Now save it back to the DB: + if ( !wfReadOnly() ) { + $dbw = wfGetDB( DB_MASTER ); + if ( $this->hash ) { + $outmd5_sql = $dbw->encodeBlob( pack( 'H32', $this->hash ) ); + } else { + $outmd5_sql = null; + } + wfDebugLog( "Math", 'store entry for $' . $this->tex . '$ in database (hash:' . $this->getInputHash() . ')\n' ); + $dbw->replace( + 'math', + array( 'math_inputhash' ), + array( + 'math_inputhash' => $this->getInputHash(), + 'math_outputhash' => $outmd5_sql , + 'math_html_conservativeness' => $this->conservativeness, + 'math_html' => $this->html, + 'math_mathml' => $this->mathml, + ), + __METHOD__ ); } } - function _attribs( $tag, $defaults = array(), $overrides = array() ) { + /** + * Returns sanitized attributes + * + * @param string $tag element name + * @param array $defaults default attributes + * @param array $overrides attributes to override defaults + * @return array HTML attributes + */ + protected function getAttributes( $tag, $defaults = array(), $overrides = array() ) { $attribs = Sanitizer::validateTagAttributes( $this->params, $tag ); $attribs = Sanitizer::mergeAttributes( $defaults, $attribs ); $attribs = Sanitizer::mergeAttributes( $attribs, $overrides ); return $attribs; } - - function _linkToMathImage() { - $url = $this->_mathImageUrl(); - - return Xml::element( 'img', - $this->_attribs( - 'img', - array( - 'class' => 'tex', - 'alt' => $this->tex - ), - array( - 'src' => $url - ) - ) - ); - } - - function _mathImageUrl() { - global $wgMathPath; - $dir = $this->_getHashSubPath(); - return "$wgMathPath/$dir/{$this->hash}.png"; + /** + * Writes cache. Does nothing by default + */ + public function writeCache() { } /** - * @return string Storage directory + * Determines if this is a cached/recalled render + * + * @return boolean true if recalled, false otherwise */ - function _getHashPath() { - $path = $this->getBackend()->getRootStoragePath() . - '/math-render/' . $this->_getHashSubPath(); - wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" ); - return $path; + public function isRecall() { + return $this->recall; } /** - * @return string Relative directory + * Gets anchor ID + * + * @return string anchor ID */ - function _getHashSubPath() { - return substr( $this->hash, 0, 1) - . '/' . substr( $this->hash, 1, 1 ) - . '/' . substr( $this->hash, 2, 1 ); + public function getAnchorID() { + return $this->anchorID; } - public static function renderMath( $tex, $params = array(), ParserOptions $parserOptions = null ) { - if( trim( $tex ) == "" ) { - return ""; - } + /** + * Sets anchor ID + * + * @param string ID anchor ID + */ + public function setAnchorID( $ID ) { + $this->anchorID = $ID; + } - $math = new MathRenderer( $tex, $params ); - if ( $parserOptions ) { - $math->setOutputMode( $parserOptions->getMath() ); - } - return $math->render(); + /** + * Gets TeX markup + * + * @return string TeX markup + */ + public function getTex() { + return $this->tex; } } diff --git a/MathSource.php b/MathSource.php new file mode 100644 index 0000000..ebc6ffd --- /dev/null +++ b/MathSource.php @@ -0,0 +1,42 @@ + parsing + * @file + */ + + +/** + * Takes LaTeX fragments and outputs the source directly to the browser + * + * @author Tomasz Wegrzanowski + * @author Brion Vibber + * @author Moritz Schubotz + * @ingroup Parser + */ +class MathSource extends MathRenderer { + /** + * Renders TeX by outputting it to the browser in a span tag + * + * @return string span tag with TeX + */ + function render() { + # No need to render or parse anything more! + # New lines are replaced with spaces, which avoids confusing our parser (bugs 23190, 22818) + return Xml::element( 'span', + $this->getAttributes( + 'span', + array( + 'class' => 'tex', + 'dir' => 'ltr' + ) + ), + '$ ' . str_replace( "\n", " ", $this->tex ) . ' $' + ); + } + +} diff --git a/MathTexvc.php b/MathTexvc.php new file mode 100644 index 0000000..80a9057 --- /dev/null +++ b/MathTexvc.php @@ -0,0 +1,317 @@ +readCache() ) { // cache miss + $result = $this->callTexvc(); + if ( $result != MW_TEXVC_SUCCESS ) { + return $result; + } + } + return $this->doHTMLRender(); + } + + /** + * Gets path to store hashes in + * + * @return string Storage directory + */ + function getHashPath() { + $path = $this->getBackend()->getRootStoragePath() . + '/math-render/' . $this->getHashSubPath(); + wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" ); + return $path; + } + + /** + * Gets relative directory for this specific hash + * + * @return string Relative directory + */ + function getHashSubPath() { + return substr( $this->hash, 0, 1 ) + . '/' . substr( $this->hash, 1, 1 ) + . '/' . substr( $this->hash, 2, 1 ); + } + + /** + * Gets URL for math image + * + * @return string image URL + */ + function getMathImageUrl() { + global $wgMathPath; + $dir = $this->getHashSubPath(); + return "$wgMathPath/$dir/{$this->hash}.png"; + } + + /** + * Gets img tag for math image + * + * @return string img HTML + */ + function getMathImageHTML() { + $url = $this->getMathImageUrl(); + + return Xml::element( 'img', + $this->getAttributes( + 'img', + array( + 'class' => 'tex', + 'alt' => $this->tex + ), + array( + 'src' => $url + ) + ) + ); + } + + /** + * Does the actual call to texvc + * + * @return int|string MW_TEXVC_SUCCESS or error string + */ + function callTexvc() { + global $wgTexvc, $wgTexvcBackgroundColor, $wgUseSquid, $wgMathCheckFiles; + $tmpDir = wfTempDir(); + if ( !is_executable( $wgTexvc ) ) { + return $this->getError( 'math_notexvc' ); + } + + $escapedTmpDir = wfEscapeShellArg( $tmpDir ); + + $cmd = $wgTexvc . ' ' . + $escapedTmpDir . ' ' . + $escapedTmpDir . ' ' . + wfEscapeShellArg( $this->tex ) . ' ' . + wfEscapeShellArg( 'UTF-8' ) . ' ' . + wfEscapeShellArg( $wgTexvcBackgroundColor ); + + if ( wfIsWindows() ) { + # Invoke it within cygwin sh, because texvc expects sh features in its default shell + $cmd = 'sh -c ' . wfEscapeShellArg( $cmd ); + } + wfDebugLog( 'Math', "TeX: $cmd\n" ); + $contents = wfShellExec( $cmd ); + wfDebugLog( 'Math', "TeX output:\n $contents\n---\n" ); + + if ( strlen( $contents ) == 0 ) { + if ( !file_exists( $tmpDir ) || !is_writable( $tmpDir ) ) { + return $this->getError( 'math_bad_tmpdir' ); + } else { + return $this->getError( 'math_unknown_error' ); + } + } + + $tempFsFile = new TempFSFile( "$tmpDir/{$this->hash}.png" ); + $tempFsFile->autocollect(); // destroy file when $tempFsFile leaves scope + + $retval = substr( $contents, 0, 1 ); + $errmsg = ''; + if ( ( $retval == 'C' ) || ( $retval == 'M' ) || ( $retval == 'L' ) ) { + if ( $retval == 'C' ) { + $this->conservativeness = self::CONSERVATIVE; + } elseif ( $retval == 'M' ) { + $this->conservativeness = self::MODERATE; + } else { + $this->conservativeness = self::LIBERAL; + } + $outdata = substr( $contents, 33 ); + + $i = strpos( $outdata, "\000" ); + + $this->html = substr( $outdata, 0, $i ); + $this->mathml = substr( $outdata, $i + 1 ); + } elseif ( ( $retval == 'c' ) || ( $retval == 'm' ) || ( $retval == 'l' ) ) { + $this->html = substr( $contents, 33 ); + if ( $retval == 'c' ) { + $this->conservativeness = self::CONSERVATIVE; + } elseif ( $retval == 'm' ) { + $this->conservativeness = self::MODERATE; + } else { + $this->conservativeness = self::LIBERAL; + } + $this->mathml = null; + } elseif ( $retval == 'X' ) { + $this->html = null; + $this->mathml = substr( $contents, 33 ); + $this->conservativeness = self::LIBERAL; + } elseif ( $retval == '+' ) { + $this->html = null; + $this->mathml = null; + $this->conservativeness = self::LIBERAL; + } else { + $errbit = htmlspecialchars( substr( $contents, 1 ) ); + switch( $retval ) { + case 'E': + $errmsg = $this->getError( 'math_lexing_error', $errbit ); + break; + case 'S': + $errmsg = $this->getError( 'math_syntax_error', $errbit ); + break; + case 'F': + $errmsg = $this->getError( 'math_unknown_function', $errbit ); + break; + default: + $errmsg = $this->getError( 'math_unknown_error', $errbit ); + } + } + + if ( !$errmsg ) { + $this->hash = substr( $contents, 1, 32 ); + } + + wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) ); + + if ( $errmsg ) { + return $errmsg; + } elseif ( !preg_match( "/^[a-f0-9]{32}$/", $this->hash ) ) { + return $this->getError( 'math_unknown_error' ); + } elseif ( !file_exists( "$tmpDir/{$this->hash}.png" ) ) { + return $this->getError( 'math_image_error' ); + } elseif ( filesize( "$tmpDir/{$this->hash}.png" ) == 0 ) { + return $this->getError( 'math_image_error' ); + } + + $hashpath = $this->getHashPath(); // final storage directory + + $backend = $this->getBackend(); + # Create any containers/directories as needed... + if ( !$backend->prepare( array( 'dir' => $hashpath ) )->isOK() ) { + return $this->getError( 'math_output_error' ); + } + // Store the file at the final storage path... + if ( !$backend->quickStore( array( + 'src' => "$tmpDir/{$this->hash}.png", 'dst' => "$hashpath/{$this->hash}.png" + ) )->isOK() + ) { + return $this->getError( 'math_output_error' ); + } + return MW_TEXVC_SUCCESS; + } + + /** + * Gets file backend + * + * @return FileBackend appropriate file backend + */ + function getBackend() { + global $wgMathFileBackend, $wgMathDirectory; + if ( $wgMathFileBackend ) { + return FileBackendGroup::singleton()->get( $wgMathFileBackend ); + } else { + static $backend = null; + if ( !$backend ) { + $backend = new FSFileBackend( array( + 'name' => 'math-backend', + 'lockManager' => 'fsLockManager', + 'containerPaths' => array( 'math-render' => $wgMathDirectory ), + 'fileMode' => 0777 + ) ); + } + return $backend; + } + } + + /** + * Does the HTML rendering + * + * @return string HTML string + */ + function doHTMLRender() { + if ( $this->mode == MW_MATH_MATHML && $this->mathml != '' ) { + return Xml::tags( 'math', + $this->getAttributes( 'math', + array( 'xmlns' => 'http://www.w3.org/1998/Math/MathML' ) ), + $this->mathml ); + } + if ( ( $this->mode == MW_MATH_PNG ) || ( $this->html == '' ) || + ( ( $this->mode == MW_MATH_SIMPLE ) && ( $this->conservativeness != self::CONSERVATIVE ) ) || + ( ( $this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML ) && ( $this->conservativeness == self::LIBERAL ) ) + ) + { + return $this->getMathImageHTML(); + } else { + return Xml::tags( 'span', + $this->getAttributes( 'span', + array( 'class' => 'texhtml', + 'dir' => 'ltr' + ) ), + $this->html + ); + } + } + + /** + * Overrides base class. Writes to database, and if configured, squid. + */ + public function writeCache() { + global $wgUseSquid; + if ( !$this->isRecall() ) { + return; + } + $this->writeDBEntry(); + // If we're replacing an older version of the image, make sure it's current. + if ( $wgUseSquid ) { + $urls = array( $this->getMathImageUrl() ); + $u = new SquidUpdate( $urls ); + $u->doUpdate(); + } + } + + /** + * Reads the rendering information from the database. If configured, checks whether files exist + * + * @return boolean true if retrieved, false otherwise + */ + function readCache() { + global $wgMathCheckFiles; + if ( $this->readFromDB() ) { + if ( !$wgMathCheckFiles ) { + // Short-circuit the file existence & migration checks + return true; + } + $filename = $this->getHashPath() . "/{$this->hash}.png"; // final storage path + $backend = $this->getBackend(); + if ( $backend->fileExists( array( 'src' => $filename ) ) ) { + if ( $backend->getFileSize( array( 'src' => $filename ) ) == 0 ) { + // Some horrible error corrupted stuff :( + $backend->quickDelete( array( 'src' => $filename ) ); + } else { + return true; // cache hit + } + } + } else { + return false; + } + } + +} \ No newline at end of file diff --git a/math/README b/math/README index d90aeb5..cdf5806 100644 --- a/math/README +++ b/math/README @@ -32,6 +32,11 @@ already contain AMS*. In Debian/Ubuntu you need to install tetex-extra. To work properly with rendering non-ASCII Unicode characters, a supplemental TeX package is needed (cjk-latex in Debian) +In Ubuntu Precise, all dependencies can be installed using: + + $ sudo apt-get install build-essential dvipng ocaml \ + texlive-fonts-recommended texlive-lang-greek texlive-latex-recommended + === Installation === Run 'make' (or 'gmake' if GNU make is not your default make). This should diff --git a/mathParserTests.txt b/mathParserTests.txt index 534c916..231cbb8 100644 --- a/mathParserTests.txt +++ b/mathParserTests.txt @@ -26,7 +26,7 @@ math !! input [[Image:foobar.jpg|thumb|2+2]] !! result -
2+2
+
2+2
!! end