PluginProbe
MBE eShip / trunk
MBE eShip vtrunk
2.8.1 trunk 1.0.0 1.1.0 1.1.3 1.2.1 1.2.2 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.6.0 1.7.0 1.7.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1.0 2.1.1 2.1.2 2.2.1 All 37 releases
mail-boxes-etc / lib / dompdf / src / FontMetrics.php

FontMetrics.php in MBE eShip trunk, at lib/dompdf/src/FontMetrics.php

636 lines 17.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package dompdf
4 * @link https://github.com/dompdf/dompdf
5 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
6 */
7 namespace Dompdf;
8
9 use FontLib\Font;
10
11 /**
12 * The font metrics class
13 *
14 * This class provides information about fonts and text. It can resolve
15 * font names into actual installed font files, as well as determine the
16 * size of text in a particular font and size.
17 *
18 * @static
19 * @package dompdf
20 */
21 class FontMetrics
22 {
23 /**
24 * Name of the user font families file
25 *
26 * This file must be writable by the webserver process only to update it
27 * with save_font_families() after adding the .afm file references of a new font family
28 * with FontMetrics::saveFontFamilies().
29 * This is typically done only from command line with load_font.php on converting
30 * ttf fonts to ufm with php-font-lib.
31 */
32 const USER_FONTS_FILE = "installed-fonts.json";
33
34
35 /**
36 * Underlying {@link Canvas} object to perform text size calculations
37 *
38 * @var Canvas
39 */
40 protected $canvas;
41
42 /**
43 * Array of bundled font family names to variants
44 *
45 * @var array
46 */
47 protected $bundledFonts = [];
48
49 /**
50 * Array of user defined font family names to variants
51 *
52 * @var array
53 */
54 protected $userFonts = [];
55
56 /**
57 * combined list of all font families with absolute paths
58 *
59 * @var array
60 */
61 protected $fontFamilies;
62
63 /**
64 * @var Options
65 */
66 private $options;
67
68 /**
69 * Class initialization
70 */
71 public function __construct(Canvas $canvas, Options $options)
72 {
73 $this->setCanvas($canvas);
74 $this->setOptions($options);
75 $this->loadFontFamilies();
76 }
77
78 /**
79 * @deprecated
80 */
81 public function save_font_families()
82 {
83 $this->saveFontFamilies();
84 }
85
86 /**
87 * Saves the stored font family cache
88 *
89 * The name and location of the cache file are determined by {@link
90 * FontMetrics::USER_FONTS_FILE}. This file should be writable by the
91 * webserver process.
92 *
93 * @see FontMetrics::loadFontFamilies()
94 */
95 public function saveFontFamilies()
96 {
97 file_put_contents($this->getUserFontsFilePath(), json_encode($this->userFonts, JSON_PRETTY_PRINT));
98 }
99
100 /**
101 * @deprecated
102 */
103 public function load_font_families()
104 {
105 $this->loadFontFamilies();
106 }
107
108 /**
109 * Loads the stored font family cache
110 *
111 * @see FontMetrics::saveFontFamilies()
112 */
113 public function loadFontFamilies()
114 {
115 $file = $this->options->getRootDir() . "/lib/fonts/installed-fonts.dist.json";
116 $this->bundledFonts = json_decode(file_get_contents($file), true);
117
118 if (is_readable($this->getUserFontsFilePath())) {
119 $this->userFonts = json_decode(file_get_contents($this->getUserFontsFilePath()), true);
120 } else {
121 $this->loadFontFamiliesLegacy();
122 }
123 }
124
125 private function loadFontFamiliesLegacy()
126 {
127 $legacyCacheFile = $this->options->getFontDir() . '/dompdf_font_family_cache.php';
128 if (is_readable($legacyCacheFile)) {
129 $fontDir = $this->options->getFontDir();
130 $rootDir = $this->options->getRootDir();
131
132 if (!defined("DOMPDF_DIR")) { define("DOMPDF_DIR", $rootDir); }
133 if (!defined("DOMPDF_FONT_DIR")) { define("DOMPDF_FONT_DIR", $fontDir); }
134
135 $cacheDataClosure = require $legacyCacheFile;
136 $cacheData = is_array($cacheDataClosure) ? $cacheDataClosure : $cacheDataClosure($fontDir, $rootDir);
137 if (is_array($cacheData)) {
138 foreach ($cacheData as $family => $variants) {
139 if (!isset($this->bundledFonts[$family]) && is_array($variants)) {
140 foreach ($variants as $variant => $variantPath) {
141 $variantName = basename($variantPath);
142 $variantDir = dirname($variantPath);
143 if ($variantDir == $fontDir) {
144 $this->userFonts[$family][$variant] = $variantName;
145 } else {
146 $this->userFonts[$family][$variant] = $variantPath;
147 }
148 }
149 }
150 }
151 $this->saveFontFamilies();
152 }
153 }
154 }
155
156 /**
157 * @param array $style
158 * @param string $remote_file
159 * @param resource $context
160 * @return bool
161 * @deprecated
162 */
163 public function register_font($style, $remote_file, $context = null)
164 {
165 return $this->registerFont($style, $remote_file);
166 }
167
168 /**
169 * @param array $style
170 * @param string $remoteFile
171 * @param resource $context
172 * @return bool
173 */
174 public function registerFont($style, $remoteFile, $context = null)
175 {
176 $fontname = mb_strtolower($style["family"]);
177 $families = $this->getFontFamilies();
178
179 $entry = [];
180 if (isset($families[$fontname])) {
181 $entry = $families[$fontname];
182 }
183
184 $styleString = $this->getType("{$style['weight']} {$style['style']}");
185
186 $remoteHash = md5($remoteFile);
187
188 $prefix = $fontname . "_" . $styleString;
189 $prefix = trim($prefix, "-");
190 if (function_exists('iconv')) {
191 $prefix = @iconv('utf-8', 'us-ascii//TRANSLIT', $prefix);
192 }
193 $prefix_encoding = mb_detect_encoding($prefix, mb_detect_order(), true);
194 $substchar = mb_substitute_character();
195 mb_substitute_character(0x005F);
196 $prefix = mb_convert_encoding($prefix, "ISO-8859-1", $prefix_encoding);
197 mb_substitute_character($substchar);
198 $prefix = preg_replace("[\W]", "_", $prefix);
199 $prefix = preg_replace("/[^-_\w]+/", "", $prefix);
200
201 $localFile = $prefix . "_" . $remoteHash;
202 $localFilePath = $this->getOptions()->getFontDir() . "/" . $localFile;
203
204 if (isset($entry[$styleString]) && $localFilePath == $entry[$styleString]) {
205 return true;
206 }
207
208
209 $entry[$styleString] = $localFile;
210
211 // Download the remote file
212 [$protocol] = Helpers::explode_url($remoteFile);
213 $allowed_protocols = $this->options->getAllowedProtocols();
214 if (!array_key_exists($protocol, $allowed_protocols)) {
215 Helpers::record_warnings(E_USER_WARNING, "Permission denied on $remoteFile. The communication protocol is not supported.", __FILE__, __LINE__);
216 return false;
217 }
218
219 foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
220 [$result, $message] = $rule($remoteFile);
221 if ($result !== true) {
222 Helpers::record_warnings(E_USER_WARNING, "Error loading $remoteFile: $message", __FILE__, __LINE__);
223 return false;
224 }
225 }
226
227 list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context);
228 if ($remoteFileContent === null) {
229 return false;
230 }
231
232 $localTempFile = @tempnam($this->options->get("tempDir"), "dompdf-font-");
233 file_put_contents($localTempFile, $remoteFileContent);
234
235 $font = Font::load($localTempFile);
236
237 if (!$font) {
238 unlink($localTempFile);
239 return false;
240 }
241
242 $font->parse();
243 $font->saveAdobeFontMetrics("$localFilePath.ufm");
244 $font->close();
245
246 unlink($localTempFile);
247
248 if ( !file_exists("$localFilePath.ufm") ) {
249 return false;
250 }
251
252 $fontExtension = ".ttf";
253 switch ($font->getFontType()) {
254 case "TrueType":
255 default:
256 $fontExtension = ".ttf";
257 break;
258 }
259
260 // Save the changes
261 file_put_contents($localFilePath.$fontExtension, $remoteFileContent);
262
263 if ( !file_exists($localFilePath.$fontExtension) ) {
264 unlink("$localFilePath.ufm");
265 return false;
266 }
267
268 $this->setFontFamily($fontname, $entry);
269
270 return true;
271 }
272
273 /**
274 * @param $text
275 * @param $font
276 * @param $size
277 * @param float $word_spacing
278 * @param float $char_spacing
279 * @return float
280 * @deprecated
281 */
282 public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
283 {
284 //return self::$_pdf->get_text_width($text, $font, $size, $word_spacing, $char_spacing);
285 return $this->getTextWidth($text, $font, $size, $word_spacing, $char_spacing);
286 }
287
288 /**
289 * Calculates text size, in points
290 *
291 * @param string $text The text to be sized
292 * @param string $font The font file to use
293 * @param float $size The font size, in points
294 * @param float $wordSpacing Word spacing, if any
295 * @param float $charSpacing Char spacing, if any
296 *
297 * @return float
298 */
299 public function getTextWidth(string $text, $font, float $size, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
300 {
301 // @todo Make sure this cache is efficient before enabling it
302 static $cache = [];
303
304 if ($text === "") {
305 return 0;
306 }
307
308 // Don't cache long strings
309 $useCache = !isset($text[50]); // Faster than strlen
310
311 // Text-size calculations depend on the canvas used. Make sure to not
312 // return wrong values when switching canvas backends
313 $canvasClass = get_class($this->canvas);
314 $key = "$canvasClass/$font/$size/$wordSpacing/$charSpacing";
315
316 if ($useCache && isset($cache[$key][$text])) {
317 return $cache[$key][$text];
318 }
319
320 $width = $this->canvas->get_text_width($text, $font, $size, $wordSpacing, $charSpacing);
321
322 if ($useCache) {
323 $cache[$key][$text] = $width;
324 }
325
326 return $width;
327 }
328
329 /**
330 * @param $font
331 * @param $size
332 * @return float
333 * @deprecated
334 */
335 public function get_font_height($font, $size)
336 {
337 return $this->getFontHeight($font, $size);
338 }
339
340 /**
341 * Calculates font height, in points
342 *
343 * @param string $font The font file to use
344 * @param float $size The font size, in points
345 *
346 * @return float
347 */
348 public function getFontHeight($font, float $size): float
349 {
350 return $this->canvas->get_font_height($font, $size);
351 }
352
353 /**
354 * Calculates font baseline, in points
355 *
356 * @param string $font The font file to use
357 * @param float $size The font size, in points
358 *
359 * @return float
360 */
361 public function getFontBaseline($font, float $size): float
362 {
363 return $this->canvas->get_font_baseline($font, $size);
364 }
365
366 /**
367 * @param $family_raw
368 * @param string $subtype_raw
369 * @return string
370 * @deprecated
371 */
372 public function get_font($family_raw, $subtype_raw = "normal")
373 {
374 return $this->getFont($family_raw, $subtype_raw);
375 }
376
377 /**
378 * Resolves a font family & subtype into an actual font file
379 * Subtype can be one of 'normal', 'bold', 'italic' or 'bold_italic'. If
380 * the particular font family has no suitable font file, the default font
381 * ({@link Options::defaultFont}) is used. The font file returned
382 * is the absolute pathname to the font file on the system.
383 *
384 * @param string|null $familyRaw
385 * @param string $subtypeRaw
386 *
387 * @return string|null
388 */
389 public function getFont($familyRaw, $subtypeRaw = "normal")
390 {
391 static $cache = [];
392
393 if (isset($cache[$familyRaw][$subtypeRaw])) {
394 return $cache[$familyRaw][$subtypeRaw];
395 }
396
397 /* Allow calling for various fonts in search path. Therefore not immediately
398 * return replacement on non match.
399 * Only when called with NULL try replacement.
400 * When this is also missing there is really trouble.
401 * If only the subtype fails, nevertheless return failure.
402 * Only on checking the fallback font, check various subtypes on same font.
403 */
404
405 $subtype = strtolower($subtypeRaw);
406
407 $families = $this->getFontFamilies();
408 if ($familyRaw) {
409 $family = str_replace(["'", '"'], "", strtolower($familyRaw));
410
411 if (isset($families[$family][$subtype])) {
412 return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype];
413 }
414
415 return null;
416 }
417
418 $fallback_families = [strtolower($this->options->getDefaultFont()), "serif"];
419 foreach ($fallback_families as $family) {
420 if (isset($families[$family][$subtype])) {
421 return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype];
422 }
423
424 if (!isset($families[$family])) {
425 continue;
426 }
427
428 $family = $families[$family];
429
430 foreach ($family as $sub => $font) {
431 if (strpos($subtype, $sub) !== false) {
432 return $cache[$familyRaw][$subtypeRaw] = $font;
433 }
434 }
435
436 if ($subtype !== "normal") {
437 foreach ($family as $sub => $font) {
438 if ($sub !== "normal") {
439 return $cache[$familyRaw][$subtypeRaw] = $font;
440 }
441 }
442 }
443
444 $subtype = "normal";
445
446 if (isset($family[$subtype])) {
447 return $cache[$familyRaw][$subtypeRaw] = $family[$subtype];
448 }
449 }
450
451 return null;
452 }
453
454 /**
455 * @param $family
456 * @return null|string
457 * @deprecated
458 */
459 public function get_family($family)
460 {
461 return $this->getFamily($family);
462 }
463
464 /**
465 * @param string $family
466 * @return null|string
467 */
468 public function getFamily($family)
469 {
470 $family = str_replace(["'", '"'], "", mb_strtolower($family));
471 $families = $this->getFontFamilies();
472
473 if (isset($families[$family])) {
474 return $families[$family];
475 }
476
477 return null;
478 }
479
480 /**
481 * @param $type
482 * @return string
483 * @deprecated
484 */
485 public function get_type($type)
486 {
487 return $this->getType($type);
488 }
489
490 /**
491 * @param string $type
492 * @return string
493 */
494 public function getType($type)
495 {
496 if (preg_match('/bold/i', $type)) {
497 $weight = 700;
498 } elseif (preg_match('/([1-9]00)/', $type, $match)) {
499 $weight = (int)$match[0];
500 } else {
501 $weight = 400;
502 }
503 $weight = $weight === 400 ? 'normal' : $weight;
504 $weight = $weight === 700 ? 'bold' : $weight;
505
506 $style = preg_match('/italic|oblique/i', $type) ? 'italic' : null;
507
508 if ($weight === 'normal' && $style !== null) {
509 return $style;
510 }
511
512 return $style === null
513 ? $weight
514 : $weight.'_'.$style;
515 }
516
517 /**
518 * @return array
519 * @deprecated
520 */
521 public function get_font_families()
522 {
523 return $this->getFontFamilies();
524 }
525
526 /**
527 * Returns the current font lookup table
528 *
529 * @return array
530 */
531 public function getFontFamilies()
532 {
533 if (!isset($this->fontFamilies)) {
534 $this->setFontFamilies();
535 }
536 return $this->fontFamilies;
537 }
538
539 /**
540 * Convert loaded fonts to font lookup table
541 *
542 * @return array
543 */
544 public function setFontFamilies()
545 {
546 $fontFamilies = [];
547 if (isset($this->bundledFonts) && is_array($this->bundledFonts)) {
548 foreach ($this->bundledFonts as $family => $variants) {
549 if (!isset($fontFamilies[$family])) {
550 $fontFamilies[$family] = array_map(function ($variant) {
551 return $this->getOptions()->getRootDir() . '/lib/fonts/' . $variant;
552 }, $variants);
553 }
554 }
555 }
556 if (isset($this->userFonts) && is_array($this->userFonts)) {
557 foreach ($this->userFonts as $family => $variants) {
558 $fontFamilies[$family] = array_map(function ($variant) {
559 $variantName = basename($variant);
560 if ($variantName === $variant) {
561 return $this->getOptions()->getFontDir() . '/' . $variant;
562 }
563 return $variant;
564 }, $variants);
565 }
566 }
567 $this->fontFamilies = $fontFamilies;
568 }
569
570 /**
571 * @param string $fontname
572 * @param mixed $entry
573 * @deprecated
574 */
575 public function set_font_family($fontname, $entry)
576 {
577 $this->setFontFamily($fontname, $entry);
578 }
579
580 /**
581 * @param string $fontname
582 * @param mixed $entry
583 */
584 public function setFontFamily($fontname, $entry)
585 {
586 $this->userFonts[mb_strtolower($fontname)] = $entry;
587 $this->saveFontFamilies();
588 unset($this->fontFamilies);
589 }
590
591 /**
592 * @return string
593 */
594 public function getUserFontsFilePath()
595 {
596 return $this->options->getFontDir() . '/' . self::USER_FONTS_FILE;
597 }
598
599 /**
600 * @param Options $options
601 * @return $this
602 */
603 public function setOptions(Options $options)
604 {
605 $this->options = $options;
606 unset($this->fontFamilies);
607 return $this;
608 }
609
610 /**
611 * @return Options
612 */
613 public function getOptions()
614 {
615 return $this->options;
616 }
617
618 /**
619 * @param Canvas $canvas
620 * @return $this
621 */
622 public function setCanvas(Canvas $canvas)
623 {
624 $this->canvas = $canvas;
625 return $this;
626 }
627
628 /**
629 * @return Canvas
630 */
631 public function getCanvas()
632 {
633 return $this->canvas;
634 }
635 }
636