Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.50% covered (warning)
82.50%
33 / 40
25.00% covered (danger)
25.00%
1 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
PageResolver
82.50% covered (warning)
82.50%
33 / 40
25.00% covered (danger)
25.00%
1 / 4
19.74
0.00% covered (danger)
0.00%
0 / 1
 getPageReferences
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
3.07
 getPageDimensions
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
6
 collectPageRefs
69.23% covered (warning)
69.23%
9 / 13
0.00% covered (danger)
0.00%
0 / 1
8.43
 toFloat
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Pdf\Toolkit\Internal;
6
7use Phpdftk\Pdf\Core\PdfArray;
8use Phpdftk\Pdf\Core\PdfDictionary;
9use Phpdftk\Pdf\Core\PdfName;
10use Phpdftk\Pdf\Core\PdfNumber;
11use Phpdftk\Pdf\Core\PdfReference;
12use Phpdftk\Pdf\Reader\PdfReader;
13
14/**
15 * Resolves page references (object numbers) from a PdfReader.
16 *
17 * @internal
18 */
19final class PageResolver
20{
21    /**
22     * Get page object references by traversing the page tree.
23     *
24     * @return list<PdfReference> 0-indexed page references
25     */
26    public static function getPageReferences(PdfReader $reader): array
27    {
28        $catalog = $reader->getCatalog();
29        $pagesRef = $catalog->get('Pages');
30        if (!$pagesRef instanceof PdfReference) {
31            return [];
32        }
33        $pagesDict = $reader->resolveReference($pagesRef);
34        if (!$pagesDict instanceof PdfDictionary) {
35            return [];
36        }
37
38        $result = [];
39        self::collectPageRefs($reader, $pagesDict, $result);
40        return $result;
41    }
42
43    /**
44     * Get the MediaBox dimensions for a page dictionary.
45     *
46     * @return array{width: float, height: float}
47     */
48    public static function getPageDimensions(PdfDictionary $page, PdfReader $reader): array
49    {
50        $mediaBox = $page->get('MediaBox');
51        if (!$mediaBox instanceof PdfArray) {
52            // Try to inherit from parent
53            $parent = $page->get('Parent');
54            if ($parent instanceof PdfReference) {
55                $parentDict = $reader->resolveReference($parent);
56                if ($parentDict instanceof PdfDictionary) {
57                    $mediaBox = $parentDict->get('MediaBox');
58                }
59            }
60        }
61
62        if ($mediaBox instanceof PdfArray && count($mediaBox->items) >= 4) {
63            $x1 = self::toFloat($mediaBox->items[0]);
64            $y1 = self::toFloat($mediaBox->items[1]);
65            $x2 = self::toFloat($mediaBox->items[2]);
66            $y2 = self::toFloat($mediaBox->items[3]);
67            return ['width' => abs($x2 - $x1), 'height' => abs($y2 - $y1)];
68        }
69
70        // Default letter size
71        return ['width' => 612.0, 'height' => 792.0];
72    }
73
74    private static function collectPageRefs(PdfReader $reader, PdfDictionary $node, array &$result): void
75    {
76        $kids = $node->get('Kids');
77        if (!$kids instanceof PdfArray) {
78            return;
79        }
80        foreach ($kids->items as $kidRef) {
81            if (!$kidRef instanceof PdfReference) {
82                continue;
83            }
84            $kid = $reader->resolveReference($kidRef);
85            if (!$kid instanceof PdfDictionary) {
86                continue;
87            }
88            $type = $kid->get('Type');
89            if ($type instanceof PdfName && $type->value === 'Pages') {
90                self::collectPageRefs($reader, $kid, $result);
91            } else {
92                $result[] = $kidRef;
93            }
94        }
95    }
96
97    private static function toFloat(mixed $val): float
98    {
99        if ($val instanceof PdfNumber) {
100            return (float) $val->toPdf();
101        }
102        return (float) $val;
103    }
104}