1<?php 2/** 3 * @package dompdf 4 * @link http://dompdf.github.com/ 5 * @author Benj Carson <benjcarson@digitaljunkies.ca> 6 * @author Helmut Tischer <htischer@weihenstephan.org> 7 * @author Fabien Ménager <fabien.menager@gmail.com> 8 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License 9 */ 10 11namespace Dompdf; 12 13use FontLib\Font; 14 15/** 16 * The font metrics class 17 * 18 * This class provides information about fonts and text. It can resolve 19 * font names into actual installed font files, as well as determine the 20 * size of text in a particular font and size. 21 * 22 * @static 23 * @package dompdf 24 */ 25class FontMetrics 26{ 27 /** 28 * Name of the font cache file 29 * 30 * This file must be writable by the webserver process only to update it 31 * with save_font_families() after adding the .afm file references of a new font family 32 * with FontMetrics::saveFontFamilies(). 33 * This is typically done only from command line with load_font.php on converting 34 * ttf fonts to ufm with php-font-lib. 35 */ 36 const CACHE_FILE = "dompdf_font_family_cache.php"; 37 38 /** 39 * @var Canvas 40 * @deprecated 41 */ 42 protected $pdf; 43 44 /** 45 * Underlying {@link Canvas} object to perform text size calculations 46 * 47 * @var Canvas 48 */ 49 protected $canvas; 50 51 /** 52 * Array of font family names to font files 53 * 54 * Usually cached by the {@link load_font.php} script 55 * 56 * @var array 57 */ 58 protected $fontLookup = array(); 59 60 /** 61 * @var Options 62 */ 63 private $options; 64 65 /** 66 * Class initialization 67 */ 68 public function __construct(Canvas $canvas, Options $options) 69 { 70 $this->setCanvas($canvas); 71 $this->setOptions($options); 72 $this->loadFontFamilies(); 73 } 74 75 /** 76 * @deprecated 77 */ 78 public function save_font_families() 79 { 80 $this->saveFontFamilies(); 81 } 82 83 /** 84 * Saves the stored font family cache 85 * 86 * The name and location of the cache file are determined by {@link 87 * FontMetrics::CACHE_FILE}. This file should be writable by the 88 * webserver process. 89 * 90 * @see FontMetrics::loadFontFamilies() 91 */ 92 public function saveFontFamilies() 93 { 94 // replace the path to the DOMPDF font directories with the corresponding constants (allows for more portability) 95 $cacheData = sprintf("<?php return array (%s", PHP_EOL); 96 foreach ($this->fontLookup as $family => $variants) { 97 $cacheData .= sprintf(" '%s' => array(%s", addslashes($family), PHP_EOL); 98 foreach ($variants as $variant => $path) { 99 $path = sprintf("'%s'", $path); 100 $path = str_replace('\'' . $this->getOptions()->getFontDir() , '$fontDir . \'' , $path); 101 $path = str_replace('\'' . $this->getOptions()->getRootDir() , '$rootDir . \'' , $path); 102 $cacheData .= sprintf(" '%s' => %s,%s", $variant, $path, PHP_EOL); 103 } 104 $cacheData .= sprintf(" ),%s", PHP_EOL); 105 } 106 $cacheData .= ") ?>"; 107 file_put_contents($this->getCacheFile(), $cacheData); 108 } 109 110 /** 111 * @deprecated 112 */ 113 public function load_font_families() 114 { 115 $this->loadFontFamilies(); 116 } 117 118 /** 119 * Loads the stored font family cache 120 * 121 * @see FontMetrics::saveFontFamilies() 122 */ 123 public function loadFontFamilies() 124 { 125 $fontDir = $this->getOptions()->getFontDir(); 126 $rootDir = $this->getOptions()->getRootDir(); 127 128 // FIXME: temporarily define constants for cache files <= v0.6.2 129 if (!defined("DOMPDF_DIR")) { define("DOMPDF_DIR", $rootDir); } 130 if (!defined("DOMPDF_FONT_DIR")) { define("DOMPDF_FONT_DIR", $fontDir); } 131 132 $file = $rootDir . "/lib/fonts/dompdf_font_family_cache.dist.php"; 133 $distFonts = require $file; 134 135 if (!is_readable($this->getCacheFile())) { 136 $this->fontLookup = $distFonts; 137 return; 138 } 139 140 $cacheData = require $this->getCacheFile(); 141 142 $this->fontLookup = array(); 143 if (is_array($this->fontLookup)) { 144 foreach ($cacheData as $key => $value) { 145 $this->fontLookup[stripslashes($key)] = $value; 146 } 147 } 148 149 // Merge provided fonts 150 $this->fontLookup += $distFonts; 151 } 152 153 /** 154 * @param array $style 155 * @param string $remote_file 156 * @param resource $context 157 * @return bool 158 * @deprecated 159 */ 160 public function register_font($style, $remote_file, $context = null) 161 { 162 return $this->registerFont($style, $remote_file); 163 } 164 165 /** 166 * @param array $style 167 * @param string $remoteFile 168 * @param resource $context 169 * @return bool 170 */ 171 public function registerFont($style, $remoteFile, $context = null) 172 { 173 $fontname = mb_strtolower($style["family"]); 174 $families = $this->getFontFamilies(); 175 176 $entry = array(); 177 if (isset($families[$fontname])) { 178 $entry = $families[$fontname]; 179 } 180 181 $styleString = $this->getType("{$style['weight']} {$style['style']}"); 182 if (isset($entry[$styleString])) { 183 return true; 184 } 185 186 $fontDir = $this->getOptions()->getFontDir(); 187 $remoteHash = md5($remoteFile); 188 $localFile = $fontDir . DIRECTORY_SEPARATOR . $remoteHash; 189 190 $cacheEntry = $localFile; 191 $localFile .= ".".strtolower(pathinfo(parse_url($remoteFile, PHP_URL_PATH),PATHINFO_EXTENSION)); 192 193 $entry[$styleString] = $cacheEntry; 194 195 // Download the remote file 196 list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context); 197 if (false === $remoteFileContent) { 198 return false; 199 } 200 201 $localTempFile = @tempnam($this->options->get("tempDir"), "dompdf-font-"); 202 file_put_contents($localTempFile, $remoteFileContent); 203 204 $font = Font::load($localTempFile); 205 206 if (!$font) { 207 unlink($localTempFile); 208 return false; 209 } 210 211 $font->parse(); 212 $font->saveAdobeFontMetrics("$cacheEntry.ufm"); 213 $font->close(); 214 215 unlink($localTempFile); 216 217 if ( !file_exists("$cacheEntry.ufm") ) { 218 return false; 219 } 220 221 // Save the changes 222 file_put_contents($localFile, $remoteFileContent); 223 224 if ( !file_exists($localFile) ) { 225 unlink("$cacheEntry.ufm"); 226 return false; 227 } 228 229 $this->setFontFamily($fontname, $entry); 230 $this->saveFontFamilies(); 231 232 return true; 233 } 234 235 /** 236 * @param $text 237 * @param $font 238 * @param $size 239 * @param float $word_spacing 240 * @param float $char_spacing 241 * @return float 242 * @deprecated 243 */ 244 public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0) 245 { 246 //return self::$_pdf->get_text_width($text, $font, $size, $word_spacing, $char_spacing); 247 return $this->getTextWidth($text, $font, $size, $word_spacing, $char_spacing); 248 } 249 250 /** 251 * Calculates text size, in points 252 * 253 * @param string $text the text to be sized 254 * @param string $font the desired font 255 * @param float $size the desired font size 256 * @param float $wordSpacing 257 * @param float $charSpacing 258 * 259 * @internal param float $spacing word spacing, if any 260 * @return float 261 */ 262 public function getTextWidth($text, $font, $size, $wordSpacing = 0.0, $charSpacing = 0.0) 263 { 264 // @todo Make sure this cache is efficient before enabling it 265 static $cache = array(); 266 267 if ($text === "") { 268 return 0; 269 } 270 271 // Don't cache long strings 272 $useCache = !isset($text[50]); // Faster than strlen 273 274 $key = "$font/$size/$wordSpacing/$charSpacing"; 275 276 if ($useCache && isset($cache[$key][$text])) { 277 return $cache[$key]["$text"]; 278 } 279 280 $width = $this->getCanvas()->get_text_width($text, $font, $size, $wordSpacing, $charSpacing); 281 282 if ($useCache) { 283 $cache[$key][$text] = $width; 284 } 285 286 return $width; 287 } 288 289 /** 290 * @param $font 291 * @param $size 292 * @return float 293 * @deprecated 294 */ 295 public function get_font_height($font, $size) 296 { 297 return $this->getFontHeight($font, $size); 298 } 299 300 /** 301 * Calculates font height 302 * 303 * @param string $font 304 * @param float $size 305 * 306 * @return float 307 */ 308 public function getFontHeight($font, $size) 309 { 310 return $this->getCanvas()->get_font_height($font, $size); 311 } 312 313 /** 314 * @param $family_raw 315 * @param string $subtype_raw 316 * @return string 317 * @deprecated 318 */ 319 public function get_font($family_raw, $subtype_raw = "normal") 320 { 321 return $this->getFont($family_raw, $subtype_raw); 322 } 323 324 /** 325 * Resolves a font family & subtype into an actual font file 326 * Subtype can be one of 'normal', 'bold', 'italic' or 'bold_italic'. If 327 * the particular font family has no suitable font file, the default font 328 * ({@link Options::defaultFont}) is used. The font file returned 329 * is the absolute pathname to the font file on the system. 330 * 331 * @param string $familyRaw 332 * @param string $subtypeRaw 333 * 334 * @return string 335 */ 336 public function getFont($familyRaw, $subtypeRaw = "normal") 337 { 338 static $cache = array(); 339 340 if (isset($cache[$familyRaw][$subtypeRaw])) { 341 return $cache[$familyRaw][$subtypeRaw]; 342 } 343 344 /* Allow calling for various fonts in search path. Therefore not immediately 345 * return replacement on non match. 346 * Only when called with NULL try replacement. 347 * When this is also missing there is really trouble. 348 * If only the subtype fails, nevertheless return failure. 349 * Only on checking the fallback font, check various subtypes on same font. 350 */ 351 352 $subtype = strtolower($subtypeRaw); 353 354 if ($familyRaw) { 355 $family = str_replace(array("'", '"'), "", strtolower($familyRaw)); 356 357 if (isset($this->fontLookup[$family][$subtype])) { 358 return $cache[$familyRaw][$subtypeRaw] = $this->fontLookup[$family][$subtype]; 359 } 360 361 return null; 362 } 363 364 $family = "serif"; 365 366 if (isset($this->fontLookup[$family][$subtype])) { 367 return $cache[$familyRaw][$subtypeRaw] = $this->fontLookup[$family][$subtype]; 368 } 369 370 if (!isset($this->fontLookup[$family])) { 371 return null; 372 } 373 374 $family = $this->fontLookup[$family]; 375 376 foreach ($family as $sub => $font) { 377 if (strpos($subtype, $sub) !== false) { 378 return $cache[$familyRaw][$subtypeRaw] = $font; 379 } 380 } 381 382 if ($subtype !== "normal") { 383 foreach ($family as $sub => $font) { 384 if ($sub !== "normal") { 385 return $cache[$familyRaw][$subtypeRaw] = $font; 386 } 387 } 388 } 389 390 $subtype = "normal"; 391 392 if (isset($family[$subtype])) { 393 return $cache[$familyRaw][$subtypeRaw] = $family[$subtype]; 394 } 395 396 return null; 397 } 398 399 /** 400 * @param $family 401 * @return null|string 402 * @deprecated 403 */ 404 public function get_family($family) 405 { 406 return $this->getFamily($family); 407 } 408 409 /** 410 * @param string $family 411 * @return null|string 412 */ 413 public function getFamily($family) 414 { 415 $family = str_replace(array("'", '"'), "", mb_strtolower($family)); 416 417 if (isset($this->fontLookup[$family])) { 418 return $this->fontLookup[$family]; 419 } 420 421 return null; 422 } 423 424 /** 425 * @param $type 426 * @return string 427 * @deprecated 428 */ 429 public function get_type($type) 430 { 431 return $this->getType($type); 432 } 433 434 /** 435 * @param string $type 436 * @return string 437 */ 438 public function getType($type) 439 { 440 if (preg_match("/bold/i", $type)) { 441 if (preg_match("/italic|oblique/i", $type)) { 442 $type = "bold_italic"; 443 } else { 444 $type = "bold"; 445 } 446 } elseif (preg_match("/italic|oblique/i", $type)) { 447 $type = "italic"; 448 } else { 449 $type = "normal"; 450 } 451 452 return $type; 453 } 454 455 /** 456 * @return array 457 * @deprecated 458 */ 459 public function get_font_families() 460 { 461 return $this->getFontFamilies(); 462 } 463 464 /** 465 * Returns the current font lookup table 466 * 467 * @return array 468 */ 469 public function getFontFamilies() 470 { 471 return $this->fontLookup; 472 } 473 474 /** 475 * @param string $fontname 476 * @param mixed $entry 477 * @deprecated 478 */ 479 public function set_font_family($fontname, $entry) 480 { 481 $this->setFontFamily($fontname, $entry); 482 } 483 484 /** 485 * @param string $fontname 486 * @param mixed $entry 487 */ 488 public function setFontFamily($fontname, $entry) 489 { 490 $this->fontLookup[mb_strtolower($fontname)] = $entry; 491 } 492 493 /** 494 * @return string 495 */ 496 public function getCacheFile() 497 { 498 return $this->getOptions()->getFontDir() . DIRECTORY_SEPARATOR . self::CACHE_FILE; 499 } 500 501 /** 502 * @param Options $options 503 * @return $this 504 */ 505 public function setOptions(Options $options) 506 { 507 $this->options = $options; 508 return $this; 509 } 510 511 /** 512 * @return Options 513 */ 514 public function getOptions() 515 { 516 return $this->options; 517 } 518 519 /** 520 * @param Canvas $canvas 521 * @return $this 522 */ 523 public function setCanvas(Canvas $canvas) 524 { 525 $this->canvas = $canvas; 526 // Still write deprecated pdf for now. It might be used by a parent class. 527 $this->pdf = $canvas; 528 return $this; 529 } 530 531 /** 532 * @return Canvas 533 */ 534 public function getCanvas() 535 { 536 return $this->canvas; 537 } 538}