Passed
Pull Request — master (#375)
by Konrad
02:55 queued 16s
created

PDFObject::getTextArray()   D

Complexity

Conditions 35
Paths 85

Size

Total Lines 118
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 55
CRAP Score 55.7453

Importance

Changes 0
Metric Value
cc 35
eloc 73
c 0
b 0
f 0
nc 85
nop 1
dl 0
loc 118
ccs 55
cts 74
cp 0.7432
crap 55.7453
rs 4.1666

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