Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
CRAP
100.00% covered (success)
100.00%
1 / 1
Table
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
15
100.00% covered (success)
100.00%
1 / 1
 parse
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
15
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\Extract\Xref;
16
17use Pop\Pdf\Extract\Exception;
18use Pop\Pdf\Extract\ObjectParser;
19use Pop\Pdf\Extract\Tokenizer;
20
21/**
22 * Pdf extract xref table class
23 *
24 * @category   Pop
25 * @package    Pop\Pdf
26 * @author     Nick Sagona, III <nick@popphp.org>
27 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
28 * @license    https://www.popphp.org/license     New BSD License
29 * @version    6.0.0
30 */
31class Table
32{
33
34    /**
35     * Parse a classic xref table at a byte position
36     *
37     * @param  string $data
38     * @param  int    $pos
39     * @throws Exception
40     * @return array
41     */
42    public static function parse(string $data, int $pos): array
43    {
44        $tokenizer = new Tokenizer($data, $pos);
45        $token     = $tokenizer->next();
46
47        if (($token['type'] !== 'keyword') || ($token['value'] !== 'xref')) {
48            throw new Exception('Error: Expected xref keyword.');
49        }
50
51        $offsets = [];
52
53        while (true) {
54            $savedPos   = $tokenizer->getPosition();
55            $startToken = $tokenizer->next();
56
57            if (($startToken['type'] === 'keyword') && ($startToken['value'] === 'trailer')) {
58                break;
59            }
60            if ($startToken['type'] !== 'number') {
61                $tokenizer->setPosition($savedPos);
62                break;
63            }
64
65            $countToken = $tokenizer->next();
66            if ($countToken['type'] !== 'number') {
67                throw new Exception('Error: Malformed xref subsection header.');
68            }
69
70            $startObj = (int) $startToken['value'];
71            $count    = (int) $countToken['value'];
72
73            // Each classic xref entry is a fixed 20 bytes on disk. A
74            // malformed subsection header (e.g. a huge declared $count with
75            // little/no actual data behind it) must not be trusted to drive
76            // the loop bound - clamp it against what could plausibly remain,
77            // and bail out cleanly if we run past the end of the data before
78            // reading $count entries.
79            $maxPossibleEntries = intdiv(strlen($data) - $tokenizer->getPosition(), 20) + 1;
80            if ($count > $maxPossibleEntries) {
81                $count = $maxPossibleEntries;
82            }
83
84            for ($i = 0; $i < $count; $i++) {
85                $offsetToken = $tokenizer->next();
86
87                if ($offsetToken['type'] === 'eof') {
88                    throw new Exception('Error: Unexpected end of data while parsing an xref subsection.');
89                }
90
91                $tokenizer->next(); // generation number - not needed for extraction
92                $typeToken   = $tokenizer->next();
93
94                if (($typeToken['type'] === 'keyword') && ($typeToken['value'] === 'n')) {
95                    $objNum = $startObj + $i;
96                    if (!isset($offsets[$objNum])) {
97                        $offsets[$objNum] = ['offset' => (int) $offsetToken['value']];
98                    }
99                }
100            }
101        }
102
103        $parser  = new ObjectParser($tokenizer);
104        $trailer = $parser->parseValue();
105
106        if (!is_array($trailer)) {
107            throw new Exception('Error: Malformed xref trailer dictionary.');
108        }
109
110        return ['offsets' => $offsets, 'trailer' => $trailer, 'endPos' => $tokenizer->getPosition()];
111    }
112
113}