Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
75.31% |
61 / 81 |
|
52.94% |
9 / 17 |
CRAP | |
0.00% |
0 / 1 |
AbstractFont | |
75.31% |
61 / 81 |
|
52.94% |
9 / 17 |
85.14 | |
0.00% |
0 / 1 |
__construct | |
73.68% |
14 / 19 |
|
0.00% |
0 / 1 |
10.48 | |||
read | |
60.00% |
6 / 10 |
|
0.00% |
0 / 1 |
12.10 | |||
readFixed | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
readInt | |
55.56% |
5 / 9 |
|
0.00% |
0 / 1 |
5.40 | |||
shiftToSigned | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
5.07 | |||
toEmSpace | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getWidthsForGlyphs | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
4.05 | |||
getStringWidth | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
calcFlags | |
71.43% |
5 / 7 |
|
0.00% |
0 / 1 |
3.21 | |||
offsetSet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetGet | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
offsetExists | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
offsetUnset | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
__set | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__get | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__isset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
__unset | |
0.00% |
0 / 1 |
|
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 | */ |
14 | namespace Pop\Pdf\Build\Font; |
15 | |
16 | use Pop\Utils\ArrayObject as Data; |
17 | use 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 | */ |
29 | abstract 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 | } |