Add experimental MathML output option for math (only works with some

browsers and requires use of an XML content type; try setting
$wgMimeType = "application/xhtml+xml" and use Mozilla).
Rearranged a couple things in prefs.
Did some refactoring in Math.php to remove some duplicated code and
figure out what is going on.
This commit is contained in:
Brion Vibber 2004-04-22 08:51:04 +00:00
parent 65b84317c9
commit 93b09379fb
6 changed files with 233 additions and 168 deletions

View file

@ -1,153 +1,205 @@
<?php
#
# 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.
#
# by Tomasz Wegrzanowski, with additions by Brion Vibber (2003, 2004)
#
function linkToMathImage ( $tex, $outputhash )
{
global $wgMathPath;
$url = htmlspecialchars( "$wgMathPath/$outputhash.png" );
$alt = htmlspecialchars( $tex );
return "<img class='tex' src=\"$url\" alt=\"$alt\" />";
}
function renderMath( $tex )
{
global $wgUser, $wgMathDirectory, $wgTmpDirectory, $wgInputEncoding;
global $wgTexvc;
$mf = wfMsg( "math_failure" );
$munk = wfMsg( "math_unknown_error" );
$fname = "renderMath";
$math = $wgUser->getOption("math");
if( $math == 3 ) {
return ('$ '.wfEscapeHTML($tex).' $');
class MathRenderer {
var $mode = MW_MATH_MODERN;
var $tex = "";
var $inputhash = "";
var $hash = "";
var $html = "";
var $mathml = "";
var $conservativeness = 0;
function MathRenderer( $tex ) {
$this->tex = $tex;
}
function setOutputMode( $mode ) {
$this->mode = $mode;
}
$md5 = md5($tex);
$md5_sql = mysql_escape_string(pack("H32", $md5));
if ($math == 0) {
$sql = "SELECT math_outputhash FROM math WHERE math_inputhash = '$md5_sql'";
} else {
$sql = "SELECT math_outputhash,math_html_conservativeness,math_html FROM math WHERE math_inputhash = '$md5_sql'";
}
$res = wfQuery( $sql, DB_READ, $fname );
if( $rpage = wfFetchObject ( $res ) ) {
$outputhash = unpack( "H32md5", $rpage->math_outputhash . " " );
$outputhash = $outputhash ['md5'];
if( file_exists( "$wgMathDirectory/$outputhash.png" ) ) {
if (($math == 0) || ($rpage->math_html == '') || (($math == 1) && ($rpage->math_html_conservativeness != 2)) || (($math == 4) && ($rpage->math_html_conservativeness == 0))) {
return linkToMathImage ( $tex, $outputhash );
function render() {
global $wgMathDirectory, $wgTmpDirectory, $wgInputEncoding;
global $wgTexvc;
if( $this->mode == MW_MATH_SOURCE ) {
# No need to render or parse anything more!
return ('$ '.htmlspecialchars( $tex ).' $');
}
if( !$this->_recall() ) {
# Ensure that the temp and output directories are available before continuing...
if( !file_exists( $wgMathDirectory ) ) {
if( !@mkdir( $wgMathDirectory ) ) {
return $this->_error( "math_bad_output" );
}
} elseif( !is_dir( $wgMathDirectory ) || !is_writable( $wgMathDirectory ) ) {
return $this->_error( "math_bad_output" );
}
if( !file_exists( $wgTmpDirectory ) ) {
if( !@mkdir( $wgTmpDirectory ) ) {
return $this->_error( "math_bad_tmpdir" );
}
} elseif( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) {
return $this->_error( "math_bad_tmpdir" );
}
if( !is_executable( $wgTexvc ) ) {
return $this->_error( "math_notexvc" );
}
$cmd = $wgTexvc." ".
escapeshellarg($wgTmpDirectory)." ".
escapeshellarg($wgMathDirectory)." ".
escapeshellarg($this->tex)." ".
escapeshellarg($wgInputEncoding);
wfDebug( "TeX: $cmd" );
$contents = `$cmd`;
if (strlen($contents) == 0) {
return $this->_error( "math_unknown_error" );
}
$retval = substr ($contents, 0, 1);
if (($retval == "C") || ($retval == "M") || ($retval == "L")) {
if ($retval == "C")
$this->conservativeness = 2;
else if ($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);
$sql_html = "'".wfStrencode($this->html)."'";
$sql_mathml = "'".wfStrencode($this->mathml)."'";
} else if (($retval == "c") || ($retval == "m") || ($retval == "l")) {
$this->html = substr ($contents, 33);
if ($retval == "c")
$this->conservativeness = 2;
else if ($retval == "m")
$this->conservativeness = 1;
else
$this->conservativeness = 0;
$sql_html = "'".wfStrencode($this->html)."'";
$this->mathml = '';
$sql_mathml = 'NULL';
} else if ($retval == "X") {
$outhtml = '';
$this->mathml = substr ($contents, 33);
$sql_html = 'NULL';
$sql_mathml = "'".wfStrencode($this->mathml)."'";
$this->conservativeness = 0;
} else if ($retval == "+") {
$this->outhtml = '';
$this->mathml = '';
$sql_html = 'NULL';
$sql_mathml = 'NULL';
$this->conservativeness = 0;
} else {
return $rpage->math_html;
$errbit = htmlspecialchars( substr($contents, 1) );
switch( $retval ) {
case "E": return $this->_error( "math_lexing_error", $errbit );
case "S": return $this->_error( "math_syntax_error", $errbit );
case "F": return $this->_error( "math_unknown_function", $errbit );
default: return $this->_error( "math_unknown_error", $errbit );
}
}
$this->hash = substr ($contents, 1, 32);
if (!preg_match("/^[a-f0-9]{32}$/", $this->hash)) {
return $this->_error( "math_unknown_error" );
}
if( !file_exists( "$wgMathDirectory/{$this->hash}.png" ) ) {
return $this->_error( "math_image_error" );
}
# Now save it back to the DB:
$outmd5_sql = wfStrencode(pack("H32", $this->hash));
$md5_sql = wfStrencode( pack("H32", $this->md5) ); # Binary packed, not hex
$sql = "REPLACE INTO math VALUES ('".$md5_sql."', '".$outmd5_sql."', ".$this->conservativeness.", ".$sql_html.", ".$sql_mathml.")";
$res = wfQuery( $sql, DB_READ, "MathRenderer::render" );
# we don't really care if it fails
}
return $this->_doRender();
}
function _error( $msg, $append = "" ) {
$mf = htmlspecialchars( wfMsg( "math_failure" ) );
$munk = htmlspecialchars( wfMsg( "math_unknown_error" ) );
$errmsg = htmlspecialchars( wfMsg( $msg ) );
$source = htmlspecialchars($this->tex);
return "<strong class='error'>$mf ($errmsg$append): $source</strong>\n";
}
function _recall() {
global $wgMathDirectory;
$this->md5 = md5( $this->tex );
$md5_sql = wfStrencode( pack("H32", $this->md5) ); # Binary packed, not hex
$sql = "SELECT math_outputhash,math_html_conservativeness,math_html,math_mathml FROM math WHERE math_inputhash = '$md5_sql'";
$res = wfQuery( $sql, DB_READ, "MathRenderer::_recall" );
if( $rpage = wfFetchObject ( $res ) ) {
# Tailing 0x20s can get dropped by the database, add it back on if necessary:
$xhash = unpack( "H32md5", $rpage->math_outputhash . " " );
$this->hash = $xhash ['md5'];
$this->conservativeness = $rpage->math_html_conservativeness;
$this->html = $rpage->math_html;
$this->mathml = $rpage->math_mathml;
if( file_exists( "$wgMathDirectory/{$this->hash}.png" ) ) {
return true;
}
}
# Missing from the database and/or the render cache
return false;
}
# Ensure that the temp and output directories are available before continuing...
if( !file_exists( $wgMathDirectory ) ) {
if( !@mkdir( $wgMathDirectory ) ) {
return "<b>$mf (" . wfMsg( "math_bad_output" ) . ")</b>";
# Select among PNG, HTML, or MathML output depending on
function _doRender() {
if( $this->mode == MW_MATH_MATHML && $this->mathml != '' ) {
return "<math xmlns='http://www.w3.org/1998/Math/MathML'>{$this->mathml}</math>";
}
} elseif( !is_dir( $wgMathDirectory ) || !is_writable( $wgMathDirectory ) ) {
return "<b>$mf (" . wfMsg( "math_bad_output" ) . ")</b>";
}
if( !file_exists( $wgTmpDirectory ) ) {
if( !@mkdir( $wgTmpDirectory ) ) {
return "<b>$mf (" . wfMsg( "math_bad_tmpdir" ) . ")</b>";
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 $this->html;
}
} elseif( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) {
return "<b>$mf (" . wfMsg( "math_bad_tmpdir" ) . ")</b>";
}
if( !is_executable( $wgTexvc ) ) {
return "<b>$mf (" . wfMsg( "math_notexvc" ) . ")</b>";
}
$cmd = $wgTexvc." ".
escapeshellarg($wgTmpDirectory)." ".
escapeshellarg($wgMathDirectory)." ".
escapeshellarg($tex)." ".
escapeshellarg($wgInputEncoding);
wfDebug( "TeX: $cmd" );
$contents = `$cmd`;
if (strlen($contents) == 0) {
return "<b>".$mf." (".$munk."): ".wfEscapeHTML($tex)."</b>";
}
$retval = substr ($contents, 0, 1);
if (($retval == "C") || ($retval == "M") || ($retval == "L")) {
if ($retval == "C")
$conservativeness = 2;
else if ($retval == "M")
$conservativeness = 1;
else
$conservativeness = 0;
$outdata = substr ($contents, 33);
$i = strpos($outdata, "\000");
$outhtml = substr($outdata, 0, $i);
$mathml = substr($outdata, $i+1);
$sql_html = "'".mysql_escape_string($outhtml)."'";
$sql_mathml = "'".mysql_escape_string($mathml)."'";
} else if (($retval == "c") || ($retval == "m") || ($retval == "l")) {
$outhtml = substr ($contents, 33);
if ($retval == "c")
$conservativeness = 2;
else if ($retval == "m")
$conservativeness = 1;
else
$conservativeness = 0;
$sql_html = "'".mysql_escape_string($outhtml)."'";
$mathml = '';
$sql_mathml = 'NULL';
} else if ($retval == "X") {
$outhtml = '';
$mathml = substr ($contents, 33);
$sql_html = 'NULL';
$sql_mathml = "'".mysql_escape_string($mathml)."'";
$conservativeness = 0;
} else if ($retval == "+") {
$outhtml = '';
$mathml = '';
$sql_html = 'NULL';
$sql_mathml = 'NULL';
$conservativeness = 0;
} else {
if ($retval == "E")
$errmsg = wfMsg( "math_lexing_error" );
else if ($retval == "S")
$errmsg = wfMsg( "math_syntax_error" );
else if ($retval == "F")
$errmsg = wfMsg( "math_unknown_function" );
else
$errmsg = $munk;
return "<h3>".$mf." (".$errmsg.substr($contents, 1)."): ".wfEscapeHTML($tex)."</h3>";
}
$outmd5 = substr ($contents, 1, 32);
if (!preg_match("/^[a-f0-9]{32}$/", $outmd5)) {
return "<b>".$mf." (".$munk."): ".wfEscapeHTML($tex)."</b>";
function _linkToMathImage() {
global $wgMathPath;
$url = htmlspecialchars( "$wgMathPath/{$this->hash}.png" );
$alt = htmlspecialchars( $this->tex );
return "<img class='tex' src=\"$url\" alt=\"$alt\" />";
}
if( !file_exists( "$wgMathDirectory/$outmd5.png" ) ) {
$errmsg = wfMsg( "math_image_error" );
return "<h3>$mf ($errmsg): " . wfEscapeHTML($tex) . "</h3>";
}
}
$outmd5_sql = mysql_escape_string(pack("H32", $outmd5));
$sql = "REPLACE INTO math VALUES ('".$md5_sql."', '".$outmd5_sql."', ".$conservativeness.", ".$sql_html.", ".$sql_mathml.")";
$res = wfQuery( $sql, DB_READ, $fname );
# we don't really care if it fails
if (($math == 0) || ($rpage->math_html == '') || (($math == 1) && ($conservativeness != 2)) || (($math == 4) && ($conservativeness == 0)))
return linkToMathImage($tex, $outmd5);
else
return $outhtml;
function renderMath( $tex ) {
global $wgUser;
$math = new MathRenderer( $tex );
$math->setOutputMode( $wgUser->getOption("math"));
return $math->render();
}
?>

View file

@ -18,7 +18,7 @@
*/
define("RDF_TYPE_PREFS", "application/rdf+xml,text/xml;q=0.7,application/xml;q=0.5,text/rdf;q=0.1");
function wfDublinCoreRdf($article) {
$url = dcReallyFullUrl($article->mTitle);

View file

@ -321,17 +321,24 @@ class PreferencesForm {
$wgOut->addHTML( "<form id=\"preferences\" name=\"preferences\" action=\"$action\"
method=\"post\">" );
# Quickbar setting
# First section: identity
# Email, etc.
#
$wgOut->addHtml( "<fieldset>\n<legend>$qb:</legend>\n" );
for ( $i = 0; $i < count( $qbs ); ++$i ) {
if ( $i == $this->mQuickbar ) { $checked = ' checked="checked"'; }
else { $checked = ""; }
$wgOut->addHTML( "<div><label><input type='radio' name=\"wpQuickbar\"
value=\"$i\"$checked /> {$qbs[$i]}</label></div>\n" );
}
$wgOut->addHtml( "</fieldset>\n\n" );
$this->mUserEmail = wfEscapeHTML( $this->mUserEmail );
$this->mRealName = wfEscapeHTML( $this->mRealName );
$this->mNick = wfEscapeHTML( $this->mNick );
if ( $this->mEmailFlag ) { $emfc = 'checked="checked"'; }
else { $emfc = ""; }
$ps = $this->namespacesCheckboxes();
$wgOut->addHTML( "<fieldset>
<legend>Idento</legend>
<div><label>$yrn: <input type='text' name=\"wpRealName\" value=\"{$this->mRealName}\" size='20' /></label></div>
<div><label>$yem: <input type='text' name=\"wpUserEmail\" value=\"{$this->mUserEmail}\" size='20' /></label></div>
<div><label><input type='checkbox' $emfc value=\"1\" name=\"wpEmailFlag\" /> $emf</label></div>
<div><label>$ynn: <input type='text' name=\"wpNick\" value=\"{$this->mNick}\" size='12' /></label></div>\n" );
# Fields for changing password
#
$this->mOldpass = wfEscapeHTML( $this->mOldpass );
@ -344,7 +351,19 @@ class PreferencesForm {
<div><label>$npw: <input type='password' name=\"wpNewpass\" value=\"{$this->mNewpass}\" size='20' /></label></div>
<div><label>$rpw: <input type='password' name=\"wpRetypePass\" value=\"{$this->mRetypePass}\" size='20' /></label></div>
" . $this->getToggle( "rememberpassword" ) . "
</fieldset>\n\n" );
</fieldset>\n</fieldset>\n" );
# Quickbar setting
#
$wgOut->addHtml( "<fieldset>\n<legend>$qb:</legend>\n" );
for ( $i = 0; $i < count( $qbs ); ++$i ) {
if ( $i == $this->mQuickbar ) { $checked = ' checked="checked"'; }
else { $checked = ""; }
$wgOut->addHTML( "<div><label><input type='radio' name=\"wpQuickbar\"
value=\"$i\"$checked /> {$qbs[$i]}</label></div>\n" );
}
$wgOut->addHtml( "</fieldset>\n\n" );
# Skin setting
#
@ -414,23 +433,7 @@ class PreferencesForm {
<div><input type=\"button\" value=\"$tzGuess\" onClick=\"javascript:guessTimezone()\" /></div>
</fieldset>\n\n" );
# Email, etc.
#
$this->mUserEmail = wfEscapeHTML( $this->mUserEmail );
$this->mRealName = wfEscapeHTML( $this->mRealName );
$this->mNick = wfEscapeHTML( $this->mNick );
if ( $this->mEmailFlag ) { $emfc = 'checked="checked"'; }
else { $emfc = ""; }
$ps = $this->namespacesCheckboxes();
$wgOut->addHTML( "<fieldset>
<div><label>$yrn: <input type='text' name=\"wpRealName\" value=\"{$this->mRealName}\" size='20' /></label></div>
<div><label>$yem: <input type='text' name=\"wpUserEmail\" value=\"{$this->mUserEmail}\" size='20' /></label></div>
<div><label><input type='checkbox' $emfc value=\"1\" name=\"wpEmailFlag\" /> $emf</label></div>
<div><label>$ynn: <input type='text' name=\"wpNick\" value=\"{$this->mNick}\" size='12' /></label></div>
</fieldset>
$wgOut->addHTML( "
<fieldset>
<div><label>$rcc: <input type='text' name=\"wpRecent\" value=\"$this->mRecent\" size='6' /></label></div>
" . $this->getToggle( "hideminor" ) .

View file

@ -1,4 +1,5 @@
<?php
# Main wiki script; see design.doc
#
$wgRequestTime = microtime();

View file

@ -68,12 +68,20 @@ if($wgMetaNamespace === FALSE)
'monobook' => "MonoBook"
);
define( "MW_MATH_PNG", 0 );
define( "MW_MATH_SIMPLE", 1 );
define( "MW_MATH_HTML", 2 );
define( "MW_MATH_SOURCE", 3 );
define( "MW_MATH_MODERN", 4 );
define( "MW_MATH_MATHML", 5 );
/* private */ $wgMathNamesEn = array(
"Always render PNG",
"HTML if very simple or else PNG",
"HTML if possible or else PNG",
"Leave it as TeX (for text browsers)",
"Recommended for modern browsers"
MW_MATH_PNG => "Always render PNG",
MW_MATH_SIMPLE => "HTML if very simple or else PNG",
MW_MATH_HTML => "HTML if possible or else PNG",
MW_MATH_SOURCE => "Leave it as TeX (for text browsers)",
MW_MATH_MODERN => "Recommended for modern browsers",
MW_MATH_MATHML => "MathML if possible (experimental)",
);
/* private */ $wgDateFormatsEn = array(

View file

@ -49,6 +49,7 @@ $wgEditEncoding = "x";
"HTMLigu se eble, aŭ PNG",
"Lasu TeX-fonton (por tekstfoliumiloj)",
"Rekomendita por modernaj foliumiloj",
"MathML seeble (provizora)",
);
/* private */ $wgUserTogglesEo = array(