xref: /plugin/dw2pdf/vendor/setasign/fpdi/src/PdfParser/Tokenizer.php (revision 92200750cd9cfc4a9489fe7c04c6e41b5a3de6ee) !
1<?php
2
3/**
4 * This file is part of FPDI
5 *
6 * @package   setasign\Fpdi
7 * @copyright Copyright (c) 2024 Setasign GmbH & Co. KG (https://www.setasign.com)
8 * @license   http://opensource.org/licenses/mit-license The MIT License
9 */
10
11namespace setasign\Fpdi\PdfParser;
12
13/**
14 * A tokenizer class.
15 */
16class Tokenizer
17{
18    /**
19     * @var StreamReader
20     */
21    protected $streamReader;
22
23    /**
24     * A token stack.
25     *
26     * @var string[]
27     */
28    protected $stack = [];
29
30    /**
31     * Tokenizer constructor.
32     *
33     * @param StreamReader $streamReader
34     */
35    public function __construct(StreamReader $streamReader)
36    {
37        $this->streamReader = $streamReader;
38    }
39
40    /**
41     * Get the stream reader instance.
42     *
43     * @return StreamReader
44     */
45    public function getStreamReader()
46    {
47        return $this->streamReader;
48    }
49
50    /**
51     * Clear the token stack.
52     */
53    public function clearStack()
54    {
55        $this->stack = [];
56    }
57
58    /**
59     * Push a token onto the stack.
60     *
61     * @param string $token
62     */
63    public function pushStack($token)
64    {
65        $this->stack[] = $token;
66    }
67
68    /**
69     * Get next token.
70     *
71     * @return false|string
72     */
73    public function getNextToken()
74    {
75        $token = \array_pop($this->stack);
76        if ($token !== null) {
77            return $token;
78        }
79
80        if (($byte = $this->streamReader->readByte()) === false) {
81            return false;
82        }
83
84        if (\in_array($byte, ["\x20", "\x0A", "\x0D", "\x0C", "\x09", "\x00"], true)) {
85            if ($this->leapWhiteSpaces() === false) {
86                return false;
87            }
88            $byte = $this->streamReader->readByte();
89        }
90
91        switch ($byte) {
92            case '/':
93            case '[':
94            case ']':
95            case '(':
96            case ')':
97            case '{':
98            case '}':
99            case '<':
100            case '>':
101                return $byte;
102            case '%':
103                $this->streamReader->readLine();
104                return $this->getNextToken();
105        }
106
107        /* This way is faster than checking single bytes.
108         */
109        $bufferOffset = $this->streamReader->getOffset();
110        do {
111            $lastBuffer = $this->streamReader->getBuffer(false);
112            $pos = \strcspn(
113                $lastBuffer,
114                "\x00\x09\x0A\x0C\x0D\x20()<>[]{}/%",
115                $bufferOffset
116            );
117        } while (
118            // Break the loop if a delimiter or white space char is matched
119            // in the current buffer or increase the buffers length
120            $bufferOffset + $pos === \strlen($lastBuffer)
121            && $this->streamReader->increaseLength()
122        );
123
124        $result = \substr($lastBuffer, $bufferOffset - 1, $pos + 1);
125        $this->streamReader->setOffset($bufferOffset + $pos);
126
127        return $result;
128    }
129
130    /**
131     * Leap white spaces.
132     *
133     * @return boolean
134     */
135    public function leapWhiteSpaces()
136    {
137        do {
138            if (!$this->streamReader->ensureContent()) {
139                return false;
140            }
141
142            $buffer = $this->streamReader->getBuffer(false);
143            $matches = \strspn($buffer, "\x20\x0A\x0C\x0D\x09\x00", $this->streamReader->getOffset());
144            if ($matches > 0) {
145                $this->streamReader->addOffset($matches);
146            }
147        } while ($this->streamReader->getOffset() >= $this->streamReader->getBufferLength());
148
149        return true;
150    }
151}
152