Completed
Push — master ( 5fb76c...11bae5 )
by Mark
32s queued 28s
created

Font::getDefaultColumnWidthByFont()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 5

Importance

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