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