Completed
Push — pr/238 ( 9ef5ec )
by Konrad
03:54
created

PDFObject::getContent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

275
                        $y = array_pop(/** @scrutinizer ignore-type */ $args);
Loading history...
276 2
                        $x = array_pop($args);
277 2
                        if (((float) $x <= 0) ||
278 2
                            (false !== $current_position_td['y'] && (float) $y < (float) ($current_position_td['y']))
279
                        ) {
280
                            // vertical offset
281 1
                            $text .= "\n";
282 2
                        } elseif (false !== $current_position_td['x'] && (float) $x > (float) (
283 2
                                $current_position_td['x']
284
                            )
285
                        ) {
286
                            // horizontal offset
287 2
                            $text .= ' ';
288
                        }
289 2
                        $current_position_td = ['x' => $x, 'y' => $y];
290 2
                        break;
291
292
                    // move text current point and set leading
293 3
                    case 'TD':
294 1
                        $args = preg_split('/\s/s', $command[self::COMMAND]);
295 1
                        $y = array_pop($args);
296 1
                        $x = array_pop($args);
297 1
                        if ((float) $y < 0) {
298 1
                            $text .= "\n";
299
                        } elseif ((float) $x <= 0) {
300
                            $text .= ' ';
301
                        }
302 1
                        break;
303
304 3
                    case 'Tf':
305 3
                        list($id) = preg_split('/\s/s', $command[self::COMMAND]);
306 3
                        $id = trim($id, '/');
307 3
                        if (null !== $page) {
308 3
                            $current_font = $page->getFont($id);
309
                        }
310 3
                        break;
311
312 3
                    case "'":
313 3
                    case 'Tj':
314 2
                        $command[self::COMMAND] = [$command];
315
                        // no break
316 3
                    case 'TJ':
317
                        // Skip if not previously defined, should never happened.
318 3
                        if (null === $current_font) {
319
                            // Fallback
320
                            // TODO : Improve
321 1
                            $text .= $command[self::COMMAND][0][self::COMMAND];
322 1
                            break;
323
                        }
324
325 3
                        $sub_text = $current_font->decodeText($command[self::COMMAND]);
326 3
                        $text .= $sub_text;
327 3
                        break;
328
329
                    // set leading
330 3
                    case 'TL':
331 1
                        $text .= ' ';
332 1
                        break;
333
334 3
                    case 'Tm':
335 3
                        $args = preg_split('/\s/s', $command[self::COMMAND]);
336 3
                        $y = array_pop($args);
337 3
                        $x = array_pop($args);
338 3
                        if (false !== $current_position_tm['x']) {
339 3
                            $delta = abs((float) $x - (float) ($current_position_tm['x']));
340 3
                            if ($delta > 10) {
341 3
                                $text .= "\t";
342
                            }
343
                        }
344 3
                        if (false !== $current_position_tm['y']) {
345 3
                            $delta = abs((float) $y - (float) ($current_position_tm['y']));
346 3
                            if ($delta > 10) {
347 2
                                $text .= "\n";
348
                            }
349
                        }
350 3
                        $current_position_tm = ['x' => $x, 'y' => $y];
351 3
                        break;
352
353
                    // set super/subscripting text rise
354 3
                    case 'Ts':
355
                        break;
356
357
                    // set word spacing
358 3
                    case 'Tw':
359
                        break;
360
361
                    // set horizontal scaling
362 3
                    case 'Tz':
363
                        $text .= "\n";
364
                        break;
365
366
                    // move to start of next line
367 3
                    case 'T*':
368 2
                        $text .= "\n";
369 2
                        break;
370
371 2
                    case 'Da':
372
                        break;
373
374 2
                    case 'Do':
375
                        if (null !== $page) {
376
                            $args = preg_split('/\s/s', $command[self::COMMAND]);
377
                            $id = trim(array_pop($args), '/ ');
378
                            $xobject = $page->getXObject($id);
379
380
                            // @todo $xobject could be a ElementXRef object, which would then throw an error
381
                            if (\is_object($xobject) && $xobject instanceof self && !\in_array($xobject->getUniqueId(), self::$recursionStack)) {
382
                                // Not a circular reference.
383
                                $text .= $xobject->getText($page);
384
                            }
385
                        }
386
                        break;
387
388 2
                    case 'rg':
389 2
                    case 'RG':
390 1
                        break;
391
392 2
                    case 're':
393
                        break;
394
395 2
                    case 'co':
396
                        break;
397
398 2
                    case 'cs':
399 1
                        break;
400
401 2
                    case 'gs':
402 2
                        break;
403
404 2
                    case 'en':
405
                        break;
406
407 2
                    case 'sc':
408 2
                    case 'SC':
409
                        break;
410
411 2
                    case 'g':
412 2
                    case 'G':
413 2
                        break;
414
415 1
                    case 'V':
416
                        break;
417
418 1
                    case 'vo':
419 1
                    case 'Vo':
420
                        break;
421
422
                    default:
423
                }
424
            }
425
        }
426
427 3
        array_pop(self::$recursionStack);
428
429 3
        return $text.' ';
430
    }
431
432
    /**
433
     * @param Page $page
434
     *
435
     * @return array
436
     *
437
     * @throws \Exception
438
     */
439
    public function getTextArray(Page $page = null)
440
    {
441
        $text = [];
442
        $sections = $this->getSectionsText($this->content);
443
        $current_font = new Font($this->document);
444
445
        foreach ($sections as $section) {
446
            $commands = $this->getCommandsText($section);
447
448
            foreach ($commands as $command) {
449
                switch ($command[self::OPERATOR]) {
450
                    // set character spacing
451
                    case 'Tc':
452
                        break;
453
454
                    // move text current point
455
                    case 'Td':
456
                        break;
457
458
                    // move text current point and set leading
459
                    case 'TD':
460
                        break;
461
462
                    case 'Tf':
463
                        list($id) = preg_split('/\s/s', $command[self::COMMAND]);
464
                        $id = trim($id, '/');
465
                        $current_font = $page->getFont($id);
0 ignored issues
show
Bug introduced by
The method getFont() does not exist on null. ( Ignorable by Annotation )

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

465
                        /** @scrutinizer ignore-call */ 
466
                        $current_font = $page->getFont($id);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
466
                        break;
467
468
                    case "'":
469
                    case 'Tj':
470
                        $command[self::COMMAND] = [$command];
471
                        // no break
472
                    case 'TJ':
473
                        // Skip if not previously defined, should never happened.
474
                        if (null === $current_font) {
475
                            // Fallback
476
                            // TODO : Improve
477
                            $text[] = $command[self::COMMAND][0][self::COMMAND];
478
                            break;
479
                        }
480
481
                        $sub_text = $current_font->decodeText($command[self::COMMAND]);
482
                        $text[] = $sub_text;
483
                        break;
484
485
                    // set leading
486
                    case 'TL':
487
                        break;
488
489
                    case 'Tm':
490
                        break;
491
492
                    // set super/subscripting text rise
493
                    case 'Ts':
494
                        break;
495
496
                    // set word spacing
497
                    case 'Tw':
498
                        break;
499
500
                    // set horizontal scaling
501
                    case 'Tz':
502
                        //$text .= "\n";
503
                        break;
504
505
                    // move to start of next line
506
                    case 'T*':
507
                        //$text .= "\n";
508
                        break;
509
510
                    case 'Da':
511
                        break;
512
513
                    case 'Do':
514
                        if (null !== $page) {
515
                            $args = preg_split('/\s/s', $command[self::COMMAND]);
516
                            $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

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