Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.04% covered (success)
87.04%
94 / 108
89.47% covered (success)
89.47%
17 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Parser
87.04% covered (success)
87.04%
94 / 108
89.47% covered (success)
89.47%
17 / 19
54.23
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 loadFromStream
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setFontIndex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFontObjectIndex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFontDescIndex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFontFileIndex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setCompression
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getFont
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFontIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFontObjectIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFontDescIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFontFileIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getObjects
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getFontReference
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFontName
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isEmbeddable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isCompressed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parse
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
1 / 1
8
 getGlyphWidthsFromCmap
55.17% covered (warning)
55.17%
16 / 29
0.00% covered (danger)
0.00%
0 / 1
43.03
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\Pdf\Build\PdfObject\StreamObject;
17
18/**
19 * Pdf font parser 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 Parser
29{
30
31    /**
32     * Font object
33     * @var ?AbstractFont
34     */
35    protected ?AbstractFont $font = null;
36
37    /**
38     * Font reference index
39     * @var ?int
40     */
41    protected ?int $fontIndex = null;
42
43    /**
44     * Font object index
45     * @var ?int
46     */
47    protected ?int $fontObjectIndex = null;
48
49    /**
50     * Font descriptor index
51     * @var ?int
52     */
53    protected ?int $fontDescIndex = null;
54
55    /**
56     * Font file index
57     * @var ?int
58     */
59    protected ?int $fontFileIndex = null;
60
61    /**
62     * Font objects
63     * @var array
64     */
65    protected array $objects = [];
66
67    /**
68     * Font compression flag
69     * @var bool
70     */
71    protected bool $compression = false;
72
73    /**
74     * Constructor
75     *
76     * Instantiate a font parser object
77     *
78     * @param  string $fontFile
79     * @param  bool   $compression
80     * @throws Exception|\Pop\Utils\Exception
81     */
82    public function __construct(string $fontFile, bool $compression = false)
83    {
84        $ext = strtolower(substr($fontFile, (strrpos($fontFile, '.') + 1)));
85        switch ($ext) {
86            case 'ttf':
87                $this->font = new TrueType($fontFile);
88                break;
89            case 'otf':
90                $this->font = new TrueType\OpenType($fontFile);
91                break;
92            case 'pfb':
93                $this->font = new Type1($fontFile);
94                if ($this->font->afmPath === null) {
95                    throw new Exception('The AFM font file was not found.');
96                }
97                break;
98            default:
99                throw new Exception('That font type is not supported.');
100        }
101
102        $this->setCompression($compression);
103    }
104
105    /**
106     * Load font from stream
107     *
108     * @param  string $stream
109     * @return void
110     */
111    public static function loadFromStream(string $stream): void
112    {
113        // TO-DO
114    }
115
116    /**
117     * Set the font index
118     *
119     * @param  int $index
120     * @return Parser
121     */
122    public function setFontIndex(int $index): Parser
123    {
124        $this->fontIndex = $index;
125        return $this;
126    }
127
128    /**
129     * Set the font object index
130     *
131     * @param  int $index
132     * @return Parser
133     */
134    public function setFontObjectIndex(int $index): Parser
135    {
136        $this->fontObjectIndex = $index;
137        return $this;
138    }
139
140    /**
141     * Set the font descriptor index
142     *
143     * @param  int $index
144     * @return Parser
145     */
146    public function setFontDescIndex(int $index): Parser
147    {
148        $this->fontDescIndex = $index;
149        return $this;
150    }
151
152    /**
153     * Set the font file index
154     *
155     * @param  int $index
156     * @return Parser
157     */
158    public function setFontFileIndex(int $index): Parser
159    {
160        $this->fontFileIndex = $index;
161        return $this;
162    }
163
164    /**
165     * Set the compression
166     *
167     * @param  bool $compression
168     * @return Parser
169     */
170    public function setCompression(bool $compression): Parser
171    {
172        $this->compression = $compression;
173        return $this;
174    }
175
176    /**
177     * Get the font object
178     *
179     * @return ?AbstractFont
180     */
181    public function getFont(): ?AbstractFont
182    {
183        return $this->font;
184    }
185
186    /**
187     * Get the font index
188     *
189     * @return ?int
190     */
191    public function getFontIndex(): ?int
192    {
193        return $this->fontIndex;
194    }
195
196    /**
197     * Get the font object index
198     *
199     * @return ?int
200     */
201    public function getFontObjectIndex(): ?int
202    {
203        return $this->fontObjectIndex;
204    }
205
206    /**
207     * Get the font descriptor index
208     *
209     * @return ?int
210     */
211    public function getFontDescIndex(): ?int
212    {
213        return $this->fontDescIndex;
214    }
215
216    /**
217     * Get the font file index
218     *
219     * @return ?int
220     */
221    public function getFontFileIndex(): ?int
222    {
223        return $this->fontFileIndex;
224    }
225
226    /**
227     * Get the font objects
228     *
229     * @return array
230     */
231    public function getObjects(): array
232    {
233        if (count($this->objects) == 0) {
234            $this->parse();
235        }
236        return $this->objects;
237    }
238
239    /**
240     * Method to get the font reference.
241     *
242     * @return string
243     */
244    public function getFontReference(): string
245    {
246        return "/TT{$this->fontIndex} {$this->fontObjectIndex} 0 R";
247    }
248
249    /**
250     * Method to get the font name.
251     *
252     * @return string
253     */
254    public function getFontName(): string
255    {
256        $fontName = ($this->font instanceof Type1) ? $this->font->info->postscriptName :
257            $this->font->tables['name']->postscriptName;
258        return $fontName;
259    }
260
261    /**
262     * Method to get if the font is embeddable.
263     *
264     * @return bool
265     */
266    public function isEmbeddable(): bool
267    {
268        return $this->font->embeddable;
269    }
270
271    /**
272     * Get whether or not the font objects are compressed
273     *
274     * @return bool
275     */
276    public function isCompressed(): bool
277    {
278        return $this->compression;
279    }
280
281    /**
282     * Parse the font data and create the font objects
283     *
284     * @throws Exception
285     * @return void
286     */
287    public function parse(): void
288    {
289        if (($this->fontIndex === null) || ($this->fontObjectIndex === null) ||
290            ($this->fontDescIndex === null) || ($this->fontFileIndex === null)) {
291            throw new Exception('Error: The font indices are not set');
292        }
293
294        if ($this->font instanceof Type1) {
295            $fontType     = 'Type1';
296            $fontName     = $this->font->info->postscriptName;
297            $fontFile     = 'FontFile';
298            $glyphWidths  = ['encoding' => 'StandardEncoding', 'widths' => $this->font->glyphWidths];
299            $unCompStream = $this->font->fontData;
300            $length1      = $this->font->length1;
301            $length2      = " /Length2 " . $this->font->length2 . " /Length3 0";
302        } else {
303            $fontType     = 'TrueType';
304            $fontName     = $this->font->tables['name']->postscriptName;
305            $fontFile     = 'FontFile2';
306            $glyphWidths  = $this->getGlyphWidthsFromCmap($this->font->tables['cmap']);
307            $unCompStream = $this->font->read();
308            $length1      = strlen($unCompStream);
309            $length2      = null;
310        }
311
312        $this->objects[$this->fontObjectIndex] = StreamObject::parse(
313            "{$this->fontObjectIndex} 0 obj\n<<\n    /Type /Font\n    /Subtype /{$fontType}\n    /FontDescriptor " .
314            $this->fontDescIndex . " 0 R\n    /Name /TT{$this->fontIndex}\n    /BaseFont /" . $fontName .
315            "\n    /FirstChar 32\n    /LastChar 255\n    /Widths [" . implode(' ', $glyphWidths['widths']) .
316            "]\n    /Encoding /" . $glyphWidths['encoding'] . "\n>>\nendobj\n\n"
317        );
318
319        $bBox = '[' . $this->font->bBox->xMin . ' ' . $this->font->bBox->yMin . ' ' .
320            $this->font->bBox->xMax . ' ' . $this->font->bBox->yMax . ']';
321
322        if (($this->compression) && function_exists('gzcompress')) {
323            $compStream  = gzcompress($unCompStream, 9);
324            $fontFileObj = "{$this->fontFileIndex} 0 obj\n<</Length " . strlen($compStream) .
325                " /Filter /FlateDecode /Length1 " . $length1 . $length2 . ">>\nstream\n" . $compStream . "\nendstream\nendobj\n\n";
326        } else {
327            $fontFileObj = "{$this->fontFileIndex} 0 obj\n<</Length " . strlen($unCompStream) . " /Length1 " .
328                $length1 . $length2 . ">>\nstream\n" . $unCompStream . "\nendstream\nendobj\n\n";
329        }
330
331        $this->objects[$this->fontDescIndex] = StreamObject::parse(
332            "{$this->fontDescIndex} 0 obj\n<<\n    /Type /FontDescriptor\n    /FontName /" . $fontName .
333            "\n    /{$fontFile} {$this->fontFileIndex} 0 R\n    /MissingWidth {$this->font->missingWidth}\n    /StemV " .
334            $this->font->stemV . "\n    /Flags " . $this->font->calcFlags() . "\n    /FontBBox {$bBox}\n    /Descent " .
335            $this->font->descent . "\n    /Ascent {$this->font->ascent}\n    /CapHeight " . $this->font->capHeight .
336            "\n    /ItalicAngle {$this->font->italicAngle}\n>>\nendobj\n\n"
337        );
338
339        $this->objects[$this->fontFileIndex] = StreamObject::parse($fontFileObj);
340    }
341
342    /**
343     * Method to to get the glyph widths from the CMap
344     *
345     * @param  TrueType\Table\Cmap $cmap
346     * @return array
347     */
348    protected function getGlyphWidthsFromCmap(TrueType\Table\Cmap $cmap): array
349    {
350        $gw       = ['encoding' => null, 'widths' => []];
351        $uniTable = null;
352        $msTable  = null;
353        $macTable = null;
354
355        foreach ($cmap->subTables as $index => $table) {
356            if ($table->encoding == 'Microsoft Unicode') {
357                $msTable = $index;
358            }
359            if ($table->encoding == 'Unicode') {
360                $uniTable = $index;
361            }
362            if (($table->encoding == 'Mac Roman') && ($table->format == 0)) {
363                $macTable = $index;
364            }
365        }
366
367        if ($msTable !== null) {
368            $gw['encoding'] = 'WinAnsiEncoding';
369            foreach ($cmap->subTables[$msTable]->parsed['glyphNumbers'] as $key => $value) {
370                if (isset($this->font->glyphWidths[$value])) {
371                    $gw['widths'][$key] = $this->font->glyphWidths[$value];
372                }
373            }
374        } else if ($uniTable !== null) {
375            $gw['encoding'] = 'WinAnsiEncoding';
376            foreach ($cmap->subTables[$uniTable]->parsed['glyphNumbers'] as $key => $value) {
377                if (isset($this->font->glyphWidths[$value])) {
378                    $gw['widths'][$key] = $this->font->glyphWidths[$value];
379                }
380            }
381        } else if ($macTable !== null) {
382            $gw['encoding'] = 'MacRomanEncoding';
383            foreach ($cmap->subTables[$macTable]->parsed as $key => $value) {
384                if (($this->font->glyphWidths[$value->ascii] != 0) &&
385                    ($this->font->glyphWidths[$value->ascii] != $this->font->missingWidth)) {
386                    if (isset($this->font->glyphWidths[$value->ascii])) {
387                        $gw['widths'][$key] = $this->font->glyphWidths[$value->ascii];
388                    }
389                }
390            }
391        }
392
393        return $gw;
394    }
395
396}