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        global $INPUT;
135
136        list($size, $math, $align) = $data;
137        $scale = 1;
138        if(is_a($R, 'renderer_plugin_dw2pdf')) $scale=3;
139        $size = $size * $scale;
140        $ident = md5($math . '-' . $size);
141
142        // check if we have a cached version available
143        $imagefile = getCacheName($ident, '.mathpublish.png');
144        if(!file_exists($imagefile) || $INPUT->bool('purge')) {
145            require_once(__DIR__ . '/phpmathpublisher/load.php');
146            $pmp = new \RL\PhpMathPublisher\PhpMathPublisher('', '', $size);
147            $pmp->getHelper()->setTransparent(true);
148            $pmp->renderImage($math, $imagefile) - 1000;
149        }
150
151        // pass local files to PDF renderer
152        if(is_a($R, 'renderer_plugin_dw2pdf')) {
153            $img = 'dw2pdf://' . $imagefile;
154        } else {
155            $img = DOKU_BASE . 'lib/plugins/mathpublish/img.php?img=' . $ident;
156        }
157
158        list($width, $height) = getimagesize($imagefile);
159        $width = $width/$scale;
160        $height = $height/$scale;
161
162        // output aligned image
163        if($mode == 'odt') {
164            if(!$align) {
165                $align = 'left';
166            }
167            $R->_odtAddImage($imagefile, $width, $height, $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 '. buildAttributes([
177                    'src' => $img,
178                    'class' => $align . ' mathpublish',
179                    'alt' => $math,
180                    'title' => $math,
181                    'width' => $width,
182                    'height' => $height,
183                    'style' => "display: $display; object-fit: fill;",
184                ]).' />';
185        }
186        return true;
187    }
188
189    /**
190     * check if php installation has required libraries/functions
191     */
192    private function _requirements_ok() {
193        if(!function_exists('imagepng')) {
194            $this->_msg($this->getLang('nopng'), -1);
195            return false;
196        }
197
198        if(!function_exists('imagettftext')) {
199            $this->_msg($this->getLang('noft'), -1);
200            return false;
201        }
202
203        return true;
204    }
205
206    /**
207     * used to avoid multiple messages
208     *
209     * @param string $str
210     * @param int $lvl
211     */
212    protected function _msg($str, $lvl = 0) {
213        if($this->msg_sent) return;
214
215        msg($str, $lvl);
216        $this->msg_sent = true;
217    }
218}
219
220