PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Shared / Font.php

Font.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Shared/Font.php

726 lines 22.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TablePress\PhpOffice\PhpSpreadsheet\Shared;
4
5 use TablePress\PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
6 use TablePress\PhpOffice\PhpSpreadsheet\RichText\RichText;
7 use TablePress\PhpOffice\PhpSpreadsheet\Style\Alignment;
8 use TablePress\PhpOffice\PhpSpreadsheet\Style\Font as FontStyle;
9 use RecursiveDirectoryIterator;
10 use RecursiveIteratorIterator;
11
12 class Font
13 {
14 // Methods for resolving autosize value
15 const AUTOSIZE_METHOD_APPROX = 'approx';
16 const AUTOSIZE_METHOD_EXACT = 'exact';
17
18 private const AUTOSIZE_METHODS = [
19 self::AUTOSIZE_METHOD_APPROX,
20 self::AUTOSIZE_METHOD_EXACT,
21 ];
22
23 /** Character set codes used by BIFF5-8 in Font records */
24 const CHARSET_ANSI_LATIN = 0x00;
25 const CHARSET_SYSTEM_DEFAULT = 0x01;
26 const CHARSET_SYMBOL = 0x02;
27 const CHARSET_APPLE_ROMAN = 0x4D;
28 const CHARSET_ANSI_JAPANESE_SHIFTJIS = 0x80;
29 const CHARSET_ANSI_KOREAN_HANGUL = 0x81;
30 const CHARSET_ANSI_KOREAN_JOHAB = 0x82;
31 const CHARSET_ANSI_CHINESE_SIMIPLIFIED = 0x86; // gb2312
32 const CHARSET_ANSI_CHINESE_TRADITIONAL = 0x88; // big5
33 const CHARSET_ANSI_GREEK = 0xA1;
34 const CHARSET_ANSI_TURKISH = 0xA2;
35 const CHARSET_ANSI_VIETNAMESE = 0xA3;
36 const CHARSET_ANSI_HEBREW = 0xB1;
37 const CHARSET_ANSI_ARABIC = 0xB2;
38 const CHARSET_ANSI_BALTIC = 0xBA;
39 const CHARSET_ANSI_CYRILLIC = 0xCC;
40 const CHARSET_ANSI_THAI = 0xDD;
41 const CHARSET_ANSI_LATIN_II = 0xEE;
42 const CHARSET_OEM_LATIN_I = 0xFF;
43
44 // XXX: Constants created!
45 /** Font filenames */
46 const ARIAL = 'arial.ttf';
47 const ARIAL_BOLD = 'arialbd.ttf';
48 const ARIAL_ITALIC = 'ariali.ttf';
49 const ARIAL_BOLD_ITALIC = 'arialbi.ttf';
50
51 const CALIBRI = 'calibri.ttf';
52 const CALIBRI_BOLD = 'calibrib.ttf';
53 const CALIBRI_ITALIC = 'calibrii.ttf';
54 const CALIBRI_BOLD_ITALIC = 'calibriz.ttf';
55
56 const COMIC_SANS_MS = 'comic.ttf';
57 const COMIC_SANS_MS_BOLD = 'comicbd.ttf';
58
59 const COURIER_NEW = 'cour.ttf';
60 const COURIER_NEW_BOLD = 'courbd.ttf';
61 const COURIER_NEW_ITALIC = 'couri.ttf';
62 const COURIER_NEW_BOLD_ITALIC = 'courbi.ttf';
63
64 const GEORGIA = 'georgia.ttf';
65 const GEORGIA_BOLD = 'georgiab.ttf';
66 const GEORGIA_ITALIC = 'georgiai.ttf';
67 const GEORGIA_BOLD_ITALIC = 'georgiaz.ttf';
68
69 const IMPACT = 'impact.ttf';
70
71 const LIBERATION_SANS = 'LiberationSans-Regular.ttf';
72 const LIBERATION_SANS_BOLD = 'LiberationSans-Bold.ttf';
73 const LIBERATION_SANS_ITALIC = 'LiberationSans-Italic.ttf';
74 const LIBERATION_SANS_BOLD_ITALIC = 'LiberationSans-BoldItalic.ttf';
75
76 const LUCIDA_CONSOLE = 'lucon.ttf';
77 const LUCIDA_SANS_UNICODE = 'l_10646.ttf';
78
79 const MICROSOFT_SANS_SERIF = 'micross.ttf';
80
81 const PALATINO_LINOTYPE = 'pala.ttf';
82 const PALATINO_LINOTYPE_BOLD = 'palab.ttf';
83 const PALATINO_LINOTYPE_ITALIC = 'palai.ttf';
84 const PALATINO_LINOTYPE_BOLD_ITALIC = 'palabi.ttf';
85
86 const SYMBOL = 'symbol.ttf';
87
88 const TAHOMA = 'tahoma.ttf';
89 const TAHOMA_BOLD = 'tahomabd.ttf';
90
91 const TIMES_NEW_ROMAN = 'times.ttf';
92 const TIMES_NEW_ROMAN_BOLD = 'timesbd.ttf';
93 const TIMES_NEW_ROMAN_ITALIC = 'timesi.ttf';
94 const TIMES_NEW_ROMAN_BOLD_ITALIC = 'timesbi.ttf';
95
96 const TREBUCHET_MS = 'trebuc.ttf';
97 const TREBUCHET_MS_BOLD = 'trebucbd.ttf';
98 const TREBUCHET_MS_ITALIC = 'trebucit.ttf';
99 const TREBUCHET_MS_BOLD_ITALIC = 'trebucbi.ttf';
100
101 const VERDANA = 'verdana.ttf';
102 const VERDANA_BOLD = 'verdanab.ttf';
103 const VERDANA_ITALIC = 'verdanai.ttf';
104 const VERDANA_BOLD_ITALIC = 'verdanaz.ttf';
105
106 const FONT_FILE_NAMES = [
107 'Arial' => [
108 'x' => self::ARIAL,
109 'xb' => self::ARIAL_BOLD,
110 'xi' => self::ARIAL_ITALIC,
111 'xbi' => self::ARIAL_BOLD_ITALIC,
112 ],
113 'Calibri' => [
114 'x' => self::CALIBRI,
115 'xb' => self::CALIBRI_BOLD,
116 'xi' => self::CALIBRI_ITALIC,
117 'xbi' => self::CALIBRI_BOLD_ITALIC,
118 ],
119 'Comic Sans MS' => [
120 'x' => self::COMIC_SANS_MS,
121 'xb' => self::COMIC_SANS_MS_BOLD,
122 'xi' => self::COMIC_SANS_MS,
123 'xbi' => self::COMIC_SANS_MS_BOLD,
124 ],
125 'Courier New' => [
126 'x' => self::COURIER_NEW,
127 'xb' => self::COURIER_NEW_BOLD,
128 'xi' => self::COURIER_NEW_ITALIC,
129 'xbi' => self::COURIER_NEW_BOLD_ITALIC,
130 ],
131 'Georgia' => [
132 'x' => self::GEORGIA,
133 'xb' => self::GEORGIA_BOLD,
134 'xi' => self::GEORGIA_ITALIC,
135 'xbi' => self::GEORGIA_BOLD_ITALIC,
136 ],
137 'Impact' => [
138 'x' => self::IMPACT,
139 'xb' => self::IMPACT,
140 'xi' => self::IMPACT,
141 'xbi' => self::IMPACT,
142 ],
143 'Liberation Sans' => [
144 'x' => self::LIBERATION_SANS,
145 'xb' => self::LIBERATION_SANS_BOLD,
146 'xi' => self::LIBERATION_SANS_ITALIC,
147 'xbi' => self::LIBERATION_SANS_BOLD_ITALIC,
148 ],
149 'Lucida Console' => [
150 'x' => self::LUCIDA_CONSOLE,
151 'xb' => self::LUCIDA_CONSOLE,
152 'xi' => self::LUCIDA_CONSOLE,
153 'xbi' => self::LUCIDA_CONSOLE,
154 ],
155 'Lucida Sans Unicode' => [
156 'x' => self::LUCIDA_SANS_UNICODE,
157 'xb' => self::LUCIDA_SANS_UNICODE,
158 'xi' => self::LUCIDA_SANS_UNICODE,
159 'xbi' => self::LUCIDA_SANS_UNICODE,
160 ],
161 'Microsoft Sans Serif' => [
162 'x' => self::MICROSOFT_SANS_SERIF,
163 'xb' => self::MICROSOFT_SANS_SERIF,
164 'xi' => self::MICROSOFT_SANS_SERIF,
165 'xbi' => self::MICROSOFT_SANS_SERIF,
166 ],
167 'Palatino Linotype' => [
168 'x' => self::PALATINO_LINOTYPE,
169 'xb' => self::PALATINO_LINOTYPE_BOLD,
170 'xi' => self::PALATINO_LINOTYPE_ITALIC,
171 'xbi' => self::PALATINO_LINOTYPE_BOLD_ITALIC,
172 ],
173 'Symbol' => [
174 'x' => self::SYMBOL,
175 'xb' => self::SYMBOL,
176 'xi' => self::SYMBOL,
177 'xbi' => self::SYMBOL,
178 ],
179 'Tahoma' => [
180 'x' => self::TAHOMA,
181 'xb' => self::TAHOMA_BOLD,
182 'xi' => self::TAHOMA,
183 'xbi' => self::TAHOMA_BOLD,
184 ],
185 'Times New Roman' => [
186 'x' => self::TIMES_NEW_ROMAN,
187 'xb' => self::TIMES_NEW_ROMAN_BOLD,
188 'xi' => self::TIMES_NEW_ROMAN_ITALIC,
189 'xbi' => self::TIMES_NEW_ROMAN_BOLD_ITALIC,
190 ],
191 'Trebuchet MS' => [
192 'x' => self::TREBUCHET_MS,
193 'xb' => self::TREBUCHET_MS_BOLD,
194 'xi' => self::TREBUCHET_MS_ITALIC,
195 'xbi' => self::TREBUCHET_MS_BOLD_ITALIC,
196 ],
197 'Verdana' => [
198 'x' => self::VERDANA,
199 'xb' => self::VERDANA_BOLD,
200 'xi' => self::VERDANA_ITALIC,
201 'xbi' => self::VERDANA_BOLD_ITALIC,
202 ],
203 ];
204
205 /**
206 * Array that can be used to supplement FONT_FILE_NAMES for calculating exact width.
207 *
208 * @var array<string, array<string, string>>
209 */
210 private static array $extraFontArray = [];
211
212 /** @param array<string, array<string, string>> $extraFontArray */
213 public static function setExtraFontArray(array $extraFontArray): void
214 {
215 self::$extraFontArray = $extraFontArray;
216 }
217
218 /** @return array<string, array<string, string>> */
219 public static function getExtraFontArray(): array
220 {
221 return self::$extraFontArray;
222 }
223
224 /**
225 * AutoSize method.
226 */
227 private static string $autoSizeMethod = self::AUTOSIZE_METHOD_APPROX;
228
229 /**
230 * Path to folder containing TrueType font .ttf files.
231 */
232 private static string $trueTypeFontPath = '';
233
234 /**
235 * How wide is a default column for a given default font and size?
236 * Empirical data found by inspecting real Excel files and reading off the pixel width
237 * in Microsoft Office Excel 2007.
238 * Added height in points.
239 */
240 public const DEFAULT_COLUMN_WIDTHS = [
241 'Arial' => [
242 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
243 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
244 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
245
246 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
247 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
248 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
249 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
250 8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
251 9 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.0],
252 10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
253 ],
254 'Calibri' => [
255 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
256 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
257 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.00],
258 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
259 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
260 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
261 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
262 8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25],
263 9 => ['px' => 56, 'width' => 9.33203125, 'height' => 12.0],
264 10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75],
265 11 => ['px' => 64, 'width' => 9.14062500, 'height' => 15.0],
266 ],
267 'Verdana' => [
268 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
269 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25],
270 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0],
271 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75],
272 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25],
273 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25],
274 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0],
275 8 => ['px' => 64, 'width' => 9.14062500, 'height' => 10.5],
276 9 => ['px' => 72, 'width' => 9.00000000, 'height' => 11.25],
277 10 => ['px' => 72, 'width' => 9.00000000, 'height' => 12.75],
278 ],
279 ];
280
281 /**
282 * Set autoSize method.
283 *
284 * @param string $method see self::AUTOSIZE_METHOD_*
285 *
286 * @return bool Success or failure
287 */
288 public static function setAutoSizeMethod(string $method): bool
289 {
290 if (!in_array($method, self::AUTOSIZE_METHODS)) {
291 return false;
292 }
293 self::$autoSizeMethod = $method;
294
295 return true;
296 }
297
298 /**
299 * Get autoSize method.
300 */
301 public static function getAutoSizeMethod(): string
302 {
303 return self::$autoSizeMethod;
304 }
305
306 /**
307 * Set the path to the folder containing .ttf files. There should be a trailing slash.
308 * Path will be recursively searched for font file.
309 * Typical locations on various platforms:
310 * <ul>
311 * <li>C:/Windows/Fonts/</li>
312 * <li>/usr/share/fonts/truetype/</li>
313 * <li>~/.fonts/</li>
314 * </ul>.
315 */
316 public static function setTrueTypeFontPath(string $folderPath): void
317 {
318 self::$trueTypeFontPath = $folderPath;
319 }
320
321 /**
322 * Get the path to the folder containing .ttf files.
323 */
324 public static function getTrueTypeFontPath(): string
325 {
326 return self::$trueTypeFontPath;
327 }
328
329 /**
330 * Pad amount for exact in pixels; use best guess if null.
331 * @var null|float|int
332 */
333 private static $paddingAmountExact = null;
334
335 /**
336 * Set pad amount for exact in pixels; use best guess if null.
337 * @param null|float|int $paddingAmountExact
338 */
339 public static function setPaddingAmountExact($paddingAmountExact): void
340 {
341 self::$paddingAmountExact = $paddingAmountExact;
342 }
343
344 /**
345 * Get pad amount for exact in pixels; or null if using best guess.
346 * @return null|float|int
347 */
348 public static function getPaddingAmountExact()
349 {
350 return self::$paddingAmountExact;
351 }
352
353 /**
354 * Calculate an (approximate) OpenXML column width, based on font size and text contained.
355 *
356 * @param FontStyle $font Font object
357 * @param null|RichText|string $cellText Text to calculate width
358 * @param int $rotation Rotation angle
359 * @param null|FontStyle $defaultFont Font object
360 * @param bool $filterAdjustment Add space for Autofilter or Table dropdown
361 */
362 public static function calculateColumnWidth(
363 FontStyle $font,
364 $cellText = '',
365 int $rotation = 0,
366 ?FontStyle $defaultFont = null,
367 bool $filterAdjustment = false,
368 int $indentAdjustment = 0
369 ): float {
370 // If it is rich text, use plain text
371 if ($cellText instanceof RichText) {
372 $cellText = $cellText->getPlainText();
373 }
374
375 // Special case if there are one or more newline characters ("\n")
376 $cellText = (string) $cellText;
377 if (str_contains($cellText, "\n")) {
378 $lineTexts = explode("\n", $cellText);
379 $lineWidths = [];
380 foreach ($lineTexts as $lineText) {
381 $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment);
382 }
383
384 return max($lineWidths); // width of longest line in cell
385 }
386
387 // Try to get the exact text width in pixels
388 $approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
389 $columnWidth = 0;
390 if (!$approximate) {
391 try {
392 $columnWidthAdjust = ceil(
393 self::getTextWidthPixelsExact(
394 str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
395 $font,
396 0
397 ) * 1.07
398 );
399
400 // Width of text in pixels excl. padding
401 // and addition because Excel adds some padding, just use approx width of 'n' glyph
402 $columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + (self::$paddingAmountExact ?? $columnWidthAdjust);
403 } catch (PhpSpreadsheetException $exception) {
404 $approximate = true;
405 }
406 }
407
408 if ($approximate) {
409 $columnWidthAdjust = self::getTextWidthPixelsApprox(
410 str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
411 $font,
412 0
413 );
414 // Width of text in pixels excl. padding, approximation
415 // and addition because Excel adds some padding, just use approx width of 'n' glyph
416 $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
417 }
418
419 // Convert from pixel width to column width
420 $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle());
421
422 // Return
423 return round($columnWidth, 4);
424 }
425
426 /**
427 * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
428 */
429 public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): float
430 {
431 // font size should really be supplied in pixels in GD2,
432 // but since GD2 seems to assume 72dpi, pixels and points are the same
433 $fontFile = self::getTrueTypeFontFileFromFont($font);
434 $textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text);
435 if ($textBox === false) {
436 // @codeCoverageIgnoreStart
437 throw new PhpSpreadsheetException('imagettfbbox failed');
438 // @codeCoverageIgnoreEnd
439 }
440
441 // Get corners positions
442 /** @var int[] $textBox */
443 $lowerLeftCornerX = $textBox[0];
444 $lowerRightCornerX = $textBox[2];
445 $upperRightCornerX = $textBox[4];
446 $upperLeftCornerX = $textBox[6];
447
448 // Consider the rotation when calculating the width
449 return round(max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX), 4);
450 }
451
452 /**
453 * Get approximate width in pixels for a string of text in a certain font at a certain rotation angle.
454 *
455 * @return int Text width in pixels (no padding added)
456 */
457 public static function getTextWidthPixelsApprox(string $columnText, FontStyle $font, int $rotation = 0): int
458 {
459 $fontName = $font->getName();
460 $fontSize = $font->getSize();
461
462 // Calculate column width in pixels.
463 // We assume fixed glyph width, but count double for "fullwidth" characters.
464 // Result varies with font name and size.
465 switch ($fontName) {
466 case 'Arial':
467 // value 8 was set because of experience in different exports at Arial 10 font.
468 $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
469 $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
470
471 break;
472 case 'Verdana':
473 // value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
474 $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
475 $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
476
477 break;
478 default:
479 // just assume Calibri
480 // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
481 $columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
482 $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
483
484 break;
485 }
486
487 // Calculate approximate rotated column width
488 if ($rotation !== 0) {
489 if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
490 // stacked text
491 $columnWidth = 4; // approximation
492 } else {
493 // rotated text
494 $columnWidth = $columnWidth * cos(deg2rad($rotation))
495 + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation
496 }
497 }
498
499 // pixel width is an integer
500 return (int) $columnWidth;
501 }
502
503 /**
504 * Calculate an (approximate) pixel size, based on a font points size.
505 *
506 * @param float|int $fontSizeInPoints Font size (in points)
507 *
508 * @return int Font size (in pixels)
509 */
510 public static function fontSizeToPixels($fontSizeInPoints): int
511 {
512 return (int) ((4 / 3) * $fontSizeInPoints);
513 }
514
515 /**
516 * Calculate an (approximate) pixel size, based on inch size.
517 *
518 * @param float|int $sizeInInch Font size (in inch)
519 *
520 * @return float|int Size (in pixels)
521 */
522 public static function inchSizeToPixels($sizeInInch)
523 {
524 return $sizeInInch * 96;
525 }
526
527 /**
528 * Calculate an (approximate) pixel size, based on centimeter size.
529 *
530 * @param float|int $sizeInCm Font size (in centimeters)
531 *
532 * @return float Size (in pixels)
533 */
534 public static function centimeterSizeToPixels($sizeInCm): float
535 {
536 return $sizeInCm * 37.795275591;
537 }
538
539 /**
540 * Returns the font path given the font.
541 *
542 * @return string Path to TrueType font file
543 */
544 public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true): string
545 {
546 if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) {
547 throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
548 }
549
550 $name = $font->getName();
551 $fontArray = array_merge(self::FONT_FILE_NAMES, self::$extraFontArray);
552 if (!isset($fontArray[$name])) {
553 throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
554 }
555 $bold = $font->getBold();
556 $italic = $font->getItalic();
557 $index = 'x';
558 if ($bold) {
559 $index .= 'b';
560 }
561 if ($italic) {
562 $index .= 'i';
563 }
564 $fontFile = $fontArray[$name][$index];
565
566 $separator = '';
567 if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
568 $separator = DIRECTORY_SEPARATOR;
569 }
570 $fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\]~', $fontFile) === 1;
571 if (!$fontFileAbsolute) {
572 $fontFile = self::findFontFile(self::$trueTypeFontPath, $fontFile) ?? self::$trueTypeFontPath . $separator . $fontFile;
573 }
574
575 // Check if file actually exists
576 if ($checkPath && !file_exists($fontFile) && !$fontFileAbsolute) {
577 $alternateName = $name;
578 if ($index !== 'x' && $fontArray[$name][$index] !== $fontArray[$name]['x']) {
579 // Bold but no italic:
580 // Comic Sans
581 // Tahoma
582 // Neither bold nor italic:
583 // Impact
584 // Lucida Console
585 // Lucida Sans Unicode
586 // Microsoft Sans Serif
587 // Symbol
588 if ($index === 'xb') {
589 $alternateName .= ' Bold';
590 } elseif ($index === 'xi') {
591 $alternateName .= ' Italic';
592 } elseif ($fontArray[$name]['xb'] === $fontArray[$name]['xbi']) {
593 $alternateName .= ' Bold';
594 } else {
595 $alternateName .= ' Bold Italic';
596 }
597 }
598 $fontFile = self::$trueTypeFontPath . $separator . $alternateName . '.ttf';
599 if (!file_exists($fontFile)) {
600 throw new PhpSpreadsheetException('TrueType Font file not found');
601 }
602 }
603
604 return $fontFile;
605 }
606
607 public const CHARSET_FROM_FONT_NAME = [
608 'EucrosiaUPC' => self::CHARSET_ANSI_THAI,
609 'Wingdings' => self::CHARSET_SYMBOL,
610 'Wingdings 2' => self::CHARSET_SYMBOL,
611 'Wingdings 3' => self::CHARSET_SYMBOL,
612 ];
613
614 /**
615 * Returns the associated charset for the font name.
616 *
617 * @param string $fontName Font name
618 *
619 * @return int Character set code
620 */
621 public static function getCharsetFromFontName(string $fontName): int
622 {
623 return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN;
624 }
625
626 /**
627 * Get the effective column width for columns without a column dimension or column with width -1
628 * For example, for Calibri 11 this is 9.140625 (64 px).
629 *
630 * @param FontStyle $font The workbooks default font
631 * @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units
632 *
633 * @return ($returnAsPixels is true ? int : float) Column width
634 */
635 public static function getDefaultColumnWidthByFont(FontStyle $font, bool $returnAsPixels = false)
636 {
637 $size = $font->getSize();
638 $sizex = ($size !== null && $size == (int) $size) ? ((int) $size) : "$size";
639 if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$sizex])) {
640 // Exact width can be determined
641 $columnWidth = $returnAsPixels
642 ? self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$sizex]['px']
643 : self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$sizex]['width'];
644 } else {
645 // We don't have data for this particular font and size, use approximation by
646 // extrapolating from Calibri 11
647 $columnWidth = $returnAsPixels
648 ? self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px']
649 : self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width'];
650 $columnWidth = $columnWidth * $font->getSize() / 11;
651
652 // Round pixels to closest integer
653 if ($returnAsPixels) {
654 $columnWidth = (int) round($columnWidth);
655 }
656 }
657
658 return $columnWidth;
659 }
660
661 /**
662 * Get the effective row height for rows without a row dimension or rows with height -1
663 * For example, for Calibri 11 this is 15 points.
664 *
665 * @param FontStyle $font The workbooks default font
666 *
667 * @return float Row height in points
668 */
669 public static function getDefaultRowHeightByFont(FontStyle $font): float
670 {
671 $name = $font->getName();
672 $size = $font->getSize();
673 $sizex = ($size !== null && $size == (int) $size) ? ((int) $size) : "$size";
674 if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$sizex])) {
675 $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$sizex]['height'];
676 } elseif ($name === 'Arial' || $name === 'Verdana') {
677 $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0;
678 } else {
679 $rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0;
680 }
681
682 return $rowHeight;
683 }
684
685 private static function findFontFile(string $startDirectory, string $desiredFont): ?string
686 {
687 $fontPath = null;
688 if ($startDirectory === '') {
689 return null;
690 }
691 if (file_exists("$startDirectory/$desiredFont")) {
692 $fontPath = "$startDirectory/$desiredFont";
693 } else {
694 $iterations = 0;
695 $it = new RecursiveDirectoryIterator(
696 $startDirectory,
697 RecursiveDirectoryIterator::SKIP_DOTS
698 | RecursiveDirectoryIterator::FOLLOW_SYMLINKS
699 );
700 foreach (
701 new RecursiveIteratorIterator(
702 $it,
703 RecursiveIteratorIterator::LEAVES_ONLY,
704 RecursiveIteratorIterator::CATCH_GET_CHILD
705 ) as $filex
706 ) {
707 /** @var string */
708 $file = $filex;
709 if (basename($file) === $desiredFont) {
710 $fontPath = $file;
711
712 break;
713 }
714 ++$iterations;
715 if ($iterations > 5000) {
716 // @codeCoverageIgnoreStart
717 break;
718 // @codeCoverageIgnoreEnd
719 }
720 }
721 }
722
723 return $fontPath;
724 }
725 }
726