]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/Math.php
Mediawiki 1.15.3-scripts
[autoinstalls/mediawiki.git] / includes / Math.php
1 <?php
2 /**
3  * Contain everything related to <math> </math> parsing
4  * @file
5  * @ingroup Parser
6  */
7
8 /**
9  * Takes LaTeX fragments, sends them to a helper program (texvc) for rendering
10  * to rasterized PNG and HTML and MathML approximations. An appropriate
11  * rendering form is picked and returned.
12  *
13  * @author Tomasz Wegrzanowski, with additions by Brion Vibber (2003, 2004)
14  * @ingroup Parser
15  */
16 class MathRenderer {
17         var $mode = MW_MATH_MODERN;
18         var $tex = '';
19         var $inputhash = '';
20         var $hash = '';
21         var $html = '';
22         var $mathml = '';
23         var $conservativeness = 0;
24
25         function __construct( $tex, $params=array() ) {
26                 $this->tex = $tex;
27                 $this->params = $params;
28         }
29
30         function setOutputMode( $mode ) {
31                 $this->mode = $mode;
32         }
33
34         function render() {
35                 global $wgTmpDirectory, $wgInputEncoding;
36                 global $wgTexvc;
37                 $fname = 'MathRenderer::render';
38
39                 if( $this->mode == MW_MATH_SOURCE ) {
40                         # No need to render or parse anything more!
41                         return ('$ '.htmlspecialchars( $this->tex ).' $');
42                 }
43                 if( $this->tex == '' ) {
44                         return; # bug 8372
45                 }
46
47                 if( !$this->_recall() ) {
48                         # Ensure that the temp and output directories are available before continuing...
49                         if( !file_exists( $wgTmpDirectory ) ) {
50                                 if( !wfMkdirParents( $wgTmpDirectory ) ) {
51                                         return $this->_error( 'math_bad_tmpdir' );
52                                 }
53                         } elseif( !is_dir( $wgTmpDirectory ) || !is_writable( $wgTmpDirectory ) ) {
54                                 return $this->_error( 'math_bad_tmpdir' );
55                         }
56
57                         if( function_exists( 'is_executable' ) && !is_executable( $wgTexvc ) ) {
58                                 return $this->_error( 'math_notexvc' );
59                         }
60                         $cmd = $wgTexvc . ' ' .
61                                         escapeshellarg( $wgTmpDirectory ).' '.
62                                         escapeshellarg( $wgTmpDirectory ).' '.
63                                         escapeshellarg( $this->tex ).' '.
64                                         escapeshellarg( $wgInputEncoding );
65
66                         if ( wfIsWindows() ) {
67                                 # Invoke it within cygwin sh, because texvc expects sh features in its default shell
68                                 $cmd = 'sh -c ' . wfEscapeShellArg( $cmd );
69                         }
70
71                         wfDebug( "TeX: $cmd\n" );
72                         $contents = `$cmd`;
73                         wfDebug( "TeX output:\n $contents\n---\n" );
74
75                         if (strlen($contents) == 0) {
76                                 return $this->_error( 'math_unknown_error' );
77                         }
78
79                         $retval = substr ($contents, 0, 1);
80                         $errmsg = '';
81                         if (($retval == 'C') || ($retval == 'M') || ($retval == 'L')) {
82                                 if ($retval == 'C') {
83                                         $this->conservativeness = 2;
84                                 } else if ($retval == 'M') {
85                                         $this->conservativeness = 1;
86                                 } else {
87                                         $this->conservativeness = 0;
88                                 }
89                                 $outdata = substr ($contents, 33);
90
91                                 $i = strpos($outdata, "\000");
92
93                                 $this->html = substr($outdata, 0, $i);
94                                 $this->mathml = substr($outdata, $i+1);
95                         } else if (($retval == 'c') || ($retval == 'm') || ($retval == 'l'))  {
96                                 $this->html = substr ($contents, 33);
97                                 if ($retval == 'c') {
98                                         $this->conservativeness = 2;
99                                 } else if ($retval == 'm') {
100                                         $this->conservativeness = 1;
101                                 } else {
102                                         $this->conservativeness = 0;
103                                 }
104                                 $this->mathml = NULL;
105                         } else if ($retval == 'X') {
106                                 $this->html = NULL;
107                                 $this->mathml = substr ($contents, 33);
108                                 $this->conservativeness = 0;
109                         } else if ($retval == '+') {
110                                 $this->html = NULL;
111                                 $this->mathml = NULL;
112                                 $this->conservativeness = 0;
113                         } else {
114                                 $errbit = htmlspecialchars( substr($contents, 1) );
115                                 switch( $retval ) {
116                                         case 'E':
117                                                 $errmsg = $this->_error( 'math_lexing_error', $errbit );
118                                                 break;
119                                         case 'S':
120                                                 $errmsg = $this->_error( 'math_syntax_error', $errbit );
121                                                 break;
122                                         case 'F':
123                                                 $errmsg = $this->_error( 'math_unknown_function', $errbit );
124                                                 break;
125                                         default:
126                                                 $errmsg = $this->_error( 'math_unknown_error', $errbit );
127                                 }
128                         }
129
130                         if ( !$errmsg ) {
131                                  $this->hash = substr ($contents, 1, 32);
132                         }
133
134                         wfRunHooks( 'MathAfterTexvc', array( &$this, &$errmsg ) );
135
136                         if ( $errmsg ) {
137                                  return $errmsg;
138                         }
139
140                         if (!preg_match("/^[a-f0-9]{32}$/", $this->hash)) {
141                                 return $this->_error( 'math_unknown_error' );
142                         }
143
144                         if( !file_exists( "$wgTmpDirectory/{$this->hash}.png" ) ) {
145                                 return $this->_error( 'math_image_error' );
146                         }
147
148                         if( filesize( "$wgTmpDirectory/{$this->hash}.png" ) == 0 ) {
149                                 return $this->_error( 'math_image_error' );
150                         }
151
152                         $hashpath = $this->_getHashPath();
153                         if( !file_exists( $hashpath ) ) {
154                                 if( !@wfMkdirParents( $hashpath, 0755 ) ) {
155                                         return $this->_error( 'math_bad_output' );
156                                 }
157                         } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
158                                 return $this->_error( 'math_bad_output' );
159                         }
160
161                         if( !rename( "$wgTmpDirectory/{$this->hash}.png", "$hashpath/{$this->hash}.png" ) ) {
162                                 return $this->_error( 'math_output_error' );
163                         }
164
165                         # Now save it back to the DB:
166                         if ( !wfReadOnly() ) {
167                                 $outmd5_sql = pack('H32', $this->hash);
168
169                                 $md5_sql = pack('H32', $this->md5); # Binary packed, not hex
170
171                                 $dbw = wfGetDB( DB_MASTER );
172                                 $dbw->replace( 'math', array( 'math_inputhash' ),
173                                   array(
174                                         'math_inputhash' => $dbw->encodeBlob($md5_sql),
175                                         'math_outputhash' => $dbw->encodeBlob($outmd5_sql),
176                                         'math_html_conservativeness' => $this->conservativeness,
177                                         'math_html' => $this->html,
178                                         'math_mathml' => $this->mathml,
179                                   ), $fname
180                                 );
181                         }
182                         
183                         // If we're replacing an older version of the image, make sure it's current.
184                         global $wgUseSquid;
185                         if ( $wgUseSquid ) {
186                                 $urls = array( $this->_mathImageUrl() );
187                                 $u = new SquidUpdate( $urls );
188                                 $u->doUpdate();
189                         }
190                 }
191
192                 return $this->_doRender();
193         }
194
195         function _error( $msg, $append = '' ) {
196                 $mf   = htmlspecialchars( wfMsg( 'math_failure' ) );
197                 $errmsg = htmlspecialchars( wfMsg( $msg ) );
198                 $source = htmlspecialchars( str_replace( "\n", ' ', $this->tex ) );
199                 return "<strong class='error'>$mf ($errmsg$append): $source</strong>\n";
200         }
201
202         function _recall() {
203                 global $wgMathDirectory;
204                 $fname = 'MathRenderer::_recall';
205
206                 $this->md5 = md5( $this->tex );
207                 $dbr = wfGetDB( DB_SLAVE );
208                 $rpage = $dbr->selectRow( 'math',
209                         array( 'math_outputhash','math_html_conservativeness','math_html','math_mathml' ),
210                         array( 'math_inputhash' => $dbr->encodeBlob(pack("H32", $this->md5))), # Binary packed, not hex
211                         $fname
212                 );
213
214                 if( $rpage !== false ) {
215                         # Tailing 0x20s can get dropped by the database, add it back on if necessary:
216                         $xhash = unpack( 'H32md5', $dbr->decodeBlob($rpage->math_outputhash) . "                " );
217                         $this->hash = $xhash ['md5'];
218
219                         $this->conservativeness = $rpage->math_html_conservativeness;
220                         $this->html = $rpage->math_html;
221                         $this->mathml = $rpage->math_mathml;
222
223                         $filename = $this->_getHashPath() . "/{$this->hash}.png";
224                         if( file_exists( $filename ) ) {
225                                 if( filesize( $filename ) == 0 ) {
226                                         // Some horrible error corrupted stuff :(
227                                         @unlink( $filename );
228                                 } else {
229                                         return true;
230                                 }
231                         }
232
233                         if( file_exists( $wgMathDirectory . "/{$this->hash}.png" ) ) {
234                                 $hashpath = $this->_getHashPath();
235
236                                 if( !file_exists( $hashpath ) ) {
237                                         if( !@wfMkdirParents( $hashpath, 0755 ) ) {
238                                                 return false;
239                                         }
240                                 } elseif( !is_dir( $hashpath ) || !is_writable( $hashpath ) ) {
241                                         return false;
242                                 }
243                                 if ( function_exists( "link" ) ) {
244                                         return link ( $wgMathDirectory . "/{$this->hash}.png",
245                                                         $hashpath . "/{$this->hash}.png" );
246                                 } else {
247                                         return rename ( $wgMathDirectory . "/{$this->hash}.png",
248                                                         $hashpath . "/{$this->hash}.png" );
249                                 }
250                         }
251
252                 }
253
254                 # Missing from the database and/or the render cache
255                 return false;
256         }
257
258         /**
259          * Select among PNG, HTML, or MathML output depending on
260          */
261         function _doRender() {
262                 if( $this->mode == MW_MATH_MATHML && $this->mathml != '' ) {
263                         return Xml::tags( 'math',
264                                 $this->_attribs( 'math',
265                                         array( 'xmlns' => 'http://www.w3.org/1998/Math/MathML' ) ),
266                                 $this->mathml );
267                 }
268                 if (($this->mode == MW_MATH_PNG) || ($this->html == '') ||
269                    (($this->mode == MW_MATH_SIMPLE) && ($this->conservativeness != 2)) ||
270                    (($this->mode == MW_MATH_MODERN || $this->mode == MW_MATH_MATHML) && ($this->conservativeness == 0))) {
271                         return $this->_linkToMathImage();
272                 } else {
273                         return Xml::tags( 'span',
274                                 $this->_attribs( 'span',
275                                         array( 'class' => 'texhtml' ) ),
276                                 $this->html );
277                 }
278         }
279
280         function _attribs( $tag, $defaults=array(), $overrides=array() ) {
281                 $attribs = Sanitizer::validateTagAttributes( $this->params, $tag );
282                 $attribs = Sanitizer::mergeAttributes( $defaults, $attribs );
283                 $attribs = Sanitizer::mergeAttributes( $attribs, $overrides );
284                 return $attribs;
285         }
286
287         function _linkToMathImage() {
288                 $url = $this->_mathImageUrl();
289
290                 return Xml::element( 'img',
291                         $this->_attribs(
292                                 'img',
293                                 array(
294                                         'class' => 'tex',
295                                         'alt' => $this->tex ),
296                                 array(
297                                         'src' => $url ) ) );
298         }
299
300         function _mathImageUrl() {
301                 global $wgMathPath;
302                 $dir = $this->_getHashSubPath();
303                 return "$wgMathPath/$dir/{$this->hash}.png";
304         }
305         
306         function _getHashPath() {
307                 global $wgMathDirectory;
308                 $path = $wgMathDirectory .'/' . $this->_getHashSubPath();
309                 wfDebug( "TeX: getHashPath, hash is: $this->hash, path is: $path\n" );
310                 return $path;
311         }
312         
313         function _getHashSubPath() {
314                 return substr($this->hash, 0, 1)
315                                         .'/'. substr($this->hash, 1, 1)
316                                         .'/'. substr($this->hash, 2, 1);
317         }
318
319         public static function renderMath( $tex, $params=array() ) {
320                 global $wgUser;
321                 $math = new MathRenderer( $tex, $params );
322                 $math->setOutputMode( $wgUser->getOption('math'));
323                 return $math->render();
324         }
325 }