[ Index ]

PHP Cross Reference of MantisBT

title

Body

[close]

/library/ezc/Graph/src/driver/ -> svg_font.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   * @author Freddie Witherden
   9   * @license http://ez.no/licenses/new_bsd New BSD License
  10   */
  11  
  12  /**
  13   * Helper class, offering requrired calculation basics and font metrics to use
  14   * SVG fonts with the SVG driver.
  15   *
  16   * You may convert any ttf font into a SVG font using the `ttf2svg` bianry from
  17   * the batik package. Depending on the distribution it may only be available as
  18   * `batik-ttf2svg-<version>`.
  19   *
  20   * Usage:
  21   * <code>
  22   *  $font = new ezcGraphSvgFont();
  23   *  var_dump(
  24   *      $font->calculateStringWidth( '../tests/data/font.svg', 'Just a test string.' ),
  25   *      $font->calculateStringWidth( '../tests/data/font2.svg', 'Just a test string.' )
  26   *  );
  27   * </code>
  28   *
  29   * @version 1.5
  30   * @package Graph
  31   * @mainclass
  32   */
  33  class ezcGraphSvgFont
  34  {
  35      /**
  36       * Units per EM
  37       *
  38       * @var float
  39       */
  40      protected $unitsPerEm;
  41  
  42      /**
  43       * Used glyphs
  44       *
  45       * @var array
  46       */
  47      protected $usedGlyphs = array();
  48  
  49      /**
  50       * Cache for glyph size to save XPath lookups.
  51       * 
  52       * @var array
  53       */
  54      protected $glyphCache = array();
  55  
  56      /**
  57       * Used kernings
  58       *
  59       * @var array
  60       */
  61      protected $usedKerns = array();
  62  
  63      /**
  64       * Path to font
  65       *
  66       * @var string
  67       */
  68      protected $fonts = array();
  69  
  70      /**
  71       * Initialize SVG font
  72       *
  73       * Loads the SVG font $filename. This should be the path to the file
  74       * generated by ttf2svg.
  75       *
  76       * Returns the (normlized) name of the initilized font.
  77       *
  78       * @param string $fontPath
  79       * @return string
  80       */
  81      protected function initializeFont( $fontPath )
  82      {
  83          if ( isset( $this->fonts[$fontPath] ) )
  84          {
  85              return $fontPath;
  86          }
  87  
  88          // Check for existance of font file
  89          if ( !is_file( $fontPath ) || !is_readable( $fontPath ) )
  90          {
  91              throw new ezcBaseFileNotFoundException( $fontPath );
  92          }
  93  
  94          $this->fonts[$fontPath] = simplexml_load_file( $fontPath )->defs->font;
  95  
  96          // SimpleXML requires us to register a namespace for XPath to work
  97          $this->fonts[$fontPath]->registerXPathNamespace( 'svg', 'http://www.w3.org/2000/svg' );
  98  
  99          // Extract the number of units per Em
 100          $this->unitsPerEm[$fontPath] = (int) $this->fonts[$fontPath]->{'font-face'}['units-per-em'];
 101  
 102          return $fontPath;
 103      }
 104  
 105      /**
 106       * Get name of font
 107       *
 108       * Get the name of the given font, by extracting its font family from the
 109       * SVG font file.
 110       * 
 111       * @param string $fontPath 
 112       * @return string
 113       */
 114      public static function getFontName( $fontPath )
 115      {
 116          $font = simplexml_load_file( $fontPath )->defs->font;
 117  
 118          // SimpleXML requires us to register a namespace for XPath to work
 119          $font->registerXPathNamespace( 'svg', 'http://www.w3.org/2000/svg' );
 120  
 121          // Extract the font family name
 122          return (string) $font->{'font-face'}['font-family'];
 123      }
 124  
 125      /**
 126       * XPath has no standard means of escaping ' and ", with the only solution
 127       * being to delimit your string with the opposite type of quote. ( And if
 128       * your string contains both concat(  ) it ).
 129       *
 130       * This method will correctly delimit $char with the appropriate quote type
 131       * so that it can be used in an XPath expression.
 132       *
 133       * @param string $char
 134       * @return string
 135       */
 136      protected static function xpathEscape( $char )
 137      {
 138          return "'" . str_replace( 
 139              array( '\'', '\\' ),
 140              array( '\\\'', '\\\\' ),
 141              $char ) . "'";
 142      }
 143  
 144      /**
 145       * Returns the <glyph> associated with $char.
 146       *
 147       * @param string $fontPath
 148       * @param string $char
 149       * @return float
 150       */
 151      protected function getGlyph( $fontPath, $char )
 152      {
 153          // Check if glyphwidth has already been calculated.
 154          if ( isset( $this->glyphCache[$fontPath][$char] ) )
 155          {
 156              return $this->glyphCache[$fontPath][$char];
 157          }
 158  
 159          $matches = $this->fonts[$fontPath]->xpath(
 160              $query = "glyph[@unicode=" . self::xpathEscape( $char ) . "]"
 161          );
 162  
 163          if ( count( $matches ) === 0 )
 164          {
 165               // Just ignore missing glyphs. The client will still render them
 166               // using a default font. We try to estimate some width by using a
 167               // common other character.
 168              return $this->glyphCache[$fontPath][$char] = 
 169                  ( $char === 'o' ? false : $this->getGlyph( $fontPath, 'o' ) );
 170          }
 171  
 172          $glyph = $matches[0];
 173          if ( !in_array( $glyph, $this->usedGlyphs ) )
 174          {
 175              $this->usedGlyphs[$fontPath][] = $glyph;
 176          }
 177  
 178          // There should only ever be one match
 179          return $this->glyphCache[$fontPath][$char] = $glyph;
 180      }
 181  
 182      /**
 183       * Returns the amount of kerning to apply for glyphs $g1 and $g2. If no
 184       * valid kerning pair can be found 0 is returned.
 185       *
 186       * @param string $fontPath
 187       * @param SimpleXMLElement $g1
 188       * @param SimpleXMLElement $g2
 189       * @return int
 190       */
 191      public function getKerning( $fontPath, SimpleXMLElement $glyph1, SimpleXMLElement $glyph2 )
 192      {
 193          // Get the glyph names
 194          $g1Name = self::xpathEscape( ( string ) $glyph1['glyph-name'] );
 195          $g2Name = self::xpathEscape( ( string ) $glyph2['glyph-name'] );
 196  
 197          // Get the unicode character names
 198          $g1Uni = self::xpathEscape( ( string ) $glyph1['unicode'] );
 199          $g2Uni = self::xpathEscape( ( string ) $glyph2['unicode'] );
 200  
 201          // Search for kerning pairs
 202          $pair = $this->fonts[$fontPath]->xpath( 
 203              "svg:hkern[( @g1=$g1Name and @g2=$g2Name )
 204                  or
 205               ( @u1=$g1Uni and @g2=$g2Uni )]"
 206          );
 207  
 208          // If we found anything return it
 209          if ( count( $pair ) )
 210          {
 211              if ( !in_array( $pair[0], $this->usedKerns ) )
 212              {
 213                  $this->usedKerns[$fontPath][] = $pair[0];
 214              }
 215  
 216              return ( int ) $pair[0]['k'];
 217          }
 218          else
 219          {
 220              return 0;
 221          }
 222      }
 223  
 224      /**
 225       * Calculates the width of $string in the current font in Em's.
 226       *
 227       * @param string $fontPath
 228       * @param string $string
 229       * @return float
 230       */
 231      public function calculateStringWidth( $fontPath, $string )
 232      {
 233          // Ensure font is properly initilized
 234          $fontPath = $this->initializeFont( $fontPath );
 235  
 236          $strlen = strlen( $string );
 237          $prevCharInfo = null;
 238          $length = 0;
 239          // @TODO: Add UTF-8 support here - iterating over the bytes does not
 240          // really help.
 241          for ( $i = 0; $i < $strlen; ++$i )
 242          {
 243              // Find the font information for the character
 244              $charInfo = $this->getGlyph( $fontPath, $string[$i] );
 245  
 246              // Handle missing glyphs
 247              if ( $charInfo === false )
 248              {
 249                  $prevCharInfo = null;
 250                  $length += .5 * $this->unitsPerEm[$fontPath];
 251                  continue;
 252              }
 253  
 254              // Add the horizontal advance for the character to the length
 255              $length += (float) $charInfo['horiz-adv-x'];
 256  
 257              // If we are not the first character, look for kerning pairs
 258              if ( $prevCharInfo !== null )
 259              {
 260                  // Apply kerning (if any)
 261                  $length -= $this->getKerning( $fontPath, $prevCharInfo, $charInfo );
 262              }
 263  
 264              $prevCharInfo = clone $charInfo;
 265          }
 266  
 267          // Divide by _unitsPerEm to get the length in Em
 268          return (float) $length / $this->unitsPerEm[$fontPath];
 269      }
 270  
 271      /**
 272       * Add font definitions to SVG document
 273       *
 274       * Add the SVG font definition paths for all used glyphs and kernings to
 275       * the given SVG document.
 276       * 
 277       * @param DOMDocument $document 
 278       * @return void
 279       */
 280      public function addFontToDocument( DOMDocument $document )
 281      {
 282          $defs = $document->getElementsByTagName( 'defs' )->item( 0 );
 283  
 284          $fontNr = 0;
 285          foreach ( $this->fonts as $path => $definition )
 286          {
 287              // Just import complete font for now.
 288              // @TODO: Only import used characters.
 289              $font = dom_import_simplexml( $definition );
 290              $font = $document->importNode( $font, true );
 291              $font->setAttribute( 'id', 'Font' . ++$fontNr );
 292  
 293              $defs->appendChild( $font );
 294          }
 295      }
 296  }
 297  
 298  ?>


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