1<?php 2/** 3 * Mathmulti Plugin: Render (La)Tex or plain text Math expression 4 * into images or mathml 5 * http://wiki.splitbrain.org/plugin:math 6 * 7 * Thanks to everyone how provided so many examples of plugin or 8 * contributed to the plugin tutorials... This was of great help! 9 * If you see part of your code here and I forgot to give credits 10 * for your work, please let me know so I could add your name. 11 * 12 * Syntax: <math format idiom size>...mathematical formula..</math> 13 * 14 * format (optional) Specify which program will be used to 15 * render the math formulae. 16 * Support: 17 * img (use mimetex http://www.forkosh.com/mimetex.html) 18 * mml (use: 19 * itex2mml http://pear.math.pitt.edu/mathzilla/itex2mml.html 20 * plain2mml http://math.wcupa.edu/~johnston/plain2mathml ) 21 * If might be better not to specify the format thus you 22 * can change it at once for all math expression in your 23 * wiki by changing the config value. 24 * idiom (optional) Specify in which idiom (math dialect) 25 * the math formulae is written in 26 * So far, only 'tex' [(La)TeX] and 'plain' style are supported 27 * 'tex': http://www.forkosh.com/mimetex.html 28 * or http://pear.math.pitt.edu/mathzilla/itex2mml.html 29 * 'plain': http://math.wcupa.edu/~johnston/plain2mathml/ 30 * please note that output for plain are in mml only 31 * if img is specified, the raw text is displayed 32 * Might be used to merge with Christopher Smith's 33 * math plugin (phpmathpublisher own synthax) 34 * size (optional) Default size of the Math char [points] 35 * 36 * Formulae syntax: (depend on Idiom) See mimetex, itex2mml, plain2mml above 37 * (may add phpmathpublisher own synthax) 38 * 39 * Config (in dokuwiki/conf/local.php) 40 * $conf['mathmulti_iscgi'] = false; //Are eqn images produced by cgi every time the page is viewed? 41 * $conf['mathmulti_mimetex'] = ""; //Path to mimetex bin (produce img) 42 * $conf['mathmulti_itex2mml'] = ""; //Path to itex2mml bin (produce mathml) 43 * $conf['mathmulti_plain2mml'] = ""; //Path to plain2mml bin (produce mathml) 44 * $conf['mathmulti_format'] = "img"; //default mathmulti rendering format 45 * $conf['mathmulti_idiom'] = "tex"; //default mathmulti math idiom (dialect) 46 * $conf['mathmulti_size'] = "12"; //default mathmulti math char size [points] 47 * 48 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 49 * @author Stephane Chamberland <stephane.chamberland@ec.gc.ca> 50 * @date 2006-05-25 51 */ 52 53if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); 54if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 55require_once(DOKU_PLUGIN.'syntax.php'); 56require_once(DOKU_INC.'inc/io.php'); 57 58// ----[ mathmulti plugin globals ]--------------------------------------- 59global $mathmultiplugin_urlimg,$mathmultiplugin_dirimg; 60 61 // base url to access images, should correspond to $dirimg below. 62 // if left at default, it will be modified to add a subfolder to avoid filling 63 // the root media folder with clutter, refer _cacheExists() 64 $mathmultiplugin_urlimg = DOKU_URL.'lib/exe/fetch.php?w=&h=&cache=cache&media='; 65 $mathmultiplugin_dirimg = DOKU_INC; 66 67// ----------------------------------------------------------------------- 68 69/** 70 * All DokuWiki plugins to extend the parser/rendering mechanism 71 * need to inherit from this class 72 */ 73class syntax_plugin_mathmulti extends DokuWiki_Syntax_Plugin { 74 75 var $prefix = 'mathmulti_'; 76 77 var $inlineopt = array(); 78 79 //TODO: take these values from conf/metadata.php 80 var $mathmulti_options = array( 81 'format' => array('img', 'mml'), 82 'idiom' => array('tex', 'plain'), 83 'size' => array('9','10','11','12','13','14','15','16','17','18') 84 ); 85 86 var $mathmulti_engines = array( 87 'mimetex' => array('idiom' => 'tex', 88 'format' => 'img'), 89 'itex2mml' => array('idiom' => 'tex', 90 'format' => 'mml'), 91 'plain2mml' => array('idiom' => 'plain', 92 'format' => 'mml') 93 ); 94 95 var $msg_disable = 'disable'; 96 97 var $enable = false; 98 var $msg_sent = false; 99 100 /** 101 * Initialization? 102 */ 103 function syntax_plugin_mathmulti() { 104 $this->msg_disable = $this->getLang($this->prefix.'disable'); 105 $this->enable = $this->_requirements_ok(); 106 } 107 108 /** 109 * return some info 110 */ 111 function getInfo(){ 112 113 return array( 114 'author' => 'Stephane Chamberland', 115 'email' => 'stephane.chamberland@ec.gc.ca', 116 'date' => '2006-05-23', 117 'name' => 'MathMulti Plugin'.(!$this->enable ? ' ('.$this->getLang($this->prefix.'disable').')' : ''), 118 'desc' => $this->getLang($this->prefix.'info'). 119 (!$this->enable ? "\n(".$this->getLang($this->prefix.'disable').")" : ''), 120 'url' => 'http://wiki.splitbrain.org/plugin:math', 121 ); 122 } 123 124 function getType(){ return 'protected'; } 125 function getPType(){ return 'normal'; } 126 function getSort(){ return 209; } 127 128 /** 129 * Connect pattern to lexer 130 */ 131 function connectTo($mode) { 132 $this->Lexer->addEntryPattern('<math(?=[^\r\n]*?>.*?</math>)',$mode,'plugin_mathmulti'); 133 } 134 135 function postConnect() { 136 $this->Lexer->addExitPattern('</math>','plugin_mathmulti'); 137 } 138 139 /** 140 * Handle the match 141 */ 142 function handle($match, $state, $pos, &$handler){ 143 144 if ( $state == DOKU_LEXER_UNMATCHED ) { 145 list($optstring, $contentstring) = preg_split('/>/u', $match, 2); // will split into format & math formulae 146 147 //TODO: ?needed? string2lower($optstring); 148 149 $tokens = preg_split('/\s+/', $optstring, 9); // limit is defensive 150 $optionlist = $this->_parseoptions($this->mathmulti_options, 151 $this->prefix,$tokens); 152 153 $engine = $this->_getengine( 154 $optionlist, 155 $this->mathmulti_engines, 156 $this->prefix, 157 !$this->getConf($this->prefix.'iscgi')); 158 159 $align = $this->_checkalign($contentstring); 160 161 return (array($engine, $optionlist['size'], $align, trim($contentstring))); 162 } 163 return false; 164 } 165 166 /** 167 * Create output 168 */ 169 function render($mode, &$renderer, $data) { 170 171 if (!$data) return; // skip rendering for the enter and exit patterns 172 list($engine, $size, $align, $eqn) = $data; 173 174 if($mode == 'xhtml'){ 175 $eqn_html = $renderer->_xmlEntities($eqn); 176 if ($this->enable) { 177 if ($engine == 'mimetex') { 178 if ($this->getConf($this->prefix.'iscgi')) { 179 $eqn_html = 180 $this->_render_mimetexcgi($eqn,$eqn_html,$size, $align); 181 } else { 182 $eqn_html = 183 $this->_render_mimetex($eqn,$eqn_html,$size, $align); 184 } 185 } else if ($engine == 'itex2mml') { 186 $eqn_html = 187 $this->_render_itex2mml($eqn,$eqn_html,$size, $align); 188 } else if ($engine == 'plain2mml') { 189 $eqn_html = 190 $this->_render_plain2mml($eqn,$eqn_html,$size, $align); 191 } else { 192 $eqn_html = 193 $this->_render_plaintext($eqn,$eqn_html,$size, $align); 194 } 195 } else { 196 $this->_msg($this->msg_disable, -1); 197 } 198 $renderer->doc .= $eqn_html; 199 // return to previous error reporting level 200 error_reporting($error_level); 201 return true; 202 } 203 return false; 204 } 205 206 207 function _render_plaintext($eqn,$eqn_html,$size,$align) { 208 if ($align == 'normal') { 209 return '<span class="math">'.$eqn_html.'</span>'; 210 } else { 211 return '<div class="math '.$align.'">'.$eqn_html.'</div>'; 212 } 213 } 214 215 //TODO: use $size param 216 function _render_mimetexcgi($eqn,$eqn_html,$size,$align) { 217 $myclass=' class="math"'; 218 if ($align != 'normal') { 219 $math_html = ' class="math media'.$align.'"'; 220 } 221 return '<img src="'.$this->getConf($this->prefix.'mimetex').'?'. 222 $eqn_html.'" alt="'.$eqn_html.'" '.$myclass.'/>'; 223 } 224 225 //TODO: create mathML eqn with mimetex 226 function _render_mimetex($eqn,$eqn_html,$size,$align) { 227 return $this->_render_plaintext($eqn,$eqn_html,$size, $align); 228 } 229 230 //TODO: create and cache img with mimetex 231// function _old_mathmultiimage() { 232// //return $renderer->_xmlEntities($mathmulti_eqn); 233// 234// $fontsize0_7 = max(0,min(intval($msize-9,7))); 235// 236// $hash = md5(serialize($data.strval($msize))); 237// 238// //write eqn in temp file 239// $tmpeqnfile= $mathmultiplugin_urlimg.""; 240// $fh = fopen($tmpeqnfile, 'w'); 241// fwrite($fh, $mathmulti_eqn); 242// fclose($fh); 243// 244// //TODO: remove old img.gif file if any 245// $oldgiffile = ""; 246// if (is_file($oldgiffile)) {unlink($oldgiffile);} 247// //TODO: define img.gif file name 248// $giffile = ""; 249// //Create img file 250// $fh = popen($this->getConf('mathmulti_mimetex').' -e '.$giffile.' -s '.$fontsize0_7." -f ".$mathmulti_eqn_file,'r'); 251// pclose($fh); 252// 253// unlink($tmpeqnfile); //TODO: remove eqn temp file 254// 255// //Write display image HTML code 256// $myclass=' class="math"'; 257// if ($align != 'normal') { 258// $math_html = 'class="math media'.$align.'"'; 259// } 260// return '<img src="'.$giffile.'" 261// alt="'.$renderer->_xmlEntities($mathmulti_eqn). 262// $myclass.'/>'; 263// } 264 265 //TODO: create mathML eqn with itex2mml 266 function _render_itex2mml($eqn,$eqn_html,$size, $align) { 267 return $this->_render_plaintext($eqn,$eqn_html,$size, $align); 268 } 269 270 //TODO: create mathML eqn with plain2mml 271 function _render_plain2mml($eqn,$eqn_html,$size, $align) { 272 return $this->_render_plaintext($eqn,$eqn_html,$size, $align); 273 } 274 275 276 /** 277 * Check string alignment (return left,right,center,normal) 278 * param: Not trimed string 279 */ 280 function _checkalign($mystring){ 281 $align = 'normal'; 282 if (strlen($mystring) > 1) { 283 $c_first = $mystring{0}; 284 $c_last = $mystring{strlen($mystring)-1}; 285 286 $align = ($c_first == ' ') ? ($c_last == ' ' ? 'center' : 'right') : ($c_last == ' ' ? 'left' : 'normal'); 287 } 288 return $align; 289 } 290 291 /** 292 * Init options (from default config) 293 * Params: 294 * $optionlistdict = array( 295 * 'option_1' => array('val_1', 'val_2'), 296 * ...); 297 * $confprefix: [string] Prefix of options name in global $conf 298 * Return: 299 * $optionlist = array( 300 * 'option_1' => 'val_opt_1', 301 * ...); 302 */ 303 function _initoptions($optionlistdict,$confprefix) { 304 $optionlist = array(); 305 foreach ($optionlistdict as $option => $optlist) { 306 $optionlist[$option] = $this->getConf($confprefix.$option); 307 } 308 return $optionlist; 309 } 310 311 /** 312 * Init options (from default config + inline options) 313 * Params: 314 * $optionlistdict = array( 315 * 'option_1' => array('val_1', 'val_2'), 316 * ...); 317 * $confprefix: [string] Prefix of options name in global $conf 318 * $tokenlist: [string array] List of option keywords 319 * Return: 320 * $optionlist = array( 321 * 'option_1' => 'val_opt_1', 322 * ...); 323 */ 324 //TODO: gengeralize to take args as in config plugin 325 function _parseoptions($optionlistdict,$confprefix,$tokenlist) { 326 $optionlist = $this->_initoptions($optionlistdict,$confprefix); 327 foreach ($tokenlist as $token) { 328 foreach ($optionlistdict as $option => $optlist) { 329 foreach ($optlist as $optionval) { 330 if (trim($token) == trim($optionval)) { 331 $optionlist[$option] = $optionval; 332 } 333 } 334 } 335 } 336 return $optionlist; 337 } 338 339 /** 340 * Look into options and "engine" spec (matching options) to 341 * 342 * Params: 343 * $optionlist: as obtained by $this->_parseoptions() 344 * $enginedictlist: list of engines and matching option => values 345 * = array('engine_1' => array('option_1' => 'val_1', 346 * 'option_2' => 'val_2',...), 347 * ...); 348 * $confprefix: [string] Prefix of options name in global $conf 349 * $checkfile: [logical] Check if specified file for engine exist 350 * Return: 351 * $engine: [string] engine name (use to render code) 352 * that matches default/specified options 353 * false: if no "engine" match the options 354 */ 355 function _getengine($optionlist,$enginedictlist,$confprefix,$checkfile) { 356 foreach ($enginedictlist as $engine => $engoptdict) { 357 $engineok = true; 358 foreach ($engoptdict as $option => $optionval) { 359 if ($optionlist[$option] != $optionval) { 360 $engineok = false; 361 } 362 } 363 if ($engineok) { 364 if ($checkfile) { 365 if (!is_file($this->getConf($confprefix.$engine))) { 366 return false; 367 } 368 } 369 return $engine; 370 } 371 } 372 return false; 373 } 374 375 /** 376 * Cheque requirements... 377 */ 378 function _requirements_ok() { 379 380 //check if at least one engine is specified 381 $engineok = false; 382 foreach ($this->mathmulti_engines as $engine => $engoptdict) { 383 if ($this->getConf($this->prefix.$engine) != "") { 384 $engineok = true; 385 } 386 } 387 if (!$engineok) { 388 $this->msg_disable .= $this->getLang($this->prefix.'noengine'); 389 return false; 390 } 391 392 //check if the default settings/engine are ok 393 if (!$this->_getengine( 394 $this->_initoptions($this->mathmulti_options,$this->prefix), 395 $this->mathmulti_engines, 396 $this->prefix, 397 !$this->getConf($this->prefix.'iscgi'))) { 398 $this->msg_disable .= $this->getLang($this->prefix.'errdefault'); 399 return false; 400 } 401 402 return true; 403 } 404 405 406 /** 407 * 408 */ 409 function _cacheExists() { 410 global $dirimg, $mathmultiplugin_urlimg, $conf; 411 412 // check for default setting 413 if (!isset($dirimg) || !$dirimg) { $dirimg = $this->conf['mediadir']; } 414 if ($dirimg == $conf['mediadir']) { 415 // we don't want to clutter the root media dir, so create our own subfolder 416 $dirimg .= "/cache_mathmultiplugin"; 417 $mathmultiplugin_urlimg .= "cache_mathmultiplugin%3a"; 418 419 if (!@is_dir($dirimg)) { 420 $this->_mkdir($dirimg); 421 } 422 } 423 424 return @is_writable($dirimg); 425 } 426 427 /** 428 * used to avoid multiple messages 429 */ 430 function _msg($str, $lvl=0) { 431 if ($this->msg_sent) return; 432 433 msg($str, $lvl); 434 $this->msg_sent = true; 435 } 436 437 /** 438 * 439 */ 440 // would like to see this function in io.php :) 441 function _mkdir($d) { 442 global $conf; 443 444 umask($conf['dmask']); 445 $ok = io_mkdir_p($d); 446 umask($conf['umask']); 447 return $ok; 448 } 449 450} 451 452//Setup VIM: ex: et ts=4 enc=utf-8 : 453