/library/ezc/Graph/src/driver/svg_font.php
PHP | 298 lines | 119 code | 36 blank | 143 comment | 11 complexity | 3e83d2ad6c08ee608d9d1de81234f991 MD5 | raw file
- <?php
- /**
- * File containing the ezcGraphSVGDriver class
- *
- * @package Graph
- * @version 1.5
- * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
- * @author Freddie Witherden
- * @license http://ez.no/licenses/new_bsd New BSD License
- */
- /**
- * Helper class, offering requrired calculation basics and font metrics to use
- * SVG fonts with the SVG driver.
- *
- * You may convert any ttf font into a SVG font using the `ttf2svg` bianry from
- * the batik package. Depending on the distribution it may only be available as
- * `batik-ttf2svg-<version>`.
- *
- * Usage:
- * <code>
- * $font = new ezcGraphSvgFont();
- * var_dump(
- * $font->calculateStringWidth( '../tests/data/font.svg', 'Just a test string.' ),
- * $font->calculateStringWidth( '../tests/data/font2.svg', 'Just a test string.' )
- * );
- * </code>
- *
- * @version 1.5
- * @package Graph
- * @mainclass
- */
- class ezcGraphSvgFont
- {
- /**
- * Units per EM
- *
- * @var float
- */
- protected $unitsPerEm;
- /**
- * Used glyphs
- *
- * @var array
- */
- protected $usedGlyphs = array();
- /**
- * Cache for glyph size to save XPath lookups.
- *
- * @var array
- */
- protected $glyphCache = array();
- /**
- * Used kernings
- *
- * @var array
- */
- protected $usedKerns = array();
- /**
- * Path to font
- *
- * @var string
- */
- protected $fonts = array();
- /**
- * Initialize SVG font
- *
- * Loads the SVG font $filename. This should be the path to the file
- * generated by ttf2svg.
- *
- * Returns the (normlized) name of the initilized font.
- *
- * @param string $fontPath
- * @return string
- */
- protected function initializeFont( $fontPath )
- {
- if ( isset( $this->fonts[$fontPath] ) )
- {
- return $fontPath;
- }
- // Check for existance of font file
- if ( !is_file( $fontPath ) || !is_readable( $fontPath ) )
- {
- throw new ezcBaseFileNotFoundException( $fontPath );
- }
- $this->fonts[$fontPath] = simplexml_load_file( $fontPath )->defs->font;
- // SimpleXML requires us to register a namespace for XPath to work
- $this->fonts[$fontPath]->registerXPathNamespace( 'svg', 'http://www.w3.org/2000/svg' );
- // Extract the number of units per Em
- $this->unitsPerEm[$fontPath] = (int) $this->fonts[$fontPath]->{'font-face'}['units-per-em'];
- return $fontPath;
- }
- /**
- * Get name of font
- *
- * Get the name of the given font, by extracting its font family from the
- * SVG font file.
- *
- * @param string $fontPath
- * @return string
- */
- public static function getFontName( $fontPath )
- {
- $font = simplexml_load_file( $fontPath )->defs->font;
- // SimpleXML requires us to register a namespace for XPath to work
- $font->registerXPathNamespace( 'svg', 'http://www.w3.org/2000/svg' );
- // Extract the font family name
- return (string) $font->{'font-face'}['font-family'];
- }
- /**
- * XPath has no standard means of escaping ' and ", with the only solution
- * being to delimit your string with the opposite type of quote. ( And if
- * your string contains both concat( ) it ).
- *
- * This method will correctly delimit $char with the appropriate quote type
- * so that it can be used in an XPath expression.
- *
- * @param string $char
- * @return string
- */
- protected static function xpathEscape( $char )
- {
- return "'" . str_replace(
- array( '\'', '\\' ),
- array( '\\\'', '\\\\' ),
- $char ) . "'";
- }
- /**
- * Returns the <glyph> associated with $char.
- *
- * @param string $fontPath
- * @param string $char
- * @return float
- */
- protected function getGlyph( $fontPath, $char )
- {
- // Check if glyphwidth has already been calculated.
- if ( isset( $this->glyphCache[$fontPath][$char] ) )
- {
- return $this->glyphCache[$fontPath][$char];
- }
- $matches = $this->fonts[$fontPath]->xpath(
- $query = "glyph[@unicode=" . self::xpathEscape( $char ) . "]"
- );
- if ( count( $matches ) === 0 )
- {
- // Just ignore missing glyphs. The client will still render them
- // using a default font. We try to estimate some width by using a
- // common other character.
- return $this->glyphCache[$fontPath][$char] =
- ( $char === 'o' ? false : $this->getGlyph( $fontPath, 'o' ) );
- }
- $glyph = $matches[0];
- if ( !in_array( $glyph, $this->usedGlyphs ) )
- {
- $this->usedGlyphs[$fontPath][] = $glyph;
- }
- // There should only ever be one match
- return $this->glyphCache[$fontPath][$char] = $glyph;
- }
- /**
- * Returns the amount of kerning to apply for glyphs $g1 and $g2. If no
- * valid kerning pair can be found 0 is returned.
- *
- * @param string $fontPath
- * @param SimpleXMLElement $g1
- * @param SimpleXMLElement $g2
- * @return int
- */
- public function getKerning( $fontPath, SimpleXMLElement $glyph1, SimpleXMLElement $glyph2 )
- {
- // Get the glyph names
- $g1Name = self::xpathEscape( ( string ) $glyph1['glyph-name'] );
- $g2Name = self::xpathEscape( ( string ) $glyph2['glyph-name'] );
- // Get the unicode character names
- $g1Uni = self::xpathEscape( ( string ) $glyph1['unicode'] );
- $g2Uni = self::xpathEscape( ( string ) $glyph2['unicode'] );
- // Search for kerning pairs
- $pair = $this->fonts[$fontPath]->xpath(
- "svg:hkern[( @g1=$g1Name and @g2=$g2Name )
- or
- ( @u1=$g1Uni and @g2=$g2Uni )]"
- );
- // If we found anything return it
- if ( count( $pair ) )
- {
- if ( !in_array( $pair[0], $this->usedKerns ) )
- {
- $this->usedKerns[$fontPath][] = $pair[0];
- }
- return ( int ) $pair[0]['k'];
- }
- else
- {
- return 0;
- }
- }
- /**
- * Calculates the width of $string in the current font in Em's.
- *
- * @param string $fontPath
- * @param string $string
- * @return float
- */
- public function calculateStringWidth( $fontPath, $string )
- {
- // Ensure font is properly initilized
- $fontPath = $this->initializeFont( $fontPath );
- $strlen = strlen( $string );
- $prevCharInfo = null;
- $length = 0;
- // @TODO: Add UTF-8 support here - iterating over the bytes does not
- // really help.
- for ( $i = 0; $i < $strlen; ++$i )
- {
- // Find the font information for the character
- $charInfo = $this->getGlyph( $fontPath, $string[$i] );
- // Handle missing glyphs
- if ( $charInfo === false )
- {
- $prevCharInfo = null;
- $length += .5 * $this->unitsPerEm[$fontPath];
- continue;
- }
- // Add the horizontal advance for the character to the length
- $length += (float) $charInfo['horiz-adv-x'];
- // If we are not the first character, look for kerning pairs
- if ( $prevCharInfo !== null )
- {
- // Apply kerning (if any)
- $length -= $this->getKerning( $fontPath, $prevCharInfo, $charInfo );
- }
- $prevCharInfo = clone $charInfo;
- }
- // Divide by _unitsPerEm to get the length in Em
- return (float) $length / $this->unitsPerEm[$fontPath];
- }
- /**
- * Add font definitions to SVG document
- *
- * Add the SVG font definition paths for all used glyphs and kernings to
- * the given SVG document.
- *
- * @param DOMDocument $document
- * @return void
- */
- public function addFontToDocument( DOMDocument $document )
- {
- $defs = $document->getElementsByTagName( 'defs' )->item( 0 );
- $fontNr = 0;
- foreach ( $this->fonts as $path => $definition )
- {
- // Just import complete font for now.
- // @TODO: Only import used characters.
- $font = dom_import_simplexml( $definition );
- $font = $document->importNode( $font, true );
- $font->setAttribute( 'id', 'Font' . ++$fontNr );
- $defs->appendChild( $font );
- }
- }
- }
- ?>