Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
CRAP
100.00% covered (success)
100.00%
1 / 1
ObjectStream
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
1 / 1
 expand
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
9
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;
16
17use Pop\Pdf\Extract\Filter\Budget;
18use Pop\Pdf\Extract\Filter\Registry;
19
20/**
21 * Pdf extract object stream class
22 *
23 * @category   Pop
24 * @package    Pop\Pdf
25 * @author     Nick Sagona, III <nick@popphp.org>
26 * @copyright  Copyright (c) 2009-2026 Nick Sagona, III
27 * @license    https://www.popphp.org/license     New BSD License
28 * @version    6.0.0
29 */
30class ObjectStream
31{
32
33    /**
34     * Expand an object stream into its map of object number to parsed object value
35     *
36     * @param  Value\Stream $stream
37     * @param  ?Budget      $budget
38     * @return array
39     */
40    public static function expand(Value\Stream $stream, ?Budget $budget = null): array
41    {
42        $dict = $stream->dict;
43        $raw  = Registry::decode($stream->raw, $dict['Filter'] ?? null, $dict['DecodeParms'] ?? null, $budget);
44
45        $n     = $dict['N'] ?? 0;
46        $first = $dict['First'] ?? 0;
47
48        // /N and /First come straight out of a producer-controlled dict and
49        // may resolve to a non-numeric Value (a Reference, a Name, ...)
50        // rather than an int/float - casting one of those directly to int
51        // emits a PHP warning that a strict error handler (common in real
52        // apps embedding this library) turns into an uncaught \Throwable,
53        // escaping every catch(Exception) guard this codebase relies on.
54        // Falling back to 0 for a non-numeric value degrades to "no pairs
55        // found", matching how a malformed/unresolvable stream is already
56        // handled elsewhere.
57        $n     = (is_int($n) || is_float($n)) ? (int) $n : 0;
58        $first = (is_int($first) || is_float($first)) ? max(0, (int) $first) : 0;
59
60        // Tokenize only the header region (bytes before /First) rather than
61        // the whole raw stream - otherwise, once a huge /N outruns the real
62        // header pairs, the tokenizer keeps reading into the object data
63        // that follows and misinterprets it as further bogus pairs instead
64        // of ever reaching EOF where the header actually ends.
65        $headerData      = substr($raw, 0, $first);
66        $headerTokenizer = new Tokenizer($headerData, 0);
67        $pairs = [];
68
69        for ($i = 0; $i < $n; $i++) {
70            $objNumToken = $headerTokenizer->next();
71            if ($objNumToken['type'] === 'eof') {
72                break;
73            }
74            $offsetToken = $headerTokenizer->next();
75            if ($offsetToken['type'] === 'eof') {
76                break;
77            }
78            $pairs[] = [(int) $objNumToken['value'], (int) $offsetToken['value']];
79        }
80
81        $objects = [];
82
83        foreach ($pairs as [$objNum, $offset]) {
84            $tokenizer = new Tokenizer($raw, $first + $offset);
85            $parser    = new ObjectParser($tokenizer);
86            $objects[$objNum] = $parser->parseValue();
87        }
88
89        return $objects;
90    }
91
92}