Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
13 / 13
CRAP
100.00% covered (success)
100.00%
1 / 1
ParentObject
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
13 / 13
15
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 parse
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
2
 addKid
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 removeKid
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setKids
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setCount
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getCount
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getKids
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDeferredKids
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDeferredKids
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasDeferredKids
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasKid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <nick@popphp.org>
8 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Pdf\Build\PdfObject;
16
17/**
18 * Pdf parent object class
19 *
20 * @category   Pop
21 * @package    Pop\Pdf
22 * @author     Nick Sagona, III <nick@popphp.org>
23 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
24 * @license    https://www.popphp.org/license     New BSD License
25 * @version    6.2.0
26 */
27class ParentObject extends AbstractObject
28{
29
30    /**
31     * PDF parent object index
32     * @var ?int
33     */
34    protected ?int $index = 2;
35
36    /**
37     * PDF parent kids
38     * @var array
39     */
40    protected array $kids = [];
41
42    /**
43     * Explicit kid count override, used when kids are not leaf pages
44     * themselves (e.g. a merge's master node, whose kids are each source's
45     * own top-level Pages node) - null falls back to count($this->kids)
46     * @var ?int
47     */
48    protected ?int $countOverride = null;
49
50    /**
51     * Kids to append after every other kid has been added, rather than at
52     * their point of assignment (e.g. a merge's own source subtrees, which
53     * must land after any pages a target document already had before the
54     * merge, even though the merge builds this object before those existing
55     * pages get their own kid indices assigned during compilation)
56     * @var array
57     */
58    protected array $deferredKids = [];
59
60    /**
61     * Constructor
62     *
63     * Instantiate a PDF parent object.
64     *
65     * @param  int $index
66     */
67    public function __construct(int $index = 2)
68    {
69        $this->setIndex($index);
70        $this->setData("[{parent_index}] 0 obj\n<</Type/Pages/Count [{count}]/Kids[[{kids}]]>>\nendobj\n");
71    }
72
73    /**
74     * Parse a parent object from a string
75     *
76     * @param  string $stream
77     * @return ParentObject
78     */
79    public static function parse(string $stream): ParentObject
80    {
81        $parent = new self();
82
83        $parent->setIndex((int)substr($stream, 0, strpos($stream, ' ')));
84        $stream = str_replace($parent->getIndex() . ' 0 obj', '[{parent_index}] 0 obj', $stream);
85
86        // Determine the kids count.
87        $matches = [];
88        preg_match('/\/Count\s\d*/', $stream, $matches);
89        $count  = $matches[0];
90        $count  = str_replace('/Count ', '', $count);
91        $stream = str_replace('Count ' . $count, 'Count [{count}]', $stream);
92
93        // Determine the kids object indices.
94        $kids = trim(substr($stream, (strpos($stream, '/Kids') + 5)));
95        $kids = (str_starts_with($kids, '[')) ? substr($kids, 0, strpos($kids, ']') + 1) :
96            substr($kids, 0, (strpos($kids, ' R') + 2));
97
98        $kidIndices = $parent->getDictionaryReferences(substr($stream, (strpos($stream, '/Kids') + 5)));
99
100        $parent->setKids($kidIndices);
101        $parent->setData(str_replace($kids, '[[{kids}]]', $stream) . "\n");
102
103        return $parent;
104    }
105
106    /**
107     * Add a kid index to the parent object
108     *
109     * @param  int $kid
110     * @return ParentObject
111     */
112    public function addKid(int$kid): ParentObject
113    {
114        $this->kids[] = (int)$kid;
115        return $this;
116    }
117
118    /**
119     * Remove a kid index from the parent object
120     *
121     * @param  int $kid
122     * @return ParentObject
123     */
124    public function removeKid(int $kid): ParentObject
125    {
126        if ($this->hasKid($kid)) {
127            unset($this->kids[array_search($kid, $this->kids)]);
128        }
129        return $this;
130    }
131
132    /**
133     * Set the parent object kids
134     *
135     * @param  array $kids
136     * @return ParentObject
137     */
138    public function setKids(array $kids): ParentObject
139    {
140        $this->kids = $kids;
141        return $this;
142    }
143
144    /**
145     * Set an explicit kid count, overriding count($this->kids)
146     *
147     * @param  int $count
148     * @return ParentObject
149     */
150    public function setCount(int $count): ParentObject
151    {
152        $this->countOverride = $count;
153        return $this;
154    }
155
156    /**
157     * Get the parent object kid count
158     *
159     * @return int
160     */
161    public function getCount(): int
162    {
163        return $this->countOverride ?? count($this->kids);
164    }
165
166    /**
167     * Get the parent object kid indices
168     *
169     * @return array
170     */
171    public function getKids(): array
172    {
173        return $this->kids;
174    }
175
176    /**
177     * Set the kids to append after every other kid has been added
178     *
179     * @param  array $kids
180     * @return ParentObject
181     */
182    public function setDeferredKids(array $kids): ParentObject
183    {
184        $this->deferredKids = $kids;
185        return $this;
186    }
187
188    /**
189     * Get the kids to append after every other kid has been added
190     *
191     * @return array
192     */
193    public function getDeferredKids(): array
194    {
195        return $this->deferredKids;
196    }
197
198    /**
199     * Determine whether there are kids to append after every other kid
200     *
201     * @return bool
202     */
203    public function hasDeferredKids(): bool
204    {
205        return (count($this->deferredKids) > 0);
206    }
207
208    /**
209     * Determine whether the parent object contains a kid object index
210     *
211     * @param  int $kid
212     * @return bool
213     */
214    public function hasKid(int $kid): bool
215    {
216        return (in_array($kid, $this->kids));
217    }
218
219    /**
220     * Method to print the parent object.
221     *
222     * @return string
223     */
224    public function __toString(): string
225    {
226        return str_replace(['[{parent_index}]', '[{count}]', '[{kids}]'],
227            [(string)$this->index, (string)$this->getCount(), implode(" 0 R ", $this->kids) . " 0 R"], $this->data);
228    }
229
230}