Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.31% covered (success)
75.31%
61 / 81
52.94% covered (warning)
52.94%
9 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractFont
75.31% covered (success)
75.31%
61 / 81
52.94% covered (warning)
52.94%
9 / 17
85.14
0.00% covered (danger)
0.00%
0 / 1
 __construct
73.68% covered (success)
73.68%
14 / 19
0.00% covered (danger)
0.00%
0 / 1
10.48
 read
60.00% covered (warning)
60.00%
6 / 10
0.00% covered (danger)
0.00%
0 / 1
12.10
 readFixed
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 readInt
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
5.40
 shiftToSigned
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
5.07
 toEmSpace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getWidthsForGlyphs
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 getStringWidth
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 calcFlags
71.43% covered (success)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 offsetSet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetGet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 offsetExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 offsetUnset
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 __set
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __isset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __unset
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
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;
17use ReturnTypeWillChange;
18
19/**
20 * Font abstract class
21 *
22 * @category   Pop
23 * @package    Pop\Pdf
24 * @author     Nick Sagona, III <dev@nolainteractive.com>
25 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
26 * @license    http://www.popphp.org/license     New BSD License
27 * @version    4.2.0
28 */
29abstract class AbstractFont implements \ArrayAccess
30{
31
32    /**
33     * Font properties
34     * @var array
35     */
36    protected $properties = [
37        'info'             => null,
38        'bBox'             => null,
39        'ascent'           => 0,
40        'descent'          => 0,
41        'numberOfGlyphs'   => 0,
42        'glyphWidths'      => [],
43        'missingWidth'     => 0,
44        'numberOfHMetrics' => 0,
45        'italicAngle'      => 0,
46        'capHeight'        => 0,
47        'stemH'            => 0,
48        'stemV'            => 0,
49        'unitsPerEm'       => 1000,
50        'flags'            => null,
51        'embeddable'       => true,
52    ];
53
54    /**
55     * Read-only properties
56     * @var array
57     */
58    protected $readOnly = [];
59
60    /**
61     * Array of allowed file types.
62     * @var array
63     */
64    protected $allowedTypes = [
65        'afm' => 'application/x-font-afm',
66        'otf' => 'application/x-font-otf',
67        'pfb' => 'application/x-font-pfb',
68        'pfm' => 'application/x-font-pfm',
69        'ttf' => 'application/x-font-ttf'
70    ];
71
72    /**
73     * Full path of font file, i.e. '/path/to/fontfile.ext'
74     * @var string
75     */
76    protected $fullpath = null;
77
78    /**
79     * Full, absolute directory of the font file, i.e. '/some/dir/'
80     * @var string
81     */
82    protected $dir = null;
83
84    /**
85     * Full basename of font file, i.e. 'fontfile.ext'
86     * @var string
87     */
88    protected $basename = null;
89
90    /**
91     * Full filename of font file, i.e. 'fontfile'
92     * @var string
93     */
94    protected $filename = null;
95
96    /**
97     * Font file extension, i.e. 'ext'
98     * @var string
99     */
100    protected $extension = null;
101
102    /**
103     * Font file size in bytes
104     * @var int
105     */
106    protected $size = 0;
107
108    /**
109     * Font file mime type
110     * @var string
111     */
112    protected $mime = 'text/plain';
113
114    /**
115     * Font stream
116     * @var string
117     */
118    protected $stream = null;
119
120    /**
121     * Constructor
122     *
123     * Instantiate a font file object based on a pre-existing font file on disk.
124     *
125     * @param  string $fontFile
126     * @param  string $fontStream
127     * @throws Exception
128     */
129    public function __construct($fontFile = null, $fontStream = null)
130    {
131        $this->properties['flags'] = new Data([
132            'isFixedPitch'  => false,
133            'isSerif'       => false,
134            'isSymbolic'    => false,
135            'isScript'      => false,
136            'isNonSymbolic' => false,
137            'isItalic'      => false,
138            'isAllCap'      => false,
139            'isSmallCap'    => false,
140            'isForceBold'   => false
141        ]);
142
143        if (null !== $fontFile) {
144            if (!file_exists($fontFile)) {
145                throw new Exception('The font file does not exist.');
146            }
147
148            $this->fullpath  = $fontFile;
149            $parts           = pathinfo($fontFile);
150            $this->size      = filesize($fontFile);
151            $this->dir       = realpath($parts['dirname']);
152            $this->basename  = $parts['basename'];
153            $this->filename  = $parts['filename'];
154            $this->extension = (isset($parts['extension']) && ($parts['extension'] != '')) ? $parts['extension'] : null;
155
156            if (null === $this->extension) {
157                throw new Exception('Error: That font file does not have an extension.');
158            }
159
160            if ((null !== $this->extension) && !isset($this->allowedTypes[strtolower($this->extension)])) {
161                throw new Exception('Error: That font file type is not allowed.');
162            }
163
164            $this->mime = $this->allowedTypes[strtolower($this->extension)];
165        } else if (null !== $fontStream) {
166            $this->stream = $fontStream;
167        } else {
168            throw new Exception('Error: You must pass either a font file or font stream.');
169        }
170    }
171
172    /**
173     * Read data from the font file.
174     *
175     * @param  int $offset
176     * @param  int $length
177     * @return string
178     */
179    public function read($offset = null, $length = null)
180    {
181        if (null !== $offset) {
182            if (null !== $this->stream) {
183                $data = ((null !== $length) && ((int)$length >= 0)) ?
184                    substr($this->stream, $offset, $length) :
185                    substr($this->stream, $offset);
186            } else {
187                $data = ((null !== $length) && ((int)$length >= 0)) ?
188                    file_get_contents($this->fullpath, false, null, $offset, $length) :
189                    file_get_contents($this->fullpath, false, null, $offset);
190            }
191        } else {
192            $data = (null !== $this->stream) ? $this->stream : file_get_contents($this->fullpath);
193        }
194
195        return $data;
196    }
197
198    /**
199     * Static method to read and return a fixed-point number
200     *
201     * @param  int    $mantissaBits
202     * @param  int    $fractionBits
203     * @param  string $bytes
204     * @return int
205     */
206    public function readFixed($mantissaBits, $fractionBits, $bytes)
207    {
208        $bitsToRead = $mantissaBits + $fractionBits;
209        $number = $this->readInt(($bitsToRead >> 3), $bytes) / (1 << $fractionBits);
210        return $number;
211    }
212
213    /**
214     * Static method to read and return a signed integer
215     *
216     * @param  int    $size
217     * @param  string $bytes
218     * @return int
219     */
220    public function readInt($size, $bytes)
221    {
222        $number = ord($bytes[0]);
223
224        if (($number & 0x80) == 0x80) {
225            $number = (~ $number) & 0xff;
226            for ($i = 1; $i < $size; $i++) {
227                $number = ($number << 8) | ((~ ord($bytes[$i])) & 0xff);
228            }
229            $number = ~$number;
230        } else {
231            for ($i = 1; $i < $size; $i++) {
232                $number = ($number << 8) | ord($bytes[$i]);
233            }
234        }
235
236        return $number;
237    }
238
239    /**
240     * Method to shift an unpacked signed short from little endian to big endian
241     *
242     * @param  int|array $values
243     * @return int|array
244     */
245    public function shiftToSigned($values)
246    {
247        if (is_array($values)) {
248            foreach ($values as $key => $value) {
249                if ($value >= pow(2, 15)) {
250                    $values[$key] -= pow(2, 16);
251                }
252            }
253        } else {
254            if ($values >= pow(2, 15)) {
255                $values -= pow(2, 16);
256            }
257        }
258
259        return $values;
260    }
261
262    /**
263     * Method to convert a value to the representative value in EM.
264     *
265     * @param int $value
266     * @return int
267     */
268    public function toEmSpace($value)
269    {
270        return ($this->properties['unitsPerEm'] == 1000) ? $value : ceil(($value / $this->properties['unitsPerEm']) * 1000);
271    }
272
273    /**
274     * Get the widths for the glyphs
275     *
276     * @param  array $glyphs
277     * @return array
278     */
279    public function getWidthsForGlyphs(array $glyphs)
280    {
281        $widths = [];
282
283        foreach ($glyphs as $glyph) {
284            if (isset($this->properties['cmap']['glyphNumbers'][$glyph]) &&
285                isset($this->properties['rawGlyphWidths'][$this->properties['cmap']['glyphNumbers'][$glyph]])) {
286                $widths[] = $this->properties['rawGlyphWidths'][$this->properties['cmap']['glyphNumbers'][$glyph]];
287            } else {
288                $widths[] = $this->properties['missingWidth'];
289            }
290        }
291
292        return $widths;
293    }
294
295    /**
296     * Attempt to get string width
297     *
298     * @param  string $string
299     * @param  mixed  $size
300     * @return mixed
301     */
302    public function getStringWidth($string, $size)
303    {
304        $width = null;
305
306        $drawingString = iconv('UTF-8', 'UTF-16BE//IGNORE', $string);
307        $characters    = [];
308
309        for ($i = 0; $i < strlen($drawingString); $i++) {
310            $characters[] = (ord($drawingString[$i++]) << 8 ) | ord($drawingString[$i]);
311        }
312
313        if (count($this->properties['rawGlyphWidths']) > 0) {
314            $widths = $this->getWidthsForGlyphs($characters);
315            $width  = (array_sum($widths) / $this->properties['unitsPerEm']) * $size;
316        }
317
318        return $width;
319    }
320
321    /**
322     * Method to calculate the font flags
323     *
324     * @return int
325     */
326    public function calcFlags()
327    {
328        $flags = 0;
329
330        if ($this->properties['flags']['isFixedPitch']) {
331            $flags += 1 << 0;
332        }
333
334        $flags += 1 << 5;
335
336        if ($this->properties['flags']['isItalic']) {
337            $flags += 1 << 6;
338        }
339
340        return $flags;
341    }
342
343    /**
344     * Offset set method
345     *
346     * @param  string $name
347     * @param  mixed  $value
348     * @return void
349     */
350    #[ReturnTypeWillChange]
351    public function offsetSet($name, $value)
352    {
353        $this->properties[$name] = $value;
354    }
355
356    /**
357     * Offset get method
358     *
359     * @param  string $name
360     * @throws \InvalidArgumentException
361     * @return mixed
362     */
363    #[ReturnTypeWillChange]
364    public function offsetGet($name)
365    {
366        return (isset($this->properties[$name])) ? $this->properties[$name] : null;
367    }
368
369    /**
370     * Offset exists method
371     *
372     * @param  mixed $offset
373     * @return boolean
374     */
375    public function offsetExists($offset): bool
376    {
377        return isset($this->properties[$offset]);
378    }
379
380    /**
381     * Offset unset method
382     *
383     * @param  mixed $offset
384     * @return void
385     */
386    #[ReturnTypeWillChange]
387    public function offsetUnset($offset)
388    {
389        if (isset($this->properties[$offset])) {
390            unset($this->properties[$offset]);
391        }
392    }
393
394    /**
395     * Set method
396     *
397     * @param  string $name
398     * @param  mixed $value
399     * @return void
400     */
401    public function __set($name, $value)
402    {
403        $this->offsetSet($name, $value);
404    }
405
406    /**
407     * Get method
408     *
409     * @param  string $name
410     * @return mixed
411     */
412    public function __get($name)
413    {
414        return $this->offsetGet($name);
415    }
416    /**
417     * Isset method
418     *
419     * @param  string $name
420     * @return boolean
421     */
422    public function __isset($name)
423    {
424        return $this->offsetExists($name);
425    }
426    /**
427     * Unset fields[$name]
428     *
429     * @param  string $name
430     * @return void
431     */
432    public function __unset($name)
433    {
434        $this->offsetUnset($name);
435    }
436
437}