Completed
Pull Request — master (#349)
by
unknown
05:52
created

PDFObject::cleanContent()   B

Complexity

Conditions 11
Paths 64

Size

Total Lines 57
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 29
CRAP Score 11.0044

Importance

Changes 0
Metric Value
cc 11
eloc 31
c 0
b 0
f 0
nc 64
nop 2
dl 0
loc 57
ccs 29
cts 30
cp 0.9667
crap 11.0044
rs 7.3166

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

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

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