Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.71% covered (warning)
89.71%
61 / 68
76.19% covered (warning)
76.19%
16 / 21
CRAP
0.00% covered (danger)
0.00%
0 / 1
WriterDocumentInspector
89.71% covered (warning)
89.71%
61 / 68
76.19% covered (warning)
76.19%
16 / 21
61.67
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCatalog
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInfo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPages
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getFonts
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
5.12
 hasEncryption
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 hasXmpMetadata
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getXmpBytes
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
6.05
 hasOutputIntents
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 hasOutputIntentWithIccProfile
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 hasTransparency
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 hasJavaScript
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
4.12
 hasEmbeddedFiles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRegisteredObjects
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasThreeDAnnotations
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
4.12
 getThreeDStreams
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 hasRasterOnlyContent
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getImageXObjects
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 hasInteractiveForms
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasMultimediaContent
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
7
 getReferenceXObjects
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Pdf\Conformance\Inspection;
6
7use Phpdftk\Pdf\Core\Action\JavaScriptAction;
8use Phpdftk\Pdf\Core\Annotation\MovieAnnotation;
9use Phpdftk\Pdf\Core\Annotation\RichMediaAnnotation;
10use Phpdftk\Pdf\Core\Annotation\ScreenAnnotation;
11use Phpdftk\Pdf\Core\Annotation\SoundAnnotation;
12use Phpdftk\Pdf\Core\Annotation\ThreeDAnnotation;
13use Phpdftk\Pdf\Core\Document\Catalog;
14use Phpdftk\Pdf\Core\Document\Info;
15use Phpdftk\Pdf\Core\Document\MetadataStream;
16use Phpdftk\Pdf\Core\Document\Page;
17use Phpdftk\Pdf\Core\File\PdfFileWriter;
18use Phpdftk\Pdf\Core\Font\Font;
19use Phpdftk\Pdf\Core\Font\Type0Font;
20use Phpdftk\Pdf\Core\Graphics\XObject\FormXObject;
21use Phpdftk\Pdf\Core\Graphics\XObject\ImageXObject;
22use Phpdftk\Pdf\Core\Interactive\Form\AcroForm;
23use Phpdftk\Pdf\Core\Multimedia\MediaRendition;
24use Phpdftk\Pdf\Core\PdfObject;
25use Phpdftk\Pdf\Core\PdfStream;
26use Phpdftk\Pdf\Core\ThreeD\ThreeDStream;
27
28/**
29 * Inspects a PdfFileWriter's internal state for conformance validation.
30 */
31final class WriterDocumentInspector implements DocumentInspector
32{
33    /** @var array<string, Font|Type0Font> */
34    private array $fonts;
35
36    /**
37     * @param array<string, Font|Type0Font> $fonts Font map from PdfWriter
38     */
39    public function __construct(
40        private readonly Catalog $catalog,
41        private readonly PdfFileWriter $fileWriter,
42        array $fonts = [],
43    ) {
44        $this->fonts = $fonts;
45    }
46
47    public function getCatalog(): Catalog
48    {
49        return $this->catalog;
50    }
51
52    public function getInfo(): ?Info
53    {
54        return $this->fileWriter->getInfo();
55    }
56
57    public function getPages(): iterable
58    {
59        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
60            if ($object instanceof Page) {
61                yield $object;
62            }
63        }
64    }
65
66    public function getFonts(): iterable
67    {
68        foreach ($this->fonts as $font) {
69            if ($font instanceof Font) {
70                yield $font;
71            }
72        }
73        // Also yield Type0Font instances (they extend PdfObject, not Font)
74        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
75            if ($object instanceof Type0Font) {
76                yield $object;
77            }
78        }
79    }
80
81    public function hasEncryption(): bool
82    {
83        // Check if any registered object is an EncryptDictionary
84        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
85            if ($object instanceof \Phpdftk\Pdf\Core\Security\EncryptDictionary) {
86                return true;
87            }
88        }
89        return false;
90    }
91
92    public function hasXmpMetadata(): bool
93    {
94        return $this->catalog->metadata !== null;
95    }
96
97    public function getXmpBytes(): ?string
98    {
99        if ($this->catalog->metadata === null) {
100            return null;
101        }
102
103        // Find the MetadataStream by object number
104        $objNum = $this->catalog->metadata->objectNumber;
105        $objects = $this->fileWriter->getRegistry()->getAll();
106        if (isset($objects[$objNum]) && $objects[$objNum] instanceof MetadataStream) {
107            return $objects[$objNum]->data;
108        }
109        if (isset($objects[$objNum]) && $objects[$objNum] instanceof PdfStream) {
110            return $objects[$objNum]->data;
111        }
112        return null;
113    }
114
115    public function hasOutputIntents(): bool
116    {
117        return $this->catalog->outputIntents !== null
118            && count($this->catalog->outputIntents->items) > 0;
119    }
120
121    public function hasOutputIntentWithIccProfile(): bool
122    {
123        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
124            if ($object instanceof \Phpdftk\Pdf\Core\Document\OutputIntent
125                && $object->destOutputProfile !== null
126            ) {
127                return true;
128            }
129        }
130        return false;
131    }
132
133    public function hasTransparency(): bool
134    {
135        foreach ($this->getPages() as $page) {
136            if ($page->group !== null) {
137                return true;
138            }
139        }
140        return false;
141    }
142
143    public function hasJavaScript(): bool
144    {
145        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
146            if ($object instanceof JavaScriptAction) {
147                return true;
148            }
149        }
150        return false;
151    }
152
153    public function hasEmbeddedFiles(): bool
154    {
155        return $this->catalog->names !== null;
156    }
157
158    public function getRegisteredObjects(): iterable
159    {
160        yield from $this->fileWriter->getRegistry()->getAll();
161    }
162
163    public function hasThreeDAnnotations(): bool
164    {
165        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
166            if ($object instanceof ThreeDAnnotation) {
167                return true;
168            }
169        }
170        return false;
171    }
172
173    public function getThreeDStreams(): iterable
174    {
175        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
176            if ($object instanceof ThreeDStream) {
177                yield $object;
178            }
179        }
180    }
181
182    public function hasRasterOnlyContent(): bool
183    {
184        // Heuristic: if any fonts are registered, pages likely contain text
185        foreach ($this->getFonts() as $_) {
186            return false;
187        }
188        return true;
189    }
190
191    public function getImageXObjects(): iterable
192    {
193        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
194            if ($object instanceof ImageXObject) {
195                yield $object;
196            }
197        }
198    }
199
200    public function hasInteractiveForms(): bool
201    {
202        return $this->catalog->acroForm !== null;
203    }
204
205    public function hasMultimediaContent(): bool
206    {
207        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
208            if ($object instanceof MovieAnnotation
209                || $object instanceof SoundAnnotation
210                || $object instanceof ScreenAnnotation
211                || $object instanceof RichMediaAnnotation
212                || $object instanceof MediaRendition
213            ) {
214                return true;
215            }
216        }
217        return false;
218    }
219
220    public function getReferenceXObjects(): iterable
221    {
222        foreach ($this->fileWriter->getRegistry()->getAll() as $object) {
223            if ($object instanceof FormXObject && $object->ref !== null) {
224                yield $object;
225            }
226        }
227    }
228}