1<?php 2/** 3 * Go diagrams for Dokuwiki 4 * 5 * This was ported from the GoDiag MediWiki extension which itself uses code 6 * and input syntax from from Sensei's Library 7 * 8 * @author Andreas Gohr <andi@splitbrain.org> 9 * 10 * @link http://meta.wikimedia.org/wiki/Go_diagrams 11 * @author Stanislav Traykov 12 * 13 * @link http://senseis.xmp.net/files/sltxt2png.php.txt published 14 * @author Arno Hollosi <ahollosi@xmp.net> 15 * @author Morten Pahle <morten@pahle.org.uk> 16 */ 17 18if(!defined('DOKU_INC')) die(); 19require_once(DOKU_PLUGIN.'syntax.php'); 20 21/** 22 * The godiag plugin class 23 */ 24class syntax_plugin_godiag extends DokuWiki_Syntax_Plugin { 25 26 var $dgm; // holds diagram data 27 var $seqno = 1; // if the same diagram is included more than once on a page, 28 // we need this to construct a unique image map id 29 30 /** 31 * Map syntax keywords to internal draw functions 32 */ 33 var $functab = array( 34 ',' => 'draw_hoshi', 35 'O' => 'draw_white', 36 'X' => 'draw_black', 37 'C' => 'draw_circle', 38 'S' => 'draw_square', 39 'B' => 'draw_black_circle', 40 'W' => 'draw_white_circle', 41 '#' => 'draw_black_square', 42 '@' => 'draw_white_square', 43 '_' => 'draw_wipe'); 44 45 /** 46 * Style settings for the generated diagram 47 */ 48 var $style = array( 49 'board_max' => 40, // max size of go board 50 'sgf_comment' => 'GoDiag Plugin for DokuWiki', 51 'sgf_link_txt' => 'SGF', 52 'ttfont' => 'Vera.ttf', // true type font 53 'ttfont_sz' => 10, // font size in px (roughly half of line_sp) 54 'line_sp' => 22, // spacing between two lines 55 'edge_sp' => 14, // spacing on edge of board 56 'line_begin' => 4, // line begin (if not edge) 57 'coord_sp' => 20, // spacing for coordinates (2 * font_sz or more) 58 'stone_radius' => 11, // (line_sp / 2 works nice) 59 'mark_radius' => 5, // radius of circle mark (about half of stone radius) 60 'mark_sqheight' => 8, // height of square mark (somewhat less than stone radius) 61 'link_sqheight' => 21, // height of link highlight (line_sp - 1) 62 'hoshi_radius' => 3, // star point radius 63 'goban_acolor' => array (242, 180, 101), // background color in RGB 64 'black_acolor' => array(0, 0, 0), 65 'white_acolor' => array(255, 255, 255), 66 'white_rim_acolor' => array(70, 70, 70), 67 'mark_acolor' => array(244, 0, 0), 68 'link_acolor_alpha' => array(10, 50, 255, 96), // R G B alpha (=transparency) 69 'line_acolor' => array(0, 0, 0), 70 'string_acolor' => array(0, 0, 0)); 71 72 /** 73 * Regular expression to parse hints for creating SGF files 74 */ 75 var $hintre = '/(\d|10) (?:at|on) (\d)/'; 76 77 /** 78 * Constructor. Initializes the diagram styles and localization 79 */ 80 function syntax_plugin_godiag() { 81 //set correct path to font file 82 $this->style['ttfont'] = dirname(__FILE__).'/Vera.ttf'; 83 } 84 85 /** 86 * What kind of syntax are we? 87 */ 88 function getType(){ 89 return 'substition'; 90 } 91 92 /** 93 * What HTML type are we? 94 */ 95 function getPType(){ 96 return 'block'; 97 } 98 99 /** 100 * Where to sort in? 101 */ 102 function getSort(){ 103 return 160; 104 } 105 106 /** 107 * Connect pattern to lexer 108 */ 109 function connectTo($mode) { 110 $this->Lexer->addSpecialPattern('<go>.*?</go>',$mode,'plugin_godiag'); 111 } 112 113 114 /** 115 * Create output 116 */ 117 function render($mode, Doku_Renderer $renderer, $data) { 118 if($mode != 'xhtml') return false; 119 120 // check for errors first 121 if($data['error']){ 122 $renderer->doc .= '<div class="error">Go Diagram plugin error: '.$data['error'].'</div>'; 123 return; 124 } 125 126 127 // create proper image map and attribute for IMG tag 128 $map_id = 'godiag__' . $this->seqno++ . $md5hash_png; 129 if($data['imap_html']) { 130 $data['imap_html'] = "<map id=\"$map_id\" name=\"$map_id\">" . $data['imap_html'] . '</map>'; 131 $godiag_imap_imgs = "usemap=\"#$map_id\""; 132 } else { 133 $godiag_imap_imgs = ''; 134 } 135 136 // now we have the HTML to be returned if everything went OK FIXME 137 $sgf_href = DOKU_BASE.'lib/plugins/godiag/fetch.php?f='.$data['md5hash_sgf'].'&t=sgf'; 138 $png_href = DOKU_BASE.'lib/plugins/godiag/fetch.php?f='.$data['md5hash_png'].'&t=png'; 139 140 $renderer->doc .= '<div class="godiag-' . $data['divclass'] . '">'; 141 $renderer->doc .= $data['imap_html']; 142 $renderer->doc .= '<div class="godiagi" style="width:'.$data['width'].'px;">'; 143 $renderer->doc .= '<img class="godiagimg" src="'.$png_href.'" alt="go diagram" '.$godiag_imap_imgs.'/>'; 144 $renderer->doc .= '<div class="godiagheading">'; 145 $renderer->doc .= hsc($data['heading']).' '; 146 $renderer->doc .= '<a href="'.$sgf_href.'" title="'.$this->getLang('sgfdownload').'">[SGF]</a>'; 147 $renderer->doc .= '</div></div></div>'; 148 149 if($data['break']) { 150 $return_str .= '<br class="godiag-' . $data['divclass'] . '"/>'; 151 } 152 153 $renderer->doc .= $return_str; 154 return true; 155 } 156 157 158 /** 159 * Handle the match. 160 * 161 * Most work is done here, like parsing the syntax and creating the image and SGF file 162 */ 163 function handle($match, $state, $pos, Doku_Handler $handler){ 164 $sourceandlinks = trim(substr($match,4,-5)); 165 166 167 // diagram specific things 168 $this->dgm = array ( 169 'dia' => array(), 170 'edge_top' => false, 171 'edge_bottom' => false, 172 'edge_left' => false, 173 'edge_right' => false, 174 'black_first' => true, 175 'coord_markers' => false, 176 'offset_x' => 0, 177 'offset_y' => 0, 178 'board_size' => 19, 179 'gridh' => 0, 180 'gridv' => 0, 181 'imap_html' => '', 182 'imappings' => array(), 183 'divclass' => 'right', // enclose HTML in div class="godiag-$whatever" 184 'break' => false); // append br style="clear: left|right" to HTML 185 186 // this will be concatenated to so we can compute a hash 187 $str_for_hash=''; 188 189 // strip empty space and final newline 190 $sourceandlinks=preg_replace('/(^|\n)\s*/',"$1", $sourceandlinks); 191 $sourceandlinks=preg_replace("/\s*$/",'', $sourceandlinks); 192 193 // separate source from links part 194 $source_parts=preg_split("/\n[\$\s]*(?=\[)/", $sourceandlinks, 2); 195 196 // links: store mappings for image map 197 if(array_key_exists(1, $source_parts)){ 198 foreach (explode("\n", $source_parts[1]) as $link_line) { 199 if(preg_match('/\[\s*([^\s])\s*\|\s*([^\]]+?)\s*\]/', $link_line, $matches)) { 200 $this->dgm['imappings'][$matches[1]]=$matches[2]; 201 } 202 } 203 } 204 ksort($this->dgm['imappings']); 205 foreach($this->dgm['imappings'] as $symb => $href) { 206 $str_for_hash .= $symb . '!' . $href . '!'; 207 } 208 209 // source 210 $source_lines=explode("\n", $source_parts[0]); 211 212 // header 213 preg_match('/(\$\$[^\s]*)?\s*(.*)/', $source_lines[0], $matches); 214 $hdr=$matches[1]; 215 $heading=$matches[2]; 216 $hdr=explode('#', $hdr); 217 $h_ops = $hdr[0]; 218 219 if(strpos($h_ops, 'W')) { 220 $this->dgm['black_first'] = false; 221 } 222 if(strpos($h_ops, 'b')) { 223 $this->dgm['break'] = 'true'; 224 } 225 if(strpos($h_ops, 'c')) { 226 $this->dgm['coord_markers'] = true; 227 } 228 if(strpos($h_ops, 'l')) { 229 $this->dgm['divclass'] = 'left'; 230 } 231 if(strpos($h_ops, 'r')) { 232 $this->dgm['divclass'] = 'right'; 233 } 234 if(preg_match('/(\d+)/', $h_ops, $matches)) { 235 $this->dgm['board_size'] = $matches[1]; 236 } 237 $this->dgm['title'] = $heading; 238 $heading = htmlspecialchars($heading); 239 $last=count($source_lines)-1; 240 if(preg_match('/^(\$|\s)*[-+]+\s*$/', $source_lines[$last])) { 241 $this->dgm['edge_bottom']=true; 242 unset($source_lines[$last]); 243 } 244 unset($source_lines[0]); 245 if(preg_match('/^(\$|\s)*[-+]+\s*$/', $source_lines[1])) { 246 $this->dgm['edge_top']=true; 247 unset($source_lines[1]); 248 } 249 250 // get the diagram into $this->dgm['dia'][y][x], figure out dimensions and edges, 251 // and generate html for image map 252 $row=0; 253 foreach ($source_lines as $source_line) { 254 if(preg_match('/^(\s|\$)*\|/', $source_line)) { $this->dgm['edge_left']=true; } 255 if(preg_match('/\|\s*$/', $source_line)) { $this->dgm['edge_right']=true; } 256 $plainstr=str_replace(array('$', ' ', '|'), '', $source_line); 257 $str_for_hash .= '$' . $plainstr; 258 $as_array=preg_split('//', $plainstr, -1, PREG_SPLIT_NO_EMPTY); 259 $len=count($as_array); 260 foreach($as_array as $bx => $symb) { 261 if(array_key_exists($symb, $this->dgm['imappings'])) { 262 $this->dgm['imap_html'] .= $this->imap_area($bx, $row, $this->dgm['imappings'][$symb]); 263 } 264 } 265 if($len>$this->dgm['gridh']) { $this->dgm['gridh']=$len; } 266 $this->dgm['dia'][$row++]=$as_array; 267 } 268 $this->dgm['gridv']=$row; 269 270 // calc dimensions 271 $dimh=($this->dgm['gridh']-1)*$this->style['line_sp']+$this->style['edge_sp']*2+1; 272 $dimv=($this->dgm['gridv']-1)*$this->style['line_sp']+$this->style['edge_sp']*2+1; 273 if($this->dgm['coord_markers']) { 274 $dimh += $this->style['coord_sp']; 275 $dimv += $this->style['coord_sp']; 276 } 277 if(($offs_sz_arr=$this->calc_offsets_and_size())===false) { 278 return $this->error_box('non-square board'); 279 } 280 list($this->dgm['board_size'], //this will be overwritten if it conflicts with other input 281 $this->dgm['offset_x'], 282 $this->dgm['offset_y']) = $offs_sz_arr; 283 284 if($this->dgm['board_size'] > $this->style['board_max']) 285 return $this->error_box('board too large (max is ' . $this->style['board_max'] . ')'); 286 287 //determine implicit edges 288 if($this->dgm['gridv'] >= $this->dgm['board_size'] - 1) { 289 $this->dgm['edge_top'] = true; 290 if($this->dgm['gridv'] == $this->dgm['board_size']) 291 $this->dgm['edge_bottom']=true; 292 } 293 if($this->dgm['gridh'] >= $this->dgm['board_size'] - 1) { 294 $this->dgm['edge_left']=true; 295 if($this->dgm['gridh'] == $this->dgm['board_size']) 296 $this->dgm['edge_right']=true; 297 } 298 299 // compute hashes (yes this duplicates a bit of what getCacheName() would do anyway, 300 // but keeping it here makes porting easier) 301 $str_for_hash .= '!' . $this->dgm['black_first'] 302 . '!' . $this->dgm['edge_top'] 303 . '!' . $this->dgm['edge_bottom'] 304 . '!' . $this->dgm['edge_left'] 305 . '!' . $this->dgm['edge_right']; 306 307 $str_for_hash_sgf = $str_for_hash 308 . '!' . $this->dgm['board_size'] 309 . '!' . $this->dgm['title']; 310 311 $str_for_hash_img = $str_for_hash 312 . '!' . ($this->dgm['coord_markers'] ? $this->dgm['board_size'] : false); 313 314 $md5hash_png=md5($str_for_hash_img . '!' . serialize($this->style)); 315 $md5hash_sgf=md5($str_for_hash_sgf); 316 317 // get filenames 318 $filename_png = getCacheName($md5hash_png,'.godiag.png'); 319 $filename_sgf = getCacheName($md5hash_sgf,'.godiag.sgf'); 320 321 // if we don't have the PNG for this diagram, create it 322 if(@filemtime($filename_png) < filemtime(__FILE__)) { 323 $draw_result = $this->save_diagram($dimh, $dimv, $filename_png); 324 if($draw_result!==0) return $draw_result; // contains error message 325 } 326 327 // if we don't have the SGF for this diagram, create it 328 if(@filemtime($filename_sgf) < filemtime(__FILE__)) { 329 if(!io_saveFile($filename_sgf,$this->SGF())){ 330 return $this->error_box("Cannot create SGF file."); 331 } 332 } 333 334 // now pass only the interesting data to the renderer 335 $data = array( 336 'md5hash_png' => $md5hash_png, 337 'md5hash_sgf' => $md5hash_sgf, 338 'imap_html' => $this->dgm['imap_html'], 339 'divclass' => $this->dgm['divclass'], 340 'break' => $this->dgm['break'], 341 'heading' => $heading, 342 'width' => $dimh, 343 'height' => $dimv, 344 ); 345 346 return $data; 347 } // end of get_html() 348 349 /** 350 * creates an anti-aliased circle 351 * 352 * @author <klaas at kosmokrator dot com> 353 * @link http://www.php.net/manual/en/function.imageantialias.php#61932 354 */ 355 function circ( &$img, $cx, $cy, $cr, $color) { 356 $ir = $cr; 357 $ix = 0; 358 $iy = $ir; 359 $ig = 2 * $ir - 3; 360 $idgr = -6; 361 $idgd = 4 * $ir - 10; 362 $fill = imageColorExactAlpha( $img, $color[0], $color[1], $color[2], 0); 363 imageLine( $img, $cx + $cr - 1, $cy, $cx, $cy, $fill ); 364 imageLine( $img, $cx - $cr + 1, $cy, $cx - 1, $cy, $fill ); 365 imageLine( $img, $cx, $cy + $cr - 1, $cx, $cy + 1, $fill ); 366 imageLine( $img, $cx, $cy - $cr + 1, $cx, $cy - 1, $fill ); 367 $draw = imageColorExactAlpha( $img, $color[0], $color[1], $color[2], 42); 368 imageSetPixel( $img, $cx + $cr, $cy, $draw ); 369 imageSetPixel( $img, $cx - $cr, $cy, $draw ); 370 imageSetPixel( $img, $cx, $cy + $cr, $draw ); 371 imageSetPixel( $img, $cx, $cy - $cr, $draw ); 372 while ( $ix <= $iy - 2 ) { 373 if ( $ig < 0 ) { 374 $ig += $idgd; 375 $idgd -= 8; 376 $iy--; 377 } else { 378 $ig += $idgr; 379 $idgd -= 4; 380 } 381 $idgr -= 4; 382 $ix++; 383 imageLine( $img, $cx + $ix, $cy + $iy - 1, $cx + $ix, $cy + $ix, $fill ); 384 imageLine( $img, $cx + $ix, $cy - $iy + 1, $cx + $ix, $cy - $ix, $fill ); 385 imageLine( $img, $cx - $ix, $cy + $iy - 1, $cx - $ix, $cy + $ix, $fill ); 386 imageLine( $img, $cx - $ix, $cy - $iy + 1, $cx - $ix, $cy - $ix, $fill ); 387 imageLine( $img, $cx + $iy - 1, $cy + $ix, $cx + $ix, $cy + $ix, $fill ); 388 imageLine( $img, $cx + $iy - 1, $cy - $ix, $cx + $ix, $cy - $ix, $fill ); 389 imageLine( $img, $cx - $iy + 1, $cy + $ix, $cx - $ix, $cy + $ix, $fill ); 390 imageLine( $img, $cx - $iy + 1, $cy - $ix, $cx - $ix, $cy - $ix, $fill ); 391 $filled = 0; 392 for ( $xx = $ix - 0.45; $xx < $ix + 0.5; $xx += 0.2 ) { 393 for ( $yy = $iy - 0.45; $yy < $iy + 0.5; $yy += 0.2 ) { 394 if ( sqrt( pow( $xx, 2 ) + pow( $yy, 2 ) ) < $cr ) $filled += 4; 395 } 396 } 397 $draw = imageColorExactAlpha( $img, $color[0], $color[1], $color[2], ( 100 - $filled ) ); 398 imageSetPixel( $img, $cx + $ix, $cy + $iy, $draw ); 399 imageSetPixel( $img, $cx + $ix, $cy - $iy, $draw ); 400 imageSetPixel( $img, $cx - $ix, $cy + $iy, $draw ); 401 imageSetPixel( $img, $cx - $ix, $cy - $iy, $draw ); 402 imageSetPixel( $img, $cx + $iy, $cy + $ix, $draw ); 403 imageSetPixel( $img, $cx + $iy, $cy - $ix, $draw ); 404 imageSetPixel( $img, $cx - $iy, $cy + $ix, $draw ); 405 imageSetPixel( $img, $cx - $iy, $cy - $ix, $draw ); 406 } 407 } 408 409 function draw_hoshi($im, $bx, $by) { 410 $coords=$this->get_coords($bx, $by); 411 $this->circ($im, $coords[0], $coords[1], $this->style['hoshi_radius'], $this->style['line_acolor']); 412 } 413 414 function draw_white($im, $bx, $by) { 415 $coords=$this->get_coords($bx, $by); 416 $this->circ($im, $coords[0], $coords[1], $this->style['stone_radius'], $this->style['white_rim_acolor']); 417 $this->circ($im, $coords[0], $coords[1], $this->style['stone_radius']-1, $this->style['white_acolor']); 418 } 419 420 function draw_black($im, $bx, $by) { 421 $coords=$this->get_coords($bx, $by); 422 $this->circ($im, $coords[0], $coords[1], $this->style['stone_radius'], $this->style['black_acolor']); 423 } 424 425 function draw_white_circle($im, $bx, $by) { 426 $coords=$this->get_coords($bx, $by); 427 $this->draw_white($im, $bx, $by); 428 $this->circ($im, $coords[0], $coords[1], $this->style['mark_radius'], $this->style['mark_acolor']); 429 $this->circ($im, $coords[0], $coords[1], $this->style['mark_radius']-2, $this->style['white_acolor']); 430 } 431 432 function draw_black_circle($im, $bx, $by) { 433 $coords=$this->get_coords($bx, $by); 434 $this->draw_black($im, $bx, $by); 435 $this->circ($im, $coords[0], $coords[1], $this->style['mark_radius'], $this->style['mark_acolor']); 436 $this->circ($im, $coords[0], $coords[1], $this->style['mark_radius']-2, $this->style['black_acolor']); 437 } 438 439 function draw_circle($im, $bx, $by) { 440 $coords=$this->get_coords($bx, $by); 441 list($x, $y) = $coords=$this->get_coords($bx, $by); 442 $r=$this->style['mark_radius']; 443 $sim = $this->style['circle_mark_img']['im']; 444 $dim = $this->style['circle_mark_img']['dim']; 445 imagecopymerge($im, $sim, $x-$r-1, $y-$r-1, 0, 0, $dim, $dim, 100); 446 } 447 448 function draw_square($im, $bx, $by) { 449 list($x, $y) = $coords=$this->get_coords($bx, $by); 450 $x1=$x-($this->style['mark_sqheight']/2); 451 $x2=$x+($this->style['mark_sqheight']/2); 452 $y1=$y-($this->style['mark_sqheight']/2); 453 $y2=$y+($this->style['mark_sqheight']/2); 454 imagefilledrectangle($im, $x1, $y1, $x2, $y2, $this->dgm['mark_color']); 455 } 456 function draw_link($im, $bx, $by) { 457 list($x, $y) = $coords=$this->get_coords($bx, $by); 458 $x1=$x-($this->style['link_sqheight']/2); 459 $x2=$x+($this->style['link_sqheight']/2); 460 $y1=$y-($this->style['link_sqheight']/2); 461 $y2=$y+($this->style['link_sqheight']/2); 462 imagefilledrectangle($im, $x1, $y1, $x2, $y2, $this->dgm['link_color']); 463 } 464 function draw_white_square($im, $bx, $by) { 465 $this->draw_white($im, $bx, $by); 466 $this->draw_square($im, $bx, $by); 467 } 468 function draw_black_square($im, $bx, $by) { 469 $this->draw_black($im, $bx, $by); 470 $this->draw_square($im, $bx, $by); 471 } 472 function draw_num($im, $bx, $by) { 473 $str=$this->dgm['dia'][$by][$bx]; 474 $str=($str=='0') ? '10' : $str; 475 $blacks_turn = intval($str) % 2 == ($this->dgm['black_first'] ? 1 : 0); 476 if($blacks_turn) 477 $this->draw_black($im, $bx, $by); 478 else 479 $this->draw_white($im, $bx, $by); 480 list($x, $y) = $coords=$this->get_coords($bx, $by); 481 $col=$blacks_turn ? $this->dgm['white_color'] : $this->dgm['black_color']; 482 $box = imagettfbbox($this->style['ttfont_sz'], 0, $this->style['ttfont'], $str); 483 $basex=$x - intval(abs($box[2]-$box[0])+1)/2 - $box[0]; 484 imagettftext($im, $this->style['ttfont_sz'], 0, $basex, $y+$this->style['majusc_voffs'], $col, $this->style['ttfont'], $str); 485 } 486 487 function draw_let($im, $bx, $by) { 488 list($x, $y) = $coords=$this->get_coords($bx, $by); 489 $str = $this->dgm['dia'][$by][$bx]; 490 $box = imagettfbbox($this->style['ttfont_sz'], 0, $this->style['ttfont'], $str); 491 $basex = $x - intval(abs($box[2]-$box[0])+1)/2 - $box[0]; 492 $r = $this->style['stone_radius']-3; 493 imagefilledrectangle($im, $x-$r, $y-$r, $x+$r, $y+$r, $this->dgm['goban_color']); 494 imagettftext($im, $this->style['ttfont_sz'], 0, $basex, $y+$this->style['minusc_voffs'], $this->dgm['string_color'], $this->style['ttfont'], $str); 495 } 496 497 function draw_coord($im, $bx, $by) { 498 list($x, $y) = $coords=$this->get_coords($bx, $by); 499 if($bx==-1) 500 $str = $this->dgm['board_size'] - $this->dgm['offset_y'] - $by; 501 else { 502 $bx2 = $this->dgm['offset_x'] + $bx; 503 $str=chr(65+$bx2+($bx2>7? 1 : 0)); 504 } 505 $box = imagettfbbox($this->style['ttfont_sz'], 0, $this->style['ttfont'], $str); 506 if($bx==-1) 507 $basex=$x - $this->style['stone_radius']+($str<10?$this->style['sm_offs']:0); 508 else { 509 $basex=$x - intval(abs($box[2]-$box[0])+1)/2 - $box[0]; 510 } 511 imagettftext($im, $this->style['ttfont_sz'], 0, $basex, $y+$this->style['majusc_voffs'], $this->dgm['string_color'], $this->style['ttfont'], $str); 512 } 513 514 function draw_wipe($im, $bx, $by) { 515 list($x, $y) = $coords=$this->get_coords($bx, $by); 516 imagefilledrectangle($im, $x-$this->style['line_sp']/2+1, $y-$this->style['line_sp']/2+1, $x+$this->style['line_sp']/2, $y+$this->style['line_sp']/2, $this->dgm['goban_color']); 517 } 518 519 /** 520 * draw and save diagram 521 */ 522 function save_diagram($dimh, $dimv, $filename_png) { 523 $im = imagecreatetruecolor($dimh, $dimv); 524 if(!$im) 525 return($this->error_box("Cannot initialize GD image stream.")); 526 527 //some things we only want to do once 528 if(!array_key_exists('sm_offs', $this->style)) { 529 // calc <10 number horiz offset for coords 530 $box = imagettfbbox($this->style['ttfont_sz'], 0, $this->style['ttfont'], '1'); 531 $box2 = imagettfbbox($this->style['ttfont_sz'], 0, $this->style['ttfont'], '11'); 532 $this->style['sm_offs'] = ($box2[2]-$box[1]) - ($box[2]-$box[0]) - 1; 533 // vertical offsets for majuscules and minuscules 534 $box = imagettfbbox($this->style['ttfont_sz'], 0, $this->style['ttfont'], 'a'); 535 $this->style['minusc_voffs'] = (-$box[5])/2+1; 536 $box = imagettfbbox($this->style['ttfont_sz'], 0, $this->style['ttfont'], 'A'); 537 $this->style['majusc_voffs'] = (-$box[5])/2; 538 } 539 if(!array_key_exists('circle_mark_img', $this->style)) { 540 $r=$this->style['mark_radius']; 541 $dim=$r*2+3; 542 $xim = imagecreatetruecolor($dim, $dim); 543 $gc = $this->style['goban_acolor']; 544 $transp = imagecolorallocate($xim, $gc[0], $gc[1], $gc[2]); 545 imagecolortransparent($xim, $transp); 546 imagefill($xim, 0, 0, $transp); 547 $this->circ($xim, $r+1, $r+1, $r, $this->style['mark_acolor']); 548 $this->circ($xim, $r+1, $r+1, $r-2, $this->style['goban_acolor']); 549 $this->style['circle_mark_img']['im'] = $xim; 550 $this->style['circle_mark_img']['dim'] = $dim; 551 } 552 553 $this->dgm['line_color'] = $this->acolor2color($im, $this->style['line_acolor']); 554 $this->dgm['mark_color'] = $this->acolor2color($im, $this->style['mark_acolor']); 555 $this->dgm['link_color'] = $this->acolor2color($im, $this->style['link_acolor_alpha'], true); 556 $this->dgm['goban_color'] = $this->acolor2color($im, $this->style['goban_acolor']); 557 $this->dgm['black_color'] = $this->acolor2color($im, $this->style['black_acolor']); 558 $this->dgm['white_color'] = $this->acolor2color($im, $this->style['white_acolor']); 559 $this->dgm['string_color'] = $this->acolor2color($im, $this->style['string_acolor']); 560 561 imagefill($im, 0, 0, $this->dgm['goban_color']); 562 563 // draw lines 564 $beginv=$this->dgm['edge_top'] ? $this->style['edge_sp'] : $this->style['line_begin']; 565 $beginh=$this->dgm['edge_left'] ? $this->style['edge_sp'] : $this->style['line_begin']; 566 $endv=$dimv - ($this->dgm['edge_bottom'] ? $this->style['edge_sp'] : $this->style['line_begin']) - 1; 567 $endh=$dimh - ($this->dgm['edge_right'] ? $this->style['edge_sp'] : $this->style['line_begin']) - 1; 568 if($this->dgm['coord_markers']) { 569 $beginv += $this->style['coord_sp']; 570 $beginh += $this->style['coord_sp']; 571 } 572 573 // draw horizontal lines 574 for($i=0; $i<$this->dgm['gridv']; $i+=1) { 575 $coords=$this->get_coords($i, $i); 576 imageline($im, $beginh, $coords[0], $endh, $coords[0], $this->dgm['line_color']); 577 } 578 // draw vertical lines 579 for($i=0; $i<$this->dgm['gridh']; $i+=1) { 580 $coords=$this->get_coords($i, $i); 581 imageline($im, $coords[0], $beginv, $coords[0], $endv, $this->dgm['line_color']); 582 } 583 584 // draw coordinates (if requested) 585 if($this->dgm['coord_markers']) { 586 $this->draw_coord($im, -1, 2); 587 for($i=0; $i<$this->dgm['gridv']; $i++) 588 $this->draw_coord($im, -1, $i); 589 for($i=0; $i<$this->dgm['gridh']; $i++) 590 $this->draw_coord($im, $i, -1); 591 } 592 593 // draw rest 594 foreach($this->dgm['dia'] as $by => $row) { 595 foreach($row as $bx => $symb) { 596 if($symb>='0' and $symb<='9') 597 $this->draw_num($im, $bx, $by); 598 else if($symb>='a' and $symb<='z') 599 $this->draw_let($im, $bx, $by); 600 else if($symb!='.') { 601 if(!array_key_exists($symb, $this->functab)) { 602 imagedestroy($im); 603 $oopshtml=htmlspecialchars("unknown symbol \"$symb\""); 604 return $this->error_box($oopshtml); 605 } 606 call_user_func(array($this, $this->functab[$symb]), $im, $bx, $by); 607 } 608 // draw link, if any 609 if(array_key_exists($symb, $this->dgm['imappings'])) { 610 $this->draw_link($im, $bx, $by); 611 } 612 613 } 614 } 615 616 // save 617 if(!imagepng($im, $filename_png)) { 618 imagedestroy($im); 619 $hfilename=htmlspecialchars($filename_png); 620 return $this->error_box("Cannot output diagram to file."); 621 } 622 imagedestroy($im); 623 return 0; 624 } // end of $this->save_diagram() 625 626 // other funcs 627 function get_coords($bx, $by) { 628 $additional_sp=0; 629 if($this->dgm['coord_markers']) 630 $additional_sp+=$this->style['coord_sp']; 631 return array($bx*$this->style['line_sp']+$this->style['edge_sp']+$additional_sp, 632 $by*$this->style['line_sp']+$this->style['edge_sp']+$additional_sp); 633 } 634 635 /** 636 * Calculate an area tag for a image map. Handles external and internal links 637 */ 638 function imap_area($bx, $by, $href) { 639 global $ID; 640 641 list($x, $y) = $coords=$this->get_coords($bx, $by); 642 $x1=$x-$this->style['line_sp']/2; 643 $y1=$y-$this->style['line_sp']/2; 644 $x2=$x+$this->style['line_sp']/2; 645 $y2=$y+$this->style['line_sp']/2; 646 // external or internal link? 647 if(strpos($href, '://')){ 648 $href = hsc($href); 649 $title = $href; 650 }else{ 651 $ns = getNS($ID); 652 resolve_pageid($ns,$href,$exists); 653 $title = $href; 654 $href = wl($href); 655 } 656 657 return "<area href=\"$href\" title=\"$title\" alt=\"$title\" coords=\"$x1,$y1,$x2,$y2\"/>"; 658 } 659 660 function acolor2color($im, $acolor, $alpha = false) { 661 if($alpha) 662 return imagecolorexactalpha($im, $acolor[0], $acolor[1], $acolor[2], $acolor[3]); 663 else 664 return imagecolorallocate($im, $acolor[0], $acolor[1], $acolor[2]); 665 } 666 667 /** 668 * Used for error reporting. Passes the string inside an array for streamlined 669 * handler/renderer error transfer 670 */ 671 function error_box($str) { 672 return array('error' => $str); 673 } 674 675 /** 676 * calculates board size and offsets for SGF and coordinate drawing 677 * returns an array (board_size, offset_x, offset_y) or false if there's a conflict 678 */ 679 function calc_offsets_and_size() { 680 $sizex = $this->dgm['gridh']; 681 $sizey = $this->dgm['gridv']; 682 $heightdefined = $this->dgm['edge_top'] && $this->dgm['edge_bottom']; 683 $widthdefined = $this->dgm['edge_left'] && $this->dgm['edge_right']; 684 $offset_x = 0; 685 $offset_y = 0; 686 if ($heightdefined) { 687 if ($widthdefined && $sizex != $sizey) 688 return false; 689 if ($sizex > $sizey) 690 return false; 691 $size = $sizey; 692 if ($this->dgm['edge_right']) $offset_x = $size-$sizex; 693 elseif (!$this->dgm['edge_left']) $offset_x = ($size-$sizex)/2; 694 } 695 elseif ($widthdefined) 696 { 697 if ($sizey > $sizex) 698 return false; 699 $size = $sizex; 700 if ($this->dgm['edge_bottom']) $offset_y = $size-$sizey; 701 elseif (!$this->dgm['edge_top']) $offset_y = ($size-$sizey)/2; 702 } 703 else 704 { 705 $size = max($sizex, $sizey, $this->dgm['board_size']); 706 707 if ($this->dgm['edge_right']) $offset_x = $size-$sizex; 708 elseif (!$this->dgm['edge_left']) $offset_x = ($size-$sizex)/2; 709 710 if ($this->dgm['edge_bottom']) $offset_y = $size-$sizey; 711 elseif (!$this->dgm['edge_top']) $offset_y = ($size-$sizey)/2; 712 } 713 return(array($size, intval($offset_x), intval($offset_y))); 714 } 715 716 /** 717 * Create an SGF file for the current diagram 718 */ 719 function SGF() { 720 $rows = $this->dgm['dia']; 721 $title = str_replace(']', '\]', $this->dgm['title']); 722 $comment = str_replace(']', '\]', $this->style['sgf_comment']); 723 $sizex = $this->dgm['gridh']; 724 $sizey = $this->dgm['gridv']; 725 $size = $this->dgm['board_size']; 726 $offset_x = $this->dgm['offset_x']; 727 $offset_y = $this->dgm['offset_y']; 728 // SGF Root node string 729 $firstcolor = $this->dgm['black_first'] ? 'B' : 'W'; 730 $SGFString = "(;GM[1]FF[4]SZ[$size]\n\n" . 731 "GN[$title]\n" . 732 "AP[GoDiag/DokuWiki]\n" . 733 "DT[".date("Y-m-d")."]\n" . 734 "PL[$firstcolor]\n" . 735 "C[$comment]\n"; 736 737 $AB = array(); 738 $AW = array(); 739 $CR = array(); 740 $SQ = array(); 741 $LB = array(); 742 743 if (!$this->dgm['black_first']) { 744 $oddplayer = 'W'; 745 $evenplayer = 'B'; 746 } else { 747 $oddplayer = 'B'; 748 $evenplayer = 'W'; 749 } 750 751 // output stones, numbers etc. for each row 752 for ($ypos=0; $ypos<$sizey; $ypos++) { 753 for ($xpos=0; $xpos<$sizex; $xpos++) { 754 if(array_key_exists($ypos, $rows) && array_key_exists($xpos, $rows[$ypos])) 755 $curchar = $rows[$ypos][$xpos]; 756 else 757 continue; 758 $position = chr(97+$xpos+$offset_x) . 759 chr(97+$ypos+$offset_y); 760 761 if ($curchar == 'X' || $curchar == 'B' || $curchar == '#') 762 $AB[] = $position; // add black stone 763 764 if ($curchar == 'O' || $curchar == 'W' || $curchar == '@') 765 $AW[] = $position; // add white stone 766 767 if ($curchar == 'B' || $curchar == 'W' || $curchar == 'C') 768 $CR[] = $position; // add circle markup 769 770 if ($curchar == '#' || $curchar == '@' || $curchar == 'S') 771 $SQ[] = $position; // add circle markup 772 773 // other markup 774 if ($curchar % 2 == 1) // odd numbers (moves) 775 { 776 $Moves[$curchar][1] = $position; 777 $Moves[$curchar][2] = $oddplayer; 778 } 779 elseif ($curchar*2 > 0 || $curchar == '0') // even num (moves) 780 { 781 if ($curchar == '0') 782 $curchar = '10'; 783 $Moves[$curchar][1] = $position; 784 $Moves[$curchar][2] = $evenplayer; 785 } 786 elseif (($curchar >= 'a') && ($curchar <= 'z')) // letter markup 787 $LB[] = "$position:$curchar"; 788 } // for xpos loop 789 }// for ypos loop 790 791 // parse title for hint of more moves 792 if ($cnt = preg_match_all($this->hintre, $title, $match)) { 793 for ($i=0; $i < $cnt; $i++) 794 { 795 if (!isset($Moves[$match[1][$i]]) // only if not set on board 796 && isset($Moves[$match[2][$i]])) // referred move must be set 797 { 798 $mvnum = $match[1][$i]; 799 $Moves[$mvnum][1] = $Moves[$match[2][$i]][1]; 800 $Moves[$mvnum][2] = $mvnum % 2 ? $oddplayer : $evenplayer; 801 } 802 } 803 } 804 805 // build SGF string 806 if (count($AB)) $SGFString .= 'AB[' . join('][', $AB) . "]\n"; 807 if (count($AW)) $SGFString .= 'AW[' . join('][', $AW) . "]\n"; 808 $Markup = ''; 809 if (count($CR)) $Markup = 'CR[' . join('][', $CR) . "]\n"; 810 if (count($SQ)) $Markup .= 'SQ[' . join('][', $SQ) . "]\n"; 811 if (count($LB)) $Markup .= 'LB[' . join('][', $LB) . "]\n"; 812 $SGFString .= "$Markup\n"; 813 814 for ($mv=1; $mv <= 10; $mv++) 815 { 816 if (isset($Moves[$mv])) { 817 $SGFString .= ';' . $Moves[$mv][2] . '[' . $Moves[$mv][1] . ']'; 818 $SGFString .= 'C['. $Moves[$mv][2] . $mv . "]\n"; 819 $SGFString .= $Markup; 820 } 821 } 822 823 $SGFString .= ")\n"; 824 825 return $SGFString; 826 } // end of $this->SGF() 827 828 829} // end of class 830 831