Font::getDefaultRowHeightByFont()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 13
ccs 9
cts 9
cp 1
rs 9.9666
cc 4
nc 3
nop 1
crap 4
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared;
4
5
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
6
use PhpOffice\PhpSpreadsheet\RichText\RichText;
7
use PhpOffice\PhpSpreadsheet\Style\Alignment;
8
use 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 62
    public static function setExtraFontArray(array $extraFontArray): void
214
    {
215 62
        self::$extraFontArray = $extraFontArray;
216
    }
217
218
    /** @return array<string, array<string, string>> */
219 56
    public static function getExtraFontArray(): array
220
    {
221 56
        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 9
    public static function setAutoSizeMethod(string $method): bool
289
    {
290 9
        if (!in_array($method, self::AUTOSIZE_METHODS)) {
291 1
            return false;
292
        }
293 8
        self::$autoSizeMethod = $method;
294
295 8
        return true;
296
    }
297
298
    /**
299
     * Get autoSize method.
300
     */
301 8
    public static function getAutoSizeMethod(): string
302
    {
303 8
        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 66
    public static function setTrueTypeFontPath(string $folderPath): void
317
    {
318 66
        self::$trueTypeFontPath = $folderPath;
319
    }
320
321
    /**
322
     * Get the path to the folder containing .ttf files.
323
     */
324 66
    public static function getTrueTypeFontPath(): string
325
    {
326 66
        return self::$trueTypeFontPath;
327
    }
328
329
    /**
330
     * Pad amount for exact in pixels; use best guess if null.
331
     */
332
    private static null|float|int $paddingAmountExact = null;
333
334
    /**
335
     * Set pad amount for exact in pixels; use best guess if null.
336
     */
337 7
    public static function setPaddingAmountExact(null|float|int $paddingAmountExact): void
338
    {
339 7
        self::$paddingAmountExact = $paddingAmountExact;
340
    }
341
342
    /**
343
     * Get pad amount for exact in pixels; or null if using best guess.
344
     */
345 7
    public static function getPaddingAmountExact(): null|float|int
346
    {
347 7
        return self::$paddingAmountExact;
348
    }
349
350
    /**
351
     * Calculate an (approximate) OpenXML column width, based on font size and text contained.
352
     *
353
     * @param FontStyle $font Font object
354
     * @param null|RichText|string $cellText Text to calculate width
355
     * @param int $rotation Rotation angle
356
     * @param null|FontStyle $defaultFont Font object
357
     * @param bool $filterAdjustment Add space for Autofilter or Table dropdown
358
     */
359 70
    public static function calculateColumnWidth(
360
        FontStyle $font,
361
        $cellText = '',
362
        int $rotation = 0,
363
        ?FontStyle $defaultFont = null,
364
        bool $filterAdjustment = false,
365
        int $indentAdjustment = 0
366
    ): float {
367
        // If it is rich text, use plain text
368 70
        if ($cellText instanceof RichText) {
369 1
            $cellText = $cellText->getPlainText();
370
        }
371
372
        // Special case if there are one or more newline characters ("\n")
373 70
        $cellText = (string) $cellText;
374 70
        if (str_contains($cellText, "\n")) {
375 3
            $lineTexts = explode("\n", $cellText);
376 3
            $lineWidths = [];
377 3
            foreach ($lineTexts as $lineText) {
378 3
                $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment);
379
            }
380
381 3
            return max($lineWidths); // width of longest line in cell
382
        }
383
384
        // Try to get the exact text width in pixels
385 70
        $approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
386 70
        $columnWidth = 0;
387 70
        if (!$approximate) {
388
            try {
389 6
                $columnWidthAdjust = ceil(
390 6
                    self::getTextWidthPixelsExact(
391 6
                        str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
392 6
                        $font,
393 6
                        0
394 6
                    ) * 1.07
395 6
                );
396
397
                // Width of text in pixels excl. padding
398
                // and addition because Excel adds some padding, just use approx width of 'n' glyph
399 5
                $columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + (self::$paddingAmountExact ?? $columnWidthAdjust);
400 1
            } catch (PhpSpreadsheetException) {
401 1
                $approximate = true;
402
            }
403
        }
404
405 70
        if ($approximate) {
406 68
            $columnWidthAdjust = self::getTextWidthPixelsApprox(
407 68
                str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
408 68
                $font,
409 68
                0
410 68
            );
411
            // Width of text in pixels excl. padding, approximation
412
            // and addition because Excel adds some padding, just use approx width of 'n' glyph
413 68
            $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
414
        }
415
416
        // Convert from pixel width to column width
417 70
        $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle());
418
419
        // Return
420 70
        return round($columnWidth, 4);
421
    }
422
423
    /**
424
     * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
425
     */
426 6
    public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): float
427
    {
428
        // font size should really be supplied in pixels in GD2,
429
        // but since GD2 seems to assume 72dpi, pixels and points are the same
430 6
        $fontFile = self::getTrueTypeFontFileFromFont($font);
431 5
        $textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text);
