Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.82% covered (success)
78.82%
67 / 85
58.82% covered (warning)
58.82%
10 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
StreamObject
78.82% covered (success)
78.82%
67 / 85
58.82% covered (warning)
58.82%
10 / 17
66.09
0.00% covered (danger)
0.00%
0 / 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%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 setDefinition
66.67% covered (warning)
66.67%
16 / 24
0.00% covered (danger)
0.00%
0 / 1
17.33
 setStream
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 appendStream
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDefinition
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getStream
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 encode
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
5
 decode
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 isEncoded
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEncoding
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPalette
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 isPalette
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isXObject
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getByteLength
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 calculateByteLength
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
12
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 stream 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 StreamObject extends AbstractObject
27{
28
29    /**
30     * PDF stream object index
31     * @var ?int
32     */
33    protected ?int $index = 5;
34
35    /**
36     * PDF stream object definition
37     * @var ?string
38     */
39    protected ?string $definition = null;
40
41    /**
42     * PDF stream object stream
43     * @var ?string
44     */
45    protected ?string $stream = null;
46
47    /**
48     * Encoding filter
49     * @var ?string
50     */
51    protected ?string $encoding = null;
52
53    /**
54     * Palette object flag
55     * @var bool
56     */
57    protected bool $isPalette = false;
58
59    /**
60     * XObject object flag
61     * @var bool
62     */
63    protected bool $isXObject = false;
64
65    /**
66     * Constructor
67     *
68     * Instantiate a PDF stream object.
69     *
70     * @param  int $index
71     */
72    public function __construct(int $index = 5)
73    {
74        $this->setIndex($index);
75        $this->setData("\n[{object_index}] 0 obj\n[{definition}]\n[{stream}]\nendobj\n\n");
76    }
77
78    /**
79     * Parse a stream object from a string
80     *
81     * @param  string $stream
82     * @return StreamObject
83     */
84    public static function parse(string $stream): StreamObject
85    {
86        $object = new self();
87        $object->setIndex(substr($stream, 0, strpos($stream, ' ')));
88        $stream = str_replace($object->getIndex() . ' 0 obj', '[{object_index}] 0 obj', $stream);
89
90        // Determine the objects definition and stream, if applicable.
91        $s = substr($stream, (strpos($stream, ' obj') + 4));
92        $s = substr($s, 0, strpos($s, 'endobj'));
93        if (str_contains($s, 'stream')) {
94            $def = substr($s, 0, strpos($s, 'stream'));
95            $str = substr($s, (strpos($s, 'stream') + 6));
96            $str = substr($str, 0, strpos($str, 'endstream'));
97            $object->setDefinition($def);
98            $object->appendStream($str);
99        } else {
100            $object->setDefinition($s);
101        }
102
103        $object->setData("\n[{object_index}] 0 obj\n[{definition}]\n[{stream}]\nendobj\n\n");
104        return $object;
105    }
106
107    /**
108     * Set the stream object definition
109     *
110     * @param  string $definition
111     * @return StreamObject
112     */
113    public function setDefinition(string $definition): StreamObject
114    {
115        $this->definition = (string)$definition;
116
117        if (str_contains($this->definition, '/ASCIIHexDecode')) {
118            $this->encoding = 'ASCIIHexDecode';
119        } else if (str_contains($this->definition, '/ASCII85Decode')) {
120            $this->encoding = 'ASCII85Decode';
121        } else if (str_contains($this->definition, '/LZWDecode')) {
122            $this->encoding = 'LZWDecode';
123        } else if (str_contains($this->definition, '/FlateDecode')) {
124            $this->encoding = 'FlateDecode';
125        } else if (str_contains($this->definition, '/RunLengthDecode')) {
126            $this->encoding = 'RunLengthDecode';
127        } else if (str_contains($this->definition, '/CCITTFaxDecode')) {
128            $this->encoding = 'CCITTFaxDecode';
129        } else if (str_contains($this->definition, '/JBIG2Decode')) {
130            $this->encoding = 'JBIG2Decode';
131        } else if (str_contains($this->definition, '/DCTDecode')) {
132            $this->encoding = 'DCTDecode';
133        } else if (str_contains($this->definition, '/JPXDecode')) {
134            $this->encoding = 'JPXDecode';
135        } else if (str_contains($this->definition, '/Crypt')) {
136            $this->encoding = 'Crypt';
137        }
138
139        if (stripos($this->definition, '/xobject') !== false) {
140            $this->isXObject = true;
141        }
142
143        return $this;
144    }
145
146    /**
147     * Set the stream object stream
148     *
149     * @param  string $stream
150     * @return StreamObject
151     */
152    public function setStream(string $stream): StreamObject
153    {
154        $this->stream = $stream;
155        return $this;
156    }
157
158    /**
159     * Append to the stream the PDF stream object
160     *
161     * @param  string $stream
162     * @return StreamObject
163     */
164    public function appendStream(string $stream): StreamObject
165    {
166        $this->stream .= $stream;
167        return $this;
168    }
169
170    /**
171     * Get the stream object definition
172     *
173     * @return ?string
174     */
175    public function getDefinition(): ?string
176    {
177        return $this->definition;
178    }
179
180    /**
181     * Get the PDF stream object stream
182     *
183     * @return ?string
184     */
185    public function getStream(): ?string
186    {
187        return $this->stream;
188    }
189
190    /**
191     * Method to encode the PDF stream object with FlateDecode (gzcompress)
192     *
193     * @return void
194     */
195    public function encode(): void
196    {
197        if (($this->stream != '') && (function_exists('gzcompress')) &&
198            (!str_contains((string)$this->definition, ' /Image')) && (!str_contains((string)$this->definition, '/FlateDecode'))) {
199            $this->stream   = "\n" . gzcompress($this->stream, 9) . "\n";
200            $this->encoding = 'FlateDecode';
201        }
202    }
203
204    /**
205     * Method to decode the PDF stream contents with FlateDecode (gzuncompress)
206     *
207     * @return bool|string
208     */
209    public function decode(): bool|string
210    {
211        $decoded = false;
212        if (($this->stream != '') && function_exists('gzuncompress')) {
213            $decoded = @gzuncompress(trim($this->stream));
214        }
215        return $decoded;
216    }
217
218    /**
219     * Determine whether or not the PDF stream object is encoded
220     *
221     * @return bool
222     */
223    public function isEncoded(): bool
224    {
225        return ($this->encoding !== null);
226    }
227
228    /**
229     * Get the encoding filter
230     *
231     * @return ?string
232     */
233    public function getEncoding(): ?string
234    {
235        return $this->encoding;
236    }
237
238    /**
239     * Set whether the PDF stream object is a palette object
240     *
241     * @param  bool $isPalette
242     * @return StreamObject
243     */
244    public function setPalette(bool $isPalette): StreamObject
245    {
246        $this->isPalette = $isPalette;
247        return $this;
248    }
249
250    /**
251     * Get whether the PDF stream object is a palette object
252     *
253     * @return bool
254     */
255    public function isPalette(): bool
256    {
257        return $this->isPalette;
258    }
259
260    /**
261     * Get whether the PDF stream object is an XObject
262     *
263     * @return bool
264     */
265    public function isXObject(): bool
266    {
267        return $this->isXObject;
268    }
269
270    /**
271     * Get the PDF stream object byte length
272     *
273     * @return int
274     */
275    public function getByteLength(): int
276    {
277        return $this->calculateByteLength((string)$this);
278    }
279
280    /**
281     * Calculate the byte length of a string
282     *
283     * @param  ?string $string
284     * @return int
285     */
286    protected function calculateByteLength(?string $string): int
287    {
288        return strlen((string)$string);
289    }
290
291    /**
292     * Method to print the PDF stream object.
293     *
294     * @return string
295     */
296    public function __toString(): string
297    {
298        // Set the stream.
299        $stream = ($this->stream !== null) ? "stream" . $this->stream . "endstream\n" : '';
300
301        // Set up the Length definition.
302        if ((str_contains((string)$this->definition, '/Length ')) && (!str_contains((string)$this->definition, '/Length1')) &&
303            (!str_contains((string)$this->definition, '/Image'))) {
304            $matches = [];
305            preg_match('/\/Length\s\d*/', $this->definition, $matches);
306            if (isset($matches[0])) {
307                $len = $matches[0];
308                $len = str_replace('/Length', '', $len);
309                $len = str_replace(' ', '', $len);
310                $this->definition = str_replace($len, '[{byte_length}]', $this->definition);
311            }
312        } else if (!str_contains((string)$this->definition, '/Length')) {
313            $this->definition .= "<</Length [{byte_length}]>>\n";
314        }
315
316        // Calculate the byte length of the stream and swap out the placeholders.
317        $byteLength = (($this->encoding == 'FlateDecode') && (function_exists('gzcompress')) &&
318            (!str_contains((string)$this->definition, ' /Image')) && (!str_contains((string)$this->definition, '/FlateDecode'))) ?
319            $this->calculateByteLength($this->stream) . " /Filter /FlateDecode" : $this->calculateByteLength($this->stream);
320
321        $data = str_replace(
322            ['[{object_index}]', '[{stream}]', '[{definition}]', '[{byte_length}]'],
323            [$this->index, $stream, $this->definition, $byteLength],
324            $this->data
325        );
326
327        // Clear Length definition if it is zero.
328        if (str_contains((string)$data, '<</Length 0>>')) {
329            $data = str_replace('<</Length 0>>', '', $data);
330        }
331
332        return $data;
333    }
334
335}