Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
/**
4
 * This file is part of FPDI
5
 *
6
 * @package   setasign\Fpdi
7
 * @copyright Copyright (c) 2023 Setasign GmbH & Co. KG (https://www.setasign.com)
8
 * @license   http://opensource.org/licenses/mit-license The MIT License
9
 */
10
 
11
namespace setasign\Fpdi\PdfParser\Type;
12
 
13
use setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException;
14
use setasign\Fpdi\PdfParser\Filter\Ascii85;
15
use setasign\Fpdi\PdfParser\Filter\AsciiHex;
16
use setasign\Fpdi\PdfParser\Filter\FilterException;
17
use setasign\Fpdi\PdfParser\Filter\Flate;
18
use setasign\Fpdi\PdfParser\Filter\Lzw;
19
use setasign\Fpdi\PdfParser\PdfParser;
20
use setasign\Fpdi\PdfParser\PdfParserException;
21
use setasign\Fpdi\PdfParser\StreamReader;
22
use setasign\FpdiPdfParser\PdfParser\Filter\Predictor;
23
 
24
/**
25
 * Class representing a PDF stream object
26
 */
27
class PdfStream extends PdfType
28
{
29
    /**
30
     * Parses a stream from a stream reader.
31
     *
32
     * @param PdfDictionary $dictionary
33
     * @param StreamReader $reader
34
     * @param PdfParser $parser Optional to keep backwards compatibility
35
     * @return self
36
     * @throws PdfTypeException
37
     */
38
    public static function parse(PdfDictionary $dictionary, StreamReader $reader, PdfParser $parser = null)
39
    {
40
        $v = new self();
41
        $v->value = $dictionary;
42
        $v->reader = $reader;
43
        $v->parser = $parser;
44
 
45
        $offset = $reader->getOffset();
46
 
47
        // Find the first "newline"
48
        while (($firstByte = $reader->getByte($offset)) !== false) {
49
            $offset++;
50
            if ($firstByte === "\n" || $firstByte === "\r") {
51
                break;
52
            }
53
        }
54
 
55
        if ($firstByte === false) {
56
            throw new PdfTypeException(
57
                'Unable to parse stream data. No newline after the stream keyword found.',
58
                PdfTypeException::NO_NEWLINE_AFTER_STREAM_KEYWORD
59
            );
60
        }
61
 
62
        $sndByte = $reader->getByte($offset);
63
        if ($sndByte === "\n" && $firstByte !== "\n") {
64
            $offset++;
65
        }
66
 
67
        $reader->setOffset($offset);
68
        // let's only save the byte-offset and read the stream only when needed
69
        $v->stream = $reader->getPosition() + $reader->getOffset();
70
 
71
        return $v;
72
    }
73
 
74
    /**
75
     * Helper method to create an instance.
76
     *
77
     * @param PdfDictionary $dictionary
78
     * @param string $stream
79
     * @return self
80
     */
81
    public static function create(PdfDictionary $dictionary, $stream)
82
    {
83
        $v = new self();
84
        $v->value = $dictionary;
85
        $v->stream = (string) $stream;
86
 
87
        return $v;
88
    }
89
 
90
    /**
91
     * Ensures that the passed value is a PdfStream instance.
92
     *
93
     * @param mixed $stream
94
     * @return self
95
     * @throws PdfTypeException
96
     */
97
    public static function ensure($stream)
98
    {
99
        return PdfType::ensureType(self::class, $stream, 'Stream value expected.');
100
    }
101
 
102
    /**
103
     * The stream or its byte-offset position.
104
     *
105
     * @var int|string
106
     */
107
    protected $stream;
108
 
109
    /**
110
     * The stream reader instance.
111
     *
112
     * @var StreamReader|null
113
     */
114
    protected $reader;
115
 
116
    /**
117
     * The PDF parser instance.
118
     *
119
     * @var PdfParser
120
     */
121
    protected $parser;
122
 
123
    /**
124
     * Get the stream data.
125
     *
126
     * @param bool $cache Whether cache the stream data or not.
127
     * @return bool|string
128
     * @throws PdfTypeException
129
     * @throws CrossReferenceException
130
     * @throws PdfParserException
131
     */
132
    public function getStream($cache = false)
133
    {
134
        if (\is_int($this->stream)) {
135
            $length = PdfDictionary::get($this->value, 'Length');
136
            if ($this->parser !== null) {
137
                $length = PdfType::resolve($length, $this->parser);
138
            }
139
 
140
            if (!($length instanceof PdfNumeric) || $length->value === 0) {
141
                $this->reader->reset($this->stream, 100000);
142
                $buffer = $this->extractStream();
143
            } else {
144
                $this->reader->reset($this->stream, $length->value);
145
                $buffer = $this->reader->getBuffer(false);
146
                if ($this->parser !== null) {
147
                    $this->reader->reset($this->stream + strlen($buffer));
148
                    $this->parser->getTokenizer()->clearStack();
149
                    $token = $this->parser->readValue();
150
                    if ($token === false || !($token instanceof PdfToken) || $token->value !== 'endstream') {
151
                        $this->reader->reset($this->stream, 100000);
152
                        $buffer = $this->extractStream();
153
                        $this->reader->reset($this->stream + strlen($buffer));
154
                    }
155
                }
156
            }
157
 
158
            if ($cache === false) {
159
                return $buffer;
160
            }
161
 
162
            $this->stream = $buffer;
163
            $this->reader = null;
164
        }
165
 
166
        return $this->stream;
167
    }
168
 
169
    /**
170
     * Extract the stream "manually".
171
     *
172
     * @return string
173
     * @throws PdfTypeException
174
     */
175
    protected function extractStream()
176
    {
177
        while (true) {
178
            $buffer = $this->reader->getBuffer(false);
179
            $length = \strpos($buffer, 'endstream');
180
            if ($length === false) {
181
                if (!$this->reader->increaseLength(100000)) {
182
                    throw new PdfTypeException('Cannot extract stream.');
183
                }
184
                continue;
185
            }
186
            break;
187
        }
188
 
189
        $buffer = \substr($buffer, 0, $length);
190
        $lastByte = \substr($buffer, -1);
191
 
192
        /* Check for EOL marker =
193
         *   CARRIAGE RETURN (\r) and a LINE FEED (\n) or just a LINE FEED (\n},
194
         *   and not by a CARRIAGE RETURN (\r) alone
195
         */
196
        if ($lastByte === "\n") {
197
            $buffer = \substr($buffer, 0, -1);
198
 
199
            $lastByte = \substr($buffer, -1);
200
            if ($lastByte === "\r") {
201
                $buffer = \substr($buffer, 0, -1);
202
            }
203
        }
204
 
205
        // There are streams in the wild, which have only white signs in them but need to be parsed manually due
206
        // to a problem encountered before (e.g. Length === 0). We should set them to empty streams to avoid problems
207
        // in further processing (e.g. applying of filters).
208
        if (trim($buffer) === '') {
209
            $buffer = '';
210
        }
211
 
212
        return $buffer;
213
    }
214
 
215
    /**
216
     * Get all filters defined for this stream.
217
     *
218
     * @return PdfType[]
219
     * @throws PdfTypeException
220
     */
221
    public function getFilters()
222
    {
223
        $filters = PdfDictionary::get($this->value, 'Filter');
224
        if ($filters instanceof PdfNull) {
225
            return [];
226
        }
227
 
228
        if ($filters instanceof PdfArray) {
229
            $filters = $filters->value;
230
        } else {
231
            $filters = [$filters];
232
        }
233
 
234
        return $filters;
235
    }
236
 
237
    /**
238
     * Get the unfiltered stream data.
239
     *
240
     * @return string
241
     * @throws FilterException
242
     * @throws PdfParserException
243
     */
244
    public function getUnfilteredStream()
245
    {
246
        $stream = $this->getStream();
247
        $filters = $this->getFilters();
248
        if ($filters === []) {
249
            return $stream;
250
        }
251
 
252
        $decodeParams = PdfDictionary::get($this->value, 'DecodeParms');
253
        if ($decodeParams instanceof PdfArray) {
254
            $decodeParams = $decodeParams->value;
255
        } else {
256
            $decodeParams = [$decodeParams];
257
        }
258
 
259
        foreach ($filters as $key => $filter) {
260
            if (!($filter instanceof PdfName)) {
261
                continue;
262
            }
263
 
264
            $decodeParam = null;
265
            if (isset($decodeParams[$key])) {
266
                $decodeParam = ($decodeParams[$key] instanceof PdfDictionary ? $decodeParams[$key] : null);
267
            }
268
 
269
            switch ($filter->value) {
270
                case 'FlateDecode':
271
                case 'Fl':
272
                case 'LZWDecode':
273
                case 'LZW':
274
                    if (\strpos($filter->value, 'LZW') === 0) {
275
                        $filterObject = new Lzw();
276
                    } else {
277
                        $filterObject = new Flate();
278
                    }
279
 
280
                    $stream = $filterObject->decode($stream);
281
 
282
                    if ($decodeParam instanceof PdfDictionary) {
283
                        $predictor = PdfDictionary::get($decodeParam, 'Predictor', PdfNumeric::create(1));
284
                        if ($predictor->value !== 1) {
285
                            if (!\class_exists(Predictor::class)) {
286
                                throw new PdfParserException(
287
                                    'This PDF document makes use of features which are only implemented in the ' .
288
                                    'commercial "FPDI PDF-Parser" add-on (see https://www.setasign.com/fpdi-pdf-' .
289
                                    'parser).',
290
                                    PdfParserException::IMPLEMENTED_IN_FPDI_PDF_PARSER
291
                                );
292
                            }
293
 
294
                            $colors = PdfDictionary::get($decodeParam, 'Colors', PdfNumeric::create(1));
295
                            $bitsPerComponent = PdfDictionary::get(
296
                                $decodeParam,
297
                                'BitsPerComponent',
298
                                PdfNumeric::create(8)
299
                            );
300
 
301
                            $columns = PdfDictionary::get($decodeParam, 'Columns', PdfNumeric::create(1));
302
 
303
                            $filterObject = new Predictor(
304
                                $predictor->value,
305
                                $colors->value,
306
                                $bitsPerComponent->value,
307
                                $columns->value
308
                            );
309
 
310
                            $stream = $filterObject->decode($stream);
311
                        }
312
                    }
313
 
314
                    break;
315
                case 'ASCII85Decode':
316
                case 'A85':
317
                    $filterObject = new Ascii85();
318
                    $stream = $filterObject->decode($stream);
319
                    break;
320
 
321
                case 'ASCIIHexDecode':
322
                case 'AHx':
323
                    $filterObject = new AsciiHex();
324
                    $stream = $filterObject->decode($stream);
325
                    break;
326
 
327
                case 'Crypt':
328
                    if (!$decodeParam instanceof PdfDictionary) {
329
                        break;
330
                    }
331
                    // Filter is "Identity"
332
                    $name = PdfDictionary::get($decodeParam, 'Name');
333
                    if (!$name instanceof PdfName || $name->value !== 'Identity') {
334
                        break;
335
                    }
336
 
337
                    throw new FilterException(
338
                        'Support for Crypt filters other than "Identity" is not implemented.',
339
                        FilterException::UNSUPPORTED_FILTER
340
                    );
341
 
342
                default:
343
                    throw new FilterException(
344
                        \sprintf('Unsupported filter "%s".', $filter->value),
345
                        FilterException::UNSUPPORTED_FILTER
346
                    );
347
            }
348
        }
349
 
350
        return $stream;
351
    }
352
}