Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.91% covered (success)
90.91%
90 / 99
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
TrueType
90.91% covered (success)
90.91%
90 / 99
60.00% covered (warning)
60.00%
3 / 5
28.59
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 parseTtfTable
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
2
 parseName
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 parseCommonTables
93.48% covered (success)
93.48%
43 / 46
0.00% covered (danger)
0.00%
0 / 1
16.07
 parseRequiredTables
45.45% covered (warning)
45.45%
5 / 11
0.00% covered (danger)
0.00%
0 / 1
6.60
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Pdf\Build\Font;
15
16use Pop\Utils\ArrayObject as Data;
17
18/**
19 * TrueType font class
20 *
21 * @category   Pop
22 * @package    Pop\Pdf
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    5.0.0
27 */
28class TrueType extends AbstractFont
29{
30
31    /**
32     * Font properties
33     * @var array
34     */
35    protected array $properties = [
36        'info'             => null,
37        'bBox'             => null,
38        'ascent'           => 0,
39        'descent'          => 0,
40        'numberOfGlyphs'   => 0,
41        'glyphWidths'      => [],
42        'rawGlyphWidths'   => [],
43        'cmap'             => ['glyphIndexArray' => [], 'glyphNumbers' => []],
44        'missingWidth'     => 0,
45        'numberOfHMetrics' => 0,
46        'italicAngle'      => 0,
47        'capHeight'        => 0,
48        'stemH'            => 0,
49        'stemV'            => 0,
50        'unitsPerEm'       => 1000,
51        'flags'            => null,
52        'embeddable'       => true,
53        'header'           => null,
54        'ttfHeader'        => null,
55        'ttfTable'         => null,
56        'tables'           => [],
57        'tableInfo'        => []
58    ];
59
60    /**
61     * Constructor
62     *
63     * Instantiate a TrueType font file object based on a pre-existing font file on disk.
64     *
65     * @param  ?string $fontFile
66     * @param  ?string $fontStream
67     * @throws Exception|\Pop\Utils\Exception
68     */
69    public function __construct(?string $fontFile = null, ?string $fontStream = null)
70    {
71        parent::__construct($fontFile, $fontStream);
72
73        $this->parseTtfTable();
74        $this->parseName();
75        $this->parseCommonTables();
76        $this->parseRequiredTables();
77    }
78
79    /**
80     * Method to parse the TTF header and table of the TrueType font file.
81     *
82     * @return void
83     */
84    protected function parseTtfTable(): void
85    {
86        $ttfHeader = unpack(
87            'nmajorVersion/' .
88            'nminorVersion/' .
89            'nnumberOfTables/' .
90            'nsearchRange/' .
91            'nentrySelector/' .
92            'nrangeShift', $this->read(0, 12)
93        );
94
95        $tableName = $this->read(12, 4);
96
97        $ttfTable = unpack(
98            'Nchecksum/' .
99            'Noffset/' .
100            'Nlength', $this->read(16, 12)
101        );
102
103        $ttfTable['name'] = $tableName;
104
105        $this->properties['ttfHeader'] = new Data($ttfHeader);
106        $this->properties['ttfTable']  = new Data($ttfTable);
107
108        $nameByteOffset  = 28;
109        $tableByteOffset = 32;
110
111        for ($i = 0; $i < $this->properties['ttfHeader']['numberOfTables']; $i++) {
112            $ttfTableName = $this->read($nameByteOffset, 4);
113            $ttfTable     = unpack(
114                'Nchecksum/' .
115                'Noffset/' .
116                'Nlength', $this->read($tableByteOffset, 12)
117            );
118
119            $this->properties['tableInfo'][trim($ttfTableName)] = new Data($ttfTable);
120
121            $nameByteOffset = $tableByteOffset + 12;
122            $tableByteOffset = $nameByteOffset + 4;
123        }
124    }
125
126    /**
127     * Method to parse the TTF info of the TrueType font file from the name table.
128     *
129     * @return void
130     */
131    protected function parseName(): void
132    {
133        if (isset($this->properties['tableInfo']['name'])) {
134            $this->properties['tables']['name'] = new TrueType\Table\Name($this);
135            $this->properties['info'] = $this->properties['tables']['name'];
136            if ((stripos($this->properties['tables']['name']['fontFamily'], 'bold') !== false) ||
137                (stripos($this->properties['tables']['name']['fullName'], 'bold') !== false) ||
138                (stripos($this->properties['tables']['name']['postscriptName'], 'bold') !== false)) {
139                $this->properties['stemV'] = 120;
140            } else {
141                $this->properties['stemV'] = 70;
142            }
143        }
144    }
145
146    /**
147     * Method to parse the common tables of the TrueType font file.
148     *
149     * @return void
150     */
151    protected function parseCommonTables(): void
152    {
153        // head
154        if (isset($this->properties['tableInfo']['head'])) {
155            $this->properties['tables']['head'] = new TrueType\Table\Head($this);
156
157            $this->properties['unitsPerEm'] = $this->properties['tables']['head']['unitsPerEm'];
158
159            $this->properties['tables']['head']['xMin'] = $this->toEmSpace($this->properties['tables']['head']['xMin']);
160            $this->properties['tables']['head']['yMin'] = $this->toEmSpace($this->properties['tables']['head']['yMin']);
161            $this->properties['tables']['head']['xMax'] = $this->toEmSpace($this->properties['tables']['head']['xMax']);
162            $this->properties['tables']['head']['yMax'] = $this->toEmSpace($this->properties['tables']['head']['yMax']);
163
164            $this->properties['bBox'] = new Data([
165                'xMin' => $this->properties['tables']['head']['xMin'],
166                'yMin' => $this->properties['tables']['head']['yMin'],
167                'xMax' => $this->properties['tables']['head']['xMax'],
168                'yMax' => $this->properties['tables']['head']['yMax']
169            ]);
170
171            $this->properties['header'] = $this->properties['tables']['head'];
172        }
173
174        // hhea
175        if (isset($this->properties['tableInfo']['hhea'])) {
176            $this->properties['tables']['hhea'] = new TrueType\Table\Hhea($this);
177            $this->properties['ascent']           = $this->properties['tables']['hhea']['ascent'];
178            $this->properties['descent']          = $this->properties['tables']['hhea']['descent'];
179            $this->properties['capHeight']        = $this->properties['ascent'] + $this->properties['descent'];
180            $this->properties['numberOfHMetrics'] = $this->properties['tables']['hhea']['numberOfHMetrics'];
181        }
182
183        // maxp
184        if (isset($this->properties['tableInfo']['maxp'])) {
185            $this->properties['tables']['maxp'] = new TrueType\Table\Maxp($this);
186            $this->properties['numberOfGlyphs'] = $this->properties['tables']['maxp']['numberOfGlyphs'];
187        }
188
189        // post
190        if (isset($this->properties['tableInfo']['post'])) {
191            $this->properties['tables']['post'] = new TrueType\Table\Post($this);
192
193            if ($this->properties['tables']['post']['italicAngle'] != 0) {
194                $this->properties['flags']['isItalic'] = true;
195                $this->properties['italicAngle'] = $this->properties['tables']['post']['italicAngle'];
196            }
197
198            if ($this->properties['tables']['post']['fixed'] != 0) {
199                $this->properties['flags']['isFixedPitch'] = true;
200            }
201        }
202
203        // hmtx
204        if (isset($this->properties['tableInfo']['hmtx'])) {
205            $this->properties['tables']['hmtx'] = new TrueType\Table\Hmtx($this);
206            $this->properties['glyphWidths'] = $this->properties['tables']['hmtx']['glyphWidths'];
207            if (isset($this->properties['glyphWidths'][0])) {
208                $this->properties['missingWidth'] = round((1000 / $this->properties['unitsPerEm']) * $this->properties['glyphWidths'][0]);
209            }
210
211            foreach ($this->properties['glyphWidths'] as $key => $value) {
212                $this->properties['rawGlyphWidths'][$key] = $value;
213                $this->properties['glyphWidths'][$key]    = round((1000 / $this->properties['unitsPerEm']) * $value);
214            }
215        }
216
217        // cmap
218        if (isset($this->properties['tableInfo']['cmap'])) {
219            $this->properties['tables']['cmap'] = new TrueType\Table\Cmap($this);
220            if (isset($this->properties['tables']['cmap']['subTables']) && isset($this->properties['tables']['cmap']['subTables'][0]) &&
221                isset($this->properties['tables']['cmap']['subTables'][0]['parsed'])) {
222                if (isset($this->properties['tables']['cmap']['subTables'][0]['parsed']['glyphIndexArray'])) {
223                    $this->properties['cmap']['glyphIndexArray'] = $this->properties['tables']['cmap']['subTables'][0]['parsed']['glyphIndexArray'];
224                }
225                if (isset($this->properties['tables']['cmap']['subTables'][0]['parsed']['glyphNumbers'])) {
226                    $this->properties['cmap']['glyphNumbers'] = $this->properties['tables']['cmap']['subTables'][0]['parsed']['glyphNumbers'];
227                }
228            }
229        }
230    }
231
232    /**
233     * Method to parse the required tables of the TrueType font file.
234     *
235     * @return void
236     */
237    protected function parseRequiredTables(): void
238    {
239        // loca
240        if (isset($this->properties['tableInfo']['loca'])) {
241            $this->properties['tables']['loca'] = new TrueType\Table\Loca($this);
242        }
243
244        // glyf
245        if (isset($this->properties['tableInfo']['glyf'])) {
246            $this->properties['tables']['glyf'] = new TrueType\Table\Glyf($this);
247        }
248
249        // OS/2 (Optional in a TTF font file)
250        if (isset($this->properties['tableInfo']['OS/2'])) {
251            $this->properties['tables']['OS/2']         = new TrueType\Table\Os2($this);
252            $this->properties['flags']['isSerif']       = $this->properties['tables']['OS/2']['flags']['isSerif'];
253            $this->properties['flags']['isScript']      = $this->properties['tables']['OS/2']['flags']['isScript'];
254            $this->properties['flags']['isSymbolic']    = $this->properties['tables']['OS/2']['flags']['isSymbolic'];
255            $this->properties['flags']['isNonSymbolic'] = $this->properties['tables']['OS/2']['flags']['isNonSymbolic'];
256            $this->properties['embeddable']             = $this->properties['tables']['OS/2']['embeddable'];
257        }
258    }
259
260}