| [ Index ] |
PHP Cross Reference of MantisBT |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * File containing the ezcGraphSVGDriver class 4 * 5 * @package Graph 6 * @version 1.5 7 * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. 8 * @license http://ez.no/licenses/new_bsd New BSD License 9 */ 10 /** 11 * Extension of the basic Driver package to utilize the SVGlib. 12 * 13 * This drivers options are defined in the class 14 * {@link ezcGraphSvgDriverOptions} extending the basic driver options class 15 * {@link ezcGraphDriverOptions}. 16 * 17 * As this is the default driver you do not need to explicitely set anything to 18 * use it, but may use some of its advanced features. 19 * 20 * <code> 21 * $graph = new ezcGraphPieChart(); 22 * $graph->background->color = '#FFFFFFFF'; 23 * $graph->title = 'Access statistics'; 24 * $graph->legend = false; 25 * 26 * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( 27 * 'Mozilla' => 19113, 28 * 'Explorer' => 10917, 29 * 'Opera' => 1464, 30 * 'Safari' => 652, 31 * 'Konqueror' => 474, 32 * ) ); 33 * 34 * $graph->renderer = new ezcGraphRenderer3d(); 35 * $graph->renderer->options->pieChartShadowSize = 10; 36 * $graph->renderer->options->pieChartGleam = .5; 37 * $graph->renderer->options->dataBorder = false; 38 * $graph->renderer->options->pieChartHeight = 16; 39 * $graph->renderer->options->legendSymbolGleam = .5; 40 * 41 * // SVG driver options 42 * $graph->driver->options->templateDocument = dirname( __FILE__ ) . '/template.svg'; 43 * $graph->driver->options->graphOffset = new ezcGraphCoordinate( 25, 40 ); 44 * $graph->driver->options->insertIntoGroup = 'ezcGraph'; 45 * 46 * $graph->render( 400, 200, 'tutorial_driver_svg.svg' ); 47 * </code> 48 * 49 * @version 1.5 50 * @package Graph 51 * @mainclass 52 */ 53 class ezcGraphSvgDriver extends ezcGraphDriver 54 { 55 56 /** 57 * DOM tree of the svg document 58 * 59 * @var DOMDocument 60 */ 61 protected $dom; 62 63 /** 64 * DOMElement containing all svg style definitions 65 * 66 * @var DOMElement 67 */ 68 protected $defs; 69 70 /** 71 * DOMElement containing all svg objects 72 * 73 * @var DOMElement 74 */ 75 protected $elements; 76 77 /** 78 * List of strings to draw 79 * array ( array( 80 * 'text' => array( 'strings' ), 81 * 'options' => ezcGraphFontOptions, 82 * ) 83 * 84 * @var array 85 */ 86 protected $strings = array(); 87 88 /** 89 * List of already created gradients 90 * 91 * @var array 92 */ 93 protected $drawnGradients = array(); 94 95 /** 96 * Numeric unique element id 97 * 98 * @var int 99 */ 100 protected $elementID = 0; 101 102 /** 103 * Font storage for SVG font glyphs and kernings. 104 * 105 * @var ezcGraphSvgFont 106 */ 107 protected $font = null; 108 109 /** 110 * Constructor 111 * 112 * @param array $options Default option array 113 * @return void 114 * @ignore 115 */ 116 public function __construct( array $options = array() ) 117 { 118 ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'dom' ); 119 $this->options = new ezcGraphSvgDriverOptions( $options ); 120 $this->font = new ezcGraphSvgFont(); 121 } 122 123 /** 124 * Creates the DOM object to insert SVG nodes in. 125 * 126 * If the DOM document does not exists it will be created or loaded 127 * according to the settings. 128 * 129 * @return void 130 */ 131 protected function createDocument() 132 { 133 if ( $this->dom === null ) 134 { 135 // Create encoding based dom document 136 if ( $this->options->encoding !== null ) 137 { 138 $this->dom = new DOMDocument( '1.0', $this->options->encoding ); 139 } 140 else 141 { 142 $this->dom = new DOMDocument( '1.0' ); 143 } 144 145 if ( $this->options->templateDocument !== false ) 146 { 147 $this->dom->load( $this->options->templateDocument ); 148 149 $this->defs = $this->dom->getElementsByTagName( 'defs' )->item( 0 ); 150 $svg = $this->dom->getElementsByTagName( 'svg' )->item( 0 ); 151 } 152 else 153 { 154 $svg = $this->dom->createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); 155 $this->dom->appendChild( $svg ); 156 157 $svg->setAttribute( 'width', $this->options->width ); 158 $svg->setAttribute( 'height', $this->options->height ); 159 $svg->setAttribute( 'version', '1.0' ); 160 $svg->setAttribute( 'id', $this->options->idPrefix ); 161 162 $this->defs = $this->dom->createElement( 'defs' ); 163 $this->defs = $svg->appendChild( $this->defs ); 164 } 165 166 if ( $this->options->insertIntoGroup !== false ) 167 { 168 // getElementById only works for Documents validated against a certain 169 // schema, so that the use of XPath should be faster in most cases. 170 $xpath = new DomXPath( $this->dom ); 171 $this->elements = $xpath->query( '//*[@id = \'' . $this->options->insertIntoGroup . '\']' )->item( 0 ); 172 if ( !$this->elements ) 173 { 174 throw new ezcGraphSvgDriverInvalidIdException( $this->options->insertIntoGroup ); 175 } 176 } 177 else 178 { 179 $this->elements = $this->dom->createElement( 'g' ); 180 $this->elements->setAttribute( 'id', $this->options->idPrefix . 'Chart' ); 181 $this->elements->setAttribute( 'color-rendering', $this->options->colorRendering ); 182 $this->elements->setAttribute( 'shape-rendering', $this->options->shapeRendering ); 183 $this->elements->setAttribute( 'text-rendering', $this->options->textRendering ); 184 $this->elements = $svg->appendChild( $this->elements ); 185 } 186 } 187 } 188 189 /** 190 * Return gradient URL 191 * 192 * Creates the definitions needed for a gradient, if a proper gradient does 193 * not yet exists. In each case a URL referencing the correct gradient will 194 * be returned. 195 * 196 * @param ezcGraphColor $color Gradient 197 * @return string Gradient URL 198 */ 199 protected function getGradientUrl( ezcGraphColor $color ) 200 { 201 switch ( true ) 202 { 203 case ( $color instanceof ezcGraphLinearGradient ): 204 if ( !in_array( $color->__toString(), $this->drawnGradients, true ) ) 205 { 206 $gradient = $this->dom->createElement( 'linearGradient' ); 207 $gradient->setAttribute( 'id', 'Definition_' . $color->__toString() ); 208 $this->defs->appendChild( $gradient ); 209 210 // Start of linear gradient 211 $stop = $this->dom->createElement( 'stop' ); 212 $stop->setAttribute( 'offset', 0 ); 213 $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;', 214 $color->startColor->red, 215 $color->startColor->green, 216 $color->startColor->blue, 217 1 - ( $color->startColor->alpha / 255 ) 218 ) 219 ); 220 $gradient->appendChild( $stop ); 221 222 // End of linear gradient 223 $stop = $this->dom->createElement( 'stop' ); 224 $stop->setAttribute( 'offset', 1 ); 225 $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;', 226 $color->endColor->red, 227 $color->endColor->green, 228 $color->endColor->blue, 229 1 - ( $color->endColor->alpha / 255 ) 230 ) 231 ); 232 $gradient->appendChild( $stop ); 233 234 $gradient = $this->dom->createElement( 'linearGradient' ); 235 $gradient->setAttribute( 'id', $color->__toString() ); 236 $gradient->setAttribute( 'x1', sprintf( '%.4F', $color->startPoint->x ) ); 237 $gradient->setAttribute( 'y1', sprintf( '%.4F', $color->startPoint->y ) ); 238 $gradient->setAttribute( 'x2', sprintf( '%.4F', $color->endPoint->x ) ); 239 $gradient->setAttribute( 'y2', sprintf( '%.4F', $color->endPoint->y ) ); 240 $gradient->setAttribute( 'gradientUnits', 'userSpaceOnUse' ); 241 $gradient->setAttributeNS( 242 'http://www.w3.org/1999/xlink', 243 'xlink:href', 244 '#Definition_' . $color->__toString() 245 ); 246 $this->defs->appendChild( $gradient ); 247 248 $this->drawnGradients[] = $color->__toString(); 249 } 250 251 return sprintf( 'url(#%s)', 252 $color->__toString() 253 ); 254 case ( $color instanceof ezcGraphRadialGradient ): 255 if ( !in_array( $color->__toString(), $this->drawnGradients, true ) ) 256 { 257 $gradient = $this->dom->createElement( 'linearGradient' ); 258 $gradient->setAttribute( 'id', 'Definition_' . $color->__toString() ); 259 $this->defs->appendChild( $gradient ); 260 261 // Start of linear gradient 262 $stop = $this->dom->createElement( 'stop' ); 263 $stop->setAttribute( 'offset', 0 ); 264 $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;', 265 $color->startColor->red, 266 $color->startColor->green, 267 $color->startColor->blue, 268 1 - ( $color->startColor->alpha / 255 ) 269 ) 270 ); 271 $gradient->appendChild( $stop ); 272 273 // End of linear gradient 274 $stop = $this->dom->createElement( 'stop' ); 275 $stop->setAttribute( 'offset', 1 ); 276 $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;', 277 $color->endColor->red, 278 $color->endColor->green, 279 $color->endColor->blue, 280 1 - ( $color->endColor->alpha / 255 ) 281 ) 282 ); 283 $gradient->appendChild( $stop ); 284 285 $gradient = $this->dom->createElement( 'radialGradient' ); 286 $gradient->setAttribute( 'id', $color->__toString() ); 287 $gradient->setAttribute( 'cx', sprintf( '%.4F', $color->center->x ) ); 288 $gradient->setAttribute( 'cy', sprintf( '%.4F', $color->center->y ) ); 289 $gradient->setAttribute( 'fx', sprintf( '%.4F', $color->center->x ) ); 290 $gradient->setAttribute( 'fy', sprintf( '%.4F', $color->center->y ) ); 291 $gradient->setAttribute( 'r', max( $color->height, $color->width ) ); 292 $gradient->setAttribute( 'gradientUnits', 'userSpaceOnUse' ); 293 $gradient->setAttributeNS( 294 'http://www.w3.org/1999/xlink', 295 'xlink:href', 296 '#Definition_' . $color->__toString() 297 ); 298 $this->defs->appendChild( $gradient ); 299 300 $this->drawnGradients[] = $color->__toString(); 301 } 302 303 return sprintf( 'url(#%s)', 304 $color->__toString() 305 ); 306 default: 307 return false; 308 } 309 310 } 311 312 /** 313 * Get SVG style definition 314 * 315 * Returns a string with SVG style definitions created from color, 316 * fillstatus and line thickness. 317 * 318 * @param ezcGraphColor $color Color 319 * @param mixed $filled Filled 320 * @param float $thickness Line thickness. 321 * @return string Formatstring 322 */ 323 protected function getStyle( ezcGraphColor $color, $filled = true, $thickness = 1. ) 324 { 325 if ( $filled ) 326 { 327 if ( $url = $this->getGradientUrl( $color ) ) 328 { 329 return sprintf( 'fill: %s; stroke: none;', $url ); 330 } 331 else 332 { 333 return sprintf( 'fill: #%02x%02x%02x; fill-opacity: %.2F; stroke: none;', 334 $color->red, 335 $color->green, 336 $color->blue, 337 1 - ( $color->alpha / 255 ) 338 ); 339 } 340 } 341 else 342 { 343 if ( $url = $this->getGradientUrl( $color ) ) 344 { 345 return sprintf( 'fill: none; stroke: %s;', $url ); 346 } 347 else 348 { 349 return sprintf( 'fill: none; stroke: #%02x%02x%02x; stroke-width: %d; stroke-opacity: %.2F; stroke-linecap: %s; stroke-linejoin: %s;', 350 $color->red, 351 $color->green, 352 $color->blue, 353 $thickness, 354 1 - ( $color->alpha / 255 ), 355 $this->options->strokeLineCap, 356 $this->options->strokeLineJoin 357 ); 358 } 359 } 360 } 361 362 /** 363 * Draws a single polygon. 364 * 365 * @param array $points Point array 366 * @param ezcGraphColor $color Polygon color 367 * @param mixed $filled Filled 368 * @param float $thickness Line thickness 369 * @return void 370 */ 371 public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. ) 372 { 373 $this->createDocument(); 374 375 if ( !$filled ) 376 { 377 // The middle of the border is on the outline of a polygon in SVG, 378 // fix that: 379 try 380 { 381 $points = $this->reducePolygonSize( $points, $thickness / 2 ); 382 } 383 catch ( ezcGraphReducementFailedException $e ) 384 { 385 return false; 386 } 387 } 388 389 $lastPoint = end( $points ); 390 $pointString = sprintf( ' M %.4F,%.4F', 391 $lastPoint->x + $this->options->graphOffset->x, 392 $lastPoint->y + $this->options->graphOffset->y 393 ); 394 395 foreach ( $points as $point ) 396 { 397 $pointString .= sprintf( ' L %.4F,%.4F', 398 $point->x + $this->options->graphOffset->x, 399 $point->y + $this->options->graphOffset->y 400 ); 401 } 402 $pointString .= ' z '; 403 404 $path = $this->dom->createElement( 'path' ); 405 $path->setAttribute( 'd', $pointString ); 406 407 $path->setAttribute( 408 'style', 409 $this->getStyle( $color, $filled, $thickness ) 410 ); 411 $path->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Polygon_' . ++$this->elementID ) ); 412 $this->elements->appendChild( $path ); 413 414 return $id; 415 } 416 417 /** 418 * Draws a line 419 * 420 * @param ezcGraphCoordinate $start Start point 421 * @param ezcGraphCoordinate $end End point 422 * @param ezcGraphColor $color Line color 423 * @param float $thickness Line thickness 424 * @return void 425 */ 426 public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. ) 427 { 428 $this->createDocument(); 429 430 $pointString = sprintf( ' M %.4F,%.4F L %.4F,%.4F', 431 $start->x + $this->options->graphOffset->x, 432 $start->y + $this->options->graphOffset->y, 433 $end->x + $this->options->graphOffset->x, 434 $end->y + $this->options->graphOffset->y 435 ); 436 437 $path = $this->dom->createElement( 'path' ); 438 $path->setAttribute( 'd', $pointString ); 439 $path->setAttribute( 440 'style', 441 $this->getStyle( $color, false, $thickness ) 442 ); 443 444 $path->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Line_' . ++$this->elementID ) ); 445 $this->elements->appendChild( $path ); 446 447 return $id; 448 } 449 450 /** 451 * Returns boundings of text depending on the available font extension 452 * 453 * @param float $size Textsize 454 * @param ezcGraphFontOptions $font Font 455 * @param string $text Text 456 * @return ezcGraphBoundings Boundings of text 457 */ 458 protected function getTextBoundings( $size, ezcGraphFontOptions $font, $text ) 459 { 460 if ( $font->type === ezcGraph::SVG_FONT ) 461 { 462 return new ezcGraphBoundings( 463 0, 464 0, 465 $this->font->calculateStringWidth( $font->path, $text ) * $size, 466 $size 467 ); 468 } 469 else 470 { 471 // If we didn't get a SVG font, continue guessing the font width. 472 return new ezcGraphBoundings( 473 0, 474 0, 475 $this->getTextWidth( $text, $size ), 476 $size 477 ); 478 } 479 } 480 481 /** 482 * Writes text in a box of desired size 483 * 484 * @param string $string Text 485 * @param ezcGraphCoordinate $position Top left position 486 * @param float $width Width of text box 487 * @param float $height Height of text box 488 * @param int $align Alignement of text 489 * @param ezcGraphRotation $rotation 490 * @return void 491 */ 492 public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null ) 493 { 494 $padding = $this->options->font->padding + ( $this->options->font->border !== false ? $this->options->font->borderWidth : 0 ); 495 496 $width -= $padding * 2; 497 $height -= $padding * 2; 498 $textPosition = new ezcGraphCoordinate( 499 $position->x + $padding, 500 $position->y + $padding 501 ); 502 503 // Try to get a font size for the text to fit into the box 504 $maxSize = min( $height, $this->options->font->maxFontSize ); 505 $result = false; 506 for ( $size = $maxSize; $size >= $this->options->font->minFontSize; ) 507 { 508 $result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size ); 509 if ( is_array( $result ) ) 510 { 511 break; 512 } 513 $size = ( ( $newsize = $size * ( $result ) ) >= $size ? $size - 1 : floor( $newsize ) ); 514 } 515 516 if ( !is_array( $result ) ) 517 { 518 if ( ( $height >= $this->options->font->minFontSize ) && 519 ( $this->options->autoShortenString ) ) 520 { 521 $result = $this->tryFitShortenedString( $string, $position, $width, $height, $size = $this->options->font->minFontSize ); 522 } 523 else 524 { 525 throw new ezcGraphFontRenderingException( $string, $this->options->font->minFontSize, $width, $height ); 526 } 527 } 528 529 $this->options->font->minimalUsedFont = $size; 530 $this->strings[] = array( 531 'text' => $result, 532 'id' => $id = ( $this->options->idPrefix . 'TextBox_' . ++$this->elementID ), 533 'position' => $textPosition, 534 'width' => $width, 535 'height' => $height, 536 'align' => $align, 537 'font' => $this->options->font, 538 'rotation' => $rotation, 539 ); 540 541 return $id; 542 } 543 544 /** 545 * Guess text width for string 546 * 547 * The is no way to know the font or fontsize used by the SVG renderer to 548 * render the string. We assume some character width defined in the SVG 549 * driver options, tu guess the length of a string. We discern between 550 * numeric an non numeric strings, because we often use only numeric 551 * strings to display chart data and numbers tend to be a bit wider then 552 * characters. 553 * 554 * @param mixed $string 555 * @param mixed $size 556 * @access protected 557 * @return void 558 */ 559 protected function getTextWidth( $string, $size ) 560 { 561 switch ( strtolower( $this->options->encoding ) ) 562 { 563 case '': 564 case 'utf-8': 565 case 'utf-16': 566 $string = utf8_decode( $string ); 567 break; 568 } 569 570 if ( is_numeric( $string ) ) 571 { 572 return $size * strlen( $string ) * $this->options->assumedNumericCharacterWidth; 573 } 574 else 575 { 576 return $size * strlen( $string ) * $this->options->assumedTextCharacterWidth; 577 } 578 } 579 580 /** 581 * Encodes non-utf-8 strings 582 * 583 * Transforms non-utf-8 strings to their hex entities, because ext/DOM 584 * fails here with conversion errors. 585 * 586 * @param string $string 587 * @return string 588 */ 589 protected function encode( $string ) 590 { 591 $string = htmlspecialchars( $string ); 592 593 switch ( strtolower( $this->options->encoding ) ) 594 { 595 case '': 596 case 'utf-8': 597 case 'utf-16': 598 return $string; 599 default: 600 // Manual escaping of non ANSII characters, because ext/DOM fails here 601 return preg_replace_callback( 602 '/[\\x80-\\xFF]/', 603 create_function( 604 '$char', 605 'return sprintf( \'&#x%02x;\', ord( $char[0] ) );' 606 ), 607 $string 608 ); 609 } 610 } 611 612 /** 613 * Draw all collected texts 614 * 615 * The texts are collected and their maximum possible font size is 616 * calculated. This function finally draws the texts on the image, this 617 * delayed drawing has two reasons: 618 * 619 * 1) This way the text strings are always on top of the image, what 620 * results in better readable texts 621 * 2) The maximum possible font size can be calculated for a set of texts 622 * with the same font configuration. Strings belonging to one chart 623 * element normally have the same font configuration, so that all texts 624 * belonging to one element will have the same font size. 625 * 626 * @access protected 627 * @return void 628 */ 629 protected function drawAllTexts() 630 { 631 $elementsRoot = $this->elements; 632 633 foreach ( $this->strings as $text ) 634 { 635 // Add all text elements into one group 636 $group = $this->dom->createElement( 'g' ); 637 $group->setAttribute( 'id', $text['id'] ); 638 639 if ( $text['rotation'] !== null ) 640 { 641 $group->setAttribute( 'transform', sprintf( 'rotate( %.2F %.4F %.4F )', 642 $text['rotation']->getRotation(), 643 $text['rotation']->getCenter()->x, 644 $text['rotation']->getCenter()->y 645 ) ); 646 } 647 648 $group = $elementsRoot->appendChild( $group ); 649 650 $size = $text['font']->minimalUsedFont; 651 $font = $text['font']->name; 652 653 $completeHeight = count( $text['text'] ) * $size + ( count( $text['text'] ) - 1 ) * $this->options->lineSpacing; 654 655 // Calculate y offset for vertical alignement 656 switch ( true ) 657 { 658 case ( $text['align'] & ezcGraph::BOTTOM ): 659 $yOffset = $text['height'] - $completeHeight; 660 break; 661 case ( $text['align'] & ezcGraph::MIDDLE ): 662 $yOffset = ( $text['height'] - $completeHeight ) / 2; 663 break; 664 case ( $text['align'] & ezcGraph::TOP ): 665 default: 666 $yOffset = 0; 667 break; 668 } 669 670 $padding = $text['font']->padding + $text['font']->borderWidth / 2; 671 if ( $this->options->font->minimizeBorder === true ) 672 { 673 // Calculate maximum width of text rows 674 $width = false; 675 foreach ( $text['text'] as $line ) 676 { 677 $string = implode( ' ', $line ); 678 if ( ( $strWidth = $this->getTextBoundings( $size, $text['font'], $string )->width ) > $width ) 679 { 680 $width = $strWidth; 681 } 682 } 683 684 switch ( true ) 685 { 686 case ( $text['align'] & ezcGraph::LEFT ): 687 $xOffset = 0; 688 break; 689 case ( $text['align'] & ezcGraph::CENTER ): 690 $xOffset = ( $text['width'] - $width ) / 2; 691 break; 692 case ( $text['align'] & ezcGraph::RIGHT ): 693 $xOffset = $text['width'] - $width; 694 break; 695 } 696 697 $borderPolygonArray = array( 698 new ezcGraphCoordinate( 699 $text['position']->x - $padding + $xOffset, 700 $text['position']->y - $padding + $yOffset 701 ), 702 new ezcGraphCoordinate( 703 $text['position']->x + $padding * 2 + $xOffset + $width, 704 $text['position']->y - $padding + $yOffset 705 ), 706 new ezcGraphCoordinate( 707 $text['position']->x + $padding * 2 + $xOffset + $width, 708 $text['position']->y + $padding * 2 + $yOffset + $completeHeight 709 ), 710 new ezcGraphCoordinate( 711 $text['position']->x - $padding + $xOffset, 712 $text['position']->y + $padding * 2 + $yOffset + $completeHeight 713 ), 714 ); 715 } 716 else 717 { 718 $borderPolygonArray = array( 719 new ezcGraphCoordinate( 720 $text['position']->x - $padding, 721 $text['position']->y - $padding 722 ), 723 new ezcGraphCoordinate( 724 $text['position']->x + $padding * 2 + $text['width'], 725 $text['position']->y - $padding 726 ), 727 new ezcGraphCoordinate( 728 $text['position']->x + $padding * 2 + $text['width'], 729 $text['position']->y + $padding * 2 + $text['height'] 730 ), 731 new ezcGraphCoordinate( 732 $text['position']->x - $padding, 733 $text['position']->y + $padding * 2 + $text['height'] 734 ), 735 ); 736 } 737 738 // Set elements root temporary to local text group to ensure 739 // background and border beeing elements of text group 740 $this->elements = $group; 741 if ( $text['font']->background !== false ) 742 { 743 $this->drawPolygon( 744 $borderPolygonArray, 745 $text['font']->background, 746 true 747 ); 748 } 749 else 750 { 751 // Always draw full tranparent background polygon as fallback, 752 // to be able to click on complete font space, not only on 753 // the text 754 $this->drawPolygon( 755 $borderPolygonArray, 756 ezcGraphColor::fromHex( '#FFFFFFFF' ), 757 true 758 ); 759 } 760 761 if ( $text['font']->border !== false ) 762 { 763 $this->drawPolygon( 764 $borderPolygonArray, 765 $text['font']->border, 766 false, 767 $text['font']->borderWidth 768 ); 769 } 770 $this->elements = $elementsRoot; 771 772 // Bottom line for SVG fonts is lifted a bit 773 $text['position']->y += $size * .85; 774 775 // Render text with evaluated font size 776 foreach ( $text['text'] as $line ) 777 { 778 $string = implode( ' ', $line ); 779 780 switch ( true ) 781 { 782 case ( $text['align'] & ezcGraph::LEFT ): 783 $position = new ezcGraphCoordinate( 784 $text['position']->x, 785 $text['position']->y + $yOffset 786 ); 787 break; 788 case ( $text['align'] & ezcGraph::RIGHT ): 789 $position = new ezcGraphCoordinate( 790 $text['position']->x + ( $text['width'] - $this->getTextBoundings( $size, $text['font'], $string )->width ), 791 $text['position']->y + $yOffset 792 ); 793 break; 794 case ( $text['align'] & ezcGraph::CENTER ): 795 $position = new ezcGraphCoordinate( 796 $text['position']->x + ( ( $text['width'] - $this->getTextBoundings( $size, $text['font'], $string )->width ) / 2 ), 797 $text['position']->y + $yOffset 798 ); 799 break; 800 } 801 802 // Optionally draw text shadow 803 if ( $text['font']->textShadow === true ) 804 { 805 $textNode = $this->dom->createElement( 'text', $this->encode( $string ) ); 806 $textNode->setAttribute( 'id', $text['id'] . '_shadow' ); 807 $textNode->setAttribute( 'x', sprintf( '%.4F', $position->x + $this->options->graphOffset->x + $text['font']->textShadowOffset ) ); 808 $textNode->setAttribute( 'text-length', sprintf( '%.4Fpx', $this->getTextBoundings( $size, $text['font'], $string )->width ) ); 809 $textNode->setAttribute( 'y', sprintf( '%.4F', $position->y + $this->options->graphOffset->y + $text['font']->textShadowOffset ) ); 810 $textNode->setAttribute( 811 'style', 812 sprintf( 813 'font-size: %dpx; font-family: \'%s\'; fill: #%02x%02x%02x; fill-opacity: %.2F; stroke: none;', 814 $size, 815 $text['font']->name, 816 $text['font']->textShadowColor->red, 817 $text['font']->textShadowColor->green, 818 $text['font']->textShadowColor->blue, 819 1 - ( $text['font']->textShadowColor->alpha / 255 ) 820 ) 821 ); 822 $group->appendChild( $textNode ); 823 } 824 825 // Finally draw text 826 $textNode = $this->dom->createElement( 'text', $this->encode( $string ) ); 827 $textNode->setAttribute( 'id', $text['id'] . '_text' ); 828 $textNode->setAttribute( 'x', sprintf( '%.4F', $position->x + $this->options->graphOffset->x ) ); 829 $textNode->setAttribute( 'text-length', sprintf( '%.4Fpx', $this->getTextBoundings( $size, $text['font'], $string )->width ) ); 830 $textNode->setAttribute( 'y', sprintf( '%.4F', $position->y + $this->options->graphOffset->y ) ); 831 $textNode->setAttribute( 832 'style', 833 sprintf( 834 'font-size: %dpx; font-family: \'%s\'; fill: #%02x%02x%02x; fill-opacity: %.2F; stroke: none;', 835 $size, 836 $text['font']->name, 837 $text['font']->color->red, 838 $text['font']->color->green, 839 $text['font']->color->blue, 840 1 - ( $text['font']->color->alpha / 255 ) 841 ) 842 ); 843 $group->appendChild( $textNode ); 844 845 $text['position']->y += $size + $size * $this->options->lineSpacing; 846 } 847 } 848 } 849 850 /** 851 * Draws a sector of cirlce 852 * 853 * @param ezcGraphCoordinate $center Center of circle 854 * @param mixed $width Width 855 * @param mixed $height Height 856 * @param mixed $startAngle Start angle of circle sector 857 * @param mixed $endAngle End angle of circle sector 858 * @param ezcGraphColor $color Color 859 * @param mixed $filled Filled; 860 * @return void 861 */ 862 public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true ) 863 { 864 $this->createDocument(); 865 866 // Normalize angles 867 if ( $startAngle > $endAngle ) 868 { 869 $tmp = $startAngle; 870 $startAngle = $endAngle; 871 $endAngle = $tmp; 872 } 873 874 if ( ( $endAngle - $startAngle ) >= 360 ) 875 { 876 return $this->drawCircle( $center, $width, $height, $color, $filled ); 877 } 878 879 // We need the radius 880 $width /= 2; 881 $height /= 2; 882 883 // Apply offset to copy of center coordinate 884 $center = clone $center; 885 $center->x += $this->options->graphOffset->x; 886 $center->y += $this->options->graphOffset->y; 887 888 if ( $filled ) 889 { 890 $Xstart = $center->x + $width * cos( -deg2rad( $startAngle ) ); 891 $Ystart = $center->y + $height * sin( deg2rad( $startAngle ) ); 892 $Xend = $center->x + $width * cos( ( -deg2rad( $endAngle ) ) ); 893 $Yend = $center->y + $height * sin( ( deg2rad( $endAngle ) ) ); 894 895 $arc = $this->dom->createElement( 'path' ); 896 $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F L %.2F,%.2F A %.2F,%.2F 0 %d,1 %.2F,%.2F z', 897 // Middle 898 $center->x, $center->y, 899 // Startpoint 900 $Xstart, $Ystart, 901 // Radius 902 $width, $height, 903 // SVG-Stuff 904 ( $endAngle - $startAngle ) > 180, 905 // Endpoint 906 $Xend, $Yend 907 ) 908 ); 909 910 $arc->setAttribute( 911 'style', 912 $this->getStyle( $color, $filled, 1 ) 913 ); 914 $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircleSector_' . ++$this->elementID ) ); 915 $this->elements->appendChild( $arc ); 916 return $id; 917 } 918 else 919 { 920 try 921 { 922 $reduced = $this->reduceEllipseSize( $center, $width * 2, $height * 2, $startAngle, $endAngle, .5 ); 923 } 924 catch ( ezcGraphReducementFailedException $e ) 925 { 926 return false; 927 } 928 929 $arc = $this->dom->createElement( 'path' ); 930 $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F L %.2F,%.2F A %.2F,%.2F 0 %d,1 %.2F,%.2F z', 931 // Middle 932 $reduced['center']->x, $reduced['center']->y, 933 // Startpoint 934 $reduced['start']->x, $reduced['start']->y, 935 // Radius 936 $width - .5, $height - .5, 937 // SVG-Stuff 938 ( $endAngle - $startAngle ) > 180, 939 // Endpoint 940 $reduced['end']->x, $reduced['end']->y 941 ) 942 ); 943 944 $arc->setAttribute( 945 'style', 946 $this->getStyle( $color, $filled, 1 ) 947 ); 948 949 $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircleSector_' . ++$this->elementID ) ); 950 $this->elements->appendChild( $arc ); 951 952 return $id; 953 } 954 } 955 956 /** 957 * Draws a circular arc 958 * 959 * @param ezcGraphCoordinate $center Center of ellipse 960 * @param integer $width Width of ellipse 961 * @param integer $height Height of ellipse 962 * @param integer $size Height of border 963 * @param float $startAngle Starting angle of circle sector 964 * @param float $endAngle Ending angle of circle sector 965 * @param ezcGraphColor $color Color of Border 966 * @param bool $filled 967 * @return void 968 */ 969 public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled = true ) 970 { 971 $this->createDocument(); 972 973 // Normalize angles 974 if ( $startAngle > $endAngle ) 975 { 976 $tmp = $startAngle; 977 $startAngle = $endAngle; 978 $endAngle = $tmp; 979 } 980 981 if ( ( $endAngle - $startAngle > 180 ) || 982 ( ( $startAngle % 180 != 0) && ( $endAngle % 180 != 0) && ( ( $startAngle % 360 > 180 ) XOR ( $endAngle % 360 > 180 ) ) ) ) 983 { 984 // Border crosses he 180 degrees border 985 $intersection = floor( $endAngle / 180 ) * 180; 986 while ( $intersection >= $endAngle ) 987 { 988 $intersection -= 180; 989 } 990 991 $this->drawCircularArc( $center, $width, $height, $size, $startAngle, $intersection, $color, $filled ); 992 $this->drawCircularArc( $center, $width, $height, $size, $intersection, $endAngle, $color, $filled ); 993 return; 994 } 995 996 // We need the radius 997 $width /= 2; 998 $height /= 2; 999 1000 $Xstart = $center->x + $this->options->graphOffset->x + $width * cos( -deg2rad( $startAngle ) ); 1001 $Ystart = $center->y + $this->options->graphOffset->y + $height * sin( deg2rad( $startAngle ) ); 1002 $Xend = $center->x + $this->options->graphOffset->x + $width * cos( ( -deg2rad( $endAngle ) ) ); 1003 $Yend = $center->y + $this->options->graphOffset->y + $height * sin( ( deg2rad( $endAngle ) ) ); 1004 1005 if ( $filled === true ) 1006 { 1007 $arc = $this->dom->createElement( 'path' ); 1008 $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F A %.2F,%.2F 0 %d,0 %.2F,%.2F L %.2F,%.2F A %.2F,%2F 0 %d,1 %.2F,%.2F z', 1009 // Endpoint low 1010 $Xend, $Yend + $size, 1011 // Radius 1012 $width, $height, 1013 // SVG-Stuff 1014 ( $endAngle - $startAngle ) > 180, 1015 // Startpoint low 1016 $Xstart, $Ystart + $size, 1017 // Startpoint 1018 $Xstart, $Ystart, 1019 // Radius 1020 $width, $height, 1021 // SVG-Stuff 1022 ( $endAngle - $startAngle ) > 180, 1023 // Endpoint 1024 $Xend, $Yend 1025 ) 1026 ); 1027 } 1028 else 1029 { 1030 $arc = $this->dom->createElement( 'path' ); 1031 $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F A %.2F,%.2F 0 %d,1 %.2F,%.2F', 1032 // Startpoint 1033 $Xstart, $Ystart, 1034 // Radius 1035 $width, $height, 1036 // SVG-Stuff 1037 ( $endAngle - $startAngle ) > 180, 1038 // Endpoint 1039 $Xend, $Yend 1040 ) 1041 ); 1042 } 1043 1044 $arc->setAttribute( 1045 'style', 1046 $this->getStyle( $color, $filled ) 1047 ); 1048 1049 $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircularArc_' . ++$this->elementID ) ); 1050 $this->elements->appendChild( $arc ); 1051 1052 if ( ( $this->options->shadeCircularArc !== false ) && 1053 $filled ) 1054 { 1055 $gradient = new ezcGraphLinearGradient( 1056 new ezcGraphCoordinate( 1057 $center->x - $width, 1058 $center->y 1059 ), 1060 new ezcGraphCoordinate( 1061 $center->x + $width, 1062 $center->y 1063 ), 1064 ezcGraphColor::fromHex( '#FFFFFF' )->transparent( $this->options->shadeCircularArc * 1.5 ), 1065 ezcGraphColor::fromHex( '#000000' )->transparent( $this->options->shadeCircularArc ) 1066 ); 1067 1068 $arc = $this->dom->createElement( 'path' ); 1069 $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F A %.2F,%.2F 0 %d,0 %.2F,%.2F L %.2F,%.2F A %.2F,%2F 0 %d,1 %.2F,%.2F z', 1070 // Endpoint low 1071 $Xend, $Yend + $size, 1072 // Radius 1073 $width, $height, 1074 // SVG-Stuff 1075 ( $endAngle - $startAngle ) > 180, 1076 // Startpoint low 1077 $Xstart, $Ystart + $size, 1078 // Startpoint 1079 $Xstart, $Ystart, 1080 // Radius 1081 $width, $height, 1082 // SVG-Stuff 1083 ( $endAngle - $startAngle ) > 180, 1084 // Endpoint 1085 $Xend, $Yend 1086 ) 1087 ); 1088 1089 $arc->setAttribute( 1090 'style', 1091 $this->getStyle( $gradient, $filled ) 1092 ); 1093 $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircularArc_' . ++$this->elementID ) ); 1094 1095 $this->elements->appendChild( $arc ); 1096 } 1097 1098 return $id; 1099 } 1100 1101 /** 1102 * Draw circle 1103 * 1104 * @param ezcGraphCoordinate $center Center of ellipse 1105 * @param mixed $width Width of ellipse 1106 * @param mixed $height height of ellipse 1107 * @param ezcGraphColor $color Color 1108 * @param mixed $filled Filled 1109 * @return void 1110 */ 1111 public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true ) 1112 { 1113 $this->createDocument(); 1114 1115 $ellipse = $this->dom->createElement( 'ellipse' ); 1116 $ellipse->setAttribute( 'cx', sprintf( '%.4F', $center->x + $this->options->graphOffset->x ) ); 1117 $ellipse->setAttribute( 'cy', sprintf( '%.4F', $center->y + $this->options->graphOffset->y ) ); 1118 $ellipse->setAttribute( 'rx', sprintf( '%.4F', $width / 2 - ( $filled ? 0 : .5 ) ) ); 1119 $ellipse->setAttribute( 'ry', sprintf( '%.4F', $height / 2 - ( $filled ? 0 : .5 ) ) ); 1120 1121 $ellipse->setAttribute( 1122 'style', 1123 $this->getStyle( $color, $filled, 1 ) 1124 ); 1125 1126 $ellipse->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Circle_' . ++$this->elementID ) ); 1127 $this->elements->appendChild( $ellipse ); 1128 1129 return $id; 1130 } 1131 1132 /** 1133 * Draw an image 1134 * 1135 * The image will be inlined in the SVG document using data URL scheme. For 1136 * this the mime type and base64 encoded file content will be merged to 1137 * URL. 1138 * 1139 * @param mixed $file Image file 1140 * @param ezcGraphCoordinate $position Top left position 1141 * @param mixed $width Width of image in destination image 1142 * @param mixed $height Height of image in destination image 1143 * @return void 1144 */ 1145 public function drawImage( $file, ezcGraphCoordinate $position, $width, $height ) 1146 { 1147 $this->createDocument(); 1148 1149 $data = getimagesize( $file ); 1150 $image = $this->dom->createElement( 'image' ); 1151 1152 $image->setAttribute( 'x', sprintf( '%.4F', $position->x + $this->options->graphOffset->x ) ); 1153 $image->setAttribute( 'y', sprintf( '%.4F', $position->y + $this->options->graphOffset->y ) ); 1154 $image->setAttribute( 'width', sprintf( '%.4Fpx', $width ) ); 1155 $image->setAttribute( 'height', sprintf( '%.4Fpx', $height ) ); 1156 $image->setAttributeNS( 1157 'http://www.w3.org/1999/xlink', 1158 'xlink:href', 1159 sprintf( 'data:%s;base64,%s', 1160 $data['mime'], 1161 base64_encode( file_get_contents( $file ) ) 1162 ) 1163 ); 1164 1165 $this->elements->appendChild( $image ); 1166 $image->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Image_' . ++$this->elementID ) ); 1167 1168 return $id; 1169 } 1170 1171 /** 1172 * Return mime type for current image format 1173 * 1174 * @return string 1175 */ 1176 public function getMimeType() 1177 { 1178 return 'image/svg+xml'; 1179 } 1180 1181 /** 1182 * Render image directly to output 1183 * 1184 * The method renders the image directly to the standard output. You 1185 * normally do not want to use this function, because it makes it harder 1186 * to proper cache the generated graphs. 1187 * 1188 * @return void 1189 */ 1190 public function renderToOutput() 1191 { 1192 $this->createDocument(); 1193 $this->drawAllTexts(); 1194 1195 header( 'Content-Type: ' . $this->getMimeType() ); 1196 echo $this->dom->saveXML(); 1197 } 1198 1199 /** 1200 * Finally save image 1201 * 1202 * @param string $file Destination filename 1203 * @return void 1204 */ 1205 public function render( $file ) 1206 { 1207 $this->createDocument(); 1208 $this->drawAllTexts(); 1209 1210 // Embed used glyphs 1211 $this->font->addFontToDocument( $this->dom ); 1212 $this->dom->save( $file ); 1213 } 1214 1215 /** 1216 * Get resource of rendered result 1217 * 1218 * Return the resource of the rendered result. You should not use this 1219 * method before you called either renderToOutput() or render(), as the 1220 * image may not be completely rendered until then. 1221 * 1222 * @return DOMDocument 1223 */ 1224 public function getResource() 1225 { 1226 return $this->dom; 1227 } 1228 } 1229 1230 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Jul 28 15:48:31 2011 | Cross-referenced by PHPXref 0.7 |