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