Completed
Push — master ( a3489b...087532 )
by Mark
33s queued 30s
created

Font::getTextWidthPixelsExact()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 20
ccs 0
cts 9
cp 0
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 6
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 62
    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
    ): float {
362
        // If it is rich text, use plain text
363 62
        if ($cellText instanceof RichText) {
364
            $cellText = $cellText->getPlainText();
365
        }
366
367
        // Special case if there are one or more newline characters ("\n")
368 62
        $cellText = (string) $cellText;
369 62
        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 62
        $approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
381 62
        $columnWidth = 0;
382 62
        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 62
        if ($approximate) {
401 62
            $columnWidthAdjust = self::getTextWidthPixelsApprox(
402 62
                str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
403 62
                $font,
404 62
                0
405 62
            );
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 62
            $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
409
        }
410
411
        // Convert from pixel width to column width
412 62
        $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle());
413
414
        // Return
415 62
        return round($columnWidth, 4);
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): float
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 round(max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX), 4);
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 63
    public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0)
452
    {
453 63
        $fontName = $font->getName();
454 63
        $fontSize = $font->getSize();
455
456
        // Calculate column width in pixels.
457
        // We assume fixed glyph width, but count double for "fullwidth" characters.
458
        // Result varies with font name and size.
459
        switch ($fontName) {
460 63
            case 'Arial':
461
                // value 8 was set because of experience in different exports at Arial 10 font.
462 2
                $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
463 2
                $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
464
465 2
                break;
466 61
            case 'Verdana':
467
                // value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
468 1
                $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
469 1
                $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
470
471 1
                break;
472
            default:
473
                // just assume Calibri
474
                // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
475 60
                $columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
476 60
                $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
477
478 60
                break;
479
        }
480
481
        // Calculate approximate rotated column width
482 63
        if ($rotation !== 0) {
483 1
            if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
484
                // stacked text
485 1
                $columnWidth = 4; // approximation
486
            } else {
487
                // rotated text
488 1
                $columnWidth = $columnWidth * cos(deg2rad($rotation))
489 1
                                + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation
490
            }
491
        }
492
493
        // pixel width is an integer
494 63
        return (int) $columnWidth;
495
    }
496
497
    /**
498
     * Calculate an (approximate) pixel size, based on a font points size.
499
     *
500
     * @param int $fontSizeInPoints Font size (in points)
501
     *
502
     * @return int Font size (in pixels)
503
     */
504 27
    public static function fontSizeToPixels($fontSizeInPoints)
505
    {
506 27
        return (int) ((4 / 3) * $fontSizeInPoints);
507
    }
508
509
    /**
510
     * Calculate an (approximate) pixel size, based on inch size.
511
     *
512
     * @param int $sizeInInch Font size (in inch)
513
     *
514
     * @return int Size (in pixels)
515
     */
516 5
    public static function inchSizeToPixels($sizeInInch)
517
    {
518 5
        return $sizeInInch * 96;
519
    }
520
521
    /**
522
     * Calculate an (approximate) pixel size, based on centimeter size.
523
     *
524
     * @param int $sizeInCm Font size (in centimeters)
525
     *
526
     * @return float Size (in pixels)
527
     */
528 5
    public static function centimeterSizeToPixels($sizeInCm)
529
    {
530 5
        return $sizeInCm * 37.795275591;
531
    }
532
533
    /**
534
     * Returns the font path given the font.
535
     *
536
     * @return string Path to TrueType font file
537
     */
538 51
    public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true)
539
    {
540 51
        if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) {
541 1
            throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
542
        }
543
544 50
        $name = $font->getName();
545 50
        $fontArray = array_merge(self::FONT_FILE_NAMES, self::$extraFontArray);
546 50
        if (!isset($fontArray[$name])) {
547 1
            throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
548
        }
549 49
        $bold = $font->getBold();
550 49
        $italic = $font->getItalic();
551 49
        $index = 'x';
552 49
        if ($bold) {
553 23
            $index .= 'b';
554
        }
555 49
        if ($italic) {
556 23
            $index .= 'i';
557
        }
