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\PdfReader;
12
 
13
use setasign\Fpdi\FpdiException;
14
use setasign\Fpdi\GraphicsState;
15
use setasign\Fpdi\Math\Vector;
16
use setasign\Fpdi\PdfParser\Filter\FilterException;
17
use setasign\Fpdi\PdfParser\PdfParser;
18
use setasign\Fpdi\PdfParser\PdfParserException;
19
use setasign\Fpdi\PdfParser\Type\PdfArray;
20
use setasign\Fpdi\PdfParser\Type\PdfDictionary;
21
use setasign\Fpdi\PdfParser\Type\PdfHexString;
22
use setasign\Fpdi\PdfParser\Type\PdfIndirectObject;
23
use setasign\Fpdi\PdfParser\Type\PdfName;
24
use setasign\Fpdi\PdfParser\Type\PdfNull;
25
use setasign\Fpdi\PdfParser\Type\PdfNumeric;
26
use setasign\Fpdi\PdfParser\Type\PdfStream;
27
use setasign\Fpdi\PdfParser\Type\PdfString;
28
use setasign\Fpdi\PdfParser\Type\PdfType;
29
use setasign\Fpdi\PdfParser\Type\PdfTypeException;
30
use setasign\Fpdi\PdfReader\DataStructure\Rectangle;
31
use setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException;
32
 
33
/**
34
 * Class representing a page of a PDF document
35
 */
