1<?php 2 3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 4 5/** 6 * Class for handling output in SVG format. 7 * 8 * LICENSE: This library is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License as published by 10 * the Free Software Foundation; either version 2.1 of the License, or (at your 11 * option) any later version. This library is distributed in the hope that it 12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 14 * General Public License for more details. You should have received a copy of 15 * the GNU Lesser General Public License along with this library; if not, write 16 * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 17 * 02111-1307 USA 18 * 19 * @category Images 20 * @package Image_Canvas 21 * @author Jesper Veggerby <pear.nosey@veggerby.dk> 22 * @copyright Copyright (C) 2003, 2004 Jesper Veggerby Hansen 23 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 24 * @version CVS: $Id: SVG.php 287471 2009-08-18 23:12:01Z clockwerx $ 25 * @link http://pear.php.net/package/Image_Canvas 26 */ 27 28/** 29 * Include file Image/Canvas.php 30 */ 31require_once 'Image/Canvas.php'; 32 33/** 34 * Include file Image/Canvas/Color.php 35 */ 36require_once 'Image/Canvas/Color.php'; 37 38/** 39 * SVG Canvas class. 40 * 41 * @category Images 42 * @package Image_Canvas 43 * @author Jesper Veggerby <pear.nosey@veggerby.dk> 44 * @copyright Copyright (C) 2003, 2004 Jesper Veggerby Hansen 45 * @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1 46 * @version Release: @package_version@ 47 * @link http://pear.php.net/package/Image_Canvas 48 */ 49class Image_Canvas_SVG extends Image_Canvas 50{ 51 52 /** 53 * The SVG elements 54 * @var string 55 * @access private 56 */ 57 var $_elements = ''; 58 59 /** 60 * The SVG defines 61 * @var string 62 * @access private 63 */ 64 var $_defs = ''; 65 66 /** 67 * The current indention level 68 * @var string 69 * @access private 70 */ 71 var $_indent = ' '; 72 73 /** 74 * A unieuq id counter 75 * @var int 76 * @access private 77 */ 78 var $_id = 1; 79 80 /** 81 * The current group ids 82 * @var array 83 * @access private 84 */ 85 var $_groupIDs = array(); 86 87 /** 88 * The XML encoding (default iso-8859-1) 89 * @var string 90 * @access private 91 */ 92 var $_encoding = 'iso-8859-1'; 93 94 /** 95 * Create the SVG canvas. 96 * 97 * Parameters available: 98 * 99 * 'width' The width of the graph 100 * 101 * 'height' The height of the graph 102 * 103 * 'encoding' The encoding of the SVG document 104 * 105 * @param array $param Parameter array 106 */ 107 function Image_Canvas_SVG($params) 108 { 109 parent::Image_Canvas($params); 110 $this->_reset(); 111 112 if (isset($params['encoding'])) { 113 $this->_encoding = $params['encoding']; 114 } 115 } 116 117 /** 118 * Add a SVG "element" to the output 119 * 120 * @param string $element The element 121 * @access private 122 */ 123 function _addElement($element, $params = array()) { 124 $elementdata = $this->_indent . $element . "\n"; 125 126 if (isset($params['url'])) { 127 $url = $params['url']; 128 $target = (isset($params['target']) ? $params['target'] : false); 129 $alt = (isset($params['alt']) ? $params['alt'] : false); 130 131 $tags = ''; 132 if (isset($params['htmltags'])) { 133 foreach ($params['htmltags'] as $key => $value) { 134 $tags .= ' '; 135 if (strpos($value, '"') >= 0) { 136 $tags .= $key . '=\'' . $value . '\''; 137 } else { 138 $tags .= $key . '="' . $value . '"'; 139 } 140 } 141 } 142 143 $elementdata = 144 $this->_indent . '<a xlink:href="' . $url . '"' . ($target ? ' target="' . $target . '"' : '') . '>' . "\n" . 145 ' ' . $elementdata . 146 $this->_indent . '</a>' . "\n"; 147 } 148 149 150 $this->_elements .= $elementdata; 151 } 152 153 /** 154 * Add a SVG "define" to the output 155 * 156 * @param string $def The define 157 * @access private 158 */ 159 function _addDefine($def) { 160 $this->_defs .= ' ' . $def . "\n"; 161 } 162 163 /** 164 * Get the color index for the RGB color 165 * 166 * @param int $color The color 167 * @return int A SVG compatible color 168 * @access private 169 */ 170 function _color($color = false) 171 { 172 if ($color === false) { 173 return 'transparent'; 174 } else { 175 $color = Image_Canvas_Color::color2RGB($color); 176 return 'rgb(' . $color[0] . ',' . $color[1] . ',' . $color[2] . ')'; 177 } 178 } 179 180 /** 181 * Get the opacity for the RGB color 182 * 183 * @param int $color The color 184 * @return int A SVG compatible opacity value 185 * @access private 186 */ 187 function _opacity($color = false) 188 { 189 if ($color === false) { 190 return false; 191 } else { 192 $color = Image_Canvas_Color::color2RGB($color); 193 if ($color[3] != 255) { 194 return sprintf('%0.1f', $color[3]/255); 195 } else { 196 return false; 197 } 198 } 199 } 200 201 /** 202 * Get the SVG applicable linestyle 203 * 204 * @param mixed $lineStyle The line style to return, false if the one 205 * explicitly set 206 * @return mixed A SVG compatible linestyle 207 * @access private 208 */ 209 function _getLineStyle($lineStyle = false) 210 { 211 $result = ''; 212 if ($lineStyle === false) { 213 $lineStyle = $this->_lineStyle; 214 } 215 216 // TODO Linestyles (i.e. fx. dotted) does not work 217 218 if (($lineStyle != 'transparent') && ($lineStyle !== false)) { 219 $result = 'stroke-width:' . $this->_thickness . ';'; 220 $result .= 'stroke:' .$this->_color($lineStyle) . ';'; 221 if ($opacity = $this->_opacity($lineStyle)) { 222 $result .= 'stroke-opacity:' . $opacity . ';'; 223 } 224 } 225 return $result; 226 } 227 228 /** 229 * Get the SVG applicable fillstyle 230 * 231 * @param mixed $fillStyle The fillstyle to return, false if the one 232 * explicitly set 233 * @return mixed A SVG compatible fillstyle 234 * @access private 235 */ 236 function _getFillStyle($fillStyle = false) 237 { 238 $result = ''; 239 if ($fillStyle === false) { 240 $fillStyle = $this->_fillStyle; 241 } 242 243 if (is_array($fillStyle)) { 244 if ($fillStyle['type'] == 'gradient') { 245 $id = 'gradient_' . ($this->_id++); 246 $startColor = $this->_color($fillStyle['start']); 247 $endColor = $this->_color($fillStyle['end']); 248 $startOpacity = $this->_opacity($fillStyle['start']); 249 $endOpacity = $this->_opacity($fillStyle['end']); 250 251 switch ($fillStyle['direction']) { 252 case 'horizontal': 253 case 'horizontal_mirror': 254 $x1 = '0%'; 255 $y1 = '0%'; 256 $x2 = '100%'; 257 $y2 = '0%'; 258 break; 259 260 case 'vertical': 261 case 'vertical_mirror': 262 $x1 = '0%'; 263 $y1 = '100%'; 264 $x2 = '0%'; 265 $y2 = '0%'; 266 break; 267 268 case 'diagonal_tl_br': 269 $x1 = '0%'; 270 $y1 = '0%'; 271 $x2 = '100%'; 272 $y2 = '100%'; 273 break; 274 275 case 'diagonal_bl_tr': 276 $x1 = '0%'; 277 $y1 = '100%'; 278 $x2 = '100%'; 279 $y2 = '0%'; 280 break; 281 282 case 'radial': 283 $cx = '50%'; 284 $cy = '50%'; 285 $r = '100%'; 286 $fx = '50%'; 287 $fy = '50%'; 288 break; 289 290 } 291 292 if ($fillStyle['direction'] == 'radial') { 293 $this->_addDefine( 294 '<radialGradient id="' . $id . '" cx="' . 295 $cx .'" cy="' . $cy .'" r="' . $r .'" fx="' . 296 $fx .'" fy="' . $fy .'">' 297 ); 298 $this->_addDefine( 299 ' <stop offset="0%" style="stop-color:' . 300 $startColor. ';' . ($startOpacity ? 'stop-opacity:' . 301 $startOpacity . ';' : ''). '"/>' 302 ); 303 $this->_addDefine( 304 ' <stop offset="100%" style="stop-color:' . 305 $endColor. ';' . ($endOpacity ? 'stop-opacity:' . 306 $endOpacity . ';' : ''). '"/>' 307 ); 308 $this->_addDefine( 309 '</radialGradient>' 310 ); 311 } elseif (($fillStyle['direction'] == 'vertical_mirror') || 312 ($fillStyle['direction'] == 'horizontal_mirror')) 313 { 314 $this->_addDefine( 315 '<linearGradient id="' . $id . '" x1="' . 316 $x1 .'" y1="' . $y1 .'" x2="' . $x2 .'" y2="' . 317 $y2 .'">' 318 ); 319 $this->_addDefine( 320 ' <stop offset="0%" style="stop-color:' . 321 $startColor. ';' . ($startOpacity ? 'stop-opacity:' . 322 $startOpacity . ';' : ''). '"/>' 323 ); 324 $this->_addDefine( 325 ' <stop offset="50%" style="stop-color:' . 326 $endColor. ';' . ($endOpacity ? 'stop-opacity:' . 327 $endOpacity . ';' : ''). '"/>' 328 ); 329 $this->_addDefine( 330 ' <stop offset="100%" style="stop-color:' . 331 $startColor. ';' . ($startOpacity ? 'stop-opacity:' . 332 $startOpacity . ';' : ''). '"/>' 333 ); 334 $this->_addDefine( 335 '</linearGradient>' 336 ); 337 } else { 338 $this->_addDefine( 339 '<linearGradient id="' . $id . '" x1="' . 340 $x1 .'" y1="' . $y1 .'" x2="' . $x2 .'" y2="' . 341 $y2 .'">' 342 ); 343 $this->_addDefine( 344 ' <stop offset="0%" style="stop-color:' . 345 $startColor. ';' . ($startOpacity ? 'stop-opacity:' . 346 $startOpacity . ';' : ''). '"/>' 347 ); 348 $this->_addDefine( 349 ' <stop offset="100%" style="stop-color:' . 350 $endColor. ';' . ($endOpacity ? 'stop-opacity:' . 351 $endOpacity . ';' : ''). '"/>' 352 ); 353 $this->_addDefine( 354 '</linearGradient>' 355 ); 356 } 357 358 return 'fill:url(#' . $id . ');'; 359 } 360 } elseif (($fillStyle != 'transparent') && ($fillStyle !== false)) { 361 $result = 'fill:' . $this->_color($fillStyle) . ';'; 362 if ($opacity = $this->_opacity($fillStyle)) { 363 $result .= 'fill-opacity:' . $opacity . ';'; 364 } 365 return $result; 366 } else { 367 return 'fill:none;'; 368 } 369 } 370 371 /** 372 * Sets an image that should be used for filling 373 * 374 * @param string $filename The filename of the image to fill with 375 */ 376 function setFillImage($filename) 377 { 378 } 379 380 /** 381 * Sets a gradient fill 382 * 383 * @param array $gradient Gradient fill options 384 */ 385 function setGradientFill($gradient) 386 { 387 $this->_fillStyle = $gradient; 388 $this->_fillStyle['type'] = 'gradient'; 389 } 390 391 /** 392 * Sets the font options. 393 * 394 * The $font array may have the following entries: 395 * 'type' = 'ttf' (TrueType) or omitted for default<br> 396 * If 'type' = 'ttf' then the following can be specified<br> 397 * 'size' = size in pixels<br> 398 * 'angle' = the angle with which to write the text 399 * 'file' = the .ttf file (either the basename, filename or full path) 400 * 401 * @param array $font The font options. 402 */ 403 function setFont($fontOptions) 404 { 405 parent::setFont($fontOptions); 406 if (!isset($this->_font['size'])) { 407 $this->_font['size'] = 10; 408 } 409 } 410 411 /** 412 * Parameter array: 413 * 'x0': int X start point 414 * 'y0': int Y start point 415 * 'x1': int X end point 416 * 'y1': int Y end point 417 * 'color': mixed [optional] The line color 418 * @param array $params Parameter array 419 */ 420 function line($params) 421 { 422 $x0 = $this->_getX($params['x0']); 423 $y0 = $this->_getY($params['y0']); 424 $x1 = $this->_getX($params['x1']); 425 $y1 = $this->_getY($params['y1']); 426 $color = (isset($params['color']) ? $params['color'] : false); 427 428 $attrs = (isset($params['attrs']) && is_array($params['attrs'])) ? $this->_getAttributes($params['attrs']) : null; 429 430 $style = $this->_getLineStyle($color) . $this->_getFillStyle('transparent'); 431 if ($style != '') { 432 $this->_addElement( 433 '<line ' . 434 'x1="' . round($x0) . '" ' . 435 'y1="' . round($y0) . '" ' . 436 'x2="' . round($x1) . '" ' . 437 'y2="' . round($y1) . '" ' . 438 'style="' . $style . '"' . 439 ($attrs ? ' ' . $attrs : '') . 440 '/>', 441 $params 442 ); 443 } 444 parent::line($params); 445 } 446 447 /** 448 * Parameter array: 449 * 'connect': bool [optional] Specifies whether the start point should be 450 * connected to the endpoint (closed polygon) or not (connected line) 451 * 'fill': mixed [optional] The fill color 452 * 'line': mixed [optional] The line color 453 * @param array $params Parameter array 454 */ 455 function polygon($params = array()) 456 { 457 $connectEnds = (isset($params['connect']) ? $params['connect'] : false); 458 $fillColor = (isset($params['fill']) ? $params['fill'] : false); 459 $lineColor = (isset($params['line']) ? $params['line'] : false); 460 461 if (!$connectEnds) { 462 $fillColor = 'transparent'; 463 } 464 $style = $this->_getLineStyle($lineColor) . $this->_getFillStyle($fillColor); 465 466 $attrs = (isset($params['attrs']) && is_array($params['attrs'])) ? $this->_getAttributes($params['attrs']) : null; 467 468 $first = true; 469 $spline = false; 470 $lastpoint = false; 471 foreach($this->_polygon as $point) { 472 if ($first) { 473 $points = 'M'; 474 } elseif (!$spline) { 475 $points .= ' L'; 476 } 477 478 if (($spline) && ($lastpoint !== false)) { 479 $points .= ' ' .round($lastpoint['P1X']) . ',' . round($lastpoint['P1Y']) . ' ' . 480 round($lastpoint['P2X']) . ',' . round($lastpoint['P2Y']); 481 } 482 483 $points .= ' ' . round($point['X']) . ',' . round($point['Y']); 484 485 if ((isset($point['P1X'])) && (isset($point['P1Y'])) && 486 (isset($point['P2X'])) && (isset($point['P2Y']))) 487 { 488 if (($first) || (!$spline)) { 489 $points .= ' C'; 490 } 491 $lastpoint = $point; 492 $spline = true; 493 } else { 494 $spline = false; 495 } 496 $first = false; 497 } 498 if ($connectEnds) { 499 $points .= ' Z'; 500 } 501 $this->_addElement( 502 '<path ' . 503 'd="' . $points . '" ' . 504 'style="' . $style . '"' . 505 ($attrs ? ' ' . $attrs : '') . 506 '/>', 507 $params 508 ); 509 510 parent::polygon($params); 511 } 512 513 /** 514 * Draw a rectangle 515 * 516 * Parameter array: 517 * 'x0': int X start point 518 * 'y0': int Y start point 519 * 'x1': int X end point 520 * 'y1': int Y end point 521 * 'fill': mixed [optional] The fill color 522 * 'line': mixed [optional] The line color 523 * @param array $params Parameter array 524 */ 525 function rectangle($params) 526 { 527 $x0 = min($this->_getX($params['x0']), $this->_getX($params['x1'])); 528 $y0 = min($this->_getY($params['y0']), $this->_getY($params['y1'])); 529 $x1 = max($this->_getX($params['x0']), $this->_getX($params['x1'])); 530 $y1 = max($this->_getY($params['y0']), $this->_getY($params['y1'])); 531 $fillColor = (isset($params['fill']) ? $params['fill'] : false); 532 $lineColor = (isset($params['line']) ? $params['line'] : false); 533 534 $attrs = (isset($params['attrs']) && is_array($params['attrs'])) ? $this->_getAttributes($params['attrs']) : null; 535 536 $style = $this->_getLineStyle($lineColor) . $this->_getFillStyle($fillColor); 537 if ($style != '') { 538 $this->_addElement( 539 '<rect ' . 540 'x="' . round($x0) . '" ' . 541 'y="' . round($y0) . '" ' . 542 'width="' . round(abs($x1 - $x0)) . '" ' . 543 'height="' . round(abs($y1 - $y0)) . '" ' . 544 'style="' . $style . '"' . 545 ($attrs ? ' ' . $attrs : '') . 546 '/>', 547 $params 548 ); 549 } 550 parent::rectangle($params); 551 } 552 553 /** 554 * Draw an ellipse 555 * 556 * Parameter array: 557 * 'x': int X center point 558 * 'y': int Y center point 559 * 'rx': int X radius 560 * 'ry': int Y radius 561 * 'fill': mixed [optional] The fill color 562 * 'line': mixed [optional] The line color 563 * @param array $params Parameter array 564 */ 565 function ellipse($params) 566 { 567 $x = $this->_getX($params['x']); 568 $y = $this->_getY($params['y']); 569 $rx = $this->_getX($params['rx']); 570 $ry = $this->_getY($params['ry']); 571 $fillColor = (isset($params['fill']) ? $params['fill'] : false); 572 $lineColor = (isset($params['line']) ? $params['line'] : false); 573 574 $attrs = (isset($params['attrs']) && is_array($params['attrs'])) ? $this->_getAttributes($params['attrs']) : null; 575 576 $style = $this->_getLineStyle($lineColor) . $this->_getFillStyle($fillColor); 577 if ($style != '') { 578 $this->_addElement( 579 '<ellipse ' . 580 'cx="' . round($x) . '" ' . 581 'cy="' . round($y) . '" ' . 582 'rx="' . round($rx) . '" ' . 583 'ry="' . round($ry) . '" ' . 584 'style="' . $style . '"' . 585 ($attrs ? ' ' . $attrs : '') . 586 '/>', 587 $params 588 ); 589 } 590 parent::ellipse($params); 591 } 592 593 /** 594 * Draw a pie slice 595 * 596 * Parameter array: 597 * 'x': int X center point 598 * 'y': int Y center point 599 * 'rx': int X radius 600 * 'ry': int Y radius 601 * 'v1': int The starting angle (in degrees) 602 * 'v2': int The end angle (in degrees) 603 * 'srx': int [optional] Starting X-radius of the pie slice (i.e. for a doughnut) 604 * 'sry': int [optional] Starting Y-radius of the pie slice (i.e. for a doughnut) 605 * 'fill': mixed [optional] The fill color 606 * 'line': mixed [optional] The line color 607 * @param array $params Parameter array 608 */ 609 function pieslice($params) 610 { 611 $x = $this->_getX($params['x']); 612 $y = $this->_getY($params['y']); 613 $rx = $this->_getX($params['rx']); 614 $ry = $this->_getY($params['ry']); 615 $v1 = $this->_getX($params['v1']); 616 $v2 = $this->_getY($params['v2']); 617 $srx = (isset($params['srx']) ? $this->_getX($params['srx']) : false); 618 $sry = (isset($params['sry']) ? $this->_getX($params['sry']) : false); 619 $fillColor = (isset($params['fill']) ? $params['fill'] : false); 620 $lineColor = (isset($params['line']) ? $params['line'] : false); 621 622 $attrs = (isset($params['attrs']) && is_array($params['attrs'])) ? $this->_getAttributes($params['attrs']) : null; 623 624 $dv = max($v2, $v1) - min($v2, $v1); 625 if ($dv >= 360) { 626 $this->ellipse($params); 627 } 628 else { 629 $style = $this->_getLineStyle($lineColor) . $this->_getFillStyle($fillColor); 630 if ($style != '') { 631 $x1 = ($x + $rx * cos(deg2rad(min($v1, $v2) % 360))); 632 $y1 = ($y + $ry * sin(deg2rad(min($v1, $v2) % 360))); 633 $x2 = ($x + $rx * cos(deg2rad(max($v1, $v2) % 360))); 634 $y2 = ($y + $ry * sin(deg2rad(max($v1, $v2) % 360))); 635 $this->_addElement( 636 '<path d="' . 637 'M' . round($x) . ',' . round($y) . ' ' . 638 'L' . round($x1) . ',' . round($y1) . ' ' . 639 'A' . round($rx) . ',' . round($ry) . ($dv > 180 ? ' 0 1,1 ' : ' 0 0,1 ') . 640 round($x2) . ',' . round($y2) . ' ' . 641 'Z" ' . 642 'style="' . $style . '"' . 643 ($attrs ? ' ' . $attrs : '') . 644 '/>', 645 $params 646 ); 647 } 648 649 parent::pieslice($params); 650 } 651 } 652 653 /** 654 * Get the width of a text, 655 * 656 * @param string $text The text to get the width of 657 * @return int The width of the text 658 */ 659 function textWidth($text) 660 { 661 if ((isset($this->_font['vertical'])) && ($this->_font['vertical'])) { 662 return $this->_font['size']; 663 } else { 664 return round($this->_font['size'] * 0.7 * strlen($text)); 665 } 666 } 667 668 /** 669 * Get the height of a text, 670 * 671 * @param string $text The text to get the height of 672 * @return int The height of the text 673 */ 674 function textHeight($text) 675 { 676 if ((isset($this->_font['vertical'])) && ($this->_font['vertical'])) { 677 return round($this->_font['size'] * 0.7 * strlen($text)); 678 } else { 679 return $this->_font['size']; 680 } 681 } 682 683 /** 684 * Writes text 685 * 686 * Parameter array: 687 * 'x': int X-point of text 688 * 'y': int Y-point of text 689 * 'text': string The text to add 690 * 'alignment': array [optional] Alignment 691 * 'color': mixed [optional] The color of the text 692 */ 693 function addText($params) 694 { 695 $x = $this->_getX($params['x']); 696 $y = $this->_getY($params['y']); 697 $text = $params['text']; 698 $color = (isset($params['color']) ? $params['color'] : false); 699 $alignment = (isset($params['alignment']) ? $params['alignment'] : false); 700 701 $attrs = (isset($params['attrs']) && is_array($params['attrs'])) ? $this->_getAttributes($params['attrs']) : null; 702 703 $textHeight = $this->textHeight($text); 704 705 if (!is_array($alignment)) { 706 $alignment = array('vertical' => 'top', 'horizontal' => 'left'); 707 } 708 709 if (!isset($alignment['vertical'])) { 710 $alignment['vertical'] = 'top'; 711 } 712 713 if (!isset($alignment['horizontal'])) { 714 $alignment['horizontal'] = 'left'; 715 } 716 717 $align = ''; 718 719 if ((isset($this->_font['vertical'])) && ($this->_font['vertical'])) { 720// $align .= 'writing-mode: tb-rl;'; 721 722 if ($alignment['vertical'] == 'bottom') { 723 $align .= 'text-anchor:end;'; 724 //$y = $y + $textHeight; 725 } elseif ($alignment['vertical'] == 'center') { 726 //$y = $y + ($textHeight / 2); 727 $align .= 'text-anchor:middle;'; 728 } 729 } else { 730 if ($alignment['horizontal'] == 'right') { 731 $align .= 'text-anchor:end;'; 732 } elseif ($alignment['horizontal'] == 'center') { 733 $align .= 'text-anchor:middle;'; 734 } 735 736 if ($alignment['vertical'] == 'top') { 737 $y = $y + $textHeight; 738 } elseif ($alignment['vertical'] == 'center') { 739 $y = $y + ($textHeight / 2); 740 } 741 } 742 743 if (($color === false) && (isset($this->_font['color']))) { 744 $color = $this->_font['color']; 745 } 746 747 $textColor = $this->_color($color); 748 $textOpacity = $this->_opacity($color); 749 750 $this->_addElement( 751 '<g transform="translate(' . round($x) . ', ' . round($y) . ')">' . "\n" . 752 $this->_indent . ' <text ' . 753 'x="0" ' . 754 'y="0" ' . 755 (isset($this->_font['angle']) && ($this->_font['angle'] > 0) ? 756 'transform="rotate(' . (($this->_font['angle'] + 180) % 360) . ')" ' : 757 '' 758 ) . 759 'style="' . 760 (isset($this->_font['name']) ? 761 'font-family:' . $this->_font['name'] . ';' : '') . 762 'font-size:' . $this->_font['size'] . 'px;fill:' . 763 $textColor . ($textOpacity ? ';fill-opacity:' . 764 $textOpacity : 765 '' 766 ) . ';' . $align . '"' . 767 ($attrs ? ' ' . $attrs : '') . 768 '>' . 769 htmlspecialchars($text) . 770 '</text>' . "\n" . 771 $this->_indent . '</g>', 772 $params 773 ); 774 parent::addText($params); 775 } 776 777 /** 778 * Overlay image 779 * 780 * Parameter array: 781 * 'x': int X-point of overlayed image 782 * 'y': int Y-point of overlayed image 783 * 'filename': string The filename of the image to overlay 784 * 'width': int [optional] The width of the overlayed image (resizing if possible) 785 * 'height': int [optional] The height of the overlayed image (resizing if possible) 786 * 'alignment': array [optional] Alignment 787 */ 788 function image($params) 789 { 790 $x = $this->_getX($params['x']); 791 $y = $this->_getY($params['y']); 792 $filename = $params['filename']; 793 794 $attrs = (isset($params['attrs']) && is_array($params['attrs'])) ? $this->_getAttributes($params['attrs']) : null; 795 796 list($width, $height, $type, $attr) = getimagesize($filename); 797 $width = (isset($params['width']) ? $params['width'] : $width); 798 $height = (isset($params['height']) ? $params['height'] : $height); 799 $alignment = (isset($params['alignment']) ? $params['alignment'] : false); 800 801 $file = fopen($filename, 'rb'); 802 $filedata = fread($file, filesize($filename)); 803 fclose($file); 804 805 $data = 'data:' . image_type_to_mime_type($type) . ';base64,' . base64_encode($filedata); 806 807 $this->_addElement( 808 '<image xlink:href="' . $data . '" x="' . $x . '" y="' . $y . '"' . 809 ($width ? ' width="' . $width . '"' : '') . 810 ($height ? ' height="' . $height . '"' : '') . 811 ($attrs ? ' ' . $attrs : '') . 812 ' preserveAspectRatio="none"/>', 813 $params 814 ); 815 parent::image($params); 816 } 817 818 /** 819 * Start a group. 820 * 821 * What this does, depends on the canvas/format. 822 * 823 * @param string $name The name of the group 824 */ 825 function startGroup($name = false) 826 { 827 $name = strtolower(str_replace(' ', '_', $name)); 828 if (in_array($name, $this->_groupIDs)) { 829 $name .= $this->_id; 830 $this->_id++; 831 } 832 $this->_groupIDs[] = $name; 833 $this->_addElement('<g id="' . htmlspecialchars($name) . '">'); 834 $this->_indent .= ' '; 835 } 836 837 /** 838 * End the "current" group. 839 * 840 * What this does, depends on the canvas/format. 841 */ 842 function endGroup() 843 { 844 $this->_indent = substr($this->_indent, 0, -4); 845 $this->_addElement('</g>'); 846 } 847 848 /** 849 * Output the result of the canvas 850 * 851 * @param array $param Parameter array 852 */ 853 function show($param = false) 854 { 855 parent::show($param); 856 857 $attrs = (isset($param['attrs']) && is_array($param['attrs'])) ? $this->_getAttributes($param['attrs']) : null; 858 859 $output = $this->getData($param); 860 861 header('Content-Type: image/svg+xml'); 862 header('Content-Disposition: inline; filename = "' . basename($_SERVER['PHP_SELF'], '.php') . '.svg"'); 863 print $output; 864 } 865 866 /** 867 * Output the result of the canvas 868 * 869 * @param array $param Parameter array 870 */ 871 function save($param = false) 872 { 873 parent::save($param); 874 875 $output = $this->getData($param); 876 877 $file = fopen($param['filename'], 'w+'); 878 fwrite($file, $output); 879 fclose($file); 880 } 881 882 883 /** 884 * Get SVG data 885 * 886 * @param array $param Parameter array 887 * 888 * @return string 889 */ 890 function getData($param = false) 891 { 892 $attrs = (isset($param['attrs']) && is_array($param['attrs'])) ? $this->_getAttributes($param['attrs']) : null; 893 894 return '<?xml version="1.0" encoding="'. $this->_encoding . '"?>' . "\n" . 895 '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"' . "\n\t" . 896 ' "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">' . "\n" . 897 '<svg width="' . $this->_width . '" height="' . $this->_height . 898 '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"' . 899 ($attrs ? ' ' . $attrs : '') . 900 '>' . "\n" . 901 ($this->_defs ? 902 ' <defs>' . "\n" . 903 $this->_defs . 904 ' </defs>' . "\n" : 905 '' 906 ) . 907 $this->_elements . 908 '</svg>'; 909 } 910 911 /** 912 * Set clipping to occur 913 * 914 * Parameter array: 915 * 916 * 'x0': int X point of Upper-left corner 917 * 'y0': int X point of Upper-left corner 918 * 'x1': int X point of lower-right corner 919 * 'y1': int Y point of lower-right corner 920 */ 921 function setClipping($params = false) 922 { 923 if ($params === false) { 924 $this->_addElement('</g>'); 925 } 926 else { 927 $group = "clipping_" . $this->_id; 928 $this->_id++; 929 $this->_addElement('<g clip-path="url(#' . $group . ')">'); 930 931 $this->_addDefine('<clipPath id="' . $group . '">'); 932 $this->_addDefine(' <path d="' . 933 'M' . $this->_getX($params['x0']) . ' ' . $this->_getY($params['y0']) . 934 ' H' . $this->_getX($params['x1']) . 935 ' V' . $this->_getY($params['y1']) . 936 ' H' . $this->_getX($params['x0']) . 937 ' Z"/>'); 938 $this->_addDefine('</clipPath>'); 939 } 940 } 941 942 /** 943 * Get a canvas specific HTML tag. 944 * 945 * This method implicitly saves the canvas to the filename in the 946 * filesystem path specified and parses it as URL specified by URL path 947 * 948 * Parameter array: 949 * 'filename': string 950 * 'filepath': string Path to the file on the file system. Remember the final slash 951 * 'urlpath': string Path to the file available through an URL. Remember the final slash 952 * 'width': int The width in pixels 953 * 'height': int The height in pixels 954 */ 955 function toHtml($params) 956 { 957 parent::toHtml($params); 958 return '<embed src="' . $params['urlpath'] . $params['filename'] . '" width=' . $params['width'] . ' height=' . $params['height'] . ' type="image/svg+xml">'; 959 } 960 961 /** 962 * Converts array of attributes to string 963 * 964 * @param array $attrs Attributes array 965 * @return array 966 */ 967 function _getAttributes($attrs) 968 { 969 $string = ''; 970 971 foreach ($attrs as $key => $value) { 972 $string .= ' ' . $key . '="' . $value . '"'; 973 } 974 975 return $string; 976 } 977} 978 979?> 980