558 49
        $fontFile = $fontArray[$name][$index];
559
560 49
        $separator = '';
561 49
        if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
562 48
            $separator = DIRECTORY_SEPARATOR;
563
        }
564 49
        $fontFile = self::$trueTypeFontPath . $separator . $fontFile;
565
566
        // Check if file actually exists
567 49
        if ($checkPath && !file_exists($fontFile)) {
568 23
            $alternateName = $name;
569 23
            if ($index !== 'x' && $fontArray[$name][$index] !== $fontArray[$name]['x']) {
570
                // Bold but no italic:
571
                //   Comic Sans
572
                //   Tahoma
573
                // Neither bold nor italic:
574
                //   Impact
575
                //   Lucida Console
576
                //   Lucida Sans Unicode
577
                //   Microsoft Sans Serif
578
                //   Symbol
579 11
                if ($index === 'xb') {
580 4
                    $alternateName .= ' Bold';
581 7
                } elseif ($index === 'xi') {
582 3
                    $alternateName .= ' Italic';
583 4
                } elseif ($fontArray[$name]['xb'] === $fontArray[$name]['xbi']) {
584 1
                    $alternateName .= ' Bold';
585
                } else {
586 3
                    $alternateName .= ' Bold Italic';
587
                }
588
            }
589 23
            $fontFile = self::$trueTypeFontPath . $separator . $alternateName . '.ttf';
590 23
            if (!file_exists($fontFile)) {
591 3
                throw new PhpSpreadsheetException('TrueType Font file not found');
592
            }
593
        }
594
595 46
        return $fontFile;
596
    }
597
598
    public const CHARSET_FROM_FONT_NAME = [
599
        'EucrosiaUPC' => self::CHARSET_ANSI_THAI,
600
        'Wingdings' => self::CHARSET_SYMBOL,
601
        'Wingdings 2' => self::CHARSET_SYMBOL,
602
        'Wingdings 3' => self::CHARSET_SYMBOL,
603
    ];
604
605
    /**
606
     * Returns the associated charset for the font name.
607
     *
608
     * @param string $fontName Font name
609
     *
610
     * @return int Character set code
611
     */
612 96
    public static function getCharsetFromFontName($fontName)
613
    {
614 96
        return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN;
615
    }
616
617
    /**
618
     * Get the effective column width for columns without a column dimension or column with width -1
619
     * For example, for Calibri 11 this is 9.140625 (64 px).
620
     *
621
     * @param FontStyle $font The workbooks default font
622
     * @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units
623
     *
624
     * @return mixed Column width
625
     */
626 89
    public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false)
627
    {
628 89
        if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) {
629
            // Exact width can be determined
630 89
            $columnWidth = $returnAsPixels ?
631 11
                self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px']
632 89
                    : self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width'];
633
        } else {
634
            // We don't have data for this particular font and size, use approximation by
635
            // extrapolating from Calibri 11
636 1
            $columnWidth = $returnAsPixels ?
637 1
                self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px']
638 1
                    : self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width'];
639 1
            $columnWidth = $columnWidth * $font->getSize() / 11;
640
641
            // Round pixels to closest integer
642 1
            if ($returnAsPixels) {
643 1
                $columnWidth = (int) round($columnWidth);
644
            }
645
        }
646
647 89
        return $columnWidth;
648
    }
649
650
    /**
651
     * Get the effective row height for rows without a row dimension or rows with height -1
652
     * For example, for Calibri 11 this is 15 points.
653
     *
654
     * @param FontStyle $font The workbooks default font
655
     *
656
     * @return float Row height in points
657
     */
658 450
    public static function getDefaultRowHeightByFont(FontStyle $font)
659
    {
660 450
        $name = $font->getName();
661 450
        $size = $font->getSize();
662 450
        if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) {
663 450
            $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height'];
664 1
        } elseif ($name === 'Arial' || $name === 'Verdana') {
665 1
            $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0;
666
        } else {
667 1
            $rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0;
668
        }
669
670 450
        return $rowHeight;
671
    }
672
}
673