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