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