Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
87 / 87
100.00% covered (success)
100.00%
20 / 20
CRAP
100.00% covered (success)
100.00%
1 / 1
Document
100.00% covered (success)
100.00%
87 / 87
100.00% covered (success)
100.00%
20 / 20
49
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 addPage
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 addPages
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 createPage
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 copyPage
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 orderPages
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 deletePage
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 addFont
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 addFonts
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 embedFont
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 embedFonts
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 createStyle
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 addStyle
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 addStyles
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setCurrentPage
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setCurrentFont
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 importObjects
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 hasImportedObjects
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getImportObjects
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
3 / 3
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;
16
17use Pop\Pdf\Document\AbstractDocument;
18use Pop\Pdf\Document\Page;
19use Pop\Pdf\Document\Font;
20use Pop\Pdf\Document\Metadata;
21use Pop\Pdf\Document\Exception;
22use Pop\Pdf\Document\Style;
23
24/**
25 * Pdf document class
26 *
27 * @category   Pop
28 * @package    Pop\Pdf
29 * @author     Nick Sagona, III <nick@popphp.org>
30 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
31 * @license    https://www.popphp.org/license     New BSD License
32 * @version    6.2.0
33 */
34class Document extends AbstractDocument
35{
36
37    /**
38     * Imported objects
39     * @var array
40     */
41    protected array $importedObjects = [];
42
43    /**
44     * Constructor
45     *
46     * Instantiate a PDF document
47     *
48     * @param ?Page            $page
49     * @param ?Metadata        $metadata
50     * @param Style|array|null $style
51     */
52    public function __construct(?Page $page = null, ?Metadata $metadata = null, Style|array|null $style = null)
53    {
54        if ($page !== null) {
55            $this->addPage($page);
56        }
57
58        $this->setMetadata((($metadata !== null) ? $metadata : new Metadata()));
59
60        if ($style !== null) {
61            if (is_array($style)) {
62                $this->addStyles($style);
63            } else {
64                $this->addStyle($style);
65            }
66        }
67    }
68
69    /**
70     * Add a page to the PDF document
71     *
72     * @param  Page $page
73     * @return Document
74     */
75    public function addPage(Page $page): Document
76    {
77        $this->pages[]     = $page;
78        $this->currentPage = count($this->pages);
79        return $this;
80    }
81
82    /**
83     * Add pages to the PDF document
84     *
85     * @param  array $pages
86     * @return Document
87     */
88    public function addPages(array $pages): Document
89    {
90        foreach ($pages as $page) {
91            $this->addPage($page);
92        }
93        return $this;
94    }
95
96    /**
97     * Create and return a new page object, adding it to the PDF document
98     *
99     * @param  mixed $size
100     * @param  mixed $height
101     * @throws Exception
102     * @return Page
103     */
104    public function createPage(mixed $size, mixed $height = null): Page
105    {
106        $page = new Page($size, $height);
107        $this->addPage($page);
108        return $page;
109    }
110
111    /**
112     * Copy and return a page of the PDF, adding it to the PDF document
113     *
114     * @param  int  $p
115     * @param  bool $preserveContent
116     * @throws Exception
117     * @return Page
118     */
119    public function copyPage(int $p, bool $preserveContent = true): Page
120    {
121        if (!isset($this->pages[$p - 1])) {
122            throw new Exception("Error: That page (" . $p . ") does not exist.");
123        }
124
125        $page = clone $this->pages[$p - 1];
126
127        if (!$preserveContent) {
128            $page->clearContent();
129        }
130
131        $this->addPage($page);
132        return $page;
133    }
134
135    /**
136     * Order the pages
137     *
138     * @param  array $pages
139     * @throws Exception
140     * @return Document
141     */
142    public function orderPages(array $pages): Document
143    {
144        $newOrder = [];
145
146        // Check if the numbers of pages passed equals the number of pages in the PDF.
147        if (count($pages) != count($this->pages)) {
148            throw new Exception('Error: The pages array passed does not contain the same number of pages as the PDF.');
149        }
150
151        // Make sure each page passed is within the PDF and not out of range.
152        foreach ($pages as $value) {
153            if (!array_key_exists(($value - 1), $this->pages)) {
154                throw new Exception('Error: The pages array passed contains a page that does not exist.');
155            }
156        }
157
158        // Set the new order of the page objects.
159        foreach ($pages as $value) {
160            $newOrder[] = $this->pages[$value - 1];
161        }
162
163        // Set the pages arrays to the new order.
164        $this->pages = $newOrder;
165        return $this;
166    }
167
168    /**
169     * Delete a page from the PDF document
170     *
171     * @param  int $p
172     * @throws Exception
173     * @return Document
174     */
175    public function deletePage(int $p): Document
176    {
177        if (!isset($this->pages[$p - 1])) {
178            throw new Exception("Error: That page (" . $p . ") does not exist.");
179        }
180
181        unset($this->pages[$p - 1]);
182
183        // Reset current page if current page was the one deleted
184        if ($this->currentPage == $p) {
185            $this->currentPage = (count($this->pages) > 0) ? (count($this->pages) - 1) : null;
186        }
187
188        return $this;
189    }
190
191    /**
192     * Add a font
193     *
194     * @param  Font|string $font
195     * @param  bool        $embedOverride
196     * @throws Exception
197     * @return Document
198     */
199    public function addFont(Font|string $font, bool $embedOverride = false): Document
200    {
201        if (is_string($font)) {
202            $font = new Font($font);
203        }
204        if (!$font->isStandard()) {
205            $this->embedFont($font, $embedOverride);
206        } else {
207            if (!array_key_exists($font->getName(), $this->fonts)) {
208                $this->fonts[$font->getName()] = $font;
209                $this->currentFont = $font->getName();
210            }
211        }
212
213        return $this;
214    }
215
216    /**
217     * Add fonts
218     *
219     * @param  array $fonts
220     * @param  bool  $embedOverride
221     * @throws Exception
222     * @return Document
223     */
224    public function addFonts(array $fonts, bool $embedOverride = false): Document
225    {
226        foreach ($fonts as $font) {
227            $this->addFont($font, $embedOverride);
228        }
229
230        return $this;
231    }
232
233    /**
234     * Add a font
235     *
236     * @param  Font $font
237     * @param  bool $embedOverride
238     * @throws Exception
239     * @return Document
240     */
241    public function embedFont(Font $font, bool $embedOverride = false): Document
242    {
243        if (!$font->isEmbedded()) {
244            $this->addFont($font);
245        } else {
246            if (!$font->parser()->isEmbeddable() && !$embedOverride) {
247                throw new Exception('Error: The font license does not allow for it to be embedded.');
248            }
249
250            if (!array_key_exists($font->parser()->getFontName(), $this->fonts)) {
251                $font->parser()->setCompression($this->compression);
252                $this->fonts[$font->parser()->getFontName()] = $font;
253                $this->currentFont = $font->parser()->getFontName();
254            }
255        }
256
257        return $this;
258    }
259
260    /**
261     * Embed fonts
262     *
263     * @param  array $fonts
264     * @param  bool  $embedOverride
265     * @throws Exception
266     * @return Document
267     */
268    public function embedFonts(array $fonts, bool $embedOverride = false): Document
269    {
270        foreach ($fonts as $font) {
271            $this->embedFont($font, $embedOverride);
272        }
273
274        return $this;
275    }
276
277    /**
278     * Create style
279     *
280     * @param  Style|string $style
281     * @return Document
282     */
283    public function createStyle(Style|string $style, ?string $font = null, int|float|null $size = null): Document
284    {
285        return ($style instanceof Style) ?
286            $this->addStyle($style) : $this->addStyle(new Style($style, $font, $size));
287    }
288
289    /**
290     * Add a style
291     *
292     * @param  Style|string $style
293     * @return Document
294     */
295    public function addStyle(Style|string $style): Document
296    {
297        if (is_string($style)) {
298            $style = new Style($style);
299        }
300
301        if (!array_key_exists($style->getName(), $this->styles)) {
302            $this->styles[$style->getName()] = $style;
303        }
304
305        return $this;
306    }
307
308    /**
309     * Add styles
310     *
311     * @param  array $styles
312     * @return Document
313     */
314    public function addStyles(array $styles): Document
315    {
316        foreach ($styles as $style) {
317            $this->addStyle($style);
318        }
319        return $this;
320    }
321
322    /**
323     * Set the current page of the PDF document
324     *
325     * @param  int $p
326     * @throws Exception
327     * @return Document
328     */
329    public function setCurrentPage(int $p): Document
330    {
331        // Check if the page exists.
332        if (!isset($this->pages[$p - 1])) {
333            throw new Exception("Error: That page (" . $p . ") does not exist.");
334        }
335        $this->currentPage = $p;
336
337        return $this;
338    }
339
340    /**
341     * Set the current font of the PDF document
342     *
343     * @param  string $name
344     * @throws Exception
345     * @return Document
346     */
347    public function setCurrentFont(string $name): Document
348    {
349        // Check if the font exists.
350        if (!isset($this->fonts[$name])) {
351            throw new Exception("Error: The font '" . $name . "' has not been added to the PDF document.");
352        }
353        $this->currentFont = $name;
354
355        return $this;
356    }
357
358    /**
359     * Import objects into document
360     *
361     * @param  array $objects
362     * @return Document
363     */
364    public function importObjects(array $objects): Document
365    {
366        $this->importedObjects = $objects;
367        return $this;
368    }
369
370    /**
371     * Determine if the document has imported objects
372     *
373     * @return bool
374     */
375    public function hasImportedObjects(): bool
376    {
377        return (count($this->importedObjects) > 0);
378    }
379
380    /**
381     * Get the imported objects
382     *
383     * @return array
384     */
385    public function getImportObjects(): array
386    {
387        return $this->importedObjects;
388    }
389
390    /**
391     * Output the PDF document to string
392     *
393     * @return string
394     */
395    public function __toString(): string
396    {
397        $compiler = new Build\Compiler();
398        $compiler->finalize($this);
399        return $compiler->getOutput();
400    }
401
402}