PageRenderTime 42ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/library/ezc/Graph/src/driver/svg_font.php

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