| [ 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 * @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 ?>
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 |