Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.12% covered (success)
78.12%
125 / 160
64.00% covered (warning)
64.00%
16 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageObject
78.12% covered (success)
78.12%
125 / 160
64.00% covered (warning)
64.00%
16 / 25
93.21
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 parse
79.49% covered (success)
79.49%
62 / 78
0.00% covered (danger)
0.00%
0 / 1
24.81
 setParentIndex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setWidth
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setHeight
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setAnnots
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setContent
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setXObjects
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setFonts
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setCurrentContentIndex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 addAnnotIndex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addContentIndex
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addXObjectReference
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 addFontReference
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getParentIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWidth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHeight
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCurrentContentIndex
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAnnots
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getXObjects
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFonts
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAnnot
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __toString
87.88% covered (success)
87.88%
29 / 33
0.00% covered (danger)
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 */
14namespace 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 */
26class 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}