Passed
Push — pr/config-object ( 88650b )
by Konrad
03:17
created

PDFObject::getCommandsText()   F

Complexity

Conditions 27
Paths 65

Size

Total Lines 149
Code Lines 105

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 97
CRAP Score 27.0196

Importance

Changes 0
Metric Value
cc 27
eloc 105
nc 65
nop 2
dl 0
loc 149
rs 3.3333
c 0
b 0
f 0
ccs 97
cts 100
cp 0.97
crap 27.0196

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
     * @param Header $header
71
     * @param string $content
72
     * @param Config $config
73
     */
74 38
    public function __construct(
75
        Document $document,
76
        Header $header = null,
77
        $content = null,
78
        ?Config $config = null
79
    ) {
80 38
        $this->document = $document;
81 38
        $this->header = null !== $header ? $header : new Header();
82 38
        $this->content = $content;
83 38
        $this->config = $config;
0 ignored issues
show
Bug Best Practice introduced by
The property config does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
84 38
    }
85
86 33
    public function init()
87
    {
88 33
    }
89
90
    /**
91
     * @return Header|null
92
     */
93 33
    public function getHeader()
94
    {
95 33
        return $this->header;
96
    }
97
98
    /**
99
     * @param string $name
100
     *
101
     * @return Element|PDFObject
102
     */
103 28
    public function get($name)
104
    {
105 28
        return $this->header->get($name);
106
    }
107
108
    /**
109
     * @param string $name
110
     *
111
     * @return bool
112
     */
113 27
    public function has($name)
114
    {
115 27
        return $this->header->has($name);
116
    }
117
118
    /**
119
     * @param bool $deep
120
     *
121
     * @return array
122
     */
123 1
    public function getDetails($deep = true)
124
    {
125 1
        return $this->header->getDetails($deep);
126
    }
127
128
    /**
129
     * @return string|null
130
     */
131 24
    public function getContent()
132
    {
133 24
        return $this->content;
134
    }
135
136
    /**
137
     * @param string $content
138
     */
139 18
    public function cleanContent($content, $char = 'X')
140
    {
141 18
        $char = $char[0];
142 18
        $content = str_replace(['\\\\', '\\)', '\\('], $char.$char, $content);
143
144
        // Remove image bloc with binary content
145 18
        preg_match_all('/\s(BI\s.*?(\sID\s).*?(\sEI))\s/s', $content, $matches, PREG_OFFSET_CAPTURE);
146 18
        foreach ($matches[0] as $part) {
147
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
148
        }
149
150
        // Clean content in square brackets [.....]
151 18
        preg_match_all('/\[((\(.*?\)|[0-9\.\-\s]*)*)\]/s', $content, $matches, PREG_OFFSET_CAPTURE);
152 18
        foreach ($matches[1] as $part) {
153 14
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
154
        }
155
156
        // Clean content in round brackets (.....)
157 18
        preg_match_all('/\((.*?)\)/s', $content, $matches, PREG_OFFSET_CAPTURE);
158 18
        foreach ($matches[1] as $part) {
159 13
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
160
        }
161
162
        // Clean structure
163 18
        if ($parts = preg_split('/(<|>)/s', $content, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE)) {
164 18
            $content = '';
165 18
            $level = 0;
166 18
            foreach ($parts as $part) {
167 18
                if ('<' == $part) {
168 11
                    ++$level;
169
                }
170
171 18
                $content .= (0 == $level ? $part : str_repeat($char, \strlen($part)));
172
173 18
                if ('>' == $part) {
174 11
                    --$level;
175
                }
176
            }
177
        }
178
179
        // Clean BDC and EMC markup
180 18
        preg_match_all(
181 18
            '/(\/[A-Za-z0-9\_]*\s*'.preg_quote($char).'*BDC)/s',
182
            $content,
183
            $matches,
184 18
            PREG_OFFSET_CAPTURE
185
        );
186 18
        foreach ($matches[1] as $part) {
187 4
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
188
        }
189
190 18
        preg_match_all('/\s(EMC)\s/s', $content, $matches, PREG_OFFSET_CAPTURE);
191 18
        foreach ($matches[1] as $part) {
192 7
            $content = substr_replace($content, str_repeat($char, \strlen($part[0])), $part[1], \strlen($part[0]));
193
        }
194
195 18
        return $content;
196
    }
197
198
    /**
199
     * @param string $content
200
     *
201
     * @return array
202
     */
203 17
    public function getSectionsText($content)
204
    {
205 17
        $sections = [];
206 17
        $content = ' '.$content.' ';
207 17
        $textCleaned = $this->cleanContent($content, '_');
208
209
        // Extract text blocks.
210 17
        if (preg_match_all('/\s+BT[\s|\(|\[]+(.*?)\s*ET/s', $textCleaned, $matches, PREG_OFFSET_CAPTURE)) {
211 17
            foreach ($matches[1] as $part) {
212 17
                $text = $part[0];
213 17
                if ('' === $text) {
214
                    continue;
215
                }
216 17
                $offset = $part[1];
217 17
                $section = substr($content, $offset, \strlen($text));
218
219
                // Removes BDC and EMC markup.
220 17
                $section = preg_replace('/(\/[A-Za-z0-9]+\s*<<.*?)(>>\s*BDC)(.*?)(EMC\s+)/s', '${3}', $section.' ');
221
222 17
                $sections[] = $section;
223
            }
224
        }
225
226
        // Extract 'do' commands.
227 17
        if (preg_match_all('/(\/[A-Za-z0-9\.\-_]+\s+Do)\s/s', $textCleaned, $matches, PREG_OFFSET_CAPTURE)) {
228 3
            foreach ($matches[1] as $part) {
229 3
                $text = $part[0];
230 3
                $offset = $part[1];
231 3
                $section = substr($content, $offset, \strlen($text));
232
233 3
                $sections[] = $section;
234
            }
235
        }
236
237 17
        return $sections;
238
    }
239
240 10
    private function getDefaultFont(Page $page = null)
241
    {
242 10
        $fonts = [];
243 10
        if (null !== $page) {
244 10
            $fonts = $page->getFonts();
245
        }
246
247 10
        $fonts = array_merge($fonts, array_values($this->document->getFonts()));
248
249 10
        if (\count($fonts) > 0) {
250 10
            return reset($fonts);
251
        }
252
253
        return new Font($this->document);
254
    }
255
256
    /**
257
     * @param Page $page
258
     *
259
     * @return string
260
     *
261
     * @throws \Exception
262
     */
263 10
    public function getText(Page $page = null)
264
    {
265 10
        $text = '';
266 10
        $sections = $this->getSectionsText($this->content);
267 10
        $current_font = $this->getDefaultFont($page);
268
269 10
        $current_position_td = ['x' => false, 'y' => false];
270 10
        $current_position_tm = ['x' => false, 'y' => false];
271
272 10
        self::$recursionStack[] = $this->getUniqueId();
273
274 10
        foreach ($sections as $section) {
275 10
            $commands = $this->getCommandsText($section);
276
277 10
            foreach ($commands as $command) {
278 10
                switch ($command[self::OPERATOR]) {
279
                    // set character spacing
280 10
                    case 'Tc':
281 2
                        break;
282
283
                    // move text current point
284 10
                    case 'Td':
285 8
                        $args = preg_split('/\s/s', $command[self::COMMAND]);
286 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

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

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