Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
ObjectStream
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
4 / 4
8
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addObject
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 count
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toPdf
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Pdf\Core\Document;
6
7use Phpdftk\Pdf\Core\PdfDictionary;
8use Phpdftk\Pdf\Core\PdfName;
9use Phpdftk\Pdf\Core\PdfNumber;
10use Phpdftk\Pdf\Core\PdfObject;
11use Phpdftk\Pdf\Core\PdfReference;
12use Phpdftk\Pdf\Core\PdfStream;
13use Phpdftk\Pdf\Core\PdfVersion;
14use Phpdftk\Pdf\Core\RequiresPdfVersion;
15
16/**
17 * Object stream (/Type /ObjStm) — ISO 32000-2 §7.5.7.
18 *
19 * A PDF 1.5+ container that holds multiple indirect objects as a single
20 * compressed stream. The stream body is laid out as:
21 *
22 *   <objNum_1> <offset_1> <objNum_2> <offset_2> ... <objNum_N> <offset_N>
23 *   <obj_1><obj_2>...<obj_N>
24 *
25 * `First` is the byte offset at which the first contained object begins
26 * (i.e. the length of the header pair sequence). Contained objects are
27 * serialized without `obj`/`endobj` wrappers.
28 *
29 * Only objects without streams and not themselves compressed may be packed.
30 */
31#[RequiresPdfVersion(PdfVersion::V1_5)]
32class ObjectStream extends PdfStream
33{
34    public const PDF_TYPE = 'ObjStm';
35
36    /** @var array<int, PdfObject> obj number => object */
37    private array $contained = [];
38
39    /** Optional /Extends reference to a parent object stream. */
40    public ?PdfReference $extends = null;
41
42    public function __construct()
43    {
44        parent::__construct(new PdfDictionary(), '');
45    }
46
47    /**
48     * Add an indirect object to the stream. The object must have been
49     * assigned an object number already. The object's `toPdf()` output is
50     * packed verbatim — `obj`/`endobj` framing is stripped by ObjStm rules.
51     */
52    public function addObject(PdfObject $object): void
53    {
54        if ($object->objectNumber === 0) {
55            throw new \InvalidArgumentException(
56                'ObjectStream requires objects with assigned object numbers',
57            );
58        }
59        $this->contained[$object->objectNumber] = $object;
60    }
61
62    /**
63     * Number of objects packed in this stream (/N).
64     */
65    public function count(): int
66    {
67        return count($this->contained);
68    }
69
70    public function toPdf(): string
71    {
72        $bodies = [];
73        $offsets = [];
74        $runningOffset = 0;
75
76        foreach ($this->contained as $objNum => $object) {
77            $body = $object->toPdf();
78            $offsets[] = $objNum . ' ' . $runningOffset;
79            $bodies[] = $body;
80            $runningOffset += strlen($body) + 1; // +1 for the separator newline
81        }
82
83        $header = implode(' ', $offsets);
84        // A single space after the header, then each body separated by \n.
85        if ($header !== '') {
86            $this->data = $header . "\n" . implode("\n", $bodies);
87            $first = strlen($header) + 1;
88        } else {
89            $this->data = '';
90            $first = 0;
91        }
92
93        $this->dictionary = new PdfDictionary();
94        $this->dictionary->set('Type', new PdfName(self::PDF_TYPE));
95        $this->dictionary->set('N', new PdfNumber(count($this->contained)));
96        $this->dictionary->set('First', new PdfNumber($first));
97        if ($this->extends !== null) {
98            $this->dictionary->set('Extends', $this->extends);
99        }
100
101        return parent::toPdf();
102    }
103}