Passed
Pull Request — master (#375)
by Konrad
07:51 queued 05:40
created

PDFObject::init()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
ccs 1
cts 1
cp 1
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/**
4
 * @file
5
 *          This file is part of the PdfParser library.
6
 *
7
 * @author  Sébastien MALOT <[email protected]>
8
 * @date    2017-01-03
9
 *
10
 * @license LGPLv3
11
 * @url     <https://github.com/smalot/pdfparser>
12
 *
13
 *  PdfParser is a pdf library written in PHP, extraction oriented.
14
 *  Copyright (C) 2017 - Sébastien MALOT <[email protected]>
15
 *
16
 *  This program is free software: you can redistribute it and/or modify
17
 *  it under the terms of the GNU Lesser General Public License as published by
18
 *  the Free Software Foundation, either version 3 of the License, or
19
 *  (at your option) any later version.
20
 *
21
 *  This program is distributed in the hope that it will be useful,
22
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 *  GNU Lesser General Public License for more details.
25
 *
26
 *  You should have received a copy of the GNU Lesser General Public License
27
 *  along with this program.
28
 *  If not, see <http://www.pdfparser.org/sites/default/LICENSE.txt>.
29
 */
30
31
namespace Smalot\PdfParser;
32
33
use Smalot\PdfParser\XObject\Form;
34
use Smalot\PdfParser\XObject\Image;
35
36
/**
37
 * Class PDFObject
38
 */
39
class PDFObject
40
{
41
    const TYPE = 't';
42
43
    const OPERATOR = 'o';
44
45
    const COMMAND = 'c';
46
47
    /**
48
     * The recursion stack.
49
     *
50
     * @var array
51
     */
52
    public static $recursionStack = [];
53
54
    /**
55
     * @var Document
56
     */
57
    protected $document = null;
58
59
    /**
60
     * @var Header
61
     */
62
    protected $header = null;
63
64
    /**
65
     * @var string
66
     */
67
    protected $content = null;
68
69
    /**
70
     * @var Config
71
     */
72
    protected $config;
73
74
    /**
75
     * @param Header $header
76
     * @param string $content
77
     * @param Config $config
78
     */
79 38
    public function __construct(
80
        Document $document,
81
        Header $header = null,
82
        $content = null,
83
        Config $config = null
84
    ) {
85 38
        $this->document = $document;
86 38
        $this->header = null !== $header ? $header : new Header();
87 38
        $this->content = $content;
88 38
        $this->config = $config;
89 38
    }
90
91 33
    public function init()
92
    {
93 33
    }
94
95
    /**
96
     * @return Header|null
97
     */
98 33
    public function getHeader()
99
    {
100 33
        return $this->header;
101
    }
102
103
    /**
104
     * @param string $name
105
     *
106
     * @return Element|PDFObject
107
     */
108 28
    public function get($name)
109
    {
110 28
        return $this->header->get($name);
111
    }
112
113
    /**
114
     * @param string $name
115
     *
116
     * @return bool
117
     */
118 27
    public function has($name)
119
    {
120 27
        return $this->header->has($name);
121
    }
122
123
    /**
124
     * @param bool $deep
125
     *
126
     * @return array
127
     */
128 1
    public function getDetails($deep = true)
129
    {
130 1
        return $this->header->getDetails($deep);
131
    }
132
133
    /**
134
     * @return string|null
135
     */
136 24
    public function getContent()
137
    {
138 24
        return $this->content;
139
    }
140
141
    /**
142
     * @param string $content
143
     */
144 18
    public function cleanContent($content, $char = 'X')
145
    {
146 18
        $char = $char[0];
147 18
        $content = str_replace(['\\\\', '\\)', '\\('], $char.$char, $content);
148
149
        // Remove image bloc with binary content
150 18
        preg_match_all('/\s(BI\s.*?(\sID\s).*?(\sEI))\s/s', $content, $matches, PREG_OFFSET_CAPTURE);
151 18
        foreach ($matches[0] as $part) {
152
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
153
        }
154
155
        // Clean content in square brackets [.....]
156 18
        preg_match_all('/\[((\(.*?\)|[0-9\.\-\s]*)*)\]/s', $content, $matches, PREG_OFFSET_CAPTURE);
157 18
        foreach ($matches[1] as $part) {
158 14
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
159
        }
160
161
        // Clean content in round brackets (.....)
162 18
        preg_match_all('/\((.*?)\)/s', $content, $matches, PREG_OFFSET_CAPTURE);
163 18
        foreach ($matches[1] as $part) {
164 13
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
165
        }
166
167
        // Clean structure
168 18
        if ($parts = preg_split('/(<|>)/s', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)) {
169 18
            $content = '';
170 18
            $level = 0;
171 18
            foreach ($parts as $part) {
172 18
                if ('<' == $part) {
173 11
                    ++$level;
174
                }
175
176 18
                $content .= (0 == $level ? $part : str_repeat($char, \strlen($part)));
177
178 18
                if ('>' == $part) {
179 11
                    --$level;
180
                }
181
            }
182
        }
183
184
        // Clean BDC and EMC markup
185 18
        preg_match_all(
186 18
            '/(\/[A-Za-z0-9\_]*\s*'.preg_quote($char).'*BDC)/s',
187
            $content,
188
            $matches,
189 18
            PREG_OFFSET_CAPTURE
190
        );
191 18
        foreach ($matches[1] as $part) {
192 4
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
193
        }
194
195 18
        preg_match_all('/\s(EMC)\s/s', $content, $matches, PREG_OFFSET_CAPTURE);
196 18
        foreach ($matches[1] as $part) {
197 7
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
198
        }
199
200 18
        return $content;
201
    }
202
203
    /**
204
     * @param string $content
205
     *
206
     * @return array
207
     */
208 17
    public function getSectionsText($content)
209
    {
210 17
        $sections = [];
211 17
        $content = ' '.$content.' ';
212 17
        $textCleaned = $this->cleanContent($content, '_');
213
214
        // Extract text blocks.
215 17
        if (preg_match_all('/\s+BT[\s|\(|\[]+(.*?)\s*ET/s', $textCleaned, $matches, PREG_OFFSET_CAPTURE)) {
216 17
            foreach ($matches[1] as $part) {
217 17
                $text = $part[0];
218 17
                if ('' === $text) {
219
                    continue;
220
                }
221 17
                $offset = $part[1];
222 17
                $section = substr($content, $offset, \strlen($text));
223
224
                // Removes BDC and EMC markup.
225 17
                $section = preg_replace('/(\/[A-Za-z0-9]+\s*<<.*?)(>>\s*BDC)(.*?)(EMC\s+)/s', '${3}', $section.' ');
226
227 17
                $sections[] = $section;
228
            }
229
        }
230
231
        // Extract 'do' commands.
232 17
        if (preg_match_all('/(\/[A-Za-z0-9\.\-_]+\s+Do)\s/s', $textCleaned, $matches, PREG_OFFSET_CAPTURE)) {
233 3
            foreach ($matches[1] as $part) {
234 3
                $text = $part[0];
235 3
                $offset = $part[1];
236 3
                $section = substr($content, $offset, \strlen($text));
237
238 3
                $sections[] = $section;
239
            }
240
        }
241
242 17
        return $sections;
243
    }
244
245 10
    private function getDefaultFont(Page $page = null)
246
    {
247 10
        $fonts = [];
248 10
        if (null !== $page) {
249 10
            $fonts = $page->getFonts();
250
        }
251
252 10
        $fonts = array_merge($fonts, array_values($this->document->getFonts()));
253
254 10
        if (\count($fonts) > 0) {
255 10
            return reset($fonts);
256
        }
257
258
        return new Font($this->document);
259
    }
260
261
    /**
262
     * @param Page $page
263
     *
264
     * @return string
265
     *
266
     * @throws \Exception
267
     */
268 10
    public function getText(Page $page = null)
269
    {
270 10
        $text = '';
271 10
        $sections = $this->getSectionsText($this->content);
272 10
        $current_font = $this->getDefaultFont($page);
273
274 10
        $current_position_td = ['x' => false, 'y' => false];
275 10
        $current_position_tm = ['x' => false, 'y' => false];
276
277 10
        self::$recursionStack[] = $this->getUniqueId();
278
279 10
        foreach ($sections as $section) {
280 10
            $commands = $this->getCommandsText($section);
281
282 10
            foreach ($commands as $command) {
283 10
                switch ($command[self::OPERATOR]) {
284
                    // set character spacing
285 10
                    case 'Tc':
286 2
                        break;
287
288
                    // move text current point
289 10
                    case 'Td':
290 8
                        $args = preg_split('/\s/s', $command[self::COMMAND]);
291 8
                        $y = array_pop($args);
0 ignored issues
show
Bug introduced by
It seems like $args can also be of type false; however, parameter $array of array_pop() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

291
                        $y = array_pop(/** @scrutinizer ignore-type */ $args);
Loading history...
292 8
                        $x = array_pop($args);
293 8
                        if (((float) $x <= 0) ||
294 8
                            (false !== $current_position_td['y'] && (float) $y < (float) ($current_position_td['y']))
295
                        ) {
296
                            // vertical offset
297 5
                            $text .= "\n";
298 8
                        } elseif (false !== $current_position_td['x'] && (float) $x > (float) (
299 8
                                $current_position_td['x']
300
                            )
301
                        ) {
302
                            // horizontal offset
303 6
                            $text .= ' ';
304
                        }
305 8
                        $current_position_td = ['x' => $x, 'y' => $y];
306 8
                        break;
307
308
                    // move text current point and set leading
309 10
                    case 'TD':
310 2
                        $args = preg_split('/\s/s', $command[self::COMMAND]);
311 2
                        $y = array_pop($args);
312 2
                        $x = array_pop($args);
313 2
                        if ((float) $y < 0) {
314 2
                            $text .= "\n";
315
                        } elseif ((float) $x <= 0) {
316
                            $text .= ' ';
317
                        }
318 2
                        break;
319
320 10
                    case 'Tf':
321 10
                        list($id) = preg_split('/\s/s', $command[self::COMMAND]);
322 10
                        $id = trim($id, '/');
323 10
                        if (null !== $page) {
324 10
                            $new_font = $page->getFont($id);
325
                            // If an invalid font ID is given, do not update the font.
326
                            // This should theoretically never happen, as the PDF spec states for the Tf operator:
327
                            // "The specified font value shall match a resource name in the Font entry of the default resource dictionary"
328
                            // (https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf, page 435)
329
                            // But we want to make sure that malformed PDFs do not simply crash.
330 10
                            if (null !== $new_font) {
331 9
                                $current_font = $new_font;
332
                            }
333
                        }
334 10
                        break;
335
336 10
                    case "'":
337 10
                    case 'Tj':
338 7
                        $command[self::COMMAND] = [$command];
339
                        // no break
340 10
                    case 'TJ':
341 10
                        $sub_text = $current_font->decodeText($command[self::COMMAND]);
342 10
                        $text .= $sub_text;
343 10
                        break;
344
345
                    // set leading
346 9
                    case 'TL':
347 1
                        $text .= ' ';
348 1
                        break;
349
350 9
                    case 'Tm':
351 9
                        $args = preg_split('/\s/s', $command[self::COMMAND]);
352 9
                        $y = array_pop($args);
353 9
                        $x = array_pop($args);
354 9
                        if (false !== $current_position_tm['x']) {
355 9
                            $delta = abs((float) $x - (float) ($current_position_tm['x']));
356 9
                            if ($delta > 10) {
357 7
                                $text .= "\t";
358
                            }
359
                        }
360 9
                        if (false !== $current_position_tm['y']) {
361 9
                            $delta = abs((float) $y - (float) ($current_position_tm['y']));
362 9
                            if ($delta > 10) {
363 5
                                $text .= "\n";
364
                            }
365
                        }
366 9
                        $current_position_tm = ['x' => $x, 'y' => $y];
367 9
                        break;
368
369
                    // set super/subscripting text rise
370 6
                    case 'Ts':
371
                        break;
372
373
                    // set word spacing
374 6
                    case 'Tw':
375 2
                        break;
376
377
                    // set horizontal scaling
378 6
                    case 'Tz':
379
                        $text .= "\n";
380
                        break;
381
382
                    // move to start of next line
383 6
                    case 'T*':
384 3
                        $text .= "\n";
385 3
                        break;
386
387 5
                    case 'Da':
388
                        break;
389
390 5
                    case 'Do':
391 3
                        if (null !== $page) {
392 3
                            $args = preg_split('/\s/s', $command[self::COMMAND]);
393 3
                            $id = trim(array_pop($args), '/ ');
394 3
                            $xobject = $page->getXObject($id);
395
396
                            // @todo $xobject could be a ElementXRef object, which would then throw an error
397 3
                            if (\is_object($xobject) && $xobject instanceof self && !\in_array($xobject->getUniqueId(), self::$recursionStack)) {
398
                                // Not a circular reference.
399 3
                                $text .= $xobject->getText($page);
400
                            }
401
                        }
402 3
                        break;
403
404 5
                    case 'rg':
405 5
                    case 'RG':
406 2
                        break;
407
408 5
                    case 're':
409
                        break;
410
411 5
                    case 'co':
412
                        break;
413
414 5
                    case 'cs':
415 1
                        break;
416
417 5
                    case 'gs':
418 4
                        break;
419
420 4
                    case 'en':
421
                        break;
422
423 4
                    case 'sc':
424 4
                    case 'SC':
425
                        break;
426
427 4
                    case 'g':
428 4
                    case 'G':
429 2
                        break;
430
431 3
                    case 'V':
432
                        break;
433
434 3
                    case 'vo':
435 3
                    case 'Vo':
436
                        break;
437
438
                    default:
439
                }
440
            }
441
        }
442
443 10
        array_pop(self::$recursionStack);
444
445 10
        return $text.' ';
446
    }
447
448
    /**
449
     * @param Page $page
450
     *
451
     * @return array
452
     *
453
     * @throws \Exception
454
     */
455 3
    public function getTextArray(Page $page = null)
456
    {
457 3
        $text = [];
458 3
        $sections = $this->getSectionsText($this->content);
459 3
        $current_font = new Font($this->document);
460
461 3
        foreach ($sections as $section) {
462 3
            $commands = $this->getCommandsText($section);
463
464 3
            foreach ($commands as $command) {
465 3
                switch ($command[self::OPERATOR]) {
466
                    // set character spacing
467 3
                    case 'Tc':
468 2
                        break;
469
470
                    // move text current point
471 3
                    case 'Td':
472 3
                        break;
473
474
                    // move text current point and set leading
475 3
                    case 'TD':
476
                        break;
477
478 3
                    case 'Tf':
479 3
                        if (null !== $page) {
480 3
                            list($id) = preg_split('/\s/s', $command[self::COMMAND]);
481 3
                            $id = trim($id, '/');
482 3
                            $current_font = $page->getFont($id);
483
                        }
484 3
                        break;
485
486 3
                    case "'":
487 3
                    case 'Tj':
488 3
                        $command[self::COMMAND] = [$command];
489
                        // no break
490 3
                    case 'TJ':
491 3
                        $sub_text = $current_font->decodeText($command[self::COMMAND]);
492 3
                        $text[] = $sub_text;
493 3
                        break;
494
495
                    // set leading
496 3
                    case 'TL':
497 2
                        break;
498
499 3
                    case 'Tm':
500 2
                        break;
501
502
                    // set super/subscripting text rise
503 3
                    case 'Ts':
504
                        break;
505
506
                    // set word spacing
507 3
                    case 'Tw':
508 1
                        break;
509
510
                    // set horizontal scaling
511 3
                    case 'Tz':
512
                        //$text .= "\n";
513
                        break;
514
515
                    // move to start of next line
516 3
                    case 'T*':
517
                        //$text .= "\n";
518 2
                        break;
519
520 3
                    case 'Da':
521
                        break;
522
523 3
                    case 'Do':
524
                        if (null !== $page) {
525
                            $args = preg_split('/\s/s', $command[self::COMMAND]);
526
                            $id = trim(array_pop($args), '/ ');
0 ignored issues
show
Bug introduced by
It seems like $args can also be of type false; however, parameter $array of array_pop() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

526
                            $id = trim(array_pop(/** @scrutinizer ignore-type */ $args), '/ ');
Loading history...
527
                            if ($xobject = $page->getXObject($id)) {
528
                                $text[] = $xobject->getText($page);
529
                            }
530
                        }
531
                        break;
532
533 3
                    case 'rg':
534 3
                    case 'RG':
535 2
                        break;
536
537 3
                    case 're':
538
                        break;
539
540 3
                    case 'co':
541
                        break;
542
543 3
                    case 'cs':
544
                        break;
545
546 3
                    case 'gs':
547
                        break;
548
549 3
                    case 'en':
550
                        break;
551
552 3
                    case 'sc':
553 3
                    case 'SC':
554
                        break;
555
556 3
                    case 'g':
557 3
                    case 'G':
558 2
                        break;
559
560 1
                    case 'V':
561
                        break;
562
563 1
                    case 'vo':
564 1
                    case 'Vo':
565
                        break;
566
567
                    default:
568
                }
569
            }
570
        }
571
572 3
        return $text;
573
    }
574
575
    /**
576
     * @param string $text_part
577
     * @param int    $offset
578
     *
579
     * @return array
580
     */
581 17
    public function getCommandsText($text_part, &$offset = 0)
582
    {
583 17
        $commands = $matches = [];
584
585 17
        while ($offset < \strlen($text_part)) {
586 17
            $offset += strspn($text_part, "\x00\x09\x0a\x0c\x0d\x20", $offset);
587 17
            $char = $text_part[$offset];
588
589 17
            $operator = '';
590 17
            $type = '';
591 17
            $command = false;
592
593 17
            switch ($char) {
594 17
                case '/':
595 17
                    $type = $char;
596 17
                    if (preg_match(
597 17
                        '/^\/([A-Z0-9\._,\+]+\s+[0-9.\-]+)\s+([A-Z]+)\s*/si',
598 17
                        substr($text_part, $offset),
599
                        $matches
600
                    )
601
                    ) {
602 17
                        $operator = $matches[2];
603 17
                        $command = $matches[1];
604 17
                        $offset += \strlen($matches[0]);
605 5
                    } elseif (preg_match(
606 5
                        '/^\/([A-Z0-9\._,\+]+)\s+([A-Z]+)\s*/si',
607 5
                        substr($text_part, $offset),
608
                        $matches
609
                    )
610
                    ) {
611 5
                        $operator = $matches[2];
612 5
                        $command = $matches[1];
613 5
                        $offset += \strlen($matches[0]);
614
                    }
615 17
                    break;
616
617 17
                case '[':
618 17
                case ']':
619
                    // array object
620 16
                    $type = $char;
621 16
                    if ('[' == $char) {
622 16
                        ++$offset;
623
                        // get elements
624 16
                        $command = $this->getCommandsText($text_part, $offset);
625
626 16
                        if (preg_match('/^\s*[A-Z]{1,2}\s*/si', substr($text_part, $offset), $matches)) {
627 16
                            $operator = trim($matches[0]);
628 16
                            $offset += \strlen($matches[0]);
629
                        }
630
                    } else {
631 16
                        ++$offset;
632 16
                        break;
633
                    }
634 16
                    break;
635
636 17
                case '<':
637 17
                case '>':
638
                    // array object
639 7
                    $type = $char;
640 7
                    ++$offset;
641 7
                    if ('<' == $char) {
642 7
                        $strpos = strpos($text_part, '>', $offset);
643 7
                        $command = substr($text_part, $offset, ($strpos - $offset));
644 7
                        $offset = $strpos + 1;
645
                    }
646
647 7
                    if (preg_match('/^\s*[A-Z]{1,2}\s*/si', substr($text_part, $offset), $matches)) {
648 6
                        $operator = trim($matches[0]);
649 6
                        $offset += \strlen($matches[0]);
650
                    }
651 7
                    break;
652
653 17
                case '(':
654 17
                case ')':
655 13
                    ++$offset;
656 13
                    $type = $char;
657 13
                    $strpos = $offset;
658 13
                    if ('(' == $char) {
659 13
                        $open_bracket = 1;
660 13
                        while ($open_bracket > 0) {
661 13
                            if (!isset($text_part[$strpos])) {
662
                                break;
663
                            }
664 13
                            $ch = $text_part[$strpos];
665 13
                            switch ($ch) {
666 13
                                case '\\':
667
                                 // REVERSE SOLIDUS (5Ch) (Backslash)
668
                                    // skip next character
669 10
                                    ++$strpos;
670 10
                                    break;
671
672 13
                                case '(':
673
                                 // LEFT PARENHESIS (28h)
674
                                    ++$open_bracket;
675
                                    break;
676
677 13
                                case ')':
678
                                 // RIGHT PARENTHESIS (29h)
679 13
                                    --$open_bracket;
680 13
                                    break;
681
                            }
682 13
                            ++$strpos;
683
                        }
684 13
                        $command = substr($text_part, $offset, ($strpos - $offset - 1));
685 13
                        $offset = $strpos;
686
687 13
                        if (preg_match('/^\s*([A-Z\']{1,2})\s*/si', substr($text_part, $offset), $matches)) {
688 11
                            $operator = $matches[1];
689 11
                            $offset += \strlen($matches[0]);
690
                        }
691
                    }
692 13
                    break;
693
694
                default:
695 17
                    if ('ET' == substr($text_part, $offset, 2)) {
696 1
                        break;
697 17
                    } elseif (preg_match(
698 17
                        '/^\s*(?P<data>([0-9\.\-]+\s*?)+)\s+(?P<id>[A-Z]{1,3})\s*/si',
699 17
                        substr($text_part, $offset),
700
                        $matches
701
                    )
702
                    ) {
703 17
                        $operator = trim($matches['id']);
704 17
                        $command = trim($matches['data']);
705 17
                        $offset += \strlen($matches[0]);
706 15
                    } elseif (preg_match('/^\s*([0-9\.\-]+\s*?)+\s*/si', substr($text_part, $offset), $matches)) {
707 15
                        $type = 'n';
708 15
                        $command = trim($matches[0]);
709 15
                        $offset += \strlen($matches[0]);
710 9
                    } elseif (preg_match('/^\s*([A-Z\*]+)\s*/si', substr($text_part, $offset), $matches)) {
711 9
                        $type = '';
712 9
                        $operator = $matches[1];
713 9
                        $command = '';
714 9
                        $offset += \strlen($matches[0]);
715
                    }
716
            }
717
718 17
            if (false !== $command) {
719 17
                $commands[] = [
720 17
                    self::TYPE => $type,
721 17
                    self::OPERATOR => $operator,
722 17
                    self::COMMAND => $command,
723
                ];
724
            } else {
725 16
                break;
726
            }
727
        }
728
729 17
        return $commands;
730
    }
731
732
    /**
733
     * @param string $content
734
     *
735
     * @return PDFObject
736
     */
737 26
    public static function factory(
738
        Document $document,
739
        Header $header,
740
        $content,
741
        Config $config = null
742
    ) {
743 26
        switch ($header->get('Type')->getContent()) {
744 26
            case 'XObject':
745 4
                switch ($header->get('Subtype')->getContent()) {
746 4
                    case 'Image':
747 2
                        return new Image($document, $header, $content, $config);
748
749 3
                    case 'Form':
750 3
                        return new Form($document, $header, $content, $config);
751
                }
752
753
                return new self($document, $header, $content, $config);
754
755 26
            case 'Pages':
756 25
                return new Pages($document, $header, $content, $config);
757
758 26
            case 'Page':
759 25
                return new Page($document, $header, $content, $config);
760
761 26
            case 'Encoding':
762 5
                return new Encoding($document, $header, $content, $config);
763
764 26
            case 'Font':
765 25
                $subtype = $header->get('Subtype')->getContent();
766 25
                $classname = '\Smalot\PdfParser\Font\Font'.$subtype;
767
768 25
                if (class_exists($classname)) {
769 25
                    return new $classname($document, $header, $content, $config);
770
                }
771
772
                return new Font($document, $header, $content, $config);
773
774
            default:
775 26
                return new self($document, $header, $content, $config);
776
        }
777
    }
778
779
    /**
780
     * Returns unique id identifying the object.
781
     *
782
     * @return string
783
     */
784 10
    protected function getUniqueId()
785
    {
786 10
        return spl_object_hash($this);
787
    }
788
}
789