mediawiki-extensions-Math/MathLaTeXML.php
physikerwelt 6cb52f9190 Add SVG generation for LaTeXML
Currently SVGs are generated only in
MathML mode but not in LaTeXML mode.
Since LaTeXML can benefit from the
SVGs as well this change introduces
the functionality to generate SVG
images from LaTeXML MathML input.

Generating SVG images for LaTeXML is an essential
part of Math 2.0. It enables to include LaTeXMLs Content
MathML and provides fallback
images for visitors with MathML disabled browsers.

Change-Id: If13a8b0825bf12dbfe4920ddb7ad552df9bc095f
2014-08-01 03:33:08 +02:00

213 lines
6.6 KiB
PHP

<?php
/**
* MediaWiki math extension
*
* (c)2012 Moritz Schubotz
* GPLv2 license; info in main package.
*
* Contains the driver function for the LaTeXML daemon
* @file
*/
class MathLaTeXML extends MathMathML {
protected $defaultAllowedRootElements = array( 'math', 'div', 'table', 'query' );
/** @var String settings for LaTeXML daemon */
private $LaTeXMLSettings = '';
public function __construct( $tex = '', $params = array() ) {
global $wgMathLaTeXMLUrl;
parent::__construct( $tex, $params );
$this->hosts = $wgMathLaTeXMLUrl;
$this->setMode( MW_MATH_LATEXML );
}
/**
* Converts an array with LaTeXML settings to a URL encoded String.
* If the argument is a string the input will be returned.
* Thus the function has projector properties and can be applied a second time safely.
* @param (string|array) $array
* @return string
*/
public function serializeSettings( $array ) {
if ( !is_array( $array ) ) {
return $array;
} else {
// removes the [1] [2]... for the unnamed subarrays since LaTeXML
// assigns multiple values to one key e.g.
// preload=amsmath.sty&preload=amsthm.sty&preload=amstext.sty
$cgi_string = wfArrayToCgi( $array );
$cgi_string = preg_replace( '|\%5B\d+\%5D|', '', $cgi_string );
$cgi_string = preg_replace( '|&\d+=|', '&', $cgi_string );
return $cgi_string;
}
}
/**
* Gets the settings for the LaTeXML daemon.
* @global (array|string) $wgMathDefaultLaTeXMLSetting
* @return string
*/
public function getLaTeXMLSettings() {
global $wgMathDefaultLaTeXMLSetting;
if ( $this->LaTeXMLSettings ) {
return $this->LaTeXMLSettings;
} else {
return $wgMathDefaultLaTeXMLSetting;
}
}
/**
* Sets the settings for the LaTeXML daemon.
* The settings affect only the current instance of the class.
* For a list of possible settings see:
* http://dlmf.nist.gov/LaTeXML/manual/commands/latexmlpost.xhtml
* An empty value indicates to use the default settings.
* @param string|array $settings
*/
public function setLaTeXMLSettings( $settings ) {
$this->LaTeXMLSettings = $settings;
}
/**
* Calculates the HTTP POST Data for the request. Depends on the settings
* and the input string only.
* @return string HTTP POST data
*/
public function getLaTeXMLPostData() {
$tex = $this->getTex();
if ( $this->getMathStyle() == MW_MATHSTYLE_INLINE_DISPLAYSTYLE ) {
// In MW_MATHSTYLE_INLINE_DISPLAYSTYLE the old
// texvc behavior is reproduced:
// The equation is rendered in displaystyle
// (texvc used $$ $tex $$ to render)
// but the equation is not centered.
$tex = '{\displaystyle ' . $tex . '}';
}
$texcmd = rawurlencode( $tex );
$settings = $this->serializeSettings( $this->getLaTeXMLSettings( ) );
$postData = $settings . '&tex=' . $texcmd;
wfDebugLog( "Math", 'Get post data: ' . $postData );
return $postData;
}
/**
* Does the actual web request to convert TeX to MathML.
* @return boolean
*/
protected function doRender() {
global $wgMathDebug;
wfProfileIn( __METHOD__ );
if ( trim( $this->getTex() ) === '' ) {
wfDebugLog( "Math", "Rendering was requested, but no TeX string is specified." );
$this->lastError = $this->getError( 'math_empty_tex' );
return false;
}
$res = '';
$host = $this->pickHost();
$post = $this->getLaTeXMLPostData();
// There is an API-inconsistency between different versions of the LaTeXML daemon
// some versions require the literal prefix other don't allow it.
if ( ! strpos( $host, '/convert' ) ){
$post = preg_replace( '/&tex=/' , '&tex=literal:', $post , 1);
}
$this->lastError = '';
$requestResult = $this->makeRequest( $host, $post, $res, $this->lastError );
if ( $requestResult ) {
$jsonResult = json_decode( $res );
if ( $jsonResult && json_last_error() === JSON_ERROR_NONE ) {
if ( $this->isValidMathML( $jsonResult->result ) ) {
$this->setMathml( $jsonResult->result );
if ( $wgMathDebug ) {
$this->setLog( $jsonResult->log );
$this->setStatusCode( $jsonResult->status_code );
}
return true;
} else {
// Do not print bad mathml. It's probably too verbose and might
// mess up the browser output.
$this->lastError = $this->getError( 'math_invalidxml', $this->getModeStr(), $host );
wfDebugLog( "Math", "\nLaTeXML InvalidMathML:"
. var_export( array( 'post' => $post, 'host' => $host
, 'result' => $res ), true ) . "\n\n" );
wfProfileOut( __METHOD__ );
return false;
}
} else {
$this->lastError = $this->getError( 'math_invalidjson', $this->getModeStr(), $host );
wfDebugLog( "Math", "\nLaTeXML InvalidJSON:"
. var_export( array( 'post' => $post, 'host' => $host
, 'res' => $res ), true ) . "\n\n" );
wfProfileOut( __METHOD__ );
return false;
}
} else {
// Error message has already been set.
wfProfileOut( __METHOD__ );
return false;
}
}
/**
* Internal version of @link self::embedMathML
* @return string
* @return html element with rendered math
*/
protected function getMathMLTag() {
return self::embedMathML( $this->getMathml(), urldecode( $this->getTex() ) );
}
/**
* Embeds the MathML-XML element in a HTML span element with class tex
* @param string $mml : the MathML string
* @param string $tagId : optional tagID for references like (pagename#equation2)
* @param bool $attribs
* @return html element with rendered math
*/
public static function embedMathML( $mml, $tagId = '', $attribs = false ) {
$mml = str_replace( "\n", " ", $mml );
if ( ! $attribs ) {
$attribs = array( 'class' => 'tex', 'dir' => 'ltr' );
if ( $tagId ) {
$attribs['id'] = $tagId;
}
$attribs = Sanitizer::validateTagAttributes( $attribs, 'span' );
}
return Xml::tags( 'span', $attribs, $mml );
}
/**
* Calculates the SVG image based on the MathML input
* No cache is used.
* @return boolean
*/
public function calulateSvg() {
$renderer = new MathMathML( $this->getTex() );
$renderer->setMathml( $this->getMathml() );
$renderer->setMode( MW_MATH_LATEXML );
$renderer->setPurge( true );
$res = $renderer->render();
if ( $res == true ) {
$this->svg = $renderer->getSvg();
} else {
$lastError = $renderer->getLastError();
wfDebugLog( 'Math', 'failed to convert LaTeXML-MathML to SVG:' . $lastError );
}
return $res;
}
/**
* Gets the SVG image
* Lazy evaluation: If no SVG image exists it's generated on the fly
* @return string XML-Document of the rendered SVG
*/
public function getSvg() {
if ( $this->isPurge() || $this->svg == '' ) {
$this->calulateSvg();
}
return $this->svg;
}
protected function getMathTableName() {
return 'mathlatexml';
}
}