Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
78.12% |
125 / 160 |
|
64.00% |
16 / 25 |
CRAP | |
0.00% |
0 / 1 |
PageObject | |
78.12% |
125 / 160 |
|
64.00% |
16 / 25 |
93.21 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
parse | |
79.49% |
62 / 78 |
|
0.00% |
0 / 1 |
24.81 | |||
setParentIndex | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setWidth | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setHeight | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setAnnots | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setContent | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setXObjects | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
setFonts | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
setCurrentContentIndex | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
addAnnotIndex | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
addContentIndex | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
addXObjectReference | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
addFontReference | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getParentIndex | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getWidth | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getHeight | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCurrentContentIndex | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getAnnots | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getContent | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getXObjects | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFonts | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasAnnot | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasContent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
__toString | |
87.88% |
29 / 33 |
|
0.00% |
0 / 1 |
11.22 |
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 | */ |
14 | namespace Pop\Pdf\Build\PdfObject; |
15 | |
16 | /** |
17 | * Pdf page object class |
18 | * |
19 | * @category Pop |
20 | * @package Pop\Pdf |
21 | * @author Nick Sagona, III <dev@nolainteractive.com> |
22 | * @copyright Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com) |
23 | * @license http://www.popphp.org/license New BSD License |
24 | * @version 5.0.0 |
25 | */ |
26 | class PageObject extends AbstractObject |
27 | { |
28 | |
29 | /** |
30 | * PDF page object index |
31 | * @var ?int |
32 | */ |
33 | protected ?int $index = 4; |
34 | |
35 | /** |
36 | * PDF page object parent index |
37 | * @var ?int |
38 | */ |
39 | protected ?int $parent = 2; |
40 | |
41 | /** |
42 | * PDF page object width |
43 | * @var int |
44 | */ |
45 | protected int $width = 612; |
46 | |
47 | /** |
48 | * PDF page object height |
49 | * @var int |
50 | */ |
51 | protected int $height = 792; |
52 | |
53 | /** |
54 | * PDF page object current content object index |
55 | * @var ?int |
56 | */ |
57 | protected ?int $currentContentIndex = null; |
58 | |
59 | /** |
60 | * PDF page object annotation object indices |
61 | * @var array |
62 | */ |
63 | protected array $annots = []; |
64 | |
65 | /** |
66 | * PDF page object content object indices |
67 | * @var array |
68 | */ |
69 | protected array $content = []; |
70 | |
71 | /** |
72 | * PDF page object XObject references |
73 | * @var array |
74 | */ |
75 | protected array $xObjects = []; |
76 | |
77 | /** |
78 | * PDF page object font object references |
79 | * @var array |
80 | */ |
81 | protected array $fonts = []; |
82 | |
83 | /** |
84 | * Constructor |
85 | * |
86 | * Instantiate a PDF page object, defaults to letter size. |
87 | * |
88 | * @param mixed $width |
89 | * @param mixed $height |
90 | * @param int $index |
91 | */ |
92 | public function __construct(mixed $width = 612, mixed$height = 792, int $index = 4) |
93 | { |
94 | $this->setWidth($width); |
95 | $this->setHeight($height); |
96 | $this->setIndex($index); |
97 | $this->setData("\n[{page_index}] 0 obj\n<</Type/Page/Parent [{parent}] 0 R[{annotations}]/MediaBox[0 0 " . |
98 | "[{width}] [{height}]][{content_objects}]/Resources" . |
99 | "<</ProcSet[/PDF/Text/ImageB/ImageC/ImageI][{xobjects}][{fonts}]>>>>\nendobj\n"); |
100 | } |
101 | |
102 | /** |
103 | * Parse a page object from a string |
104 | * |
105 | * @param string $stream |
106 | * @return PageObject |
107 | */ |
108 | public static function parse(string $stream): PageObject |
109 | { |
110 | $page = new self(); |
111 | $page->setIndex(substr($stream, 0, strpos($stream, ' '))); |
112 | |
113 | // Determine the page parent object index. |
114 | $parent = substr($stream, (strpos($stream, '/Parent') + 7)); |
115 | $parent = trim(substr($parent, 0, strpos($parent, '0 R'))); |
116 | $page->setParentIndex($parent); |
117 | |
118 | // Determine the page width and height. |
119 | $dims = substr($stream, (strpos($stream, '/MediaBox') + 9)); |
120 | $dims = substr($dims, 0, strpos($dims, ']')); |
121 | $dims = trim(str_replace('[', '', $dims)); |
122 | $dims = explode(' ', $dims); |
123 | $page->setWidth($dims[2]); |
124 | $page->setHeight($dims[3]); |
125 | |
126 | // Determine the page content objects. |
127 | if (str_contains($stream, '/Contents')) { |
128 | $contents = substr($stream, (strpos($stream, '/Contents') + 9)); |
129 | $contents = $page->getDictionaryReferences($contents); |
130 | foreach ($contents as $content) { |
131 | $page->addContentIndex($content); |
132 | } |
133 | |
134 | // Set placeholder |
135 | $contents = substr($stream, (strpos($stream, '/Contents') + 9)); |
136 | if (str_contains($contents, '[')) { |
137 | $contents = substr($contents, 0, (strpos($contents, ']') + 1)); |
138 | $stream = str_replace($contents, '[{content_objects}]', $stream); |
139 | } else { |
140 | $contents = (str_contains($contents, '/')) ? |
141 | substr($contents, 0, strpos($contents, '/')) : |
142 | substr($contents, 0, strpos($contents, '>')); |
143 | $stream = str_replace($contents, '[{content_objects}]', $stream); |
144 | } |
145 | } |
146 | |
147 | // If they exist, determine the page annotation objects. |
148 | if (str_contains($stream, '/Annots')) { |
149 | $annots = substr($stream, (strpos($stream, '/Annots') + 7)); |
150 | $annots = $page->getDictionaryReferences($annots); |
151 | foreach ($annots as $annot) { |
152 | $page->addAnnotIndex($annot); |
153 | } |
154 | |
155 | // Set placeholder |
156 | $annots = substr($stream, (strpos($stream, '/Annots') + 7)); |
157 | if (str_contains($annots, '[')) { |
158 | $annots = substr($annots, 0, (strpos($annots, ']') + 1)); |
159 | $stream = str_replace($annots, '[{annotations}]', $stream); |
160 | } else { |
161 | $annots = (str_contains($annots, '/')) ? |
162 | substr($annots, 0, strpos($annots, '/')) : |
163 | substr($annots, 0, strpos($annots, '>')); |
164 | $stream = str_replace($annots, '[{annotations}]', $stream); |
165 | } |
166 | } |
167 | |
168 | // If they exist, determine the page font references. |
169 | if (str_contains($stream, '/Font')) { |
170 | $fonts = substr($stream, strpos($stream, 'Font')); |
171 | $fonts = substr($fonts, 0, (strpos($fonts, '>>') + 2)); |
172 | $stream = str_replace('/' . $fonts, '[{fonts}]', $stream); |
173 | $fonts = str_replace('Font<<', '', $fonts); |
174 | $fonts = str_replace('>>', '', $fonts); |
175 | $fonts = explode('/', $fonts); |
176 | foreach ($fonts as $value) { |
177 | if ($value != '') { |
178 | $page->addFontReference('/' . $value); |
179 | } |
180 | } |
181 | } |
182 | |
183 | // If they exist, determine the page XObjects references. |
184 | if (str_contains($stream, '/XObject')) { |
185 | $xo = substr($stream, strpos($stream, 'XObject')); |
186 | $xo = substr($xo, 0, (strpos($xo, '>>') + 2)); |
187 | $stream = str_replace('/' . $xo, '[{xobjects}]', $stream); |
188 | $xo = str_replace('XObject<<', '', $xo); |
189 | $xo = str_replace('>>', '', $xo); |
190 | $xo = explode('/', $xo); |
191 | foreach ($xo as $value) { |
192 | if ($value != '') { |
193 | $page->addXObjectReference('/' . $value); |
194 | } |
195 | } |
196 | } |
197 | |
198 | // If they exist, determine the page graphic states. |
199 | if (str_contains($stream, '/ExtGState')) { |
200 | $gState = substr($stream, strpos($stream, 'ExtGState')); |
201 | $gState = '/' . substr($gState, 0, (strpos($gState, '>>') + 2)); |
202 | } else { |
203 | $gState = ''; |
204 | } |
205 | |
206 | // If any groups exist |
207 | if (str_contains($stream, '/Group')) { |
208 | $group = substr($stream, strpos($stream, 'Group')); |
209 | $group = '/' . substr($group, 0, (strpos($group, '>>') + 2)); |
210 | } else { |
211 | $group = ''; |
212 | } |
213 | |
214 | // If resources exists |
215 | if (str_contains($stream, '/Resources')) { |
216 | $resources = substr($stream, strpos($stream, 'Resources')); |
217 | if (str_contains($resources, ' R')) { |
218 | $resources = '/' . substr($resources, 0, (strpos($resources, ' R') + 2)); |
219 | } else if (str_contains($resources, '>>')) { |
220 | $resources = '/' . substr($resources, 0, (strpos($resources, '>>') + 2)); |
221 | } else { |
222 | $resources = "/Resources<</ProcSet[/PDF/Text/ImageB/ImageC/ImageI][{xobjects}][{fonts}]{$gState}>>"; |
223 | } |
224 | } else { |
225 | $resources = "/Resources<</ProcSet[/PDF/Text/ImageB/ImageC/ImageI][{xobjects}][{fonts}]{$gState}>>"; |
226 | } |
227 | |
228 | if (substr_count($resources, '<<') > substr_count($resources, '>>')) { |
229 | $resources .= str_repeat('>>', (substr_count($resources, '<<') - substr_count($resources, '>>'))); |
230 | } |
231 | |
232 | $page->setData("\n[{page_index}] 0 obj\n<</Type/Page/Parent [{parent}] 0 R[{annotations}]/MediaBox" . |
233 | "[0 0 [{width}] [{height}]]{$group}[{content_objects}]{$resources}>>\nendobj\n"); |
234 | |
235 | return $page; |
236 | } |
237 | |
238 | /** |
239 | * Set the page object parent index |
240 | * |
241 | * @param int $parent |
242 | * @return PageObject |
243 | */ |
244 | public function setParentIndex(int $parent): PageObject |
245 | { |
246 | $this->parent = $parent; |
247 | return $this; |
248 | } |
249 | |
250 | /** |
251 | * Set the page object width |
252 | * |
253 | * @param mixed $width |
254 | * @return PageObject |
255 | */ |
256 | public function setWidth(mixed $width): PageObject |
257 | { |
258 | $this->width = $width; |
259 | return $this; |
260 | } |
261 | |
262 | /** |
263 | * Set the page object height |
264 | * |
265 | * @param mixed $height |
266 | * @return PageObject |
267 | */ |
268 | public function setHeight(mixed $height): PageObject |
269 | { |
270 | $this->height = $height; |
271 | return $this; |
272 | } |
273 | |
274 | /** |
275 | * Set the page object annotation indices |
276 | * |
277 | * @param array $annots |
278 | * @return PageObject |
279 | */ |
280 | public function setAnnots(array $annots): PageObject |
281 | { |
282 | $this->annots = $annots; |
283 | return $this; |
284 | } |
285 | |
286 | /** |
287 | * Set the page object content object indices |
288 | * |
289 | * @param array $content |
290 | * @return PageObject |
291 | */ |
292 | public function setContent(array $content): PageObject |
293 | { |
294 | $this->content = $content; |
295 | return $this; |
296 | } |
297 | |
298 | /** |
299 | * Set the page object XObject references |
300 | * |
301 | * @param array $xObjects |
302 | * @return PageObject |
303 | */ |
304 | public function setXObjects(array $xObjects): PageObject |
305 | { |
306 | $this->xObjects = []; |
307 | foreach ($xObjects as $xObject) { |
308 | $this->addXObjectReference($xObject); |
309 | } |
310 | return $this; |
311 | } |
312 | |
313 | /** |
314 | * Set the page object font references |
315 | * |
316 | * @param array $fonts |
317 | * @return PageObject |
318 | */ |
319 | public function setFonts(array $fonts): PageObject |
320 | { |
321 | $this->fonts = []; |
322 | foreach ($fonts as $font) { |
323 | $this->addFontReference($font); |
324 | } |
325 | return $this; |
326 | } |
327 | |
328 | /** |
329 | * Set the page object current content object index |
330 | * |
331 | * @param mixed $i |
332 | * @return PageObject |
333 | */ |
334 | public function setCurrentContentIndex(mixed $i = null): PageObject |
335 | { |
336 | $this->currentContentIndex = ($i !== null) ? (int)$i : null; |
337 | return $this; |
338 | } |
339 | |
340 | /** |
341 | * Add annotation index |
342 | * |
343 | * @param int $i |
344 | * @return PageObject |
345 | */ |
346 | public function addAnnotIndex(int $i): PageObject |
347 | { |
348 | $this->annots[] = $i; |
349 | return $this; |
350 | } |
351 | |
352 | /** |
353 | * Add content object index |
354 | * |
355 | * @param int $i |
356 | * @return PageObject |
357 | */ |
358 | public function addContentIndex(int $i): PageObject |
359 | { |
360 | $this->content[] = $i; |
361 | $this->setCurrentContentIndex($i); |
362 | return $this; |
363 | } |
364 | |
365 | /** |
366 | * Add XObject reference |
367 | * |
368 | * @param string $xObject |
369 | * @return PageObject |
370 | */ |
371 | public function addXObjectReference(string $xObject): PageObject |
372 | { |
373 | $i = substr($xObject, (strpos($xObject, ' ') + 1)); |
374 | $i = substr($i, 0, strpos($i, ' ')); |
375 | $this->xObjects[(int)$i] = $xObject; |
376 | return $this; |
377 | } |
378 | |
379 | /** |
380 | * Add font reference |
381 | * |
382 | * @param string $font |
383 | * @return PageObject |
384 | */ |
385 | public function addFontReference(string $font): PageObject |
386 | { |
387 | $i = substr($font, (strpos($font, ' ') + 1)); |
388 | $i = substr($i, 0, strpos($i, ' ')); |
389 | $this->fonts[(int)$i] = $font; |
390 | return $this; |
391 | } |
392 | |
393 | /** |
394 | * Get the page object parent index |
395 | * |
396 | * @return ?int |
397 | */ |
398 | public function getParentIndex(): ?int |
399 | { |
400 | return $this->parent; |
401 | } |
402 | |
403 | /** |
404 | * Get the page object width |
405 | * |
406 | * @return mixed |
407 | */ |
408 | public function getWidth(): mixed |
409 | { |
410 | return $this->width; |
411 | } |
412 | |
413 | /** |
414 | * Get the page object height |
415 | * |
416 | * @return mixed |
417 | */ |
418 | public function getHeight(): mixed |
419 | { |
420 | return $this->height; |
421 | } |
422 | |
423 | /** |
424 | * Get the page object current content object index |
425 | * |
426 | * @return ?int |
427 | */ |
428 | public function getCurrentContentIndex(): ?int |
429 | { |
430 | return $this->currentContentIndex; |
431 | } |
432 | |
433 | /** |
434 | * Get the page object annotation indices |
435 | * |
436 | * @return array |
437 | */ |
438 | public function getAnnots(): array |
439 | { |
440 | return $this->annots; |
441 | } |
442 | |
443 | /** |
444 | * Get the page object content object indices |
445 | * |
446 | * @return array |
447 | */ |
448 | public function getContent(): array |
449 | { |
450 | return $this->content; |
451 | } |
452 | |
453 | /** |
454 | * Get the page object XObject references |
455 | * |
456 | * @return array |
457 | */ |
458 | public function getXObjects(): array |
459 | { |
460 | return $this->xObjects; |
461 | } |
462 | |
463 | /** |
464 | * Get the page object font references |
465 | * |
466 | * @return array |
467 | */ |
468 | public function getFonts(): array |
469 | { |
470 | return $this->fonts; |
471 | } |
472 | |
473 | /** |
474 | * Determine if the page object has an annotation index |
475 | * |
476 | * @param int $i |
477 | * @return bool |
478 | */ |
479 | public function hasAnnot(int $i): bool |
480 | { |
481 | return (isset($this->annots[$i])); |
482 | } |
483 | |
484 | /** |
485 | * Determine if the page object has a content index |
486 | * |
487 | * @param int $i |
488 | * @return bool |
489 | */ |
490 | public function hasContent(int $i): bool |
491 | { |
492 | return (isset($this->content[$i])); |
493 | } |
494 | |
495 | /** |
496 | * Method to print the page object. |
497 | * |
498 | * @return string |
499 | */ |
500 | public function __toString(): string |
501 | { |
502 | $annots = ''; |
503 | $xObjects = ''; |
504 | $fonts = ''; |
505 | |
506 | // Format the annotations. |
507 | if (count($this->annots) > 0) { |
508 | $annots = '/Annots['; |
509 | $annots .= implode(" 0 R ", $this->annots); |
510 | $annots .= " 0 R]"; |
511 | } |
512 | |
513 | // Format the xobjects. |
514 | if (count($this->xObjects) > 0) { |
515 | $xObjects = '/XObject<<'; |
516 | $xObjects .= implode('', $this->xObjects); |
517 | $xObjects .= '>>'; |
518 | } |
519 | |
520 | // Format the fonts. |
521 | if (count($this->fonts) > 0) { |
522 | $fonts = '/Font<<'; |
523 | $fonts .= implode('', $this->fonts); |
524 | $fonts .= '>>'; |
525 | } |
526 | |
527 | // Swap out the placeholders. |
528 | $obj = str_replace( |
529 | ['[{page_index}]', '[{parent}]', '[{width}]', '[{height}]'], |
530 | [$this->index, $this->parent, $this->width, $this->height], |
531 | $this->data |
532 | ); |
533 | |
534 | $obj = (($annots != '') && (!str_contains($obj, '[{annotations}]'))) ? |
535 | str_replace('/MediaBox', $annots . '/MediaBox', $obj) : |
536 | str_replace('[{annotations}]', $annots, $obj); |
537 | |
538 | $obj = (($xObjects != '') && (!str_contains($obj, '[{xobjects}]'))) ? |
539 | str_replace('/ProcSet', $xObjects . '/ProcSet', $obj) : |
540 | str_replace('[{xobjects}]', $xObjects, $obj); |
541 | |
542 | $obj = (($fonts != '') && (!str_contains($obj, '[{fonts}]'))) ? |
543 | str_replace('/ProcSet', $fonts . '/ProcSet', $obj) : |
544 | str_replace('[{fonts}]', $fonts, $obj); |
545 | |
546 | if (count($this->content) > 0) { |
547 | $obj = str_replace('[{content_objects}]', '/Contents[' . implode(" 0 R ", $this->content) . " 0 R]", $obj); |
548 | } else { |
549 | $obj = str_replace('[{content_objects}]', '', $obj); |
550 | } |
551 | |
552 | return $obj; |
553 | } |
554 | |
555 | } |