432 5
        if ($textBox === false) {
433
            // @codeCoverageIgnoreStart
434
            throw new PhpSpreadsheetException('imagettfbbox failed');
435
            // @codeCoverageIgnoreEnd
436
        }
437
438
        // Get corners positions
439 5
        $lowerLeftCornerX = $textBox[0];
440 5
        $lowerRightCornerX = $textBox[2];
441 5
        $upperRightCornerX = $textBox[4];
442 5
        $upperLeftCornerX = $textBox[6];
443
444
        // Consider the rotation when calculating the width
445 5
        return round(max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX), 4);
446
    }
447
448
    /**
449
     * Get approximate width in pixels for a string of text in a certain font at a certain rotation angle.
450
     *
451
     * @return int Text width in pixels (no padding added)
452
     */
453 69
    public static function getTextWidthPixelsApprox(string $columnText, FontStyle $font, int $rotation = 0): int
454
    {
455 69
        $fontName = $font->getName();
456 69
        $fontSize = $font->getSize();
457
458
        // Calculate column width in pixels.
459
        // We assume fixed glyph width, but count double for "fullwidth" characters.
460
        // Result varies with font name and size.
461
        switch ($fontName) {
462 69
            case 'Arial':
463
                // value 8 was set because of experience in different exports at Arial 10 font.
464 4
                $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
465 4
                $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
466
467 4
                break;
468 65
            case 'Verdana':
469
                // value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
470 1
                $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
471 1
                $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
472
473 1
                break;
474
            default:
475
                // just assume Calibri
476
                // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
477 64
                $columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
478 64
                $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
479
480 64
                break;
481
        }
482
483
        // Calculate approximate rotated column width
484 69
        if ($rotation !== 0) {
485 1
            if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
486
                // stacked text
487 1
                $columnWidth = 4; // approximation
488
            } else {
489
                // rotated text
490 1
                $columnWidth = $columnWidth * cos(deg2rad($rotation))
491 1
                                + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation
492
            }
493
        }
494
495
        // pixel width is an integer
496 69
        return (int) $columnWidth;
497
    }
498
499
    /**
500
     * Calculate an (approximate) pixel size, based on a font points size.
501
     *
502
     * @param float|int $fontSizeInPoints Font size (in points)
503
     *
504
     * @return int Font size (in pixels)
505
     */
506 27
    public static function fontSizeToPixels(float|int $fontSizeInPoints): int
507
    {
508 27
        return (int) ((4 / 3) * $fontSizeInPoints);
509
    }
510
511
    /**
512
     * Calculate an (approximate) pixel size, based on inch size.
513
     *
514
     * @param float|int $sizeInInch Font size (in inch)
515
     *
516
     * @return float|int Size (in pixels)
517
     */
518 5
    public static function inchSizeToPixels(int|float $sizeInInch): int|float
519
    {
520 5
        return $sizeInInch * 96;
521
    }
522
523
    /**
524
     * Calculate an (approximate) pixel size, based on centimeter size.
525
     *
526
     * @param float|int $sizeInCm Font size (in centimeters)
527
     *
528
     * @return float Size (in pixels)
529
     */
530 5
    public static function centimeterSizeToPixels(int|float $sizeInCm): float
531
    {
532 5
        return $sizeInCm * 37.795275591;
533
    }
534
535
    /**
536
     * Returns the font path given the font.
537
     *
538
     * @return string Path to TrueType font file
539
     */
540 66
    public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true): string
541
    {
542 66
        if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) {
543 1
            throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
544
        }
545
546 65
        $name = $font->getName();
547 65
        $fontArray = array_merge(self::FONT_FILE_NAMES, self::$extraFontArray);
548 65
        if (!isset($fontArray[$name])) {
549 1
            throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
550
        }
551 64
        $bold = $font->getBold();
552 64
        $italic = $font->getItalic();
553 64
        $index = 'x';
554 64
        if ($bold) {
555 27
            $index .= 'b';
556
        }
557 64
        if ($italic) {
558 25
            $index .= 'i';
559
        }
560 64
        $fontFile = $fontArray[$name][$index];
561
562 64
        $separator = '';
563 64
        if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
564 57
            $separator = DIRECTORY_SEPARATOR;
565
        }
566 64
        $fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\\]~', $fontFile) === 1;
567 64
        if (!$fontFileAbsolute) {
568 60
            $fontFile = self::findFontFile(self::$trueTypeFontPath, $fontFile) ?? self::$trueTypeFontPath . $separator . $fontFile;
569
        }
570
571
        // Check if file actually exists