36
class Page
37
{
38
    /**
39
     * @var PdfIndirectObject
40
     */
41
    protected $pageObject;
42
 
43
    /**
44
     * @var PdfDictionary
45
     */
46
    protected $pageDictionary;
47
 
48
    /**
49
     * @var PdfParser
50
     */
51
    protected $parser;
52
 
53
    /**
54
     * Inherited attributes
55
     *
56
     * @var null|array
57
     */
58
    protected $inheritedAttributes;
59
 
60
    /**
61
     * Page constructor.
62
     *
63
     * @param PdfIndirectObject $page
64
     * @param PdfParser $parser
65
     */
66
    public function __construct(PdfIndirectObject $page, PdfParser $parser)
67
    {
68
        $this->pageObject = $page;
69
        $this->parser = $parser;
70
    }
71
 
72
    /**
73
     * Get the indirect object of this page.
74
     *
75
     * @return PdfIndirectObject
76
     */
77
    public function getPageObject()
78
    {
79
        return $this->pageObject;
80
    }
81
 
82
    /**
83
     * Get the dictionary of this page.
84
     *
85
     * @return PdfDictionary
86
     * @throws PdfParserException
87
     * @throws PdfTypeException
88
     * @throws CrossReferenceException
89
     */
90
    public function getPageDictionary()
91
    {
92
        if ($this->pageDictionary === null) {
93
            $this->pageDictionary = PdfDictionary::ensure(PdfType::resolve($this->getPageObject(), $this->parser));
94
        }
95
 
96
        return $this->pageDictionary;
97
    }
98
 
99
    /**
100
     * Get a page attribute.
101
     *
102
     * @param string $name
103
     * @param bool $inherited
104
     * @return PdfType|null
105
     * @throws PdfParserException
106
     * @throws PdfTypeException
107
     * @throws CrossReferenceException
108
     */
109
    public function getAttribute($name, $inherited = true)
110
    {
111
        $dict = $this->getPageDictionary();
112
 
113
        if (isset($dict->value[$name])) {
114
            return $dict->value[$name];
115
        }
116
 
117
        $inheritedKeys = ['Resources', 'MediaBox', 'CropBox', 'Rotate'];
118
        if ($inherited && \in_array($name, $inheritedKeys, true)) {
119
            if ($this->inheritedAttributes === null) {
120
                $this->inheritedAttributes = [];
121
                $inheritedKeys = \array_filter($inheritedKeys, function ($key) use ($dict) {
122
                    return !isset($dict->value[$key]);
123
                });
124
 
125
                if (\count($inheritedKeys) > 0) {
126
                    $parentDict = PdfType::resolve(PdfDictionary::get($dict, 'Parent'), $this->parser);
127
                    while ($parentDict instanceof PdfDictionary) {
128
                        foreach ($inheritedKeys as $index => $key) {
129
                            if (isset($parentDict->value[$key])) {
130
                                $this->inheritedAttributes[$key] = $parentDict->value[$key];
131
                                unset($inheritedKeys[$index]);
132
                            }
133
                        }
134
 
135
                        /** @noinspection NotOptimalIfConditionsInspection */
136
                        if (isset($parentDict->value['Parent']) && \count($inheritedKeys) > 0) {
137
                            $parentDict = PdfType::resolve(PdfDictionary::get($parentDict, 'Parent'), $this->parser);
138
                        } else {
139
                            break;
140
                        }
141
                    }
142
                }
143
            }
144
 
145
            if (isset($this->inheritedAttributes[$name])) {
146
                return $this->inheritedAttributes[$name];
147
            }
148
        }
149
 
150
        return null;
151
    }
152
 
153
    /**
154
     * Get the rotation value.
155
     *
156
     * @return int
157
     * @throws PdfParserException
158
     * @throws PdfTypeException
159
     * @throws CrossReferenceException
160
     */
161
    public function getRotation()
162
    {
163
        $rotation = $this->getAttribute('Rotate');
164
        if ($rotation === null) {
165
            return 0;
166
        }
167
 
168
        $rotation = PdfNumeric::ensure(PdfType::resolve($rotation, $this->parser))->value % 360;
169
 
170
        if ($rotation < 0) {
171
            $rotation += 360;
172
        }
173
 
174
        return $rotation;
175
    }
176
 
177
    /**
178
     * Get a boundary of this page.
179
     *
180
     * @param string $box
181
     * @param bool $fallback
182
     * @return bool|Rectangle
183
     * @throws PdfParserException
184
     * @throws PdfTypeException
185
     * @throws CrossReferenceException
186
     * @see PageBoundaries
187
     */
188
    public function getBoundary($box = PageBoundaries::CROP_BOX, $fallback = true)
189
    {
190
        $value = $this->getAttribute($box);
191
 
192
        if ($value !== null) {
193
            return Rectangle::byPdfArray($value, $this->parser);
194
        }
195
 
196
        if ($fallback === false) {
197
            return false;
198
        }
199
 
200
        switch ($box) {
201
            case PageBoundaries::BLEED_BOX:
202
            case PageBoundaries::TRIM_BOX:
203
            case PageBoundaries::ART_BOX:
204
                return $this->getBoundary(PageBoundaries::CROP_BOX, true);
205
            case PageBoundaries::CROP_BOX:
206
                return $this->getBoundary(PageBoundaries::MEDIA_BOX, true);
207
        }
208
 
209
        return false;
210
    }
211
 
212
    /**
213
     * Get the width and height of this page.
214
     *
215
     * @param string $box
216
     * @param bool $fallback
217
     * @return array|bool
218
     * @throws PdfParserException
219
     * @throws PdfTypeException
220
     * @throws CrossReferenceException
221
     */
222
    public function getWidthAndHeight($box = PageBoundaries::CROP_BOX, $fallback = true)
223
    {
224
        $boundary = $this->getBoundary($box, $fallback);
225
        if ($boundary === false) {
226
            return false;
227
        }
228
 
229
        $rotation = $this->getRotation();
230
        $interchange = ($rotation / 90) % 2;
231
 
232
        return [
233
            $interchange ? $boundary->getHeight() : $boundary->getWidth(),
234
            $interchange ? $boundary->getWidth() : $boundary->getHeight()
235
        ];
236
    }
237
 
238
    /**
239
     * Get the raw content stream.
240
     *
241
     * @return string
242
     * @throws PdfReaderException
243
     * @throws PdfTypeException
244
     * @throws FilterException
245
     * @throws PdfParserException
246
     */
247
    public function getContentStream()
248
    {
249
        $dict = $this->getPageDictionary();
250
        $contents = PdfType::resolve(PdfDictionary::get($dict, 'Contents'), $this->parser);
251
        if ($contents instanceof PdfNull) {
252
            return '';
253
        }
254
 
255
        if ($contents instanceof PdfArray) {
256
            $result = [];
257
            foreach ($contents->value as $content) {
258
                $content = PdfType::resolve($content, $this->parser);
259
                if (!($content instanceof PdfStream)) {
260
                    continue;
261
                }
262
                $result[] = $content->getUnfilteredStream();
263
            }
264
 
265
            return \implode("\n", $result);
266
        }
267
 
268
        if ($contents instanceof PdfStream) {
269
            return $contents->getUnfilteredStream();
270
        }
271
 
272
        throw new PdfReaderException(
273
            'Array or stream expected.',
274
            PdfReaderException::UNEXPECTED_DATA_TYPE
275
        );
276
    }
277
 
278
    /**
279
     * Get information of all external links on this page.
280
     *
281
     * All coordinates are normalized in view to rotation and translation of the boundary-box, so that their
282
     * origin is lower-left.
283
     *
284
     * @return array
285
     */
286
    public function getExternalLinks($box = PageBoundaries::CROP_BOX)
287
    {
288
        try {
289
            $dict = $this->getPageDictionary();
290
            $annotations = PdfType::resolve(PdfDictionary::get($dict, 'Annots'), $this->parser);
291
        } catch (FpdiException $e) {
292
            return [];
293
        }
294
 
295
        if (!$annotations instanceof PdfArray) {
296
            return [];
297
        }
298
 
299
        $links = [];
300
 
301
        foreach ($annotations->value as $entry) {
302
            try {
303
                $annotation = PdfType::resolve($entry, $this->parser);
304
 
305
                $value = PdfType::resolve(PdfDictionary::get($annotation, 'Subtype'), $this->parser);
306
                if (!$value instanceof PdfName || $value->value !== 'Link') {
307
                    continue;
308
                }
309
 
310
                $dest = PdfType::resolve(PdfDictionary::get($annotation, 'Dest'), $this->parser);
311
                if (!$dest instanceof PdfNull) {
312
                    continue;
313
                }
314
 
315
                $action = PdfType::resolve(PdfDictionary::get($annotation, 'A'), $this->parser);
316
                if (!$action instanceof PdfDictionary) {
317
                    continue;
318
                }
319
 
320
                $actionType = PdfType::resolve(PdfDictionary::get($action, 'S'), $this->parser);
321
                if (!$actionType instanceof PdfName || $actionType->value !== 'URI') {
322
                    continue;
323
                }
324
 
325
                $uri = PdfType::resolve(PdfDictionary::get($action, 'URI'), $this->parser);
326
                if ($uri instanceof PdfString) {
327
                    $uriValue = PdfString::unescape($uri->value);
328
                } elseif ($uri instanceof PdfHexString) {
329
                    $uriValue = \hex2bin($uri->value);
330
                } else {
331
                    continue;
332
                }
333
 
334
                $rect = PdfType::resolve(PdfDictionary::get($annotation, 'Rect'), $this->parser);
335
                if (!$rect instanceof PdfArray || count($rect->value) !== 4) {
336
                    continue;
337
                }
338
 
339
                $rect = Rectangle::byPdfArray($rect, $this->parser);
340
                if ($rect->getWidth() === 0 || $rect->getHeight() === 0) {
341
                    continue;
342
                }
343
 
344
                $bbox = $this->getBoundary($box);
345
                $rotation = $this->getRotation();
346
 
347
                $gs = new GraphicsState();
348
                $gs->translate(-$bbox->getLlx(), -$bbox->getLly());
349
                $gs->rotate($bbox->getLlx(), $bbox->getLly(), -$rotation);
350
 
351
                switch ($rotation) {
352
                    case 90:
353
                        $gs->translate(-$bbox->getWidth(), 0);
354
                        break;
355
                    case 180:
356
                        $gs->translate(-$bbox->getWidth(), -$bbox->getHeight());
357
                        break;
358
                    case 270:
359
                        $gs->translate(0, -$bbox->getHeight());
360
                        break;
361
                }
362
 
363
                $normalizedRect = Rectangle::byVectors(
364
                    $gs->toUserSpace(new Vector($rect->getLlx(), $rect->getLly())),
365
                    $gs->toUserSpace(new Vector($rect->getUrx(), $rect->getUry()))
366
                );
367
 
368
                $quadPoints = PdfType::resolve(PdfDictionary::get($annotation, 'QuadPoints'), $this->parser);
369
                $normalizedQuadPoints = [];
370
                if ($quadPoints instanceof PdfArray) {
371
                    $quadPointsCount = count($quadPoints->value);
372
                    if ($quadPointsCount % 8 === 0) {
373
                        for ($i = 0; ($i + 1) < $quadPointsCount; $i += 2) {
374
                            $x = PdfNumeric::ensure(PdfType::resolve($quadPoints->value[$i], $this->parser));
375
                            $y = PdfNumeric::ensure(PdfType::resolve($quadPoints->value[$i + 1], $this->parser));
376
 
377
                            $v = $gs->toUserSpace(new Vector($x->value, $y->value));
378
                            $normalizedQuadPoints[] = $v->getX();
379
                            $normalizedQuadPoints[] = $v->getY();
380
                        }
381
                    }
382
                }
383
 
384
                // we remove unsupported/unneeded values here
385
                unset(
386
                    $annotation->value['P'],
387
                    $annotation->value['NM'],
388
                    $annotation->value['AP'],
389
                    $annotation->value['AS'],
390
                    $annotation->value['Type'],
391
                    $annotation->value['Subtype'],
392
                    $annotation->value['Rect'],
393
                    $annotation->value['A'],
394
                    $annotation->value['QuadPoints'],
395
                    $annotation->value['Rotate'],
396
                    $annotation->value['M'],
397
                    $annotation->value['StructParent'],
398
                    $annotation->value['OC']
399
                );
400
 
401
                // ...and flatten the PDF object to eliminate any indirect references.
402
                // Indirect references are a problem when writing the output in FPDF
403
                // because FPDF uses pre-calculated object numbers while FPDI creates
404
                // them at runtime.
405
                $annotation = PdfType::flatten($annotation, $this->parser);
406
 
407
                $links[] = [
408
                    'rect' => $normalizedRect,
409
                    'quadPoints' => $normalizedQuadPoints,
410
                    'uri' => $uriValue,
411
                    'pdfObject' => $annotation
412
                ];
413
            } catch (FpdiException $e) {
414
                continue;
415
            }
416
        }
417
 
418
        return $links;
419
    }
420
}