Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
89.81% |
141 / 157 |
|
40.00% |
2 / 5 |
CRAP | |
0.00% |
0 / 1 |
Type1 | |
89.81% |
141 / 157 |
|
40.00% |
2 / 5 |
48.24 | |
0.00% |
0 / 1 |
__construct | |
50.00% |
10 / 20 |
|
0.00% |
0 / 1 |
19.12 | |||
parsePfb | |
95.00% |
76 / 80 |
|
0.00% |
0 / 1 |
18 | |||
parseAfm | |
95.24% |
40 / 42 |
|
0.00% |
0 / 1 |
11 | |||
convertToHex | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
strip | |
100.00% |
11 / 11 |
|
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 | */ |
14 | namespace Pop\Pdf\Build\Font; |
15 | |
16 | use 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 | */ |
28 | class 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 | } |