[ Index ]

PHP Cross Reference of MantisBT

title

Body

[close]

/library/ezc/Graph/src/driver/ -> svg.php (source)

   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  ?>


Generated: Thu Jul 28 15:48:31 2011 Cross-referenced by PHPXref 0.7