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
This commit is contained in:
Physikerwelt 2012-10-27 16:30:50 +02:00 committed by Ori Livneh
parent c14159404f
commit 926db7c3bd
8 changed files with 565 additions and 314 deletions

View File

@ -36,23 +36,27 @@ class MathHooks {
/**
* Callback function for the <math> 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(

View File

@ -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';

36
MathMathJax.php Normal file
View File

@ -0,0 +1,36 @@
<?php
/**
* MediaWiki math extension
*
* (c) 2002-2012 Tomasz Wegrzanowski, Brion Vibber, Moritz Schubotz and other MediaWiki contributors
* GPLv2 license; info in main package.
*
* Renderer for MathJax
* @file
*/
/**
* Takes LaTeX fragments and outputs the source directly to the browser
*
* @author Tomasz Wegrzanowski
* @author Brion Vibber
* @author Moritz Schubotz
* @ingroup Parser
*/
class MathMathJax extends MathRenderer {
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 ) . ' $'
);
}
}

View File

@ -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 <math> </math> 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 "<strong class='error'>$mf ($errmsg$append): $source</strong>\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;
}
}

42
MathSource.php Normal file
View File

@ -0,0 +1,42 @@
<?php
/**
* MediaWiki math extension
*
* (c) 2002-2012 Tomasz Wegrzanowski, Brion Vibber, Moritz Schubotz and other MediaWiki contributors
* GPLv2 license; info in main package.
*
* Contains everything related to <math> </math> 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 ) . ' $'
);
}
}

317
MathTexvc.php Normal file
View File

@ -0,0 +1,317 @@
<?php
/**
* MediaWiki math extension
*
* (c) 2002-2012 Tomasz Wegrzanowski, Brion Vibber, Moritz Schubotz, and other MediaWiki contributors
* GPLv2 license; info in main package.
*
* Contains the driver function for the texvc program
* @file
*/
/**
* 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.
*
* @author Tomasz Wegrzanowski
* @author Brion Vibber
* @author Moritz Schubotz
*/
define( 'MW_TEXVC_SUCCESS', -1 );
class MathTexvc extends MathRenderer {
const CONSERVATIVE = 2;
const MODERATE = 1;
const LIBERAL = 0;
/**
* Renders TeX using texvc
*
* @return string rendered TeK
*/
function render() {
if ( !$this->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;
}
}
}

View File

@ -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

View File

@ -26,7 +26,7 @@ math
!! input
[[Image:foobar.jpg|thumb|<math>2+2</math>]]
!! result
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><img class="tex" alt="2+2" src="/images/math/f/a/5/fa50b8b616463173474302ca3e63586b.png" /></div></div></div>
<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><img class="tex" alt="2+2" src="/images/math/f/a/5/fa50b8b616463173474302ca3e63586b.png" /></div></div></div>
!! end