xref: /plugin/ditaa/syntax.php (revision 400928764bab3ea578d88188e43b8b6f9d0390b8)
1<?php
2/**
3 * Ditaa-Plugin: Converts Ascii-Flowcharts into a png-File
4 *
5 * @license     GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author      Dennis Ploeger <develop [at] dieploegers [dot] de>
7 * @author      Christoph Mertins <c [dot] mertins [at] gmail [dot] com>
8 * @author      Gerry Weißbach / i-net software <tools [at] inetsoftware [dot] de>
9 * @author      Christian Marg <marg@rz.tu-clausthal.de>
10 * @author      Andreas Gohr <andi@splitbrain.org>
11 */
12
13if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
14if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
15require_once(DOKU_PLUGIN.'syntax.php');
16
17class syntax_plugin_ditaa extends DokuWiki_Syntax_Plugin {
18
19    /**
20     * What about paragraphs?
21     */
22    function getPType(){
23        return 'normal';
24    }
25
26    /**
27     * What kind of syntax are we?
28     */
29    function getType(){
30        return 'substition';
31    }
32
33    /**
34     * Where to sort in?
35     */
36    function getSort(){
37        return 200;
38    }
39
40    /**
41     * Connect pattern to lexer
42     */
43
44    function connectTo($mode) {
45        $this->Lexer->addSpecialPattern('<ditaa.*?>\n.*?\n</ditaa>',$mode,'plugin_ditaa');
46    }
47
48    /**
49     * Handle the match
50     */
51    function handle($match, $state, $pos, Doku_Handler $handler) {
52        $info = $this->getInfo();
53
54        // prepare default data
55        $return = array(
56                        'width'     => 0,
57                        'height'    => 0,
58                        'antialias' => true,
59                        'edgesep'   => true,
60                        'round'     => false,
61                        'shadow'    => true,
62                        'scale'     => 1,
63                        'align'     => '',
64                        'version'   => $info['date'], //force rebuild of images on update
65                       );
66
67
68        // prepare input
69        $lines = explode("\n",$match);
70        $conf = array_shift($lines);
71        array_pop($lines);
72
73        // match config options
74        if(preg_match('/\b(left|center|right)\b/i',$conf,$match)) $return['align'] = $match[1];
75        if(preg_match('/\b(\d+)x(\d+)\b/',$conf,$match)){
76            $return['width']  = $match[1];
77            $return['height'] = $match[2];
78        }
79        if(preg_match('/\b(\d+(\.\d+)?)X\b/',$conf,$match)) $return['scale']  = $match[1];
80        if(preg_match('/\bwidth=([0-9]+)\b/i', $conf,$match)) $return['width'] = $match[1];
81        if(preg_match('/\bheight=([0-9]+)\b/i', $conf,$match)) $return['height'] = $match[1];
82        // match boolean toggles
83        if(preg_match_all('/\b(no)?(antialias|edgesep|round|shadow)\b/i',$conf,$matches,PREG_SET_ORDER)){
84            foreach($matches as $match){
85                $return[$match[2]] = ! $match[1];
86            }
87        }
88
89        $input = join("\n",$lines);
90        $return['md5'] = md5($input); // we only pass a hash around
91
92        // store input for later use
93        io_saveFile($this->_cachename($return,'txt'),$input);
94
95        return $return;
96    }
97
98    /**
99     * Prepares the Data that is used for the cache name
100     * Width, height and scale are left out.
101     * Ensures sanity.
102     */
103    function _prepareData($input)
104    {
105        $output = array();
106        foreach( $input as $key => $value ) {
107            switch ($key) {
108                case 'scale':
109                case 'antialias':
110                case 'edgesep':
111                case 'round':
112                case 'shadow':
113                case 'md5':
114                    $output[$key] = $value;
115            };
116        }
117
118        ksort($output);
119        return $output;
120    }
121
122    /**
123     * Cache file is based on parameters that influence the result image
124     */
125    function _cachename($data,$ext){
126		$data = $this->_prepareData($data);
127        return getcachename(join('x',array_values($data)),'.ditaa.'.$ext);
128    }
129
130    /**
131     * Create output
132     */
133    function render($format, Doku_Renderer $R, $data) {
134        global $ID;
135        if($format == 'xhtml'){
136
137            // Only use the md5 key
138            $img = ml($ID, array('ditaa' => $data['md5']));
139            $R->doc .= '<img src="'.$img.'" class="media'.$data['align'].'" alt=""';
140            if($data['width'])  $R->doc .= ' width="'.$data['width'].'"';
141            if($data['height']) $R->doc .= ' height="'.$data['height'].'"';
142            if($data['align'] == 'right') $R->doc .= ' align="right"';
143            if($data['align'] == 'left')  $R->doc .= ' align="left"';
144            $R->doc .= '/>';
145            return true;
146        }else if($format == 'odt'){
147            $src = $this->_imgfile($data);
148            $R->_odtAddImage($src,$data['width'],$data['height'],$data['align']);
149            return true;
150        }else if($format == 'metadata'){
151            // Save for later use
152            $R->meta['ditaa'][$data['md5']] = $data;
153            return true;
154        }
155        return false;
156    }
157
158
159    /**
160     * Return path to the rendered image on our local system
161     */
162    function _imgfile($id, $data, $secondTry=false){
163
164        $cache  = $this->_cachename($data,'png');
165
166        // create the file if needed
167        if(!file_exists($cache)){
168            $in = $this->_cachename($data,'txt');
169            // If this is nt yet here, force geting instructions and writing the thing back.
170            if ( $secondTry != true && !file_exists($in)) {
171                p_get_instructions( io_readFile( wikiFN( $id) ) );
172                return $this->_imgfile($id, $data, true);
173            }
174
175            if($this->getConf('java')){
176                $ok = $this->_run($data,$in,$cache);
177            }else{
178                $ok = $this->_remote($data,$in,$cache);
179            }
180            if(!$ok) return false;
181            clearstatcache();
182        }
183
184        // resized version
185        if($data['width']){
186            $cache = media_resize_image($cache,'png',$data['width'],$data['height']);
187        }
188
189        // something went wrong, we're missing the file
190        if(!file_exists($cache)) return false;
191
192        return $cache;
193    }
194
195    /**
196     * Render the output remotely at ditaa.org
197     */
198    function _remote($data,$in,$out){
199        if(!file_exists($in)){
200            if($conf['debug']){
201                dbglog($in,'no such ditaa input file');
202            }
203            return false;
204        }
205
206        $http = new DokuHTTPClient();
207        $http->timeout=30;
208
209        $pass = array();
210        $pass['scale']   = $data['scale'];
211        $pass['timeout'] = 25;
212        $pass['grid']    = io_readFile($in);
213        if(!$data['antialias']) $pass['A'] = 'on';
214        if(!$data['shadow'])    $pass['S'] = 'on';
215        if($data['round'])      $pass['r'] = 'on';
216        if(!$data['edgesep'])   $pass['E'] = 'on';
217
218        $img = $http->post('http://ditaa.org/ditaa/render',$pass);
219        if(!$img) return false;
220
221        return io_saveFile($out,$img);
222    }
223
224    /**
225     * Run the ditaa Java program
226     */
227    function _run($data,$in,$out) {
228        global $conf;
229
230        if(!file_exists($in)){
231            if($conf['debug']){
232                dbglog($in,'no such ditaa input file');
233            }
234            return false;
235        }
236
237        $cmd  = $this->getConf('java');
238        $cmd .= ' -Djava.awt.headless=true -Dfile.encoding=UTF-8 -jar';
239        $cmd .= ' '.escapeshellarg(dirname(__FILE__).'/ditaa/ditaa0_9.jar'); //ditaa jar
240        $cmd .= ' --encoding UTF-8';
241        $cmd .= ' '.escapeshellarg($in); //input
242        $cmd .= ' '.escapeshellarg($out); //output
243        $cmd .= ' -s '.escapeshellarg($data['scale']);
244        if(!$data['antialias']) $cmd .= ' -A';
245        if(!$data['shadow'])    $cmd .= ' -S';
246        if($data['round'])      $cmd .= ' -r';
247        if(!$data['edgesep'])   $cmd .= ' -E';
248
249        exec($cmd, $output, $error);
250
251        if ($error != 0){
252            if($conf['debug']){
253                dbglog(join("\n",$output),'ditaa command failed: '.$cmd);
254            }
255            return false;
256        }
257
258        return true;
259    }
260
261}
262
263