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