Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.81% covered (success)
89.81%
141 / 157
40.00% covered (warning)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Type1
89.81% covered (success)
89.81%
141 / 157
40.00% covered (warning)
40.00%
2 / 5
48.24
0.00% covered (danger)
0.00%
0 / 1
 __construct
50.00% covered (warning)
50.00%
10 / 20
0.00% covered (danger)
0.00%
0 / 1
19.12
 parsePfb
95.00% covered (success)
95.00%
76 / 80
0.00% covered (danger)
0.00%
0 / 1
18
 parseAfm
95.24% covered (success)
95.24%
40 / 42
0.00% covered (danger)
0.00%
0 / 1
11
 convertToHex
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 strip
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
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-2023 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 * Type1 font class
20 *
21 * @category   Pop
22 * @package    Pop\Pdf
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    4.2.0
27 */
28class Type1 extends AbstractFont
29{
30
31    /**
32     * Font properties
33     * @var array
34     */
35    protected $properties = [
36        'info'             => null,
37        'bBox'             => null,
38        'ascent'           => 0,
39        'descent'          => 0,
40        'numberOfGlyphs'   => 0,
41        'glyphWidths'      => [],
42        'missingWidth'     => 0,
43        'numberOfHMetrics' => 0,
44        'italicAngle'      => 0,
45        'capHeight'        => 0,
46        'stemH'            => 0,
47        'stemV'            => 0,
48        'unitsPerEm'       => 1000,
49        'flags'            => null,
50        'embeddable'       => true,
51        'dict'             => null,
52        'data'             => null,
53        'hex'              => null,
54        'encoding'         => null,
55        'length1'          => null,
56        'length2'          => null,
57        'fontData'         => null,
58        'pfbPath'          => null,
59        'afmPath'          => null,
60    ];
61
62    /**
63     * Constructor
64     *
65     * Instantiate a Type1 font file object based on a pre-existing font file on disk.
66     *
67     * @param  string $fontFile
68     * @param  string $fontStream
69     */
70    public function __construct($fontFile = null, $fontStream = null)
71    {
72        parent::__construct($fontFile, $fontStream);
73
74        $dir = realpath($this->dir);
75
76        if (strtolower($this->extension) == 'pfb') {
77            $this->properties['pfbPath'] = $this->fullpath;
78            $this->parsePfb($this->fullpath);
79            if (file_exists($dir . DIRECTORY_SEPARATOR . $this->filename . '.afm')) {
80                $this->properties['afmPath'] = $dir . DIRECTORY_SEPARATOR . $this->filename . '.afm';
81            } else if (file_exists($dir . DIRECTORY_SEPARATOR . $this->filename . '.AFM')) {
82                $this->properties['afmPath'] = $dir . DIRECTORY_SEPARATOR . $this->filename . '.AFM';
83            }
84            if (null !== $this->properties['afmPath']) {
85                $this->parseAfm($this->properties['afmPath']);
86            }
87        } else if (strtolower($this->extension) == 'afm') {
88            $this->properties['afmPath'] = $this->fullpath;
89            $this->parseAfm($this->properties['afmPath']);
90            if (file_exists($dir . DIRECTORY_SEPARATOR . $this->filename . '.pfb')) {
91                $this->properties['pfbPath'] = $dir . DIRECTORY_SEPARATOR . $this->filename . '.pfb';
92            } else if (file_exists($dir . DIRECTORY_SEPARATOR . $this->filename . '.PFB')) {
93                $this->properties['pfbPath'] = $dir . DIRECTORY_SEPARATOR . $this->filename . '.PFB';
94            }
95            if (null !== $this->properties['pfbPath']) {
96                $this->parsePfb($this->properties['pfbPath']);
97            }
98        }
99    }
100
101    /**
102     * Method to parse the Type1 PFB file.
103     *
104     * @param  string $pfb
105     * @return void
106     */
107    protected function parsePfb($pfb)
108    {
109        $data = file_get_contents($pfb);
110
111        // Get lengths and data
112        $f = fopen($pfb, 'rb');
113        $a = unpack('Cmarker/Ctype/Vsize', fread($f,6));
114        $this->properties['length1'] = $a['size'];
115        $this->properties['fontData'] = fread($f, $this->properties['length1']);
116        $a = unpack('Cmarker/Ctype/Vsize', fread($f,6));
117        $this->properties['length2'] = $a['size'];
118        $this->properties['fontData'] .= fread($f, $this->properties['length2']);
119
120        $info = [];
121        $this->properties['dict'] = substr($data, stripos($data, 'FontDirectory'));
122        $this->properties['dict'] = substr($this->properties['dict'], 0, stripos($this->properties['dict'], 'currentdict end'));
123
124        $this->properties['data'] = substr($data, (stripos($data, 'currentfile eexec') + 18));
125        $this->properties['data'] = substr(
126            $this->properties['data'], 0,
127            (stripos($this->properties['data'], '0000000000000000000000000000000000000000000000000000000000000000') - 1)
128        );
129
130        $this->convertToHex();
131
132        if (stripos($this->properties['dict'], '/FullName') !== false) {
133            $name = substr($this->properties['dict'], (stripos($this->properties['dict'], '/FullName ') + 10));
134            $name = trim(substr($name, 0, stripos($name, 'readonly def')));
135            $info['fullName'] = $this->strip($name);
136        }
137
138        if (stripos($this->properties['dict'], '/FamilyName') !== false) {
139            $family = substr($this->properties['dict'], (stripos($this->properties['dict'], '/FamilyName ') + 12));
140            $family = trim(substr($family, 0, stripos($family, 'readonly def')));
141            $info['fontFamily'] = $this->strip($family);
142        }
143
144        if (stripos($this->properties['dict'], '/FontName') !== false) {
145            $font = substr($this->properties['dict'], (stripos($this->properties['dict'], '/FontName ') + 10));
146            $font = trim(substr($font, 0, stripos($font, 'def')));
147            $info['postscriptName'] = $this->strip($font);
148        }
149
150        if (stripos($this->properties['dict'], '/version') !== false) {
151            $version = substr($this->properties['dict'], (stripos($this->properties['dict'], '/version ') + 9));
152            $version = trim(substr($version, 0, stripos($version, 'readonly def')));
153            $info['version'] = $this->strip($version);
154        }
155
156        if (stripos($this->properties['dict'], '/UniqueId') !== false) {
157            $matches = [];
158            preg_match('/UniqueID\s\d/', $this->properties['dict'], $matches, PREG_OFFSET_CAPTURE);
159            $id = substr($this->properties['dict'], ($matches[0][1] + 9));
160            $id = trim(substr($id, 0, stripos($id, 'def')));
161            $info['uniqueId'] = $this->strip($id);
162        }
163
164        if (stripos($this->properties['dict'], '/Notice') !== false) {
165            $copyright = substr($this->properties['dict'], (stripos($this->properties['dict'], '/Notice ') + 8));
166            $copyright = substr($copyright, 0, stripos($copyright, 'readonly def'));
167            $copyright = str_replace('\\(', '(', $copyright);
168            $copyright = trim(str_replace('\\)', ')', $copyright));
169            $info['copyright'] = $this->strip($copyright);
170        }
171
172        $this->properties['info'] = new Data($info);
173
174        if (stripos($this->properties['dict'], '/FontBBox') !== false) {
175            $bBox = substr($this->properties['dict'], (stripos($this->properties['dict'], '/FontBBox') + 9));
176            $bBox = substr($bBox, 0, stripos($bBox, 'readonly def'));
177            $bBox = trim($this->strip($bBox));
178            $bBoxAry = explode(' ', $bBox);
179            $this->properties['bBox'] = new Data([
180                'xMin' => str_replace('{', '', $bBoxAry[0]),
181                'yMin' => $bBoxAry[1],
182                'xMax' => $bBoxAry[2],
183                'yMax' => str_replace('}', '', $bBoxAry[3])
184            ]);
185        }
186
187        if (stripos($this->properties['dict'], '/Ascent') !== false) {
188            $ascent = substr($this->properties['dict'], (stripos($this->properties['dict'], '/ascent ') + 8));
189            $this->properties['ascent'] = trim(substr($ascent, 0, stripos($ascent, 'def')));
190        }
191
192        if (stripos($this->properties['dict'], '/Descent') !== false) {
193            $descent = substr($this->properties['dict'], (stripos($this->properties['dict'], '/descent ') + 9));
194            $this->properties['descent'] = trim(substr($descent, 0, stripos($descent, 'def')));
195        }
196
197        if (stripos($this->properties['dict'], '/ItalicAngle') !== false) {
198            $italic = substr($this->properties['dict'], (stripos($this->properties['dict'], '/ItalicAngle ') + 13));
199            $this->properties['italicAngle'] = trim(substr($italic, 0, stripos($italic, 'def')));
200            if ($this->properties['italicAngle'] != 0) {
201                $this->properties['flags']->isItalic = true;
202            }
203        }
204
205        if (stripos($this->properties['dict'], '/em') !== false) {
206            $units = substr($this->properties['dict'], (stripos($this->properties['dict'], '/em ') + 4));
207            $this->properties['unitsPerEm'] = trim(substr($units, 0, stripos($units, 'def')));
208        }
209
210        if (stripos($this->properties['dict'], '/isFixedPitch') !== false) {
211            $fixed = substr($this->properties['dict'], (stripos($this->properties['dict'], '/isFixedPitch ') + 14));
212            $fixed = trim(substr($fixed, 0, stripos($fixed, 'def')));
213            $this->properties['flags']->isFixedPitch = ($fixed == 'true') ? true : false;
214        }
215
216        if (stripos($this->properties['dict'], '/ForceBold') !== false) {
217            $force = substr($this->properties['dict'], (stripos($this->properties['dict'], '/ForceBold ') + 11));
218            $force = trim(substr($force, 0, stripos($force, 'def')));
219            $this->properties['flags']->isForceBold = ($force == 'true') ? true : false;
220        }
221
222        if (stripos($this->properties['dict'], '/Encoding') !== false) {
223            $enc = substr($this->properties['dict'], (stripos($this->properties['dict'], '/Encoding ') + 10));
224            $this->properties['encoding'] = trim(substr($enc, 0, stripos($enc, 'def')));
225        }
226    }
227
228    /**
229     * Method to parse the Type1 Adobe Font Metrics file
230     *
231     * @param  string $afm
232     * @return void
233     */
234    protected function parseAfm($afm)
235    {
236        $data = file_get_contents($afm);
237
238        if (stripos($data, 'FontBBox') !== false) {
239            $bBox = substr($data, (stripos($data, 'FontBBox') + 8));
240            $bBox = substr($bBox, 0, stripos($bBox, "\n"));
241            $bBox = trim($bBox);
242            $bBoxAry = explode(' ', $bBox);
243            $this->properties['bBox'] = new Data([
244                'xMin' => $bBoxAry[0],
245                'yMin' => $bBoxAry[1],
246                'xMax' => $bBoxAry[2],
247                'yMax' => $bBoxAry[3]
248            ]);
249        }
250
251        if (stripos($data, 'ItalicAngle') !== false) {
252            $ital = substr($data, (stripos($data, 'ItalicAngle ') + 11));
253            $this->properties['italicAngle'] = trim(substr($ital, 0, stripos($ital, "\n")));
254            if ($this->properties['italicAngle'] != 0) {
255                $this->properties['flags']->isItalic = true;
256            }
257        }
258
259        if (stripos($data, 'IsFixedPitch') !== false) {
260            $fixed = substr($data, (stripos($data, 'IsFixedPitch ') + 13));
261            $fixed = strtolower(trim(substr($fixed, 0, stripos($fixed, "\n"))));
262            if ($fixed == 'true') {
263                $this->properties['flags']->isFixedPitch = true;
264            }
265        }
266
267        if (stripos($data, 'CapHeight') !== false) {
268            $cap = substr($data, (stripos($data, 'CapHeight ') + 10));
269            $this->properties['capHeight'] = trim(substr($cap, 0, stripos($cap, "\n")));
270        }
271
272        if (stripos($data, 'Ascender') !== false) {
273            $asc = substr($data, (stripos($data, 'Ascender ') + 9));
274            $this->properties['ascent'] = trim(substr($asc, 0, stripos($asc, "\n")));
275        }
276
277        if (stripos($data, 'Descender') !== false) {
278            $desc = substr($data, (stripos($data, 'Descender ') + 10));
279            $this->properties['descent'] = trim(substr($desc, 0, stripos($desc, "\n")));
280        }
281
282        if (stripos($data, 'StartCharMetrics') !== false) {
283            $num = substr($data, (stripos($data, 'StartCharMetrics ') + 17));
284            $this->properties['numberOfGlyphs'] = trim(substr($num, 0, stripos($num, "\n")));
285            $chars = substr($data, (stripos($data, 'StartCharMetrics ') + 17 + strlen($this->properties['numberOfGlyphs'])));
286            $chars = trim(substr($chars, 0, stripos($chars, 'EndCharMetrics')));
287            $glyphs = explode("\n", $chars);
288            $widths = [];
289            foreach ($glyphs as $glyph) {
290                $w = substr($glyph, (stripos($glyph, 'WX ') + 3));
291                $w = substr($w, 0, strpos($w, ' ;'));
292                $widths[] = $w;
293            }
294            $this->properties['glyphWidths'] = $widths;
295        }
296    }
297
298    /**
299     * Method to convert the data string to hex.
300     *
301     * @return void
302     */
303    protected function convertToHex()
304    {
305        $ary = str_split($this->properties['data']);
306        $length = count($ary);
307
308        for ($i = 0; $i < $length; $i++) {
309            $this->properties['hex'] .= bin2hex($ary[$i]);
310        }
311    }
312
313    /**
314     * Method to strip parentheses et al from a string.
315     *
316     * @param  string $str
317     * @return string
318     */
319    protected function strip($str)
320    {
321        // Strip parentheses
322        if (substr($str, 0, 1) == '(') {
323            $str = substr($str, 1);
324        }
325        if (substr($str, -1) == ')') {
326            $str = substr($str, 0, -1);
327        }
328        // Strip curly brackets
329        if (substr($str, 0, 1) == '{') {
330            $str = substr($str, 1);
331        }
332        if (substr($str, -1) == '}') {
333            $str = substr($str, 0, -1);
334        }
335        // Strip leading slash
336        if (substr($str, 0, 1) == '/') {
337            $str = substr($str, 1);
338        }
339
340        return $str;
341    }
342
343}