299 lines
8.3 KiB
PHP
299 lines
8.3 KiB
PHP
<?php
|
|
/**
|
|
* File containing the ezcGraphSVGDriver class
|
|
*
|
|
* @package Graph
|
|
* @version 1.4.3
|
|
* @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.4.3
|
|
* @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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
?>
|