1<?php
2/**
3 * Math Plugin: incorporate mathematical formulae using MathPublisher into Dokuwiki
4 *
5 * Syntax:     <m size>...mathematical formula..</m>
6 *   size      (optional) base glyph size in pixels,
7 *             if not present will use the value of $mathplugin_size global, the value
8 *             of which can be set below (default: 12)
9 *
10 * Formulae syntax:  refer http://www.xm1math.net/phpmathpublisher/doc/help.html
11 *
12 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
13 * @author     Christopher Smith <chris@jalakai.co.uk>
14 * @date       2005-12-17
15 *
16 * phpmathpublisher
17 * @link       http://www.xm1math.net/phpmathpublisher/
18 * @author     Pascal Brachet
19 */
20
21if(!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/');
22if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
23require_once(DOKU_PLUGIN . 'syntax.php');
24
25/**
26 * All DokuWiki plugins to extend the parser/rendering mechanism
27 * need to inherit from this class
28 */
29class syntax_plugin_mathpublish extends DokuWiki_Syntax_Plugin {
30
31    protected $enable = false;
32    protected $msg_sent = false;
33
34    /**
35     * syntax_plugin_mathpublish constructor.
36     */
37    public function __construct() {
38        $this->enable = $this->_requirements_ok();
39    }
40
41    /**
42     * Syntax Type
43     *
44     * Needs to return one of the mode types defined in $PARSER_MODES in parser.php
45     *
46     * @return string
47     */
48    public function getType() {
49        return 'substition';
50    }
51
52    /**
53     * Paragraph Type
54     *
55     * Defines how this syntax is handled regarding paragraphs. This is important
56     * for correct XHTML nesting. Should return one of the following:
57     *
58     * 'normal' - The plugin can be used inside paragraphs
59     * 'block'  - Open paragraphs need to be closed before plugin output
60     * 'stack'  - Special case. Plugin wraps other paragraphs.
61     *
62     * @see Doku_Handler_Block
63     *
64     * @return string
65     */
66    public function getPType() {
67        return 'normal';
68    }
69
70    /**
71     * Sort for applying this mode
72     *
73     * @return int
74     */
75    public function getSort() {
76        return 208;
77    }
78
79    /**
80     * Connect pattern to lexer
81     * @param string $mode
82     */
83    public function connectTo($mode) {
84        $this->Lexer->addSpecialPattern('<m.*?>.*?(?:</m>)', $mode, 'plugin_mathpublish');
85    }
86
87    /**
88     * Handle the match
89     * @param string $match
90     * @param int $state
91     * @param int $pos
92     * @param Doku_Handler $handler
93     * @return array
94     */
95    public function handle($match, $state, $pos, Doku_Handler $handler) {
96        $match = substr($match, 2, -4); // strip '<m' and '</m>'
97        list($size, $math) = explode('>', $match, 2);
98        if(!$math) {
99            $math = $size;
100            $size = '';
101        }
102        $size = (int) trim($size);
103        if(!$size) $size = 12;
104
105        if(strlen($math) > 1) {
106            $c_first = $math{0};
107            $c_last = $math{strlen($math) - 1};
108            if($c_first == ' ') {
109                if($c_last == ' ') {
110                    $align = 'center';
111                } else {
112                    $align = 'right';
113                }
114            } else {
115                if($c_last == ' ') {
116                    $align = 'left';
117                } else {
118                    $align = '';
119                }
120            };
121        } else {
122            $align = '';
123        }
124
125        return (array($size, trim($math), $align));
126    }
127
128    /**
129     * Create output
130     * @param string $mode
131     * @param Doku_Renderer $R
132     * @param array $data
133     * @return bool
134     */
135    function render($mode, Doku_Renderer $R, $data) {
136        if(!$this->enable) return true;
137        if($mode != 'xhtml' && $mode != 'odt') return false;
138
139        list($size, $math, $align) = $data;
140        $ident = md5($math . '-' . $size);
141
142        // check if we have a cached version available
143        $valignfile = getCacheName($ident, '.mathpublish.valign');
144        $imagefile = getCacheName($ident, '.mathpublish.png');
145        if(file_exists($valignfile)) {
146            $valign = (int) io_readFile($valignfile);
147        } else {
148            require_once(__DIR__ . '/phpmathpublisher/load.php');
149            $pmp = new \RL\PhpMathPublisher\PhpMathPublisher('', '', $size);
150            $pmp->getHelper()->setTransparent(true);
151            $valign = $pmp->renderImage($math, $imagefile) - 1000;
152            io_saveFile($valignfile, $valign);
153        }
154
155        // pass local files to PDF renderer
156        if(is_a($R, 'renderer_plugin_dw2pdf')) {
157            $img = 'dw2pdf://' . $imagefile;
158        } else {
159            $img = DOKU_BASE . 'lib/plugins/mathpublish/img.php?img=' . $ident;
160        }
161
162        // output aligned image
163        if($mode == 'odt') {
164            if(!$align) {
165                $align = 'left';
166            }
167            $R->_odtAddImage($imagefile, NULL, NULL, $align, NULL, NULL);
168        } else {
169            if($align) {
170                $display = 'block';
171                $align = "media$align";
172            } else {
173                $display = 'inline-block';
174            }
175
176            $R->doc .= '<img src="' . $img . '"
177                         class="' . $align . ' mathpublish"
178                         alt="' . hsc($math) . '"
179                         title="' . hsc($math) . '"
180                         style="display: ' . $display . '; vertical-align:' . $valign . 'px" />';
181        }
182        return true;
183    }
184
185    /**
186     * check if php installation has required libraries/functions
187     */
188    private function _requirements_ok() {
189        if(!function_exists('imagepng')) {
190            $this->_msg($this->getLang('nopng'), -1);
191            return false;
192        }
193
194        if(!function_exists('imagettftext')) {
195            $this->_msg($this->getLang('noft'), -1);
196            return false;
197        }
198
199        return true;
200    }
201
202    /**
203     * used to avoid multiple messages
204     *
205     * @param string $str
206     * @param int $lvl
207     */
208    protected function _msg($str, $lvl = 0) {
209        if($this->msg_sent) return;
210
211        msg($str, $lvl);
212        $this->msg_sent = true;
213    }
214}
215
216