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

PDFObject::getText()   F

Complexity

Conditions 49
Paths 114

Size

Total Lines 178
Code Lines 112

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 100
CRAP Score 52.6516

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 49
eloc 112
c 1
b 1
f 0
nc 114
nop 1
dl 0
loc 178
ccs 100
cts 113
cp 0.885
crap 52.6516
rs 3.24

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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