572 64
        if ($checkPath && !file_exists($fontFile) && !$fontFileAbsolute) {
573 25
            $alternateName = $name;
574 25
            if ($index !== 'x' && $fontArray[$name][$index] !== $fontArray[$name]['x']) {
575
                // Bold but no italic:
576
                //   Comic Sans
577
                //   Tahoma
578
                // Neither bold nor italic:
579
                //   Impact
580
                //   Lucida Console
581
                //   Lucida Sans Unicode
582
                //   Microsoft Sans Serif
583
                //   Symbol
584 12
                if ($index === 'xb') {
585 5
                    $alternateName .= ' Bold';
586 7
                } elseif ($index === 'xi') {
587 3
                    $alternateName .= ' Italic';
588 4
                } elseif ($fontArray[$name]['xb'] === $fontArray[$name]['xbi']) {
589 1
                    $alternateName .= ' Bold';
590
                } else {
591 3
                    $alternateName .= ' Bold Italic';
592
                }
593
            }
594 25
            $fontFile = self::$trueTypeFontPath . $separator . $alternateName . '.ttf';
595 25
            if (!file_exists($fontFile)) {
596 5
                throw new PhpSpreadsheetException('TrueType Font file not found');
597
            }
598
        }
599
600 59
        return $fontFile;
601
    }
602
603
    public const CHARSET_FROM_FONT_NAME = [
604
        'EucrosiaUPC' => self::CHARSET_ANSI_THAI,
605
        'Wingdings' => self::CHARSET_SYMBOL,
606
        'Wingdings 2' => self::CHARSET_SYMBOL,
607
        'Wingdings 3' => self::CHARSET_SYMBOL,
608
    ];
609
610
    /**
611
     * Returns the associated charset for the font name.
612
     *
613
     * @param string $fontName Font name
614
     *
615
     * @return int Character set code
616
     */
617 104
    public static function getCharsetFromFontName(string $fontName): int
618
    {
619 104
        return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN;
620
    }
621
622
    /**
623
     * Get the effective column width for columns without a column dimension or column with width -1
624
     * For example, for Calibri 11 this is 9.140625 (64 px).
625
     *
626
     * @param FontStyle $font The workbooks default font
627
     * @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units
628
     *
629
     * @return ($returnAsPixels is true ? int : float) Column width
630
     */
631 97
    public static function getDefaultColumnWidthByFont(FontStyle $font, bool $returnAsPixels = false): float|int
632
    {
633 97
        if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) {
634
            // Exact width can be determined
635 97
            $columnWidth = $returnAsPixels
636 11
                ? self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px']
637 96
                    : self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width'];
638
        } else {
639
            // We don't have data for this particular font and size, use approximation by
640
            // extrapolating from Calibri 11
641 1
            $columnWidth = $returnAsPixels
642 1
                ? self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px']
643 1
                    : self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width'];
644 1
            $columnWidth = $columnWidth * $font->getSize() / 11;
645
646
            // Round pixels to closest integer
647 1
            if ($returnAsPixels) {
648 1
                $columnWidth = (int) round($columnWidth);
649
            }
650
        }
651
652 97
        return $columnWidth;
653
    }
654
655
    /**
656
     * Get the effective row height for rows without a row dimension or rows with height -1
657
     * For example, for Calibri 11 this is 15 points.
658
     *
659
     * @param FontStyle $font The workbooks default font
660
     *
661
     * @return float Row height in points
662
     */
663 493
    public static function getDefaultRowHeightByFont(FontStyle $font): float
664
    {
665 493
        $name = $font->getName();
666 493
        $size = $font->getSize();
667 493
        if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) {
668 493
            $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height'];
669 1
        } elseif ($name === 'Arial' || $name === 'Verdana') {
670 1
            $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0;
671
        } else {
672 1
            $rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0;
673
        }
674
675 493
        return $rowHeight;
676
    }
677
678 60
    private static function findFontFile(string $startDirectory, string $desiredFont): ?string
679
    {
680 60
        $fontPath = null;
681 60
        if ($startDirectory === '') {
682 1
            return null;
683
        }
684 59
        if (file_exists("$startDirectory/$desiredFont")) {
685 32
            $fontPath = "$startDirectory/$desiredFont";
686
        } else {
687 27
            $iterations = 0;
688 27
            $it = new RecursiveDirectoryIterator(
689 27
                $startDirectory,
690 27
                RecursiveDirectoryIterator::SKIP_DOTS
691 27
                | RecursiveDirectoryIterator::FOLLOW_SYMLINKS
692 27
            );
693
            foreach (
694 27
                new RecursiveIteratorIterator(
695 27
                    $it,
696 27
                    RecursiveIteratorIterator::LEAVES_ONLY,
697 27
                    RecursiveIteratorIterator::CATCH_GET_CHILD
698 27
                ) as $file
699
            ) {
700 27
                if (basename($file) === $desiredFont) {
701 2
                    $fontPath = $file;
702
703 2
                    break;
704
                }
705 27
                ++$iterations;
706 27
                if ($iterations > 5000) {
707
                    // @codeCoverageIgnoreStart
708
                    break;
709
                    // @codeCoverageIgnoreEnd
710
                }
711
            }
712
        }
713
714 59
        return $fontPath;
715
    }
716
}
717