Completed
Branch master (8e512a)
by
unknown
17:03 queued 09:20
created
vendor/phenx/php-svg-lib/src/Svg/Surface/CPdf.php 2 patches
Indentation   +6361 added lines, -6361 removed lines patch added patch discarded remove patch
@@ -20,1276 +20,1276 @@  discard block
 block discarded – undo
20 20
 
21 21
 class CPdf
22 22
 {
23
-    const PDF_VERSION = '1.7';
24
-
25
-    const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
26
-    const ACROFORM_SIG_APPENDONLY =       0x0002;
27
-
28
-    const ACROFORM_FIELD_BUTTON =   'Btn';
29
-    const ACROFORM_FIELD_TEXT =     'Tx';
30
-    const ACROFORM_FIELD_CHOICE =   'Ch';
31
-    const ACROFORM_FIELD_SIG =      'Sig';
32
-
33
-    const ACROFORM_FIELD_READONLY =               0x0001;
34
-    const ACROFORM_FIELD_REQUIRED =               0x0002;
35
-
36
-    const ACROFORM_FIELD_TEXT_MULTILINE =         0x1000;
37
-    const ACROFORM_FIELD_TEXT_PASSWORD =          0x2000;
38
-    const ACROFORM_FIELD_TEXT_RICHTEXT =         0x10000;
39
-
40
-    const ACROFORM_FIELD_CHOICE_COMBO =          0x20000;
41
-    const ACROFORM_FIELD_CHOICE_EDIT =           0x40000;
42
-    const ACROFORM_FIELD_CHOICE_SORT =           0x80000;
43
-    const ACROFORM_FIELD_CHOICE_MULTISELECT =   0x200000;
44
-
45
-    const XOBJECT_SUBTYPE_FORM = 'Form';
46
-
47
-    /**
48
-     * @var integer The current number of pdf objects in the document
49
-     */
50
-    public $numObj = 0;
51
-
52
-    /**
53
-     * @var array This array contains all of the pdf objects, ready for final assembly
54
-     */
55
-    public $objects = [];
56
-
57
-    /**
58
-     * @var integer The objectId (number within the objects array) of the document catalog
59
-     */
60
-    public $catalogId;
61
-
62
-    /**
63
-     * @var integer The objectId (number within the objects array) of indirect references (Javascript EmbeddedFiles)
64
-     */
65
-    protected $indirectReferenceId = 0;
66
-
67
-    /**
68
-     * @var integer The objectId (number within the objects array)
69
-     */
70
-    protected $embeddedFilesId = 0;
71
-
72
-    /**
73
-     * AcroForm objectId
74
-     *
75
-     * @var integer
76
-     */
77
-    public $acroFormId;
78
-
79
-    /**
80
-     * @var int
81
-     */
82
-    public $signatureMaxLen = 5000;
83
-
84
-    /**
85
-     * @var array Array carrying information about the fonts that the system currently knows about
86
-     * Used to ensure that a font is not loaded twice, among other things
87
-     */
88
-    public $fonts = [];
89
-
90
-    /**
91
-     * @var string The default font metrics file to use if no other font has been loaded.
92
-     * The path to the directory containing the font metrics should be included
93
-     */
94
-    public $defaultFont = './fonts/Helvetica.afm';
95
-
96
-    /**
97
-     * @string A record of the current font
98
-     */
99
-    public $currentFont = '';
100
-
101
-    /**
102
-     * @var string The current base font
103
-     */
104
-    public $currentBaseFont = '';
105
-
106
-    /**
107
-     * @var integer The number of the current font within the font array
108
-     */
109
-    public $currentFontNum = 0;
110
-
111
-    /**
112
-     * @var integer
113
-     */
114
-    public $currentNode;
115
-
116
-    /**
117
-     * @var integer Object number of the current page
118
-     */
119
-    public $currentPage;
120
-
121
-    /**
122
-     * @var integer Object number of the currently active contents block
123
-     */
124
-    public $currentContents;
125
-
126
-    /**
127
-     * @var integer Number of fonts within the system
128
-     */
129
-    public $numFonts = 0;
130
-
131
-    /**
132
-     * @var integer Number of graphic state resources used
133
-     */
134
-    private $numStates = 0;
135
-
136
-    /**
137
-     * @var array Number of graphic state resources used
138
-     */
139
-    private $gstates = [];
140
-
141
-    /**
142
-     * @var array Current color for fill operations, defaults to inactive value,
143
-     * all three components should be between 0 and 1 inclusive when active
144
-     */
145
-    public $currentColor = null;
146
-
147
-    /**
148
-     * @var array Current color for stroke operations (lines etc.)
149
-     */
150
-    public $currentStrokeColor = null;
151
-
152
-    /**
153
-     * @var string Fill rule (nonzero or evenodd)
154
-     */
155
-    public $fillRule = "nonzero";
156
-
157
-    /**
158
-     * @var string Current style that lines are drawn in
159
-     */
160
-    public $currentLineStyle = '';
161
-
162
-    /**
163
-     * @var array Current line transparency (partial graphics state)
164
-     */
165
-    public $currentLineTransparency = ["mode" => "Normal", "opacity" => 1.0];
166
-
167
-    /**
168
-     * array Current fill transparency (partial graphics state)
169
-     */
170
-    public $currentFillTransparency = ["mode" => "Normal", "opacity" => 1.0];
171
-
172
-    /**
173
-     * @var array An array which is used to save the state of the document, mainly the colors and styles
174
-     * it is used to temporarily change to another state, then change back to what it was before
175
-     */
176
-    public $stateStack = [];
177
-
178
-    /**
179
-     * @var integer Number of elements within the state stack
180
-     */
181
-    public $nStateStack = 0;
182
-
183
-    /**
184
-     * @var integer Number of page objects within the document
185
-     */
186
-    public $numPages = 0;
187
-
188
-    /**
189
-     * @var array Object Id storage stack
190
-     */
191
-    public $stack = [];
192
-
193
-    /**
194
-     * @var integer Number of elements within the object Id storage stack
195
-     */
196
-    public $nStack = 0;
197
-
198
-    /**
199
-     * an array which contains information about the objects which are not firmly attached to pages
200
-     * these have been added with the addObject function
201
-     */
202
-    public $looseObjects = [];
203
-
204
-    /**
205
-     * array contains information about how the loose objects are to be added to the document
206
-     */
207
-    public $addLooseObjects = [];
208
-
209
-    /**
210
-     * @var integer The objectId of the information object for the document
211
-     * this contains authorship, title etc.
212
-     */
213
-    public $infoObject = 0;
214
-
215
-    /**
216
-     * @var integer Number of images being tracked within the document
217
-     */
218
-    public $numImages = 0;
219
-
220
-    /**
221
-     * @var array An array containing options about the document
222
-     * it defaults to turning on the compression of the objects
223
-     */
224
-    public $options = ['compression' => true];
225
-
226
-    /**
227
-     * @var integer The objectId of the first page of the document
228
-     */
229
-    public $firstPageId;
230
-
231
-    /**
232
-     * @var integer The object Id of the procset object
233
-     */
234
-    public $procsetObjectId;
235
-
236
-    /**
237
-     * @var array Store the information about the relationship between font families
238
-     * this used so that the code knows which font is the bold version of another font, etc.
239
-     * the value of this array is initialised in the constructor function.
240
-     */
241
-    public $fontFamilies = [];
242
-
243
-    /**
244
-     * @var string Folder for php serialized formats of font metrics files.
245
-     * If empty string, use same folder as original metrics files.
246
-     * This can be passed in from class creator.
247
-     * If this folder does not exist or is not writable, Cpdf will be **much** slower.
248
-     * Because of potential trouble with php safe mode, folder cannot be created at runtime.
249
-     */
250
-    public $fontcache = '';
251
-
252
-    /**
253
-     * @var integer The version of the font metrics cache file.
254
-     * This value must be manually incremented whenever the internal font data structure is modified.
255
-     */
256
-    public $fontcacheVersion = 6;
257
-
258
-    /**
259
-     * @var string Temporary folder.
260
-     * If empty string, will attempt system tmp folder.
261
-     * This can be passed in from class creator.
262
-     */
263
-    public $tmp = '';
264
-
265
-    /**
266
-     * @var string Track if the current font is bolded or italicised
267
-     */
268
-    public $currentTextState = '';
269
-
270
-    /**
271
-     * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
272
-     */
273
-    public $messages = '';
274
-
275
-    /**
276
-     * @var string The encryption array for the document encryption is stored here
277
-     */
278
-    public $arc4 = '';
279
-
280
-    /**
281
-     * @var integer The object Id of the encryption information
282
-     */
283
-    public $arc4_objnum = 0;
284
-
285
-    /**
286
-     * @var string The file identifier, used to uniquely identify a pdf document
287
-     */
288
-    public $fileIdentifier = '';
289
-
290
-    /**
291
-     * @var boolean A flag to say if a document is to be encrypted or not
292
-     */
293
-    public $encrypted = false;
294
-
295
-    /**
296
-     * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
297
-     */
298
-    public $encryptionKey = '';
299
-
300
-    /**
301
-     * @var array Array which forms a stack to keep track of nested callback functions
302
-     */
303
-    public $callback = [];
304
-
305
-    /**
306
-     * @var integer The number of callback functions in the callback array
307
-     */
308
-    public $nCallback = 0;
309
-
310
-    /**
311
-     * @var array Store label->id pairs for named destinations, these will be used to replace internal links
312
-     * done this way so that destinations can be defined after the location that links to them
313
-     */
314
-    public $destinations = [];
315
-
316
-    /**
317
-     * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
318
-     * publiciables within the class, so that the user can rollback at will (from each 'start' command)
319
-     * note that this includes the objects array, so these can be large.
320
-     */
321
-    public $checkpoint = '';
322
-
323
-    /**
324
-     * @var array Table of Image origin filenames and image labels which were already added with o_image().
325
-     * Allows to merge identical images
326
-     */
327
-    public $imagelist = [];
328
-
329
-    /**
330
-     * @var array Table of already added alpha and plain image files for transparent PNG images.
331
-     */
332
-    protected $imageAlphaList = [];
333
-
334
-    /**
335
-     * @var array List of temporary image files to be deleted after processing.
336
-     */
337
-    protected $imageCache = [];
338
-
339
-    /**
340
-     * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
341
-     */
342
-    public $isUnicode = false;
343
-
344
-    /**
345
-     * @var string the JavaScript code of the document
346
-     */
347
-    public $javascript = '';
348
-
349
-    /**
350
-     * @var boolean whether the compression is possible
351
-     */
352
-    protected $compressionReady = false;
353
-
354
-    /**
355
-     * @var array Current page size
356
-     */
357
-    protected $currentPageSize = ["width" => 0, "height" => 0];
358
-
359
-    /**
360
-     * @var array All the chars that will be required in the font subsets
361
-     */
362
-    protected $stringSubsets = [];
363
-
364
-    /**
365
-     * @var string The target internal encoding
366
-     */
367
-    protected static $targetEncoding = 'Windows-1252';
368
-
369
-    /**
370
-     * @var array
371
-     */
372
-    protected $byteRange = array();
373
-
374
-    /**
375
-     * @var array The list of the core fonts
376
-     */
377
-    protected static $coreFonts = [
378
-        'courier',
379
-        'courier-bold',
380
-        'courier-oblique',
381
-        'courier-boldoblique',
382
-        'helvetica',
383
-        'helvetica-bold',
384
-        'helvetica-oblique',
385
-        'helvetica-boldoblique',
386
-        'times-roman',
387
-        'times-bold',
388
-        'times-italic',
389
-        'times-bolditalic',
390
-        'symbol',
391
-        'zapfdingbats'
392
-    ];
393
-
394
-    /**
395
-     * Class constructor
396
-     * This will start a new document
397
-     *
398
-     * @param array   $pageSize  Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
399
-     * @param boolean $isUnicode Whether text will be treated as Unicode or not.
400
-     * @param string  $fontcache The font cache folder
401
-     * @param string  $tmp       The temporary folder
402
-     */
403
-    function __construct($pageSize = [0, 0, 612, 792], $isUnicode = false, $fontcache = '', $tmp = '')
404
-    {
405
-        $this->isUnicode = $isUnicode;
406
-        $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
407
-        $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
408
-        $this->newDocument($pageSize);
409
-
410
-        $this->compressionReady = function_exists('gzcompress');
411
-
412
-        if (in_array('Windows-1252', mb_list_encodings())) {
413
-            self::$targetEncoding = 'Windows-1252';
414
-        }
415
-
416
-        // also initialize the font families that are known about already
417
-        $this->setFontFamily('init');
418
-    }
419
-
420
-    public function __destruct()
421
-    {
422
-        foreach ($this->imageCache as $file) {
423
-            if (file_exists($file)) {
424
-                unlink($file);
425
-            }
426
-        }
427
-    }
428
-
429
-    /**
430
-     * Document object methods (internal use only)
431
-     *
432
-     * There is about one object method for each type of object in the pdf document
433
-     * Each function has the same call list ($id,$action,$options).
434
-     * $id = the object ID of the object, or what it is to be if it is being created
435
-     * $action = a string specifying the action to be performed, though ALL must support:
436
-     *           'new' - create the object with the id $id
437
-     *           'out' - produce the output for the pdf object
438
-     * $options = optional, a string or array containing the various parameters for the object
439
-     *
440
-     * These, in conjunction with the output function are the ONLY way for output to be produced
441
-     * within the pdf 'file'.
442
-     */
443
-
444
-    /**
445
-     * Destination object, used to specify the location for the user to jump to, presently on opening
446
-     *
447
-     * @param $id
448
-     * @param $action
449
-     * @param string $options
450
-     * @return string|null
451
-     */
452
-    protected function o_destination($id, $action, $options = '')
453
-    {
454
-        switch ($action) {
455
-            case 'new':
456
-                $this->objects[$id] = ['t' => 'destination', 'info' => []];
457
-                $tmp = '';
458
-                switch ($options['type']) {
459
-                    case 'XYZ':
460
-                    /** @noinspection PhpMissingBreakStatementInspection */
461
-                    case 'FitR':
462
-                        $tmp = ' ' . $options['p3'] . $tmp;
463
-                    case 'FitH':
464
-                    case 'FitV':
465
-                    case 'FitBH':
466
-                    /** @noinspection PhpMissingBreakStatementInspection */
467
-                    case 'FitBV':
468
-                        $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
469
-                    case 'Fit':
470
-                    case 'FitB':
471
-                        $tmp = $options['type'] . $tmp;
472
-                        $this->objects[$id]['info']['string'] = $tmp;
473
-                        $this->objects[$id]['info']['page'] = $options['page'];
474
-                }
475
-                break;
476
-
477
-            case 'out':
478
-                $o = &$this->objects[$id];
479
-
480
-                $tmp = $o['info'];
481
-                $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
482
-
483
-                return $res;
484
-        }
485
-
486
-        return null;
487
-    }
488
-
489
-    /**
490
-     * set the viewer preferences
491
-     *
492
-     * @param $id
493
-     * @param $action
494
-     * @param string|array $options
495
-     * @return string|null
496
-     */
497
-    protected function o_viewerPreferences($id, $action, $options = '')
498
-    {
499
-        switch ($action) {
500
-            case 'new':
501
-                $this->objects[$id] = ['t' => 'viewerPreferences', 'info' => []];
502
-                break;
503
-
504
-            case 'add':
505
-                $o = &$this->objects[$id];
506
-
507
-                foreach ($options as $k => $v) {
508
-                    switch ($k) {
509
-                        // Boolean keys
510
-                        case 'HideToolbar':
511
-                        case 'HideMenubar':
512
-                        case 'HideWindowUI':
513
-                        case 'FitWindow':
514
-                        case 'CenterWindow':
515
-                        case 'DisplayDocTitle':
516
-                        case 'PickTrayByPDFSize':
517
-                            $o['info'][$k] = (bool)$v;
518
-                            break;
519
-
520
-                        // Integer keys
521
-                        case 'NumCopies':
522
-                            $o['info'][$k] = (int)$v;
523
-                            break;
524
-
525
-                        // Name keys
526
-                        case 'ViewArea':
527
-                        case 'ViewClip':
528
-                        case 'PrintClip':
529
-                        case 'PrintArea':
530
-                            $o['info'][$k] = (string)$v;
531
-                            break;
532
-
533
-                        // Named with limited valid values
534
-                        case 'NonFullScreenPageMode':
535
-                            if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
536
-                                break;
537
-                            }
538
-                            $o['info'][$k] = $v;
539
-                            break;
540
-
541
-                        case 'Direction':
542
-                            if (!in_array($v, ['L2R', 'R2L'])) {
543
-                                break;
544
-                            }
545
-                            $o['info'][$k] = $v;
546
-                            break;
547
-
548
-                        case 'PrintScaling':
549
-                            if (!in_array($v, ['None', 'AppDefault'])) {
550
-                                break;
551
-                            }
552
-                            $o['info'][$k] = $v;
553
-                            break;
554
-
555
-                        case 'Duplex':
556
-                            if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
557
-                                break;
558
-                            }
559
-                            $o['info'][$k] = $v;
560
-                            break;
561
-
562
-                        // Integer array
563
-                        case 'PrintPageRange':
564
-                            // Cast to integer array
565
-                            foreach ($v as $vK => $vV) {
566
-                                $v[$vK] = (int)$vV;
567
-                            }
568
-                            $o['info'][$k] = array_values($v);
569
-                            break;
570
-                    }
571
-                }
572
-                break;
573
-
574
-            case 'out':
575
-                $o = &$this->objects[$id];
576
-                $res = "\n$id 0 obj\n<< ";
577
-
578
-                foreach ($o['info'] as $k => $v) {
579
-                    if (is_string($v)) {
580
-                        $v = '/' . $v;
581
-                    } elseif (is_int($v)) {
582
-                        $v = (string) $v;
583
-                    } elseif (is_bool($v)) {
584
-                        $v = ($v ? 'true' : 'false');
585
-                    } elseif (is_array($v)) {
586
-                        $v = '[' . implode(' ', $v) . ']';
587
-                    }
588
-                    $res .= "\n/$k $v";
589
-                }
590
-                $res .= "\n>>\nendobj";
591
-
592
-                return $res;
593
-        }
594
-
595
-        return null;
596
-    }
597
-
598
-    /**
599
-     * define the document catalog, the overall controller for the document
600
-     *
601
-     * @param $id
602
-     * @param $action
603
-     * @param string|array $options
604
-     * @return string|null
605
-     */
606
-    protected function o_catalog($id, $action, $options = '')
607
-    {
608
-        if ($action !== 'new') {
609
-            $o = &$this->objects[$id];
610
-        }
611
-
612
-        switch ($action) {
613
-            case 'new':
614
-                $this->objects[$id] = ['t' => 'catalog', 'info' => []];
615
-                $this->catalogId = $id;
616
-                break;
617
-
618
-            case 'acroform':
619
-            case 'outlines':
620
-            case 'pages':
621
-            case 'openHere':
622
-            case 'names':
623
-                $o['info'][$action] = $options;
624
-                break;
625
-
626
-            case 'viewerPreferences':
627
-                if (!isset($o['info']['viewerPreferences'])) {
628
-                    $this->numObj++;
629
-                    $this->o_viewerPreferences($this->numObj, 'new');
630
-                    $o['info']['viewerPreferences'] = $this->numObj;
631
-                }
632
-
633
-                $vp = $o['info']['viewerPreferences'];
634
-                $this->o_viewerPreferences($vp, 'add', $options);
635
-
636
-                break;
637
-
638
-            case 'out':
639
-                $res = "\n$id 0 obj\n<< /Type /Catalog";
640
-
641
-                foreach ($o['info'] as $k => $v) {
642
-                    switch ($k) {
643
-                        case 'outlines':
644
-                            $res .= "\n/Outlines $v 0 R";
645
-                            break;
646
-
647
-                        case 'pages':
648
-                            $res .= "\n/Pages $v 0 R";
649
-                            break;
650
-
651
-                        case 'viewerPreferences':
652
-                            $res .= "\n/ViewerPreferences $v 0 R";
653
-                            break;
654
-
655
-                        case 'openHere':
656
-                            $res .= "\n/OpenAction $v 0 R";
657
-                            break;
658
-
659
-                        case 'names':
660
-                            $res .= "\n/Names $v 0 R";
661
-                            break;
662
-
663
-                        case 'acroform':
664
-                            $res .= "\n/AcroForm $v 0 R";
665
-                            break;
666
-                    }
667
-                }
668
-
669
-                $res .= " >>\nendobj";
670
-
671
-                return $res;
672
-        }
673
-
674
-        return null;
675
-    }
676
-
677
-    /**
678
-     * object which is a parent to the pages in the document
679
-     *
680
-     * @param $id
681
-     * @param $action
682
-     * @param string $options
683
-     * @return string|null
684
-     */
685
-    protected function o_pages($id, $action, $options = '')
686
-    {
687
-        if ($action !== 'new') {
688
-            $o = &$this->objects[$id];
689
-        }
690
-
691
-        switch ($action) {
692
-            case 'new':
693
-                $this->objects[$id] = ['t' => 'pages', 'info' => []];
694
-                $this->o_catalog($this->catalogId, 'pages', $id);
695
-                break;
696
-
697
-            case 'page':
698
-                if (!is_array($options)) {
699
-                    // then it will just be the id of the new page
700
-                    $o['info']['pages'][] = $options;
701
-                } else {
702
-                    // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
703
-                    // and pos is either 'before' or 'after', saying where this page will fit.
704
-                    if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
705
-                        $i = array_search($options['rid'], $o['info']['pages']);
706
-                        if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
707
-
708
-                            // then there is a match
709
-                            // make a space
710
-                            switch ($options['pos']) {
711
-                                case 'before':
712
-                                    $k = $i;
713
-                                    break;
714
-
715
-                                case 'after':
716
-                                    $k = $i + 1;
717
-                                    break;
718
-
719
-                                default:
720
-                                    $k = -1;
721
-                                    break;
722
-                            }
723
-
724
-                            if ($k >= 0) {
725
-                                for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
726
-                                    $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
727
-                                }
728
-
729
-                                $o['info']['pages'][$k] = $options['id'];
730
-                            }
731
-                        }
732
-                    }
733
-                }
734
-                break;
735
-
736
-            case 'procset':
737
-                $o['info']['procset'] = $options;
738
-                break;
739
-
740
-            case 'mediaBox':
741
-                $o['info']['mediaBox'] = $options;
742
-                // which should be an array of 4 numbers
743
-                $this->currentPageSize = ['width' => $options[2], 'height' => $options[3]];
744
-                break;
745
-
746
-            case 'font':
747
-                $o['info']['fonts'][] = ['objNum' => $options['objNum'], 'fontNum' => $options['fontNum']];
748
-                break;
749
-
750
-            case 'extGState':
751
-                $o['info']['extGStates'][] = ['objNum' => $options['objNum'], 'stateNum' => $options['stateNum']];
752
-                break;
753
-
754
-            case 'xObject':
755
-                $o['info']['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
756
-                break;
757
-
758
-            case 'out':
759
-                if (count($o['info']['pages'])) {
760
-                    $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
761
-                    foreach ($o['info']['pages'] as $v) {
762
-                        $res .= "$v 0 R\n";
763
-                    }
764
-
765
-                    $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
766
-
767
-                    if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
768
-                        isset($o['info']['procset']) ||
769
-                        (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
770
-                    ) {
771
-                        $res .= "\n/Resources <<";
772
-
773
-                        if (isset($o['info']['procset'])) {
774
-                            $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
775
-                        }
776
-
777
-                        if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
778
-                            $res .= "\n/Font << ";
779
-                            foreach ($o['info']['fonts'] as $finfo) {
780
-                                $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
781
-                            }
782
-                            $res .= "\n>>";
783
-                        }
784
-
785
-                        if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
786
-                            $res .= "\n/XObject << ";
787
-                            foreach ($o['info']['xObjects'] as $finfo) {
788
-                                $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
789
-                            }
790
-                            $res .= "\n>>";
791
-                        }
792
-
793
-                        if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
794
-                            $res .= "\n/ExtGState << ";
795
-                            foreach ($o['info']['extGStates'] as $gstate) {
796
-                                $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
797
-                            }
798
-                            $res .= "\n>>";
799
-                        }
800
-
801
-                        $res .= "\n>>";
802
-                        if (isset($o['info']['mediaBox'])) {
803
-                            $tmp = $o['info']['mediaBox'];
804
-                            $res .= "\n/MediaBox [" . sprintf(
805
-                                    '%.3F %.3F %.3F %.3F',
806
-                                    $tmp[0],
807
-                                    $tmp[1],
808
-                                    $tmp[2],
809
-                                    $tmp[3]
810
-                                ) . ']';
811
-                        }
812
-                    }
813
-
814
-                    $res .= "\n >>\nendobj";
815
-                } else {
816
-                    $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
817
-                }
818
-
819
-                return $res;
820
-        }
821
-
822
-        return null;
823
-    }
824
-
825
-    /**
826
-     * define the outlines in the doc, empty for now
827
-     *
828
-     * @param $id
829
-     * @param $action
830
-     * @param string $options
831
-     * @return string|null
832
-     */
833
-    protected function o_outlines($id, $action, $options = '')
834
-    {
835
-        if ($action !== 'new') {
836
-            $o = &$this->objects[$id];
837
-        }
838
-
839
-        switch ($action) {
840
-            case 'new':
841
-                $this->objects[$id] = ['t' => 'outlines', 'info' => ['outlines' => []]];
842
-                $this->o_catalog($this->catalogId, 'outlines', $id);
843
-                break;
844
-
845
-            case 'outline':
846
-                $o['info']['outlines'][] = $options;
847
-                break;
848
-
849
-            case 'out':
850
-                if (count($o['info']['outlines'])) {
851
-                    $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
852
-                    foreach ($o['info']['outlines'] as $v) {
853
-                        $res .= "$v 0 R ";
854
-                    }
855
-
856
-                    $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
857
-                } else {
858
-                    $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
859
-                }
860
-
861
-                return $res;
862
-        }
863
-
864
-        return null;
865
-    }
866
-
867
-    /**
868
-     * an object to hold the font description
869
-     *
870
-     * @param $id
871
-     * @param $action
872
-     * @param string|array $options
873
-     * @return string|null
874
-     * @throws FontNotFoundException
875
-     */
876
-    protected function o_font($id, $action, $options = '')
877
-    {
878
-        if ($action !== 'new') {
879
-            $o = &$this->objects[$id];
880
-        }
881
-
882
-        switch ($action) {
883
-            case 'new':
884
-                $this->objects[$id] = [
885
-                    't'    => 'font',
886
-                    'info' => [
887
-                        'name'         => $options['name'],
888
-                        'fontFileName' => $options['fontFileName'],
889
-                        'SubType'      => 'Type1',
890
-                        'isSubsetting'   => $options['isSubsetting']
891
-                    ]
892
-                ];
893
-                $fontNum = $this->numFonts;
894
-                $this->objects[$id]['info']['fontNum'] = $fontNum;
895
-
896
-                // deal with the encoding and the differences
897
-                if (isset($options['differences'])) {
898
-                    // then we'll need an encoding dictionary
899
-                    $this->numObj++;
900
-                    $this->o_fontEncoding($this->numObj, 'new', $options);
901
-                    $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
902
-                } else {
903
-                    if (isset($options['encoding'])) {
904
-                        // we can specify encoding here
905
-                        switch ($options['encoding']) {
906
-                            case 'WinAnsiEncoding':
907
-                            case 'MacRomanEncoding':
908
-                            case 'MacExpertEncoding':
909
-                                $this->objects[$id]['info']['encoding'] = $options['encoding'];
910
-                                break;
911
-
912
-                            case 'none':
913
-                                break;
914
-
915
-                            default:
916
-                                $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
917
-                                break;
918
-                        }
919
-                    } else {
920
-                        $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
921
-                    }
922
-                }
923
-
924
-                if ($this->fonts[$options['fontFileName']]['isUnicode']) {
925
-                    // For Unicode fonts, we need to incorporate font data into
926
-                    // sub-sections that are linked from the primary font section.
927
-                    // Look at o_fontGIDtoCID and o_fontDescendentCID functions
928
-                    // for more information.
929
-                    //
930
-                    // All of this code is adapted from the excellent changes made to
931
-                    // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
932
-
933
-                    $toUnicodeId = ++$this->numObj;
934
-                    $this->o_toUnicode($toUnicodeId, 'new');
935
-                    $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
936
-
937
-                    $cidFontId = ++$this->numObj;
938
-                    $this->o_fontDescendentCID($cidFontId, 'new', $options);
939
-                    $this->objects[$id]['info']['cidFont'] = $cidFontId;
940
-                }
941
-
942
-                // also tell the pages node about the new font
943
-                $this->o_pages($this->currentNode, 'font', ['fontNum' => $fontNum, 'objNum' => $id]);
944
-                break;
945
-
946
-            case 'add':
947
-                $font_options = $this->processFont($id, $o['info']);
948
-
949
-                if ($font_options !== false) {
950
-                    foreach ($font_options as $k => $v) {
951
-                        switch ($k) {
952
-                            case 'BaseFont':
953
-                                $o['info']['name'] = $v;
954
-                                break;
955
-                            case 'FirstChar':
956
-                            case 'LastChar':
957
-                            case 'Widths':
958
-                            case 'FontDescriptor':
959
-                            case 'SubType':
960
-                                $this->addMessage('o_font ' . $k . " : " . $v);
961
-                                $o['info'][$k] = $v;
962
-                                break;
963
-                        }
964
-                    }
965
-
966
-                    // pass values down to descendent font
967
-                    if (isset($o['info']['cidFont'])) {
968
-                        $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $font_options);
969
-                    }
970
-                }
971
-                break;
972
-
973
-            case 'out':
974
-                if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
975
-                    // For Unicode fonts, we need to incorporate font data into
976
-                    // sub-sections that are linked from the primary font section.
977
-                    // Look at o_fontGIDtoCID and o_fontDescendentCID functions
978
-                    // for more information.
979
-                    //
980
-                    // All of this code is adapted from the excellent changes made to
981
-                    // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
982
-
983
-                    $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
984
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
985
-
986
-                    // The horizontal identity mapping for 2-byte CIDs; may be used
987
-                    // with CIDFonts using any Registry, Ordering, and Supplement values.
988
-                    $res .= "/Encoding /Identity-H\n";
989
-                    $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
990
-                    $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
991
-                    $res .= ">>\n";
992
-                    $res .= "endobj";
993
-                } else {
994
-                    $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
995
-                    $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
996
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
997
-
998
-                    if (isset($o['info']['encodingDictionary'])) {
999
-                        // then place a reference to the dictionary
1000
-                        $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
1001
-                    } else {
1002
-                        if (isset($o['info']['encoding'])) {
1003
-                            // use the specified encoding
1004
-                            $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
1005
-                        }
1006
-                    }
1007
-
1008
-                    if (isset($o['info']['FirstChar'])) {
1009
-                        $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
1010
-                    }
1011
-
1012
-                    if (isset($o['info']['LastChar'])) {
1013
-                        $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
1014
-                    }
1015
-
1016
-                    if (isset($o['info']['Widths'])) {
1017
-                        $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
1018
-                    }
1019
-
1020
-                    if (isset($o['info']['FontDescriptor'])) {
1021
-                        $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1022
-                    }
1023
-
1024
-                    $res .= ">>\n";
1025
-                    $res .= "endobj";
1026
-                }
1027
-
1028
-                return $res;
1029
-        }
1030
-
1031
-        return null;
1032
-    }
1033
-
1034
-    protected function getFontSubsettingTag(array $font): string
1035
-    {
1036
-        // convert font num to hexavigesimal numeral system letters A - Z only
1037
-        $base_26 = strtoupper(base_convert($font['fontNum'], 10, 26));
1038
-        for ($i = 0; $i < strlen($base_26); $i++) {
1039
-            $char = $base_26[$i];
1040
-            if ($char <= "9") {
1041
-                $base_26[$i] = chr(65 + intval($char));
1042
-            } else {
1043
-                $base_26[$i] = chr(ord($char) + 10);
1044
-            }
1045
-        }
1046
-
1047
-        return 'SUB' . str_pad($base_26, 3 , 'A', STR_PAD_LEFT);
1048
-    }
1049
-
1050
-    /**
1051
-     * @param int $fontObjId
1052
-     * @param array $object_info
1053
-     * @return array|false
1054
-     * @throws FontNotFoundException
1055
-     */
1056
-    private function processFont(int $fontObjId, array $object_info)
1057
-    {
1058
-        $fontFileName = $object_info['fontFileName'];
1059
-        if (!isset($this->fonts[$fontFileName])) {
1060
-            return false;
1061
-        }
1062
-
1063
-        $font = &$this->fonts[$fontFileName];
1064
-
1065
-        $fileSuffix = $font['fileSuffix'];
1066
-        $fileSuffixLower = strtolower($font['fileSuffix']);
1067
-        $fbfile = "$fontFileName.$fileSuffix";
1068
-        $isTtfFont = $fileSuffixLower === 'ttf';
1069
-        $isPfbFont = $fileSuffixLower === 'pfb';
1070
-
1071
-        $this->addMessage('selectFont: checking for - ' . $fbfile);
1072
-
1073
-        if (!$fileSuffix) {
1074
-            $this->addMessage(
1075
-                'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
1076
-            );
1077
-
1078
-            return false;
1079
-        } else {
1080
-            $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
1081
-            //        $fontObj = $this->numObj;
1082
-            $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
1083
-
1084
-            // find the array of font widths, and put that into an object.
1085
-            $firstChar = -1;
1086
-            $lastChar = 0;
1087
-            $widths = [];
1088
-            $cid_widths = [];
1089
-
1090
-            foreach ($font['C'] as $num => $d) {
1091
-                if (intval($num) > 0 || $num == '0') {
1092
-                    if (!$font['isUnicode']) {
1093
-                        // With Unicode, widths array isn't used
1094
-                        if ($lastChar > 0 && $num > $lastChar + 1) {
1095
-                            for ($i = $lastChar + 1; $i < $num; $i++) {
1096
-                                $widths[] = 0;
1097
-                            }
1098
-                        }
1099
-                    }
1100
-
1101
-                    $widths[] = $d;
1102
-
1103
-                    if ($font['isUnicode']) {
1104
-                        $cid_widths[$num] = $d;
1105
-                    }
1106
-
1107
-                    if ($firstChar == -1) {
1108
-                        $firstChar = $num;
1109
-                    }
1110
-
1111
-                    $lastChar = $num;
1112
-                }
1113
-            }
1114
-
1115
-            // also need to adjust the widths for the differences array
1116
-            if (isset($object['differences'])) {
1117
-                foreach ($object['differences'] as $charNum => $charName) {
1118
-                    if ($charNum > $lastChar) {
1119
-                        if (!$object['isUnicode']) {
1120
-                            // With Unicode, widths array isn't used
1121
-                            for ($i = $lastChar + 1; $i <= $charNum; $i++) {
1122
-                                $widths[] = 0;
1123
-                            }
1124
-                        }
1125
-
1126
-                        $lastChar = $charNum;
1127
-                    }
1128
-
1129
-                    if (isset($font['C'][$charName])) {
1130
-                        $widths[$charNum - $firstChar] = $font['C'][$charName];
1131
-                        if ($font['isUnicode']) {
1132
-                            $cid_widths[$charName] = $font['C'][$charName];
1133
-                        }
1134
-                    }
1135
-                }
1136
-            }
1137
-
1138
-            if ($font['isUnicode']) {
1139
-                $font['CIDWidths'] = $cid_widths;
1140
-            }
1141
-
1142
-            $this->addMessage('selectFont: FirstChar = ' . $firstChar);
1143
-            $this->addMessage('selectFont: LastChar = ' . $lastChar);
1144
-
1145
-            $widthid = -1;
1146
-
1147
-            if (!$font['isUnicode']) {
1148
-                // With Unicode, widths array isn't used
1149
-
1150
-                $this->numObj++;
1151
-                $this->o_contents($this->numObj, 'new', 'raw');
1152
-                $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
1153
-                $widthid = $this->numObj;
1154
-            }
1155
-
1156
-            $missing_width = 500;
1157
-            $stemV = 70;
1158
-
1159
-            if (isset($font['MissingWidth'])) {
1160
-                $missing_width = $font['MissingWidth'];
1161
-            }
1162
-            if (isset($font['StdVW'])) {
1163
-                $stemV = $font['StdVW'];
1164
-            } else {
1165
-                if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
1166
-                    $stemV = 120;
1167
-                }
1168
-            }
1169
-
1170
-            // load the pfb file, and put that into an object too.
1171
-            // note that pdf supports only binary format type 1 font files, though there is a
1172
-            // simple utility to convert them from pfa to pfb.
1173
-            $data = file_get_contents($fbfile);
1174
-
1175
-            // create the font descriptor
1176
-            $this->numObj++;
1177
-            $fontDescriptorId = $this->numObj;
1178
-
1179
-            $this->numObj++;
1180
-            $pfbid = $this->numObj;
1181
-
1182
-            // determine flags (more than a little flakey, hopefully will not matter much)
1183
-            $flags = 0;
1184
-
1185
-            if ($font['ItalicAngle'] != 0) {
1186
-                $flags += pow(2, 6);
1187
-            }
1188
-
1189
-            if ($font['IsFixedPitch'] === 'true') {
1190
-                $flags += 1;
1191
-            }
1192
-
1193
-            $flags += pow(2, 5); // assume non-sybolic
1194
-            $list = [
1195
-                'Ascent'       => 'Ascender',
1196
-                'CapHeight'    => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
1197
-                'MissingWidth' => 'MissingWidth',
1198
-                'Descent'      => 'Descender',
1199
-                'FontBBox'     => 'FontBBox',
1200
-                'ItalicAngle'  => 'ItalicAngle'
1201
-            ];
1202
-            $fdopt = [
1203
-                'Flags'    => $flags,
1204
-                'FontName' => $adobeFontName,
1205
-                'StemV'    => $stemV
1206
-            ];
1207
-
1208
-            foreach ($list as $k => $v) {
1209
-                if (isset($font[$v])) {
1210
-                    $fdopt[$k] = $font[$v];
1211
-                }
1212
-            }
1213
-
1214
-            if ($isPfbFont) {
1215
-                $fdopt['FontFile'] = $pfbid;
1216
-            } elseif ($isTtfFont) {
1217
-                $fdopt['FontFile2'] = $pfbid;
1218
-            }
1219
-
1220
-            $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
1221
-
1222
-            // embed the font program
1223
-            $this->o_contents($this->numObj, 'new');
1224
-            $this->objects[$pfbid]['c'] .= $data;
1225
-
1226
-            // determine the cruicial lengths within this file
1227
-            if ($isPfbFont) {
1228
-                $l1 = strpos($data, 'eexec') + 6;
1229
-                $l2 = strpos($data, '00000000') - $l1;
1230
-                $l3 = mb_strlen($data, '8bit') - $l2 - $l1;
1231
-                $this->o_contents(
1232
-                    $this->numObj,
1233
-                    'add',
1234
-                    ['Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3]
1235
-                );
1236
-            } elseif ($isTtfFont) {
1237
-                $l1 = mb_strlen($data, '8bit');
1238
-                $this->o_contents($this->numObj, 'add', ['Length1' => $l1]);
1239
-            }
1240
-
1241
-            // tell the font object about all this new stuff
1242
-            $options = [
1243
-                'BaseFont'       => $adobeFontName,
1244
-                'MissingWidth'   => $missing_width,
1245
-                'Widths'         => $widthid,
1246
-                'FirstChar'      => $firstChar,
1247
-                'LastChar'       => $lastChar,
1248
-                'FontDescriptor' => $fontDescriptorId
1249
-            ];
1250
-
1251
-            if ($isTtfFont) {
1252
-                $options['SubType'] = 'TrueType';
1253
-            }
1254
-
1255
-            $this->addMessage("adding extra info to font.($fontObjId)");
1256
-
1257
-            foreach ($options as $fk => $fv) {
1258
-                $this->addMessage("$fk : $fv");
1259
-            }
1260
-        }
1261
-
1262
-        return $options;
1263
-    }
1264
-
1265
-    /**
1266
-     * A toUnicode section, needed for unicode fonts
1267
-     *
1268
-     * @param $id
1269
-     * @param $action
1270
-     * @return null|string
1271
-     */
1272
-    protected function o_toUnicode($id, $action)
1273
-    {
1274
-        switch ($action) {
1275
-            case 'new':
1276
-                $this->objects[$id] = [
1277
-                    't'    => 'toUnicode'
1278
-                ];
1279
-                break;
1280
-            case 'add':
1281
-                break;
1282
-            case 'out':
1283
-                $ordering = 'UCS';
1284
-                $registry = 'Adobe';
1285
-
1286
-                if ($this->encrypted) {
1287
-                    $this->encryptInit($id);
1288
-                    $ordering = $this->ARC4($ordering);
1289
-                    $registry = $this->filterText($this->ARC4($registry), false, false);
1290
-                }
1291
-
1292
-                $stream = <<<EOT
23
+	const PDF_VERSION = '1.7';
24
+
25
+	const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
26
+	const ACROFORM_SIG_APPENDONLY =       0x0002;
27
+
28
+	const ACROFORM_FIELD_BUTTON =   'Btn';
29
+	const ACROFORM_FIELD_TEXT =     'Tx';
30
+	const ACROFORM_FIELD_CHOICE =   'Ch';
31
+	const ACROFORM_FIELD_SIG =      'Sig';
32
+
33
+	const ACROFORM_FIELD_READONLY =               0x0001;
34
+	const ACROFORM_FIELD_REQUIRED =               0x0002;
35
+
36
+	const ACROFORM_FIELD_TEXT_MULTILINE =         0x1000;
37
+	const ACROFORM_FIELD_TEXT_PASSWORD =          0x2000;
38
+	const ACROFORM_FIELD_TEXT_RICHTEXT =         0x10000;
39
+
40
+	const ACROFORM_FIELD_CHOICE_COMBO =          0x20000;
41
+	const ACROFORM_FIELD_CHOICE_EDIT =           0x40000;
42
+	const ACROFORM_FIELD_CHOICE_SORT =           0x80000;
43
+	const ACROFORM_FIELD_CHOICE_MULTISELECT =   0x200000;
44
+
45
+	const XOBJECT_SUBTYPE_FORM = 'Form';
46
+
47
+	/**
48
+	 * @var integer The current number of pdf objects in the document
49
+	 */
50
+	public $numObj = 0;
51
+
52
+	/**
53
+	 * @var array This array contains all of the pdf objects, ready for final assembly
54
+	 */
55
+	public $objects = [];
56
+
57
+	/**
58
+	 * @var integer The objectId (number within the objects array) of the document catalog
59
+	 */
60
+	public $catalogId;
61
+
62
+	/**
63
+	 * @var integer The objectId (number within the objects array) of indirect references (Javascript EmbeddedFiles)
64
+	 */
65
+	protected $indirectReferenceId = 0;
66
+
67
+	/**
68
+	 * @var integer The objectId (number within the objects array)
69
+	 */
70
+	protected $embeddedFilesId = 0;
71
+
72
+	/**
73
+	 * AcroForm objectId
74
+	 *
75
+	 * @var integer
76
+	 */
77
+	public $acroFormId;
78
+
79
+	/**
80
+	 * @var int
81
+	 */
82
+	public $signatureMaxLen = 5000;
83
+
84
+	/**
85
+	 * @var array Array carrying information about the fonts that the system currently knows about
86
+	 * Used to ensure that a font is not loaded twice, among other things
87
+	 */
88
+	public $fonts = [];
89
+
90
+	/**
91
+	 * @var string The default font metrics file to use if no other font has been loaded.
92
+	 * The path to the directory containing the font metrics should be included
93
+	 */
94
+	public $defaultFont = './fonts/Helvetica.afm';
95
+
96
+	/**
97
+	 * @string A record of the current font
98
+	 */
99
+	public $currentFont = '';
100
+
101
+	/**
102
+	 * @var string The current base font
103
+	 */
104
+	public $currentBaseFont = '';
105
+
106
+	/**
107
+	 * @var integer The number of the current font within the font array
108
+	 */
109
+	public $currentFontNum = 0;
110
+
111
+	/**
112
+	 * @var integer
113
+	 */
114
+	public $currentNode;
115
+
116
+	/**
117
+	 * @var integer Object number of the current page
118
+	 */
119
+	public $currentPage;
120
+
121
+	/**
122
+	 * @var integer Object number of the currently active contents block
123
+	 */
124
+	public $currentContents;
125
+
126
+	/**
127
+	 * @var integer Number of fonts within the system
128
+	 */
129
+	public $numFonts = 0;
130
+
131
+	/**
132
+	 * @var integer Number of graphic state resources used
133
+	 */
134
+	private $numStates = 0;
135
+
136
+	/**
137
+	 * @var array Number of graphic state resources used
138
+	 */
139
+	private $gstates = [];
140
+
141
+	/**
142
+	 * @var array Current color for fill operations, defaults to inactive value,
143
+	 * all three components should be between 0 and 1 inclusive when active
144
+	 */
145
+	public $currentColor = null;
146
+
147
+	/**
148
+	 * @var array Current color for stroke operations (lines etc.)
149
+	 */
150
+	public $currentStrokeColor = null;
151
+
152
+	/**
153
+	 * @var string Fill rule (nonzero or evenodd)
154
+	 */
155
+	public $fillRule = "nonzero";
156
+
157
+	/**
158
+	 * @var string Current style that lines are drawn in
159
+	 */
160
+	public $currentLineStyle = '';
161
+
162
+	/**
163
+	 * @var array Current line transparency (partial graphics state)
164
+	 */
165
+	public $currentLineTransparency = ["mode" => "Normal", "opacity" => 1.0];
166
+
167
+	/**
168
+	 * array Current fill transparency (partial graphics state)
169
+	 */
170
+	public $currentFillTransparency = ["mode" => "Normal", "opacity" => 1.0];
171
+
172
+	/**
173
+	 * @var array An array which is used to save the state of the document, mainly the colors and styles
174
+	 * it is used to temporarily change to another state, then change back to what it was before
175
+	 */
176
+	public $stateStack = [];
177
+
178
+	/**
179
+	 * @var integer Number of elements within the state stack
180
+	 */
181
+	public $nStateStack = 0;
182
+
183
+	/**
184
+	 * @var integer Number of page objects within the document
185
+	 */
186
+	public $numPages = 0;
187
+
188
+	/**
189
+	 * @var array Object Id storage stack
190
+	 */
191
+	public $stack = [];
192
+
193
+	/**
194
+	 * @var integer Number of elements within the object Id storage stack
195
+	 */
196
+	public $nStack = 0;
197
+
198
+	/**
199
+	 * an array which contains information about the objects which are not firmly attached to pages
200
+	 * these have been added with the addObject function
201
+	 */
202
+	public $looseObjects = [];
203
+
204
+	/**
205
+	 * array contains information about how the loose objects are to be added to the document
206
+	 */
207
+	public $addLooseObjects = [];
208
+
209
+	/**
210
+	 * @var integer The objectId of the information object for the document
211
+	 * this contains authorship, title etc.
212
+	 */
213
+	public $infoObject = 0;
214
+
215
+	/**
216
+	 * @var integer Number of images being tracked within the document
217
+	 */
218
+	public $numImages = 0;
219
+
220
+	/**
221
+	 * @var array An array containing options about the document
222
+	 * it defaults to turning on the compression of the objects
223
+	 */
224
+	public $options = ['compression' => true];
225
+
226
+	/**
227
+	 * @var integer The objectId of the first page of the document
228
+	 */
229
+	public $firstPageId;
230
+
231
+	/**
232
+	 * @var integer The object Id of the procset object
233
+	 */
234
+	public $procsetObjectId;
235
+
236
+	/**
237
+	 * @var array Store the information about the relationship between font families
238
+	 * this used so that the code knows which font is the bold version of another font, etc.
239
+	 * the value of this array is initialised in the constructor function.
240
+	 */
241
+	public $fontFamilies = [];
242
+
243
+	/**
244
+	 * @var string Folder for php serialized formats of font metrics files.
245
+	 * If empty string, use same folder as original metrics files.
246
+	 * This can be passed in from class creator.
247
+	 * If this folder does not exist or is not writable, Cpdf will be **much** slower.
248
+	 * Because of potential trouble with php safe mode, folder cannot be created at runtime.
249
+	 */
250
+	public $fontcache = '';
251
+
252
+	/**
253
+	 * @var integer The version of the font metrics cache file.
254
+	 * This value must be manually incremented whenever the internal font data structure is modified.
255
+	 */
256
+	public $fontcacheVersion = 6;
257
+
258
+	/**
259
+	 * @var string Temporary folder.
260
+	 * If empty string, will attempt system tmp folder.
261
+	 * This can be passed in from class creator.
262
+	 */
263
+	public $tmp = '';
264
+
265
+	/**
266
+	 * @var string Track if the current font is bolded or italicised
267
+	 */
268
+	public $currentTextState = '';
269
+
270
+	/**
271
+	 * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
272
+	 */
273
+	public $messages = '';
274
+
275
+	/**
276
+	 * @var string The encryption array for the document encryption is stored here
277
+	 */
278
+	public $arc4 = '';
279
+
280
+	/**
281
+	 * @var integer The object Id of the encryption information
282
+	 */
283
+	public $arc4_objnum = 0;
284
+
285
+	/**
286
+	 * @var string The file identifier, used to uniquely identify a pdf document
287
+	 */
288
+	public $fileIdentifier = '';
289
+
290
+	/**
291
+	 * @var boolean A flag to say if a document is to be encrypted or not
292
+	 */
293
+	public $encrypted = false;
294
+
295
+	/**
296
+	 * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
297
+	 */
298
+	public $encryptionKey = '';
299
+
300
+	/**
301
+	 * @var array Array which forms a stack to keep track of nested callback functions
302
+	 */
303
+	public $callback = [];
304
+
305
+	/**
306
+	 * @var integer The number of callback functions in the callback array
307
+	 */
308
+	public $nCallback = 0;
309
+
310
+	/**
311
+	 * @var array Store label->id pairs for named destinations, these will be used to replace internal links
312
+	 * done this way so that destinations can be defined after the location that links to them
313
+	 */
314
+	public $destinations = [];
315
+
316
+	/**
317
+	 * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
318
+	 * publiciables within the class, so that the user can rollback at will (from each 'start' command)
319
+	 * note that this includes the objects array, so these can be large.
320
+	 */
321
+	public $checkpoint = '';
322
+
323
+	/**
324
+	 * @var array Table of Image origin filenames and image labels which were already added with o_image().
325
+	 * Allows to merge identical images
326
+	 */
327
+	public $imagelist = [];
328
+
329
+	/**
330
+	 * @var array Table of already added alpha and plain image files for transparent PNG images.
331
+	 */
332
+	protected $imageAlphaList = [];
333
+
334
+	/**
335
+	 * @var array List of temporary image files to be deleted after processing.
336
+	 */
337
+	protected $imageCache = [];
338
+
339
+	/**
340
+	 * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
341
+	 */
342
+	public $isUnicode = false;
343
+
344
+	/**
345
+	 * @var string the JavaScript code of the document
346
+	 */
347
+	public $javascript = '';
348
+
349
+	/**
350
+	 * @var boolean whether the compression is possible
351
+	 */
352
+	protected $compressionReady = false;
353
+
354
+	/**
355
+	 * @var array Current page size
356
+	 */
357
+	protected $currentPageSize = ["width" => 0, "height" => 0];
358
+
359
+	/**
360
+	 * @var array All the chars that will be required in the font subsets
361
+	 */
362
+	protected $stringSubsets = [];
363
+
364
+	/**
365
+	 * @var string The target internal encoding
366
+	 */
367
+	protected static $targetEncoding = 'Windows-1252';
368
+
369
+	/**
370
+	 * @var array
371
+	 */
372
+	protected $byteRange = array();
373
+
374
+	/**
375
+	 * @var array The list of the core fonts
376
+	 */
377
+	protected static $coreFonts = [
378
+		'courier',
379
+		'courier-bold',
380
+		'courier-oblique',
381
+		'courier-boldoblique',
382
+		'helvetica',
383
+		'helvetica-bold',
384
+		'helvetica-oblique',
385
+		'helvetica-boldoblique',
386
+		'times-roman',
387
+		'times-bold',
388
+		'times-italic',
389
+		'times-bolditalic',
390
+		'symbol',
391
+		'zapfdingbats'
392
+	];
393
+
394
+	/**
395
+	 * Class constructor
396
+	 * This will start a new document
397
+	 *
398
+	 * @param array   $pageSize  Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
399
+	 * @param boolean $isUnicode Whether text will be treated as Unicode or not.
400
+	 * @param string  $fontcache The font cache folder
401
+	 * @param string  $tmp       The temporary folder
402
+	 */
403
+	function __construct($pageSize = [0, 0, 612, 792], $isUnicode = false, $fontcache = '', $tmp = '')
404
+	{
405
+		$this->isUnicode = $isUnicode;
406
+		$this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
407
+		$this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
408
+		$this->newDocument($pageSize);
409
+
410
+		$this->compressionReady = function_exists('gzcompress');
411
+
412
+		if (in_array('Windows-1252', mb_list_encodings())) {
413
+			self::$targetEncoding = 'Windows-1252';
414
+		}
415
+
416
+		// also initialize the font families that are known about already
417
+		$this->setFontFamily('init');
418
+	}
419
+
420
+	public function __destruct()
421
+	{
422
+		foreach ($this->imageCache as $file) {
423
+			if (file_exists($file)) {
424
+				unlink($file);
425
+			}
426
+		}
427
+	}
428
+
429
+	/**
430
+	 * Document object methods (internal use only)
431
+	 *
432
+	 * There is about one object method for each type of object in the pdf document
433
+	 * Each function has the same call list ($id,$action,$options).
434
+	 * $id = the object ID of the object, or what it is to be if it is being created
435
+	 * $action = a string specifying the action to be performed, though ALL must support:
436
+	 *           'new' - create the object with the id $id
437
+	 *           'out' - produce the output for the pdf object
438
+	 * $options = optional, a string or array containing the various parameters for the object
439
+	 *
440
+	 * These, in conjunction with the output function are the ONLY way for output to be produced
441
+	 * within the pdf 'file'.
442
+	 */
443
+
444
+	/**
445
+	 * Destination object, used to specify the location for the user to jump to, presently on opening
446
+	 *
447
+	 * @param $id
448
+	 * @param $action
449
+	 * @param string $options
450
+	 * @return string|null
451
+	 */
452
+	protected function o_destination($id, $action, $options = '')
453
+	{
454
+		switch ($action) {
455
+			case 'new':
456
+				$this->objects[$id] = ['t' => 'destination', 'info' => []];
457
+				$tmp = '';
458
+				switch ($options['type']) {
459
+					case 'XYZ':
460
+					/** @noinspection PhpMissingBreakStatementInspection */
461
+					case 'FitR':
462
+						$tmp = ' ' . $options['p3'] . $tmp;
463
+					case 'FitH':
464
+					case 'FitV':
465
+					case 'FitBH':
466
+					/** @noinspection PhpMissingBreakStatementInspection */
467
+					case 'FitBV':
468
+						$tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
469
+					case 'Fit':
470
+					case 'FitB':
471
+						$tmp = $options['type'] . $tmp;
472
+						$this->objects[$id]['info']['string'] = $tmp;
473
+						$this->objects[$id]['info']['page'] = $options['page'];
474
+				}
475
+				break;
476
+
477
+			case 'out':
478
+				$o = &$this->objects[$id];
479
+
480
+				$tmp = $o['info'];
481
+				$res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
482
+
483
+				return $res;
484
+		}
485
+
486
+		return null;
487
+	}
488
+
489
+	/**
490
+	 * set the viewer preferences
491
+	 *
492
+	 * @param $id
493
+	 * @param $action
494
+	 * @param string|array $options
495
+	 * @return string|null
496
+	 */
497
+	protected function o_viewerPreferences($id, $action, $options = '')
498
+	{
499
+		switch ($action) {
500
+			case 'new':
501
+				$this->objects[$id] = ['t' => 'viewerPreferences', 'info' => []];
502
+				break;
503
+
504
+			case 'add':
505
+				$o = &$this->objects[$id];
506
+
507
+				foreach ($options as $k => $v) {
508
+					switch ($k) {
509
+						// Boolean keys
510
+						case 'HideToolbar':
511
+						case 'HideMenubar':
512
+						case 'HideWindowUI':
513
+						case 'FitWindow':
514
+						case 'CenterWindow':
515
+						case 'DisplayDocTitle':
516
+						case 'PickTrayByPDFSize':
517
+							$o['info'][$k] = (bool)$v;
518
+							break;
519
+
520
+						// Integer keys
521
+						case 'NumCopies':
522
+							$o['info'][$k] = (int)$v;
523
+							break;
524
+
525
+						// Name keys
526
+						case 'ViewArea':
527
+						case 'ViewClip':
528
+						case 'PrintClip':
529
+						case 'PrintArea':
530
+							$o['info'][$k] = (string)$v;
531
+							break;
532
+
533
+						// Named with limited valid values
534
+						case 'NonFullScreenPageMode':
535
+							if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
536
+								break;
537
+							}
538
+							$o['info'][$k] = $v;
539
+							break;
540
+
541
+						case 'Direction':
542
+							if (!in_array($v, ['L2R', 'R2L'])) {
543
+								break;
544
+							}
545
+							$o['info'][$k] = $v;
546
+							break;
547
+
548
+						case 'PrintScaling':
549
+							if (!in_array($v, ['None', 'AppDefault'])) {
550
+								break;
551
+							}
552
+							$o['info'][$k] = $v;
553
+							break;
554
+
555
+						case 'Duplex':
556
+							if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
557
+								break;
558
+							}
559
+							$o['info'][$k] = $v;
560
+							break;
561
+
562
+						// Integer array
563
+						case 'PrintPageRange':
564
+							// Cast to integer array
565
+							foreach ($v as $vK => $vV) {
566
+								$v[$vK] = (int)$vV;
567
+							}
568
+							$o['info'][$k] = array_values($v);
569
+							break;
570
+					}
571
+				}
572
+				break;
573
+
574
+			case 'out':
575
+				$o = &$this->objects[$id];
576
+				$res = "\n$id 0 obj\n<< ";
577
+
578
+				foreach ($o['info'] as $k => $v) {
579
+					if (is_string($v)) {
580
+						$v = '/' . $v;
581
+					} elseif (is_int($v)) {
582
+						$v = (string) $v;
583
+					} elseif (is_bool($v)) {
584
+						$v = ($v ? 'true' : 'false');
585
+					} elseif (is_array($v)) {
586
+						$v = '[' . implode(' ', $v) . ']';
587
+					}
588
+					$res .= "\n/$k $v";
589
+				}
590
+				$res .= "\n>>\nendobj";
591
+
592
+				return $res;
593
+		}
594
+
595
+		return null;
596
+	}
597
+
598
+	/**
599
+	 * define the document catalog, the overall controller for the document
600
+	 *
601
+	 * @param $id
602
+	 * @param $action
603
+	 * @param string|array $options
604
+	 * @return string|null
605
+	 */
606
+	protected function o_catalog($id, $action, $options = '')
607
+	{
608
+		if ($action !== 'new') {
609
+			$o = &$this->objects[$id];
610
+		}
611
+
612
+		switch ($action) {
613
+			case 'new':
614
+				$this->objects[$id] = ['t' => 'catalog', 'info' => []];
615
+				$this->catalogId = $id;
616
+				break;
617
+
618
+			case 'acroform':
619
+			case 'outlines':
620
+			case 'pages':
621
+			case 'openHere':
622
+			case 'names':
623
+				$o['info'][$action] = $options;
624
+				break;
625
+
626
+			case 'viewerPreferences':
627
+				if (!isset($o['info']['viewerPreferences'])) {
628
+					$this->numObj++;
629
+					$this->o_viewerPreferences($this->numObj, 'new');
630
+					$o['info']['viewerPreferences'] = $this->numObj;
631
+				}
632
+
633
+				$vp = $o['info']['viewerPreferences'];
634
+				$this->o_viewerPreferences($vp, 'add', $options);
635
+
636
+				break;
637
+
638
+			case 'out':
639
+				$res = "\n$id 0 obj\n<< /Type /Catalog";
640
+
641
+				foreach ($o['info'] as $k => $v) {
642
+					switch ($k) {
643
+						case 'outlines':
644
+							$res .= "\n/Outlines $v 0 R";
645
+							break;
646
+
647
+						case 'pages':
648
+							$res .= "\n/Pages $v 0 R";
649
+							break;
650
+
651
+						case 'viewerPreferences':
652
+							$res .= "\n/ViewerPreferences $v 0 R";
653
+							break;
654
+
655
+						case 'openHere':
656
+							$res .= "\n/OpenAction $v 0 R";
657
+							break;
658
+
659
+						case 'names':
660
+							$res .= "\n/Names $v 0 R";
661
+							break;
662
+
663
+						case 'acroform':
664
+							$res .= "\n/AcroForm $v 0 R";
665
+							break;
666
+					}
667
+				}
668
+
669
+				$res .= " >>\nendobj";
670
+
671
+				return $res;
672
+		}
673
+
674
+		return null;
675
+	}
676
+
677
+	/**
678
+	 * object which is a parent to the pages in the document
679
+	 *
680
+	 * @param $id
681
+	 * @param $action
682
+	 * @param string $options
683
+	 * @return string|null
684
+	 */
685
+	protected function o_pages($id, $action, $options = '')
686
+	{
687
+		if ($action !== 'new') {
688
+			$o = &$this->objects[$id];
689
+		}
690
+
691
+		switch ($action) {
692
+			case 'new':
693
+				$this->objects[$id] = ['t' => 'pages', 'info' => []];
694
+				$this->o_catalog($this->catalogId, 'pages', $id);
695
+				break;
696
+
697
+			case 'page':
698
+				if (!is_array($options)) {
699
+					// then it will just be the id of the new page
700
+					$o['info']['pages'][] = $options;
701
+				} else {
702
+					// then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
703
+					// and pos is either 'before' or 'after', saying where this page will fit.
704
+					if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
705
+						$i = array_search($options['rid'], $o['info']['pages']);
706
+						if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
707
+
708
+							// then there is a match
709
+							// make a space
710
+							switch ($options['pos']) {
711
+								case 'before':
712
+									$k = $i;
713
+									break;
714
+
715
+								case 'after':
716
+									$k = $i + 1;
717
+									break;
718
+
719
+								default:
720
+									$k = -1;
721
+									break;
722
+							}
723
+
724
+							if ($k >= 0) {
725
+								for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
726
+									$o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
727
+								}
728
+
729
+								$o['info']['pages'][$k] = $options['id'];
730
+							}
731
+						}
732
+					}
733
+				}
734
+				break;
735
+
736
+			case 'procset':
737
+				$o['info']['procset'] = $options;
738
+				break;
739
+
740
+			case 'mediaBox':
741
+				$o['info']['mediaBox'] = $options;
742
+				// which should be an array of 4 numbers
743
+				$this->currentPageSize = ['width' => $options[2], 'height' => $options[3]];
744
+				break;
745
+
746
+			case 'font':
747
+				$o['info']['fonts'][] = ['objNum' => $options['objNum'], 'fontNum' => $options['fontNum']];
748
+				break;
749
+
750
+			case 'extGState':
751
+				$o['info']['extGStates'][] = ['objNum' => $options['objNum'], 'stateNum' => $options['stateNum']];
752
+				break;
753
+
754
+			case 'xObject':
755
+				$o['info']['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
756
+				break;
757
+
758
+			case 'out':
759
+				if (count($o['info']['pages'])) {
760
+					$res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
761
+					foreach ($o['info']['pages'] as $v) {
762
+						$res .= "$v 0 R\n";
763
+					}
764
+
765
+					$res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
766
+
767
+					if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
768
+						isset($o['info']['procset']) ||
769
+						(isset($o['info']['extGStates']) && count($o['info']['extGStates']))
770
+					) {
771
+						$res .= "\n/Resources <<";
772
+
773
+						if (isset($o['info']['procset'])) {
774
+							$res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
775
+						}
776
+
777
+						if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
778
+							$res .= "\n/Font << ";
779
+							foreach ($o['info']['fonts'] as $finfo) {
780
+								$res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
781
+							}
782
+							$res .= "\n>>";
783
+						}
784
+
785
+						if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
786
+							$res .= "\n/XObject << ";
787
+							foreach ($o['info']['xObjects'] as $finfo) {
788
+								$res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
789
+							}
790
+							$res .= "\n>>";
791
+						}
792
+
793
+						if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
794
+							$res .= "\n/ExtGState << ";
795
+							foreach ($o['info']['extGStates'] as $gstate) {
796
+								$res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
797
+							}
798
+							$res .= "\n>>";
799
+						}
800
+
801
+						$res .= "\n>>";
802
+						if (isset($o['info']['mediaBox'])) {
803
+							$tmp = $o['info']['mediaBox'];
804
+							$res .= "\n/MediaBox [" . sprintf(
805
+									'%.3F %.3F %.3F %.3F',
806
+									$tmp[0],
807
+									$tmp[1],
808
+									$tmp[2],
809
+									$tmp[3]
810
+								) . ']';
811
+						}
812
+					}
813
+
814
+					$res .= "\n >>\nendobj";
815
+				} else {
816
+					$res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
817
+				}
818
+
819
+				return $res;
820
+		}
821
+
822
+		return null;
823
+	}
824
+
825
+	/**
826
+	 * define the outlines in the doc, empty for now
827
+	 *
828
+	 * @param $id
829
+	 * @param $action
830
+	 * @param string $options
831
+	 * @return string|null
832
+	 */
833
+	protected function o_outlines($id, $action, $options = '')
834
+	{
835
+		if ($action !== 'new') {
836
+			$o = &$this->objects[$id];
837
+		}
838
+
839
+		switch ($action) {
840
+			case 'new':
841
+				$this->objects[$id] = ['t' => 'outlines', 'info' => ['outlines' => []]];
842
+				$this->o_catalog($this->catalogId, 'outlines', $id);
843
+				break;
844
+
845
+			case 'outline':
846
+				$o['info']['outlines'][] = $options;
847
+				break;
848
+
849
+			case 'out':
850
+				if (count($o['info']['outlines'])) {
851
+					$res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
852
+					foreach ($o['info']['outlines'] as $v) {
853
+						$res .= "$v 0 R ";
854
+					}
855
+
856
+					$res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
857
+				} else {
858
+					$res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
859
+				}
860
+
861
+				return $res;
862
+		}
863
+
864
+		return null;
865
+	}
866
+
867
+	/**
868
+	 * an object to hold the font description
869
+	 *
870
+	 * @param $id
871
+	 * @param $action
872
+	 * @param string|array $options
873
+	 * @return string|null
874
+	 * @throws FontNotFoundException
875
+	 */
876
+	protected function o_font($id, $action, $options = '')
877
+	{
878
+		if ($action !== 'new') {
879
+			$o = &$this->objects[$id];
880
+		}
881
+
882
+		switch ($action) {
883
+			case 'new':
884
+				$this->objects[$id] = [
885
+					't'    => 'font',
886
+					'info' => [
887
+						'name'         => $options['name'],
888
+						'fontFileName' => $options['fontFileName'],
889
+						'SubType'      => 'Type1',
890
+						'isSubsetting'   => $options['isSubsetting']
891
+					]
892
+				];
893
+				$fontNum = $this->numFonts;
894
+				$this->objects[$id]['info']['fontNum'] = $fontNum;
895
+
896
+				// deal with the encoding and the differences
897
+				if (isset($options['differences'])) {
898
+					// then we'll need an encoding dictionary
899
+					$this->numObj++;
900
+					$this->o_fontEncoding($this->numObj, 'new', $options);
901
+					$this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
902
+				} else {
903
+					if (isset($options['encoding'])) {
904
+						// we can specify encoding here
905
+						switch ($options['encoding']) {
906
+							case 'WinAnsiEncoding':
907
+							case 'MacRomanEncoding':
908
+							case 'MacExpertEncoding':
909
+								$this->objects[$id]['info']['encoding'] = $options['encoding'];
910
+								break;
911
+
912
+							case 'none':
913
+								break;
914
+
915
+							default:
916
+								$this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
917
+								break;
918
+						}
919
+					} else {
920
+						$this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
921
+					}
922
+				}
923
+
924
+				if ($this->fonts[$options['fontFileName']]['isUnicode']) {
925
+					// For Unicode fonts, we need to incorporate font data into
926
+					// sub-sections that are linked from the primary font section.
927
+					// Look at o_fontGIDtoCID and o_fontDescendentCID functions
928
+					// for more information.
929
+					//
930
+					// All of this code is adapted from the excellent changes made to
931
+					// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
932
+
933
+					$toUnicodeId = ++$this->numObj;
934
+					$this->o_toUnicode($toUnicodeId, 'new');
935
+					$this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
936
+
937
+					$cidFontId = ++$this->numObj;
938
+					$this->o_fontDescendentCID($cidFontId, 'new', $options);
939
+					$this->objects[$id]['info']['cidFont'] = $cidFontId;
940
+				}
941
+
942
+				// also tell the pages node about the new font
943
+				$this->o_pages($this->currentNode, 'font', ['fontNum' => $fontNum, 'objNum' => $id]);
944
+				break;
945
+
946
+			case 'add':
947
+				$font_options = $this->processFont($id, $o['info']);
948
+
949
+				if ($font_options !== false) {
950
+					foreach ($font_options as $k => $v) {
951
+						switch ($k) {
952
+							case 'BaseFont':
953
+								$o['info']['name'] = $v;
954
+								break;
955
+							case 'FirstChar':
956
+							case 'LastChar':
957
+							case 'Widths':
958
+							case 'FontDescriptor':
959
+							case 'SubType':
960
+								$this->addMessage('o_font ' . $k . " : " . $v);
961
+								$o['info'][$k] = $v;
962
+								break;
963
+						}
964
+					}
965
+
966
+					// pass values down to descendent font
967
+					if (isset($o['info']['cidFont'])) {
968
+						$this->o_fontDescendentCID($o['info']['cidFont'], 'add', $font_options);
969
+					}
970
+				}
971
+				break;
972
+
973
+			case 'out':
974
+				if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
975
+					// For Unicode fonts, we need to incorporate font data into
976
+					// sub-sections that are linked from the primary font section.
977
+					// Look at o_fontGIDtoCID and o_fontDescendentCID functions
978
+					// for more information.
979
+					//
980
+					// All of this code is adapted from the excellent changes made to
981
+					// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
982
+
983
+					$res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
984
+					$res .= "/BaseFont /" . $o['info']['name'] . "\n";
985
+
986
+					// The horizontal identity mapping for 2-byte CIDs; may be used
987
+					// with CIDFonts using any Registry, Ordering, and Supplement values.
988
+					$res .= "/Encoding /Identity-H\n";
989
+					$res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
990
+					$res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
991
+					$res .= ">>\n";
992
+					$res .= "endobj";
993
+				} else {
994
+					$res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
995
+					$res .= "/Name /F" . $o['info']['fontNum'] . "\n";
996
+					$res .= "/BaseFont /" . $o['info']['name'] . "\n";
997
+
998
+					if (isset($o['info']['encodingDictionary'])) {
999
+						// then place a reference to the dictionary
1000
+						$res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
1001
+					} else {
1002
+						if (isset($o['info']['encoding'])) {
1003
+							// use the specified encoding
1004
+							$res .= "/Encoding /" . $o['info']['encoding'] . "\n";
1005
+						}
1006
+					}
1007
+
1008
+					if (isset($o['info']['FirstChar'])) {
1009
+						$res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
1010
+					}
1011
+
1012
+					if (isset($o['info']['LastChar'])) {
1013
+						$res .= "/LastChar " . $o['info']['LastChar'] . "\n";
1014
+					}
1015
+
1016
+					if (isset($o['info']['Widths'])) {
1017
+						$res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
1018
+					}
1019
+
1020
+					if (isset($o['info']['FontDescriptor'])) {
1021
+						$res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1022
+					}
1023
+
1024
+					$res .= ">>\n";
1025
+					$res .= "endobj";
1026
+				}
1027
+
1028
+				return $res;
1029
+		}
1030
+
1031
+		return null;
1032
+	}
1033
+
1034
+	protected function getFontSubsettingTag(array $font): string
1035
+	{
1036
+		// convert font num to hexavigesimal numeral system letters A - Z only
1037
+		$base_26 = strtoupper(base_convert($font['fontNum'], 10, 26));
1038
+		for ($i = 0; $i < strlen($base_26); $i++) {
1039
+			$char = $base_26[$i];
1040
+			if ($char <= "9") {
1041
+				$base_26[$i] = chr(65 + intval($char));
1042
+			} else {
1043
+				$base_26[$i] = chr(ord($char) + 10);
1044
+			}
1045
+		}
1046
+
1047
+		return 'SUB' . str_pad($base_26, 3 , 'A', STR_PAD_LEFT);
1048
+	}
1049
+
1050
+	/**
1051
+	 * @param int $fontObjId
1052
+	 * @param array $object_info
1053
+	 * @return array|false
1054
+	 * @throws FontNotFoundException
1055
+	 */
1056
+	private function processFont(int $fontObjId, array $object_info)
1057
+	{
1058
+		$fontFileName = $object_info['fontFileName'];
1059
+		if (!isset($this->fonts[$fontFileName])) {
1060
+			return false;
1061
+		}
1062
+
1063
+		$font = &$this->fonts[$fontFileName];
1064
+
1065
+		$fileSuffix = $font['fileSuffix'];
1066
+		$fileSuffixLower = strtolower($font['fileSuffix']);
1067
+		$fbfile = "$fontFileName.$fileSuffix";
1068
+		$isTtfFont = $fileSuffixLower === 'ttf';
1069
+		$isPfbFont = $fileSuffixLower === 'pfb';
1070
+
1071
+		$this->addMessage('selectFont: checking for - ' . $fbfile);
1072
+
1073
+		if (!$fileSuffix) {
1074
+			$this->addMessage(
1075
+				'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
1076
+			);
1077
+
1078
+			return false;
1079
+		} else {
1080
+			$adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
1081
+			//        $fontObj = $this->numObj;
1082
+			$this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
1083
+
1084
+			// find the array of font widths, and put that into an object.
1085
+			$firstChar = -1;
1086
+			$lastChar = 0;
1087
+			$widths = [];
1088
+			$cid_widths = [];
1089
+
1090
+			foreach ($font['C'] as $num => $d) {
1091
+				if (intval($num) > 0 || $num == '0') {
1092
+					if (!$font['isUnicode']) {
1093
+						// With Unicode, widths array isn't used
1094
+						if ($lastChar > 0 && $num > $lastChar + 1) {
1095
+							for ($i = $lastChar + 1; $i < $num; $i++) {
1096
+								$widths[] = 0;
1097
+							}
1098
+						}
1099
+					}
1100
+
1101
+					$widths[] = $d;
1102
+
1103
+					if ($font['isUnicode']) {
1104
+						$cid_widths[$num] = $d;
1105
+					}
1106
+
1107
+					if ($firstChar == -1) {
1108
+						$firstChar = $num;
1109
+					}
1110
+
1111
+					$lastChar = $num;
1112
+				}
1113
+			}
1114
+
1115
+			// also need to adjust the widths for the differences array
1116
+			if (isset($object['differences'])) {
1117
+				foreach ($object['differences'] as $charNum => $charName) {
1118
+					if ($charNum > $lastChar) {
1119
+						if (!$object['isUnicode']) {
1120
+							// With Unicode, widths array isn't used
1121
+							for ($i = $lastChar + 1; $i <= $charNum; $i++) {
1122
+								$widths[] = 0;
1123
+							}
1124
+						}
1125
+
1126
+						$lastChar = $charNum;
1127
+					}
1128
+
1129
+					if (isset($font['C'][$charName])) {
1130
+						$widths[$charNum - $firstChar] = $font['C'][$charName];
1131
+						if ($font['isUnicode']) {
1132
+							$cid_widths[$charName] = $font['C'][$charName];
1133
+						}
1134
+					}
1135
+				}
1136
+			}
1137
+
1138
+			if ($font['isUnicode']) {
1139
+				$font['CIDWidths'] = $cid_widths;
1140
+			}
1141
+
1142
+			$this->addMessage('selectFont: FirstChar = ' . $firstChar);
1143
+			$this->addMessage('selectFont: LastChar = ' . $lastChar);
1144
+
1145
+			$widthid = -1;
1146
+
1147
+			if (!$font['isUnicode']) {
1148
+				// With Unicode, widths array isn't used
1149
+
1150
+				$this->numObj++;
1151
+				$this->o_contents($this->numObj, 'new', 'raw');
1152
+				$this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
1153
+				$widthid = $this->numObj;
1154
+			}
1155
+
1156
+			$missing_width = 500;
1157
+			$stemV = 70;
1158
+
1159
+			if (isset($font['MissingWidth'])) {
1160
+				$missing_width = $font['MissingWidth'];
1161
+			}
1162
+			if (isset($font['StdVW'])) {
1163
+				$stemV = $font['StdVW'];
1164
+			} else {
1165
+				if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
1166
+					$stemV = 120;
1167
+				}
1168
+			}
1169
+
1170
+			// load the pfb file, and put that into an object too.
1171
+			// note that pdf supports only binary format type 1 font files, though there is a
1172
+			// simple utility to convert them from pfa to pfb.
1173
+			$data = file_get_contents($fbfile);
1174
+
1175
+			// create the font descriptor
1176
+			$this->numObj++;
1177
+			$fontDescriptorId = $this->numObj;
1178
+
1179
+			$this->numObj++;
1180
+			$pfbid = $this->numObj;
1181
+
1182
+			// determine flags (more than a little flakey, hopefully will not matter much)
1183
+			$flags = 0;
1184
+
1185
+			if ($font['ItalicAngle'] != 0) {
1186
+				$flags += pow(2, 6);
1187
+			}
1188
+
1189
+			if ($font['IsFixedPitch'] === 'true') {
1190
+				$flags += 1;
1191
+			}
1192
+
1193
+			$flags += pow(2, 5); // assume non-sybolic
1194
+			$list = [
1195
+				'Ascent'       => 'Ascender',
1196
+				'CapHeight'    => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
1197
+				'MissingWidth' => 'MissingWidth',
1198
+				'Descent'      => 'Descender',
1199
+				'FontBBox'     => 'FontBBox',
1200
+				'ItalicAngle'  => 'ItalicAngle'
1201
+			];
1202
+			$fdopt = [
1203
+				'Flags'    => $flags,
1204
+				'FontName' => $adobeFontName,
1205
+				'StemV'    => $stemV
1206
+			];
1207
+
1208
+			foreach ($list as $k => $v) {
1209
+				if (isset($font[$v])) {
1210
+					$fdopt[$k] = $font[$v];
1211
+				}
1212
+			}
1213
+
1214
+			if ($isPfbFont) {
1215
+				$fdopt['FontFile'] = $pfbid;
1216
+			} elseif ($isTtfFont) {
1217
+				$fdopt['FontFile2'] = $pfbid;
1218
+			}
1219
+
1220
+			$this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
1221
+
1222
+			// embed the font program
1223
+			$this->o_contents($this->numObj, 'new');
1224
+			$this->objects[$pfbid]['c'] .= $data;
1225
+
1226
+			// determine the cruicial lengths within this file
1227
+			if ($isPfbFont) {
1228
+				$l1 = strpos($data, 'eexec') + 6;
1229
+				$l2 = strpos($data, '00000000') - $l1;
1230
+				$l3 = mb_strlen($data, '8bit') - $l2 - $l1;
1231
+				$this->o_contents(
1232
+					$this->numObj,
1233
+					'add',
1234
+					['Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3]
1235
+				);
1236
+			} elseif ($isTtfFont) {
1237
+				$l1 = mb_strlen($data, '8bit');
1238
+				$this->o_contents($this->numObj, 'add', ['Length1' => $l1]);
1239
+			}
1240
+
1241
+			// tell the font object about all this new stuff
1242
+			$options = [
1243
+				'BaseFont'       => $adobeFontName,
1244
+				'MissingWidth'   => $missing_width,
1245
+				'Widths'         => $widthid,
1246
+				'FirstChar'      => $firstChar,
1247
+				'LastChar'       => $lastChar,
1248
+				'FontDescriptor' => $fontDescriptorId
1249
+			];
1250
+
1251
+			if ($isTtfFont) {
1252
+				$options['SubType'] = 'TrueType';
1253
+			}
1254
+
1255
+			$this->addMessage("adding extra info to font.($fontObjId)");
1256
+
1257
+			foreach ($options as $fk => $fv) {
1258
+				$this->addMessage("$fk : $fv");
1259
+			}
1260
+		}
1261
+
1262
+		return $options;
1263
+	}
1264
+
1265
+	/**
1266
+	 * A toUnicode section, needed for unicode fonts
1267
+	 *
1268
+	 * @param $id
1269
+	 * @param $action
1270
+	 * @return null|string
1271
+	 */
1272
+	protected function o_toUnicode($id, $action)
1273
+	{
1274
+		switch ($action) {
1275
+			case 'new':
1276
+				$this->objects[$id] = [
1277
+					't'    => 'toUnicode'
1278
+				];
1279
+				break;
1280
+			case 'add':
1281
+				break;
1282
+			case 'out':
1283
+				$ordering = 'UCS';
1284
+				$registry = 'Adobe';
1285
+
1286
+				if ($this->encrypted) {
1287
+					$this->encryptInit($id);
1288
+					$ordering = $this->ARC4($ordering);
1289
+					$registry = $this->filterText($this->ARC4($registry), false, false);
1290
+				}
1291
+
1292
+				$stream = <<<EOT
1293 1293
 /CIDInit /ProcSet findresource begin
1294 1294
 12 dict begin
1295 1295
 begincmap
@@ -1312,5107 +1312,5107 @@  discard block
 block discarded – undo
1312 1312
 end
1313 1313
 EOT;
1314 1314
 
1315
-                $res = "\n$id 0 obj\n";
1316
-                $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1317
-                $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";;
1318
-
1319
-                return $res;
1320
-        }
1321
-
1322
-        return null;
1323
-    }
1324
-
1325
-    /**
1326
-     * a font descriptor, needed for including additional fonts
1327
-     *
1328
-     * @param $id
1329
-     * @param $action
1330
-     * @param string $options
1331
-     * @return null|string
1332
-     */
1333
-    protected function o_fontDescriptor($id, $action, $options = '')
1334
-    {
1335
-        if ($action !== 'new') {
1336
-            $o = &$this->objects[$id];
1337
-        }
1338
-
1339
-        switch ($action) {
1340
-            case 'new':
1341
-                $this->objects[$id] = ['t' => 'fontDescriptor', 'info' => $options];
1342
-                break;
1343
-
1344
-            case 'out':
1345
-                $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1346
-                foreach ($o['info'] as $label => $value) {
1347
-                    switch ($label) {
1348
-                        case 'Ascent':
1349
-                        case 'CapHeight':
1350
-                        case 'Descent':
1351
-                        case 'Flags':
1352
-                        case 'ItalicAngle':
1353
-                        case 'StemV':
1354
-                        case 'AvgWidth':
1355
-                        case 'Leading':
1356
-                        case 'MaxWidth':
1357
-                        case 'MissingWidth':
1358
-                        case 'StemH':
1359
-                        case 'XHeight':
1360
-                        case 'CharSet':
1361
-                            if (mb_strlen($value, '8bit')) {
1362
-                                $res .= "/$label $value\n";
1363
-                            }
1364
-
1365
-                            break;
1366
-                        case 'FontFile':
1367
-                        case 'FontFile2':
1368
-                        case 'FontFile3':
1369
-                            $res .= "/$label $value 0 R\n";
1370
-                            break;
1371
-
1372
-                        case 'FontBBox':
1373
-                            $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1374
-                            break;
1375
-
1376
-                        case 'FontName':
1377
-                            $res .= "/$label /$value\n";
1378
-                            break;
1379
-                    }
1380
-                }
1381
-
1382
-                $res .= ">>\nendobj";
1383
-
1384
-                return $res;
1385
-        }
1386
-
1387
-        return null;
1388
-    }
1389
-
1390
-    /**
1391
-     * the font encoding
1392
-     *
1393
-     * @param $id
1394
-     * @param $action
1395
-     * @param string $options
1396
-     * @return null|string
1397
-     */
1398
-    protected function o_fontEncoding($id, $action, $options = '')
1399
-    {
1400
-        if ($action !== 'new') {
1401
-            $o = &$this->objects[$id];
1402
-        }
1403
-
1404
-        switch ($action) {
1405
-            case 'new':
1406
-                // the options array should contain 'differences' and maybe 'encoding'
1407
-                $this->objects[$id] = ['t' => 'fontEncoding', 'info' => $options];
1408
-                break;
1409
-
1410
-            case 'out':
1411
-                $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1412
-                if (!isset($o['info']['encoding'])) {
1413
-                    $o['info']['encoding'] = 'WinAnsiEncoding';
1414
-                }
1415
-
1416
-                if ($o['info']['encoding'] !== 'none') {
1417
-                    $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1418
-                }
1419
-
1420
-                $res .= "/Differences \n[";
1421
-
1422
-                $onum = -100;
1423
-
1424
-                foreach ($o['info']['differences'] as $num => $label) {
1425
-                    if ($num != $onum + 1) {
1426
-                        // we cannot make use of consecutive numbering
1427
-                        $res .= "\n$num /$label";
1428
-                    } else {
1429
-                        $res .= " /$label";
1430
-                    }
1431
-
1432
-                    $onum = $num;
1433
-                }
1434
-
1435
-                $res .= "\n]\n>>\nendobj";
1436
-
1437
-                return $res;
1438
-        }
1439
-
1440
-        return null;
1441
-    }
1442
-
1443
-    /**
1444
-     * a descendent cid font, needed for unicode fonts
1445
-     *
1446
-     * @param $id
1447
-     * @param $action
1448
-     * @param string|array $options
1449
-     * @return null|string
1450
-     */
1451
-    protected function o_fontDescendentCID($id, $action, $options = '')
1452
-    {
1453
-        if ($action !== 'new') {
1454
-            $o = &$this->objects[$id];
1455
-        }
1456
-
1457
-        switch ($action) {
1458
-            case 'new':
1459
-                $this->objects[$id] = ['t' => 'fontDescendentCID', 'info' => $options];
1460
-
1461
-                // we need a CID system info section
1462
-                $cidSystemInfoId = ++$this->numObj;
1463
-                $this->o_cidSystemInfo($cidSystemInfoId, 'new');
1464
-                $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1465
-
1466
-                // and a CID to GID map
1467
-                $cidToGidMapId = ++$this->numObj;
1468
-                $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1469
-                $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1470
-                break;
1471
-
1472
-            case 'add':
1473
-                foreach ($options as $k => $v) {
1474
-                    switch ($k) {
1475
-                        case 'BaseFont':
1476
-                            $o['info']['name'] = $v;
1477
-                            break;
1478
-
1479
-                        case 'FirstChar':
1480
-                        case 'LastChar':
1481
-                        case 'MissingWidth':
1482
-                        case 'FontDescriptor':
1483
-                        case 'SubType':
1484
-                            $this->addMessage("o_fontDescendentCID $k : $v");
1485
-                            $o['info'][$k] = $v;
1486
-                            break;
1487
-                    }
1488
-                }
1489
-
1490
-                // pass values down to cid to gid map
1491
-                $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1492
-                break;
1493
-
1494
-            case 'out':
1495
-                $res = "\n$id 0 obj\n";
1496
-                $res .= "<</Type /Font\n";
1497
-                $res .= "/Subtype /CIDFontType2\n";
1498
-                $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1499
-                $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1500
-                //      if (isset($o['info']['FirstChar'])) {
1501
-                //        $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1502
-                //      }
1503
-
1504
-                //      if (isset($o['info']['LastChar'])) {
1505
-                //        $res.= "/LastChar ".$o['info']['LastChar']."\n";
1506
-                //      }
1507
-                if (isset($o['info']['FontDescriptor'])) {
1508
-                    $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1509
-                }
1510
-
1511
-                if (isset($o['info']['MissingWidth'])) {
1512
-                    $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1513
-                }
1514
-
1515
-                if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1516
-                    $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1517
-                    $w = '';
1518
-                    foreach ($cid_widths as $cid => $width) {
1519
-                        $w .= "$cid [$width] ";
1520
-                    }
1521
-                    $res .= "/W [$w]\n";
1522
-                }
1523
-
1524
-                $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1525
-                $res .= ">>\n";
1526
-                $res .= "endobj";
1527
-
1528
-                return $res;
1529
-        }
1530
-
1531
-        return null;
1532
-    }
1533
-
1534
-    /**
1535
-     * CID system info section, needed for unicode fonts
1536
-     *
1537
-     * @param $id
1538
-     * @param $action
1539
-     * @return null|string
1540
-     */
1541
-    protected function o_cidSystemInfo($id, $action)
1542
-    {
1543
-        switch ($action) {
1544
-            case 'new':
1545
-                $this->objects[$id] = [
1546
-                    't' => 'cidSystemInfo'
1547
-                ];
1548
-                break;
1549
-            case 'add':
1550
-                break;
1551
-            case 'out':
1552
-                $ordering = 'UCS';
1553
-                $registry = 'Adobe';
1554
-
1555
-                if ($this->encrypted) {
1556
-                    $this->encryptInit($id);
1557
-                    $ordering = $this->ARC4($ordering);
1558
-                    $registry = $this->ARC4($registry);
1559
-                }
1560
-
1561
-
1562
-                $res = "\n$id 0 obj\n";
1563
-
1564
-                $res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
1565
-                $res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
1566
-                $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1567
-                $res .= ">>";
1568
-
1569
-                $res .= "\nendobj";
1570
-
1571
-                return $res;
1572
-        }
1573
-
1574
-        return null;
1575
-    }
1576
-
1577
-    /**
1578
-     * a font glyph to character map, needed for unicode fonts
1579
-     *
1580
-     * @param $id
1581
-     * @param $action
1582
-     * @param string $options
1583
-     * @return null|string
1584
-     */
1585
-    protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1586
-    {
1587
-        if ($action !== 'new') {
1588
-            $o = &$this->objects[$id];
1589
-        }
1590
-
1591
-        switch ($action) {
1592
-            case 'new':
1593
-                $this->objects[$id] = ['t' => 'fontGIDtoCIDMap', 'info' => $options];
1594
-                break;
1595
-
1596
-            case 'out':
1597
-                $res = "\n$id 0 obj\n";
1598
-                $fontFileName = $o['info']['fontFileName'];
1599
-                $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1600
-
1601
-                $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1602
-                    $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1603
-
1604
-                if (!$compressed && isset($o['raw'])) {
1605
-                    $res .= $tmp;
1606
-                } else {
1607
-                    $res .= "<<";
1608
-
1609
-                    if (!$compressed && $this->compressionReady && $this->options['compression']) {
1610
-                        // then implement ZLIB based compression on this content stream
1611
-                        $compressed = true;
1612
-                        $tmp = gzcompress($tmp, 6);
1613
-                    }
1614
-                    if ($compressed) {
1615
-                        $res .= "\n/Filter /FlateDecode";
1616
-                    }
1617
-
1618
-                    if ($this->encrypted) {
1619
-                        $this->encryptInit($id);
1620
-                        $tmp = $this->ARC4($tmp);
1621
-                    }
1622
-
1623
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1624
-                }
1625
-
1626
-                $res .= "\nendobj";
1627
-
1628
-                return $res;
1629
-        }
1630
-
1631
-        return null;
1632
-    }
1633
-
1634
-    /**
1635
-     * the document procset, solves some problems with printing to old PS printers
1636
-     *
1637
-     * @param $id
1638
-     * @param $action
1639
-     * @param string $options
1640
-     * @return null|string
1641
-     */
1642
-    protected function o_procset($id, $action, $options = '')
1643
-    {
1644
-        if ($action !== 'new') {
1645
-            $o = &$this->objects[$id];
1646
-        }
1647
-
1648
-        switch ($action) {
1649
-            case 'new':
1650
-                $this->objects[$id] = ['t' => 'procset', 'info' => ['PDF' => 1, 'Text' => 1]];
1651
-                $this->o_pages($this->currentNode, 'procset', $id);
1652
-                $this->procsetObjectId = $id;
1653
-                break;
1654
-
1655
-            case 'add':
1656
-                // this is to add new items to the procset list, despite the fact that this is considered
1657
-                // obsolete, the items are required for printing to some postscript printers
1658
-                switch ($options) {
1659
-                    case 'ImageB':
1660
-                    case 'ImageC':
1661
-                    case 'ImageI':
1662
-                        $o['info'][$options] = 1;
1663
-                        break;
1664
-                }
1665
-                break;
1666
-
1667
-            case 'out':
1668
-                $res = "\n$id 0 obj\n[";
1669
-                foreach ($o['info'] as $label => $val) {
1670
-                    $res .= "/$label ";
1671
-                }
1672
-                $res .= "]\nendobj";
1673
-
1674
-                return $res;
1675
-        }
1676
-
1677
-        return null;
1678
-    }
1679
-
1680
-    /**
1681
-     * define the document information
1682
-     *
1683
-     * @param $id
1684
-     * @param $action
1685
-     * @param string $options
1686
-     * @return null|string
1687
-     */
1688
-    protected function o_info($id, $action, $options = '')
1689
-    {
1690
-        switch ($action) {
1691
-            case 'new':
1692
-                $this->infoObject = $id;
1693
-                $date = 'D:' . @date('Ymd');
1694
-                $this->objects[$id] = [
1695
-                    't'    => 'info',
1696
-                    'info' => [
1697
-                        'Producer'      => 'CPDF (dompdf)',
1698
-                        'CreationDate' => $date
1699
-                    ]
1700
-                ];
1701
-                break;
1702
-            case 'Title':
1703
-            case 'Author':
1704
-            case 'Subject':
1705
-            case 'Keywords':
1706
-            case 'Creator':
1707
-            case 'Producer':
1708
-            case 'CreationDate':
1709
-            case 'ModDate':
1710
-            case 'Trapped':
1711
-                $this->objects[$id]['info'][$action] = $options;
1712
-                break;
1713
-
1714
-            case 'out':
1715
-                $encrypted = $this->encrypted;
1716
-                if ($encrypted) {
1717
-                    $this->encryptInit($id);
1718
-                }
1719
-
1720
-                $res = "\n$id 0 obj\n<<\n";
1721
-                $o = &$this->objects[$id];
1722
-                foreach ($o['info'] as $k => $v) {
1723
-                    $res .= "/$k (";
1724
-
1725
-                    // dates must be outputted as-is, without Unicode transformations
1726
-                    if ($k !== 'CreationDate' && $k !== 'ModDate') {
1727
-                        $v = $this->filterText($v, true, false);
1728
-                    }
1729
-
1730
-                    if ($encrypted) {
1731
-                        $v = $this->ARC4($v);
1732
-                    }
1733
-
1734
-                    $res .= $v;
1735
-                    $res .= ")\n";
1736
-                }
1737
-
1738
-                $res .= ">>\nendobj";
1739
-
1740
-                return $res;
1741
-        }
1742
-
1743
-        return null;
1744
-    }
1745
-
1746
-    /**
1747
-     * an action object, used to link to URLS initially
1748
-     *
1749
-     * @param $id
1750
-     * @param $action
1751
-     * @param string $options
1752
-     * @return null|string
1753
-     */
1754
-    protected function o_action($id, $action, $options = '')
1755
-    {
1756
-        if ($action !== 'new') {
1757
-            $o = &$this->objects[$id];
1758
-        }
1759
-
1760
-        switch ($action) {
1761
-            case 'new':
1762
-                if (is_array($options)) {
1763
-                    $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => $options['type']];
1764
-                } else {
1765
-                    // then assume a URI action
1766
-                    $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => 'URI'];
1767
-                }
1768
-                break;
1769
-
1770
-            case 'out':
1771
-                if ($this->encrypted) {
1772
-                    $this->encryptInit($id);
1773
-                }
1774
-
1775
-                $res = "\n$id 0 obj\n<< /Type /Action";
1776
-                switch ($o['type']) {
1777
-                    case 'ilink':
1778
-                        if (!isset($this->destinations[(string)$o['info']['label']])) {
1779
-                            break;
1780
-                        }
1781
-
1782
-                        // there will be an 'label' setting, this is the name of the destination
1783
-                        $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1784
-                        break;
1785
-
1786
-                    case 'URI':
1787
-                        $res .= "\n/S /URI\n/URI (";
1788
-                        if ($this->encrypted) {
1789
-                            $res .= $this->filterText($this->ARC4($o['info']), false, false);
1790
-                        } else {
1791
-                            $res .= $this->filterText($o['info'], false, false);
1792
-                        }
1793
-
1794
-                        $res .= ")";
1795
-                        break;
1796
-                }
1797
-
1798
-                $res .= "\n>>\nendobj";
1799
-
1800
-                return $res;
1801
-        }
1802
-
1803
-        return null;
1804
-    }
1805
-
1806
-    /**
1807
-     * an annotation object, this will add an annotation to the current page.
1808
-     * initially will support just link annotations
1809
-     *
1810
-     * @param $id
1811
-     * @param $action
1812
-     * @param string $options
1813
-     * @return null|string
1814
-     */
1815
-    protected function o_annotation($id, $action, $options = '')
1816
-    {
1817
-        if ($action !== 'new') {
1818
-            $o = &$this->objects[$id];
1819
-        }
1820
-
1821
-        switch ($action) {
1822
-            case 'new':
1823
-                // add the annotation to the current page
1824
-                $pageId = $this->currentPage;
1825
-                $this->o_page($pageId, 'annot', $id);
1826
-
1827
-                // and add the action object which is going to be required
1828
-                switch ($options['type']) {
1829
-                    case 'link':
1830
-                        $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1831
-                        $this->numObj++;
1832
-                        $this->o_action($this->numObj, 'new', $options['url']);
1833
-                        $this->objects[$id]['info']['actionId'] = $this->numObj;
1834
-                        break;
1835
-
1836
-                    case 'ilink':
1837
-                        // this is to a named internal link
1838
-                        $label = $options['label'];
1839
-                        $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1840
-                        $this->numObj++;
1841
-                        $this->o_action($this->numObj, 'new', ['type' => 'ilink', 'label' => $label]);
1842
-                        $this->objects[$id]['info']['actionId'] = $this->numObj;
1843
-                        break;
1844
-                }
1845
-                break;
1846
-
1847
-            case 'out':
1848
-                $res = "\n$id 0 obj\n<< /Type /Annot";
1849
-                switch ($o['info']['type']) {
1850
-                    case 'link':
1851
-                    case 'ilink':
1852
-                        $res .= "\n/Subtype /Link";
1853
-                        break;
1854
-                }
1855
-                $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1856
-                $res .= "\n/Border [0 0 0]";
1857
-                $res .= "\n/H /I";
1858
-                $res .= "\n/Rect [ ";
1859
-
1860
-                foreach ($o['info']['rect'] as $v) {
1861
-                    $res .= sprintf("%.4F ", $v);
1862
-                }
1863
-
1864
-                $res .= "]";
1865
-                $res .= "\n>>\nendobj";
1866
-
1867
-                return $res;
1868
-        }
1869
-
1870
-        return null;
1871
-    }
1872
-
1873
-    /**
1874
-     * a page object, it also creates a contents object to hold its contents
1875
-     *
1876
-     * @param $id
1877
-     * @param $action
1878
-     * @param string $options
1879
-     * @return null|string
1880
-     */
1881
-    protected function o_page($id, $action, $options = '')
1882
-    {
1883
-        if ($action !== 'new') {
1884
-            $o = &$this->objects[$id];
1885
-        }
1886
-
1887
-        switch ($action) {
1888
-            case 'new':
1889
-                $this->numPages++;
1890
-                $this->objects[$id] = [
1891
-                    't'    => 'page',
1892
-                    'info' => [
1893
-                        'parent'  => $this->currentNode,
1894
-                        'pageNum' => $this->numPages,
1895
-                        'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1896
-                    ]
1897
-                ];
1898
-
1899
-                if (is_array($options)) {
1900
-                    // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1901
-                    $options['id'] = $id;
1902
-                    $this->o_pages($this->currentNode, 'page', $options);
1903
-                } else {
1904
-                    $this->o_pages($this->currentNode, 'page', $id);
1905
-                }
1906
-
1907
-                $this->currentPage = $id;
1908
-                //make a contents object to go with this page
1909
-                $this->numObj++;
1910
-                $this->o_contents($this->numObj, 'new', $id);
1911
-                $this->currentContents = $this->numObj;
1912
-                $this->objects[$id]['info']['contents'] = [];
1913
-                $this->objects[$id]['info']['contents'][] = $this->numObj;
1914
-
1915
-                $match = ($this->numPages % 2 ? 'odd' : 'even');
1916
-                foreach ($this->addLooseObjects as $oId => $target) {
1917
-                    if ($target === 'all' || $match === $target) {
1918
-                        $this->objects[$id]['info']['contents'][] = $oId;
1919
-                    }
1920
-                }
1921
-                break;
1922
-
1923
-            case 'content':
1924
-                $o['info']['contents'][] = $options;
1925
-                break;
1926
-
1927
-            case 'annot':
1928
-                // add an annotation to this page
1929
-                if (!isset($o['info']['annot'])) {
1930
-                    $o['info']['annot'] = [];
1931
-                }
1932
-
1933
-                // $options should contain the id of the annotation dictionary
1934
-                $o['info']['annot'][] = $options;
1935
-                break;
1936
-
1937
-            case 'out':
1938
-                $res = "\n$id 0 obj\n<< /Type /Page";
1939
-                if (isset($o['info']['mediaBox'])) {
1940
-                    $tmp = $o['info']['mediaBox'];
1941
-                    $res .= "\n/MediaBox [" . sprintf(
1942
-                            '%.3F %.3F %.3F %.3F',
1943
-                            $tmp[0],
1944
-                            $tmp[1],
1945
-                            $tmp[2],
1946
-                            $tmp[3]
1947
-                        ) . ']';
1948
-                }
1949
-                $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1950
-
1951
-                if (isset($o['info']['annot'])) {
1952
-                    $res .= "\n/Annots [";
1953
-                    foreach ($o['info']['annot'] as $aId) {
1954
-                        $res .= " $aId 0 R";
1955
-                    }
1956
-                    $res .= " ]";
1957
-                }
1958
-
1959
-                $count = count($o['info']['contents']);
1960
-                if ($count == 1) {
1961
-                    $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1962
-                } else {
1963
-                    if ($count > 1) {
1964
-                        $res .= "\n/Contents [\n";
1965
-
1966
-                        // reverse the page contents so added objects are below normal content
1967
-                        //foreach (array_reverse($o['info']['contents']) as $cId) {
1968
-                        // Back to normal now that I've got transparency working --Benj
1969
-                        foreach ($o['info']['contents'] as $cId) {
1970
-                            $res .= "$cId 0 R\n";
1971
-                        }
1972
-                        $res .= "]";
1973
-                    }
1974
-                }
1975
-
1976
-                $res .= "\n>>\nendobj";
1977
-
1978
-                return $res;
1979
-        }
1980
-
1981
-        return null;
1982
-    }
1983
-
1984
-    /**
1985
-     * the contents objects hold all of the content which appears on pages
1986
-     *
1987
-     * @param $id
1988
-     * @param $action
1989
-     * @param string|array $options
1990
-     * @return null|string
1991
-     */
1992
-    protected function o_contents($id, $action, $options = '')
1993
-    {
1994
-        if ($action !== 'new') {
1995
-            $o = &$this->objects[$id];
1996
-        }
1997
-
1998
-        switch ($action) {
1999
-            case 'new':
2000
-                $this->objects[$id] = ['t' => 'contents', 'c' => '', 'info' => []];
2001
-                if (mb_strlen($options, '8bit') && intval($options)) {
2002
-                    // then this contents is the primary for a page
2003
-                    $this->objects[$id]['onPage'] = $options;
2004
-                } else {
2005
-                    if ($options === 'raw') {
2006
-                        // then this page contains some other type of system object
2007
-                        $this->objects[$id]['raw'] = 1;
2008
-                    }
2009
-                }
2010
-                break;
2011
-
2012
-            case 'add':
2013
-                // add more options to the declaration
2014
-                foreach ($options as $k => $v) {
2015
-                    $o['info'][$k] = $v;
2016
-                }
2017
-
2018
-            case 'out':
2019
-                $tmp = $o['c'];
2020
-                $res = "\n$id 0 obj\n";
2021
-
2022
-                if (isset($this->objects[$id]['raw'])) {
2023
-                    $res .= $tmp;
2024
-                } else {
2025
-                    $res .= "<<";
2026
-                    if ($this->compressionReady && $this->options['compression']) {
2027
-                        // then implement ZLIB based compression on this content stream
2028
-                        $res .= " /Filter /FlateDecode";
2029
-                        $tmp = gzcompress($tmp, 6);
2030
-                    }
2031
-
2032
-                    if ($this->encrypted) {
2033
-                        $this->encryptInit($id);
2034
-                        $tmp = $this->ARC4($tmp);
2035
-                    }
2036
-
2037
-                    foreach ($o['info'] as $k => $v) {
2038
-                        $res .= "\n/$k $v";
2039
-                    }
2040
-
2041
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
2042
-                }
2043
-
2044
-                $res .= "\nendobj";
2045
-
2046
-                return $res;
2047
-        }
2048
-
2049
-        return null;
2050
-    }
2051
-
2052
-    /**
2053
-     * @param $id
2054
-     * @param $action
2055
-     * @return string|null
2056
-     */
2057
-    protected function o_embedjs($id, $action)
2058
-    {
2059
-        switch ($action) {
2060
-            case 'new':
2061
-                $this->objects[$id] = [
2062
-                    't'    => 'embedjs',
2063
-                    'info' => [
2064
-                        'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
2065
-                    ]
2066
-                ];
2067
-                break;
2068
-
2069
-            case 'out':
2070
-                $o = &$this->objects[$id];
2071
-                $res = "\n$id 0 obj\n<< ";
2072
-                foreach ($o['info'] as $k => $v) {
2073
-                    $res .= "\n/$k $v";
2074
-                }
2075
-                $res .= "\n>>\nendobj";
2076
-
2077
-                return $res;
2078
-        }
2079
-
2080
-        return null;
2081
-    }
2082
-
2083
-    /**
2084
-     * @param $id
2085
-     * @param $action
2086
-     * @param string $code
2087
-     * @return null|string
2088
-     */
2089
-    protected function o_javascript($id, $action, $code = '')
2090
-    {
2091
-        switch ($action) {
2092
-            case 'new':
2093
-                $this->objects[$id] = [
2094
-                    't'    => 'javascript',
2095
-                    'info' => [
2096
-                        'S'  => '/JavaScript',
2097
-                        'JS' => '(' . $this->filterText($code, true, false) . ')',
2098
-                    ]
2099
-                ];
2100
-                break;
2101
-
2102
-            case 'out':
2103
-                $o = &$this->objects[$id];
2104
-                $res = "\n$id 0 obj\n<< ";
2105
-
2106
-                foreach ($o['info'] as $k => $v) {
2107
-                    $res .= "\n/$k $v";
2108
-                }
2109
-                $res .= "\n>>\nendobj";
2110
-
2111
-                return $res;
2112
-        }
2113
-
2114
-        return null;
2115
-    }
2116
-
2117
-    /**
2118
-     * an image object, will be an XObject in the document, includes description and data
2119
-     *
2120
-     * @param $id
2121
-     * @param $action
2122
-     * @param string $options
2123
-     * @return null|string
2124
-     */
2125
-    protected function o_image($id, $action, $options = '')
2126
-    {
2127
-        switch ($action) {
2128
-            case 'new':
2129
-                // make the new object
2130
-                $this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
2131
-
2132
-                $info =& $this->objects[$id]['info'];
2133
-
2134
-                $info['Type'] = '/XObject';
2135
-                $info['Subtype'] = '/Image';
2136
-                $info['Width'] = $options['iw'];
2137
-                $info['Height'] = $options['ih'];
2138
-
2139
-                if (isset($options['masked']) && $options['masked']) {
2140
-                    $info['SMask'] = ($this->numObj - 1) . ' 0 R';
2141
-                }
2142
-
2143
-                if (!isset($options['type']) || $options['type'] === 'jpg') {
2144
-                    if (!isset($options['channels'])) {
2145
-                        $options['channels'] = 3;
2146
-                    }
2147
-
2148
-                    switch ($options['channels']) {
2149
-                        case 1:
2150
-                            $info['ColorSpace'] = '/DeviceGray';
2151
-                            break;
2152
-                        case 4:
2153
-                            $info['ColorSpace'] = '/DeviceCMYK';
2154
-                            break;
2155
-                        default:
2156
-                            $info['ColorSpace'] = '/DeviceRGB';
2157
-                            break;
2158
-                    }
2159
-
2160
-                    if ($info['ColorSpace'] === '/DeviceCMYK') {
2161
-                        $info['Decode'] = '[1 0 1 0 1 0 1 0]';
2162
-                    }
2163
-
2164
-                    $info['Filter'] = '/DCTDecode';
2165
-                    $info['BitsPerComponent'] = 8;
2166
-                } else {
2167
-                    if ($options['type'] === 'png') {
2168
-                        $info['Filter'] = '/FlateDecode';
2169
-                        $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
2170
-
2171
-                        if ($options['isMask']) {
2172
-                            $info['ColorSpace'] = '/DeviceGray';
2173
-                        } else {
2174
-                            if (mb_strlen($options['pdata'], '8bit')) {
2175
-                                $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
2176
-                                $this->numObj++;
2177
-                                $this->o_contents($this->numObj, 'new');
2178
-                                $this->objects[$this->numObj]['c'] = $options['pdata'];
2179
-                                $tmp .= $this->numObj . ' 0 R';
2180
-                                $tmp .= ' ]';
2181
-                                $info['ColorSpace'] = $tmp;
2182
-
2183
-                                if (isset($options['transparency'])) {
2184
-                                    $transparency = $options['transparency'];
2185
-                                    switch ($transparency['type']) {
2186
-                                        case 'indexed':
2187
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2188
-                                            $info['Mask'] = $tmp;
2189
-                                            break;
2190
-
2191
-                                        case 'color-key':
2192
-                                            $tmp = ' [ ' .
2193
-                                                $transparency['r'] . ' ' . $transparency['r'] .
2194
-                                                $transparency['g'] . ' ' . $transparency['g'] .
2195
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2196
-                                                ' ] ';
2197
-                                            $info['Mask'] = $tmp;
2198
-                                            break;
2199
-                                    }
2200
-                                }
2201
-                            } else {
2202
-                                if (isset($options['transparency'])) {
2203
-                                    $transparency = $options['transparency'];
2204
-
2205
-                                    switch ($transparency['type']) {
2206
-                                        case 'indexed':
2207
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2208
-                                            $info['Mask'] = $tmp;
2209
-                                            break;
2210
-
2211
-                                        case 'color-key':
2212
-                                            $tmp = ' [ ' .
2213
-                                                $transparency['r'] . ' ' . $transparency['r'] . ' ' .
2214
-                                                $transparency['g'] . ' ' . $transparency['g'] . ' ' .
2215
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2216
-                                                ' ] ';
2217
-                                            $info['Mask'] = $tmp;
2218
-                                            break;
2219
-                                    }
2220
-                                }
2221
-                                $info['ColorSpace'] = '/' . $options['color'];
2222
-                            }
2223
-                        }
2224
-
2225
-                        $info['BitsPerComponent'] = $options['bitsPerComponent'];
2226
-                    }
2227
-                }
2228
-
2229
-                // assign it a place in the named resource dictionary as an external object, according to
2230
-                // the label passed in with it.
2231
-                $this->o_pages($this->currentNode, 'xObject', ['label' => $options['label'], 'objNum' => $id]);
2232
-
2233
-                // also make sure that we have the right procset object for it.
2234
-                $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
2235
-                break;
2236
-
2237
-            case 'out':
2238
-                $o = &$this->objects[$id];
2239
-                $tmp = &$o['data'];
2240
-                $res = "\n$id 0 obj\n<<";
2241
-
2242
-                foreach ($o['info'] as $k => $v) {
2243
-                    $res .= "\n/$k $v";
2244
-                }
2245
-
2246
-                if ($this->encrypted) {
2247
-                    $this->encryptInit($id);
2248
-                    $tmp = $this->ARC4($tmp);
2249
-                }
2250
-
2251
-                $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
2252
-
2253
-                return $res;
2254
-        }
2255
-
2256
-        return null;
2257
-    }
2258
-
2259
-    /**
2260
-     * graphics state object
2261
-     *
2262
-     * @param $id
2263
-     * @param $action
2264
-     * @param string $options
2265
-     * @return null|string
2266
-     */
2267
-    protected function o_extGState($id, $action, $options = "")
2268
-    {
2269
-        static $valid_params = [
2270
-            "LW",
2271
-            "LC",
2272
-            "LC",
2273
-            "LJ",
2274
-            "ML",
2275
-            "D",
2276
-            "RI",
2277
-            "OP",
2278
-            "op",
2279
-            "OPM",
2280
-            "Font",
2281
-            "BG",
2282
-            "BG2",
2283
-            "UCR",
2284
-            "TR",
2285
-            "TR2",
2286
-            "HT",
2287
-            "FL",
2288
-            "SM",
2289
-            "SA",
2290
-            "BM",
2291
-            "SMask",
2292
-            "CA",
2293
-            "ca",
2294
-            "AIS",
2295
-            "TK"
2296
-        ];
2297
-
2298
-        switch ($action) {
2299
-            case "new":
2300
-                $this->objects[$id] = ['t' => 'extGState', 'info' => $options];
2301
-
2302
-                // Tell the pages about the new resource
2303
-                $this->numStates++;
2304
-                $this->o_pages($this->currentNode, 'extGState', ["objNum" => $id, "stateNum" => $this->numStates]);
2305
-                break;
2306
-
2307
-            case "out":
2308
-                $o = &$this->objects[$id];
2309
-                $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
2310
-
2311
-                foreach ($o["info"] as $k => $v) {
2312
-                    if (!in_array($k, $valid_params)) {
2313
-                        continue;
2314
-                    }
2315
-                    $res .= "/$k $v\n";
2316
-                }
2317
-
2318
-                $res .= ">>\nendobj";
2319
-
2320
-                return $res;
2321
-        }
2322
-
2323
-        return null;
2324
-    }
2325
-
2326
-    /**
2327
-     * @param integer $id
2328
-     * @param string $action
2329
-     * @param mixed $options
2330
-     * @return string
2331
-     */
2332
-    protected function o_xobject($id, $action, $options = '')
2333
-    {
2334
-        switch ($action) {
2335
-            case 'new':
2336
-                $this->objects[$id] = ['t' => 'xobject', 'info' => $options, 'c' => ''];
2337
-                break;
2338
-
2339
-            case 'procset':
2340
-                $this->objects[$id]['procset'] = $options;
2341
-                break;
2342
-
2343
-            case 'font':
2344
-                $this->objects[$id]['fonts'][$options['fontNum']] = [
2345
-                  'objNum' => $options['objNum'],
2346
-                  'fontNum' => $options['fontNum']
2347
-                ];
2348
-                break;
2349
-
2350
-            case 'xObject':
2351
-                $this->objects[$id]['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
2352
-                break;
2353
-
2354
-            case 'out':
2355
-                $o = &$this->objects[$id];
2356
-                $res = "\n$id 0 obj\n<< /Type /XObject\n";
2357
-
2358
-                foreach ($o["info"] as $k => $v) {
2359
-                    switch($k)
2360
-                    {
2361
-                        case 'Subtype':
2362
-                            $res .= "/Subtype /$v\n";
2363
-                            break;
2364
-                        case 'bbox':
2365
-                            $res .= "/BBox [";
2366
-                            foreach ($v as $value) {
2367
-                                $res .= sprintf("%.4F ", $value);
2368
-                            }
2369
-                            $res .= "]\n";
2370
-                            break;
2371
-                        default:
2372
-                            $res .= "/$k $v\n";
2373
-                            break;
2374
-                    }
2375
-                }
2376
-                $res .= "/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]\n";
2377
-
2378
-                $res .= "/Resources <<";
2379
-                if (isset($o['procset'])) {
2380
-                    $res .= "\n/ProcSet " . $o['procset'] . " 0 R";
2381
-                } else {
2382
-                    $res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
2383
-                }
2384
-                if (isset($o['fonts']) && count($o['fonts'])) {
2385
-                    $res .= "\n/Font << ";
2386
-                    foreach ($o['fonts'] as $finfo) {
2387
-                        $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
2388
-                    }
2389
-                    $res .= "\n>>";
2390
-                }
2391
-                if (isset($o['xObjects']) && count($o['xObjects'])) {
2392
-                    $res .= "\n/XObject << ";
2393
-                    foreach ($o['xObjects'] as $finfo) {
2394
-                        $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
2395
-                    }
2396
-                    $res .= "\n>>";
2397
-                }
2398
-                $res .= "\n>>\n";
2399
-
2400
-                $tmp = $o["c"];
2401
-                if ($this->compressionReady && $this->options['compression']) {
2402
-                    // then implement ZLIB based compression on this content stream
2403
-                    $res .= " /Filter /FlateDecode\n";
2404
-                    $tmp = gzcompress($tmp, 6);
2405
-                }
2406
-
2407
-                if ($this->encrypted) {
2408
-                    $this->encryptInit($id);
2409
-                    $tmp = $this->ARC4($tmp);
2410
-                }
2411
-
2412
-                $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2413
-                $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";;
2414
-
2415
-                return $res;
2416
-        }
2417
-
2418
-        return null;
2419
-    }
2420
-
2421
-    /**
2422
-     * @param $id
2423
-     * @param $action
2424
-     * @param string $options
2425
-     * @return null|string
2426
-     */
2427
-    protected function o_acroform($id, $action, $options = '')
2428
-    {
2429
-        switch ($action) {
2430
-            case "new":
2431
-                $this->o_catalog($this->catalogId, 'acroform', $id);
2432
-                $this->objects[$id] = array('t' => 'acroform', 'info' => $options);
2433
-                break;
2434
-
2435
-            case 'addfield':
2436
-                $this->objects[$id]['info']['Fields'][] = $options;
2437
-                break;
2438
-
2439
-            case 'font':
2440
-                $this->objects[$id]['fonts'][$options['fontNum']] = [
2441
-                  'objNum' => $options['objNum'],
2442
-                  'fontNum' => $options['fontNum']
2443
-                ];
2444
-                break;
2445
-
2446
-            case "out":
2447
-                $o = &$this->objects[$id];
2448
-                $res = "\n$id 0 obj\n<<";
2449
-
2450
-                foreach ($o["info"] as $k => $v) {
2451
-                    switch($k) {
2452
-                        case 'Fields':
2453
-                            $res .= " /Fields [";
2454
-                            foreach ($v as $i) {
2455
-                                $res .= "$i 0 R ";
2456
-                            }
2457
-                            $res .= "]\n";
2458
-                            break;
2459
-                        default:
2460
-                            $res .= "/$k $v\n";
2461
-                    }
2462
-                }
2463
-
2464
-                $res .= "/DR <<\n";
2465
-                if (isset($o['fonts']) && count($o['fonts'])) {
2466
-                    $res .= "/Font << \n";
2467
-                    foreach ($o['fonts'] as $finfo) {
2468
-                        $res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
2469
-                    }
2470
-                    $res .= ">>\n";
2471
-                }
2472
-                $res .= ">>\n";
2473
-
2474
-                $res .= ">>\nendobj";
2475
-
2476
-                return $res;
2477
-        }
2478
-
2479
-        return null;
2480
-    }
2481
-
2482
-    /**
2483
-     * @param $id
2484
-     * @param $action
2485
-     * @param mixed $options
2486
-     * @return null|string
2487
-     */
2488
-    protected function o_field($id, $action, $options = '')
2489
-    {
2490
-        switch ($action) {
2491
-            case "new":
2492
-                $this->o_page($options['pageid'], 'annot', $id);
2493
-                $this->o_acroform($this->acroFormId, 'addfield', $id);
2494
-                $this->objects[$id] = ['t' => 'field', 'info' => $options];
2495
-                break;
2496
-
2497
-            case 'set':
2498
-                $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2499
-                break;
2500
-
2501
-            case "out":
2502
-                $o = &$this->objects[$id];
2503
-                $res = "\n$id 0 obj\n<< /Type /Annot /Subtype /Widget \n";
2504
-
2505
-                $encrypted = $this->encrypted;
2506
-                if ($encrypted) {
2507
-                    $this->encryptInit($id);
2508
-                }
2509
-
2510
-                foreach ($o["info"] as $k => $v) {
2511
-                    switch ($k) {
2512
-                        case 'pageid':
2513
-                            $res .= "/P $v 0 R\n";
2514
-                            break;
2515
-                        case 'value':
2516
-                            if ($encrypted) {
2517
-                                $v = $this->filterText($this->ARC4($v), false, false);
2518
-                            }
2519
-                            $res .= "/V ($v)\n";
2520
-                            break;
2521
-                        case 'refvalue':
2522
-                            $res .= "/V $v 0 R\n";
2523
-                            break;
2524
-                        case 'da':
2525
-                            if ($encrypted) {
2526
-                                $v = $this->filterText($this->ARC4($v), false, false);
2527
-                            }
2528
-                            $res .= "/DA ($v)\n";
2529
-                            break;
2530
-                        case 'options':
2531
-                            $res .= "/Opt [\n";
2532
-                            foreach ($v as $opt) {
2533
-                                if ($encrypted) {
2534
-                                    $opt = $this->filterText($this->ARC4($opt), false, false);
2535
-                                }
2536
-                                $res .= "($opt)\n";
2537
-                            }
2538
-                            $res .= "]\n";
2539
-                            break;
2540
-                        case 'rect':
2541
-                            $res .= "/Rect [";
2542
-                            foreach ($v as $value) {
2543
-                                $res .= sprintf("%.4F ", $value);
2544
-                            }
2545
-                            $res .= "]\n";
2546
-                            break;
2547
-                        case 'appearance':
2548
-                            $res .= "/AP << ";
2549
-                            foreach ($v as $a => $ref) {
2550
-                                $res .= "/$a $ref 0 R ";
2551
-                            }
2552
-                            $res .= ">>\n";
2553
-                            break;
2554
-                        case 'T':
2555
-                            if($encrypted) {
2556
-                                $v = $this->filterText($this->ARC4($v), false, false);
2557
-                            }
2558
-                            $res .= "/T ($v)\n";
2559
-                            break;
2560
-                        default:
2561
-                            $res .= "/$k $v\n";
2562
-                    }
2563
-
2564
-                }
2565
-
2566
-                $res .= ">>\nendobj";
2567
-
2568
-                return $res;
2569
-        }
2570
-
2571
-        return null;
2572
-    }
2573
-
2574
-    /**
2575
-     *
2576
-     * @param $id
2577
-     * @param $action
2578
-     * @param string $options
2579
-     * @return null|string
2580
-     */
2581
-    protected function o_sig($id, $action, $options = '')
2582
-    {
2583
-        $sign_maxlen = $this->signatureMaxLen;
2584
-
2585
-        switch ($action) {
2586
-            case "new":
2587
-                $this->objects[$id] = array('t' => 'sig', 'info' => $options);
2588
-                $this->byteRange[$id] = ['t' => 'sig'];
2589
-                break;
2590
-
2591
-            case 'byterange':
2592
-                $o = &$this->objects[$id];
2593
-                $content =& $options['content'];
2594
-                $content_len = strlen($content);
2595
-                $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2596
-                $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
2597
-                $rangeStartPos = $pos + $len + 1 + 10; // before '<'
2598
-                $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos ), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2599
-
2600
-                $fuid = uniqid();
2601
-                $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2602
-                $tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
2603
-
2604
-                if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
2605
-                    throw new \Exception("Unable to write temporary file for signing.");
2606
-                }
2607
-                if (file_put_contents($tmpInput, substr($content, $rangeStartPos + 2 + $sign_maxlen),
2608
-                    FILE_APPEND) === false) {
2609
-                    throw new \Exception("Unable to write temporary file for signing.");
2610
-                }
2611
-
2612
-                if (openssl_pkcs7_sign($tmpInput, $tmpOutput,
2613
-                    $o['info']['SignCert'],
2614
-                    array($o['info']['PrivKey'], $o['info']['Password']),
2615
-                    array(), PKCS7_BINARY | PKCS7_DETACHED) === false) {
2616
-                    throw new \Exception("Failed to prepare signature.");
2617
-                }
2618
-
2619
-                $signature = file_get_contents($tmpOutput);
2620
-
2621
-                unlink($tmpInput);
2622
-                unlink($tmpOutput);
2623
-
2624
-                $sign = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
2625
-                list($head, $signature) = explode("\n\n", $sign);
2626
-
2627
-                $signature = base64_decode(trim($signature));
2628
-
2629
-                $signature = current(unpack('H*', $signature));
2630
-                $signature = str_pad($signature, $sign_maxlen, '0');
2631
-                $siglen = strlen($signature);
2632
-                if (strlen($signature) > $sign_maxlen) {
2633
-                    throw new \Exception("Signature length ($siglen) exceeds the $sign_maxlen limit.");
2634
-                }
2635
-
2636
-                $content = substr_replace($content, $signature, $rangeStartPos + 1, $sign_maxlen);
2637
-                break;
2638
-
2639
-            case "out":
2640
-                $res = "\n$id 0 obj\n<<\n";
2641
-
2642
-                $encrypted = $this->encrypted;
2643
-                if ($encrypted) {
2644
-                    $this->encryptInit($id);
2645
-                }
2646
-
2647
-                $res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2648
-                $res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
2649
-                $res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
2650
-                $res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
2651
-
2652
-                $date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
2653
-                if ($encrypted) {
2654
-                    $date = $this->ARC4($date);
2655
-                }
2656
-
2657
-                $res .= "/M ($date)\n";
2658
-                $res .= "/Prop_Build << /App << /Name /DomPDF >> /Filter << /Name /Adobe.PPKLite >> >>\n";
2659
-
2660
-                $o = &$this->objects[$id];
2661
-                foreach ($o['info'] as $k => $v) {
2662
-                    switch($k) {
2663
-                        case 'Name':
2664
-                        case 'Location':
2665
-                        case 'Reason':
2666
-                        case 'ContactInfo':
2667
-                            if ($v !== null && $v !== '') {
2668
-                                $res .= "/$k (" .
2669
-                                  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
2670
-                            }
2671
-                            break;
2672
-                    }
2673
-                }
2674
-                $res .= ">>\nendobj";
2675
-
2676
-                return $res;
2677
-        }
2678
-
2679
-        return null;
2680
-    }
2681
-
2682
-    /**
2683
-     * encryption object.
2684
-     *
2685
-     * @param $id
2686
-     * @param $action
2687
-     * @param string $options
2688
-     * @return string|null
2689
-     */
2690
-    protected function o_encryption($id, $action, $options = '')
2691
-    {
2692
-        switch ($action) {
2693
-            case 'new':
2694
-                // make the new object
2695
-                $this->objects[$id] = ['t' => 'encryption', 'info' => $options];
2696
-                $this->arc4_objnum = $id;
2697
-                break;
2698
-
2699
-            case 'keys':
2700
-                // figure out the additional parameters required
2701
-                $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2702
-                    . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2703
-                    . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2704
-                    . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2705
-
2706
-                $info = $this->objects[$id]['info'];
2707
-
2708
-                $len = mb_strlen($info['owner'], '8bit');
2709
-
2710
-                if ($len > 32) {
2711
-                    $owner = substr($info['owner'], 0, 32);
2712
-                } else {
2713
-                    if ($len < 32) {
2714
-                        $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2715
-                    } else {
2716
-                        $owner = $info['owner'];
2717
-                    }
2718
-                }
2719
-
2720
-                $len = mb_strlen($info['user'], '8bit');
2721
-                if ($len > 32) {
2722
-                    $user = substr($info['user'], 0, 32);
2723
-                } else {
2724
-                    if ($len < 32) {
2725
-                        $user = $info['user'] . substr($pad, 0, 32 - $len);
2726
-                    } else {
2727
-                        $user = $info['user'];
2728
-                    }
2729
-                }
2730
-
2731
-                $tmp = $this->md5_16($owner);
2732
-                $okey = substr($tmp, 0, 5);
2733
-                $this->ARC4_init($okey);
2734
-                $ovalue = $this->ARC4($user);
2735
-                $this->objects[$id]['info']['O'] = $ovalue;
2736
-
2737
-                // now make the u value, phew.
2738
-                $tmp = $this->md5_16(
2739
-                    $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2740
-                );
2741
-
2742
-                $ukey = substr($tmp, 0, 5);
2743
-                $this->ARC4_init($ukey);
2744
-                $this->encryptionKey = $ukey;
2745
-                $this->encrypted = true;
2746
-                $uvalue = $this->ARC4($pad);
2747
-                $this->objects[$id]['info']['U'] = $uvalue;
2748
-                // initialize the arc4 array
2749
-                break;
2750
-
2751
-            case 'out':
2752
-                $o = &$this->objects[$id];
2753
-
2754
-                $res = "\n$id 0 obj\n<<";
2755
-                $res .= "\n/Filter /Standard";
2756
-                $res .= "\n/V 1";
2757
-                $res .= "\n/R 2";
2758
-                $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2759
-                $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2760
-                // and the p-value needs to be converted to account for the twos-complement approach
2761
-                $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2762
-                $res .= "\n/P " . ($o['info']['p']);
2763
-                $res .= "\n>>\nendobj";
2764
-
2765
-                return $res;
2766
-        }
2767
-
2768
-        return null;
2769
-    }
2770
-
2771
-    protected function o_indirect_references($id, $action, $options = null)
2772
-    {
2773
-        switch ($action) {
2774
-            case 'new':
2775
-            case 'add':
2776
-                if ($id === 0) {
2777
-                    $id = ++$this->numObj;
2778
-                    $this->o_catalog($this->catalogId, 'names', $id);
2779
-                    $this->objects[$id] = ['t' => 'indirect_references', 'info' => $options];
2780
-                    $this->indirectReferenceId = $id;
2781
-                } else {
2782
-                    $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2783
-                }
2784
-                break;
2785
-            case 'out':
2786
-                $res = "\n$id 0 obj << ";
2787
-
2788
-                foreach($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2789
-                    $res .= "/$referenceObjName $referenceObjId 0 R ";
2790
-                }
2791
-
2792
-                $res .= ">> endobj";
2793
-                return $res;
2794
-        }
2795
-
2796
-        return null;
2797
-    }
2798
-
2799
-    protected function o_names($id, $action, $options = null)
2800
-    {
2801
-        switch ($action) {
2802
-            case 'new':
2803
-            case 'add':
2804
-                if ($id === 0) {
2805
-                    $id = ++$this->numObj;
2806
-                    $this->objects[$id] = ['t' => 'names', 'info' => [$options]];
2807
-                    $this->o_indirect_references($this->indirectReferenceId, 'add', ['EmbeddedFiles' => $id]);
2808
-                    $this->embeddedFilesId = $id;
2809
-                } else {
2810
-                    $this->objects[$id]['info'][] = $options;
2811
-                }
2812
-                break;
2813
-            case 'out':
2814
-                $info = &$this->objects[$id]['info'];
2815
-                $res = '';
2816
-                if (count($info) > 0) {
2817
-                    $res = "\n$id 0 obj << /Names [ ";
2818
-
2819
-                    if ($this->encrypted) {
2820
-                        $this->encryptInit($id);
2821
-                    }
2822
-
2823
-                    foreach ($info as $entry) {
2824
-                        if ($this->encrypted) {
2825
-                            $filename = $this->ARC4($entry['filename']);
2826
-                        } else {
2827
-                            $filename = $entry['filename'];
2828
-                        }
2829
-
2830
-                        $res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
2831
-                    }
2832
-
2833
-                    $res .= "] >> endobj";
2834
-                }
2835
-                return $res;
2836
-        }
2837
-
2838
-        return null;
2839
-    }
2840
-
2841
-    protected function o_embedded_file_dictionary($id, $action, $options = null)
2842
-    {
2843
-        switch ($action) {
2844
-            case 'new':
2845
-                $embeddedFileId = ++$this->numObj;
2846
-                $options['embedded_reference'] = $embeddedFileId;
2847
-                $this->objects[$id] = ['t' => 'embedded_file_dictionary', 'info' => $options];
2848
-                $this->o_embedded_file($embeddedFileId, 'new', $options);
2849
-                $options['dict_reference'] = $id;
2850
-                $this->o_names($this->embeddedFilesId, 'add', $options);
2851
-                break;
2852
-            case 'out':
2853
-                $info = &$this->objects[$id]['info'];
2854
-
2855
-                if ($this->encrypted) {
2856
-                    $this->encryptInit($id);
2857
-                    $filename = $this->ARC4($info['filename']);
2858
-                    $description = $this->ARC4($info['description']);
2859
-                } else {
2860
-                    $filename = $info['filename'];
2861
-                    $description = $info['description'];
2862
-                }
2863
-
2864
-                $res = "\n$id 0 obj <</Type /Filespec /EF";
2865
-                $res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
2866
-                $res .= " /F ($filename) /UF ($filename) /Desc ($description)";
2867
-                $res .= " >> endobj";
2868
-                return $res;
2869
-        }
2870
-
2871
-        return null;
2872
-    }
2873
-
2874
-    protected function o_embedded_file($id, $action, $options = null): ?string
2875
-    {
2876
-        switch ($action) {
2877
-            case 'new':
2878
-                $this->objects[$id] = ['t' => 'embedded_file', 'info' => $options];
2879
-                break;
2880
-            case 'out':
2881
-                $info = &$this->objects[$id]['info'];
2882
-
2883
-                if ($this->compressionReady) {
2884
-                    $filepath = $info['filepath'];
2885
-                    $checksum = md5_file($filepath);
2886
-                    $f = fopen($filepath, "rb");
2887
-
2888
-                    $file_content_compressed = '';
2889
-                    $deflateContext = deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => 6]);
2890
-                    while (($block = fread($f, 8192))) {
2891
-                        $file_content_compressed .= deflate_add($deflateContext, $block, ZLIB_NO_FLUSH);
2892
-                    }
2893
-                    $file_content_compressed .= deflate_add($deflateContext, '', ZLIB_FINISH);
2894
-                    $file_size_uncompressed = ftell($f);
2895
-                    fclose($f);
2896
-                } else {
2897
-                    $file_content = file_get_contents($info['filepath']);
2898
-                    $file_size_uncompressed = mb_strlen($file_content, '8bit');
2899
-                    $checksum = md5($file_content);
2900
-                }
2901
-
2902
-                if ($this->encrypted) {
2903
-                    $this->encryptInit($id);
2904
-                    $checksum = $this->ARC4($checksum);
2905
-                    $file_content_compressed = $this->ARC4($file_content_compressed);
2906
-                }
2907
-                $file_size_compressed = mb_strlen($file_content_compressed, '8bit');
2908
-
2909
-                $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
2910
-                    " /Type/EmbeddedFile /Filter/FlateDecode" .
2911
-                    " /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
2912
-
2913
-                return $res;
2914
-        }
2915
-
2916
-        return null;
2917
-    }
2918
-
2919
-    /**
2920
-     * ARC4 functions
2921
-     * A series of function to implement ARC4 encoding in PHP
2922
-     */
2923
-
2924
-    /**
2925
-     * calculate the 16 byte version of the 128 bit md5 digest of the string
2926
-     *
2927
-     * @param $string
2928
-     * @return string
2929
-     */
2930
-    function md5_16($string)
2931
-    {
2932
-        $tmp = md5($string);
2933
-        $out = '';
2934
-        for ($i = 0; $i <= 30; $i = $i + 2) {
2935
-            $out .= chr(hexdec(substr($tmp, $i, 2)));
2936
-        }
2937
-
2938
-        return $out;
2939
-    }
2940
-
2941
-    /**
2942
-     * initialize the encryption for processing a particular object
2943
-     *
2944
-     * @param $id
2945
-     */
2946
-    function encryptInit($id)
2947
-    {
2948
-        $tmp = $this->encryptionKey;
2949
-        $hex = dechex($id);
2950
-        if (mb_strlen($hex, '8bit') < 6) {
2951
-            $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2952
-        }
2953
-        $tmp .= chr(hexdec(substr($hex, 4, 2)))
2954
-            . chr(hexdec(substr($hex, 2, 2)))
2955
-            . chr(hexdec(substr($hex, 0, 2)))
2956
-            . chr(0)
2957
-            . chr(0)
2958
-        ;
2959
-        $key = $this->md5_16($tmp);
2960
-        $this->ARC4_init(substr($key, 0, 10));
2961
-    }
2962
-
2963
-    /**
2964
-     * initialize the ARC4 encryption
2965
-     *
2966
-     * @param string $key
2967
-     */
2968
-    function ARC4_init($key = '')
2969
-    {
2970
-        $this->arc4 = '';
2971
-
2972
-        // setup the control array
2973
-        if (mb_strlen($key, '8bit') == 0) {
2974
-            return;
2975
-        }
2976
-
2977
-        $k = '';
2978
-        while (mb_strlen($k, '8bit') < 256) {
2979
-            $k .= $key;
2980
-        }
2981
-
2982
-        $k = substr($k, 0, 256);
2983
-        for ($i = 0; $i < 256; $i++) {
2984
-            $this->arc4 .= chr($i);
2985
-        }
2986
-
2987
-        $j = 0;
2988
-
2989
-        for ($i = 0; $i < 256; $i++) {
2990
-            $t = $this->arc4[$i];
2991
-            $j = ($j + ord($t) + ord($k[$i])) % 256;
2992
-            $this->arc4[$i] = $this->arc4[$j];
2993
-            $this->arc4[$j] = $t;
2994
-        }
2995
-    }
2996
-
2997
-    /**
2998
-     * ARC4 encrypt a text string
2999
-     *
3000
-     * @param $text
3001
-     * @return string
3002
-     */
3003
-    function ARC4($text)
3004
-    {
3005
-        $len = mb_strlen($text, '8bit');
3006
-        $a = 0;
3007
-        $b = 0;
3008
-        $c = $this->arc4;
3009
-        $out = '';
3010
-        for ($i = 0; $i < $len; $i++) {
3011
-            $a = ($a + 1) % 256;
3012
-            $t = $c[$a];
3013
-            $b = ($b + ord($t)) % 256;
3014
-            $c[$a] = $c[$b];
3015
-            $c[$b] = $t;
3016
-            $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
3017
-            $out .= chr(ord($text[$i]) ^ $k);
3018
-        }
3019
-
3020
-        return $out;
3021
-    }
3022
-
3023
-    /**
3024
-     * functions which can be called to adjust or add to the document
3025
-     */
3026
-
3027
-    /**
3028
-     * add a link in the document to an external URL
3029
-     *
3030
-     * @param $url
3031
-     * @param $x0
3032
-     * @param $y0
3033
-     * @param $x1
3034
-     * @param $y1
3035
-     */
3036
-    function addLink($url, $x0, $y0, $x1, $y1)
3037
-    {
3038
-        $this->numObj++;
3039
-        $info = ['type' => 'link', 'url' => $url, 'rect' => [$x0, $y0, $x1, $y1]];
3040
-        $this->o_annotation($this->numObj, 'new', $info);
3041
-    }
3042
-
3043
-    /**
3044
-     * add a link in the document to an internal destination (ie. within the document)
3045
-     *
3046
-     * @param $label
3047
-     * @param $x0
3048
-     * @param $y0
3049
-     * @param $x1
3050
-     * @param $y1
3051
-     */
3052
-    function addInternalLink($label, $x0, $y0, $x1, $y1)
3053
-    {
3054
-        $this->numObj++;
3055
-        $info = ['type' => 'ilink', 'label' => $label, 'rect' => [$x0, $y0, $x1, $y1]];
3056
-        $this->o_annotation($this->numObj, 'new', $info);
3057
-    }
3058
-
3059
-    /**
3060
-     * set the encryption of the document
3061
-     * can be used to turn it on and/or set the passwords which it will have.
3062
-     * also the functions that the user will have are set here, such as print, modify, add
3063
-     *
3064
-     * @param string $userPass
3065
-     * @param string $ownerPass
3066
-     * @param array $pc
3067
-     */
3068
-    function setEncryption($userPass = '', $ownerPass = '', $pc = [])
3069
-    {
3070
-        $p = bindec("11000000");
3071
-
3072
-        $options = ['print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32];
3073
-
3074
-        foreach ($pc as $k => $v) {
3075
-            if ($v && isset($options[$k])) {
3076
-                $p += $options[$k];
3077
-            } else {
3078
-                if (isset($options[$v])) {
3079
-                    $p += $options[$v];
3080
-                }
3081
-            }
3082
-        }
3083
-
3084
-        // implement encryption on the document
3085
-        if ($this->arc4_objnum == 0) {
3086
-            // then the block does not exist already, add it.
3087
-            $this->numObj++;
3088
-            if (mb_strlen($ownerPass) == 0) {
3089
-                $ownerPass = $userPass;
3090
-            }
3091
-
3092
-            $this->o_encryption($this->numObj, 'new', ['user' => $userPass, 'owner' => $ownerPass, 'p' => $p]);
3093
-        }
3094
-    }
3095
-
3096
-    /**
3097
-     * should be used for internal checks, not implemented as yet
3098
-     */
3099
-    function checkAllHere()
3100
-    {
3101
-    }
3102
-
3103
-    /**
3104
-     * return the pdf stream as a string returned from the function
3105
-     *
3106
-     * @param bool $debug
3107
-     * @return string
3108
-     */
3109
-    function output($debug = false)
3110
-    {
3111
-        if ($debug) {
3112
-            // turn compression off
3113
-            $this->options['compression'] = false;
3114
-        }
3115
-
3116
-        if ($this->javascript) {
3117
-            $this->numObj++;
3118
-
3119
-            $js_id = $this->numObj;
3120
-            $this->o_embedjs($js_id, 'new');
3121
-            $this->o_javascript(++$this->numObj, 'new', $this->javascript);
3122
-
3123
-            $id = $this->catalogId;
3124
-
3125
-            $this->o_indirect_references($this->indirectReferenceId, 'add', ['JavaScript' => $js_id]);
3126
-        }
3127
-
3128
-        if ($this->fileIdentifier === '') {
3129
-            $tmp = implode('', $this->objects[$this->infoObject]['info']);
3130
-            $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
3131
-        }
3132
-
3133
-        if ($this->arc4_objnum) {
3134
-            $this->o_encryption($this->arc4_objnum, 'keys');
3135
-            $this->ARC4_init($this->encryptionKey);
3136
-        }
3137
-
3138
-        $this->checkAllHere();
3139
-
3140
-        $xref = [];
3141
-        $content = '%PDF-' . self::PDF_VERSION;
3142
-        $pos = mb_strlen($content, '8bit');
3143
-
3144
-        // pre-process o_font objects before output of all objects
3145
-        foreach ($this->objects as $k => $v) {
3146
-            if ($v['t'] === 'font') {
3147
-                $this->o_font($k, 'add');
3148
-            }
3149
-        }
3150
-
3151
-        foreach ($this->objects as $k => $v) {
3152
-            $tmp = 'o_' . $v['t'];
3153
-            $cont = $this->$tmp($k, 'out');
3154
-            $content .= $cont;
3155
-            $xref[] = $pos + 1; //+1 to account for \n at the start of each object
3156
-            $pos += mb_strlen($cont, '8bit');
3157
-        }
3158
-
3159
-        $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
3160
-
3161
-        foreach ($xref as $p) {
3162
-            $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
3163
-        }
3164
-
3165
-        $content .= "trailer\n<<\n" .
3166
-            '/Size ' . (count($xref) + 1) . "\n" .
3167
-            '/Root 1 0 R' . "\n" .
3168
-            '/Info ' . $this->infoObject . " 0 R\n"
3169
-        ;
3170
-
3171
-        // if encryption has been applied to this document then add the marker for this dictionary
3172
-        if ($this->arc4_objnum > 0) {
3173
-            $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
3174
-        }
3175
-
3176
-        $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
3177
-
3178
-        // account for \n added at start of xref table
3179
-        $pos++;
3180
-
3181
-        $content .= ">>\nstartxref\n$pos\n%%EOF\n";
3182
-
3183
-        if (count($this->byteRange) > 0) {
3184
-            foreach ($this->byteRange as $k => $v) {
3185
-                $tmp = 'o_' . $v['t'];
3186
-                $this->$tmp($k, 'byterange', ['content' => &$content]);
3187
-            }
3188
-        }
3189
-
3190
-        return $content;
3191
-    }
3192
-
3193
-    /**
3194
-     * initialize a new document
3195
-     * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
3196
-     * this function is called automatically by the constructor function
3197
-     *
3198
-     * @param array $pageSize
3199
-     */
3200
-    private function newDocument($pageSize = [0, 0, 612, 792])
3201
-    {
3202
-        $this->numObj = 0;
3203
-        $this->objects = [];
3204
-
3205
-        $this->numObj++;
3206
-        $this->o_catalog($this->numObj, 'new');
3207
-
3208
-        $this->numObj++;
3209
-        $this->o_outlines($this->numObj, 'new');
3210
-
3211
-        $this->numObj++;
3212
-        $this->o_pages($this->numObj, 'new');
3213
-
3214
-        $this->o_pages($this->numObj, 'mediaBox', $pageSize);
3215
-        $this->currentNode = 3;
3216
-
3217
-        $this->numObj++;
3218
-        $this->o_procset($this->numObj, 'new');
3219
-
3220
-        $this->numObj++;
3221
-        $this->o_info($this->numObj, 'new');
3222
-
3223
-        $this->numObj++;
3224
-        $this->o_page($this->numObj, 'new');
3225
-
3226
-        // need to store the first page id as there is no way to get it to the user during
3227
-        // startup
3228
-        $this->firstPageId = $this->currentContents;
3229
-    }
3230
-
3231
-    /**
3232
-     * open the font file and return a php structure containing it.
3233
-     * first check if this one has been done before and saved in a form more suited to php
3234
-     * note that if a php serialized version does not exist it will try and make one, but will
3235
-     * require write access to the directory to do it... it is MUCH faster to have these serialized
3236
-     * files.
3237
-     *
3238
-     * @param $font
3239
-     */
3240
-    private function openFont($font)
3241
-    {
3242
-        // assume that $font contains the path and file but not the extension
3243
-        $name = basename($font);
3244
-        $dir = dirname($font) . '/';
3245
-
3246
-        $fontcache = $this->fontcache;
3247
-        if ($fontcache == '') {
3248
-            $fontcache = rtrim($dir, DIRECTORY_SEPARATOR."/\\");
3249
-        }
3250
-
3251
-        //$name       filename without folder and extension of font metrics
3252
-        //$dir        folder of font metrics
3253
-        //$fontcache  folder of runtime created php serialized version of font metrics.
3254
-        //            If this is not given, the same folder as the font metrics will be used.
3255
-        //            Storing and reusing serialized versions improves speed much
3256
-
3257
-        $this->addMessage("openFont: $font - $name");
3258
-
3259
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3260
-            $metrics_name = "$name.afm";
3261
-        } else {
3262
-            $metrics_name = "$name.ufm";
3263
-        }
3264
-
3265
-        $cache_name = "$metrics_name.php";
3266
-        $this->addMessage("metrics: $metrics_name, cache: $cache_name");
3267
-
3268
-        if (file_exists($fontcache . '/' . $cache_name)) {
3269
-            $this->addMessage("openFont: php file exists $fontcache/$cache_name");
3270
-            $this->fonts[$font] = require($fontcache . '/' . $cache_name);
3271
-
3272
-            if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
3273
-                // if the font file is old, then clear it out and prepare for re-creation
3274
-                $this->addMessage('openFont: clear out, make way for new version.');
3275
-                $this->fonts[$font] = null;
3276
-                unset($this->fonts[$font]);
3277
-            }
3278
-        } else {
3279
-            $old_cache_name = "php_$metrics_name";
3280
-            if (file_exists($fontcache . '/' . $old_cache_name)) {
3281
-                $this->addMessage(
3282
-                    "openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format"
3283
-                );
3284
-                $old_cache = file_get_contents($fontcache . '/' . $old_cache_name);
3285
-                file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';');
3286
-
3287
-                $this->openFont($font);
3288
-                return;
3289
-            }
3290
-        }
3291
-
3292
-        if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) {
3293
-            // then rebuild the php_<font>.afm file from the <font>.afm file
3294
-            $this->addMessage("openFont: build php file from $dir$metrics_name");
3295
-            $data = [];
3296
-
3297
-            // 20 => 'space'
3298
-            $data['codeToName'] = [];
3299
-
3300
-            // Since we're not going to enable Unicode for the core fonts we need to use a font-based
3301
-            // setting for Unicode support rather than a global setting.
3302
-            $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
3303
-
3304
-            $cidtogid = '';
3305
-            if ($data['isUnicode']) {
3306
-                $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
3307
-            }
3308
-
3309
-            $file = file($dir . $metrics_name);
3310
-
3311
-            foreach ($file as $rowA) {
3312
-                $row = trim($rowA);
3313
-                $pos = strpos($row, ' ');
3314
-
3315
-                if ($pos) {
3316
-                    // then there must be some keyword
3317
-                    $key = substr($row, 0, $pos);
3318
-                    switch ($key) {
3319
-                        case 'FontName':
3320
-                        case 'FullName':
3321
-                        case 'FamilyName':
3322
-                        case 'PostScriptName':
3323
-                        case 'Weight':
3324
-                        case 'ItalicAngle':
3325
-                        case 'IsFixedPitch':
3326
-                        case 'CharacterSet':
3327
-                        case 'UnderlinePosition':
3328
-                        case 'UnderlineThickness':
3329
-                        case 'Version':
3330
-                        case 'EncodingScheme':
3331
-                        case 'CapHeight':
3332
-                        case 'XHeight':
3333
-                        case 'Ascender':
3334
-                        case 'Descender':
3335
-                        case 'StdHW':
3336
-                        case 'StdVW':
3337
-                        case 'StartCharMetrics':
3338
-                        case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font.  Otherwise it's too big.
3339
-                            $data[$key] = trim(substr($row, $pos));
3340
-                            break;
3341
-
3342
-                        case 'FontBBox':
3343
-                            $data[$key] = explode(' ', trim(substr($row, $pos)));
3344
-                            break;
3345
-
3346
-                        //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
3347
-                        case 'C': // Found in AFM files
3348
-                            $bits = explode(';', trim($row));
3349
-                            $dtmp = ['C' => null, 'N' => null, 'WX' => null, 'B' => []];
3350
-
3351
-                            foreach ($bits as $bit) {
3352
-                                $bits2 = explode(' ', trim($bit));
3353
-                                if (mb_strlen($bits2[0], '8bit') == 0) {
3354
-                                    continue;
3355
-                                }
3356
-
3357
-                                if (count($bits2) > 2) {
3358
-                                    $dtmp[$bits2[0]] = [];
3359
-                                    for ($i = 1; $i < count($bits2); $i++) {
3360
-                                        $dtmp[$bits2[0]][] = $bits2[$i];
3361
-                                    }
3362
-                                } else {
3363
-                                    if (count($bits2) == 2) {
3364
-                                        $dtmp[$bits2[0]] = $bits2[1];
3365
-                                    }
3366
-                                }
3367
-                            }
3368
-
3369
-                            $c = (int)$dtmp['C'];
3370
-                            $n = $dtmp['N'];
3371
-                            $width = floatval($dtmp['WX']);
3372
-
3373
-                            if ($c >= 0) {
3374
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3375
-                                    $data['codeToName'][$c] = $n;
3376
-                                }
3377
-                                $data['C'][$c] = $width;
3378
-                            } elseif (isset($n)) {
3379
-                                $data['C'][$n] = $width;
3380
-                            }
3381
-
3382
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3383
-                                $data['MissingWidth'] = $width;
3384
-                            }
3385
-
3386
-                            break;
3387
-
3388
-                        // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
3389
-                        case 'U': // Found in UFM files
3390
-                            if (!$data['isUnicode']) {
3391
-                                break;
3392
-                            }
3393
-
3394
-                            $bits = explode(';', trim($row));
3395
-                            $dtmp = ['G' => null, 'N' => null, 'U' => null, 'WX' => null];
3396
-
3397
-                            foreach ($bits as $bit) {
3398
-                                $bits2 = explode(' ', trim($bit));
3399
-                                if (mb_strlen($bits2[0], '8bit') === 0) {
3400
-                                    continue;
3401
-                                }
3402
-
3403
-                                if (count($bits2) > 2) {
3404
-                                    $dtmp[$bits2[0]] = [];
3405
-                                    for ($i = 1; $i < count($bits2); $i++) {
3406
-                                        $dtmp[$bits2[0]][] = $bits2[$i];
3407
-                                    }
3408
-                                } else {
3409
-                                    if (count($bits2) == 2) {
3410
-                                        $dtmp[$bits2[0]] = $bits2[1];
3411
-                                    }
3412
-                                }
3413
-                            }
3414
-
3415
-                            $c = (int)$dtmp['U'];
3416
-                            $n = $dtmp['N'];
3417
-                            $glyph = $dtmp['G'];
3418
-                            $width = floatval($dtmp['WX']);
3419
-
3420
-                            if ($c >= 0) {
3421
-                                // Set values in CID to GID map
3422
-                                if ($c >= 0 && $c < 0xFFFF && $glyph) {
3423
-                                    $cidtogid[$c * 2] = chr($glyph >> 8);
3424
-                                    $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
3425
-                                }
3426
-
3427
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3428
-                                    $data['codeToName'][$c] = $n;
3429
-                                }
3430
-                                $data['C'][$c] = $width;
3431
-                            } elseif (isset($n)) {
3432
-                                $data['C'][$n] = $width;
3433
-                            }
3434
-
3435
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3436
-                                $data['MissingWidth'] = $width;
3437
-                            }
3438
-
3439
-                            break;
3440
-
3441
-                        case 'KPX':
3442
-                            break; // don't include them as they are not used yet
3443
-                            //KPX Adieresis yacute -40
3444
-                            /*$bits = explode(' ', trim($row));
1315
+				$res = "\n$id 0 obj\n";
1316
+				$res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1317
+				$res .= "stream\n" . $stream . "\nendstream" . "\nendobj";;
1318
+
1319
+				return $res;
1320
+		}
1321
+
1322
+		return null;
1323
+	}
1324
+
1325
+	/**
1326
+	 * a font descriptor, needed for including additional fonts
1327
+	 *
1328
+	 * @param $id
1329
+	 * @param $action
1330
+	 * @param string $options
1331
+	 * @return null|string
1332
+	 */
1333
+	protected function o_fontDescriptor($id, $action, $options = '')
1334
+	{
1335
+		if ($action !== 'new') {
1336
+			$o = &$this->objects[$id];
1337
+		}
1338
+
1339
+		switch ($action) {
1340
+			case 'new':
1341
+				$this->objects[$id] = ['t' => 'fontDescriptor', 'info' => $options];
1342
+				break;
1343
+
1344
+			case 'out':
1345
+				$res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1346
+				foreach ($o['info'] as $label => $value) {
1347
+					switch ($label) {
1348
+						case 'Ascent':
1349
+						case 'CapHeight':
1350
+						case 'Descent':
1351
+						case 'Flags':
1352
+						case 'ItalicAngle':
1353
+						case 'StemV':
1354
+						case 'AvgWidth':
1355
+						case 'Leading':
1356
+						case 'MaxWidth':
1357
+						case 'MissingWidth':
1358
+						case 'StemH':
1359
+						case 'XHeight':
1360
+						case 'CharSet':
1361
+							if (mb_strlen($value, '8bit')) {
1362
+								$res .= "/$label $value\n";
1363
+							}
1364
+
1365
+							break;
1366
+						case 'FontFile':
1367
+						case 'FontFile2':
1368
+						case 'FontFile3':
1369
+							$res .= "/$label $value 0 R\n";
1370
+							break;
1371
+
1372
+						case 'FontBBox':
1373
+							$res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1374
+							break;
1375
+
1376
+						case 'FontName':
1377
+							$res .= "/$label /$value\n";
1378
+							break;
1379
+					}
1380
+				}
1381
+
1382
+				$res .= ">>\nendobj";
1383
+
1384
+				return $res;
1385
+		}
1386
+
1387
+		return null;
1388
+	}
1389
+
1390
+	/**
1391
+	 * the font encoding
1392
+	 *
1393
+	 * @param $id
1394
+	 * @param $action
1395
+	 * @param string $options
1396
+	 * @return null|string
1397
+	 */
1398
+	protected function o_fontEncoding($id, $action, $options = '')
1399
+	{
1400
+		if ($action !== 'new') {
1401
+			$o = &$this->objects[$id];
1402
+		}
1403
+
1404
+		switch ($action) {
1405
+			case 'new':
1406
+				// the options array should contain 'differences' and maybe 'encoding'
1407
+				$this->objects[$id] = ['t' => 'fontEncoding', 'info' => $options];
1408
+				break;
1409
+
1410
+			case 'out':
1411
+				$res = "\n$id 0 obj\n<< /Type /Encoding\n";
1412
+				if (!isset($o['info']['encoding'])) {
1413
+					$o['info']['encoding'] = 'WinAnsiEncoding';
1414
+				}
1415
+
1416
+				if ($o['info']['encoding'] !== 'none') {
1417
+					$res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1418
+				}
1419
+
1420
+				$res .= "/Differences \n[";
1421
+
1422
+				$onum = -100;
1423
+
1424
+				foreach ($o['info']['differences'] as $num => $label) {
1425
+					if ($num != $onum + 1) {
1426
+						// we cannot make use of consecutive numbering
1427
+						$res .= "\n$num /$label";
1428
+					} else {
1429
+						$res .= " /$label";
1430
+					}
1431
+
1432
+					$onum = $num;
1433
+				}
1434
+
1435
+				$res .= "\n]\n>>\nendobj";
1436
+
1437
+				return $res;
1438
+		}
1439
+
1440
+		return null;
1441
+	}
1442
+
1443
+	/**
1444
+	 * a descendent cid font, needed for unicode fonts
1445
+	 *
1446
+	 * @param $id
1447
+	 * @param $action
1448
+	 * @param string|array $options
1449
+	 * @return null|string
1450
+	 */
1451
+	protected function o_fontDescendentCID($id, $action, $options = '')
1452
+	{
1453
+		if ($action !== 'new') {
1454
+			$o = &$this->objects[$id];
1455
+		}
1456
+
1457
+		switch ($action) {
1458
+			case 'new':
1459
+				$this->objects[$id] = ['t' => 'fontDescendentCID', 'info' => $options];
1460
+
1461
+				// we need a CID system info section
1462
+				$cidSystemInfoId = ++$this->numObj;
1463
+				$this->o_cidSystemInfo($cidSystemInfoId, 'new');
1464
+				$this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1465
+
1466
+				// and a CID to GID map
1467
+				$cidToGidMapId = ++$this->numObj;
1468
+				$this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1469
+				$this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1470
+				break;
1471
+
1472
+			case 'add':
1473
+				foreach ($options as $k => $v) {
1474
+					switch ($k) {
1475
+						case 'BaseFont':
1476
+							$o['info']['name'] = $v;
1477
+							break;
1478
+
1479
+						case 'FirstChar':
1480
+						case 'LastChar':
1481
+						case 'MissingWidth':
1482
+						case 'FontDescriptor':
1483
+						case 'SubType':
1484
+							$this->addMessage("o_fontDescendentCID $k : $v");
1485
+							$o['info'][$k] = $v;
1486
+							break;
1487
+					}
1488
+				}
1489
+
1490
+				// pass values down to cid to gid map
1491
+				$this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1492
+				break;
1493
+
1494
+			case 'out':
1495
+				$res = "\n$id 0 obj\n";
1496
+				$res .= "<</Type /Font\n";
1497
+				$res .= "/Subtype /CIDFontType2\n";
1498
+				$res .= "/BaseFont /" . $o['info']['name'] . "\n";
1499
+				$res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1500
+				//      if (isset($o['info']['FirstChar'])) {
1501
+				//        $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1502
+				//      }
1503
+
1504
+				//      if (isset($o['info']['LastChar'])) {
1505
+				//        $res.= "/LastChar ".$o['info']['LastChar']."\n";
1506
+				//      }
1507
+				if (isset($o['info']['FontDescriptor'])) {
1508
+					$res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1509
+				}
1510
+
1511
+				if (isset($o['info']['MissingWidth'])) {
1512
+					$res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1513
+				}
1514
+
1515
+				if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1516
+					$cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1517
+					$w = '';
1518
+					foreach ($cid_widths as $cid => $width) {
1519
+						$w .= "$cid [$width] ";
1520
+					}
1521
+					$res .= "/W [$w]\n";
1522
+				}
1523
+
1524
+				$res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1525
+				$res .= ">>\n";
1526
+				$res .= "endobj";
1527
+
1528
+				return $res;
1529
+		}
1530
+
1531
+		return null;
1532
+	}
1533
+
1534
+	/**
1535
+	 * CID system info section, needed for unicode fonts
1536
+	 *
1537
+	 * @param $id
1538
+	 * @param $action
1539
+	 * @return null|string
1540
+	 */
1541
+	protected function o_cidSystemInfo($id, $action)
1542
+	{
1543
+		switch ($action) {
1544
+			case 'new':
1545
+				$this->objects[$id] = [
1546
+					't' => 'cidSystemInfo'
1547
+				];
1548
+				break;
1549
+			case 'add':
1550
+				break;
1551
+			case 'out':
1552
+				$ordering = 'UCS';
1553
+				$registry = 'Adobe';
1554
+
1555
+				if ($this->encrypted) {
1556
+					$this->encryptInit($id);
1557
+					$ordering = $this->ARC4($ordering);
1558
+					$registry = $this->ARC4($registry);
1559
+				}
1560
+
1561
+
1562
+				$res = "\n$id 0 obj\n";
1563
+
1564
+				$res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
1565
+				$res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
1566
+				$res .= "/Supplement 0\n"; // The supplement number of the character collection.
1567
+				$res .= ">>";
1568
+
1569
+				$res .= "\nendobj";
1570
+
1571
+				return $res;
1572
+		}
1573
+
1574
+		return null;
1575
+	}
1576
+
1577
+	/**
1578
+	 * a font glyph to character map, needed for unicode fonts
1579
+	 *
1580
+	 * @param $id
1581
+	 * @param $action
1582
+	 * @param string $options
1583
+	 * @return null|string
1584
+	 */
1585
+	protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1586
+	{
1587
+		if ($action !== 'new') {
1588
+			$o = &$this->objects[$id];
1589
+		}
1590
+
1591
+		switch ($action) {
1592
+			case 'new':
1593
+				$this->objects[$id] = ['t' => 'fontGIDtoCIDMap', 'info' => $options];
1594
+				break;
1595
+
1596
+			case 'out':
1597
+				$res = "\n$id 0 obj\n";
1598
+				$fontFileName = $o['info']['fontFileName'];
1599
+				$tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1600
+
1601
+				$compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1602
+					$this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1603
+
1604
+				if (!$compressed && isset($o['raw'])) {
1605
+					$res .= $tmp;
1606
+				} else {
1607
+					$res .= "<<";
1608
+
1609
+					if (!$compressed && $this->compressionReady && $this->options['compression']) {
1610
+						// then implement ZLIB based compression on this content stream
1611
+						$compressed = true;
1612
+						$tmp = gzcompress($tmp, 6);
1613
+					}
1614
+					if ($compressed) {
1615
+						$res .= "\n/Filter /FlateDecode";
1616
+					}
1617
+
1618
+					if ($this->encrypted) {
1619
+						$this->encryptInit($id);
1620
+						$tmp = $this->ARC4($tmp);
1621
+					}
1622
+
1623
+					$res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1624
+				}
1625
+
1626
+				$res .= "\nendobj";
1627
+
1628
+				return $res;
1629
+		}
1630
+
1631
+		return null;
1632
+	}
1633
+
1634
+	/**
1635
+	 * the document procset, solves some problems with printing to old PS printers
1636
+	 *
1637
+	 * @param $id
1638
+	 * @param $action
1639
+	 * @param string $options
1640
+	 * @return null|string
1641
+	 */
1642
+	protected function o_procset($id, $action, $options = '')
1643
+	{
1644
+		if ($action !== 'new') {
1645
+			$o = &$this->objects[$id];
1646
+		}
1647
+
1648
+		switch ($action) {
1649
+			case 'new':
1650
+				$this->objects[$id] = ['t' => 'procset', 'info' => ['PDF' => 1, 'Text' => 1]];
1651
+				$this->o_pages($this->currentNode, 'procset', $id);
1652
+				$this->procsetObjectId = $id;
1653
+				break;
1654
+
1655
+			case 'add':
1656
+				// this is to add new items to the procset list, despite the fact that this is considered
1657
+				// obsolete, the items are required for printing to some postscript printers
1658
+				switch ($options) {
1659
+					case 'ImageB':
1660
+					case 'ImageC':
1661
+					case 'ImageI':
1662
+						$o['info'][$options] = 1;
1663
+						break;
1664
+				}
1665
+				break;
1666
+
1667
+			case 'out':
1668
+				$res = "\n$id 0 obj\n[";
1669
+				foreach ($o['info'] as $label => $val) {
1670
+					$res .= "/$label ";
1671
+				}
1672
+				$res .= "]\nendobj";
1673
+
1674
+				return $res;
1675
+		}
1676
+
1677
+		return null;
1678
+	}
1679
+
1680
+	/**
1681
+	 * define the document information
1682
+	 *
1683
+	 * @param $id
1684
+	 * @param $action
1685
+	 * @param string $options
1686
+	 * @return null|string
1687
+	 */
1688
+	protected function o_info($id, $action, $options = '')
1689
+	{
1690
+		switch ($action) {
1691
+			case 'new':
1692
+				$this->infoObject = $id;
1693
+				$date = 'D:' . @date('Ymd');
1694
+				$this->objects[$id] = [
1695
+					't'    => 'info',
1696
+					'info' => [
1697
+						'Producer'      => 'CPDF (dompdf)',
1698
+						'CreationDate' => $date
1699
+					]
1700
+				];
1701
+				break;
1702
+			case 'Title':
1703
+			case 'Author':
1704
+			case 'Subject':
1705
+			case 'Keywords':
1706
+			case 'Creator':
1707
+			case 'Producer':
1708
+			case 'CreationDate':
1709
+			case 'ModDate':
1710
+			case 'Trapped':
1711
+				$this->objects[$id]['info'][$action] = $options;
1712
+				break;
1713
+
1714
+			case 'out':
1715
+				$encrypted = $this->encrypted;
1716
+				if ($encrypted) {
1717
+					$this->encryptInit($id);
1718
+				}
1719
+
1720
+				$res = "\n$id 0 obj\n<<\n";
1721
+				$o = &$this->objects[$id];
1722
+				foreach ($o['info'] as $k => $v) {
1723
+					$res .= "/$k (";
1724
+
1725
+					// dates must be outputted as-is, without Unicode transformations
1726
+					if ($k !== 'CreationDate' && $k !== 'ModDate') {
1727
+						$v = $this->filterText($v, true, false);
1728
+					}
1729
+
1730
+					if ($encrypted) {
1731
+						$v = $this->ARC4($v);
1732
+					}
1733
+
1734
+					$res .= $v;
1735
+					$res .= ")\n";
1736
+				}
1737
+
1738
+				$res .= ">>\nendobj";
1739
+
1740
+				return $res;
1741
+		}
1742
+
1743
+		return null;
1744
+	}
1745
+
1746
+	/**
1747
+	 * an action object, used to link to URLS initially
1748
+	 *
1749
+	 * @param $id
1750
+	 * @param $action
1751
+	 * @param string $options
1752
+	 * @return null|string
1753
+	 */
1754
+	protected function o_action($id, $action, $options = '')
1755
+	{
1756
+		if ($action !== 'new') {
1757
+			$o = &$this->objects[$id];
1758
+		}
1759
+
1760
+		switch ($action) {
1761
+			case 'new':
1762
+				if (is_array($options)) {
1763
+					$this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => $options['type']];
1764
+				} else {
1765
+					// then assume a URI action
1766
+					$this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => 'URI'];
1767
+				}
1768
+				break;
1769
+
1770
+			case 'out':
1771
+				if ($this->encrypted) {
1772
+					$this->encryptInit($id);
1773
+				}
1774
+
1775
+				$res = "\n$id 0 obj\n<< /Type /Action";
1776
+				switch ($o['type']) {
1777
+					case 'ilink':
1778
+						if (!isset($this->destinations[(string)$o['info']['label']])) {
1779
+							break;
1780
+						}
1781
+
1782
+						// there will be an 'label' setting, this is the name of the destination
1783
+						$res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1784
+						break;
1785
+
1786
+					case 'URI':
1787
+						$res .= "\n/S /URI\n/URI (";
1788
+						if ($this->encrypted) {
1789
+							$res .= $this->filterText($this->ARC4($o['info']), false, false);
1790
+						} else {
1791
+							$res .= $this->filterText($o['info'], false, false);
1792
+						}
1793
+
1794
+						$res .= ")";
1795
+						break;
1796
+				}
1797
+
1798
+				$res .= "\n>>\nendobj";
1799
+
1800
+				return $res;
1801
+		}
1802
+
1803
+		return null;
1804
+	}
1805
+
1806
+	/**
1807
+	 * an annotation object, this will add an annotation to the current page.
1808
+	 * initially will support just link annotations
1809
+	 *
1810
+	 * @param $id
1811
+	 * @param $action
1812
+	 * @param string $options
1813
+	 * @return null|string
1814
+	 */
1815
+	protected function o_annotation($id, $action, $options = '')
1816
+	{
1817
+		if ($action !== 'new') {
1818
+			$o = &$this->objects[$id];
1819
+		}
1820
+
1821
+		switch ($action) {
1822
+			case 'new':
1823
+				// add the annotation to the current page
1824
+				$pageId = $this->currentPage;
1825
+				$this->o_page($pageId, 'annot', $id);
1826
+
1827
+				// and add the action object which is going to be required
1828
+				switch ($options['type']) {
1829
+					case 'link':
1830
+						$this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1831
+						$this->numObj++;
1832
+						$this->o_action($this->numObj, 'new', $options['url']);
1833
+						$this->objects[$id]['info']['actionId'] = $this->numObj;
1834
+						break;
1835
+
1836
+					case 'ilink':
1837
+						// this is to a named internal link
1838
+						$label = $options['label'];
1839
+						$this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1840
+						$this->numObj++;
1841
+						$this->o_action($this->numObj, 'new', ['type' => 'ilink', 'label' => $label]);
1842
+						$this->objects[$id]['info']['actionId'] = $this->numObj;
1843
+						break;
1844
+				}
1845
+				break;
1846
+
1847
+			case 'out':
1848
+				$res = "\n$id 0 obj\n<< /Type /Annot";
1849
+				switch ($o['info']['type']) {
1850
+					case 'link':
1851
+					case 'ilink':
1852
+						$res .= "\n/Subtype /Link";
1853
+						break;
1854
+				}
1855
+				$res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1856
+				$res .= "\n/Border [0 0 0]";
1857
+				$res .= "\n/H /I";
1858
+				$res .= "\n/Rect [ ";
1859
+
1860
+				foreach ($o['info']['rect'] as $v) {
1861
+					$res .= sprintf("%.4F ", $v);
1862
+				}
1863
+
1864
+				$res .= "]";
1865
+				$res .= "\n>>\nendobj";
1866
+
1867
+				return $res;
1868
+		}
1869
+
1870
+		return null;
1871
+	}
1872
+
1873
+	/**
1874
+	 * a page object, it also creates a contents object to hold its contents
1875
+	 *
1876
+	 * @param $id
1877
+	 * @param $action
1878
+	 * @param string $options
1879
+	 * @return null|string
1880
+	 */
1881
+	protected function o_page($id, $action, $options = '')
1882
+	{
1883
+		if ($action !== 'new') {
1884
+			$o = &$this->objects[$id];
1885
+		}
1886
+
1887
+		switch ($action) {
1888
+			case 'new':
1889
+				$this->numPages++;
1890
+				$this->objects[$id] = [
1891
+					't'    => 'page',
1892
+					'info' => [
1893
+						'parent'  => $this->currentNode,
1894
+						'pageNum' => $this->numPages,
1895
+						'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1896
+					]
1897
+				];
1898
+
1899
+				if (is_array($options)) {
1900
+					// then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1901
+					$options['id'] = $id;
1902
+					$this->o_pages($this->currentNode, 'page', $options);
1903
+				} else {
1904
+					$this->o_pages($this->currentNode, 'page', $id);
1905
+				}
1906
+
1907
+				$this->currentPage = $id;
1908
+				//make a contents object to go with this page
1909
+				$this->numObj++;
1910
+				$this->o_contents($this->numObj, 'new', $id);
1911
+				$this->currentContents = $this->numObj;
1912
+				$this->objects[$id]['info']['contents'] = [];
1913
+				$this->objects[$id]['info']['contents'][] = $this->numObj;
1914
+
1915
+				$match = ($this->numPages % 2 ? 'odd' : 'even');
1916
+				foreach ($this->addLooseObjects as $oId => $target) {
1917
+					if ($target === 'all' || $match === $target) {
1918
+						$this->objects[$id]['info']['contents'][] = $oId;
1919
+					}
1920
+				}
1921
+				break;
1922
+
1923
+			case 'content':
1924
+				$o['info']['contents'][] = $options;
1925
+				break;
1926
+
1927
+			case 'annot':
1928
+				// add an annotation to this page
1929
+				if (!isset($o['info']['annot'])) {
1930
+					$o['info']['annot'] = [];
1931
+				}
1932
+
1933
+				// $options should contain the id of the annotation dictionary
1934
+				$o['info']['annot'][] = $options;
1935
+				break;
1936
+
1937
+			case 'out':
1938
+				$res = "\n$id 0 obj\n<< /Type /Page";
1939
+				if (isset($o['info']['mediaBox'])) {
1940
+					$tmp = $o['info']['mediaBox'];
1941
+					$res .= "\n/MediaBox [" . sprintf(
1942
+							'%.3F %.3F %.3F %.3F',
1943
+							$tmp[0],
1944
+							$tmp[1],
1945
+							$tmp[2],
1946
+							$tmp[3]
1947
+						) . ']';
1948
+				}
1949
+				$res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1950
+
1951
+				if (isset($o['info']['annot'])) {
1952
+					$res .= "\n/Annots [";
1953
+					foreach ($o['info']['annot'] as $aId) {
1954
+						$res .= " $aId 0 R";
1955
+					}
1956
+					$res .= " ]";
1957
+				}
1958
+
1959
+				$count = count($o['info']['contents']);
1960
+				if ($count == 1) {
1961
+					$res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1962
+				} else {
1963
+					if ($count > 1) {
1964
+						$res .= "\n/Contents [\n";
1965
+
1966
+						// reverse the page contents so added objects are below normal content
1967
+						//foreach (array_reverse($o['info']['contents']) as $cId) {
1968
+						// Back to normal now that I've got transparency working --Benj
1969
+						foreach ($o['info']['contents'] as $cId) {
1970
+							$res .= "$cId 0 R\n";
1971
+						}
1972
+						$res .= "]";
1973
+					}
1974
+				}
1975
+
1976
+				$res .= "\n>>\nendobj";
1977
+
1978
+				return $res;
1979
+		}
1980
+
1981
+		return null;
1982
+	}
1983
+
1984
+	/**
1985
+	 * the contents objects hold all of the content which appears on pages
1986
+	 *
1987
+	 * @param $id
1988
+	 * @param $action
1989
+	 * @param string|array $options
1990
+	 * @return null|string
1991
+	 */
1992
+	protected function o_contents($id, $action, $options = '')
1993
+	{
1994
+		if ($action !== 'new') {
1995
+			$o = &$this->objects[$id];
1996
+		}
1997
+
1998
+		switch ($action) {
1999
+			case 'new':
2000
+				$this->objects[$id] = ['t' => 'contents', 'c' => '', 'info' => []];
2001
+				if (mb_strlen($options, '8bit') && intval($options)) {
2002
+					// then this contents is the primary for a page
2003
+					$this->objects[$id]['onPage'] = $options;
2004
+				} else {
2005
+					if ($options === 'raw') {
2006
+						// then this page contains some other type of system object
2007
+						$this->objects[$id]['raw'] = 1;
2008
+					}
2009
+				}
2010
+				break;
2011
+
2012
+			case 'add':
2013
+				// add more options to the declaration
2014
+				foreach ($options as $k => $v) {
2015
+					$o['info'][$k] = $v;
2016
+				}
2017
+
2018
+			case 'out':
2019
+				$tmp = $o['c'];
2020
+				$res = "\n$id 0 obj\n";
2021
+
2022
+				if (isset($this->objects[$id]['raw'])) {
2023
+					$res .= $tmp;
2024
+				} else {
2025
+					$res .= "<<";
2026
+					if ($this->compressionReady && $this->options['compression']) {
2027
+						// then implement ZLIB based compression on this content stream
2028
+						$res .= " /Filter /FlateDecode";
2029
+						$tmp = gzcompress($tmp, 6);
2030
+					}
2031
+
2032
+					if ($this->encrypted) {
2033
+						$this->encryptInit($id);
2034
+						$tmp = $this->ARC4($tmp);
2035
+					}
2036
+
2037
+					foreach ($o['info'] as $k => $v) {
2038
+						$res .= "\n/$k $v";
2039
+					}
2040
+
2041
+					$res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
2042
+				}
2043
+
2044
+				$res .= "\nendobj";
2045
+
2046
+				return $res;
2047
+		}
2048
+
2049
+		return null;
2050
+	}
2051
+
2052
+	/**
2053
+	 * @param $id
2054
+	 * @param $action
2055
+	 * @return string|null
2056
+	 */
2057
+	protected function o_embedjs($id, $action)
2058
+	{
2059
+		switch ($action) {
2060
+			case 'new':
2061
+				$this->objects[$id] = [
2062
+					't'    => 'embedjs',
2063
+					'info' => [
2064
+						'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
2065
+					]
2066
+				];
2067
+				break;
2068
+
2069
+			case 'out':
2070
+				$o = &$this->objects[$id];
2071
+				$res = "\n$id 0 obj\n<< ";
2072
+				foreach ($o['info'] as $k => $v) {
2073
+					$res .= "\n/$k $v";
2074
+				}
2075
+				$res .= "\n>>\nendobj";
2076
+
2077
+				return $res;
2078
+		}
2079
+
2080
+		return null;
2081
+	}
2082
+
2083
+	/**
2084
+	 * @param $id
2085
+	 * @param $action
2086
+	 * @param string $code
2087
+	 * @return null|string
2088
+	 */
2089
+	protected function o_javascript($id, $action, $code = '')
2090
+	{
2091
+		switch ($action) {
2092
+			case 'new':
2093
+				$this->objects[$id] = [
2094
+					't'    => 'javascript',
2095
+					'info' => [
2096
+						'S'  => '/JavaScript',
2097
+						'JS' => '(' . $this->filterText($code, true, false) . ')',
2098
+					]
2099
+				];
2100
+				break;
2101
+
2102
+			case 'out':
2103
+				$o = &$this->objects[$id];
2104
+				$res = "\n$id 0 obj\n<< ";
2105
+
2106
+				foreach ($o['info'] as $k => $v) {
2107
+					$res .= "\n/$k $v";
2108
+				}
2109
+				$res .= "\n>>\nendobj";
2110
+
2111
+				return $res;
2112
+		}
2113
+
2114
+		return null;
2115
+	}
2116
+
2117
+	/**
2118
+	 * an image object, will be an XObject in the document, includes description and data
2119
+	 *
2120
+	 * @param $id
2121
+	 * @param $action
2122
+	 * @param string $options
2123
+	 * @return null|string
2124
+	 */
2125
+	protected function o_image($id, $action, $options = '')
2126
+	{
2127
+		switch ($action) {
2128
+			case 'new':
2129
+				// make the new object
2130
+				$this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
2131
+
2132
+				$info =& $this->objects[$id]['info'];
2133
+
2134
+				$info['Type'] = '/XObject';
2135
+				$info['Subtype'] = '/Image';
2136
+				$info['Width'] = $options['iw'];
2137
+				$info['Height'] = $options['ih'];
2138
+
2139
+				if (isset($options['masked']) && $options['masked']) {
2140
+					$info['SMask'] = ($this->numObj - 1) . ' 0 R';
2141
+				}
2142
+
2143
+				if (!isset($options['type']) || $options['type'] === 'jpg') {
2144
+					if (!isset($options['channels'])) {
2145
+						$options['channels'] = 3;
2146
+					}
2147
+
2148
+					switch ($options['channels']) {
2149
+						case 1:
2150
+							$info['ColorSpace'] = '/DeviceGray';
2151
+							break;
2152
+						case 4:
2153
+							$info['ColorSpace'] = '/DeviceCMYK';
2154
+							break;
2155
+						default:
2156
+							$info['ColorSpace'] = '/DeviceRGB';
2157
+							break;
2158
+					}
2159
+
2160
+					if ($info['ColorSpace'] === '/DeviceCMYK') {
2161
+						$info['Decode'] = '[1 0 1 0 1 0 1 0]';
2162
+					}
2163
+
2164
+					$info['Filter'] = '/DCTDecode';
2165
+					$info['BitsPerComponent'] = 8;
2166
+				} else {
2167
+					if ($options['type'] === 'png') {
2168
+						$info['Filter'] = '/FlateDecode';
2169
+						$info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
2170
+
2171
+						if ($options['isMask']) {
2172
+							$info['ColorSpace'] = '/DeviceGray';
2173
+						} else {
2174
+							if (mb_strlen($options['pdata'], '8bit')) {
2175
+								$tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
2176
+								$this->numObj++;
2177
+								$this->o_contents($this->numObj, 'new');
2178
+								$this->objects[$this->numObj]['c'] = $options['pdata'];
2179
+								$tmp .= $this->numObj . ' 0 R';
2180
+								$tmp .= ' ]';
2181
+								$info['ColorSpace'] = $tmp;
2182
+
2183
+								if (isset($options['transparency'])) {
2184
+									$transparency = $options['transparency'];
2185
+									switch ($transparency['type']) {
2186
+										case 'indexed':
2187
+											$tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2188
+											$info['Mask'] = $tmp;
2189
+											break;
2190
+
2191
+										case 'color-key':
2192
+											$tmp = ' [ ' .
2193
+												$transparency['r'] . ' ' . $transparency['r'] .
2194
+												$transparency['g'] . ' ' . $transparency['g'] .
2195
+												$transparency['b'] . ' ' . $transparency['b'] .
2196
+												' ] ';
2197
+											$info['Mask'] = $tmp;
2198
+											break;
2199
+									}
2200
+								}
2201
+							} else {
2202
+								if (isset($options['transparency'])) {
2203
+									$transparency = $options['transparency'];
2204
+
2205
+									switch ($transparency['type']) {
2206
+										case 'indexed':
2207
+											$tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2208
+											$info['Mask'] = $tmp;
2209
+											break;
2210
+
2211
+										case 'color-key':
2212
+											$tmp = ' [ ' .
2213
+												$transparency['r'] . ' ' . $transparency['r'] . ' ' .
2214
+												$transparency['g'] . ' ' . $transparency['g'] . ' ' .
2215
+												$transparency['b'] . ' ' . $transparency['b'] .
2216
+												' ] ';
2217
+											$info['Mask'] = $tmp;
2218
+											break;
2219
+									}
2220
+								}
2221
+								$info['ColorSpace'] = '/' . $options['color'];
2222
+							}
2223
+						}
2224
+
2225
+						$info['BitsPerComponent'] = $options['bitsPerComponent'];
2226
+					}
2227
+				}
2228
+
2229
+				// assign it a place in the named resource dictionary as an external object, according to
2230
+				// the label passed in with it.
2231
+				$this->o_pages($this->currentNode, 'xObject', ['label' => $options['label'], 'objNum' => $id]);
2232
+
2233
+				// also make sure that we have the right procset object for it.
2234
+				$this->o_procset($this->procsetObjectId, 'add', 'ImageC');
2235
+				break;
2236
+
2237
+			case 'out':
2238
+				$o = &$this->objects[$id];
2239
+				$tmp = &$o['data'];
2240
+				$res = "\n$id 0 obj\n<<";
2241
+
2242
+				foreach ($o['info'] as $k => $v) {
2243
+					$res .= "\n/$k $v";
2244
+				}
2245
+
2246
+				if ($this->encrypted) {
2247
+					$this->encryptInit($id);
2248
+					$tmp = $this->ARC4($tmp);
2249
+				}
2250
+
2251
+				$res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
2252
+
2253
+				return $res;
2254
+		}
2255
+
2256
+		return null;
2257
+	}
2258
+
2259
+	/**
2260
+	 * graphics state object
2261
+	 *
2262
+	 * @param $id
2263
+	 * @param $action
2264
+	 * @param string $options
2265
+	 * @return null|string
2266
+	 */
2267
+	protected function o_extGState($id, $action, $options = "")
2268
+	{
2269
+		static $valid_params = [
2270
+			"LW",
2271
+			"LC",
2272
+			"LC",
2273
+			"LJ",
2274
+			"ML",
2275
+			"D",
2276
+			"RI",
2277
+			"OP",
2278
+			"op",
2279
+			"OPM",
2280
+			"Font",
2281
+			"BG",
2282
+			"BG2",
2283
+			"UCR",
2284
+			"TR",
2285
+			"TR2",
2286
+			"HT",
2287
+			"FL",
2288
+			"SM",
2289
+			"SA",
2290
+			"BM",
2291
+			"SMask",
2292
+			"CA",
2293
+			"ca",
2294
+			"AIS",
2295
+			"TK"
2296
+		];
2297
+
2298
+		switch ($action) {
2299
+			case "new":
2300
+				$this->objects[$id] = ['t' => 'extGState', 'info' => $options];
2301
+
2302
+				// Tell the pages about the new resource
2303
+				$this->numStates++;
2304
+				$this->o_pages($this->currentNode, 'extGState', ["objNum" => $id, "stateNum" => $this->numStates]);
2305
+				break;
2306
+
2307
+			case "out":
2308
+				$o = &$this->objects[$id];
2309
+				$res = "\n$id 0 obj\n<< /Type /ExtGState\n";
2310
+
2311
+				foreach ($o["info"] as $k => $v) {
2312
+					if (!in_array($k, $valid_params)) {
2313
+						continue;
2314
+					}
2315
+					$res .= "/$k $v\n";
2316
+				}
2317
+
2318
+				$res .= ">>\nendobj";
2319
+
2320
+				return $res;
2321
+		}
2322
+
2323
+		return null;
2324
+	}
2325
+
2326
+	/**
2327
+	 * @param integer $id
2328
+	 * @param string $action
2329
+	 * @param mixed $options
2330
+	 * @return string
2331
+	 */
2332
+	protected function o_xobject($id, $action, $options = '')
2333
+	{
2334
+		switch ($action) {
2335
+			case 'new':
2336
+				$this->objects[$id] = ['t' => 'xobject', 'info' => $options, 'c' => ''];
2337
+				break;
2338
+
2339
+			case 'procset':
2340
+				$this->objects[$id]['procset'] = $options;
2341
+				break;
2342
+
2343
+			case 'font':
2344
+				$this->objects[$id]['fonts'][$options['fontNum']] = [
2345
+				  'objNum' => $options['objNum'],
2346
+				  'fontNum' => $options['fontNum']
2347
+				];
2348
+				break;
2349
+
2350
+			case 'xObject':
2351
+				$this->objects[$id]['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
2352
+				break;
2353
+
2354
+			case 'out':
2355
+				$o = &$this->objects[$id];
2356
+				$res = "\n$id 0 obj\n<< /Type /XObject\n";
2357
+
2358
+				foreach ($o["info"] as $k => $v) {
2359
+					switch($k)
2360
+					{
2361
+						case 'Subtype':
2362
+							$res .= "/Subtype /$v\n";
2363
+							break;
2364
+						case 'bbox':
2365
+							$res .= "/BBox [";
2366
+							foreach ($v as $value) {
2367
+								$res .= sprintf("%.4F ", $value);
2368
+							}
2369
+							$res .= "]\n";
2370
+							break;
2371
+						default:
2372
+							$res .= "/$k $v\n";
2373
+							break;
2374
+					}
2375
+				}
2376
+				$res .= "/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]\n";
2377
+
2378
+				$res .= "/Resources <<";
2379
+				if (isset($o['procset'])) {
2380
+					$res .= "\n/ProcSet " . $o['procset'] . " 0 R";
2381
+				} else {
2382
+					$res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
2383
+				}
2384
+				if (isset($o['fonts']) && count($o['fonts'])) {
2385
+					$res .= "\n/Font << ";
2386
+					foreach ($o['fonts'] as $finfo) {
2387
+						$res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
2388
+					}
2389
+					$res .= "\n>>";
2390
+				}
2391
+				if (isset($o['xObjects']) && count($o['xObjects'])) {
2392
+					$res .= "\n/XObject << ";
2393
+					foreach ($o['xObjects'] as $finfo) {
2394
+						$res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
2395
+					}
2396
+					$res .= "\n>>";
2397
+				}
2398
+				$res .= "\n>>\n";
2399
+
2400
+				$tmp = $o["c"];
2401
+				if ($this->compressionReady && $this->options['compression']) {
2402
+					// then implement ZLIB based compression on this content stream
2403
+					$res .= " /Filter /FlateDecode\n";
2404
+					$tmp = gzcompress($tmp, 6);
2405
+				}
2406
+
2407
+				if ($this->encrypted) {
2408
+					$this->encryptInit($id);
2409
+					$tmp = $this->ARC4($tmp);
2410
+				}
2411
+
2412
+				$res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2413
+				$res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";;
2414
+
2415
+				return $res;
2416
+		}
2417
+
2418
+		return null;
2419
+	}
2420
+
2421
+	/**
2422
+	 * @param $id
2423
+	 * @param $action
2424
+	 * @param string $options
2425
+	 * @return null|string
2426
+	 */
2427
+	protected function o_acroform($id, $action, $options = '')
2428
+	{
2429
+		switch ($action) {
2430
+			case "new":
2431
+				$this->o_catalog($this->catalogId, 'acroform', $id);
2432
+				$this->objects[$id] = array('t' => 'acroform', 'info' => $options);
2433
+				break;
2434
+
2435
+			case 'addfield':
2436
+				$this->objects[$id]['info']['Fields'][] = $options;
2437
+				break;
2438
+
2439
+			case 'font':
2440
+				$this->objects[$id]['fonts'][$options['fontNum']] = [
2441
+				  'objNum' => $options['objNum'],
2442
+				  'fontNum' => $options['fontNum']
2443
+				];
2444
+				break;
2445
+
2446
+			case "out":
2447
+				$o = &$this->objects[$id];
2448
+				$res = "\n$id 0 obj\n<<";
2449
+
2450
+				foreach ($o["info"] as $k => $v) {
2451
+					switch($k) {
2452
+						case 'Fields':
2453
+							$res .= " /Fields [";
2454
+							foreach ($v as $i) {
2455
+								$res .= "$i 0 R ";
2456
+							}
2457
+							$res .= "]\n";
2458
+							break;
2459
+						default:
2460
+							$res .= "/$k $v\n";
2461
+					}
2462
+				}
2463
+
2464
+				$res .= "/DR <<\n";
2465
+				if (isset($o['fonts']) && count($o['fonts'])) {
2466
+					$res .= "/Font << \n";
2467
+					foreach ($o['fonts'] as $finfo) {
2468
+						$res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
2469
+					}
2470
+					$res .= ">>\n";
2471
+				}
2472
+				$res .= ">>\n";
2473
+
2474
+				$res .= ">>\nendobj";
2475
+
2476
+				return $res;
2477
+		}
2478
+
2479
+		return null;
2480
+	}
2481
+
2482
+	/**
2483
+	 * @param $id
2484
+	 * @param $action
2485
+	 * @param mixed $options
2486
+	 * @return null|string
2487
+	 */
2488
+	protected function o_field($id, $action, $options = '')
2489
+	{
2490
+		switch ($action) {
2491
+			case "new":
2492
+				$this->o_page($options['pageid'], 'annot', $id);
2493
+				$this->o_acroform($this->acroFormId, 'addfield', $id);
2494
+				$this->objects[$id] = ['t' => 'field', 'info' => $options];
2495
+				break;
2496
+
2497
+			case 'set':
2498
+				$this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2499
+				break;
2500
+
2501
+			case "out":
2502
+				$o = &$this->objects[$id];
2503
+				$res = "\n$id 0 obj\n<< /Type /Annot /Subtype /Widget \n";
2504
+
2505
+				$encrypted = $this->encrypted;
2506
+				if ($encrypted) {
2507
+					$this->encryptInit($id);
2508
+				}
2509
+
2510
+				foreach ($o["info"] as $k => $v) {
2511
+					switch ($k) {
2512
+						case 'pageid':
2513
+							$res .= "/P $v 0 R\n";
2514
+							break;
2515
+						case 'value':
2516
+							if ($encrypted) {
2517
+								$v = $this->filterText($this->ARC4($v), false, false);
2518
+							}
2519
+							$res .= "/V ($v)\n";
2520
+							break;
2521
+						case 'refvalue':
2522
+							$res .= "/V $v 0 R\n";
2523
+							break;
2524
+						case 'da':
2525
+							if ($encrypted) {
2526
+								$v = $this->filterText($this->ARC4($v), false, false);
2527
+							}
2528
+							$res .= "/DA ($v)\n";
2529
+							break;
2530
+						case 'options':
2531
+							$res .= "/Opt [\n";
2532
+							foreach ($v as $opt) {
2533
+								if ($encrypted) {
2534
+									$opt = $this->filterText($this->ARC4($opt), false, false);
2535
+								}
2536
+								$res .= "($opt)\n";
2537
+							}
2538
+							$res .= "]\n";
2539
+							break;
2540
+						case 'rect':
2541
+							$res .= "/Rect [";
2542
+							foreach ($v as $value) {
2543
+								$res .= sprintf("%.4F ", $value);
2544
+							}
2545
+							$res .= "]\n";
2546
+							break;
2547
+						case 'appearance':
2548
+							$res .= "/AP << ";
2549
+							foreach ($v as $a => $ref) {
2550
+								$res .= "/$a $ref 0 R ";
2551
+							}
2552
+							$res .= ">>\n";
2553
+							break;
2554
+						case 'T':
2555
+							if($encrypted) {
2556
+								$v = $this->filterText($this->ARC4($v), false, false);
2557
+							}
2558
+							$res .= "/T ($v)\n";
2559
+							break;
2560
+						default:
2561
+							$res .= "/$k $v\n";
2562
+					}
2563
+
2564
+				}
2565
+
2566
+				$res .= ">>\nendobj";
2567
+
2568
+				return $res;
2569
+		}
2570
+
2571
+		return null;
2572
+	}
2573
+
2574
+	/**
2575
+	 *
2576
+	 * @param $id
2577
+	 * @param $action
2578
+	 * @param string $options
2579
+	 * @return null|string
2580
+	 */
2581
+	protected function o_sig($id, $action, $options = '')
2582
+	{
2583
+		$sign_maxlen = $this->signatureMaxLen;
2584
+
2585
+		switch ($action) {
2586
+			case "new":
2587
+				$this->objects[$id] = array('t' => 'sig', 'info' => $options);
2588
+				$this->byteRange[$id] = ['t' => 'sig'];
2589
+				break;
2590
+
2591
+			case 'byterange':
2592
+				$o = &$this->objects[$id];
2593
+				$content =& $options['content'];
2594
+				$content_len = strlen($content);
2595
+				$pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2596
+				$len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
2597
+				$rangeStartPos = $pos + $len + 1 + 10; // before '<'
2598
+				$content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos ), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2599
+
2600
+				$fuid = uniqid();
2601
+				$tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2602
+				$tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
2603
+
2604
+				if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
2605
+					throw new \Exception("Unable to write temporary file for signing.");
2606
+				}
2607
+				if (file_put_contents($tmpInput, substr($content, $rangeStartPos + 2 + $sign_maxlen),
2608
+					FILE_APPEND) === false) {
2609
+					throw new \Exception("Unable to write temporary file for signing.");
2610
+				}
2611
+
2612
+				if (openssl_pkcs7_sign($tmpInput, $tmpOutput,
2613
+					$o['info']['SignCert'],
2614
+					array($o['info']['PrivKey'], $o['info']['Password']),
2615
+					array(), PKCS7_BINARY | PKCS7_DETACHED) === false) {
2616
+					throw new \Exception("Failed to prepare signature.");
2617
+				}
2618
+
2619
+				$signature = file_get_contents($tmpOutput);
2620
+
2621
+				unlink($tmpInput);
2622
+				unlink($tmpOutput);
2623
+
2624
+				$sign = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
2625
+				list($head, $signature) = explode("\n\n", $sign);
2626
+
2627
+				$signature = base64_decode(trim($signature));
2628
+
2629
+				$signature = current(unpack('H*', $signature));
2630
+				$signature = str_pad($signature, $sign_maxlen, '0');
2631
+				$siglen = strlen($signature);
2632
+				if (strlen($signature) > $sign_maxlen) {
2633
+					throw new \Exception("Signature length ($siglen) exceeds the $sign_maxlen limit.");
2634
+				}
2635
+
2636
+				$content = substr_replace($content, $signature, $rangeStartPos + 1, $sign_maxlen);
2637
+				break;
2638
+
2639
+			case "out":
2640
+				$res = "\n$id 0 obj\n<<\n";
2641
+
2642
+				$encrypted = $this->encrypted;
2643
+				if ($encrypted) {
2644
+					$this->encryptInit($id);
2645
+				}
2646
+
2647
+				$res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2648
+				$res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
2649
+				$res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
2650
+				$res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
2651
+
2652
+				$date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
2653
+				if ($encrypted) {
2654
+					$date = $this->ARC4($date);
2655
+				}
2656
+
2657
+				$res .= "/M ($date)\n";
2658
+				$res .= "/Prop_Build << /App << /Name /DomPDF >> /Filter << /Name /Adobe.PPKLite >> >>\n";
2659
+
2660
+				$o = &$this->objects[$id];
2661
+				foreach ($o['info'] as $k => $v) {
2662
+					switch($k) {
2663
+						case 'Name':
2664
+						case 'Location':
2665
+						case 'Reason':
2666
+						case 'ContactInfo':
2667
+							if ($v !== null && $v !== '') {
2668
+								$res .= "/$k (" .
2669
+								  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
2670
+							}
2671
+							break;
2672
+					}
2673
+				}
2674
+				$res .= ">>\nendobj";
2675
+
2676
+				return $res;
2677
+		}
2678
+
2679
+		return null;
2680
+	}
2681
+
2682
+	/**
2683
+	 * encryption object.
2684
+	 *
2685
+	 * @param $id
2686
+	 * @param $action
2687
+	 * @param string $options
2688
+	 * @return string|null
2689
+	 */
2690
+	protected function o_encryption($id, $action, $options = '')
2691
+	{
2692
+		switch ($action) {
2693
+			case 'new':
2694
+				// make the new object
2695
+				$this->objects[$id] = ['t' => 'encryption', 'info' => $options];
2696
+				$this->arc4_objnum = $id;
2697
+				break;
2698
+
2699
+			case 'keys':
2700
+				// figure out the additional parameters required
2701
+				$pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2702
+					. chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2703
+					. chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2704
+					. chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2705
+
2706
+				$info = $this->objects[$id]['info'];
2707
+
2708
+				$len = mb_strlen($info['owner'], '8bit');
2709
+
2710
+				if ($len > 32) {
2711
+					$owner = substr($info['owner'], 0, 32);
2712
+				} else {
2713
+					if ($len < 32) {
2714
+						$owner = $info['owner'] . substr($pad, 0, 32 - $len);
2715
+					} else {
2716
+						$owner = $info['owner'];
2717
+					}
2718
+				}
2719
+
2720
+				$len = mb_strlen($info['user'], '8bit');
2721
+				if ($len > 32) {
2722
+					$user = substr($info['user'], 0, 32);
2723
+				} else {
2724
+					if ($len < 32) {
2725
+						$user = $info['user'] . substr($pad, 0, 32 - $len);
2726
+					} else {
2727
+						$user = $info['user'];
2728
+					}
2729
+				}
2730
+
2731
+				$tmp = $this->md5_16($owner);
2732
+				$okey = substr($tmp, 0, 5);
2733
+				$this->ARC4_init($okey);
2734
+				$ovalue = $this->ARC4($user);
2735
+				$this->objects[$id]['info']['O'] = $ovalue;
2736
+
2737
+				// now make the u value, phew.
2738
+				$tmp = $this->md5_16(
2739
+					$user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2740
+				);
2741
+
2742
+				$ukey = substr($tmp, 0, 5);
2743
+				$this->ARC4_init($ukey);
2744
+				$this->encryptionKey = $ukey;
2745
+				$this->encrypted = true;
2746
+				$uvalue = $this->ARC4($pad);
2747
+				$this->objects[$id]['info']['U'] = $uvalue;
2748
+				// initialize the arc4 array
2749
+				break;
2750
+
2751
+			case 'out':
2752
+				$o = &$this->objects[$id];
2753
+
2754
+				$res = "\n$id 0 obj\n<<";
2755
+				$res .= "\n/Filter /Standard";
2756
+				$res .= "\n/V 1";
2757
+				$res .= "\n/R 2";
2758
+				$res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2759
+				$res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2760
+				// and the p-value needs to be converted to account for the twos-complement approach
2761
+				$o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2762
+				$res .= "\n/P " . ($o['info']['p']);
2763
+				$res .= "\n>>\nendobj";
2764
+
2765
+				return $res;
2766
+		}
2767
+
2768
+		return null;
2769
+	}
2770
+
2771
+	protected function o_indirect_references($id, $action, $options = null)
2772
+	{
2773
+		switch ($action) {
2774
+			case 'new':
2775
+			case 'add':
2776
+				if ($id === 0) {
2777
+					$id = ++$this->numObj;
2778
+					$this->o_catalog($this->catalogId, 'names', $id);
2779
+					$this->objects[$id] = ['t' => 'indirect_references', 'info' => $options];
2780
+					$this->indirectReferenceId = $id;
2781
+				} else {
2782
+					$this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2783
+				}
2784
+				break;
2785
+			case 'out':
2786
+				$res = "\n$id 0 obj << ";
2787
+
2788
+				foreach($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2789
+					$res .= "/$referenceObjName $referenceObjId 0 R ";
2790
+				}
2791
+
2792
+				$res .= ">> endobj";
2793
+				return $res;
2794
+		}
2795
+
2796
+		return null;
2797
+	}
2798
+
2799
+	protected function o_names($id, $action, $options = null)
2800
+	{
2801
+		switch ($action) {
2802
+			case 'new':
2803
+			case 'add':
2804
+				if ($id === 0) {
2805
+					$id = ++$this->numObj;
2806
+					$this->objects[$id] = ['t' => 'names', 'info' => [$options]];
2807
+					$this->o_indirect_references($this->indirectReferenceId, 'add', ['EmbeddedFiles' => $id]);
2808
+					$this->embeddedFilesId = $id;
2809
+				} else {
2810
+					$this->objects[$id]['info'][] = $options;
2811
+				}
2812
+				break;
2813
+			case 'out':
2814
+				$info = &$this->objects[$id]['info'];
2815
+				$res = '';
2816
+				if (count($info) > 0) {
2817
+					$res = "\n$id 0 obj << /Names [ ";
2818
+
2819
+					if ($this->encrypted) {
2820
+						$this->encryptInit($id);
2821
+					}
2822
+
2823
+					foreach ($info as $entry) {
2824
+						if ($this->encrypted) {
2825
+							$filename = $this->ARC4($entry['filename']);
2826
+						} else {
2827
+							$filename = $entry['filename'];
2828
+						}
2829
+
2830
+						$res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
2831
+					}
2832
+
2833
+					$res .= "] >> endobj";
2834
+				}
2835
+				return $res;
2836
+		}
2837
+
2838
+		return null;
2839
+	}
2840
+
2841
+	protected function o_embedded_file_dictionary($id, $action, $options = null)
2842
+	{
2843
+		switch ($action) {
2844
+			case 'new':
2845
+				$embeddedFileId = ++$this->numObj;
2846
+				$options['embedded_reference'] = $embeddedFileId;
2847
+				$this->objects[$id] = ['t' => 'embedded_file_dictionary', 'info' => $options];
2848
+				$this->o_embedded_file($embeddedFileId, 'new', $options);
2849
+				$options['dict_reference'] = $id;
2850
+				$this->o_names($this->embeddedFilesId, 'add', $options);
2851
+				break;
2852
+			case 'out':
2853
+				$info = &$this->objects[$id]['info'];
2854
+
2855
+				if ($this->encrypted) {
2856
+					$this->encryptInit($id);
2857
+					$filename = $this->ARC4($info['filename']);
2858
+					$description = $this->ARC4($info['description']);
2859
+				} else {
2860
+					$filename = $info['filename'];
2861
+					$description = $info['description'];
2862
+				}
2863
+
2864
+				$res = "\n$id 0 obj <</Type /Filespec /EF";
2865
+				$res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
2866
+				$res .= " /F ($filename) /UF ($filename) /Desc ($description)";
2867
+				$res .= " >> endobj";
2868
+				return $res;
2869
+		}
2870
+
2871
+		return null;
2872
+	}
2873
+
2874
+	protected function o_embedded_file($id, $action, $options = null): ?string
2875
+	{
2876
+		switch ($action) {
2877
+			case 'new':
2878
+				$this->objects[$id] = ['t' => 'embedded_file', 'info' => $options];
2879
+				break;
2880
+			case 'out':
2881
+				$info = &$this->objects[$id]['info'];
2882
+
2883
+				if ($this->compressionReady) {
2884
+					$filepath = $info['filepath'];
2885
+					$checksum = md5_file($filepath);
2886
+					$f = fopen($filepath, "rb");
2887
+
2888
+					$file_content_compressed = '';
2889
+					$deflateContext = deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => 6]);
2890
+					while (($block = fread($f, 8192))) {
2891
+						$file_content_compressed .= deflate_add($deflateContext, $block, ZLIB_NO_FLUSH);
2892
+					}
2893
+					$file_content_compressed .= deflate_add($deflateContext, '', ZLIB_FINISH);
2894
+					$file_size_uncompressed = ftell($f);
2895
+					fclose($f);
2896
+				} else {
2897
+					$file_content = file_get_contents($info['filepath']);
2898
+					$file_size_uncompressed = mb_strlen($file_content, '8bit');
2899
+					$checksum = md5($file_content);
2900
+				}
2901
+
2902
+				if ($this->encrypted) {
2903
+					$this->encryptInit($id);
2904
+					$checksum = $this->ARC4($checksum);
2905
+					$file_content_compressed = $this->ARC4($file_content_compressed);
2906
+				}
2907
+				$file_size_compressed = mb_strlen($file_content_compressed, '8bit');
2908
+
2909
+				$res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
2910
+					" /Type/EmbeddedFile /Filter/FlateDecode" .
2911
+					" /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
2912
+
2913
+				return $res;
2914
+		}
2915
+
2916
+		return null;
2917
+	}
2918
+
2919
+	/**
2920
+	 * ARC4 functions
2921
+	 * A series of function to implement ARC4 encoding in PHP
2922
+	 */
2923
+
2924
+	/**
2925
+	 * calculate the 16 byte version of the 128 bit md5 digest of the string
2926
+	 *
2927
+	 * @param $string
2928
+	 * @return string
2929
+	 */
2930
+	function md5_16($string)
2931
+	{
2932
+		$tmp = md5($string);
2933
+		$out = '';
2934
+		for ($i = 0; $i <= 30; $i = $i + 2) {
2935
+			$out .= chr(hexdec(substr($tmp, $i, 2)));
2936
+		}
2937
+
2938
+		return $out;
2939
+	}
2940
+
2941
+	/**
2942
+	 * initialize the encryption for processing a particular object
2943
+	 *
2944
+	 * @param $id
2945
+	 */
2946
+	function encryptInit($id)
2947
+	{
2948
+		$tmp = $this->encryptionKey;
2949
+		$hex = dechex($id);
2950
+		if (mb_strlen($hex, '8bit') < 6) {
2951
+			$hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2952
+		}
2953
+		$tmp .= chr(hexdec(substr($hex, 4, 2)))
2954
+			. chr(hexdec(substr($hex, 2, 2)))
2955
+			. chr(hexdec(substr($hex, 0, 2)))
2956
+			. chr(0)
2957
+			. chr(0)
2958
+		;
2959
+		$key = $this->md5_16($tmp);
2960
+		$this->ARC4_init(substr($key, 0, 10));
2961
+	}
2962
+
2963
+	/**
2964
+	 * initialize the ARC4 encryption
2965
+	 *
2966
+	 * @param string $key
2967
+	 */
2968
+	function ARC4_init($key = '')
2969
+	{
2970
+		$this->arc4 = '';
2971
+
2972
+		// setup the control array
2973
+		if (mb_strlen($key, '8bit') == 0) {
2974
+			return;
2975
+		}
2976
+
2977
+		$k = '';
2978
+		while (mb_strlen($k, '8bit') < 256) {
2979
+			$k .= $key;
2980
+		}
2981
+
2982
+		$k = substr($k, 0, 256);
2983
+		for ($i = 0; $i < 256; $i++) {
2984
+			$this->arc4 .= chr($i);
2985
+		}
2986
+
2987
+		$j = 0;
2988
+
2989
+		for ($i = 0; $i < 256; $i++) {
2990
+			$t = $this->arc4[$i];
2991
+			$j = ($j + ord($t) + ord($k[$i])) % 256;
2992
+			$this->arc4[$i] = $this->arc4[$j];
2993
+			$this->arc4[$j] = $t;
2994
+		}
2995
+	}
2996
+
2997
+	/**
2998
+	 * ARC4 encrypt a text string
2999
+	 *
3000
+	 * @param $text
3001
+	 * @return string
3002
+	 */
3003
+	function ARC4($text)
3004
+	{
3005
+		$len = mb_strlen($text, '8bit');
3006
+		$a = 0;
3007
+		$b = 0;
3008
+		$c = $this->arc4;
3009
+		$out = '';
3010
+		for ($i = 0; $i < $len; $i++) {
3011
+			$a = ($a + 1) % 256;
3012
+			$t = $c[$a];
3013
+			$b = ($b + ord($t)) % 256;
3014
+			$c[$a] = $c[$b];
3015
+			$c[$b] = $t;
3016
+			$k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
3017
+			$out .= chr(ord($text[$i]) ^ $k);
3018
+		}
3019
+
3020
+		return $out;
3021
+	}
3022
+
3023
+	/**
3024
+	 * functions which can be called to adjust or add to the document
3025
+	 */
3026
+
3027
+	/**
3028
+	 * add a link in the document to an external URL
3029
+	 *
3030
+	 * @param $url
3031
+	 * @param $x0
3032
+	 * @param $y0
3033
+	 * @param $x1
3034
+	 * @param $y1
3035
+	 */
3036
+	function addLink($url, $x0, $y0, $x1, $y1)
3037
+	{
3038
+		$this->numObj++;
3039
+		$info = ['type' => 'link', 'url' => $url, 'rect' => [$x0, $y0, $x1, $y1]];
3040
+		$this->o_annotation($this->numObj, 'new', $info);
3041
+	}
3042
+
3043
+	/**
3044
+	 * add a link in the document to an internal destination (ie. within the document)
3045
+	 *
3046
+	 * @param $label
3047
+	 * @param $x0
3048
+	 * @param $y0
3049
+	 * @param $x1
3050
+	 * @param $y1
3051
+	 */
3052
+	function addInternalLink($label, $x0, $y0, $x1, $y1)
3053
+	{
3054
+		$this->numObj++;
3055
+		$info = ['type' => 'ilink', 'label' => $label, 'rect' => [$x0, $y0, $x1, $y1]];
3056
+		$this->o_annotation($this->numObj, 'new', $info);
3057
+	}
3058
+
3059
+	/**
3060
+	 * set the encryption of the document
3061
+	 * can be used to turn it on and/or set the passwords which it will have.
3062
+	 * also the functions that the user will have are set here, such as print, modify, add
3063
+	 *
3064
+	 * @param string $userPass
3065
+	 * @param string $ownerPass
3066
+	 * @param array $pc
3067
+	 */
3068
+	function setEncryption($userPass = '', $ownerPass = '', $pc = [])
3069
+	{
3070
+		$p = bindec("11000000");
3071
+
3072
+		$options = ['print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32];
3073
+
3074
+		foreach ($pc as $k => $v) {
3075
+			if ($v && isset($options[$k])) {
3076
+				$p += $options[$k];
3077
+			} else {
3078
+				if (isset($options[$v])) {
3079
+					$p += $options[$v];
3080
+				}
3081
+			}
3082
+		}
3083
+
3084
+		// implement encryption on the document
3085
+		if ($this->arc4_objnum == 0) {
3086
+			// then the block does not exist already, add it.
3087
+			$this->numObj++;
3088
+			if (mb_strlen($ownerPass) == 0) {
3089
+				$ownerPass = $userPass;
3090
+			}
3091
+
3092
+			$this->o_encryption($this->numObj, 'new', ['user' => $userPass, 'owner' => $ownerPass, 'p' => $p]);
3093
+		}
3094
+	}
3095
+
3096
+	/**
3097
+	 * should be used for internal checks, not implemented as yet
3098
+	 */
3099
+	function checkAllHere()
3100
+	{
3101
+	}
3102
+
3103
+	/**
3104
+	 * return the pdf stream as a string returned from the function
3105
+	 *
3106
+	 * @param bool $debug
3107
+	 * @return string
3108
+	 */
3109
+	function output($debug = false)
3110
+	{
3111
+		if ($debug) {
3112
+			// turn compression off
3113
+			$this->options['compression'] = false;
3114
+		}
3115
+
3116
+		if ($this->javascript) {
3117
+			$this->numObj++;
3118
+
3119
+			$js_id = $this->numObj;
3120
+			$this->o_embedjs($js_id, 'new');
3121
+			$this->o_javascript(++$this->numObj, 'new', $this->javascript);
3122
+
3123
+			$id = $this->catalogId;
3124
+
3125
+			$this->o_indirect_references($this->indirectReferenceId, 'add', ['JavaScript' => $js_id]);
3126
+		}
3127
+
3128
+		if ($this->fileIdentifier === '') {
3129
+			$tmp = implode('', $this->objects[$this->infoObject]['info']);
3130
+			$this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
3131
+		}
3132
+
3133
+		if ($this->arc4_objnum) {
3134
+			$this->o_encryption($this->arc4_objnum, 'keys');
3135
+			$this->ARC4_init($this->encryptionKey);
3136
+		}
3137
+
3138
+		$this->checkAllHere();
3139
+
3140
+		$xref = [];
3141
+		$content = '%PDF-' . self::PDF_VERSION;
3142
+		$pos = mb_strlen($content, '8bit');
3143
+
3144
+		// pre-process o_font objects before output of all objects
3145
+		foreach ($this->objects as $k => $v) {
3146
+			if ($v['t'] === 'font') {
3147
+				$this->o_font($k, 'add');
3148
+			}
3149
+		}
3150
+
3151
+		foreach ($this->objects as $k => $v) {
3152
+			$tmp = 'o_' . $v['t'];
3153
+			$cont = $this->$tmp($k, 'out');
3154
+			$content .= $cont;
3155
+			$xref[] = $pos + 1; //+1 to account for \n at the start of each object
3156
+			$pos += mb_strlen($cont, '8bit');
3157
+		}
3158
+
3159
+		$content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
3160
+
3161
+		foreach ($xref as $p) {
3162
+			$content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
3163
+		}
3164
+
3165
+		$content .= "trailer\n<<\n" .
3166
+			'/Size ' . (count($xref) + 1) . "\n" .
3167
+			'/Root 1 0 R' . "\n" .
3168
+			'/Info ' . $this->infoObject . " 0 R\n"
3169
+		;
3170
+
3171
+		// if encryption has been applied to this document then add the marker for this dictionary
3172
+		if ($this->arc4_objnum > 0) {
3173
+			$content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
3174
+		}
3175
+
3176
+		$content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
3177
+
3178
+		// account for \n added at start of xref table
3179
+		$pos++;
3180
+
3181
+		$content .= ">>\nstartxref\n$pos\n%%EOF\n";
3182
+
3183
+		if (count($this->byteRange) > 0) {
3184
+			foreach ($this->byteRange as $k => $v) {
3185
+				$tmp = 'o_' . $v['t'];
3186
+				$this->$tmp($k, 'byterange', ['content' => &$content]);
3187
+			}
3188
+		}
3189
+
3190
+		return $content;
3191
+	}
3192
+
3193
+	/**
3194
+	 * initialize a new document
3195
+	 * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
3196
+	 * this function is called automatically by the constructor function
3197
+	 *
3198
+	 * @param array $pageSize
3199
+	 */
3200
+	private function newDocument($pageSize = [0, 0, 612, 792])
3201
+	{
3202
+		$this->numObj = 0;
3203
+		$this->objects = [];
3204
+
3205
+		$this->numObj++;
3206
+		$this->o_catalog($this->numObj, 'new');
3207
+
3208
+		$this->numObj++;
3209
+		$this->o_outlines($this->numObj, 'new');
3210
+
3211
+		$this->numObj++;
3212
+		$this->o_pages($this->numObj, 'new');
3213
+
3214
+		$this->o_pages($this->numObj, 'mediaBox', $pageSize);
3215
+		$this->currentNode = 3;
3216
+
3217
+		$this->numObj++;
3218
+		$this->o_procset($this->numObj, 'new');
3219
+
3220
+		$this->numObj++;
3221
+		$this->o_info($this->numObj, 'new');
3222
+
3223
+		$this->numObj++;
3224
+		$this->o_page($this->numObj, 'new');
3225
+
3226
+		// need to store the first page id as there is no way to get it to the user during
3227
+		// startup
3228
+		$this->firstPageId = $this->currentContents;
3229
+	}
3230
+
3231
+	/**
3232
+	 * open the font file and return a php structure containing it.
3233
+	 * first check if this one has been done before and saved in a form more suited to php
3234
+	 * note that if a php serialized version does not exist it will try and make one, but will
3235
+	 * require write access to the directory to do it... it is MUCH faster to have these serialized
3236
+	 * files.
3237
+	 *
3238
+	 * @param $font
3239
+	 */
3240
+	private function openFont($font)
3241
+	{
3242
+		// assume that $font contains the path and file but not the extension
3243
+		$name = basename($font);
3244
+		$dir = dirname($font) . '/';
3245
+
3246
+		$fontcache = $this->fontcache;
3247
+		if ($fontcache == '') {
3248
+			$fontcache = rtrim($dir, DIRECTORY_SEPARATOR."/\\");
3249
+		}
3250
+
3251
+		//$name       filename without folder and extension of font metrics
3252
+		//$dir        folder of font metrics
3253
+		//$fontcache  folder of runtime created php serialized version of font metrics.
3254
+		//            If this is not given, the same folder as the font metrics will be used.
3255
+		//            Storing and reusing serialized versions improves speed much
3256
+
3257
+		$this->addMessage("openFont: $font - $name");
3258
+
3259
+		if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3260
+			$metrics_name = "$name.afm";
3261
+		} else {
3262
+			$metrics_name = "$name.ufm";
3263
+		}
3264
+
3265
+		$cache_name = "$metrics_name.php";
3266
+		$this->addMessage("metrics: $metrics_name, cache: $cache_name");
3267
+
3268
+		if (file_exists($fontcache . '/' . $cache_name)) {
3269
+			$this->addMessage("openFont: php file exists $fontcache/$cache_name");
3270
+			$this->fonts[$font] = require($fontcache . '/' . $cache_name);
3271
+
3272
+			if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
3273
+				// if the font file is old, then clear it out and prepare for re-creation
3274
+				$this->addMessage('openFont: clear out, make way for new version.');
3275
+				$this->fonts[$font] = null;
3276
+				unset($this->fonts[$font]);
3277
+			}
3278
+		} else {
3279
+			$old_cache_name = "php_$metrics_name";
3280
+			if (file_exists($fontcache . '/' . $old_cache_name)) {
3281
+				$this->addMessage(
3282
+					"openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format"
3283
+				);
3284
+				$old_cache = file_get_contents($fontcache . '/' . $old_cache_name);
3285
+				file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';');
3286
+
3287
+				$this->openFont($font);
3288
+				return;
3289
+			}
3290
+		}
3291
+
3292
+		if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) {
3293
+			// then rebuild the php_<font>.afm file from the <font>.afm file
3294
+			$this->addMessage("openFont: build php file from $dir$metrics_name");
3295
+			$data = [];
3296
+
3297
+			// 20 => 'space'
3298
+			$data['codeToName'] = [];
3299
+
3300
+			// Since we're not going to enable Unicode for the core fonts we need to use a font-based
3301
+			// setting for Unicode support rather than a global setting.
3302
+			$data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
3303
+
3304
+			$cidtogid = '';
3305
+			if ($data['isUnicode']) {
3306
+				$cidtogid = str_pad('', 256 * 256 * 2, "\x00");
3307
+			}
3308
+
3309
+			$file = file($dir . $metrics_name);
3310
+
3311
+			foreach ($file as $rowA) {
3312
+				$row = trim($rowA);
3313
+				$pos = strpos($row, ' ');
3314
+
3315
+				if ($pos) {
3316
+					// then there must be some keyword
3317
+					$key = substr($row, 0, $pos);
3318
+					switch ($key) {
3319
+						case 'FontName':
3320
+						case 'FullName':
3321
+						case 'FamilyName':
3322
+						case 'PostScriptName':
3323
+						case 'Weight':
3324
+						case 'ItalicAngle':
3325
+						case 'IsFixedPitch':
3326
+						case 'CharacterSet':
3327
+						case 'UnderlinePosition':
3328
+						case 'UnderlineThickness':
3329
+						case 'Version':
3330
+						case 'EncodingScheme':
3331
+						case 'CapHeight':
3332
+						case 'XHeight':
3333
+						case 'Ascender':
3334
+						case 'Descender':
3335
+						case 'StdHW':
3336
+						case 'StdVW':
3337
+						case 'StartCharMetrics':
3338
+						case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font.  Otherwise it's too big.
3339
+							$data[$key] = trim(substr($row, $pos));
3340
+							break;
3341
+
3342
+						case 'FontBBox':
3343
+							$data[$key] = explode(' ', trim(substr($row, $pos)));
3344
+							break;
3345
+
3346
+						//C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
3347
+						case 'C': // Found in AFM files
3348
+							$bits = explode(';', trim($row));
3349
+							$dtmp = ['C' => null, 'N' => null, 'WX' => null, 'B' => []];
3350
+
3351
+							foreach ($bits as $bit) {
3352
+								$bits2 = explode(' ', trim($bit));
3353
+								if (mb_strlen($bits2[0], '8bit') == 0) {
3354
+									continue;
3355
+								}
3356
+
3357
+								if (count($bits2) > 2) {
3358
+									$dtmp[$bits2[0]] = [];
3359
+									for ($i = 1; $i < count($bits2); $i++) {
3360
+										$dtmp[$bits2[0]][] = $bits2[$i];
3361
+									}
3362
+								} else {
3363
+									if (count($bits2) == 2) {
3364
+										$dtmp[$bits2[0]] = $bits2[1];
3365
+									}
3366
+								}
3367
+							}
3368
+
3369
+							$c = (int)$dtmp['C'];
3370
+							$n = $dtmp['N'];
3371
+							$width = floatval($dtmp['WX']);
3372
+
3373
+							if ($c >= 0) {
3374
+								if (!ctype_xdigit($n) || $c != hexdec($n)) {
3375
+									$data['codeToName'][$c] = $n;
3376
+								}
3377
+								$data['C'][$c] = $width;
3378
+							} elseif (isset($n)) {
3379
+								$data['C'][$n] = $width;
3380
+							}
3381
+
3382
+							if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3383
+								$data['MissingWidth'] = $width;
3384
+							}
3385
+
3386
+							break;
3387
+
3388
+						// U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
3389
+						case 'U': // Found in UFM files
3390
+							if (!$data['isUnicode']) {
3391
+								break;
3392
+							}
3393
+
3394
+							$bits = explode(';', trim($row));
3395
+							$dtmp = ['G' => null, 'N' => null, 'U' => null, 'WX' => null];
3396
+
3397
+							foreach ($bits as $bit) {
3398
+								$bits2 = explode(' ', trim($bit));
3399
+								if (mb_strlen($bits2[0], '8bit') === 0) {
3400
+									continue;
3401
+								}
3402
+
3403
+								if (count($bits2) > 2) {
3404
+									$dtmp[$bits2[0]] = [];
3405
+									for ($i = 1; $i < count($bits2); $i++) {
3406
+										$dtmp[$bits2[0]][] = $bits2[$i];
3407
+									}
3408
+								} else {
3409
+									if (count($bits2) == 2) {
3410
+										$dtmp[$bits2[0]] = $bits2[1];
3411
+									}
3412
+								}
3413
+							}
3414
+
3415
+							$c = (int)$dtmp['U'];
3416
+							$n = $dtmp['N'];
3417
+							$glyph = $dtmp['G'];
3418
+							$width = floatval($dtmp['WX']);
3419
+
3420
+							if ($c >= 0) {
3421
+								// Set values in CID to GID map
3422
+								if ($c >= 0 && $c < 0xFFFF && $glyph) {
3423
+									$cidtogid[$c * 2] = chr($glyph >> 8);
3424
+									$cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
3425
+								}
3426
+
3427
+								if (!ctype_xdigit($n) || $c != hexdec($n)) {
3428
+									$data['codeToName'][$c] = $n;
3429
+								}
3430
+								$data['C'][$c] = $width;
3431
+							} elseif (isset($n)) {
3432
+								$data['C'][$n] = $width;
3433
+							}
3434
+
3435
+							if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3436
+								$data['MissingWidth'] = $width;
3437
+							}
3438
+
3439
+							break;
3440
+
3441
+						case 'KPX':
3442
+							break; // don't include them as they are not used yet
3443
+							//KPX Adieresis yacute -40
3444
+							/*$bits = explode(' ', trim($row));
3445 3445
                             $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
3446 3446
                             break;*/
3447
-                    }
3448
-                }
3449
-            }
3450
-
3451
-            if ($this->compressionReady && $this->options['compression']) {
3452
-                // then implement ZLIB based compression on CIDtoGID string
3453
-                $data['CIDtoGID_Compressed'] = true;
3454
-                $cidtogid = gzcompress($cidtogid, 6);
3455
-            }
3456
-            $data['CIDtoGID'] = base64_encode($cidtogid);
3457
-            $data['_version_'] = $this->fontcacheVersion;
3458
-            $this->fonts[$font] = $data;
3459
-
3460
-            //Because of potential trouble with php safe mode, expect that the folder already exists.
3461
-            //If not existing, this will hit performance because of missing cached results.
3462
-            if (is_dir($fontcache) && is_writable($fontcache)) {
3463
-                file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';');
3464
-            }
3465
-            $data = null;
3466
-        }
3467
-
3468
-        if (!isset($this->fonts[$font])) {
3469
-            $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3470
-        }
3471
-
3472
-        //pre_r($this->messages);
3473
-    }
3474
-
3475
-    /**
3476
-     * if the font is not loaded then load it and make the required object
3477
-     * else just make it the current font
3478
-     * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
3479
-     * note that encoding='none' will need to be used for symbolic fonts
3480
-     * and 'differences' => an array of mappings between numbers 0->255 and character names.
3481
-     *
3482
-     * @param $fontName
3483
-     * @param string $encoding
3484
-     * @param bool $set
3485
-     * @param bool $isSubsetting
3486
-     * @return int
3487
-     * @throws FontNotFoundException
3488
-     */
3489
-    function selectFont($fontName, $encoding = '', $set = true, $isSubsetting = true)
3490
-    {
3491
-        if ($fontName === null || $fontName === '') {
3492
-            return $this->currentFontNum;
3493
-        }
3494
-
3495
-        $ext = substr($fontName, -4);
3496
-        if ($ext === '.afm' || $ext === '.ufm') {
3497
-            $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
3498
-        }
3499
-
3500
-        if (!isset($this->fonts[$fontName])) {
3501
-            $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
3502
-
3503
-            // load the file
3504
-            $this->openFont($fontName);
3505
-
3506
-            if (isset($this->fonts[$fontName])) {
3507
-                $this->numObj++;
3508
-                $this->numFonts++;
3509
-
3510
-                $font = &$this->fonts[$fontName];
3511
-
3512
-                $name = basename($fontName);
3513
-                $options = ['name' => $name, 'fontFileName' => $fontName, 'isSubsetting' => $isSubsetting];
3514
-
3515
-                if (is_array($encoding)) {
3516
-                    // then encoding and differences might be set
3517
-                    if (isset($encoding['encoding'])) {
3518
-                        $options['encoding'] = $encoding['encoding'];
3519
-                    }
3520
-
3521
-                    if (isset($encoding['differences'])) {
3522
-                        $options['differences'] = $encoding['differences'];
3523
-                    }
3524
-                } else {
3525
-                    if (mb_strlen($encoding, '8bit')) {
3526
-                        // then perhaps only the encoding has been set
3527
-                        $options['encoding'] = $encoding;
3528
-                    }
3529
-                }
3530
-
3531
-                $this->o_font($this->numObj, 'new', $options);
3532
-
3533
-                if (file_exists("$fontName.ttf")) {
3534
-                    $fileSuffix = 'ttf';
3535
-                } elseif (file_exists("$fontName.TTF")) {
3536
-                    $fileSuffix = 'TTF';
3537
-                } elseif (file_exists("$fontName.pfb")) {
3538
-                    $fileSuffix = 'pfb';
3539
-                } elseif (file_exists("$fontName.PFB")) {
3540
-                    $fileSuffix = 'PFB';
3541
-                } else {
3542
-                    $fileSuffix = '';
3543
-                }
3544
-
3545
-                $font['fileSuffix'] = $fileSuffix;
3546
-
3547
-                $font['fontNum'] = $this->numFonts;
3548
-                $font['isSubsetting'] = $isSubsetting && $font['isUnicode'] && strtolower($fileSuffix) === 'ttf';
3549
-
3550
-                // also set the differences here, note that this means that these will take effect only the
3551
-                //first time that a font is selected, else they are ignored
3552
-                if (isset($options['differences'])) {
3553
-                    $font['differences'] = $options['differences'];
3554
-                }
3555
-            }
3556
-        }
3557
-
3558
-        if ($set && isset($this->fonts[$fontName])) {
3559
-            // so if for some reason the font was not set in the last one then it will not be selected
3560
-            $this->currentBaseFont = $fontName;
3561
-
3562
-            // the next lines mean that if a new font is selected, then the current text state will be
3563
-            // applied to it as well.
3564
-            $this->currentFont = $this->currentBaseFont;
3565
-            $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3566
-        }
3567
-
3568
-        return $this->currentFontNum;
3569
-    }
3570
-
3571
-    /**
3572
-     * sets up the current font, based on the font families, and the current text state
3573
-     * note that this system is quite flexible, a bold-italic font can be completely different to a
3574
-     * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3575
-     * This function is to be called whenever the currentTextState is changed, it will update
3576
-     * the currentFont setting to whatever the appropriate family one is.
3577
-     * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3578
-     * This function will change the currentFont to whatever it should be, but will not change the
3579
-     * currentBaseFont.
3580
-     */
3581
-    private function setCurrentFont()
3582
-    {
3583
-        //   if (strlen($this->currentBaseFont) == 0){
3584
-        //     // then assume an initial font
3585
-        //     $this->selectFont($this->defaultFont);
3586
-        //   }
3587
-        //   $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3588
-        //   if (strlen($this->currentTextState)
3589
-        //     && isset($this->fontFamilies[$cf])
3590
-        //       && isset($this->fontFamilies[$cf][$this->currentTextState])){
3591
-        //     // then we are in some state or another
3592
-        //     // and this font has a family, and the current setting exists within it
3593
-        //     // select the font, then return it
3594
-        //     $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3595
-        //     $this->selectFont($nf,'',0);
3596
-        //     $this->currentFont = $nf;
3597
-        //     $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3598
-        //   } else {
3599
-        //     // the this font must not have the right family member for the current state
3600
-        //     // simply assume the base font
3601
-        $this->currentFont = $this->currentBaseFont;
3602
-        $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3603
-        //  }
3604
-    }
3605
-
3606
-    /**
3607
-     * function for the user to find out what the ID is of the first page that was created during
3608
-     * startup - useful if they wish to add something to it later.
3609
-     *
3610
-     * @return int
3611
-     */
3612
-    function getFirstPageId()
3613
-    {
3614
-        return $this->firstPageId;
3615
-    }
3616
-
3617
-    /**
3618
-     * add content to the currently active object
3619
-     *
3620
-     * @param $content
3621
-     */
3622
-    private function addContent($content)
3623
-    {
3624
-        $this->objects[$this->currentContents]['c'] .= $content;
3625
-    }
3626
-
3627
-    /**
3628
-     * sets the color for fill operations
3629
-     *
3630
-     * @param $color
3631
-     * @param bool $force
3632
-     */
3633
-    function setColor($color, $force = false)
3634
-    {
3635
-        $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3636
-
3637
-        if (!$force && $this->currentColor == $new_color) {
3638
-            return;
3639
-        }
3640
-
3641
-        if (isset($new_color[3])) {
3642
-            $this->currentColor = $new_color;
3643
-            $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3644
-        } else {
3645
-            if (isset($new_color[2])) {
3646
-                $this->currentColor = $new_color;
3647
-                $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3648
-            }
3649
-        }
3650
-    }
3651
-
3652
-    /**
3653
-     * sets the color for fill operations
3654
-     *
3655
-     * @param $fillRule
3656
-     */
3657
-    function setFillRule($fillRule)
3658
-    {
3659
-        if (!in_array($fillRule, ["nonzero", "evenodd"])) {
3660
-            return;
3661
-        }
3662
-
3663
-        $this->fillRule = $fillRule;
3664
-    }
3665
-
3666
-    /**
3667
-     * sets the color for stroke operations
3668
-     *
3669
-     * @param $color
3670
-     * @param bool $force
3671
-     */
3672
-    function setStrokeColor($color, $force = false)
3673
-    {
3674
-        $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3675
-
3676
-        if (!$force && $this->currentStrokeColor == $new_color) {
3677
-            return;
3678
-        }
3679
-
3680
-        if (isset($new_color[3])) {
3681
-            $this->currentStrokeColor = $new_color;
3682
-            $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3683
-        } else {
3684
-            if (isset($new_color[2])) {
3685
-                $this->currentStrokeColor = $new_color;
3686
-                $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3687
-            }
3688
-        }
3689
-    }
3690
-
3691
-    /**
3692
-     * Set the graphics state for compositions
3693
-     *
3694
-     * @param $parameters
3695
-     */
3696
-    function setGraphicsState($parameters)
3697
-    {
3698
-        // Create a new graphics state object if necessary
3699
-        if (($gstate = array_search($parameters, $this->gstates)) === false) {
3700
-            $this->numObj++;
3701
-            $this->o_extGState($this->numObj, 'new', $parameters);
3702
-            $gstate = $this->numStates;
3703
-            $this->gstates[$gstate] = $parameters;
3704
-        }
3705
-        $this->addContent("\n/GS$gstate gs");
3706
-    }
3707
-
3708
-    /**
3709
-     * Set current blend mode & opacity for lines.
3710
-     *
3711
-     * Valid blend modes are:
3712
-     *
3713
-     * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3714
-     * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3715
-     * Exclusion
3716
-     *
3717
-     * @param string $mode    the blend mode to use
3718
-     * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3719
-     */
3720
-    function setLineTransparency($mode, $opacity)
3721
-    {
3722
-        static $blend_modes = [
3723
-            "Normal",
3724
-            "Multiply",
3725
-            "Screen",
3726
-            "Overlay",
3727
-            "Darken",
3728
-            "Lighten",
3729
-            "ColorDogde",
3730
-            "ColorBurn",
3731
-            "HardLight",
3732
-            "SoftLight",
3733
-            "Difference",
3734
-            "Exclusion"
3735
-        ];
3736
-
3737
-        if (!in_array($mode, $blend_modes)) {
3738
-            $mode = "Normal";
3739
-        }
3740
-
3741
-        if (is_null($this->currentLineTransparency)) {
3742
-            $this->currentLineTransparency = [];
3743
-        }
3744
-
3745
-        if ($mode === (key_exists('mode', $this->currentLineTransparency) ?
3746
-            $this->currentLineTransparency['mode'] : '') &&
3747
-            $opacity === (key_exists('opacity', $this->currentLineTransparency) ?
3748
-            $this->currentLineTransparency["opacity"] : '')) {
3749
-            return;
3750
-        }
3751
-
3752
-        $this->currentLineTransparency["mode"] = $mode;
3753
-        $this->currentLineTransparency["opacity"] = $opacity;
3754
-
3755
-        $options = [
3756
-            "BM" => "/$mode",
3757
-            "CA" => (float)$opacity
3758
-        ];
3759
-
3760
-        $this->setGraphicsState($options);
3761
-    }
3762
-
3763
-    /**
3764
-     * Set current blend mode & opacity for filled objects.
3765
-     *
3766
-     * Valid blend modes are:
3767
-     *
3768
-     * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3769
-     * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3770
-     * Exclusion
3771
-     *
3772
-     * @param string $mode    the blend mode to use
3773
-     * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3774
-     */
3775
-    function setFillTransparency($mode, $opacity)
3776
-    {
3777
-        static $blend_modes = [
3778
-            "Normal",
3779
-            "Multiply",
3780
-            "Screen",
3781
-            "Overlay",
3782
-            "Darken",
3783
-            "Lighten",
3784
-            "ColorDogde",
3785
-            "ColorBurn",
3786
-            "HardLight",
3787
-            "SoftLight",
3788
-            "Difference",
3789
-            "Exclusion"
3790
-        ];
3791
-
3792
-        if (!in_array($mode, $blend_modes)) {
3793
-            $mode = "Normal";
3794
-        }
3795
-
3796
-        if (is_null($this->currentFillTransparency)) {
3797
-            $this->currentFillTransparency = [];
3798
-        }
3799
-
3800
-        if ($mode === (key_exists('mode', $this->currentFillTransparency) ?
3801
-            $this->currentFillTransparency['mode'] : '') &&
3802
-            $opacity === (key_exists('opacity', $this->currentFillTransparency) ?
3803
-            $this->currentFillTransparency["opacity"] : '')) {
3804
-            return;
3805
-        }
3806
-
3807
-        $this->currentFillTransparency["mode"] = $mode;
3808
-        $this->currentFillTransparency["opacity"] = $opacity;
3809
-
3810
-        $options = [
3811
-            "BM" => "/$mode",
3812
-            "ca" => (float)$opacity,
3813
-        ];
3814
-
3815
-        $this->setGraphicsState($options);
3816
-    }
3817
-
3818
-    /**
3819
-     * draw a line from one set of coordinates to another
3820
-     *
3821
-     * @param $x1
3822
-     * @param $y1
3823
-     * @param $x2
3824
-     * @param $y2
3825
-     * @param bool $stroke
3826
-     */
3827
-    function line($x1, $y1, $x2, $y2, $stroke = true)
3828
-    {
3829
-        $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3830
-
3831
-        if ($stroke) {
3832
-            $this->addContent(' S');
3833
-        }
3834
-    }
3835
-
3836
-    /**
3837
-     * draw a bezier curve based on 4 control points
3838
-     *
3839
-     * @param $x0
3840
-     * @param $y0
3841
-     * @param $x1
3842
-     * @param $y1
3843
-     * @param $x2
3844
-     * @param $y2
3845
-     * @param $x3
3846
-     * @param $y3
3847
-     */
3848
-    function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3849
-    {
3850
-        // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3851
-        // as the control points for the curve.
3852
-        $this->addContent(
3853
-            sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3854
-        );
3855
-    }
3856
-
3857
-    /**
3858
-     * draw a part of an ellipse
3859
-     *
3860
-     * @param $x0
3861
-     * @param $y0
3862
-     * @param $astart
3863
-     * @param $afinish
3864
-     * @param $r1
3865
-     * @param int $r2
3866
-     * @param int $angle
3867
-     * @param int $nSeg
3868
-     */
3869
-    function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3870
-    {
3871
-        $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3872
-    }
3873
-
3874
-    /**
3875
-     * draw a filled ellipse
3876
-     *
3877
-     * @param $x0
3878
-     * @param $y0
3879
-     * @param $r1
3880
-     * @param int $r2
3881
-     * @param int $angle
3882
-     * @param int $nSeg
3883
-     * @param int $astart
3884
-     * @param int $afinish
3885
-     */
3886
-    function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3887
-    {
3888
-        $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3889
-    }
3890
-
3891
-    /**
3892
-     * @param $x
3893
-     * @param $y
3894
-     */
3895
-    function lineTo($x, $y)
3896
-    {
3897
-        $this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3898
-    }
3899
-
3900
-    /**
3901
-     * @param $x
3902
-     * @param $y
3903
-     */
3904
-    function moveTo($x, $y)
3905
-    {
3906
-        $this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3907
-    }
3908
-
3909
-    /**
3910
-     * draw a bezier curve based on 4 control points
3911
-     *
3912
-     * @param $x1
3913
-     * @param $y1
3914
-     * @param $x2
3915
-     * @param $y2
3916
-     * @param $x3
3917
-     * @param $y3
3918
-     */
3919
-    function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3920
-    {
3921
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3922
-    }
3923
-
3924
-    /**
3925
-     * draw a bezier curve based on 4 control points
3926
-     */
3927
-    function quadTo($cpx, $cpy, $x, $y)
3928
-    {
3929
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3930
-    }
3931
-
3932
-    function closePath()
3933
-    {
3934
-        $this->addContent(' h');
3935
-    }
3936
-
3937
-    function endPath()
3938
-    {
3939
-        $this->addContent(' n');
3940
-    }
3941
-
3942
-    /**
3943
-     * draw an ellipse
3944
-     * note that the part and filled ellipse are just special cases of this function
3945
-     *
3946
-     * draws an ellipse in the current line style
3947
-     * centered at $x0,$y0, radii $r1,$r2
3948
-     * if $r2 is not set, then a circle is drawn
3949
-     * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3950
-     * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3951
-     * pretty crappy shape at 2, as we are approximating with bezier curves.
3952
-     *
3953
-     * @param $x0
3954
-     * @param $y0
3955
-     * @param $r1
3956
-     * @param int $r2
3957
-     * @param int $angle
3958
-     * @param int $nSeg
3959
-     * @param int $astart
3960
-     * @param int $afinish
3961
-     * @param bool $close
3962
-     * @param bool $fill
3963
-     * @param bool $stroke
3964
-     * @param bool $incomplete
3965
-     */
3966
-    function ellipse(
3967
-        $x0,
3968
-        $y0,
3969
-        $r1,
3970
-        $r2 = 0,
3971
-        $angle = 0,
3972
-        $nSeg = 8,
3973
-        $astart = 0,
3974
-        $afinish = 360,
3975
-        $close = true,
3976
-        $fill = false,
3977
-        $stroke = true,
3978
-        $incomplete = false
3979
-    ) {
3980
-        if ($r1 == 0) {
3981
-            return;
3982
-        }
3983
-
3984
-        if ($r2 == 0) {
3985
-            $r2 = $r1;
3986
-        }
3987
-
3988
-        if ($nSeg < 2) {
3989
-            $nSeg = 2;
3990
-        }
3991
-
3992
-        $astart = deg2rad((float)$astart);
3993
-        $afinish = deg2rad((float)$afinish);
3994
-        $totalAngle = $afinish - $astart;
3995
-
3996
-        $dt = $totalAngle / $nSeg;
3997
-        $dtm = $dt / 3;
3998
-
3999
-        if ($angle != 0) {
4000
-            $a = -1 * deg2rad((float)$angle);
4001
-
4002
-            $this->addContent(
4003
-                sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
4004
-            );
4005
-
4006
-            $x0 = 0;
4007
-            $y0 = 0;
4008
-        }
4009
-
4010
-        $t1 = $astart;
4011
-        $a0 = $x0 + $r1 * cos($t1);
4012
-        $b0 = $y0 + $r2 * sin($t1);
4013
-        $c0 = -$r1 * sin($t1);
4014
-        $d0 = $r2 * cos($t1);
4015
-
4016
-        if (!$incomplete) {
4017
-            $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
4018
-        }
4019
-
4020
-        for ($i = 1; $i <= $nSeg; $i++) {
4021
-            // draw this bit of the total curve
4022
-            $t1 = $i * $dt + $astart;
4023
-            $a1 = $x0 + $r1 * cos($t1);
4024
-            $b1 = $y0 + $r2 * sin($t1);
4025
-            $c1 = -$r1 * sin($t1);
4026
-            $d1 = $r2 * cos($t1);
4027
-
4028
-            $this->addContent(
4029
-                sprintf(
4030
-                    "\n%.3F %.3F %.3F %.3F %.3F %.3F c",
4031
-                    ($a0 + $c0 * $dtm),
4032
-                    ($b0 + $d0 * $dtm),
4033
-                    ($a1 - $c1 * $dtm),
4034
-                    ($b1 - $d1 * $dtm),
4035
-                    $a1,
4036
-                    $b1
4037
-                )
4038
-            );
4039
-
4040
-            $a0 = $a1;
4041
-            $b0 = $b1;
4042
-            $c0 = $c1;
4043
-            $d0 = $d1;
4044
-        }
4045
-
4046
-        if (!$incomplete) {
4047
-            if ($fill) {
4048
-                $this->addContent(' f');
4049
-            }
4050
-
4051
-            if ($stroke) {
4052
-                if ($close) {
4053
-                    $this->addContent(' s'); // small 's' signifies closing the path as well
4054
-                } else {
4055
-                    $this->addContent(' S');
4056
-                }
4057
-            }
4058
-        }
4059
-
4060
-        if ($angle != 0) {
4061
-            $this->addContent(' Q');
4062
-        }
4063
-    }
4064
-
4065
-    /**
4066
-     * this sets the line drawing style.
4067
-     * width, is the thickness of the line in user units
4068
-     * cap is the type of cap to put on the line, values can be 'butt','round','square'
4069
-     *    where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
4070
-     *    end of the line.
4071
-     * join can be 'miter', 'round', 'bevel'
4072
-     * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
4073
-     *   on and off dashes.
4074
-     *   (2) represents 2 on, 2 off, 2 on , 2 off ...
4075
-     *   (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
4076
-     * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
4077
-     *
4078
-     * @param int $width
4079
-     * @param string $cap
4080
-     * @param string $join
4081
-     * @param string $dash
4082
-     * @param int $phase
4083
-     */
4084
-    function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
4085
-    {
4086
-        // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
4087
-        $string = '';
4088
-
4089
-        if ($width > 0) {
4090
-            $string .= "$width w";
4091
-        }
4092
-
4093
-        $ca = ['butt' => 0, 'round' => 1, 'square' => 2];
4094
-
4095
-        if (isset($ca[$cap])) {
4096
-            $string .= " $ca[$cap] J";
4097
-        }
4098
-
4099
-        $ja = ['miter' => 0, 'round' => 1, 'bevel' => 2];
4100
-
4101
-        if (isset($ja[$join])) {
4102
-            $string .= " $ja[$join] j";
4103
-        }
4104
-
4105
-        if (is_array($dash)) {
4106
-            $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
4107
-        }
4108
-
4109
-        $this->currentLineStyle = $string;
4110
-        $this->addContent("\n$string");
4111
-    }
4112
-
4113
-    /**
4114
-     * draw a polygon, the syntax for this is similar to the GD polygon command
4115
-     *
4116
-     * @param $p
4117
-     * @param $np
4118
-     * @param bool $f
4119
-     */
4120
-    function polygon($p, $np, $f = false)
4121
-    {
4122
-        $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4123
-
4124
-        for ($i = 2; $i < $np * 2; $i = $i + 2) {
4125
-            $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4126
-        }
4127
-
4128
-        if ($f) {
4129
-            $this->addContent(' f');
4130
-        } else {
4131
-            $this->addContent(' S');
4132
-        }
4133
-    }
4134
-
4135
-    /**
4136
-     * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4137
-     * the coordinates of the upper-right corner
4138
-     *
4139
-     * @param $x1
4140
-     * @param $y1
4141
-     * @param $width
4142
-     * @param $height
4143
-     */
4144
-    function filledRectangle($x1, $y1, $width, $height)
4145
-    {
4146
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
4147
-    }
4148
-
4149
-    /**
4150
-     * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4151
-     * the coordinates of the upper-right corner
4152
-     *
4153
-     * @param $x1
4154
-     * @param $y1
4155
-     * @param $width
4156
-     * @param $height
4157
-     */
4158
-    function rectangle($x1, $y1, $width, $height)
4159
-    {
4160
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
4161
-    }
4162
-
4163
-    /**
4164
-     * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4165
-     * the coordinates of the upper-right corner
4166
-     *
4167
-     * @param $x1
4168
-     * @param $y1
4169
-     * @param $width
4170
-     * @param $height
4171
-     */
4172
-    function rect($x1, $y1, $width, $height)
4173
-    {
4174
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
4175
-    }
4176
-
4177
-    function stroke(bool $close = false)
4178
-    {
4179
-        $this->addContent("\n" . ($close ? "s" : "S"));
4180
-    }
4181
-
4182
-    function fill()
4183
-    {
4184
-        $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
4185
-    }
4186
-
4187
-    function fillStroke(bool $close = false)
4188
-    {
4189
-        $this->addContent("\n" . ($close ? "b" : "B") . ($this->fillRule === "evenodd" ? "*" : ""));
4190
-    }
4191
-
4192
-    /**
4193
-     * @param string $subtype
4194
-     * @param integer $x
4195
-     * @param integer $y
4196
-     * @param integer $w
4197
-     * @param integer $h
4198
-     * @return int
4199
-     */
4200
-    function addXObject($subtype, $x, $y, $w, $h)
4201
-    {
4202
-        $id = ++$this->numObj;
4203
-        $this->o_xobject($id, 'new', ['Subtype' => $subtype, 'bbox' => [$x, $y, $w, $h]]);
4204
-        return $id;
4205
-    }
4206
-
4207
-    /**
4208
-     * @param integer $numXObject
4209
-     * @param string $type
4210
-     * @param array $options
4211
-     */
4212
-    function setXObjectResource($numXObject, $type, $options)
4213
-    {
4214
-        if (in_array($type, ['procset', 'font', 'xObject'])) {
4215
-            $this->o_xobject($numXObject, $type, $options);
4216
-        }
4217
-    }
4218
-
4219
-    /**
4220
-     * add signature
4221
-     *
4222
-     * $fieldSigId = $cpdf->addFormField(Cpdf::ACROFORM_FIELD_SIG, 'Signature1', 0, 0, 0, 0, 0);
4223
-     *
4224
-     * $signatureId = $cpdf->addSignature([
4225
-     *   'signcert' => file_get_contents('dompdf.crt'),
4226
-     *   'privkey' => file_get_contents('dompdf.key'),
4227
-     *   'password' => 'password',
4228
-     *   'name' => 'DomPDF DEMO',
4229
-     *   'location' => 'Home',
4230
-     *   'reason' => 'First Form',
4231
-     *   'contactinfo' => 'info'
4232
-     * ]);
4233
-     * $cpdf->setFormFieldValue($fieldSigId, "$signatureId 0 R");
4234
-     *
4235
-     * @param string $signcert
4236
-     * @param string $privkey
4237
-     * @param string $password
4238
-     * @param string|null $name
4239
-     * @param string|null $location
4240
-     * @param string|null $reason
4241
-     * @param string|null $contactinfo
4242
-     * @return int
4243
-     */
4244
-    function addSignature($signcert, $privkey, $password = '', $name = null, $location = null, $reason = null, $contactinfo = null) {
4245
-        $sigId = ++$this->numObj;
4246
-        $this->o_sig($sigId, 'new', [
4247
-          'SignCert' => $signcert,
4248
-          'PrivKey' => $privkey,
4249
-          'Password' => $password,
4250
-          'Name' => $name,
4251
-          'Location' => $location,
4252
-          'Reason' => $reason,
4253
-          'ContactInfo' => $contactinfo
4254
-        ]);
4255
-
4256
-        return $sigId;
4257
-    }
4258
-
4259
-    /**
4260
-     * add field to form
4261
-     *
4262
-     * @param string $type ACROFORM_FIELD_*
4263
-     * @param string $name
4264
-     * @param $x0
4265
-     * @param $y0
4266
-     * @param $x1
4267
-     * @param $y1
4268
-     * @param integer $ff Field Flag ACROFORM_FIELD_*_*
4269
-     * @param float $size
4270
-     * @param array $color
4271
-     * @return int
4272
-     */
4273
-    public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
4274
-    {
4275
-        if (!$this->numFonts) {
4276
-            $this->selectFont($this->defaultFont);
4277
-        }
4278
-
4279
-        $color = implode(' ', $color) . ' rg';
4280
-
4281
-        $currentFontNum = $this->currentFontNum;
4282
-        $font = array_filter($this->objects[$this->currentNode]['info']['fonts'],
4283
-          function($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; });
4284
-
4285
-        $this->o_acroform($this->acroFormId, 'font',
4286
-          ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
4287
-
4288
-        $fieldId = ++$this->numObj;
4289
-        $this->o_field($fieldId, 'new', [
4290
-          'rect' => [$x0, $y0, $x1, $y1],
4291
-          'F' => 4,
4292
-          'FT' => "/$type",
4293
-          'T' => $name,
4294
-          'Ff' => $ff,
4295
-          'pageid' => $this->currentPage,
4296
-          'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
4297
-        ]);
4298
-
4299
-        return $fieldId;
4300
-    }
4301
-
4302
-    /**
4303
-     * set Field value
4304
-     *
4305
-     * @param integer $numFieldObj
4306
-     * @param string $value
4307
-     */
4308
-    public function setFormFieldValue($numFieldObj, $value)
4309
-    {
4310
-        $this->o_field($numFieldObj, 'set', ['value' => $value]);
4311
-    }
4312
-
4313
-    /**
4314
-     * set Field value (reference)
4315
-     *
4316
-     * @param integer $numFieldObj
4317
-     * @param integer $numObj Object number
4318
-     */
4319
-    public function setFormFieldRefValue($numFieldObj, $numObj)
4320
-    {
4321
-        $this->o_field($numFieldObj, 'set', ['refvalue' => $numObj]);
4322
-    }
4323
-
4324
-    /**
4325
-     * set Field Appearanc (reference)
4326
-     *
4327
-     * @param integer $numFieldObj
4328
-     * @param integer $normalNumObj
4329
-     * @param integer|null $rolloverNumObj
4330
-     * @param integer|null $downNumObj
4331
-     */
4332
-    public function setFormFieldAppearance($numFieldObj, $normalNumObj, $rolloverNumObj = null, $downNumObj = null)
4333
-    {
4334
-        $appearance['N'] = $normalNumObj;
4335
-
4336
-        if ($rolloverNumObj !== null) {
4337
-            $appearance['R'] = $rolloverNumObj;
4338
-        }
4339
-
4340
-        if ($downNumObj !== null) {
4341
-            $appearance['D'] = $downNumObj;
4342
-        }
4343
-
4344
-        $this->o_field($numFieldObj, 'set', ['appearance' => $appearance]);
4345
-    }
4346
-
4347
-    /**
4348
-     * set Choice Field option values
4349
-     *
4350
-     * @param integer $numFieldObj
4351
-     * @param array $value
4352
-     */
4353
-    public function setFormFieldOpt($numFieldObj, $value)
4354
-    {
4355
-        $this->o_field($numFieldObj, 'set', ['options' => $value]);
4356
-    }
4357
-
4358
-    /**
4359
-     * add form to document
4360
-     *
4361
-     * @param integer $sigFlags
4362
-     * @param boolean $needAppearances
4363
-     */
4364
-    public function addForm($sigFlags = 0, $needAppearances = false)
4365
-    {
4366
-        $this->acroFormId = ++$this->numObj;
4367
-        $this->o_acroform($this->acroFormId, 'new', [
4368
-          'NeedAppearances' => $needAppearances ? 'true' : 'false',
4369
-          'SigFlags' => $sigFlags
4370
-        ]);
4371
-    }
4372
-
4373
-    /**
4374
-     * save the current graphic state
4375
-     */
4376
-    function save()
4377
-    {
4378
-        // we must reset the color cache or it will keep bad colors after clipping
4379
-        $this->currentColor = null;
4380
-        $this->currentStrokeColor = null;
4381
-        $this->addContent("\nq");
4382
-    }
4383
-
4384
-    /**
4385
-     * restore the last graphic state
4386
-     */
4387
-    function restore()
4388
-    {
4389
-        // we must reset the color cache or it will keep bad colors after clipping
4390
-        $this->currentColor = null;
4391
-        $this->currentStrokeColor = null;
4392
-        $this->addContent("\nQ");
4393
-    }
4394
-
4395
-    /**
4396
-     * draw a clipping rectangle, all the elements added after this will be clipped
4397
-     *
4398
-     * @param $x1
4399
-     * @param $y1
4400
-     * @param $width
4401
-     * @param $height
4402
-     */
4403
-    function clippingRectangle($x1, $y1, $width, $height)
4404
-    {
4405
-        $this->save();
4406
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
4407
-    }
4408
-
4409
-    /**
4410
-     * draw a clipping rounded rectangle, all the elements added after this will be clipped
4411
-     *
4412
-     * @param $x1
4413
-     * @param $y1
4414
-     * @param $w
4415
-     * @param $h
4416
-     * @param $rTL
4417
-     * @param $rTR
4418
-     * @param $rBR
4419
-     * @param $rBL
4420
-     */
4421
-    function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
4422
-    {
4423
-        $this->save();
4424
-
4425
-        // start: top edge, left end
4426
-        $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
4427
-
4428
-        // line: bottom edge, left end
4429
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
4430
-
4431
-        // curve: bottom-left corner
4432
-        $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
4433
-
4434
-        // line: right edge, bottom end
4435
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
4436
-
4437
-        // curve: bottom-right corner
4438
-        $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
4439
-
4440
-        // line: right edge, top end
4441
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
4442
-
4443
-        // curve: bottom-right corner
4444
-        $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
4445
-
4446
-        // line: bottom edge, right end
4447
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
4448
-
4449
-        // curve: top-right corner
4450
-        $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
4451
-
4452
-        // line: top edge, left end
4453
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
4454
-
4455
-        // Close & clip
4456
-        $this->addContent(" W n");
4457
-    }
4458
-
4459
-    /**
4460
-     * ends the last clipping shape
4461
-     */
4462
-    function clippingEnd()
4463
-    {
4464
-        $this->restore();
4465
-    }
4466
-
4467
-    /**
4468
-     * scale
4469
-     *
4470
-     * @param float $s_x scaling factor for width as percent
4471
-     * @param float $s_y scaling factor for height as percent
4472
-     * @param float $x   Origin abscissa
4473
-     * @param float $y   Origin ordinate
4474
-     */
4475
-    function scale($s_x, $s_y, $x, $y)
4476
-    {
4477
-        $y = $this->currentPageSize["height"] - $y;
4478
-
4479
-        $tm = [
4480
-            $s_x,
4481
-            0,
4482
-            0,
4483
-            $s_y,
4484
-            $x * (1 - $s_x),
4485
-            $y * (1 - $s_y)
4486
-        ];
4487
-
4488
-        $this->transform($tm);
4489
-    }
4490
-
4491
-    /**
4492
-     * translate
4493
-     *
4494
-     * @param float $t_x movement to the right
4495
-     * @param float $t_y movement to the bottom
4496
-     */
4497
-    function translate($t_x, $t_y)
4498
-    {
4499
-        $tm = [
4500
-            1,
4501
-            0,
4502
-            0,
4503
-            1,
4504
-            $t_x,
4505
-            -$t_y
4506
-        ];
4507
-
4508
-        $this->transform($tm);
4509
-    }
4510
-
4511
-    /**
4512
-     * rotate
4513
-     *
4514
-     * @param float $angle angle in degrees for counter-clockwise rotation
4515
-     * @param float $x     Origin abscissa
4516
-     * @param float $y     Origin ordinate
4517
-     */
4518
-    function rotate($angle, $x, $y)
4519
-    {
4520
-        $y = $this->currentPageSize["height"] - $y;
4521
-
4522
-        $a = deg2rad($angle);
4523
-        $cos_a = cos($a);
4524
-        $sin_a = sin($a);
4525
-
4526
-        $tm = [
4527
-            $cos_a,
4528
-            -$sin_a,
4529
-            $sin_a,
4530
-            $cos_a,
4531
-            $x - $sin_a * $y - $cos_a * $x,
4532
-            $y - $cos_a * $y + $sin_a * $x,
4533
-        ];
4534
-
4535
-        $this->transform($tm);
4536
-    }
4537
-
4538
-    /**
4539
-     * skew
4540
-     *
4541
-     * @param float $angle_x
4542
-     * @param float $angle_y
4543
-     * @param float $x Origin abscissa
4544
-     * @param float $y Origin ordinate
4545
-     */
4546
-    function skew($angle_x, $angle_y, $x, $y)
4547
-    {
4548
-        $y = $this->currentPageSize["height"] - $y;
4549
-
4550
-        $tan_x = tan(deg2rad($angle_x));
4551
-        $tan_y = tan(deg2rad($angle_y));
4552
-
4553
-        $tm = [
4554
-            1,
4555
-            -$tan_y,
4556
-            -$tan_x,
4557
-            1,
4558
-            $tan_x * $y,
4559
-            $tan_y * $x,
4560
-        ];
4561
-
4562
-        $this->transform($tm);
4563
-    }
4564
-
4565
-    /**
4566
-     * apply graphic transformations
4567
-     *
4568
-     * @param array $tm transformation matrix
4569
-     */
4570
-    function transform($tm)
4571
-    {
4572
-        $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
4573
-    }
4574
-
4575
-    /**
4576
-     * add a new page to the document
4577
-     * this also makes the new page the current active object
4578
-     *
4579
-     * @param int $insert
4580
-     * @param int $id
4581
-     * @param string $pos
4582
-     * @return int
4583
-     */
4584
-    function newPage($insert = 0, $id = 0, $pos = 'after')
4585
-    {
4586
-        // if there is a state saved, then go up the stack closing them
4587
-        // then on the new page, re-open them with the right setings
4588
-
4589
-        if ($this->nStateStack) {
4590
-            for ($i = $this->nStateStack; $i >= 1; $i--) {
4591
-                $this->restoreState($i);
4592
-            }
4593
-        }
4594
-
4595
-        $this->numObj++;
4596
-
4597
-        if ($insert) {
4598
-            // the id from the ezPdf class is the id of the contents of the page, not the page object itself
4599
-            // query that object to find the parent
4600
-            $rid = $this->objects[$id]['onPage'];
4601
-            $opt = ['rid' => $rid, 'pos' => $pos];
4602
-            $this->o_page($this->numObj, 'new', $opt);
4603
-        } else {
4604
-            $this->o_page($this->numObj, 'new');
4605
-        }
4606
-
4607
-        // if there is a stack saved, then put that onto the page
4608
-        if ($this->nStateStack) {
4609
-            for ($i = 1; $i <= $this->nStateStack; $i++) {
4610
-                $this->saveState($i);
4611
-            }
4612
-        }
4613
-
4614
-        // and if there has been a stroke or fill color set, then transfer them
4615
-        if (isset($this->currentColor)) {
4616
-            $this->setColor($this->currentColor, true);
4617
-        }
4618
-
4619
-        if (isset($this->currentStrokeColor)) {
4620
-            $this->setStrokeColor($this->currentStrokeColor, true);
4621
-        }
4622
-
4623
-        // if there is a line style set, then put this in too
4624
-        if (mb_strlen($this->currentLineStyle, '8bit')) {
4625
-            $this->addContent("\n$this->currentLineStyle");
4626
-        }
4627
-
4628
-        // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
4629
-        return $this->currentContents;
4630
-    }
4631
-
4632
-    /**
4633
-     * Streams the PDF to the client.
4634
-     *
4635
-     * @param string $filename The filename to present to the client.
4636
-     * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
4637
-     */
4638
-    function stream($filename = "document.pdf", $options = [])
4639
-    {
4640
-        if (headers_sent()) {
4641
-            die("Unable to stream pdf: headers already sent");
4642
-        }
4643
-
4644
-        if (!isset($options["compress"])) $options["compress"] = true;
4645
-        if (!isset($options["Attachment"])) $options["Attachment"] = true;
4646
-
4647
-        $debug = !$options['compress'];
4648
-        $tmp = ltrim($this->output($debug));
4649
-
4650
-        header("Cache-Control: private");
4651
-        header("Content-Type: application/pdf");
4652
-        header("Content-Length: " . mb_strlen($tmp, "8bit"));
4653
-
4654
-        $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
4655
-        $attachment = $options["Attachment"] ? "attachment" : "inline";
4656
-
4657
-        $encoding = mb_detect_encoding($filename);
4658
-        $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
4659
-        $fallbackfilename = str_replace("\"", "", $fallbackfilename);
4660
-        $encodedfilename = rawurlencode($filename);
4661
-
4662
-        $contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
4663
-        if ($fallbackfilename !== $filename) {
4664
-            $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
4665
-        }
4666
-        header($contentDisposition);
4667
-
4668
-        echo $tmp;
4669
-        flush();
4670
-    }
4671
-
4672
-    /**
4673
-     * return the height in units of the current font in the given size
4674
-     *
4675
-     * @param $size
4676
-     * @return float|int
4677
-     */
4678
-    function getFontHeight($size)
4679
-    {
4680
-        if (!$this->numFonts) {
4681
-            $this->selectFont($this->defaultFont);
4682
-        }
4683
-
4684
-        $font = $this->fonts[$this->currentFont];
4685
-
4686
-        // for the current font, and the given size, what is the height of the font in user units
4687
-        if (isset($font['Ascender']) && isset($font['Descender'])) {
4688
-            $h = $font['Ascender'] - $font['Descender'];
4689
-        } else {
4690
-            $h = $font['FontBBox'][3] - $font['FontBBox'][1];
4691
-        }
4692
-
4693
-        // have to adjust by a font offset for Windows fonts.  unfortunately it looks like
4694
-        // the bounding box calculations are wrong and I don't know why.
4695
-        if (isset($font['FontHeightOffset'])) {
4696
-            // For CourierNew from Windows this needs to be -646 to match the
4697
-            // Adobe native Courier font.
4698
-            //
4699
-            // For FreeMono from GNU this needs to be -337 to match the
4700
-            // Courier font.
4701
-            //
4702
-            // Both have been added manually to the .afm and .ufm files.
4703
-            $h += (int)$font['FontHeightOffset'];
4704
-        }
4705
-
4706
-        return $size * $h / 1000;
4707
-    }
4708
-
4709
-    /**
4710
-     * @param $size
4711
-     * @return float|int
4712
-     */
4713
-    function getFontXHeight($size)
4714
-    {
4715
-        if (!$this->numFonts) {
4716
-            $this->selectFont($this->defaultFont);
4717
-        }
4718
-
4719
-        $font = $this->fonts[$this->currentFont];
4720
-
4721
-        // for the current font, and the given size, what is the height of the font in user units
4722
-        if (isset($font['XHeight'])) {
4723
-            $xh = $font['Ascender'] - $font['Descender'];
4724
-        } else {
4725
-            $xh = $this->getFontHeight($size) / 2;
4726
-        }
4727
-
4728
-        return $size * $xh / 1000;
4729
-    }
4730
-
4731
-    /**
4732
-     * return the font descender, this will normally return a negative number
4733
-     * if you add this number to the baseline, you get the level of the bottom of the font
4734
-     * it is in the pdf user units
4735
-     *
4736
-     * @param $size
4737
-     * @return float|int
4738
-     */
4739
-    function getFontDescender($size)
4740
-    {
4741
-        // note that this will most likely return a negative value
4742
-        if (!$this->numFonts) {
4743
-            $this->selectFont($this->defaultFont);
4744
-        }
4745
-
4746
-        //$h = $this->fonts[$this->currentFont]['FontBBox'][1];
4747
-        $h = $this->fonts[$this->currentFont]['Descender'];
4748
-
4749
-        return $size * $h / 1000;
4750
-    }
4751
-
4752
-    /**
4753
-     * filter the text, this is applied to all text just before being inserted into the pdf document
4754
-     * it escapes the various things that need to be escaped, and so on
4755
-     *
4756
-     * @access private
4757
-     *
4758
-     * @param $text
4759
-     * @param bool $bom
4760
-     * @param bool $convert_encoding
4761
-     * @return string
4762
-     */
4763
-    function filterText($text, $bom = true, $convert_encoding = true)
4764
-    {
4765
-        if (!$this->numFonts) {
4766
-            $this->selectFont($this->defaultFont);
4767
-        }
4768
-
4769
-        if ($convert_encoding) {
4770
-            $cf = $this->currentFont;
4771
-            if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4772
-                $text = $this->utf8toUtf16BE($text, $bom);
4773
-            } else {
4774
-                //$text = html_entity_decode($text, ENT_QUOTES);
4775
-                $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4776
-            }
4777
-        } else if ($bom) {
4778
-            $text = $this->utf8toUtf16BE($text, $bom);
4779
-        }
4780
-
4781
-        // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4782
-        return strtr($text, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
4783
-    }
4784
-
4785
-    /**
4786
-     * return array containing codepoints (UTF-8 character values) for the
4787
-     * string passed in.
4788
-     *
4789
-     * based on the excellent TCPDF code by Nicola Asuni and the
4790
-     * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4791
-     *
4792
-     * @access private
4793
-     * @author Orion Richardson
4794
-     * @since  January 5, 2008
4795
-     *
4796
-     * @param string $text UTF-8 string to process
4797
-     *
4798
-     * @return array UTF-8 codepoints array for the string
4799
-     */
4800
-    function utf8toCodePointsArray(&$text)
4801
-    {
4802
-        $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4803
-        $unicode = []; // array containing unicode values
4804
-        $bytes = []; // array containing single character byte sequences
4805
-        $numbytes = 1; // number of octets needed to represent the UTF-8 character
4806
-
4807
-        for ($i = 0; $i < $length; $i++) {
4808
-            $c = ord($text[$i]); // get one string character at time
4809
-            if (count($bytes) === 0) { // get starting octect
4810
-                if ($c <= 0x7F) {
4811
-                    $unicode[] = $c; // use the character "as is" because is ASCII
4812
-                    $numbytes = 1;
4813
-                } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4814
-                    $bytes[] = ($c - 0xC0) << 0x06;
4815
-                    $numbytes = 2;
4816
-                } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4817
-                    $bytes[] = ($c - 0xE0) << 0x0C;
4818
-                    $numbytes = 3;
4819
-                } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4820
-                    $bytes[] = ($c - 0xF0) << 0x12;
4821
-                    $numbytes = 4;
4822
-                } else {
4823
-                    // use replacement character for other invalid sequences
4824
-                    $unicode[] = 0xFFFD;
4825
-                    $bytes = [];
4826
-                    $numbytes = 1;
4827
-                }
4828
-            } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4829
-                $bytes[] = $c - 0x80;
4830
-                if (count($bytes) === $numbytes) {
4831
-                    // compose UTF-8 bytes to a single unicode value
4832
-                    $c = $bytes[0];
4833
-                    for ($j = 1; $j < $numbytes; $j++) {
4834
-                        $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4835
-                    }
4836
-                    if ((($c >= 0xD800) and ($c <= 0xDFFF)) or ($c >= 0x10FFFF)) {
4837
-                        // The definition of UTF-8 prohibits encoding character numbers between
4838
-                        // U+D800 and U+DFFF, which are reserved for use with the UTF-16
4839
-                        // encoding form (as surrogate pairs) and do not directly represent
4840
-                        // characters.
4841
-                        $unicode[] = 0xFFFD; // use replacement character
4842
-                    } else {
4843
-                        $unicode[] = $c; // add char to array
4844
-                    }
4845
-                    // reset data for next char
4846
-                    $bytes = [];
4847
-                    $numbytes = 1;
4848
-                }
4849
-            } else {
4850
-                // use replacement character for other invalid sequences
4851
-                $unicode[] = 0xFFFD;
4852
-                $bytes = [];
4853
-                $numbytes = 1;
4854
-            }
4855
-        }
4856
-
4857
-        return $unicode;
4858
-    }
4859
-
4860
-    /**
4861
-     * convert UTF-8 to UTF-16 with an additional byte order marker
4862
-     * at the front if required.
4863
-     *
4864
-     * based on the excellent TCPDF code by Nicola Asuni and the
4865
-     * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4866
-     *
4867
-     * @access private
4868
-     * @author Orion Richardson
4869
-     * @since  January 5, 2008
4870
-     *
4871
-     * @param string  $text UTF-8 string to process
4872
-     * @param boolean $bom  whether to add the byte order marker
4873
-     *
4874
-     * @return string UTF-16 result string
4875
-     */
4876
-    function utf8toUtf16BE(&$text, $bom = true)
4877
-    {
4878
-        $out = $bom ? "\xFE\xFF" : '';
4879
-
4880
-        $unicode = $this->utf8toCodePointsArray($text);
4881
-        foreach ($unicode as $c) {
4882
-            if ($c === 0xFFFD) {
4883
-                $out .= "\xFF\xFD"; // replacement character
4884
-            } elseif ($c < 0x10000) {
4885
-                $out .= chr($c >> 0x08) . chr($c & 0xFF);
4886
-            } else {
4887
-                $c -= 0x10000;
4888
-                $w1 = 0xD800 | ($c >> 0x10);
4889
-                $w2 = 0xDC00 | ($c & 0x3FF);
4890
-                $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4891
-            }
4892
-        }
4893
-
4894
-        return $out;
4895
-    }
4896
-
4897
-    /**
4898
-     * given a start position and information about how text is to be laid out, calculate where
4899
-     * on the page the text will end
4900
-     *
4901
-     * @param $x
4902
-     * @param $y
4903
-     * @param $angle
4904
-     * @param $size
4905
-     * @param $wa
4906
-     * @param $text
4907
-     * @return array
4908
-     */
4909
-    private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4910
-    {
4911
-        // given this information return an array containing x and y for the end position as elements 0 and 1
4912
-        $w = $this->getTextWidth($size, $text);
4913
-
4914
-        // need to adjust for the number of spaces in this text
4915
-        $words = explode(' ', $text);
4916
-        $nspaces = count($words) - 1;
4917
-        $w += $wa * $nspaces;
4918
-        $a = deg2rad((float)$angle);
4919
-
4920
-        return [cos($a) * $w + $x, -sin($a) * $w + $y];
4921
-    }
4922
-
4923
-    /**
4924
-     * Callback method used by smallCaps
4925
-     *
4926
-     * @param array $matches
4927
-     *
4928
-     * @return string
4929
-     */
4930
-    function toUpper($matches)
4931
-    {
4932
-        return mb_strtoupper($matches[0]);
4933
-    }
4934
-
4935
-    function concatMatches($matches)
4936
-    {
4937
-        $str = "";
4938
-        foreach ($matches as $match) {
4939
-            $str .= $match[0];
4940
-        }
4941
-
4942
-        return $str;
4943
-    }
4944
-
4945
-    /**
4946
-     * register text for font subsetting
4947
-     *
4948
-     * @param $font
4949
-     * @param $text
4950
-     */
4951
-    function registerText($font, $text)
4952
-    {
4953
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4954
-            return;
4955
-        }
4956
-
4957
-        if (!isset($this->stringSubsets[$font])) {
4958
-            $this->stringSubsets[$font] = [];
4959
-        }
4960
-
4961
-        $this->stringSubsets[$font] = array_unique(
4962
-            array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
4963
-        );
4964
-    }
4965
-
4966
-    /**
4967
-     * add text to the document, at a specified location, size and angle on the page
4968
-     *
4969
-     * @param $x
4970
-     * @param $y
4971
-     * @param $size
4972
-     * @param $text
4973
-     * @param int $angle
4974
-     * @param int $wordSpaceAdjust
4975
-     * @param int $charSpaceAdjust
4976
-     * @param bool $smallCaps
4977
-     */
4978
-    function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4979
-    {
4980
-        if (!$this->numFonts) {
4981
-            $this->selectFont($this->defaultFont);
4982
-        }
4983
-
4984
-        $text = str_replace(["\r", "\n"], "", $text);
4985
-
4986
-        // if ($smallCaps) {
4987
-        //     preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4988
-        //     $lower = $this->concatMatches($matches);
4989
-        //     d($lower);
4990
-
4991
-        //     preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4992
-        //     $other = $this->concatMatches($matches);
4993
-        //     d($other);
4994
-
4995
-        //     $text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
4996
-        // }
4997
-
4998
-        // if there are any open callbacks, then they should be called, to show the start of the line
4999
-        if ($this->nCallback > 0) {
5000
-            for ($i = $this->nCallback; $i > 0; $i--) {
5001
-                // call each function
5002
-                $info = [
5003
-                    'x'         => $x,
5004
-                    'y'         => $y,
5005
-                    'angle'     => $angle,
5006
-                    'status'    => 'sol',
5007
-                    'p'         => $this->callback[$i]['p'],
5008
-                    'nCallback' => $this->callback[$i]['nCallback'],
5009
-                    'height'    => $this->callback[$i]['height'],
5010
-                    'descender' => $this->callback[$i]['descender']
5011
-                ];
5012
-
5013
-                $func = $this->callback[$i]['f'];
5014
-                $this->$func($info);
5015
-            }
5016
-        }
5017
-
5018
-        if ($angle == 0) {
5019
-            $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
5020
-        } else {
5021
-            $a = deg2rad((float)$angle);
5022
-            $this->addContent(
5023
-                sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
5024
-            );
5025
-        }
5026
-
5027
-        if ($wordSpaceAdjust != 0) {
5028
-            $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
5029
-        }
5030
-
5031
-        if ($charSpaceAdjust != 0) {
5032
-            $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
5033
-        }
5034
-
5035
-        $len = mb_strlen($text);
5036
-        $start = 0;
5037
-
5038
-        if ($start < $len) {
5039
-            $part = $text; // OAR - Don't need this anymore, given that $start always equals zero.  substr($text, $start);
5040
-            $place_text = $this->filterText($part, false);
5041
-            // modify unicode text so that extra word spacing is manually implemented (bug #)
5042
-            if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
5043
-                $space_scale = 1000 / $size;
5044
-                $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
5045
-            }
5046
-            $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
5047
-            $this->addContent(" [($place_text)] TJ");
5048
-        }
5049
-
5050
-        if ($wordSpaceAdjust != 0) {
5051
-            $this->addContent(sprintf(" %.3F Tw", 0));
5052
-        }
5053
-
5054
-        if ($charSpaceAdjust != 0) {
5055
-            $this->addContent(sprintf(" %.3F Tc", 0));
5056
-        }
5057
-
5058
-        $this->addContent(' ET');
5059
-
5060
-        // if there are any open callbacks, then they should be called, to show the end of the line
5061
-        if ($this->nCallback > 0) {
5062
-            for ($i = $this->nCallback; $i > 0; $i--) {
5063
-                // call each function
5064
-                $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
5065
-                $info = [
5066
-                    'x'         => $tmp[0],
5067
-                    'y'         => $tmp[1],
5068
-                    'angle'     => $angle,
5069
-                    'status'    => 'eol',
5070
-                    'p'         => $this->callback[$i]['p'],
5071
-                    'nCallback' => $this->callback[$i]['nCallback'],
5072
-                    'height'    => $this->callback[$i]['height'],
5073
-                    'descender' => $this->callback[$i]['descender']
5074
-                ];
5075
-                $func = $this->callback[$i]['f'];
5076
-                $this->$func($info);
5077
-            }
5078
-        }
5079
-
5080
-        if ($this->fonts[$this->currentFont]['isSubsetting']) {
5081
-            $this->registerText($this->currentFont, $text);
5082
-        }
5083
-    }
5084
-
5085
-    /**
5086
-     * calculate how wide a given text string will be on a page, at a given size.
5087
-     * this can be called externally, but is also used by the other class functions
5088
-     *
5089
-     * @param float $size
5090
-     * @param string $text
5091
-     * @param float $word_spacing
5092
-     * @param float $char_spacing
5093
-     * @return float
5094
-     */
5095
-    function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0)
5096
-    {
5097
-        static $ord_cache = [];
5098
-
5099
-        // this function should not change any of the settings, though it will need to
5100
-        // track any directives which change during calculation, so copy them at the start
5101
-        // and put them back at the end.
5102
-        $store_currentTextState = $this->currentTextState;
5103
-
5104
-        if (!$this->numFonts) {
5105
-            $this->selectFont($this->defaultFont);
5106
-        }
5107
-
5108
-        $text = str_replace(["\r", "\n"], "", $text);
5109
-
5110
-        // converts a number or a float to a string so it can get the width
5111
-        $text = "$text";
5112
-
5113
-        // hmm, this is where it all starts to get tricky - use the font information to
5114
-        // calculate the width of each character, add them up and convert to user units
5115
-        $w = 0;
5116
-        $cf = $this->currentFont;
5117
-        $current_font = $this->fonts[$cf];
5118
-        $space_scale = 1000 / ($size > 0 ? $size : 1);
5119
-
5120
-        if ($current_font['isUnicode']) {
5121
-            // for Unicode, use the code points array to calculate width rather
5122
-            // than just the string itself
5123
-            $unicode = $this->utf8toCodePointsArray($text);
5124
-
5125
-            foreach ($unicode as $char) {
5126
-                // check if we have to replace character
5127
-                if (isset($current_font['differences'][$char])) {
5128
-                    $char = $current_font['differences'][$char];
5129
-                }
5130
-
5131
-                if (isset($current_font['C'][$char])) {
5132
-                    $char_width = $current_font['C'][$char];
5133
-
5134
-                    // add the character width
5135
-                    $w += $char_width;
5136
-
5137
-                    // add additional padding for space
5138
-                    if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5139
-                        $w += $word_spacing * $space_scale;
5140
-                    }
5141
-                }
5142
-            }
5143
-
5144
-            // add additional char spacing
5145
-            if ($char_spacing != 0) {
5146
-                $w += $char_spacing * $space_scale * count($unicode);
5147
-            }
5148
-
5149
-        } else {
5150
-            // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
5151
-            if ($this->isUnicode) {
5152
-                $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
5153
-            }
5154
-
5155
-            $len = mb_strlen($text, 'Windows-1252');
5156
-
5157
-            for ($i = 0; $i < $len; $i++) {
5158
-                $c = $text[$i];
5159
-                $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
5160
-
5161
-                // check if we have to replace character
5162
-                if (isset($current_font['differences'][$char])) {
5163
-                    $char = $current_font['differences'][$char];
5164
-                }
5165
-
5166
-                if (isset($current_font['C'][$char])) {
5167
-                    $char_width = $current_font['C'][$char];
5168
-
5169
-                    // add the character width
5170
-                    $w += $char_width;
5171
-
5172
-                    // add additional padding for space
5173
-                    if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5174
-                        $w += $word_spacing * $space_scale;
5175
-                    }
5176
-                }
5177
-            }
5178
-
5179
-            // add additional char spacing
5180
-            if ($char_spacing != 0) {
5181
-                $w += $char_spacing * $space_scale * $len;
5182
-            }
5183
-        }
5184
-
5185
-        $this->currentTextState = $store_currentTextState;
5186
-        $this->setCurrentFont();
5187
-
5188
-        return $w * $size / 1000;
5189
-    }
5190
-
5191
-    /**
5192
-     * this will be called at a new page to return the state to what it was on the
5193
-     * end of the previous page, before the stack was closed down
5194
-     * This is to get around not being able to have open 'q' across pages
5195
-     *
5196
-     * @param int $pageEnd
5197
-     */
5198
-    function saveState($pageEnd = 0)
5199
-    {
5200
-        if ($pageEnd) {
5201
-            // this will be called at a new page to return the state to what it was on the
5202
-            // end of the previous page, before the stack was closed down
5203
-            // This is to get around not being able to have open 'q' across pages
5204
-            $opt = $this->stateStack[$pageEnd];
5205
-            // ok to use this as stack starts numbering at 1
5206
-            $this->setColor($opt['col'], true);
5207
-            $this->setStrokeColor($opt['str'], true);
5208
-            $this->addContent("\n" . $opt['lin']);
5209
-            //    $this->currentLineStyle = $opt['lin'];
5210
-        } else {
5211
-            $this->nStateStack++;
5212
-            $this->stateStack[$this->nStateStack] = [
5213
-                'col' => $this->currentColor,
5214
-                'str' => $this->currentStrokeColor,
5215
-                'lin' => $this->currentLineStyle
5216
-            ];
5217
-        }
5218
-
5219
-        $this->save();
5220
-    }
5221
-
5222
-    /**
5223
-     * restore a previously saved state
5224
-     *
5225
-     * @param int $pageEnd
5226
-     */
5227
-    function restoreState($pageEnd = 0)
5228
-    {
5229
-        if (!$pageEnd) {
5230
-            $n = $this->nStateStack;
5231
-            $this->currentColor = $this->stateStack[$n]['col'];
5232
-            $this->currentStrokeColor = $this->stateStack[$n]['str'];
5233
-            $this->addContent("\n" . $this->stateStack[$n]['lin']);
5234
-            $this->currentLineStyle = $this->stateStack[$n]['lin'];
5235
-            $this->stateStack[$n] = null;
5236
-            unset($this->stateStack[$n]);
5237
-            $this->nStateStack--;
5238
-        }
5239
-
5240
-        $this->restore();
5241
-    }
5242
-
5243
-    /**
5244
-     * make a loose object, the output will go into this object, until it is closed, then will revert to
5245
-     * the current one.
5246
-     * this object will not appear until it is included within a page.
5247
-     * the function will return the object number
5248
-     *
5249
-     * @return int
5250
-     */
5251
-    function openObject()
5252
-    {
5253
-        $this->nStack++;
5254
-        $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5255
-        // add a new object of the content type, to hold the data flow
5256
-        $this->numObj++;
5257
-        $this->o_contents($this->numObj, 'new');
5258
-        $this->currentContents = $this->numObj;
5259
-        $this->looseObjects[$this->numObj] = 1;
5260
-
5261
-        return $this->numObj;
5262
-    }
5263
-
5264
-    /**
5265
-     * open an existing object for editing
5266
-     *
5267
-     * @param $id
5268
-     */
5269
-    function reopenObject($id)
5270
-    {
5271
-        $this->nStack++;
5272
-        $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5273
-        $this->currentContents = $id;
5274
-
5275
-        // also if this object is the primary contents for a page, then set the current page to its parent
5276
-        if (isset($this->objects[$id]['onPage'])) {
5277
-            $this->currentPage = $this->objects[$id]['onPage'];
5278
-        }
5279
-    }
5280
-
5281
-    /**
5282
-     * close an object
5283
-     */
5284
-    function closeObject()
5285
-    {
5286
-        // close the object, as long as there was one open in the first place, which will be indicated by
5287
-        // an objectId on the stack.
5288
-        if ($this->nStack > 0) {
5289
-            $this->currentContents = $this->stack[$this->nStack]['c'];
5290
-            $this->currentPage = $this->stack[$this->nStack]['p'];
5291
-            $this->nStack--;
5292
-            // easier to probably not worry about removing the old entries, they will be overwritten
5293
-            // if there are new ones.
5294
-        }
5295
-    }
5296
-
5297
-    /**
5298
-     * stop an object from appearing on pages from this point on
5299
-     *
5300
-     * @param $id
5301
-     */
5302
-    function stopObject($id)
5303
-    {
5304
-        // if an object has been appearing on pages up to now, then stop it, this page will
5305
-        // be the last one that could contain it.
5306
-        if (isset($this->addLooseObjects[$id])) {
5307
-            $this->addLooseObjects[$id] = '';
5308
-        }
5309
-    }
5310
-
5311
-    /**
5312
-     * after an object has been created, it wil only show if it has been added, using this function.
5313
-     *
5314
-     * @param $id
5315
-     * @param string $options
5316
-     */
5317
-    function addObject($id, $options = 'add')
5318
-    {
5319
-        // add the specified object to the page
5320
-        if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
5321
-            // then it is a valid object, and it is not being added to itself
5322
-            switch ($options) {
5323
-                case 'all':
5324
-                    // then this object is to be added to this page (done in the next block) and
5325
-                    // all future new pages.
5326
-                    $this->addLooseObjects[$id] = 'all';
5327
-
5328
-                case 'add':
5329
-                    if (isset($this->objects[$this->currentContents]['onPage'])) {
5330
-                        // then the destination contents is the primary for the page
5331
-                        // (though this object is actually added to that page)
5332
-                        $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
5333
-                    }
5334
-                    break;
5335
-
5336
-                case 'even':
5337
-                    $this->addLooseObjects[$id] = 'even';
5338
-                    $pageObjectId = $this->objects[$this->currentContents]['onPage'];
5339
-                    if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
5340
-                        $this->addObject($id);
5341
-                        // hacky huh :)
5342
-                    }
5343
-                    break;
5344
-
5345
-                case 'odd':
5346
-                    $this->addLooseObjects[$id] = 'odd';
5347
-                    $pageObjectId = $this->objects[$this->currentContents]['onPage'];
5348
-                    if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
5349
-                        $this->addObject($id);
5350
-                        // hacky huh :)
5351
-                    }
5352
-                    break;
5353
-
5354
-                case 'next':
5355
-                    $this->addLooseObjects[$id] = 'all';
5356
-                    break;
5357
-
5358
-                case 'nexteven':
5359
-                    $this->addLooseObjects[$id] = 'even';
5360
-                    break;
5361
-
5362
-                case 'nextodd':
5363
-                    $this->addLooseObjects[$id] = 'odd';
5364
-                    break;
5365
-            }
5366
-        }
5367
-    }
5368
-
5369
-    /**
5370
-     * return a storable representation of a specific object
5371
-     *
5372
-     * @param $id
5373
-     * @return string|null
5374
-     */
5375
-    function serializeObject($id)
5376
-    {
5377
-        if (array_key_exists($id, $this->objects)) {
5378
-            return serialize($this->objects[$id]);
5379
-        }
5380
-
5381
-        return null;
5382
-    }
5383
-
5384
-    /**
5385
-     * restore an object from its stored representation. Returns its new object id.
5386
-     *
5387
-     * @param $obj
5388
-     * @return int
5389
-     */
5390
-    function restoreSerializedObject($obj)
5391
-    {
5392
-        $obj_id = $this->openObject();
5393
-        $this->objects[$obj_id] = unserialize($obj);
5394
-        $this->closeObject();
5395
-
5396
-        return $obj_id;
5397
-    }
5398
-
5399
-    /**
5400
-     * Embeds a file inside the PDF
5401
-     *
5402
-     * @param string $filepath path to the file to store inside the PDF
5403
-     * @param string $embeddedFilename the filename displayed in the list of embedded files
5404
-     * @param string $description a description in the list of embedded files
5405
-     */
5406
-    public function addEmbeddedFile(string $filepath, string $embeddedFilename, string $description): void
5407
-    {
5408
-        $this->numObj++;
5409
-        $this->o_embedded_file_dictionary(
5410
-            $this->numObj,
5411
-            'new',
5412
-            [
5413
-                'filepath' => $filepath,
5414
-                'filename' => $embeddedFilename,
5415
-                'description' => $description
5416
-            ]
5417
-        );
5418
-    }
5419
-
5420
-    /**
5421
-     * add content to the documents info object
5422
-     *
5423
-     * @param $label
5424
-     * @param int $value
5425
-     */
5426
-    function addInfo($label, $value = 0)
5427
-    {
5428
-        // this will only work if the label is one of the valid ones.
5429
-        // modify this so that arrays can be passed as well.
5430
-        // if $label is an array then assume that it is key => value pairs
5431
-        // else assume that they are both scalar, anything else will probably error
5432
-        if (is_array($label)) {
5433
-            foreach ($label as $l => $v) {
5434
-                $this->o_info($this->infoObject, $l, $v);
5435
-            }
5436
-        } else {
5437
-            $this->o_info($this->infoObject, $label, $value);
5438
-        }
5439
-    }
5440
-
5441
-    /**
5442
-     * set the viewer preferences of the document, it is up to the browser to obey these.
5443
-     *
5444
-     * @param $label
5445
-     * @param int $value
5446
-     */
5447
-    function setPreferences($label, $value = 0)
5448
-    {
5449
-        // this will only work if the label is one of the valid ones.
5450
-        if (is_array($label)) {
5451
-            foreach ($label as $l => $v) {
5452
-                $this->o_catalog($this->catalogId, 'viewerPreferences', [$l => $v]);
5453
-            }
5454
-        } else {
5455
-            $this->o_catalog($this->catalogId, 'viewerPreferences', [$label => $value]);
5456
-        }
5457
-    }
5458
-
5459
-    /**
5460
-     * extract an integer from a position in a byte stream
5461
-     *
5462
-     * @param $data
5463
-     * @param $pos
5464
-     * @param $num
5465
-     * @return int
5466
-     */
5467
-    private function getBytes(&$data, $pos, $num)
5468
-    {
5469
-        // return the integer represented by $num bytes from $pos within $data
5470
-        $ret = 0;
5471
-        for ($i = 0; $i < $num; $i++) {
5472
-            $ret *= 256;
5473
-            $ret += ord($data[$pos + $i]);
5474
-        }
5475
-
5476
-        return $ret;
5477
-    }
5478
-
5479
-    /**
5480
-     * Check if image already added to pdf image directory.
5481
-     * If yes, need not to create again (pass empty data)
5482
-     *
5483
-     * @param string $imgname
5484
-     * @return bool
5485
-     */
5486
-    function image_iscached($imgname)
5487
-    {
5488
-        return isset($this->imagelist[$imgname]);
5489
-    }
5490
-
5491
-    /**
5492
-     * add a PNG image into the document, from a GD object
5493
-     * this should work with remote files
5494
-     *
5495
-     * @param \GdImage|resource $img A GD resource
5496
-     * @param string $file The PNG file
5497
-     * @param float $x X position
5498
-     * @param float $y Y position
5499
-     * @param float $w Width
5500
-     * @param float $h Height
5501
-     * @param bool $is_mask true if the image is a mask
5502
-     * @param bool $mask true if the image is masked
5503
-     * @throws Exception
5504
-     */
5505
-    function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5506
-    {
5507
-        if (!function_exists("imagepng")) {
5508
-            throw new \Exception("The PHP GD extension is required, but is not installed.");
5509
-        }
5510
-
5511
-        //if already cached, need not to read again
5512
-        if (isset($this->imagelist[$file])) {
5513
-            $data = null;
5514
-        } else {
5515
-            // Example for transparency handling on new image. Retain for current image
5516
-            // $tIndex = imagecolortransparent($img);
5517
-            // if ($tIndex > 0) {
5518
-            //   $tColor    = imagecolorsforindex($img, $tIndex);
5519
-            //   $new_tIndex    = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
5520
-            //   imagefill($new_img, 0, 0, $new_tIndex);
5521
-            //   imagecolortransparent($new_img, $new_tIndex);
5522
-            // }
5523
-            // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
5524
-            //imagealphablending($img, true);
5525
-
5526
-            //default, but explicitely set to ensure pdf compatibility
5527
-            imagesavealpha($img, false/*!$is_mask && !$mask*/);
5528
-
5529
-            $error = 0;
5530
-            //DEBUG_IMG_TEMP
5531
-            //debugpng
5532
-            if (defined("DEBUGPNG") && DEBUGPNG) {
5533
-                print '[addImagePng ' . $file . ']';
5534
-            }
5535
-
5536
-            ob_start();
5537
-            @imagepng($img);
5538
-            $data = ob_get_clean();
5539
-
5540
-            if ($data == '') {
5541
-                $error = 1;
5542
-                $errormsg = 'trouble writing file from GD';
5543
-                //DEBUG_IMG_TEMP
5544
-                //debugpng
5545
-                if (defined("DEBUGPNG") && DEBUGPNG) {
5546
-                    print 'trouble writing file from GD';
5547
-                }
5548
-            }
5549
-
5550
-            if ($error) {
5551
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5552
-
5553
-                return;
5554
-            }
5555
-        }  //End isset($this->imagelist[$file]) (png Duplicate removal)
5556
-
5557
-        $this->addPngFromBuf($data, $file, $x, $y, $w, $h, $is_mask, $mask);
5558
-    }
5559
-
5560
-    /**
5561
-     * @param $file
5562
-     * @param $x
5563
-     * @param $y
5564
-     * @param $w
5565
-     * @param $h
5566
-     * @param $byte
5567
-     */
5568
-    protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
5569
-    {
5570
-        // generate images
5571
-        $img = imagecreatefrompng($file);
5572
-
5573
-        if ($img === false) {
5574
-            return;
5575
-        }
5576
-
5577
-        // FIXME The pixel transformation doesn't work well with 8bit PNGs
5578
-        $eight_bit = ($byte & 4) !== 4;
5579
-
5580
-        $wpx = imagesx($img);
5581
-        $hpx = imagesy($img);
5582
-
5583
-        imagesavealpha($img, false);
5584
-
5585
-        // create temp alpha file
5586
-        $tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
5587
-        @unlink($tempfile_alpha);
5588
-        $tempfile_alpha = "$tempfile_alpha.png";
5589
-
5590
-        // create temp plain file
5591
-        $tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
5592
-        @unlink($tempfile_plain);
5593
-        $tempfile_plain = "$tempfile_plain.png";
5594
-
5595
-        $imgalpha = imagecreate($wpx, $hpx);
5596
-        imagesavealpha($imgalpha, false);
5597
-
5598
-        // generate gray scale palette (0 -> 255)
5599
-        for ($c = 0; $c < 256; ++$c) {
5600
-            imagecolorallocate($imgalpha, $c, $c, $c);
5601
-        }
5602
-
5603
-        // Use PECL gmagick + Graphics Magic to process transparent PNG images
5604
-        if (extension_loaded("gmagick")) {
5605
-            $gmagick = new \Gmagick($file);
5606
-            $gmagick->setimageformat('png');
5607
-
5608
-            // Get opacity channel (negative of alpha channel)
5609
-            $alpha_channel_neg = clone $gmagick;
5610
-            $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
5611
-
5612
-            // Negate opacity channel
5613
-            $alpha_channel = new \Gmagick();
5614
-            $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
5615
-            $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
5616
-            $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
5617
-            $alpha_channel->writeimage($tempfile_alpha);
5618
-
5619
-            // Cast to 8bit+palette
5620
-            $imgalpha_ = imagecreatefrompng($tempfile_alpha);
5621
-            imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5622
-            imagedestroy($imgalpha_);
5623
-            imagepng($imgalpha, $tempfile_alpha);
5624
-
5625
-            // Make opaque image
5626
-            $color_channels = new \Gmagick();
5627
-            $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
5628
-            $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
5629
-            $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
5630
-            $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
5631
-            $color_channels->writeimage($tempfile_plain);
5632
-
5633
-            $imgplain = imagecreatefrompng($tempfile_plain);
5634
-        }
5635
-        // Use PECL imagick + ImageMagic to process transparent PNG images
5636
-        elseif (extension_loaded("imagick")) {
5637
-            // Native cloning was added to pecl-imagick in svn commit 263814
5638
-            // the first version containing it was 3.0.1RC1
5639
-            static $imagickClonable = null;
5640
-            if ($imagickClonable === null) {
5641
-                $imagickClonable = true;
5642
-                if (defined('Imagick::IMAGICK_EXTVER')) {
5643
-                    $imagickVersion = \Imagick::IMAGICK_EXTVER;
5644
-                } else {
5645
-                    $imagickVersion = '0';
5646
-                }
5647
-                if (version_compare($imagickVersion, '0.0.1', '>=')) {
5648
-                    $imagickClonable = version_compare($imagickVersion, '3.0.1rc1', '>=');
5649
-                }
5650
-            }
5651
-
5652
-            $imagick = new \Imagick($file);
5653
-            $imagick->setFormat('png');
5654
-
5655
-            // Get opacity channel (negative of alpha channel)
5656
-            if ($imagick->getImageAlphaChannel() !== 0) {
5657
-                $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
5658
-                $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
5659
-                // Since ImageMagick7 negate invert transparency as default
5660
-                if (\Imagick::getVersion()['versionNumber'] < 1800) {
5661
-                    $alpha_channel->negateImage(true);
5662
-                }
5663
-                $alpha_channel->writeImage($tempfile_alpha);
5664
-
5665
-                // Cast to 8bit+palette
5666
-                $imgalpha_ = imagecreatefrompng($tempfile_alpha);
5667
-                imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5668
-                imagedestroy($imgalpha_);
5669
-                imagepng($imgalpha, $tempfile_alpha);
5670
-            } else {
5671
-                $tempfile_alpha = null;
5672
-            }
5673
-
5674
-            // Make opaque image
5675
-            $color_channels = new \Imagick();
5676
-            $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
5677
-            $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
5678
-            $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
5679
-            $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
5680
-            $color_channels->writeImage($tempfile_plain);
5681
-
5682
-            $imgplain = imagecreatefrompng($tempfile_plain);
5683
-        } else {
5684
-            // allocated colors cache
5685
-            $allocated_colors = [];
5686
-
5687
-            // extract alpha channel
5688
-            for ($xpx = 0; $xpx < $wpx; ++$xpx) {
5689
-                for ($ypx = 0; $ypx < $hpx; ++$ypx) {
5690
-                    $color = imagecolorat($img, $xpx, $ypx);
5691
-                    $col = imagecolorsforindex($img, $color);
5692
-                    $alpha = $col['alpha'];
5693
-
5694
-                    if ($eight_bit) {
5695
-                        // with gamma correction
5696
-                        $gammacorr = 2.2;
5697
-                        $pixel = round(pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255);
5698
-                    } else {
5699
-                        // without gamma correction
5700
-                        $pixel = (127 - $alpha) * 2;
5701
-
5702
-                        $key = $col['red'] . $col['green'] . $col['blue'];
5703
-
5704
-                        if (!isset($allocated_colors[$key])) {
5705
-                            $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
5706
-                            $allocated_colors[$key] = $pixel_img;
5707
-                        } else {
5708
-                            $pixel_img = $allocated_colors[$key];
5709
-                        }
5710
-
5711
-                        imagesetpixel($img, $xpx, $ypx, $pixel_img);
5712
-                    }
5713
-
5714
-                    imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
5715
-                }
5716
-            }
5717
-
5718
-            // extract image without alpha channel
5719
-            $imgplain = imagecreatetruecolor($wpx, $hpx);
5720
-            imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
5721
-            imagedestroy($img);
5722
-
5723
-            imagepng($imgalpha, $tempfile_alpha);
5724
-            imagepng($imgplain, $tempfile_plain);
5725
-        }
5726
-
5727
-        $this->imageAlphaList[$file] = [$tempfile_alpha, $tempfile_plain];
5728
-
5729
-        // embed mask image
5730
-        if ($tempfile_alpha) {
5731
-            $this->addImagePng($imgalpha, $tempfile_alpha, $x, $y, $w, $h, true);
5732
-            imagedestroy($imgalpha);
5733
-            $this->imageCache[] = $tempfile_alpha;
5734
-        }
5735
-
5736
-        // embed image, masked with previously embedded mask
5737
-        $this->addImagePng($imgplain, $tempfile_plain, $x, $y, $w, $h, false, ($tempfile_alpha !== null));
5738
-        imagedestroy($imgplain);
5739
-        $this->imageCache[] = $tempfile_plain;
5740
-    }
5741
-
5742
-    /**
5743
-     * add a PNG image into the document, from a file
5744
-     * this should work with remote files
5745
-     *
5746
-     * @param $file
5747
-     * @param $x
5748
-     * @param $y
5749
-     * @param int $w
5750
-     * @param int $h
5751
-     * @throws Exception
5752
-     */
5753
-    function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
5754
-    {
5755
-        if (!function_exists("imagecreatefrompng")) {
5756
-            throw new \Exception("The PHP GD extension is required, but is not installed.");
5757
-        }
5758
-
5759
-        if (isset($this->imageAlphaList[$file])) {
5760
-            [$alphaFile, $plainFile] = $this->imageAlphaList[$file];
5761
-
5762
-            if ($alphaFile) {
5763
-                $img = null;
5764
-                $this->addImagePng($img, $alphaFile, $x, $y, $w, $h, true);
5765
-            }
5766
-
5767
-            $img = null;
5768
-            $this->addImagePng($img, $plainFile, $x, $y, $w, $h, false, ($plainFile !== null));
5769
-            return;
5770
-        }
5771
-
5772
-        //if already cached, need not to read again
5773
-        if (isset($this->imagelist[$file])) {
5774
-            $img = null;
5775
-        } else {
5776
-            $info = file_get_contents($file, false, null, 24, 5);
5777
-            $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
5778
-            $bit_depth = $meta["bitDepth"];
5779
-            $color_type = $meta["colorType"];
5780
-
5781
-            // http://www.w3.org/TR/PNG/#11IHDR
5782
-            // 3 => indexed
5783
-            // 4 => greyscale with alpha
5784
-            // 6 => fullcolor with alpha
5785
-            $is_alpha = in_array($color_type, [4, 6]) || ($color_type == 3 && $bit_depth != 4);
5786
-
5787
-            if ($is_alpha) { // exclude grayscale alpha
5788
-                $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
5789
-                return;
5790
-            }
5791
-
5792
-            //png files typically contain an alpha channel.
5793
-            //pdf file format or class.pdf does not support alpha blending.
5794
-            //on alpha blended images, more transparent areas have a color near black.
5795
-            //This appears in the result on not storing the alpha channel.
5796
-            //Correct would be the box background image or its parent when transparent.
5797
-            //But this would make the image dependent on the background.
5798
-            //Therefore create an image with white background and copy in
5799
-            //A more natural background than black is white.
5800
-            //Therefore create an empty image with white background and merge the
5801
-            //image in with alpha blending.
5802
-            $imgtmp = @imagecreatefrompng($file);
5803
-            if (!$imgtmp) {
5804
-                return;
5805
-            }
5806
-            $sx = imagesx($imgtmp);
5807
-            $sy = imagesy($imgtmp);
5808
-            $img = imagecreatetruecolor($sx, $sy);
5809
-            imagealphablending($img, true);
5810
-
5811
-            // @todo is it still needed ??
5812
-            $ti = imagecolortransparent($imgtmp);
5813
-            if ($ti >= 0) {
5814
-                $tc = imagecolorsforindex($imgtmp, $ti);
5815
-                $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5816
-                imagefill($img, 0, 0, $ti);
5817
-                imagecolortransparent($img, $ti);
5818
-            } else {
5819
-                imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5820
-            }
5821
-
5822
-            imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5823
-            imagedestroy($imgtmp);
5824
-        }
5825
-        $this->addImagePng($img, $file, $x, $y, $w, $h);
5826
-
5827
-        if ($img) {
5828
-            imagedestroy($img);
5829
-        }
5830
-    }
5831
-
5832
-    /**
5833
-     * add a PNG image into the document, from a memory buffer of the file
5834
-     *
5835
-     * @param $data
5836
-     * @param $file
5837
-     * @param $x
5838
-     * @param $y
5839
-     * @param float $w
5840
-     * @param float $h
5841
-     * @param bool $is_mask
5842
-     * @param null $mask
5843
-     */
5844
-    function addPngFromBuf(&$data, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5845
-    {
5846
-        if (isset($this->imagelist[$file])) {
5847
-            $data = null;
5848
-            $info['width'] = $this->imagelist[$file]['w'];
5849
-            $info['height'] = $this->imagelist[$file]['h'];
5850
-            $label = $this->imagelist[$file]['label'];
5851
-        } else {
5852
-            if ($data == null) {
5853
-                $this->addMessage('addPngFromBuf error - data not present!');
5854
-
5855
-                return;
5856
-            }
5857
-
5858
-            $error = 0;
5859
-
5860
-            if (!$error) {
5861
-                $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5862
-
5863
-                if (mb_substr($data, 0, 8, '8bit') != $header) {
5864
-                    $error = 1;
5865
-
5866
-                    if (defined("DEBUGPNG") && DEBUGPNG) {
5867
-                        print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5868
-                    }
5869
-
5870
-                    $errormsg = 'this file does not have a valid header';
5871
-                }
5872
-            }
5873
-
5874
-            if (!$error) {
5875
-                // set pointer
5876
-                $p = 8;
5877
-                $len = mb_strlen($data, '8bit');
5878
-
5879
-                // cycle through the file, identifying chunks
5880
-                $haveHeader = 0;
5881
-                $info = [];
5882
-                $idata = '';
5883
-                $pdata = '';
5884
-
5885
-                while ($p < $len) {
5886
-                    $chunkLen = $this->getBytes($data, $p, 4);
5887
-                    $chunkType = mb_substr($data, $p + 4, 4, '8bit');
5888
-
5889
-                    switch ($chunkType) {
5890
-                        case 'IHDR':
5891
-                            // this is where all the file information comes from
5892
-                            $info['width'] = $this->getBytes($data, $p + 8, 4);
5893
-                            $info['height'] = $this->getBytes($data, $p + 12, 4);
5894
-                            $info['bitDepth'] = ord($data[$p + 16]);
5895
-                            $info['colorType'] = ord($data[$p + 17]);
5896
-                            $info['compressionMethod'] = ord($data[$p + 18]);
5897
-                            $info['filterMethod'] = ord($data[$p + 19]);
5898
-                            $info['interlaceMethod'] = ord($data[$p + 20]);
5899
-
5900
-                            //print_r($info);
5901
-                            $haveHeader = 1;
5902
-                            if ($info['compressionMethod'] != 0) {
5903
-                                $error = 1;
5904
-
5905
-                                //debugpng
5906
-                                if (defined("DEBUGPNG") && DEBUGPNG) {
5907
-                                    print '[addPngFromFile unsupported compression method ' . $file . ']';
5908
-                                }
5909
-
5910
-                                $errormsg = 'unsupported compression method';
5911
-                            }
5912
-
5913
-                            if ($info['filterMethod'] != 0) {
5914
-                                $error = 1;
5915
-
5916
-                                //debugpng
5917
-                                if (defined("DEBUGPNG") && DEBUGPNG) {
5918
-                                    print '[addPngFromFile unsupported filter method ' . $file . ']';
5919
-                                }
5920
-
5921
-                                $errormsg = 'unsupported filter method';
5922
-                            }
5923
-                            break;
5924
-
5925
-                        case 'PLTE':
5926
-                            $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5927
-                            break;
5928
-
5929
-                        case 'IDAT':
5930
-                            $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5931
-                            break;
5932
-
5933
-                        case 'tRNS':
5934
-                            //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
5935
-                            //print "tRNS found, color type = ".$info['colorType']."\n";
5936
-                            $transparency = [];
5937
-
5938
-                            switch ($info['colorType']) {
5939
-                                // indexed color, rbg
5940
-                                case 3:
5941
-                                    /* corresponding to entries in the plte chunk
3447
+					}
3448
+				}
3449
+			}
3450
+
3451
+			if ($this->compressionReady && $this->options['compression']) {
3452
+				// then implement ZLIB based compression on CIDtoGID string
3453
+				$data['CIDtoGID_Compressed'] = true;
3454
+				$cidtogid = gzcompress($cidtogid, 6);
3455
+			}
3456
+			$data['CIDtoGID'] = base64_encode($cidtogid);
3457
+			$data['_version_'] = $this->fontcacheVersion;
3458
+			$this->fonts[$font] = $data;
3459
+
3460
+			//Because of potential trouble with php safe mode, expect that the folder already exists.
3461
+			//If not existing, this will hit performance because of missing cached results.
3462
+			if (is_dir($fontcache) && is_writable($fontcache)) {
3463
+				file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';');
3464
+			}
3465
+			$data = null;
3466
+		}
3467
+
3468
+		if (!isset($this->fonts[$font])) {
3469
+			$this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3470
+		}
3471
+
3472
+		//pre_r($this->messages);
3473
+	}
3474
+
3475
+	/**
3476
+	 * if the font is not loaded then load it and make the required object
3477
+	 * else just make it the current font
3478
+	 * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
3479
+	 * note that encoding='none' will need to be used for symbolic fonts
3480
+	 * and 'differences' => an array of mappings between numbers 0->255 and character names.
3481
+	 *
3482
+	 * @param $fontName
3483
+	 * @param string $encoding
3484
+	 * @param bool $set
3485
+	 * @param bool $isSubsetting
3486
+	 * @return int
3487
+	 * @throws FontNotFoundException
3488
+	 */
3489
+	function selectFont($fontName, $encoding = '', $set = true, $isSubsetting = true)
3490
+	{
3491
+		if ($fontName === null || $fontName === '') {
3492
+			return $this->currentFontNum;
3493
+		}
3494
+
3495
+		$ext = substr($fontName, -4);
3496
+		if ($ext === '.afm' || $ext === '.ufm') {
3497
+			$fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
3498
+		}
3499
+
3500
+		if (!isset($this->fonts[$fontName])) {
3501
+			$this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
3502
+
3503
+			// load the file
3504
+			$this->openFont($fontName);
3505
+
3506
+			if (isset($this->fonts[$fontName])) {
3507
+				$this->numObj++;
3508
+				$this->numFonts++;
3509
+
3510
+				$font = &$this->fonts[$fontName];
3511
+
3512
+				$name = basename($fontName);
3513
+				$options = ['name' => $name, 'fontFileName' => $fontName, 'isSubsetting' => $isSubsetting];
3514
+
3515
+				if (is_array($encoding)) {
3516
+					// then encoding and differences might be set
3517
+					if (isset($encoding['encoding'])) {
3518
+						$options['encoding'] = $encoding['encoding'];
3519
+					}
3520
+
3521
+					if (isset($encoding['differences'])) {
3522
+						$options['differences'] = $encoding['differences'];
3523
+					}
3524
+				} else {
3525
+					if (mb_strlen($encoding, '8bit')) {
3526
+						// then perhaps only the encoding has been set
3527
+						$options['encoding'] = $encoding;
3528
+					}
3529
+				}
3530
+
3531
+				$this->o_font($this->numObj, 'new', $options);
3532
+
3533
+				if (file_exists("$fontName.ttf")) {
3534
+					$fileSuffix = 'ttf';
3535
+				} elseif (file_exists("$fontName.TTF")) {
3536
+					$fileSuffix = 'TTF';
3537
+				} elseif (file_exists("$fontName.pfb")) {
3538
+					$fileSuffix = 'pfb';
3539
+				} elseif (file_exists("$fontName.PFB")) {
3540
+					$fileSuffix = 'PFB';
3541
+				} else {
3542
+					$fileSuffix = '';
3543
+				}
3544
+
3545
+				$font['fileSuffix'] = $fileSuffix;
3546
+
3547
+				$font['fontNum'] = $this->numFonts;
3548
+				$font['isSubsetting'] = $isSubsetting && $font['isUnicode'] && strtolower($fileSuffix) === 'ttf';
3549
+
3550
+				// also set the differences here, note that this means that these will take effect only the
3551
+				//first time that a font is selected, else they are ignored
3552
+				if (isset($options['differences'])) {
3553
+					$font['differences'] = $options['differences'];
3554
+				}
3555
+			}
3556
+		}
3557
+
3558
+		if ($set && isset($this->fonts[$fontName])) {
3559
+			// so if for some reason the font was not set in the last one then it will not be selected
3560
+			$this->currentBaseFont = $fontName;
3561
+
3562
+			// the next lines mean that if a new font is selected, then the current text state will be
3563
+			// applied to it as well.
3564
+			$this->currentFont = $this->currentBaseFont;
3565
+			$this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3566
+		}
3567
+
3568
+		return $this->currentFontNum;
3569
+	}
3570
+
3571
+	/**
3572
+	 * sets up the current font, based on the font families, and the current text state
3573
+	 * note that this system is quite flexible, a bold-italic font can be completely different to a
3574
+	 * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3575
+	 * This function is to be called whenever the currentTextState is changed, it will update
3576
+	 * the currentFont setting to whatever the appropriate family one is.
3577
+	 * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3578
+	 * This function will change the currentFont to whatever it should be, but will not change the
3579
+	 * currentBaseFont.
3580
+	 */
3581
+	private function setCurrentFont()
3582
+	{
3583
+		//   if (strlen($this->currentBaseFont) == 0){
3584
+		//     // then assume an initial font
3585
+		//     $this->selectFont($this->defaultFont);
3586
+		//   }
3587
+		//   $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3588
+		//   if (strlen($this->currentTextState)
3589
+		//     && isset($this->fontFamilies[$cf])
3590
+		//       && isset($this->fontFamilies[$cf][$this->currentTextState])){
3591
+		//     // then we are in some state or another
3592
+		//     // and this font has a family, and the current setting exists within it
3593
+		//     // select the font, then return it
3594
+		//     $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3595
+		//     $this->selectFont($nf,'',0);
3596
+		//     $this->currentFont = $nf;
3597
+		//     $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3598
+		//   } else {
3599
+		//     // the this font must not have the right family member for the current state
3600
+		//     // simply assume the base font
3601
+		$this->currentFont = $this->currentBaseFont;
3602
+		$this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3603
+		//  }
3604
+	}
3605
+
3606
+	/**
3607
+	 * function for the user to find out what the ID is of the first page that was created during
3608
+	 * startup - useful if they wish to add something to it later.
3609
+	 *
3610
+	 * @return int
3611
+	 */
3612
+	function getFirstPageId()
3613
+	{
3614
+		return $this->firstPageId;
3615
+	}
3616
+
3617
+	/**
3618
+	 * add content to the currently active object
3619
+	 *
3620
+	 * @param $content
3621
+	 */
3622
+	private function addContent($content)
3623
+	{
3624
+		$this->objects[$this->currentContents]['c'] .= $content;
3625
+	}
3626
+
3627
+	/**
3628
+	 * sets the color for fill operations
3629
+	 *
3630
+	 * @param $color
3631
+	 * @param bool $force
3632
+	 */
3633
+	function setColor($color, $force = false)
3634
+	{
3635
+		$new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3636
+
3637
+		if (!$force && $this->currentColor == $new_color) {
3638
+			return;
3639
+		}
3640
+
3641
+		if (isset($new_color[3])) {
3642
+			$this->currentColor = $new_color;
3643
+			$this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3644
+		} else {
3645
+			if (isset($new_color[2])) {
3646
+				$this->currentColor = $new_color;
3647
+				$this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3648
+			}
3649
+		}
3650
+	}
3651
+
3652
+	/**
3653
+	 * sets the color for fill operations
3654
+	 *
3655
+	 * @param $fillRule
3656
+	 */
3657
+	function setFillRule($fillRule)
3658
+	{
3659
+		if (!in_array($fillRule, ["nonzero", "evenodd"])) {
3660
+			return;
3661
+		}
3662
+
3663
+		$this->fillRule = $fillRule;
3664
+	}
3665
+
3666
+	/**
3667
+	 * sets the color for stroke operations
3668
+	 *
3669
+	 * @param $color
3670
+	 * @param bool $force
3671
+	 */
3672
+	function setStrokeColor($color, $force = false)
3673
+	{
3674
+		$new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3675
+
3676
+		if (!$force && $this->currentStrokeColor == $new_color) {
3677
+			return;
3678
+		}
3679
+
3680
+		if (isset($new_color[3])) {
3681
+			$this->currentStrokeColor = $new_color;
3682
+			$this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3683
+		} else {
3684
+			if (isset($new_color[2])) {
3685
+				$this->currentStrokeColor = $new_color;
3686
+				$this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3687
+			}
3688
+		}
3689
+	}
3690
+
3691
+	/**
3692
+	 * Set the graphics state for compositions
3693
+	 *
3694
+	 * @param $parameters
3695
+	 */
3696
+	function setGraphicsState($parameters)
3697
+	{
3698
+		// Create a new graphics state object if necessary
3699
+		if (($gstate = array_search($parameters, $this->gstates)) === false) {
3700
+			$this->numObj++;
3701
+			$this->o_extGState($this->numObj, 'new', $parameters);
3702
+			$gstate = $this->numStates;
3703
+			$this->gstates[$gstate] = $parameters;
3704
+		}
3705
+		$this->addContent("\n/GS$gstate gs");
3706
+	}
3707
+
3708
+	/**
3709
+	 * Set current blend mode & opacity for lines.
3710
+	 *
3711
+	 * Valid blend modes are:
3712
+	 *
3713
+	 * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3714
+	 * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3715
+	 * Exclusion
3716
+	 *
3717
+	 * @param string $mode    the blend mode to use
3718
+	 * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3719
+	 */
3720
+	function setLineTransparency($mode, $opacity)
3721
+	{
3722
+		static $blend_modes = [
3723
+			"Normal",
3724
+			"Multiply",
3725
+			"Screen",
3726
+			"Overlay",
3727
+			"Darken",
3728
+			"Lighten",
3729
+			"ColorDogde",
3730
+			"ColorBurn",
3731
+			"HardLight",
3732
+			"SoftLight",
3733
+			"Difference",
3734
+			"Exclusion"
3735
+		];
3736
+
3737
+		if (!in_array($mode, $blend_modes)) {
3738
+			$mode = "Normal";
3739
+		}
3740
+
3741
+		if (is_null($this->currentLineTransparency)) {
3742
+			$this->currentLineTransparency = [];
3743
+		}
3744
+
3745
+		if ($mode === (key_exists('mode', $this->currentLineTransparency) ?
3746
+			$this->currentLineTransparency['mode'] : '') &&
3747
+			$opacity === (key_exists('opacity', $this->currentLineTransparency) ?
3748
+			$this->currentLineTransparency["opacity"] : '')) {
3749
+			return;
3750
+		}
3751
+
3752
+		$this->currentLineTransparency["mode"] = $mode;
3753
+		$this->currentLineTransparency["opacity"] = $opacity;
3754
+
3755
+		$options = [
3756
+			"BM" => "/$mode",
3757
+			"CA" => (float)$opacity
3758
+		];
3759
+
3760
+		$this->setGraphicsState($options);
3761
+	}
3762
+
3763
+	/**
3764
+	 * Set current blend mode & opacity for filled objects.
3765
+	 *
3766
+	 * Valid blend modes are:
3767
+	 *
3768
+	 * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3769
+	 * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3770
+	 * Exclusion
3771
+	 *
3772
+	 * @param string $mode    the blend mode to use
3773
+	 * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3774
+	 */
3775
+	function setFillTransparency($mode, $opacity)
3776
+	{
3777
+		static $blend_modes = [
3778
+			"Normal",
3779
+			"Multiply",
3780
+			"Screen",
3781
+			"Overlay",
3782
+			"Darken",
3783
+			"Lighten",
3784
+			"ColorDogde",
3785
+			"ColorBurn",
3786
+			"HardLight",
3787
+			"SoftLight",
3788
+			"Difference",
3789
+			"Exclusion"
3790
+		];
3791
+
3792
+		if (!in_array($mode, $blend_modes)) {
3793
+			$mode = "Normal";
3794
+		}
3795
+
3796
+		if (is_null($this->currentFillTransparency)) {
3797
+			$this->currentFillTransparency = [];
3798
+		}
3799
+
3800
+		if ($mode === (key_exists('mode', $this->currentFillTransparency) ?
3801
+			$this->currentFillTransparency['mode'] : '') &&
3802
+			$opacity === (key_exists('opacity', $this->currentFillTransparency) ?
3803
+			$this->currentFillTransparency["opacity"] : '')) {
3804
+			return;
3805
+		}
3806
+
3807
+		$this->currentFillTransparency["mode"] = $mode;
3808
+		$this->currentFillTransparency["opacity"] = $opacity;
3809
+
3810
+		$options = [
3811
+			"BM" => "/$mode",
3812
+			"ca" => (float)$opacity,
3813
+		];
3814
+
3815
+		$this->setGraphicsState($options);
3816
+	}
3817
+
3818
+	/**
3819
+	 * draw a line from one set of coordinates to another
3820
+	 *
3821
+	 * @param $x1
3822
+	 * @param $y1
3823
+	 * @param $x2
3824
+	 * @param $y2
3825
+	 * @param bool $stroke
3826
+	 */
3827
+	function line($x1, $y1, $x2, $y2, $stroke = true)
3828
+	{
3829
+		$this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3830
+
3831
+		if ($stroke) {
3832
+			$this->addContent(' S');
3833
+		}
3834
+	}
3835
+
3836
+	/**
3837
+	 * draw a bezier curve based on 4 control points
3838
+	 *
3839
+	 * @param $x0
3840
+	 * @param $y0
3841
+	 * @param $x1
3842
+	 * @param $y1
3843
+	 * @param $x2
3844
+	 * @param $y2
3845
+	 * @param $x3
3846
+	 * @param $y3
3847
+	 */
3848
+	function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3849
+	{
3850
+		// in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3851
+		// as the control points for the curve.
3852
+		$this->addContent(
3853
+			sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3854
+		);
3855
+	}
3856
+
3857
+	/**
3858
+	 * draw a part of an ellipse
3859
+	 *
3860
+	 * @param $x0
3861
+	 * @param $y0
3862
+	 * @param $astart
3863
+	 * @param $afinish
3864
+	 * @param $r1
3865
+	 * @param int $r2
3866
+	 * @param int $angle
3867
+	 * @param int $nSeg
3868
+	 */
3869
+	function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3870
+	{
3871
+		$this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3872
+	}
3873
+
3874
+	/**
3875
+	 * draw a filled ellipse
3876
+	 *
3877
+	 * @param $x0
3878
+	 * @param $y0
3879
+	 * @param $r1
3880
+	 * @param int $r2
3881
+	 * @param int $angle
3882
+	 * @param int $nSeg
3883
+	 * @param int $astart
3884
+	 * @param int $afinish
3885
+	 */
3886
+	function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3887
+	{
3888
+		$this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3889
+	}
3890
+
3891
+	/**
3892
+	 * @param $x
3893
+	 * @param $y
3894
+	 */
3895
+	function lineTo($x, $y)
3896
+	{
3897
+		$this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3898
+	}
3899
+
3900
+	/**
3901
+	 * @param $x
3902
+	 * @param $y
3903
+	 */
3904
+	function moveTo($x, $y)
3905
+	{
3906
+		$this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3907
+	}
3908
+
3909
+	/**
3910
+	 * draw a bezier curve based on 4 control points
3911
+	 *
3912
+	 * @param $x1
3913
+	 * @param $y1
3914
+	 * @param $x2
3915
+	 * @param $y2
3916
+	 * @param $x3
3917
+	 * @param $y3
3918
+	 */
3919
+	function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3920
+	{
3921
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3922
+	}
3923
+
3924
+	/**
3925
+	 * draw a bezier curve based on 4 control points
3926
+	 */
3927
+	function quadTo($cpx, $cpy, $x, $y)
3928
+	{
3929
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3930
+	}
3931
+
3932
+	function closePath()
3933
+	{
3934
+		$this->addContent(' h');
3935
+	}
3936
+
3937
+	function endPath()
3938
+	{
3939
+		$this->addContent(' n');
3940
+	}
3941
+
3942
+	/**
3943
+	 * draw an ellipse
3944
+	 * note that the part and filled ellipse are just special cases of this function
3945
+	 *
3946
+	 * draws an ellipse in the current line style
3947
+	 * centered at $x0,$y0, radii $r1,$r2
3948
+	 * if $r2 is not set, then a circle is drawn
3949
+	 * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3950
+	 * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3951
+	 * pretty crappy shape at 2, as we are approximating with bezier curves.
3952
+	 *
3953
+	 * @param $x0
3954
+	 * @param $y0
3955
+	 * @param $r1
3956
+	 * @param int $r2
3957
+	 * @param int $angle
3958
+	 * @param int $nSeg
3959
+	 * @param int $astart
3960
+	 * @param int $afinish
3961
+	 * @param bool $close
3962
+	 * @param bool $fill
3963
+	 * @param bool $stroke
3964
+	 * @param bool $incomplete
3965
+	 */
3966
+	function ellipse(
3967
+		$x0,
3968
+		$y0,
3969
+		$r1,
3970
+		$r2 = 0,
3971
+		$angle = 0,
3972
+		$nSeg = 8,
3973
+		$astart = 0,
3974
+		$afinish = 360,
3975
+		$close = true,
3976
+		$fill = false,
3977
+		$stroke = true,
3978
+		$incomplete = false
3979
+	) {
3980
+		if ($r1 == 0) {
3981
+			return;
3982
+		}
3983
+
3984
+		if ($r2 == 0) {
3985
+			$r2 = $r1;
3986
+		}
3987
+
3988
+		if ($nSeg < 2) {
3989
+			$nSeg = 2;
3990
+		}
3991
+
3992
+		$astart = deg2rad((float)$astart);
3993
+		$afinish = deg2rad((float)$afinish);
3994
+		$totalAngle = $afinish - $astart;
3995
+
3996
+		$dt = $totalAngle / $nSeg;
3997
+		$dtm = $dt / 3;
3998
+
3999
+		if ($angle != 0) {
4000
+			$a = -1 * deg2rad((float)$angle);
4001
+
4002
+			$this->addContent(
4003
+				sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
4004
+			);
4005
+
4006
+			$x0 = 0;
4007
+			$y0 = 0;
4008
+		}
4009
+
4010
+		$t1 = $astart;
4011
+		$a0 = $x0 + $r1 * cos($t1);
4012
+		$b0 = $y0 + $r2 * sin($t1);
4013
+		$c0 = -$r1 * sin($t1);
4014
+		$d0 = $r2 * cos($t1);
4015
+
4016
+		if (!$incomplete) {
4017
+			$this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
4018
+		}
4019
+
4020
+		for ($i = 1; $i <= $nSeg; $i++) {
4021
+			// draw this bit of the total curve
4022
+			$t1 = $i * $dt + $astart;
4023
+			$a1 = $x0 + $r1 * cos($t1);
4024
+			$b1 = $y0 + $r2 * sin($t1);
4025
+			$c1 = -$r1 * sin($t1);
4026
+			$d1 = $r2 * cos($t1);
4027
+
4028
+			$this->addContent(
4029
+				sprintf(
4030
+					"\n%.3F %.3F %.3F %.3F %.3F %.3F c",
4031
+					($a0 + $c0 * $dtm),
4032
+					($b0 + $d0 * $dtm),
4033
+					($a1 - $c1 * $dtm),
4034
+					($b1 - $d1 * $dtm),
4035
+					$a1,
4036
+					$b1
4037
+				)
4038
+			);
4039
+
4040
+			$a0 = $a1;
4041
+			$b0 = $b1;
4042
+			$c0 = $c1;
4043
+			$d0 = $d1;
4044
+		}
4045
+
4046
+		if (!$incomplete) {
4047
+			if ($fill) {
4048
+				$this->addContent(' f');
4049
+			}
4050
+
4051
+			if ($stroke) {
4052
+				if ($close) {
4053
+					$this->addContent(' s'); // small 's' signifies closing the path as well
4054
+				} else {
4055
+					$this->addContent(' S');
4056
+				}
4057
+			}
4058
+		}
4059
+
4060
+		if ($angle != 0) {
4061
+			$this->addContent(' Q');
4062
+		}
4063
+	}
4064
+
4065
+	/**
4066
+	 * this sets the line drawing style.
4067
+	 * width, is the thickness of the line in user units
4068
+	 * cap is the type of cap to put on the line, values can be 'butt','round','square'
4069
+	 *    where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
4070
+	 *    end of the line.
4071
+	 * join can be 'miter', 'round', 'bevel'
4072
+	 * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
4073
+	 *   on and off dashes.
4074
+	 *   (2) represents 2 on, 2 off, 2 on , 2 off ...
4075
+	 *   (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
4076
+	 * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
4077
+	 *
4078
+	 * @param int $width
4079
+	 * @param string $cap
4080
+	 * @param string $join
4081
+	 * @param string $dash
4082
+	 * @param int $phase
4083
+	 */
4084
+	function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
4085
+	{
4086
+		// this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
4087
+		$string = '';
4088
+
4089
+		if ($width > 0) {
4090
+			$string .= "$width w";
4091
+		}
4092
+
4093
+		$ca = ['butt' => 0, 'round' => 1, 'square' => 2];
4094
+
4095
+		if (isset($ca[$cap])) {
4096
+			$string .= " $ca[$cap] J";
4097
+		}
4098
+
4099
+		$ja = ['miter' => 0, 'round' => 1, 'bevel' => 2];
4100
+
4101
+		if (isset($ja[$join])) {
4102
+			$string .= " $ja[$join] j";
4103
+		}
4104
+
4105
+		if (is_array($dash)) {
4106
+			$string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
4107
+		}
4108
+
4109
+		$this->currentLineStyle = $string;
4110
+		$this->addContent("\n$string");
4111
+	}
4112
+
4113
+	/**
4114
+	 * draw a polygon, the syntax for this is similar to the GD polygon command
4115
+	 *
4116
+	 * @param $p
4117
+	 * @param $np
4118
+	 * @param bool $f
4119
+	 */
4120
+	function polygon($p, $np, $f = false)
4121
+	{
4122
+		$this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4123
+
4124
+		for ($i = 2; $i < $np * 2; $i = $i + 2) {
4125
+			$this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4126
+		}
4127
+
4128
+		if ($f) {
4129
+			$this->addContent(' f');
4130
+		} else {
4131
+			$this->addContent(' S');
4132
+		}
4133
+	}
4134
+
4135
+	/**
4136
+	 * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4137
+	 * the coordinates of the upper-right corner
4138
+	 *
4139
+	 * @param $x1
4140
+	 * @param $y1
4141
+	 * @param $width
4142
+	 * @param $height
4143
+	 */
4144
+	function filledRectangle($x1, $y1, $width, $height)
4145
+	{
4146
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
4147
+	}
4148
+
4149
+	/**
4150
+	 * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4151
+	 * the coordinates of the upper-right corner
4152
+	 *
4153
+	 * @param $x1
4154
+	 * @param $y1
4155
+	 * @param $width
4156
+	 * @param $height
4157
+	 */
4158
+	function rectangle($x1, $y1, $width, $height)
4159
+	{
4160
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
4161
+	}
4162
+
4163
+	/**
4164
+	 * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4165
+	 * the coordinates of the upper-right corner
4166
+	 *
4167
+	 * @param $x1
4168
+	 * @param $y1
4169
+	 * @param $width
4170
+	 * @param $height
4171
+	 */
4172
+	function rect($x1, $y1, $width, $height)
4173
+	{
4174
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
4175
+	}
4176
+
4177
+	function stroke(bool $close = false)
4178
+	{
4179
+		$this->addContent("\n" . ($close ? "s" : "S"));
4180
+	}
4181
+
4182
+	function fill()
4183
+	{
4184
+		$this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
4185
+	}
4186
+
4187
+	function fillStroke(bool $close = false)
4188
+	{
4189
+		$this->addContent("\n" . ($close ? "b" : "B") . ($this->fillRule === "evenodd" ? "*" : ""));
4190
+	}
4191
+
4192
+	/**
4193
+	 * @param string $subtype
4194
+	 * @param integer $x
4195
+	 * @param integer $y
4196
+	 * @param integer $w
4197
+	 * @param integer $h
4198
+	 * @return int
4199
+	 */
4200
+	function addXObject($subtype, $x, $y, $w, $h)
4201
+	{
4202
+		$id = ++$this->numObj;
4203
+		$this->o_xobject($id, 'new', ['Subtype' => $subtype, 'bbox' => [$x, $y, $w, $h]]);
4204
+		return $id;
4205
+	}
4206
+
4207
+	/**
4208
+	 * @param integer $numXObject
4209
+	 * @param string $type
4210
+	 * @param array $options
4211
+	 */
4212
+	function setXObjectResource($numXObject, $type, $options)
4213
+	{
4214
+		if (in_array($type, ['procset', 'font', 'xObject'])) {
4215
+			$this->o_xobject($numXObject, $type, $options);
4216
+		}
4217
+	}
4218
+
4219
+	/**
4220
+	 * add signature
4221
+	 *
4222
+	 * $fieldSigId = $cpdf->addFormField(Cpdf::ACROFORM_FIELD_SIG, 'Signature1', 0, 0, 0, 0, 0);
4223
+	 *
4224
+	 * $signatureId = $cpdf->addSignature([
4225
+	 *   'signcert' => file_get_contents('dompdf.crt'),
4226
+	 *   'privkey' => file_get_contents('dompdf.key'),
4227
+	 *   'password' => 'password',
4228
+	 *   'name' => 'DomPDF DEMO',
4229
+	 *   'location' => 'Home',
4230
+	 *   'reason' => 'First Form',
4231
+	 *   'contactinfo' => 'info'
4232
+	 * ]);
4233
+	 * $cpdf->setFormFieldValue($fieldSigId, "$signatureId 0 R");
4234
+	 *
4235
+	 * @param string $signcert
4236
+	 * @param string $privkey
4237
+	 * @param string $password
4238
+	 * @param string|null $name
4239
+	 * @param string|null $location
4240
+	 * @param string|null $reason
4241
+	 * @param string|null $contactinfo
4242
+	 * @return int
4243
+	 */
4244
+	function addSignature($signcert, $privkey, $password = '', $name = null, $location = null, $reason = null, $contactinfo = null) {
4245
+		$sigId = ++$this->numObj;
4246
+		$this->o_sig($sigId, 'new', [
4247
+		  'SignCert' => $signcert,
4248
+		  'PrivKey' => $privkey,
4249
+		  'Password' => $password,
4250
+		  'Name' => $name,
4251
+		  'Location' => $location,
4252
+		  'Reason' => $reason,
4253
+		  'ContactInfo' => $contactinfo
4254
+		]);
4255
+
4256
+		return $sigId;
4257
+	}
4258
+
4259
+	/**
4260
+	 * add field to form
4261
+	 *
4262
+	 * @param string $type ACROFORM_FIELD_*
4263
+	 * @param string $name
4264
+	 * @param $x0
4265
+	 * @param $y0
4266
+	 * @param $x1
4267
+	 * @param $y1
4268
+	 * @param integer $ff Field Flag ACROFORM_FIELD_*_*
4269
+	 * @param float $size
4270
+	 * @param array $color
4271
+	 * @return int
4272
+	 */
4273
+	public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
4274
+	{
4275
+		if (!$this->numFonts) {
4276
+			$this->selectFont($this->defaultFont);
4277
+		}
4278
+
4279
+		$color = implode(' ', $color) . ' rg';
4280
+
4281
+		$currentFontNum = $this->currentFontNum;
4282
+		$font = array_filter($this->objects[$this->currentNode]['info']['fonts'],
4283
+		  function($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; });
4284
+
4285
+		$this->o_acroform($this->acroFormId, 'font',
4286
+		  ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
4287
+
4288
+		$fieldId = ++$this->numObj;
4289
+		$this->o_field($fieldId, 'new', [
4290
+		  'rect' => [$x0, $y0, $x1, $y1],
4291
+		  'F' => 4,
4292
+		  'FT' => "/$type",
4293
+		  'T' => $name,
4294
+		  'Ff' => $ff,
4295
+		  'pageid' => $this->currentPage,
4296
+		  'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
4297
+		]);
4298
+
4299
+		return $fieldId;
4300
+	}
4301
+
4302
+	/**
4303
+	 * set Field value
4304
+	 *
4305
+	 * @param integer $numFieldObj
4306
+	 * @param string $value
4307
+	 */
4308
+	public function setFormFieldValue($numFieldObj, $value)
4309
+	{
4310
+		$this->o_field($numFieldObj, 'set', ['value' => $value]);
4311
+	}
4312
+
4313
+	/**
4314
+	 * set Field value (reference)
4315
+	 *
4316
+	 * @param integer $numFieldObj
4317
+	 * @param integer $numObj Object number
4318
+	 */
4319
+	public function setFormFieldRefValue($numFieldObj, $numObj)
4320
+	{
4321
+		$this->o_field($numFieldObj, 'set', ['refvalue' => $numObj]);
4322
+	}
4323
+
4324
+	/**
4325
+	 * set Field Appearanc (reference)
4326
+	 *
4327
+	 * @param integer $numFieldObj
4328
+	 * @param integer $normalNumObj
4329
+	 * @param integer|null $rolloverNumObj
4330
+	 * @param integer|null $downNumObj
4331
+	 */
4332
+	public function setFormFieldAppearance($numFieldObj, $normalNumObj, $rolloverNumObj = null, $downNumObj = null)
4333
+	{
4334
+		$appearance['N'] = $normalNumObj;
4335
+
4336
+		if ($rolloverNumObj !== null) {
4337
+			$appearance['R'] = $rolloverNumObj;
4338
+		}
4339
+
4340
+		if ($downNumObj !== null) {
4341
+			$appearance['D'] = $downNumObj;
4342
+		}
4343
+
4344
+		$this->o_field($numFieldObj, 'set', ['appearance' => $appearance]);
4345
+	}
4346
+
4347
+	/**
4348
+	 * set Choice Field option values
4349
+	 *
4350
+	 * @param integer $numFieldObj
4351
+	 * @param array $value
4352
+	 */
4353
+	public function setFormFieldOpt($numFieldObj, $value)
4354
+	{
4355
+		$this->o_field($numFieldObj, 'set', ['options' => $value]);
4356
+	}
4357
+
4358
+	/**
4359
+	 * add form to document
4360
+	 *
4361
+	 * @param integer $sigFlags
4362
+	 * @param boolean $needAppearances
4363
+	 */
4364
+	public function addForm($sigFlags = 0, $needAppearances = false)
4365
+	{
4366
+		$this->acroFormId = ++$this->numObj;
4367
+		$this->o_acroform($this->acroFormId, 'new', [
4368
+		  'NeedAppearances' => $needAppearances ? 'true' : 'false',
4369
+		  'SigFlags' => $sigFlags
4370
+		]);
4371
+	}
4372
+
4373
+	/**
4374
+	 * save the current graphic state
4375
+	 */
4376
+	function save()
4377
+	{
4378
+		// we must reset the color cache or it will keep bad colors after clipping
4379
+		$this->currentColor = null;
4380
+		$this->currentStrokeColor = null;
4381
+		$this->addContent("\nq");
4382
+	}
4383
+
4384
+	/**
4385
+	 * restore the last graphic state
4386
+	 */
4387
+	function restore()
4388
+	{
4389
+		// we must reset the color cache or it will keep bad colors after clipping
4390
+		$this->currentColor = null;
4391
+		$this->currentStrokeColor = null;
4392
+		$this->addContent("\nQ");
4393
+	}
4394
+
4395
+	/**
4396
+	 * draw a clipping rectangle, all the elements added after this will be clipped
4397
+	 *
4398
+	 * @param $x1
4399
+	 * @param $y1
4400
+	 * @param $width
4401
+	 * @param $height
4402
+	 */
4403
+	function clippingRectangle($x1, $y1, $width, $height)
4404
+	{
4405
+		$this->save();
4406
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
4407
+	}
4408
+
4409
+	/**
4410
+	 * draw a clipping rounded rectangle, all the elements added after this will be clipped
4411
+	 *
4412
+	 * @param $x1
4413
+	 * @param $y1
4414
+	 * @param $w
4415
+	 * @param $h
4416
+	 * @param $rTL
4417
+	 * @param $rTR
4418
+	 * @param $rBR
4419
+	 * @param $rBL
4420
+	 */
4421
+	function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
4422
+	{
4423
+		$this->save();
4424
+
4425
+		// start: top edge, left end
4426
+		$this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
4427
+
4428
+		// line: bottom edge, left end
4429
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
4430
+
4431
+		// curve: bottom-left corner
4432
+		$this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
4433
+
4434
+		// line: right edge, bottom end
4435
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
4436
+
4437
+		// curve: bottom-right corner
4438
+		$this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
4439
+
4440
+		// line: right edge, top end
4441
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
4442
+
4443
+		// curve: bottom-right corner
4444
+		$this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
4445
+
4446
+		// line: bottom edge, right end
4447
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
4448
+
4449
+		// curve: top-right corner
4450
+		$this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
4451
+
4452
+		// line: top edge, left end
4453
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
4454
+
4455
+		// Close & clip
4456
+		$this->addContent(" W n");
4457
+	}
4458
+
4459
+	/**
4460
+	 * ends the last clipping shape
4461
+	 */
4462
+	function clippingEnd()
4463
+	{
4464
+		$this->restore();
4465
+	}
4466
+
4467
+	/**
4468
+	 * scale
4469
+	 *
4470
+	 * @param float $s_x scaling factor for width as percent
4471
+	 * @param float $s_y scaling factor for height as percent
4472
+	 * @param float $x   Origin abscissa
4473
+	 * @param float $y   Origin ordinate
4474
+	 */
4475
+	function scale($s_x, $s_y, $x, $y)
4476
+	{
4477
+		$y = $this->currentPageSize["height"] - $y;
4478
+
4479
+		$tm = [
4480
+			$s_x,
4481
+			0,
4482
+			0,
4483
+			$s_y,
4484
+			$x * (1 - $s_x),
4485
+			$y * (1 - $s_y)
4486
+		];
4487
+
4488
+		$this->transform($tm);
4489
+	}
4490
+
4491
+	/**
4492
+	 * translate
4493
+	 *
4494
+	 * @param float $t_x movement to the right
4495
+	 * @param float $t_y movement to the bottom
4496
+	 */
4497
+	function translate($t_x, $t_y)
4498
+	{
4499
+		$tm = [
4500
+			1,
4501
+			0,
4502
+			0,
4503
+			1,
4504
+			$t_x,
4505
+			-$t_y
4506
+		];
4507
+
4508
+		$this->transform($tm);
4509
+	}
4510
+
4511
+	/**
4512
+	 * rotate
4513
+	 *
4514
+	 * @param float $angle angle in degrees for counter-clockwise rotation
4515
+	 * @param float $x     Origin abscissa
4516
+	 * @param float $y     Origin ordinate
4517
+	 */
4518
+	function rotate($angle, $x, $y)
4519
+	{
4520
+		$y = $this->currentPageSize["height"] - $y;
4521
+
4522
+		$a = deg2rad($angle);
4523
+		$cos_a = cos($a);
4524
+		$sin_a = sin($a);
4525
+
4526
+		$tm = [
4527
+			$cos_a,
4528
+			-$sin_a,
4529
+			$sin_a,
4530
+			$cos_a,
4531
+			$x - $sin_a * $y - $cos_a * $x,
4532
+			$y - $cos_a * $y + $sin_a * $x,
4533
+		];
4534
+
4535
+		$this->transform($tm);
4536
+	}
4537
+
4538
+	/**
4539
+	 * skew
4540
+	 *
4541
+	 * @param float $angle_x
4542
+	 * @param float $angle_y
4543
+	 * @param float $x Origin abscissa
4544
+	 * @param float $y Origin ordinate
4545
+	 */
4546
+	function skew($angle_x, $angle_y, $x, $y)
4547
+	{
4548
+		$y = $this->currentPageSize["height"] - $y;
4549
+
4550
+		$tan_x = tan(deg2rad($angle_x));
4551
+		$tan_y = tan(deg2rad($angle_y));
4552
+
4553
+		$tm = [
4554
+			1,
4555
+			-$tan_y,
4556
+			-$tan_x,
4557
+			1,
4558
+			$tan_x * $y,
4559
+			$tan_y * $x,
4560
+		];
4561
+
4562
+		$this->transform($tm);
4563
+	}
4564
+
4565
+	/**
4566
+	 * apply graphic transformations
4567
+	 *
4568
+	 * @param array $tm transformation matrix
4569
+	 */
4570
+	function transform($tm)
4571
+	{
4572
+		$this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
4573
+	}
4574
+
4575
+	/**
4576
+	 * add a new page to the document
4577
+	 * this also makes the new page the current active object
4578
+	 *
4579
+	 * @param int $insert
4580
+	 * @param int $id
4581
+	 * @param string $pos
4582
+	 * @return int
4583
+	 */
4584
+	function newPage($insert = 0, $id = 0, $pos = 'after')
4585
+	{
4586
+		// if there is a state saved, then go up the stack closing them
4587
+		// then on the new page, re-open them with the right setings
4588
+
4589
+		if ($this->nStateStack) {
4590
+			for ($i = $this->nStateStack; $i >= 1; $i--) {
4591
+				$this->restoreState($i);
4592
+			}
4593
+		}
4594
+
4595
+		$this->numObj++;
4596
+
4597
+		if ($insert) {
4598
+			// the id from the ezPdf class is the id of the contents of the page, not the page object itself
4599
+			// query that object to find the parent
4600
+			$rid = $this->objects[$id]['onPage'];
4601
+			$opt = ['rid' => $rid, 'pos' => $pos];
4602
+			$this->o_page($this->numObj, 'new', $opt);
4603
+		} else {
4604
+			$this->o_page($this->numObj, 'new');
4605
+		}
4606
+
4607
+		// if there is a stack saved, then put that onto the page
4608
+		if ($this->nStateStack) {
4609
+			for ($i = 1; $i <= $this->nStateStack; $i++) {
4610
+				$this->saveState($i);
4611
+			}
4612
+		}
4613
+
4614
+		// and if there has been a stroke or fill color set, then transfer them
4615
+		if (isset($this->currentColor)) {
4616
+			$this->setColor($this->currentColor, true);
4617
+		}
4618
+
4619
+		if (isset($this->currentStrokeColor)) {
4620
+			$this->setStrokeColor($this->currentStrokeColor, true);
4621
+		}
4622
+
4623
+		// if there is a line style set, then put this in too
4624
+		if (mb_strlen($this->currentLineStyle, '8bit')) {
4625
+			$this->addContent("\n$this->currentLineStyle");
4626
+		}
4627
+
4628
+		// the call to the o_page object set currentContents to the present page, so this can be returned as the page id
4629
+		return $this->currentContents;
4630
+	}
4631
+
4632
+	/**
4633
+	 * Streams the PDF to the client.
4634
+	 *
4635
+	 * @param string $filename The filename to present to the client.
4636
+	 * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
4637
+	 */
4638
+	function stream($filename = "document.pdf", $options = [])
4639
+	{
4640
+		if (headers_sent()) {
4641
+			die("Unable to stream pdf: headers already sent");
4642
+		}
4643
+
4644
+		if (!isset($options["compress"])) $options["compress"] = true;
4645
+		if (!isset($options["Attachment"])) $options["Attachment"] = true;
4646
+
4647
+		$debug = !$options['compress'];
4648
+		$tmp = ltrim($this->output($debug));
4649
+
4650
+		header("Cache-Control: private");
4651
+		header("Content-Type: application/pdf");
4652
+		header("Content-Length: " . mb_strlen($tmp, "8bit"));
4653
+
4654
+		$filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
4655
+		$attachment = $options["Attachment"] ? "attachment" : "inline";
4656
+
4657
+		$encoding = mb_detect_encoding($filename);
4658
+		$fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
4659
+		$fallbackfilename = str_replace("\"", "", $fallbackfilename);
4660
+		$encodedfilename = rawurlencode($filename);
4661
+
4662
+		$contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
4663
+		if ($fallbackfilename !== $filename) {
4664
+			$contentDisposition .= "; filename*=UTF-8''$encodedfilename";
4665
+		}
4666
+		header($contentDisposition);
4667
+
4668
+		echo $tmp;
4669
+		flush();
4670
+	}
4671
+
4672
+	/**
4673
+	 * return the height in units of the current font in the given size
4674
+	 *
4675
+	 * @param $size
4676
+	 * @return float|int
4677
+	 */
4678
+	function getFontHeight($size)
4679
+	{
4680
+		if (!$this->numFonts) {
4681
+			$this->selectFont($this->defaultFont);
4682
+		}
4683
+
4684
+		$font = $this->fonts[$this->currentFont];
4685
+
4686
+		// for the current font, and the given size, what is the height of the font in user units
4687
+		if (isset($font['Ascender']) && isset($font['Descender'])) {
4688
+			$h = $font['Ascender'] - $font['Descender'];
4689
+		} else {
4690
+			$h = $font['FontBBox'][3] - $font['FontBBox'][1];
4691
+		}
4692
+
4693
+		// have to adjust by a font offset for Windows fonts.  unfortunately it looks like
4694
+		// the bounding box calculations are wrong and I don't know why.
4695
+		if (isset($font['FontHeightOffset'])) {
4696
+			// For CourierNew from Windows this needs to be -646 to match the
4697
+			// Adobe native Courier font.
4698
+			//
4699
+			// For FreeMono from GNU this needs to be -337 to match the
4700
+			// Courier font.
4701
+			//
4702
+			// Both have been added manually to the .afm and .ufm files.
4703
+			$h += (int)$font['FontHeightOffset'];
4704
+		}
4705
+
4706
+		return $size * $h / 1000;
4707
+	}
4708
+
4709
+	/**
4710
+	 * @param $size
4711
+	 * @return float|int
4712
+	 */
4713
+	function getFontXHeight($size)
4714
+	{
4715
+		if (!$this->numFonts) {
4716
+			$this->selectFont($this->defaultFont);
4717
+		}
4718
+
4719
+		$font = $this->fonts[$this->currentFont];
4720
+
4721
+		// for the current font, and the given size, what is the height of the font in user units
4722
+		if (isset($font['XHeight'])) {
4723
+			$xh = $font['Ascender'] - $font['Descender'];
4724
+		} else {
4725
+			$xh = $this->getFontHeight($size) / 2;
4726
+		}
4727
+
4728
+		return $size * $xh / 1000;
4729
+	}
4730
+
4731
+	/**
4732
+	 * return the font descender, this will normally return a negative number
4733
+	 * if you add this number to the baseline, you get the level of the bottom of the font
4734
+	 * it is in the pdf user units
4735
+	 *
4736
+	 * @param $size
4737
+	 * @return float|int
4738
+	 */
4739
+	function getFontDescender($size)
4740
+	{
4741
+		// note that this will most likely return a negative value
4742
+		if (!$this->numFonts) {
4743
+			$this->selectFont($this->defaultFont);
4744
+		}
4745
+
4746
+		//$h = $this->fonts[$this->currentFont]['FontBBox'][1];
4747
+		$h = $this->fonts[$this->currentFont]['Descender'];
4748
+
4749
+		return $size * $h / 1000;
4750
+	}
4751
+
4752
+	/**
4753
+	 * filter the text, this is applied to all text just before being inserted into the pdf document
4754
+	 * it escapes the various things that need to be escaped, and so on
4755
+	 *
4756
+	 * @access private
4757
+	 *
4758
+	 * @param $text
4759
+	 * @param bool $bom
4760
+	 * @param bool $convert_encoding
4761
+	 * @return string
4762
+	 */
4763
+	function filterText($text, $bom = true, $convert_encoding = true)
4764
+	{
4765
+		if (!$this->numFonts) {
4766
+			$this->selectFont($this->defaultFont);
4767
+		}
4768
+
4769
+		if ($convert_encoding) {
4770
+			$cf = $this->currentFont;
4771
+			if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4772
+				$text = $this->utf8toUtf16BE($text, $bom);
4773
+			} else {
4774
+				//$text = html_entity_decode($text, ENT_QUOTES);
4775
+				$text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4776
+			}
4777
+		} else if ($bom) {
4778
+			$text = $this->utf8toUtf16BE($text, $bom);
4779
+		}
4780
+
4781
+		// the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4782
+		return strtr($text, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
4783
+	}
4784
+
4785
+	/**
4786
+	 * return array containing codepoints (UTF-8 character values) for the
4787
+	 * string passed in.
4788
+	 *
4789
+	 * based on the excellent TCPDF code by Nicola Asuni and the
4790
+	 * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4791
+	 *
4792
+	 * @access private
4793
+	 * @author Orion Richardson
4794
+	 * @since  January 5, 2008
4795
+	 *
4796
+	 * @param string $text UTF-8 string to process
4797
+	 *
4798
+	 * @return array UTF-8 codepoints array for the string
4799
+	 */
4800
+	function utf8toCodePointsArray(&$text)
4801
+	{
4802
+		$length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4803
+		$unicode = []; // array containing unicode values
4804
+		$bytes = []; // array containing single character byte sequences
4805
+		$numbytes = 1; // number of octets needed to represent the UTF-8 character
4806
+
4807
+		for ($i = 0; $i < $length; $i++) {
4808
+			$c = ord($text[$i]); // get one string character at time
4809
+			if (count($bytes) === 0) { // get starting octect
4810
+				if ($c <= 0x7F) {
4811
+					$unicode[] = $c; // use the character "as is" because is ASCII
4812
+					$numbytes = 1;
4813
+				} elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4814
+					$bytes[] = ($c - 0xC0) << 0x06;
4815
+					$numbytes = 2;
4816
+				} elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4817
+					$bytes[] = ($c - 0xE0) << 0x0C;
4818
+					$numbytes = 3;
4819
+				} elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4820
+					$bytes[] = ($c - 0xF0) << 0x12;
4821
+					$numbytes = 4;
4822
+				} else {
4823
+					// use replacement character for other invalid sequences
4824
+					$unicode[] = 0xFFFD;
4825
+					$bytes = [];
4826
+					$numbytes = 1;
4827
+				}
4828
+			} elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4829
+				$bytes[] = $c - 0x80;
4830
+				if (count($bytes) === $numbytes) {
4831
+					// compose UTF-8 bytes to a single unicode value
4832
+					$c = $bytes[0];
4833
+					for ($j = 1; $j < $numbytes; $j++) {
4834
+						$c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4835
+					}
4836
+					if ((($c >= 0xD800) and ($c <= 0xDFFF)) or ($c >= 0x10FFFF)) {
4837
+						// The definition of UTF-8 prohibits encoding character numbers between
4838
+						// U+D800 and U+DFFF, which are reserved for use with the UTF-16
4839
+						// encoding form (as surrogate pairs) and do not directly represent
4840
+						// characters.
4841
+						$unicode[] = 0xFFFD; // use replacement character
4842
+					} else {
4843
+						$unicode[] = $c; // add char to array
4844
+					}
4845
+					// reset data for next char
4846
+					$bytes = [];
4847
+					$numbytes = 1;
4848
+				}
4849
+			} else {
4850
+				// use replacement character for other invalid sequences
4851
+				$unicode[] = 0xFFFD;
4852
+				$bytes = [];
4853
+				$numbytes = 1;
4854
+			}
4855
+		}
4856
+
4857
+		return $unicode;
4858
+	}
4859
+
4860
+	/**
4861
+	 * convert UTF-8 to UTF-16 with an additional byte order marker
4862
+	 * at the front if required.
4863
+	 *
4864
+	 * based on the excellent TCPDF code by Nicola Asuni and the
4865
+	 * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4866
+	 *
4867
+	 * @access private
4868
+	 * @author Orion Richardson
4869
+	 * @since  January 5, 2008
4870
+	 *
4871
+	 * @param string  $text UTF-8 string to process
4872
+	 * @param boolean $bom  whether to add the byte order marker
4873
+	 *
4874
+	 * @return string UTF-16 result string
4875
+	 */
4876
+	function utf8toUtf16BE(&$text, $bom = true)
4877
+	{
4878
+		$out = $bom ? "\xFE\xFF" : '';
4879
+
4880
+		$unicode = $this->utf8toCodePointsArray($text);
4881
+		foreach ($unicode as $c) {
4882
+			if ($c === 0xFFFD) {
4883
+				$out .= "\xFF\xFD"; // replacement character
4884
+			} elseif ($c < 0x10000) {
4885
+				$out .= chr($c >> 0x08) . chr($c & 0xFF);
4886
+			} else {
4887
+				$c -= 0x10000;
4888
+				$w1 = 0xD800 | ($c >> 0x10);
4889
+				$w2 = 0xDC00 | ($c & 0x3FF);
4890
+				$out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4891
+			}
4892
+		}
4893
+
4894
+		return $out;
4895
+	}
4896
+
4897
+	/**
4898
+	 * given a start position and information about how text is to be laid out, calculate where
4899
+	 * on the page the text will end
4900
+	 *
4901
+	 * @param $x
4902
+	 * @param $y
4903
+	 * @param $angle
4904
+	 * @param $size
4905
+	 * @param $wa
4906
+	 * @param $text
4907
+	 * @return array
4908
+	 */
4909
+	private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4910
+	{
4911
+		// given this information return an array containing x and y for the end position as elements 0 and 1
4912
+		$w = $this->getTextWidth($size, $text);
4913
+
4914
+		// need to adjust for the number of spaces in this text
4915
+		$words = explode(' ', $text);
4916
+		$nspaces = count($words) - 1;
4917
+		$w += $wa * $nspaces;
4918
+		$a = deg2rad((float)$angle);
4919
+
4920
+		return [cos($a) * $w + $x, -sin($a) * $w + $y];
4921
+	}
4922
+
4923
+	/**
4924
+	 * Callback method used by smallCaps
4925
+	 *
4926
+	 * @param array $matches
4927
+	 *
4928
+	 * @return string
4929
+	 */
4930
+	function toUpper($matches)
4931
+	{
4932
+		return mb_strtoupper($matches[0]);
4933
+	}
4934
+
4935
+	function concatMatches($matches)
4936
+	{
4937
+		$str = "";
4938
+		foreach ($matches as $match) {
4939
+			$str .= $match[0];
4940
+		}
4941
+
4942
+		return $str;
4943
+	}
4944
+
4945
+	/**
4946
+	 * register text for font subsetting
4947
+	 *
4948
+	 * @param $font
4949
+	 * @param $text
4950
+	 */
4951
+	function registerText($font, $text)
4952
+	{
4953
+		if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4954
+			return;
4955
+		}
4956
+
4957
+		if (!isset($this->stringSubsets[$font])) {
4958
+			$this->stringSubsets[$font] = [];
4959
+		}
4960
+
4961
+		$this->stringSubsets[$font] = array_unique(
4962
+			array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
4963
+		);
4964
+	}
4965
+
4966
+	/**
4967
+	 * add text to the document, at a specified location, size and angle on the page
4968
+	 *
4969
+	 * @param $x
4970
+	 * @param $y
4971
+	 * @param $size
4972
+	 * @param $text
4973
+	 * @param int $angle
4974
+	 * @param int $wordSpaceAdjust
4975
+	 * @param int $charSpaceAdjust
4976
+	 * @param bool $smallCaps
4977
+	 */
4978
+	function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4979
+	{
4980
+		if (!$this->numFonts) {
4981
+			$this->selectFont($this->defaultFont);
4982
+		}
4983
+
4984
+		$text = str_replace(["\r", "\n"], "", $text);
4985
+
4986
+		// if ($smallCaps) {
4987
+		//     preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4988
+		//     $lower = $this->concatMatches($matches);
4989
+		//     d($lower);
4990
+
4991
+		//     preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
4992
+		//     $other = $this->concatMatches($matches);
4993
+		//     d($other);
4994
+
4995
+		//     $text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
4996
+		// }
4997
+
4998
+		// if there are any open callbacks, then they should be called, to show the start of the line
4999
+		if ($this->nCallback > 0) {
5000
+			for ($i = $this->nCallback; $i > 0; $i--) {
5001
+				// call each function
5002
+				$info = [
5003
+					'x'         => $x,
5004
+					'y'         => $y,
5005
+					'angle'     => $angle,
5006
+					'status'    => 'sol',
5007
+					'p'         => $this->callback[$i]['p'],
5008
+					'nCallback' => $this->callback[$i]['nCallback'],
5009
+					'height'    => $this->callback[$i]['height'],
5010
+					'descender' => $this->callback[$i]['descender']
5011
+				];
5012
+
5013
+				$func = $this->callback[$i]['f'];
5014
+				$this->$func($info);
5015
+			}
5016
+		}
5017
+
5018
+		if ($angle == 0) {
5019
+			$this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
5020
+		} else {
5021
+			$a = deg2rad((float)$angle);
5022
+			$this->addContent(
5023
+				sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
5024
+			);
5025
+		}
5026
+
5027
+		if ($wordSpaceAdjust != 0) {
5028
+			$this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
5029
+		}
5030
+
5031
+		if ($charSpaceAdjust != 0) {
5032
+			$this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
5033
+		}
5034
+
5035
+		$len = mb_strlen($text);
5036
+		$start = 0;
5037
+
5038
+		if ($start < $len) {
5039
+			$part = $text; // OAR - Don't need this anymore, given that $start always equals zero.  substr($text, $start);
5040
+			$place_text = $this->filterText($part, false);
5041
+			// modify unicode text so that extra word spacing is manually implemented (bug #)
5042
+			if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
5043
+				$space_scale = 1000 / $size;
5044
+				$place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
5045
+			}
5046
+			$this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
5047
+			$this->addContent(" [($place_text)] TJ");
5048
+		}
5049
+
5050
+		if ($wordSpaceAdjust != 0) {
5051
+			$this->addContent(sprintf(" %.3F Tw", 0));
5052
+		}
5053
+
5054
+		if ($charSpaceAdjust != 0) {
5055
+			$this->addContent(sprintf(" %.3F Tc", 0));
5056
+		}
5057
+
5058
+		$this->addContent(' ET');
5059
+
5060
+		// if there are any open callbacks, then they should be called, to show the end of the line
5061
+		if ($this->nCallback > 0) {
5062
+			for ($i = $this->nCallback; $i > 0; $i--) {
5063
+				// call each function
5064
+				$tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
5065
+				$info = [
5066
+					'x'         => $tmp[0],
5067
+					'y'         => $tmp[1],
5068
+					'angle'     => $angle,
5069
+					'status'    => 'eol',
5070
+					'p'         => $this->callback[$i]['p'],
5071
+					'nCallback' => $this->callback[$i]['nCallback'],
5072
+					'height'    => $this->callback[$i]['height'],
5073
+					'descender' => $this->callback[$i]['descender']
5074
+				];
5075
+				$func = $this->callback[$i]['f'];
5076
+				$this->$func($info);
5077
+			}
5078
+		}
5079
+
5080
+		if ($this->fonts[$this->currentFont]['isSubsetting']) {
5081
+			$this->registerText($this->currentFont, $text);
5082
+		}
5083
+	}
5084
+
5085
+	/**
5086
+	 * calculate how wide a given text string will be on a page, at a given size.
5087
+	 * this can be called externally, but is also used by the other class functions
5088
+	 *
5089
+	 * @param float $size
5090
+	 * @param string $text
5091
+	 * @param float $word_spacing
5092
+	 * @param float $char_spacing
5093
+	 * @return float
5094
+	 */
5095
+	function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0)
5096
+	{
5097
+		static $ord_cache = [];
5098
+
5099
+		// this function should not change any of the settings, though it will need to
5100
+		// track any directives which change during calculation, so copy them at the start
5101
+		// and put them back at the end.
5102
+		$store_currentTextState = $this->currentTextState;
5103
+
5104
+		if (!$this->numFonts) {
5105
+			$this->selectFont($this->defaultFont);
5106
+		}
5107
+
5108
+		$text = str_replace(["\r", "\n"], "", $text);
5109
+
5110
+		// converts a number or a float to a string so it can get the width
5111
+		$text = "$text";
5112
+
5113
+		// hmm, this is where it all starts to get tricky - use the font information to
5114
+		// calculate the width of each character, add them up and convert to user units
5115
+		$w = 0;
5116
+		$cf = $this->currentFont;
5117
+		$current_font = $this->fonts[$cf];
5118
+		$space_scale = 1000 / ($size > 0 ? $size : 1);
5119
+
5120
+		if ($current_font['isUnicode']) {
5121
+			// for Unicode, use the code points array to calculate width rather
5122
+			// than just the string itself
5123
+			$unicode = $this->utf8toCodePointsArray($text);
5124
+
5125
+			foreach ($unicode as $char) {
5126
+				// check if we have to replace character
5127
+				if (isset($current_font['differences'][$char])) {
5128
+					$char = $current_font['differences'][$char];
5129
+				}
5130
+
5131
+				if (isset($current_font['C'][$char])) {
5132
+					$char_width = $current_font['C'][$char];
5133
+
5134
+					// add the character width
5135
+					$w += $char_width;
5136
+
5137
+					// add additional padding for space
5138
+					if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5139
+						$w += $word_spacing * $space_scale;
5140
+					}
5141
+				}
5142
+			}
5143
+
5144
+			// add additional char spacing
5145
+			if ($char_spacing != 0) {
5146
+				$w += $char_spacing * $space_scale * count($unicode);
5147
+			}
5148
+
5149
+		} else {
5150
+			// If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
5151
+			if ($this->isUnicode) {
5152
+				$text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
5153
+			}
5154
+
5155
+			$len = mb_strlen($text, 'Windows-1252');
5156
+
5157
+			for ($i = 0; $i < $len; $i++) {
5158
+				$c = $text[$i];
5159
+				$char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
5160
+
5161
+				// check if we have to replace character
5162
+				if (isset($current_font['differences'][$char])) {
5163
+					$char = $current_font['differences'][$char];
5164
+				}
5165
+
5166
+				if (isset($current_font['C'][$char])) {
5167
+					$char_width = $current_font['C'][$char];
5168
+
5169
+					// add the character width
5170
+					$w += $char_width;
5171
+
5172
+					// add additional padding for space
5173
+					if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5174
+						$w += $word_spacing * $space_scale;
5175
+					}
5176
+				}
5177
+			}
5178
+
5179
+			// add additional char spacing
5180
+			if ($char_spacing != 0) {
5181
+				$w += $char_spacing * $space_scale * $len;
5182
+			}
5183
+		}
5184
+
5185
+		$this->currentTextState = $store_currentTextState;
5186
+		$this->setCurrentFont();
5187
+
5188
+		return $w * $size / 1000;
5189
+	}
5190
+
5191
+	/**
5192
+	 * this will be called at a new page to return the state to what it was on the
5193
+	 * end of the previous page, before the stack was closed down
5194
+	 * This is to get around not being able to have open 'q' across pages
5195
+	 *
5196
+	 * @param int $pageEnd
5197
+	 */
5198
+	function saveState($pageEnd = 0)
5199
+	{
5200
+		if ($pageEnd) {
5201
+			// this will be called at a new page to return the state to what it was on the
5202
+			// end of the previous page, before the stack was closed down
5203
+			// This is to get around not being able to have open 'q' across pages
5204
+			$opt = $this->stateStack[$pageEnd];
5205
+			// ok to use this as stack starts numbering at 1
5206
+			$this->setColor($opt['col'], true);
5207
+			$this->setStrokeColor($opt['str'], true);
5208
+			$this->addContent("\n" . $opt['lin']);
5209
+			//    $this->currentLineStyle = $opt['lin'];
5210
+		} else {
5211
+			$this->nStateStack++;
5212
+			$this->stateStack[$this->nStateStack] = [
5213
+				'col' => $this->currentColor,
5214
+				'str' => $this->currentStrokeColor,
5215
+				'lin' => $this->currentLineStyle
5216
+			];
5217
+		}
5218
+
5219
+		$this->save();
5220
+	}
5221
+
5222
+	/**
5223
+	 * restore a previously saved state
5224
+	 *
5225
+	 * @param int $pageEnd
5226
+	 */
5227
+	function restoreState($pageEnd = 0)
5228
+	{
5229
+		if (!$pageEnd) {
5230
+			$n = $this->nStateStack;
5231
+			$this->currentColor = $this->stateStack[$n]['col'];
5232
+			$this->currentStrokeColor = $this->stateStack[$n]['str'];
5233
+			$this->addContent("\n" . $this->stateStack[$n]['lin']);
5234
+			$this->currentLineStyle = $this->stateStack[$n]['lin'];
5235
+			$this->stateStack[$n] = null;
5236
+			unset($this->stateStack[$n]);
5237
+			$this->nStateStack--;
5238
+		}
5239
+
5240
+		$this->restore();
5241
+	}
5242
+
5243
+	/**
5244
+	 * make a loose object, the output will go into this object, until it is closed, then will revert to
5245
+	 * the current one.
5246
+	 * this object will not appear until it is included within a page.
5247
+	 * the function will return the object number
5248
+	 *
5249
+	 * @return int
5250
+	 */
5251
+	function openObject()
5252
+	{
5253
+		$this->nStack++;
5254
+		$this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5255
+		// add a new object of the content type, to hold the data flow
5256
+		$this->numObj++;
5257
+		$this->o_contents($this->numObj, 'new');
5258
+		$this->currentContents = $this->numObj;
5259
+		$this->looseObjects[$this->numObj] = 1;
5260
+
5261
+		return $this->numObj;
5262
+	}
5263
+
5264
+	/**
5265
+	 * open an existing object for editing
5266
+	 *
5267
+	 * @param $id
5268
+	 */
5269
+	function reopenObject($id)
5270
+	{
5271
+		$this->nStack++;
5272
+		$this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5273
+		$this->currentContents = $id;
5274
+
5275
+		// also if this object is the primary contents for a page, then set the current page to its parent
5276
+		if (isset($this->objects[$id]['onPage'])) {
5277
+			$this->currentPage = $this->objects[$id]['onPage'];
5278
+		}
5279
+	}
5280
+
5281
+	/**
5282
+	 * close an object
5283
+	 */
5284
+	function closeObject()
5285
+	{
5286
+		// close the object, as long as there was one open in the first place, which will be indicated by
5287
+		// an objectId on the stack.
5288
+		if ($this->nStack > 0) {
5289
+			$this->currentContents = $this->stack[$this->nStack]['c'];
5290
+			$this->currentPage = $this->stack[$this->nStack]['p'];
5291
+			$this->nStack--;
5292
+			// easier to probably not worry about removing the old entries, they will be overwritten
5293
+			// if there are new ones.
5294
+		}
5295
+	}
5296
+
5297
+	/**
5298
+	 * stop an object from appearing on pages from this point on
5299
+	 *
5300
+	 * @param $id
5301
+	 */
5302
+	function stopObject($id)
5303
+	{
5304
+		// if an object has been appearing on pages up to now, then stop it, this page will
5305
+		// be the last one that could contain it.
5306
+		if (isset($this->addLooseObjects[$id])) {
5307
+			$this->addLooseObjects[$id] = '';
5308
+		}
5309
+	}
5310
+
5311
+	/**
5312
+	 * after an object has been created, it wil only show if it has been added, using this function.
5313
+	 *
5314
+	 * @param $id
5315
+	 * @param string $options
5316
+	 */
5317
+	function addObject($id, $options = 'add')
5318
+	{
5319
+		// add the specified object to the page
5320
+		if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
5321
+			// then it is a valid object, and it is not being added to itself
5322
+			switch ($options) {
5323
+				case 'all':
5324
+					// then this object is to be added to this page (done in the next block) and
5325
+					// all future new pages.
5326
+					$this->addLooseObjects[$id] = 'all';
5327
+
5328
+				case 'add':
5329
+					if (isset($this->objects[$this->currentContents]['onPage'])) {
5330
+						// then the destination contents is the primary for the page
5331
+						// (though this object is actually added to that page)
5332
+						$this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
5333
+					}
5334
+					break;
5335
+
5336
+				case 'even':
5337
+					$this->addLooseObjects[$id] = 'even';
5338
+					$pageObjectId = $this->objects[$this->currentContents]['onPage'];
5339
+					if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
5340
+						$this->addObject($id);
5341
+						// hacky huh :)
5342
+					}
5343
+					break;
5344
+
5345
+				case 'odd':
5346
+					$this->addLooseObjects[$id] = 'odd';
5347
+					$pageObjectId = $this->objects[$this->currentContents]['onPage'];
5348
+					if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
5349
+						$this->addObject($id);
5350
+						// hacky huh :)
5351
+					}
5352
+					break;
5353
+
5354
+				case 'next':
5355
+					$this->addLooseObjects[$id] = 'all';
5356
+					break;
5357
+
5358
+				case 'nexteven':
5359
+					$this->addLooseObjects[$id] = 'even';
5360
+					break;
5361
+
5362
+				case 'nextodd':
5363
+					$this->addLooseObjects[$id] = 'odd';
5364
+					break;
5365
+			}
5366
+		}
5367
+	}
5368
+
5369
+	/**
5370
+	 * return a storable representation of a specific object
5371
+	 *
5372
+	 * @param $id
5373
+	 * @return string|null
5374
+	 */
5375
+	function serializeObject($id)
5376
+	{
5377
+		if (array_key_exists($id, $this->objects)) {
5378
+			return serialize($this->objects[$id]);
5379
+		}
5380
+
5381
+		return null;
5382
+	}
5383
+
5384
+	/**
5385
+	 * restore an object from its stored representation. Returns its new object id.
5386
+	 *
5387
+	 * @param $obj
5388
+	 * @return int
5389
+	 */
5390
+	function restoreSerializedObject($obj)
5391
+	{
5392
+		$obj_id = $this->openObject();
5393
+		$this->objects[$obj_id] = unserialize($obj);
5394
+		$this->closeObject();
5395
+
5396
+		return $obj_id;
5397
+	}
5398
+
5399
+	/**
5400
+	 * Embeds a file inside the PDF
5401
+	 *
5402
+	 * @param string $filepath path to the file to store inside the PDF
5403
+	 * @param string $embeddedFilename the filename displayed in the list of embedded files
5404
+	 * @param string $description a description in the list of embedded files
5405
+	 */
5406
+	public function addEmbeddedFile(string $filepath, string $embeddedFilename, string $description): void
5407
+	{
5408
+		$this->numObj++;
5409
+		$this->o_embedded_file_dictionary(
5410
+			$this->numObj,
5411
+			'new',
5412
+			[
5413
+				'filepath' => $filepath,
5414
+				'filename' => $embeddedFilename,
5415
+				'description' => $description
5416
+			]
5417
+		);
5418
+	}
5419
+
5420
+	/**
5421
+	 * add content to the documents info object
5422
+	 *
5423
+	 * @param $label
5424
+	 * @param int $value
5425
+	 */
5426
+	function addInfo($label, $value = 0)
5427
+	{
5428
+		// this will only work if the label is one of the valid ones.
5429
+		// modify this so that arrays can be passed as well.
5430
+		// if $label is an array then assume that it is key => value pairs
5431
+		// else assume that they are both scalar, anything else will probably error
5432
+		if (is_array($label)) {
5433
+			foreach ($label as $l => $v) {
5434
+				$this->o_info($this->infoObject, $l, $v);
5435
+			}
5436
+		} else {
5437
+			$this->o_info($this->infoObject, $label, $value);
5438
+		}
5439
+	}
5440
+
5441
+	/**
5442
+	 * set the viewer preferences of the document, it is up to the browser to obey these.
5443
+	 *
5444
+	 * @param $label
5445
+	 * @param int $value
5446
+	 */
5447
+	function setPreferences($label, $value = 0)
5448
+	{
5449
+		// this will only work if the label is one of the valid ones.
5450
+		if (is_array($label)) {
5451
+			foreach ($label as $l => $v) {
5452
+				$this->o_catalog($this->catalogId, 'viewerPreferences', [$l => $v]);
5453
+			}
5454
+		} else {
5455
+			$this->o_catalog($this->catalogId, 'viewerPreferences', [$label => $value]);
5456
+		}
5457
+	}
5458
+
5459
+	/**
5460
+	 * extract an integer from a position in a byte stream
5461
+	 *
5462
+	 * @param $data
5463
+	 * @param $pos
5464
+	 * @param $num
5465
+	 * @return int
5466
+	 */
5467
+	private function getBytes(&$data, $pos, $num)
5468
+	{
5469
+		// return the integer represented by $num bytes from $pos within $data
5470
+		$ret = 0;
5471
+		for ($i = 0; $i < $num; $i++) {
5472
+			$ret *= 256;
5473
+			$ret += ord($data[$pos + $i]);
5474
+		}
5475
+
5476
+		return $ret;
5477
+	}
5478
+
5479
+	/**
5480
+	 * Check if image already added to pdf image directory.
5481
+	 * If yes, need not to create again (pass empty data)
5482
+	 *
5483
+	 * @param string $imgname
5484
+	 * @return bool
5485
+	 */
5486
+	function image_iscached($imgname)
5487
+	{
5488
+		return isset($this->imagelist[$imgname]);
5489
+	}
5490
+
5491
+	/**
5492
+	 * add a PNG image into the document, from a GD object
5493
+	 * this should work with remote files
5494
+	 *
5495
+	 * @param \GdImage|resource $img A GD resource
5496
+	 * @param string $file The PNG file
5497
+	 * @param float $x X position
5498
+	 * @param float $y Y position
5499
+	 * @param float $w Width
5500
+	 * @param float $h Height
5501
+	 * @param bool $is_mask true if the image is a mask
5502
+	 * @param bool $mask true if the image is masked
5503
+	 * @throws Exception
5504
+	 */
5505
+	function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5506
+	{
5507
+		if (!function_exists("imagepng")) {
5508
+			throw new \Exception("The PHP GD extension is required, but is not installed.");
5509
+		}
5510
+
5511
+		//if already cached, need not to read again
5512
+		if (isset($this->imagelist[$file])) {
5513
+			$data = null;
5514
+		} else {
5515
+			// Example for transparency handling on new image. Retain for current image
5516
+			// $tIndex = imagecolortransparent($img);
5517
+			// if ($tIndex > 0) {
5518
+			//   $tColor    = imagecolorsforindex($img, $tIndex);
5519
+			//   $new_tIndex    = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
5520
+			//   imagefill($new_img, 0, 0, $new_tIndex);
5521
+			//   imagecolortransparent($new_img, $new_tIndex);
5522
+			// }
5523
+			// blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
5524
+			//imagealphablending($img, true);
5525
+
5526
+			//default, but explicitely set to ensure pdf compatibility
5527
+			imagesavealpha($img, false/*!$is_mask && !$mask*/);
5528
+
5529
+			$error = 0;
5530
+			//DEBUG_IMG_TEMP
5531
+			//debugpng
5532
+			if (defined("DEBUGPNG") && DEBUGPNG) {
5533
+				print '[addImagePng ' . $file . ']';
5534
+			}
5535
+
5536
+			ob_start();
5537
+			@imagepng($img);
5538
+			$data = ob_get_clean();
5539
+
5540
+			if ($data == '') {
5541
+				$error = 1;
5542
+				$errormsg = 'trouble writing file from GD';
5543
+				//DEBUG_IMG_TEMP
5544
+				//debugpng
5545
+				if (defined("DEBUGPNG") && DEBUGPNG) {
5546
+					print 'trouble writing file from GD';
5547
+				}
5548
+			}
5549
+
5550
+			if ($error) {
5551
+				$this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5552
+
5553
+				return;
5554
+			}
5555
+		}  //End isset($this->imagelist[$file]) (png Duplicate removal)
5556
+
5557
+		$this->addPngFromBuf($data, $file, $x, $y, $w, $h, $is_mask, $mask);
5558
+	}
5559
+
5560
+	/**
5561
+	 * @param $file
5562
+	 * @param $x
5563
+	 * @param $y
5564
+	 * @param $w
5565
+	 * @param $h
5566
+	 * @param $byte
5567
+	 */
5568
+	protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
5569
+	{
5570
+		// generate images
5571
+		$img = imagecreatefrompng($file);
5572
+
5573
+		if ($img === false) {
5574
+			return;
5575
+		}
5576
+
5577
+		// FIXME The pixel transformation doesn't work well with 8bit PNGs
5578
+		$eight_bit = ($byte & 4) !== 4;
5579
+
5580
+		$wpx = imagesx($img);
5581
+		$hpx = imagesy($img);
5582
+
5583
+		imagesavealpha($img, false);
5584
+
5585
+		// create temp alpha file
5586
+		$tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
5587
+		@unlink($tempfile_alpha);
5588
+		$tempfile_alpha = "$tempfile_alpha.png";
5589
+
5590
+		// create temp plain file
5591
+		$tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
5592
+		@unlink($tempfile_plain);
5593
+		$tempfile_plain = "$tempfile_plain.png";
5594
+
5595
+		$imgalpha = imagecreate($wpx, $hpx);
5596
+		imagesavealpha($imgalpha, false);
5597
+
5598
+		// generate gray scale palette (0 -> 255)
5599
+		for ($c = 0; $c < 256; ++$c) {
5600
+			imagecolorallocate($imgalpha, $c, $c, $c);
5601
+		}
5602
+
5603
+		// Use PECL gmagick + Graphics Magic to process transparent PNG images
5604
+		if (extension_loaded("gmagick")) {
5605
+			$gmagick = new \Gmagick($file);
5606
+			$gmagick->setimageformat('png');
5607
+
5608
+			// Get opacity channel (negative of alpha channel)
5609
+			$alpha_channel_neg = clone $gmagick;
5610
+			$alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
5611
+
5612
+			// Negate opacity channel
5613
+			$alpha_channel = new \Gmagick();
5614
+			$alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
5615
+			$alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
5616
+			$alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
5617
+			$alpha_channel->writeimage($tempfile_alpha);
5618
+
5619
+			// Cast to 8bit+palette
5620
+			$imgalpha_ = imagecreatefrompng($tempfile_alpha);
5621
+			imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5622
+			imagedestroy($imgalpha_);
5623
+			imagepng($imgalpha, $tempfile_alpha);
5624
+
5625
+			// Make opaque image
5626
+			$color_channels = new \Gmagick();
5627
+			$color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
5628
+			$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
5629
+			$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
5630
+			$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
5631
+			$color_channels->writeimage($tempfile_plain);
5632
+
5633
+			$imgplain = imagecreatefrompng($tempfile_plain);
5634
+		}
5635
+		// Use PECL imagick + ImageMagic to process transparent PNG images
5636
+		elseif (extension_loaded("imagick")) {
5637
+			// Native cloning was added to pecl-imagick in svn commit 263814
5638
+			// the first version containing it was 3.0.1RC1
5639
+			static $imagickClonable = null;
5640
+			if ($imagickClonable === null) {
5641
+				$imagickClonable = true;
5642
+				if (defined('Imagick::IMAGICK_EXTVER')) {
5643
+					$imagickVersion = \Imagick::IMAGICK_EXTVER;
5644
+				} else {
5645
+					$imagickVersion = '0';
5646
+				}
5647
+				if (version_compare($imagickVersion, '0.0.1', '>=')) {
5648
+					$imagickClonable = version_compare($imagickVersion, '3.0.1rc1', '>=');
5649
+				}
5650
+			}
5651
+
5652
+			$imagick = new \Imagick($file);
5653
+			$imagick->setFormat('png');
5654
+
5655
+			// Get opacity channel (negative of alpha channel)
5656
+			if ($imagick->getImageAlphaChannel() !== 0) {
5657
+				$alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
5658
+				$alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
5659
+				// Since ImageMagick7 negate invert transparency as default
5660
+				if (\Imagick::getVersion()['versionNumber'] < 1800) {
5661
+					$alpha_channel->negateImage(true);
5662
+				}
5663
+				$alpha_channel->writeImage($tempfile_alpha);
5664
+
5665
+				// Cast to 8bit+palette
5666
+				$imgalpha_ = imagecreatefrompng($tempfile_alpha);
5667
+				imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5668
+				imagedestroy($imgalpha_);
5669
+				imagepng($imgalpha, $tempfile_alpha);
5670
+			} else {
5671
+				$tempfile_alpha = null;
5672
+			}
5673
+
5674
+			// Make opaque image
5675
+			$color_channels = new \Imagick();
5676
+			$color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
5677
+			$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
5678
+			$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
5679
+			$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
5680
+			$color_channels->writeImage($tempfile_plain);
5681
+
5682
+			$imgplain = imagecreatefrompng($tempfile_plain);
5683
+		} else {
5684
+			// allocated colors cache
5685
+			$allocated_colors = [];
5686
+
5687
+			// extract alpha channel
5688
+			for ($xpx = 0; $xpx < $wpx; ++$xpx) {
5689
+				for ($ypx = 0; $ypx < $hpx; ++$ypx) {
5690
+					$color = imagecolorat($img, $xpx, $ypx);
5691
+					$col = imagecolorsforindex($img, $color);
5692
+					$alpha = $col['alpha'];
5693
+
5694
+					if ($eight_bit) {
5695
+						// with gamma correction
5696
+						$gammacorr = 2.2;
5697
+						$pixel = round(pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255);
5698
+					} else {
5699
+						// without gamma correction
5700
+						$pixel = (127 - $alpha) * 2;
5701
+
5702
+						$key = $col['red'] . $col['green'] . $col['blue'];
5703
+
5704
+						if (!isset($allocated_colors[$key])) {
5705
+							$pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
5706
+							$allocated_colors[$key] = $pixel_img;
5707
+						} else {
5708
+							$pixel_img = $allocated_colors[$key];
5709
+						}
5710
+
5711
+						imagesetpixel($img, $xpx, $ypx, $pixel_img);
5712
+					}
5713
+
5714
+					imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
5715
+				}
5716
+			}
5717
+
5718
+			// extract image without alpha channel
5719
+			$imgplain = imagecreatetruecolor($wpx, $hpx);
5720
+			imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
5721
+			imagedestroy($img);
5722
+
5723
+			imagepng($imgalpha, $tempfile_alpha);
5724
+			imagepng($imgplain, $tempfile_plain);
5725
+		}
5726
+
5727
+		$this->imageAlphaList[$file] = [$tempfile_alpha, $tempfile_plain];
5728
+
5729
+		// embed mask image
5730
+		if ($tempfile_alpha) {
5731
+			$this->addImagePng($imgalpha, $tempfile_alpha, $x, $y, $w, $h, true);
5732
+			imagedestroy($imgalpha);
5733
+			$this->imageCache[] = $tempfile_alpha;
5734
+		}
5735
+
5736
+		// embed image, masked with previously embedded mask
5737
+		$this->addImagePng($imgplain, $tempfile_plain, $x, $y, $w, $h, false, ($tempfile_alpha !== null));
5738
+		imagedestroy($imgplain);
5739
+		$this->imageCache[] = $tempfile_plain;
5740
+	}
5741
+
5742
+	/**
5743
+	 * add a PNG image into the document, from a file
5744
+	 * this should work with remote files
5745
+	 *
5746
+	 * @param $file
5747
+	 * @param $x
5748
+	 * @param $y
5749
+	 * @param int $w
5750
+	 * @param int $h
5751
+	 * @throws Exception
5752
+	 */
5753
+	function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
5754
+	{
5755
+		if (!function_exists("imagecreatefrompng")) {
5756
+			throw new \Exception("The PHP GD extension is required, but is not installed.");
5757
+		}
5758
+
5759
+		if (isset($this->imageAlphaList[$file])) {
5760
+			[$alphaFile, $plainFile] = $this->imageAlphaList[$file];
5761
+
5762
+			if ($alphaFile) {
5763
+				$img = null;
5764
+				$this->addImagePng($img, $alphaFile, $x, $y, $w, $h, true);
5765
+			}
5766
+
5767
+			$img = null;
5768
+			$this->addImagePng($img, $plainFile, $x, $y, $w, $h, false, ($plainFile !== null));
5769
+			return;
5770
+		}
5771
+
5772
+		//if already cached, need not to read again
5773
+		if (isset($this->imagelist[$file])) {
5774
+			$img = null;
5775
+		} else {
5776
+			$info = file_get_contents($file, false, null, 24, 5);
5777
+			$meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
5778
+			$bit_depth = $meta["bitDepth"];
5779
+			$color_type = $meta["colorType"];
5780
+
5781
+			// http://www.w3.org/TR/PNG/#11IHDR
5782
+			// 3 => indexed
5783
+			// 4 => greyscale with alpha
5784
+			// 6 => fullcolor with alpha
5785
+			$is_alpha = in_array($color_type, [4, 6]) || ($color_type == 3 && $bit_depth != 4);
5786
+
5787
+			if ($is_alpha) { // exclude grayscale alpha
5788
+				$this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
5789
+				return;
5790
+			}
5791
+
5792
+			//png files typically contain an alpha channel.
5793
+			//pdf file format or class.pdf does not support alpha blending.
5794
+			//on alpha blended images, more transparent areas have a color near black.
5795
+			//This appears in the result on not storing the alpha channel.
5796
+			//Correct would be the box background image or its parent when transparent.
5797
+			//But this would make the image dependent on the background.
5798
+			//Therefore create an image with white background and copy in
5799
+			//A more natural background than black is white.
5800
+			//Therefore create an empty image with white background and merge the
5801
+			//image in with alpha blending.
5802
+			$imgtmp = @imagecreatefrompng($file);
5803
+			if (!$imgtmp) {
5804
+				return;
5805
+			}
5806
+			$sx = imagesx($imgtmp);
5807
+			$sy = imagesy($imgtmp);
5808
+			$img = imagecreatetruecolor($sx, $sy);
5809
+			imagealphablending($img, true);
5810
+
5811
+			// @todo is it still needed ??
5812
+			$ti = imagecolortransparent($imgtmp);
5813
+			if ($ti >= 0) {
5814
+				$tc = imagecolorsforindex($imgtmp, $ti);
5815
+				$ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5816
+				imagefill($img, 0, 0, $ti);
5817
+				imagecolortransparent($img, $ti);
5818
+			} else {
5819
+				imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5820
+			}
5821
+
5822
+			imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5823
+			imagedestroy($imgtmp);
5824
+		}
5825
+		$this->addImagePng($img, $file, $x, $y, $w, $h);
5826
+
5827
+		if ($img) {
5828
+			imagedestroy($img);
5829
+		}
5830
+	}
5831
+
5832
+	/**
5833
+	 * add a PNG image into the document, from a memory buffer of the file
5834
+	 *
5835
+	 * @param $data
5836
+	 * @param $file
5837
+	 * @param $x
5838
+	 * @param $y
5839
+	 * @param float $w
5840
+	 * @param float $h
5841
+	 * @param bool $is_mask
5842
+	 * @param null $mask
5843
+	 */
5844
+	function addPngFromBuf(&$data, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5845
+	{
5846
+		if (isset($this->imagelist[$file])) {
5847
+			$data = null;
5848
+			$info['width'] = $this->imagelist[$file]['w'];
5849
+			$info['height'] = $this->imagelist[$file]['h'];
5850
+			$label = $this->imagelist[$file]['label'];
5851
+		} else {
5852
+			if ($data == null) {
5853
+				$this->addMessage('addPngFromBuf error - data not present!');
5854
+
5855
+				return;
5856
+			}
5857
+
5858
+			$error = 0;
5859
+
5860
+			if (!$error) {
5861
+				$header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5862
+
5863
+				if (mb_substr($data, 0, 8, '8bit') != $header) {
5864
+					$error = 1;
5865
+
5866
+					if (defined("DEBUGPNG") && DEBUGPNG) {
5867
+						print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5868
+					}
5869
+
5870
+					$errormsg = 'this file does not have a valid header';
5871
+				}
5872
+			}
5873
+
5874
+			if (!$error) {
5875
+				// set pointer
5876
+				$p = 8;
5877
+				$len = mb_strlen($data, '8bit');
5878
+
5879
+				// cycle through the file, identifying chunks
5880
+				$haveHeader = 0;
5881
+				$info = [];
5882
+				$idata = '';
5883
+				$pdata = '';
5884
+
5885
+				while ($p < $len) {
5886
+					$chunkLen = $this->getBytes($data, $p, 4);
5887
+					$chunkType = mb_substr($data, $p + 4, 4, '8bit');
5888
+
5889
+					switch ($chunkType) {
5890
+						case 'IHDR':
5891
+							// this is where all the file information comes from
5892
+							$info['width'] = $this->getBytes($data, $p + 8, 4);
5893
+							$info['height'] = $this->getBytes($data, $p + 12, 4);
5894
+							$info['bitDepth'] = ord($data[$p + 16]);
5895
+							$info['colorType'] = ord($data[$p + 17]);
5896
+							$info['compressionMethod'] = ord($data[$p + 18]);
5897
+							$info['filterMethod'] = ord($data[$p + 19]);
5898
+							$info['interlaceMethod'] = ord($data[$p + 20]);
5899
+
5900
+							//print_r($info);
5901
+							$haveHeader = 1;
5902
+							if ($info['compressionMethod'] != 0) {
5903
+								$error = 1;
5904
+
5905
+								//debugpng
5906
+								if (defined("DEBUGPNG") && DEBUGPNG) {
5907
+									print '[addPngFromFile unsupported compression method ' . $file . ']';
5908
+								}
5909
+
5910
+								$errormsg = 'unsupported compression method';
5911
+							}
5912
+
5913
+							if ($info['filterMethod'] != 0) {
5914
+								$error = 1;
5915
+
5916
+								//debugpng
5917
+								if (defined("DEBUGPNG") && DEBUGPNG) {
5918
+									print '[addPngFromFile unsupported filter method ' . $file . ']';
5919
+								}
5920
+
5921
+								$errormsg = 'unsupported filter method';
5922
+							}
5923
+							break;
5924
+
5925
+						case 'PLTE':
5926
+							$pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5927
+							break;
5928
+
5929
+						case 'IDAT':
5930
+							$idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
5931
+							break;
5932
+
5933
+						case 'tRNS':
5934
+							//this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
5935
+							//print "tRNS found, color type = ".$info['colorType']."\n";
5936
+							$transparency = [];
5937
+
5938
+							switch ($info['colorType']) {
5939
+								// indexed color, rbg
5940
+								case 3:
5941
+									/* corresponding to entries in the plte chunk
5942 5942
                                      Alpha for palette index 0: 1 byte
5943 5943
                                      Alpha for palette index 1: 1 byte
5944 5944
                                      ...etc...
5945 5945
                                     */
5946
-                                    // there will be one entry for each palette entry. up until the last non-opaque entry.
5947
-                                    // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
5948
-                                    $transparency['type'] = 'indexed';
5949
-                                    $trans = 0;
5950
-
5951
-                                    for ($i = $chunkLen; $i >= 0; $i--) {
5952
-                                        if (ord($data[$p + 8 + $i]) == 0) {
5953
-                                            $trans = $i;
5954
-                                        }
5955
-                                    }
5956
-
5957
-                                    $transparency['data'] = $trans;
5958
-                                    break;
5959
-
5960
-                                // grayscale
5961
-                                case 0:
5962
-                                    /* corresponding to entries in the plte chunk
5946
+									// there will be one entry for each palette entry. up until the last non-opaque entry.
5947
+									// set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
5948
+									$transparency['type'] = 'indexed';
5949
+									$trans = 0;
5950
+
5951
+									for ($i = $chunkLen; $i >= 0; $i--) {
5952
+										if (ord($data[$p + 8 + $i]) == 0) {
5953
+											$trans = $i;
5954
+										}
5955
+									}
5956
+
5957
+									$transparency['data'] = $trans;
5958
+									break;
5959
+
5960
+								// grayscale
5961
+								case 0:
5962
+									/* corresponding to entries in the plte chunk
5963 5963
                                      Gray: 2 bytes, range 0 .. (2^bitdepth)-1
5964 5964
                                     */
5965
-                                    //            $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
5966
-                                    $transparency['type'] = 'indexed';
5967
-                                    $transparency['data'] = ord($data[$p + 8 + 1]);
5968
-                                    break;
5969
-
5970
-                                // truecolor
5971
-                                case 2:
5972
-                                    /* corresponding to entries in the plte chunk
5965
+									//            $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
5966
+									$transparency['type'] = 'indexed';
5967
+									$transparency['data'] = ord($data[$p + 8 + 1]);
5968
+									break;
5969
+
5970
+								// truecolor
5971
+								case 2:
5972
+									/* corresponding to entries in the plte chunk
5973 5973
                                      Red: 2 bytes, range 0 .. (2^bitdepth)-1
5974 5974
                                      Green: 2 bytes, range 0 .. (2^bitdepth)-1
5975 5975
                                      Blue: 2 bytes, range 0 .. (2^bitdepth)-1
5976 5976
                                     */
5977
-                                    $transparency['r'] = $this->getBytes($data, $p + 8, 2);
5978
-                                    // r from truecolor
5979
-                                    $transparency['g'] = $this->getBytes($data, $p + 10, 2);
5980
-                                    // g from truecolor
5981
-                                    $transparency['b'] = $this->getBytes($data, $p + 12, 2);
5982
-                                    // b from truecolor
5983
-
5984
-                                    $transparency['type'] = 'color-key';
5985
-                                    break;
5986
-
5987
-                                //unsupported transparency type
5988
-                                default:
5989
-                                    if (defined("DEBUGPNG") && DEBUGPNG) {
5990
-                                        print '[addPngFromFile unsupported transparency type ' . $file . ']';
5991
-                                    }
5992
-                                    break;
5993
-                            }
5994
-
5995
-                            // KS End new code
5996
-                            break;
5997
-
5998
-                        default:
5999
-                            break;
6000
-                    }
6001
-
6002
-                    $p += $chunkLen + 12;
6003
-                }
6004
-
6005
-                if (!$haveHeader) {
6006
-                    $error = 1;
6007
-
6008
-                    //debugpng
6009
-                    if (defined("DEBUGPNG") && DEBUGPNG) {
6010
-                        print '[addPngFromFile information header is missing ' . $file . ']';
6011
-                    }
6012
-
6013
-                    $errormsg = 'information header is missing';
6014
-                }
6015
-
6016
-                if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
6017
-                    $error = 1;
6018
-
6019
-                    //debugpng
6020
-                    if (defined("DEBUGPNG") && DEBUGPNG) {
6021
-                        print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
6022
-                    }
6023
-
6024
-                    $errormsg = 'There appears to be no support for interlaced images in pdf.';
6025
-                }
6026
-            }
6027
-
6028
-            if (!$error && $info['bitDepth'] > 8) {
6029
-                $error = 1;
6030
-
6031
-                //debugpng
6032
-                if (defined("DEBUGPNG") && DEBUGPNG) {
6033
-                    print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
6034
-                }
6035
-
6036
-                $errormsg = 'only bit depth of 8 or less is supported';
6037
-            }
6038
-
6039
-            if (!$error) {
6040
-                switch ($info['colorType']) {
6041
-                    case 3:
6042
-                        $color = 'DeviceRGB';
6043
-                        $ncolor = 1;
6044
-                        break;
6045
-
6046
-                    case 2:
6047
-                        $color = 'DeviceRGB';
6048
-                        $ncolor = 3;
6049
-                        break;
6050
-
6051
-                    case 0:
6052
-                        $color = 'DeviceGray';
6053
-                        $ncolor = 1;
6054
-                        break;
6055
-
6056
-                    default:
6057
-                        $error = 1;
6058
-
6059
-                        //debugpng
6060
-                        if (defined("DEBUGPNG") && DEBUGPNG) {
6061
-                            print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
6062
-                        }
6063
-
6064
-                        $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
6065
-                }
6066
-            }
6067
-
6068
-            if ($error) {
6069
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
6070
-
6071
-                return;
6072
-            }
6073
-
6074
-            //print_r($info);
6075
-            // so this image is ok... add it in.
6076
-            $this->numImages++;
6077
-            $im = $this->numImages;
6078
-            $label = "I$im";
6079
-            $this->numObj++;
6080
-
6081
-            //  $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
6082
-            $options = [
6083
-                'label'            => $label,
6084
-                'data'             => $idata,
6085
-                'bitsPerComponent' => $info['bitDepth'],
6086
-                'pdata'            => $pdata,
6087
-                'iw'               => $info['width'],
6088
-                'ih'               => $info['height'],
6089
-                'type'             => 'png',
6090
-                'color'            => $color,
6091
-                'ncolor'           => $ncolor,
6092
-                'masked'           => $mask,
6093
-                'isMask'           => $is_mask
6094
-            ];
6095
-
6096
-            if (isset($transparency)) {
6097
-                $options['transparency'] = $transparency;
6098
-            }
6099
-
6100
-            $this->o_image($this->numObj, 'new', $options);
6101
-            $this->imagelist[$file] = ['label' => $label, 'w' => $info['width'], 'h' => $info['height']];
6102
-        }
6103
-
6104
-        if ($is_mask) {
6105
-            return;
6106
-        }
6107
-
6108
-        if ($w <= 0 && $h <= 0) {
6109
-            $w = $info['width'];
6110
-            $h = $info['height'];
6111
-        }
6112
-
6113
-        if ($w <= 0) {
6114
-            $w = $h / $info['height'] * $info['width'];
6115
-        }
6116
-
6117
-        if ($h <= 0) {
6118
-            $h = $w * $info['height'] / $info['width'];
6119
-        }
6120
-
6121
-        $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
6122
-    }
6123
-
6124
-    /**
6125
-     * add a JPEG image into the document, from a file
6126
-     *
6127
-     * @param $img
6128
-     * @param $x
6129
-     * @param $y
6130
-     * @param int $w
6131
-     * @param int $h
6132
-     */
6133
-    function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
6134
-    {
6135
-        // attempt to add a jpeg image straight from a file, using no GD commands
6136
-        // note that this function is unable to operate on a remote file.
6137
-
6138
-        if (!file_exists($img)) {
6139
-            return;
6140
-        }
6141
-
6142
-        if ($this->image_iscached($img)) {
6143
-            $data = null;
6144
-            $imageWidth = $this->imagelist[$img]['w'];
6145
-            $imageHeight = $this->imagelist[$img]['h'];
6146
-            $channels = $this->imagelist[$img]['c'];
6147
-        } else {
6148
-            $tmp = getimagesize($img);
6149
-            $imageWidth = $tmp[0];
6150
-            $imageHeight = $tmp[1];
6151
-
6152
-            if (isset($tmp['channels'])) {
6153
-                $channels = $tmp['channels'];
6154
-            } else {
6155
-                $channels = 3;
6156
-            }
6157
-
6158
-            $data = file_get_contents($img);
6159
-        }
6160
-
6161
-        if ($w <= 0 && $h <= 0) {
6162
-            $w = $imageWidth;
6163
-        }
6164
-
6165
-        if ($w == 0) {
6166
-            $w = $h / $imageHeight * $imageWidth;
6167
-        }
6168
-
6169
-        if ($h == 0) {
6170
-            $h = $w * $imageHeight / $imageWidth;
6171
-        }
6172
-
6173
-        $this->addJpegImage_common($data, $img, $imageWidth, $imageHeight, $x, $y, $w, $h, $channels);
6174
-    }
6175
-
6176
-    /**
6177
-     * common code used by the two JPEG adding functions
6178
-     * @param $data
6179
-     * @param $imgname
6180
-     * @param $imageWidth
6181
-     * @param $imageHeight
6182
-     * @param $x
6183
-     * @param $y
6184
-     * @param int $w
6185
-     * @param int $h
6186
-     * @param int $channels
6187
-     */
6188
-    private function addJpegImage_common(
6189
-        &$data,
6190
-        $imgname,
6191
-        $imageWidth,
6192
-        $imageHeight,
6193
-        $x,
6194
-        $y,
6195
-        $w = 0,
6196
-        $h = 0,
6197
-        $channels = 3
6198
-    ) {
6199
-        if ($this->image_iscached($imgname)) {
6200
-            $label = $this->imagelist[$imgname]['label'];
6201
-            //debugpng
6202
-            //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
6203
-
6204
-        } else {
6205
-            if ($data == null) {
6206
-                $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
6207
-
6208
-                return;
6209
-            }
6210
-
6211
-            // note that this function is not to be called externally
6212
-            // it is just the common code between the GD and the file options
6213
-            $this->numImages++;
6214
-            $im = $this->numImages;
6215
-            $label = "I$im";
6216
-            $this->numObj++;
6217
-
6218
-            $this->o_image(
6219
-                $this->numObj,
6220
-                'new',
6221
-                [
6222
-                    'label'    => $label,
6223
-                    'data'     => &$data,
6224
-                    'iw'       => $imageWidth,
6225
-                    'ih'       => $imageHeight,
6226
-                    'channels' => $channels
6227
-                ]
6228
-            );
6229
-
6230
-            $this->imagelist[$imgname] = [
6231
-                'label' => $label,
6232
-                'w'     => $imageWidth,
6233
-                'h'     => $imageHeight,
6234
-                'c'     => $channels
6235
-            ];
6236
-        }
6237
-
6238
-        $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
6239
-    }
6240
-
6241
-    /**
6242
-     * specify where the document should open when it first starts
6243
-     *
6244
-     * @param $style
6245
-     * @param int $a
6246
-     * @param int $b
6247
-     * @param int $c
6248
-     */
6249
-    function openHere($style, $a = 0, $b = 0, $c = 0)
6250
-    {
6251
-        // this function will open the document at a specified page, in a specified style
6252
-        // the values for style, and the required parameters are:
6253
-        // 'XYZ'  left, top, zoom
6254
-        // 'Fit'
6255
-        // 'FitH' top
6256
-        // 'FitV' left
6257
-        // 'FitR' left,bottom,right
6258
-        // 'FitB'
6259
-        // 'FitBH' top
6260
-        // 'FitBV' left
6261
-        $this->numObj++;
6262
-        $this->o_destination(
6263
-            $this->numObj,
6264
-            'new',
6265
-            ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6266
-        );
6267
-        $id = $this->catalogId;
6268
-        $this->o_catalog($id, 'openHere', $this->numObj);
6269
-    }
6270
-
6271
-    /**
6272
-     * Add JavaScript code to the PDF document
6273
-     *
6274
-     * @param string $code
6275
-     */
6276
-    function addJavascript($code)
6277
-    {
6278
-        $this->javascript .= $code;
6279
-    }
6280
-
6281
-    /**
6282
-     * create a labelled destination within the document
6283
-     *
6284
-     * @param $label
6285
-     * @param $style
6286
-     * @param int $a
6287
-     * @param int $b
6288
-     * @param int $c
6289
-     */
6290
-    function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
6291
-    {
6292
-        // associates the given label with the destination, it is done this way so that a destination can be specified after
6293
-        // it has been linked to
6294
-        // styles are the same as the 'openHere' function
6295
-        $this->numObj++;
6296
-        $this->o_destination(
6297
-            $this->numObj,
6298
-            'new',
6299
-            ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6300
-        );
6301
-        $id = $this->numObj;
6302
-
6303
-        // store the label->idf relationship, note that this means that labels can be used only once
6304
-        $this->destinations["$label"] = $id;
6305
-    }
6306
-
6307
-    /**
6308
-     * define font families, this is used to initialize the font families for the default fonts
6309
-     * and for the user to add new ones for their fonts. The default bahavious can be overridden should
6310
-     * that be desired.
6311
-     *
6312
-     * @param $family
6313
-     * @param string $options
6314
-     */
6315
-    function setFontFamily($family, $options = '')
6316
-    {
6317
-        if (!is_array($options)) {
6318
-            if ($family === 'init') {
6319
-                // set the known family groups
6320
-                // these font families will be used to enable bold and italic markers to be included
6321
-                // within text streams. html forms will be used... <b></b> <i></i>
6322
-                $this->fontFamilies['Helvetica.afm'] =
6323
-                    [
6324
-                        'b'  => 'Helvetica-Bold.afm',
6325
-                        'i'  => 'Helvetica-Oblique.afm',
6326
-                        'bi' => 'Helvetica-BoldOblique.afm',
6327
-                        'ib' => 'Helvetica-BoldOblique.afm'
6328
-                    ];
6329
-
6330
-                $this->fontFamilies['Courier.afm'] =
6331
-                    [
6332
-                        'b'  => 'Courier-Bold.afm',
6333
-                        'i'  => 'Courier-Oblique.afm',
6334
-                        'bi' => 'Courier-BoldOblique.afm',
6335
-                        'ib' => 'Courier-BoldOblique.afm'
6336
-                    ];
6337
-
6338
-                $this->fontFamilies['Times-Roman.afm'] =
6339
-                    [
6340
-                        'b'  => 'Times-Bold.afm',
6341
-                        'i'  => 'Times-Italic.afm',
6342
-                        'bi' => 'Times-BoldItalic.afm',
6343
-                        'ib' => 'Times-BoldItalic.afm'
6344
-                    ];
6345
-            }
6346
-        } else {
6347
-
6348
-            // the user is trying to set a font family
6349
-            // note that this can also be used to set the base ones to something else
6350
-            if (mb_strlen($family)) {
6351
-                $this->fontFamilies[$family] = $options;
6352
-            }
6353
-        }
6354
-    }
6355
-
6356
-    /**
6357
-     * used to add messages for use in debugging
6358
-     *
6359
-     * @param $message
6360
-     */
6361
-    function addMessage($message)
6362
-    {
6363
-        $this->messages .= $message . "\n";
6364
-    }
6365
-
6366
-    /**
6367
-     * a few functions which should allow the document to be treated transactionally.
6368
-     *
6369
-     * @param $action
6370
-     */
6371
-    function transaction($action)
6372
-    {
6373
-        switch ($action) {
6374
-            case 'start':
6375
-                // store all the data away into the checkpoint variable
6376
-                $data = get_object_vars($this);
6377
-                $this->checkpoint = $data;
6378
-                unset($data);
6379
-                break;
6380
-
6381
-            case 'commit':
6382
-                if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
6383
-                    $tmp = $this->checkpoint['checkpoint'];
6384
-                    $this->checkpoint = $tmp;
6385
-                    unset($tmp);
6386
-                } else {
6387
-                    $this->checkpoint = '';
6388
-                }
6389
-                break;
6390
-
6391
-            case 'rewind':
6392
-                // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
6393
-                if (is_array($this->checkpoint)) {
6394
-                    // can only abort if were inside a checkpoint
6395
-                    $tmp = $this->checkpoint;
6396
-
6397
-                    foreach ($tmp as $k => $v) {
6398
-                        if ($k !== 'checkpoint') {
6399
-                            $this->$k = $v;
6400
-                        }
6401
-                    }
6402
-                    unset($tmp);
6403
-                }
6404
-                break;
6405
-
6406
-            case 'abort':
6407
-                if (is_array($this->checkpoint)) {
6408
-                    // can only abort if were inside a checkpoint
6409
-                    $tmp = $this->checkpoint;
6410
-                    foreach ($tmp as $k => $v) {
6411
-                        $this->$k = $v;
6412
-                    }
6413
-                    unset($tmp);
6414
-                }
6415
-                break;
6416
-        }
6417
-    }
5977
+									$transparency['r'] = $this->getBytes($data, $p + 8, 2);
5978
+									// r from truecolor
5979
+									$transparency['g'] = $this->getBytes($data, $p + 10, 2);
5980
+									// g from truecolor
5981
+									$transparency['b'] = $this->getBytes($data, $p + 12, 2);
5982
+									// b from truecolor
5983
+
5984
+									$transparency['type'] = 'color-key';
5985
+									break;
5986
+
5987
+								//unsupported transparency type
5988
+								default:
5989
+									if (defined("DEBUGPNG") && DEBUGPNG) {
5990
+										print '[addPngFromFile unsupported transparency type ' . $file . ']';
5991
+									}
5992
+									break;
5993
+							}
5994
+
5995
+							// KS End new code
5996
+							break;
5997
+
5998
+						default:
5999
+							break;
6000
+					}
6001
+
6002
+					$p += $chunkLen + 12;
6003
+				}
6004
+
6005
+				if (!$haveHeader) {
6006
+					$error = 1;
6007
+
6008
+					//debugpng
6009
+					if (defined("DEBUGPNG") && DEBUGPNG) {
6010
+						print '[addPngFromFile information header is missing ' . $file . ']';
6011
+					}
6012
+
6013
+					$errormsg = 'information header is missing';
6014
+				}
6015
+
6016
+				if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
6017
+					$error = 1;
6018
+
6019
+					//debugpng
6020
+					if (defined("DEBUGPNG") && DEBUGPNG) {
6021
+						print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
6022
+					}
6023
+
6024
+					$errormsg = 'There appears to be no support for interlaced images in pdf.';
6025
+				}
6026
+			}
6027
+
6028
+			if (!$error && $info['bitDepth'] > 8) {
6029
+				$error = 1;
6030
+
6031
+				//debugpng
6032
+				if (defined("DEBUGPNG") && DEBUGPNG) {
6033
+					print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
6034
+				}
6035
+
6036
+				$errormsg = 'only bit depth of 8 or less is supported';
6037
+			}
6038
+
6039
+			if (!$error) {
6040
+				switch ($info['colorType']) {
6041
+					case 3:
6042
+						$color = 'DeviceRGB';
6043
+						$ncolor = 1;
6044
+						break;
6045
+
6046
+					case 2:
6047
+						$color = 'DeviceRGB';
6048
+						$ncolor = 3;
6049
+						break;
6050
+
6051
+					case 0:
6052
+						$color = 'DeviceGray';
6053
+						$ncolor = 1;
6054
+						break;
6055
+
6056
+					default:
6057
+						$error = 1;
6058
+
6059
+						//debugpng
6060
+						if (defined("DEBUGPNG") && DEBUGPNG) {
6061
+							print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
6062
+						}
6063
+
6064
+						$errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
6065
+				}
6066
+			}
6067
+
6068
+			if ($error) {
6069
+				$this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
6070
+
6071
+				return;
6072
+			}
6073
+
6074
+			//print_r($info);
6075
+			// so this image is ok... add it in.
6076
+			$this->numImages++;
6077
+			$im = $this->numImages;
6078
+			$label = "I$im";
6079
+			$this->numObj++;
6080
+
6081
+			//  $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
6082
+			$options = [
6083
+				'label'            => $label,
6084
+				'data'             => $idata,
6085
+				'bitsPerComponent' => $info['bitDepth'],
6086
+				'pdata'            => $pdata,
6087
+				'iw'               => $info['width'],
6088
+				'ih'               => $info['height'],
6089
+				'type'             => 'png',
6090
+				'color'            => $color,
6091
+				'ncolor'           => $ncolor,
6092
+				'masked'           => $mask,
6093
+				'isMask'           => $is_mask
6094
+			];
6095
+
6096
+			if (isset($transparency)) {
6097
+				$options['transparency'] = $transparency;
6098
+			}
6099
+
6100
+			$this->o_image($this->numObj, 'new', $options);
6101
+			$this->imagelist[$file] = ['label' => $label, 'w' => $info['width'], 'h' => $info['height']];
6102
+		}
6103
+
6104
+		if ($is_mask) {
6105
+			return;
6106
+		}
6107
+
6108
+		if ($w <= 0 && $h <= 0) {
6109
+			$w = $info['width'];
6110
+			$h = $info['height'];
6111
+		}
6112
+
6113
+		if ($w <= 0) {
6114
+			$w = $h / $info['height'] * $info['width'];
6115
+		}
6116
+
6117
+		if ($h <= 0) {
6118
+			$h = $w * $info['height'] / $info['width'];
6119
+		}
6120
+
6121
+		$this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
6122
+	}
6123
+
6124
+	/**
6125
+	 * add a JPEG image into the document, from a file
6126
+	 *
6127
+	 * @param $img
6128
+	 * @param $x
6129
+	 * @param $y
6130
+	 * @param int $w
6131
+	 * @param int $h
6132
+	 */
6133
+	function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
6134
+	{
6135
+		// attempt to add a jpeg image straight from a file, using no GD commands
6136
+		// note that this function is unable to operate on a remote file.
6137
+
6138
+		if (!file_exists($img)) {
6139
+			return;
6140
+		}
6141
+
6142
+		if ($this->image_iscached($img)) {
6143
+			$data = null;
6144
+			$imageWidth = $this->imagelist[$img]['w'];
6145
+			$imageHeight = $this->imagelist[$img]['h'];
6146
+			$channels = $this->imagelist[$img]['c'];
6147
+		} else {
6148
+			$tmp = getimagesize($img);
6149
+			$imageWidth = $tmp[0];
6150
+			$imageHeight = $tmp[1];
6151
+
6152
+			if (isset($tmp['channels'])) {
6153
+				$channels = $tmp['channels'];
6154
+			} else {
6155
+				$channels = 3;
6156
+			}
6157
+
6158
+			$data = file_get_contents($img);
6159
+		}
6160
+
6161
+		if ($w <= 0 && $h <= 0) {
6162
+			$w = $imageWidth;
6163
+		}
6164
+
6165
+		if ($w == 0) {
6166
+			$w = $h / $imageHeight * $imageWidth;
6167
+		}
6168
+
6169
+		if ($h == 0) {
6170
+			$h = $w * $imageHeight / $imageWidth;
6171
+		}
6172
+
6173
+		$this->addJpegImage_common($data, $img, $imageWidth, $imageHeight, $x, $y, $w, $h, $channels);
6174
+	}
6175
+
6176
+	/**
6177
+	 * common code used by the two JPEG adding functions
6178
+	 * @param $data
6179
+	 * @param $imgname
6180
+	 * @param $imageWidth
6181
+	 * @param $imageHeight
6182
+	 * @param $x
6183
+	 * @param $y
6184
+	 * @param int $w
6185
+	 * @param int $h
6186
+	 * @param int $channels
6187
+	 */
6188
+	private function addJpegImage_common(
6189
+		&$data,
6190
+		$imgname,
6191
+		$imageWidth,
6192
+		$imageHeight,
6193
+		$x,
6194
+		$y,
6195
+		$w = 0,
6196
+		$h = 0,
6197
+		$channels = 3
6198
+	) {
6199
+		if ($this->image_iscached($imgname)) {
6200
+			$label = $this->imagelist[$imgname]['label'];
6201
+			//debugpng
6202
+			//if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
6203
+
6204
+		} else {
6205
+			if ($data == null) {
6206
+				$this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
6207
+
6208
+				return;
6209
+			}
6210
+
6211
+			// note that this function is not to be called externally
6212
+			// it is just the common code between the GD and the file options
6213
+			$this->numImages++;
6214
+			$im = $this->numImages;
6215
+			$label = "I$im";
6216
+			$this->numObj++;
6217
+
6218
+			$this->o_image(
6219
+				$this->numObj,
6220
+				'new',
6221
+				[
6222
+					'label'    => $label,
6223
+					'data'     => &$data,
6224
+					'iw'       => $imageWidth,
6225
+					'ih'       => $imageHeight,
6226
+					'channels' => $channels
6227
+				]
6228
+			);
6229
+
6230
+			$this->imagelist[$imgname] = [
6231
+				'label' => $label,
6232
+				'w'     => $imageWidth,
6233
+				'h'     => $imageHeight,
6234
+				'c'     => $channels
6235
+			];
6236
+		}
6237
+
6238
+		$this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
6239
+	}
6240
+
6241
+	/**
6242
+	 * specify where the document should open when it first starts
6243
+	 *
6244
+	 * @param $style
6245
+	 * @param int $a
6246
+	 * @param int $b
6247
+	 * @param int $c
6248
+	 */
6249
+	function openHere($style, $a = 0, $b = 0, $c = 0)
6250
+	{
6251
+		// this function will open the document at a specified page, in a specified style
6252
+		// the values for style, and the required parameters are:
6253
+		// 'XYZ'  left, top, zoom
6254
+		// 'Fit'
6255
+		// 'FitH' top
6256
+		// 'FitV' left
6257
+		// 'FitR' left,bottom,right
6258
+		// 'FitB'
6259
+		// 'FitBH' top
6260
+		// 'FitBV' left
6261
+		$this->numObj++;
6262
+		$this->o_destination(
6263
+			$this->numObj,
6264
+			'new',
6265
+			['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6266
+		);
6267
+		$id = $this->catalogId;
6268
+		$this->o_catalog($id, 'openHere', $this->numObj);
6269
+	}
6270
+
6271
+	/**
6272
+	 * Add JavaScript code to the PDF document
6273
+	 *
6274
+	 * @param string $code
6275
+	 */
6276
+	function addJavascript($code)
6277
+	{
6278
+		$this->javascript .= $code;
6279
+	}
6280
+
6281
+	/**
6282
+	 * create a labelled destination within the document
6283
+	 *
6284
+	 * @param $label
6285
+	 * @param $style
6286
+	 * @param int $a
6287
+	 * @param int $b
6288
+	 * @param int $c
6289
+	 */
6290
+	function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
6291
+	{
6292
+		// associates the given label with the destination, it is done this way so that a destination can be specified after
6293
+		// it has been linked to
6294
+		// styles are the same as the 'openHere' function
6295
+		$this->numObj++;
6296
+		$this->o_destination(
6297
+			$this->numObj,
6298
+			'new',
6299
+			['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6300
+		);
6301
+		$id = $this->numObj;
6302
+
6303
+		// store the label->idf relationship, note that this means that labels can be used only once
6304
+		$this->destinations["$label"] = $id;
6305
+	}
6306
+
6307
+	/**
6308
+	 * define font families, this is used to initialize the font families for the default fonts
6309
+	 * and for the user to add new ones for their fonts. The default bahavious can be overridden should
6310
+	 * that be desired.
6311
+	 *
6312
+	 * @param $family
6313
+	 * @param string $options
6314
+	 */
6315
+	function setFontFamily($family, $options = '')
6316
+	{
6317
+		if (!is_array($options)) {
6318
+			if ($family === 'init') {
6319
+				// set the known family groups
6320
+				// these font families will be used to enable bold and italic markers to be included
6321
+				// within text streams. html forms will be used... <b></b> <i></i>
6322
+				$this->fontFamilies['Helvetica.afm'] =
6323
+					[
6324
+						'b'  => 'Helvetica-Bold.afm',
6325
+						'i'  => 'Helvetica-Oblique.afm',
6326
+						'bi' => 'Helvetica-BoldOblique.afm',
6327
+						'ib' => 'Helvetica-BoldOblique.afm'
6328
+					];
6329
+
6330
+				$this->fontFamilies['Courier.afm'] =
6331
+					[
6332
+						'b'  => 'Courier-Bold.afm',
6333
+						'i'  => 'Courier-Oblique.afm',
6334
+						'bi' => 'Courier-BoldOblique.afm',
6335
+						'ib' => 'Courier-BoldOblique.afm'
6336
+					];
6337
+
6338
+				$this->fontFamilies['Times-Roman.afm'] =
6339
+					[
6340
+						'b'  => 'Times-Bold.afm',
6341
+						'i'  => 'Times-Italic.afm',
6342
+						'bi' => 'Times-BoldItalic.afm',
6343
+						'ib' => 'Times-BoldItalic.afm'
6344
+					];
6345
+			}
6346
+		} else {
6347
+
6348
+			// the user is trying to set a font family
6349
+			// note that this can also be used to set the base ones to something else
6350
+			if (mb_strlen($family)) {
6351
+				$this->fontFamilies[$family] = $options;
6352
+			}
6353
+		}
6354
+	}
6355
+
6356
+	/**
6357
+	 * used to add messages for use in debugging
6358
+	 *
6359
+	 * @param $message
6360
+	 */
6361
+	function addMessage($message)
6362
+	{
6363
+		$this->messages .= $message . "\n";
6364
+	}
6365
+
6366
+	/**
6367
+	 * a few functions which should allow the document to be treated transactionally.
6368
+	 *
6369
+	 * @param $action
6370
+	 */
6371
+	function transaction($action)
6372
+	{
6373
+		switch ($action) {
6374
+			case 'start':
6375
+				// store all the data away into the checkpoint variable
6376
+				$data = get_object_vars($this);
6377
+				$this->checkpoint = $data;
6378
+				unset($data);
6379
+				break;
6380
+
6381
+			case 'commit':
6382
+				if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
6383
+					$tmp = $this->checkpoint['checkpoint'];
6384
+					$this->checkpoint = $tmp;
6385
+					unset($tmp);
6386
+				} else {
6387
+					$this->checkpoint = '';
6388
+				}
6389
+				break;
6390
+
6391
+			case 'rewind':
6392
+				// do not destroy the current checkpoint, but move us back to the state then, so that we can try again
6393
+				if (is_array($this->checkpoint)) {
6394
+					// can only abort if were inside a checkpoint
6395
+					$tmp = $this->checkpoint;
6396
+
6397
+					foreach ($tmp as $k => $v) {
6398
+						if ($k !== 'checkpoint') {
6399
+							$this->$k = $v;
6400
+						}
6401
+					}
6402
+					unset($tmp);
6403
+				}
6404
+				break;
6405
+
6406
+			case 'abort':
6407
+				if (is_array($this->checkpoint)) {
6408
+					// can only abort if were inside a checkpoint
6409
+					$tmp = $this->checkpoint;
6410
+					foreach ($tmp as $k => $v) {
6411
+						$this->$k = $v;
6412
+					}
6413
+					unset($tmp);
6414
+				}
6415
+				break;
6416
+		}
6417
+	}
6418 6418
 }
Please login to merge, or discard this patch.
Spacing   +242 added lines, -242 removed lines patch added patch discarded remove patch
@@ -23,24 +23,24 @@  discard block
 block discarded – undo
23 23
     const PDF_VERSION = '1.7';
24 24
 
25 25
     const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
26
-    const ACROFORM_SIG_APPENDONLY =       0x0002;
26
+    const ACROFORM_SIG_APPENDONLY = 0x0002;
27 27
 
28
-    const ACROFORM_FIELD_BUTTON =   'Btn';
29
-    const ACROFORM_FIELD_TEXT =     'Tx';
30
-    const ACROFORM_FIELD_CHOICE =   'Ch';
31
-    const ACROFORM_FIELD_SIG =      'Sig';
28
+    const ACROFORM_FIELD_BUTTON = 'Btn';
29
+    const ACROFORM_FIELD_TEXT = 'Tx';
30
+    const ACROFORM_FIELD_CHOICE = 'Ch';
31
+    const ACROFORM_FIELD_SIG = 'Sig';
32 32
 
33
-    const ACROFORM_FIELD_READONLY =               0x0001;
34
-    const ACROFORM_FIELD_REQUIRED =               0x0002;
33
+    const ACROFORM_FIELD_READONLY = 0x0001;
34
+    const ACROFORM_FIELD_REQUIRED = 0x0002;
35 35
 
36
-    const ACROFORM_FIELD_TEXT_MULTILINE =         0x1000;
37
-    const ACROFORM_FIELD_TEXT_PASSWORD =          0x2000;
38
-    const ACROFORM_FIELD_TEXT_RICHTEXT =         0x10000;
36
+    const ACROFORM_FIELD_TEXT_MULTILINE = 0x1000;
37
+    const ACROFORM_FIELD_TEXT_PASSWORD = 0x2000;
38
+    const ACROFORM_FIELD_TEXT_RICHTEXT = 0x10000;
39 39
 
40
-    const ACROFORM_FIELD_CHOICE_COMBO =          0x20000;
41
-    const ACROFORM_FIELD_CHOICE_EDIT =           0x40000;
42
-    const ACROFORM_FIELD_CHOICE_SORT =           0x80000;
43
-    const ACROFORM_FIELD_CHOICE_MULTISELECT =   0x200000;
40
+    const ACROFORM_FIELD_CHOICE_COMBO = 0x20000;
41
+    const ACROFORM_FIELD_CHOICE_EDIT = 0x40000;
42
+    const ACROFORM_FIELD_CHOICE_SORT = 0x80000;
43
+    const ACROFORM_FIELD_CHOICE_MULTISELECT = 0x200000;
44 44
 
45 45
     const XOBJECT_SUBTYPE_FORM = 'Form';
46 46
 
@@ -459,16 +459,16 @@  discard block
 block discarded – undo
459 459
                     case 'XYZ':
460 460
                     /** @noinspection PhpMissingBreakStatementInspection */
461 461
                     case 'FitR':
462
-                        $tmp = ' ' . $options['p3'] . $tmp;
462
+                        $tmp = ' '.$options['p3'].$tmp;
463 463
                     case 'FitH':
464 464
                     case 'FitV':
465 465
                     case 'FitBH':
466 466
                     /** @noinspection PhpMissingBreakStatementInspection */
467 467
                     case 'FitBV':
468
-                        $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
468
+                        $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp;
469 469
                     case 'Fit':
470 470
                     case 'FitB':
471
-                        $tmp = $options['type'] . $tmp;
471
+                        $tmp = $options['type'].$tmp;
472 472
                         $this->objects[$id]['info']['string'] = $tmp;
473 473
                         $this->objects[$id]['info']['page'] = $options['page'];
474 474
                 }
@@ -478,7 +478,7 @@  discard block
 block discarded – undo
478 478
                 $o = &$this->objects[$id];
479 479
 
480 480
                 $tmp = $o['info'];
481
-                $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
481
+                $res = "\n$id 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj";
482 482
 
483 483
                 return $res;
484 484
         }
@@ -514,12 +514,12 @@  discard block
 block discarded – undo
514 514
                         case 'CenterWindow':
515 515
                         case 'DisplayDocTitle':
516 516
                         case 'PickTrayByPDFSize':
517
-                            $o['info'][$k] = (bool)$v;
517
+                            $o['info'][$k] = (bool) $v;
518 518
                             break;
519 519
 
520 520
                         // Integer keys
521 521
                         case 'NumCopies':
522
-                            $o['info'][$k] = (int)$v;
522
+                            $o['info'][$k] = (int) $v;
523 523
                             break;
524 524
 
525 525
                         // Name keys
@@ -527,33 +527,33 @@  discard block
 block discarded – undo
527 527
                         case 'ViewClip':
528 528
                         case 'PrintClip':
529 529
                         case 'PrintArea':
530
-                            $o['info'][$k] = (string)$v;
530
+                            $o['info'][$k] = (string) $v;
531 531
                             break;
532 532
 
533 533
                         // Named with limited valid values
534 534
                         case 'NonFullScreenPageMode':
535
-                            if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
535
+                            if ( ! in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
536 536
                                 break;
537 537
                             }
538 538
                             $o['info'][$k] = $v;
539 539
                             break;
540 540
 
541 541
                         case 'Direction':
542
-                            if (!in_array($v, ['L2R', 'R2L'])) {
542
+                            if ( ! in_array($v, ['L2R', 'R2L'])) {
543 543
                                 break;
544 544
                             }
545 545
                             $o['info'][$k] = $v;
546 546
                             break;
547 547
 
548 548
                         case 'PrintScaling':
549
-                            if (!in_array($v, ['None', 'AppDefault'])) {
549
+                            if ( ! in_array($v, ['None', 'AppDefault'])) {
550 550
                                 break;
551 551
                             }
552 552
                             $o['info'][$k] = $v;
553 553
                             break;
554 554
 
555 555
                         case 'Duplex':
556
-                            if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
556
+                            if ( ! in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
557 557
                                 break;
558 558
                             }
559 559
                             $o['info'][$k] = $v;
@@ -563,7 +563,7 @@  discard block
 block discarded – undo
563 563
                         case 'PrintPageRange':
564 564
                             // Cast to integer array
565 565
                             foreach ($v as $vK => $vV) {
566
-                                $v[$vK] = (int)$vV;
566
+                                $v[$vK] = (int) $vV;
567 567
                             }
568 568
                             $o['info'][$k] = array_values($v);
569 569
                             break;
@@ -577,13 +577,13 @@  discard block
 block discarded – undo
577 577
 
578 578
                 foreach ($o['info'] as $k => $v) {
579 579
                     if (is_string($v)) {
580
-                        $v = '/' . $v;
580
+                        $v = '/'.$v;
581 581
                     } elseif (is_int($v)) {
582 582
                         $v = (string) $v;
583 583
                     } elseif (is_bool($v)) {
584 584
                         $v = ($v ? 'true' : 'false');
585 585
                     } elseif (is_array($v)) {
586
-                        $v = '[' . implode(' ', $v) . ']';
586
+                        $v = '['.implode(' ', $v).']';
587 587
                     }
588 588
                     $res .= "\n/$k $v";
589 589
                 }
@@ -624,7 +624,7 @@  discard block
 block discarded – undo
624 624
                 break;
625 625
 
626 626
             case 'viewerPreferences':
627
-                if (!isset($o['info']['viewerPreferences'])) {
627
+                if ( ! isset($o['info']['viewerPreferences'])) {
628 628
                     $this->numObj++;
629 629
                     $this->o_viewerPreferences($this->numObj, 'new');
630 630
                     $o['info']['viewerPreferences'] = $this->numObj;
@@ -695,7 +695,7 @@  discard block
 block discarded – undo
695 695
                 break;
696 696
 
697 697
             case 'page':
698
-                if (!is_array($options)) {
698
+                if ( ! is_array($options)) {
699 699
                     // then it will just be the id of the new page
700 700
                     $o['info']['pages'][] = $options;
701 701
                 } else {
@@ -762,7 +762,7 @@  discard block
 block discarded – undo
762 762
                         $res .= "$v 0 R\n";
763 763
                     }
764 764
 
765
-                    $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
765
+                    $res .= "]\n/Count ".count($this->objects[$id]['info']['pages']);
766 766
 
767 767
                     if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
768 768
                         isset($o['info']['procset']) ||
@@ -771,13 +771,13 @@  discard block
 block discarded – undo
771 771
                         $res .= "\n/Resources <<";
772 772
 
773 773
                         if (isset($o['info']['procset'])) {
774
-                            $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
774
+                            $res .= "\n/ProcSet ".$o['info']['procset']." 0 R";
775 775
                         }
776 776
 
777 777
                         if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
778 778
                             $res .= "\n/Font << ";
779 779
                             foreach ($o['info']['fonts'] as $finfo) {
780
-                                $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
780
+                                $res .= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
781 781
                             }
782 782
                             $res .= "\n>>";
783 783
                         }
@@ -785,7 +785,7 @@  discard block
 block discarded – undo
785 785
                         if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
786 786
                             $res .= "\n/XObject << ";
787 787
                             foreach ($o['info']['xObjects'] as $finfo) {
788
-                                $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
788
+                                $res .= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
789 789
                             }
790 790
                             $res .= "\n>>";
791 791
                         }
@@ -793,7 +793,7 @@  discard block
 block discarded – undo
793 793
                         if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
794 794
                             $res .= "\n/ExtGState << ";
795 795
                             foreach ($o['info']['extGStates'] as $gstate) {
796
-                                $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
796
+                                $res .= "\n/GS".$gstate['stateNum']." ".$gstate['objNum']." 0 R";
797 797
                             }
798 798
                             $res .= "\n>>";
799 799
                         }
@@ -801,13 +801,13 @@  discard block
 block discarded – undo
801 801
                         $res .= "\n>>";
802 802
                         if (isset($o['info']['mediaBox'])) {
803 803
                             $tmp = $o['info']['mediaBox'];
804
-                            $res .= "\n/MediaBox [" . sprintf(
804
+                            $res .= "\n/MediaBox [".sprintf(
805 805
                                     '%.3F %.3F %.3F %.3F',
806 806
                                     $tmp[0],
807 807
                                     $tmp[1],
808 808
                                     $tmp[2],
809 809
                                     $tmp[3]
810
-                                ) . ']';
810
+                                ).']';
811 811
                         }
812 812
                     }
813 813
 
@@ -853,7 +853,7 @@  discard block
 block discarded – undo
853 853
                         $res .= "$v 0 R ";
854 854
                     }
855 855
 
856
-                    $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
856
+                    $res .= "] /Count ".count($o['info']['outlines'])." >>\nendobj";
857 857
                 } else {
858 858
                     $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
859 859
                 }
@@ -957,7 +957,7 @@  discard block
 block discarded – undo
957 957
                             case 'Widths':
958 958
                             case 'FontDescriptor':
959 959
                             case 'SubType':
960
-                                $this->addMessage('o_font ' . $k . " : " . $v);
960
+                                $this->addMessage('o_font '.$k." : ".$v);
961 961
                                 $o['info'][$k] = $v;
962 962
                                 break;
963 963
                         }
@@ -981,44 +981,44 @@  discard block
 block discarded – undo
981 981
                     // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
982 982
 
983 983
                     $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
984
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
984
+                    $res .= "/BaseFont /".$o['info']['name']."\n";
985 985
 
986 986
                     // The horizontal identity mapping for 2-byte CIDs; may be used
987 987
                     // with CIDFonts using any Registry, Ordering, and Supplement values.
988 988
                     $res .= "/Encoding /Identity-H\n";
989
-                    $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
990
-                    $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
989
+                    $res .= "/DescendantFonts [".$o['info']['cidFont']." 0 R]\n";
990
+                    $res .= "/ToUnicode ".$o['info']['toUnicode']." 0 R\n";
991 991
                     $res .= ">>\n";
992 992
                     $res .= "endobj";
993 993
                 } else {
994
-                    $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
995
-                    $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
996
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
994
+                    $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
995
+                    $res .= "/Name /F".$o['info']['fontNum']."\n";
996
+                    $res .= "/BaseFont /".$o['info']['name']."\n";
997 997
 
998 998
                     if (isset($o['info']['encodingDictionary'])) {
999 999
                         // then place a reference to the dictionary
1000
-                        $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
1000
+                        $res .= "/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
1001 1001
                     } else {
1002 1002
                         if (isset($o['info']['encoding'])) {
1003 1003
                             // use the specified encoding
1004
-                            $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
1004
+                            $res .= "/Encoding /".$o['info']['encoding']."\n";
1005 1005
                         }
1006 1006
                     }
1007 1007
 
1008 1008
                     if (isset($o['info']['FirstChar'])) {
1009
-                        $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
1009
+                        $res .= "/FirstChar ".$o['info']['FirstChar']."\n";
1010 1010
                     }
1011 1011
 
1012 1012
                     if (isset($o['info']['LastChar'])) {
1013
-                        $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
1013
+                        $res .= "/LastChar ".$o['info']['LastChar']."\n";
1014 1014
                     }
1015 1015
 
1016 1016
                     if (isset($o['info']['Widths'])) {
1017
-                        $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
1017
+                        $res .= "/Widths ".$o['info']['Widths']." 0 R\n";
1018 1018
                     }
1019 1019
 
1020 1020
                     if (isset($o['info']['FontDescriptor'])) {
1021
-                        $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1021
+                        $res .= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
1022 1022
                     }
1023 1023
 
1024 1024
                     $res .= ">>\n";
@@ -1044,7 +1044,7 @@  discard block
 block discarded – undo
1044 1044
             }
1045 1045
         }
1046 1046
 
1047
-        return 'SUB' . str_pad($base_26, 3 , 'A', STR_PAD_LEFT);
1047
+        return 'SUB'.str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1048 1048
     }
1049 1049
 
1050 1050
     /**
@@ -1056,7 +1056,7 @@  discard block
 block discarded – undo
1056 1056
     private function processFont(int $fontObjId, array $object_info)
1057 1057
     {
1058 1058
         $fontFileName = $object_info['fontFileName'];
1059
-        if (!isset($this->fonts[$fontFileName])) {
1059
+        if ( ! isset($this->fonts[$fontFileName])) {
1060 1060
             return false;
1061 1061
         }
1062 1062
 
@@ -1068,9 +1068,9 @@  discard block
 block discarded – undo
1068 1068
         $isTtfFont = $fileSuffixLower === 'ttf';
1069 1069
         $isPfbFont = $fileSuffixLower === 'pfb';
1070 1070
 
1071
-        $this->addMessage('selectFont: checking for - ' . $fbfile);
1071
+        $this->addMessage('selectFont: checking for - '.$fbfile);
1072 1072
 
1073
-        if (!$fileSuffix) {
1073
+        if ( ! $fileSuffix) {
1074 1074
             $this->addMessage(
1075 1075
                 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
1076 1076
             );
@@ -1089,7 +1089,7 @@  discard block
 block discarded – undo
1089 1089
 
1090 1090
             foreach ($font['C'] as $num => $d) {
1091 1091
                 if (intval($num) > 0 || $num == '0') {
1092
-                    if (!$font['isUnicode']) {
1092
+                    if ( ! $font['isUnicode']) {
1093 1093
                         // With Unicode, widths array isn't used
1094 1094
                         if ($lastChar > 0 && $num > $lastChar + 1) {
1095 1095
                             for ($i = $lastChar + 1; $i < $num; $i++) {
@@ -1116,7 +1116,7 @@  discard block
 block discarded – undo
1116 1116
             if (isset($object['differences'])) {
1117 1117
                 foreach ($object['differences'] as $charNum => $charName) {
1118 1118
                     if ($charNum > $lastChar) {
1119
-                        if (!$object['isUnicode']) {
1119
+                        if ( ! $object['isUnicode']) {
1120 1120
                             // With Unicode, widths array isn't used
1121 1121
                             for ($i = $lastChar + 1; $i <= $charNum; $i++) {
1122 1122
                                 $widths[] = 0;
@@ -1139,17 +1139,17 @@  discard block
 block discarded – undo
1139 1139
                 $font['CIDWidths'] = $cid_widths;
1140 1140
             }
1141 1141
 
1142
-            $this->addMessage('selectFont: FirstChar = ' . $firstChar);
1143
-            $this->addMessage('selectFont: LastChar = ' . $lastChar);
1142
+            $this->addMessage('selectFont: FirstChar = '.$firstChar);
1143
+            $this->addMessage('selectFont: LastChar = '.$lastChar);
1144 1144
 
1145 1145
             $widthid = -1;
1146 1146
 
1147
-            if (!$font['isUnicode']) {
1147
+            if ( ! $font['isUnicode']) {
1148 1148
                 // With Unicode, widths array isn't used
1149 1149
 
1150 1150
                 $this->numObj++;
1151 1151
                 $this->o_contents($this->numObj, 'new', 'raw');
1152
-                $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
1152
+                $this->objects[$this->numObj]['c'] .= '['.implode(' ', $widths).']';
1153 1153
                 $widthid = $this->numObj;
1154 1154
             }
1155 1155
 
@@ -1313,8 +1313,8 @@  discard block
 block discarded – undo
1313 1313
 EOT;
1314 1314
 
1315 1315
                 $res = "\n$id 0 obj\n";
1316
-                $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1317
-                $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";;
1316
+                $res .= "<</Length ".mb_strlen($stream, '8bit')." >>\n";
1317
+                $res .= "stream\n".$stream."\nendstream"."\nendobj"; ;
1318 1318
 
1319 1319
                 return $res;
1320 1320
         }
@@ -1409,12 +1409,12 @@  discard block
 block discarded – undo
1409 1409
 
1410 1410
             case 'out':
1411 1411
                 $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1412
-                if (!isset($o['info']['encoding'])) {
1412
+                if ( ! isset($o['info']['encoding'])) {
1413 1413
                     $o['info']['encoding'] = 'WinAnsiEncoding';
1414 1414
                 }
1415 1415
 
1416 1416
                 if ($o['info']['encoding'] !== 'none') {
1417
-                    $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1417
+                    $res .= "/BaseEncoding /".$o['info']['encoding']."\n";
1418 1418
                 }
1419 1419
 
1420 1420
                 $res .= "/Differences \n[";
@@ -1495,8 +1495,8 @@  discard block
 block discarded – undo
1495 1495
                 $res = "\n$id 0 obj\n";
1496 1496
                 $res .= "<</Type /Font\n";
1497 1497
                 $res .= "/Subtype /CIDFontType2\n";
1498
-                $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1499
-                $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1498
+                $res .= "/BaseFont /".$o['info']['name']."\n";
1499
+                $res .= "/CIDSystemInfo ".$o['info']['cidSystemInfo']." 0 R\n";
1500 1500
                 //      if (isset($o['info']['FirstChar'])) {
1501 1501
                 //        $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1502 1502
                 //      }
@@ -1505,11 +1505,11 @@  discard block
 block discarded – undo
1505 1505
                 //        $res.= "/LastChar ".$o['info']['LastChar']."\n";
1506 1506
                 //      }
1507 1507
                 if (isset($o['info']['FontDescriptor'])) {
1508
-                    $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1508
+                    $res .= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
1509 1509
                 }
1510 1510
 
1511 1511
                 if (isset($o['info']['MissingWidth'])) {
1512
-                    $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1512
+                    $res .= "/DW ".$o['info']['MissingWidth']."\n";
1513 1513
                 }
1514 1514
 
1515 1515
                 if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
@@ -1521,7 +1521,7 @@  discard block
 block discarded – undo
1521 1521
                     $res .= "/W [$w]\n";
1522 1522
                 }
1523 1523
 
1524
-                $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1524
+                $res .= "/CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R\n";
1525 1525
                 $res .= ">>\n";
1526 1526
                 $res .= "endobj";
1527 1527
 
@@ -1561,8 +1561,8 @@  discard block
 block discarded – undo
1561 1561
 
1562 1562
                 $res = "\n$id 0 obj\n";
1563 1563
 
1564
-                $res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
1565
-                $res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
1564
+                $res .= '<</Registry ('.$registry.")\n"; // A string identifying an issuer of character collections
1565
+                $res .= '/Ordering ('.$ordering.")\n"; // A string that uniquely names a character collection issued by a specific registry
1566 1566
                 $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1567 1567
                 $res .= ">>";
1568 1568
 
@@ -1601,12 +1601,12 @@  discard block
 block discarded – undo
1601 1601
                 $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1602 1602
                     $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1603 1603
 
1604
-                if (!$compressed && isset($o['raw'])) {
1604
+                if ( ! $compressed && isset($o['raw'])) {
1605 1605
                     $res .= $tmp;
1606 1606
                 } else {
1607 1607
                     $res .= "<<";
1608 1608
 
1609
-                    if (!$compressed && $this->compressionReady && $this->options['compression']) {
1609
+                    if ( ! $compressed && $this->compressionReady && $this->options['compression']) {
1610 1610
                         // then implement ZLIB based compression on this content stream
1611 1611
                         $compressed = true;
1612 1612
                         $tmp = gzcompress($tmp, 6);
@@ -1620,7 +1620,7 @@  discard block
 block discarded – undo
1620 1620
                         $tmp = $this->ARC4($tmp);
1621 1621
                     }
1622 1622
 
1623
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1623
+                    $res .= "\n/Length ".mb_strlen($tmp, '8bit').">>\nstream\n$tmp\nendstream";
1624 1624
                 }
1625 1625
 
1626 1626
                 $res .= "\nendobj";
@@ -1690,7 +1690,7 @@  discard block
 block discarded – undo
1690 1690
         switch ($action) {
1691 1691
             case 'new':
1692 1692
                 $this->infoObject = $id;
1693
-                $date = 'D:' . @date('Ymd');
1693
+                $date = 'D:'.@date('Ymd');
1694 1694
                 $this->objects[$id] = [
1695 1695
                     't'    => 'info',
1696 1696
                     'info' => [
@@ -1775,12 +1775,12 @@  discard block
 block discarded – undo
1775 1775
                 $res = "\n$id 0 obj\n<< /Type /Action";
1776 1776
                 switch ($o['type']) {
1777 1777
                     case 'ilink':
1778
-                        if (!isset($this->destinations[(string)$o['info']['label']])) {
1778
+                        if ( ! isset($this->destinations[(string) $o['info']['label']])) {
1779 1779
                             break;
1780 1780
                         }
1781 1781
 
1782 1782
                         // there will be an 'label' setting, this is the name of the destination
1783
-                        $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1783
+                        $res .= "\n/S /GoTo\n/D ".$this->destinations[(string) $o['info']['label']]." 0 R";
1784 1784
                         break;
1785 1785
 
1786 1786
                     case 'URI':
@@ -1852,7 +1852,7 @@  discard block
 block discarded – undo
1852 1852
                         $res .= "\n/Subtype /Link";
1853 1853
                         break;
1854 1854
                 }
1855
-                $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1855
+                $res .= "\n/A ".$o['info']['actionId']." 0 R";
1856 1856
                 $res .= "\n/Border [0 0 0]";
1857 1857
                 $res .= "\n/H /I";
1858 1858
                 $res .= "\n/Rect [ ";
@@ -1926,7 +1926,7 @@  discard block
 block discarded – undo
1926 1926
 
1927 1927
             case 'annot':
1928 1928
                 // add an annotation to this page
1929
-                if (!isset($o['info']['annot'])) {
1929
+                if ( ! isset($o['info']['annot'])) {
1930 1930
                     $o['info']['annot'] = [];
1931 1931
                 }
1932 1932
 
@@ -1938,15 +1938,15 @@  discard block
 block discarded – undo
1938 1938
                 $res = "\n$id 0 obj\n<< /Type /Page";
1939 1939
                 if (isset($o['info']['mediaBox'])) {
1940 1940
                     $tmp = $o['info']['mediaBox'];
1941
-                    $res .= "\n/MediaBox [" . sprintf(
1941
+                    $res .= "\n/MediaBox [".sprintf(
1942 1942
                             '%.3F %.3F %.3F %.3F',
1943 1943
                             $tmp[0],
1944 1944
                             $tmp[1],
1945 1945
                             $tmp[2],
1946 1946
                             $tmp[3]
1947
-                        ) . ']';
1947
+                        ).']';
1948 1948
                 }
1949
-                $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
1949
+                $res .= "\n/Parent ".$o['info']['parent']." 0 R";
1950 1950
 
1951 1951
                 if (isset($o['info']['annot'])) {
1952 1952
                     $res .= "\n/Annots [";
@@ -1958,7 +1958,7 @@  discard block
 block discarded – undo
1958 1958
 
1959 1959
                 $count = count($o['info']['contents']);
1960 1960
                 if ($count == 1) {
1961
-                    $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
1961
+                    $res .= "\n/Contents ".$o['info']['contents'][0]." 0 R";
1962 1962
                 } else {
1963 1963
                     if ($count > 1) {
1964 1964
                         $res .= "\n/Contents [\n";
@@ -2038,7 +2038,7 @@  discard block
 block discarded – undo
2038 2038
                         $res .= "\n/$k $v";
2039 2039
                     }
2040 2040
 
2041
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
2041
+                    $res .= "\n/Length ".mb_strlen($tmp, '8bit')." >>\nstream\n$tmp\nendstream";
2042 2042
                 }
2043 2043
 
2044 2044
                 $res .= "\nendobj";
@@ -2061,7 +2061,7 @@  discard block
 block discarded – undo
2061 2061
                 $this->objects[$id] = [
2062 2062
                     't'    => 'embedjs',
2063 2063
                     'info' => [
2064
-                        'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
2064
+                        'Names' => '[(EmbeddedJS) '.($id + 1).' 0 R]'
2065 2065
                     ]
2066 2066
                 ];
2067 2067
                 break;
@@ -2094,7 +2094,7 @@  discard block
 block discarded – undo
2094 2094
                     't'    => 'javascript',
2095 2095
                     'info' => [
2096 2096
                         'S'  => '/JavaScript',
2097
-                        'JS' => '(' . $this->filterText($code, true, false) . ')',
2097
+                        'JS' => '('.$this->filterText($code, true, false).')',
2098 2098
                     ]
2099 2099
                 ];
2100 2100
                 break;
@@ -2129,7 +2129,7 @@  discard block
 block discarded – undo
2129 2129
                 // make the new object
2130 2130
                 $this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
2131 2131
 
2132
-                $info =& $this->objects[$id]['info'];
2132
+                $info = & $this->objects[$id]['info'];
2133 2133
 
2134 2134
                 $info['Type'] = '/XObject';
2135 2135
                 $info['Subtype'] = '/Image';
@@ -2137,11 +2137,11 @@  discard block
 block discarded – undo
2137 2137
                 $info['Height'] = $options['ih'];
2138 2138
 
2139 2139
                 if (isset($options['masked']) && $options['masked']) {
2140
-                    $info['SMask'] = ($this->numObj - 1) . ' 0 R';
2140
+                    $info['SMask'] = ($this->numObj - 1).' 0 R';
2141 2141
                 }
2142 2142
 
2143
-                if (!isset($options['type']) || $options['type'] === 'jpg') {
2144
-                    if (!isset($options['channels'])) {
2143
+                if ( ! isset($options['type']) || $options['type'] === 'jpg') {
2144
+                    if ( ! isset($options['channels'])) {
2145 2145
                         $options['channels'] = 3;
2146 2146
                     }
2147 2147
 
@@ -2166,17 +2166,17 @@  discard block
 block discarded – undo
2166 2166
                 } else {
2167 2167
                     if ($options['type'] === 'png') {
2168 2168
                         $info['Filter'] = '/FlateDecode';
2169
-                        $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
2169
+                        $info['DecodeParms'] = '<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
2170 2170
 
2171 2171
                         if ($options['isMask']) {
2172 2172
                             $info['ColorSpace'] = '/DeviceGray';
2173 2173
                         } else {
2174 2174
                             if (mb_strlen($options['pdata'], '8bit')) {
2175
-                                $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
2175
+                                $tmp = ' [ /Indexed /DeviceRGB '.(mb_strlen($options['pdata'], '8bit') / 3 - 1).' ';
2176 2176
                                 $this->numObj++;
2177 2177
                                 $this->o_contents($this->numObj, 'new');
2178 2178
                                 $this->objects[$this->numObj]['c'] = $options['pdata'];
2179
-                                $tmp .= $this->numObj . ' 0 R';
2179
+                                $tmp .= $this->numObj.' 0 R';
2180 2180
                                 $tmp .= ' ]';
2181 2181
                                 $info['ColorSpace'] = $tmp;
2182 2182
 
@@ -2184,15 +2184,15 @@  discard block
 block discarded – undo
2184 2184
                                     $transparency = $options['transparency'];
2185 2185
                                     switch ($transparency['type']) {
2186 2186
                                         case 'indexed':
2187
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2187
+                                            $tmp = ' [ '.$transparency['data'].' '.$transparency['data'].'] ';
2188 2188
                                             $info['Mask'] = $tmp;
2189 2189
                                             break;
2190 2190
 
2191 2191
                                         case 'color-key':
2192
-                                            $tmp = ' [ ' .
2193
-                                                $transparency['r'] . ' ' . $transparency['r'] .
2194
-                                                $transparency['g'] . ' ' . $transparency['g'] .
2195
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2192
+                                            $tmp = ' [ '.
2193
+                                                $transparency['r'].' '.$transparency['r'].
2194
+                                                $transparency['g'].' '.$transparency['g'].
2195
+                                                $transparency['b'].' '.$transparency['b'].
2196 2196
                                                 ' ] ';
2197 2197
                                             $info['Mask'] = $tmp;
2198 2198
                                             break;
@@ -2204,21 +2204,21 @@  discard block
 block discarded – undo
2204 2204
 
2205 2205
                                     switch ($transparency['type']) {
2206 2206
                                         case 'indexed':
2207
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2207
+                                            $tmp = ' [ '.$transparency['data'].' '.$transparency['data'].'] ';
2208 2208
                                             $info['Mask'] = $tmp;
2209 2209
                                             break;
2210 2210
 
2211 2211
                                         case 'color-key':
2212
-                                            $tmp = ' [ ' .
2213
-                                                $transparency['r'] . ' ' . $transparency['r'] . ' ' .
2214
-                                                $transparency['g'] . ' ' . $transparency['g'] . ' ' .
2215
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2212
+                                            $tmp = ' [ '.
2213
+                                                $transparency['r'].' '.$transparency['r'].' '.
2214
+                                                $transparency['g'].' '.$transparency['g'].' '.
2215
+                                                $transparency['b'].' '.$transparency['b'].
2216 2216
                                                 ' ] ';
2217 2217
                                             $info['Mask'] = $tmp;
2218 2218
                                             break;
2219 2219
                                     }
2220 2220
                                 }
2221
-                                $info['ColorSpace'] = '/' . $options['color'];
2221
+                                $info['ColorSpace'] = '/'.$options['color'];
2222 2222
                             }
2223 2223
                         }
2224 2224
 
@@ -2248,7 +2248,7 @@  discard block
 block discarded – undo
2248 2248
                     $tmp = $this->ARC4($tmp);
2249 2249
                 }
2250 2250
 
2251
-                $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
2251
+                $res .= "\n/Length ".mb_strlen($tmp, '8bit').">>\nstream\n$tmp\nendstream\nendobj";
2252 2252
 
2253 2253
                 return $res;
2254 2254
         }
@@ -2309,7 +2309,7 @@  discard block
 block discarded – undo
2309 2309
                 $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
2310 2310
 
2311 2311
                 foreach ($o["info"] as $k => $v) {
2312
-                    if (!in_array($k, $valid_params)) {
2312
+                    if ( ! in_array($k, $valid_params)) {
2313 2313
                         continue;
2314 2314
                     }
2315 2315
                     $res .= "/$k $v\n";
@@ -2356,7 +2356,7 @@  discard block
 block discarded – undo
2356 2356
                 $res = "\n$id 0 obj\n<< /Type /XObject\n";
2357 2357
 
2358 2358
                 foreach ($o["info"] as $k => $v) {
2359
-                    switch($k)
2359
+                    switch ($k)
2360 2360
                     {
2361 2361
                         case 'Subtype':
2362 2362
                             $res .= "/Subtype /$v\n";
@@ -2377,21 +2377,21 @@  discard block
 block discarded – undo
2377 2377
 
2378 2378
                 $res .= "/Resources <<";
2379 2379
                 if (isset($o['procset'])) {
2380
-                    $res .= "\n/ProcSet " . $o['procset'] . " 0 R";
2380
+                    $res .= "\n/ProcSet ".$o['procset']." 0 R";
2381 2381
                 } else {
2382 2382
                     $res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
2383 2383
                 }
2384 2384
                 if (isset($o['fonts']) && count($o['fonts'])) {
2385 2385
                     $res .= "\n/Font << ";
2386 2386
                     foreach ($o['fonts'] as $finfo) {
2387
-                        $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
2387
+                        $res .= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
2388 2388
                     }
2389 2389
                     $res .= "\n>>";
2390 2390
                 }
2391 2391
                 if (isset($o['xObjects']) && count($o['xObjects'])) {
2392 2392
                     $res .= "\n/XObject << ";
2393 2393
                     foreach ($o['xObjects'] as $finfo) {
2394
-                        $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
2394
+                        $res .= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
2395 2395
                     }
2396 2396
                     $res .= "\n>>";
2397 2397
                 }
@@ -2409,8 +2409,8 @@  discard block
 block discarded – undo
2409 2409
                     $tmp = $this->ARC4($tmp);
2410 2410
                 }
2411 2411
 
2412
-                $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2413
-                $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";;
2412
+                $res .= "/Length ".mb_strlen($tmp, '8bit')." >>\n";
2413
+                $res .= "stream\n".$tmp."\nendstream"."\nendobj"; ;
2414 2414
 
2415 2415
                 return $res;
2416 2416
         }
@@ -2448,7 +2448,7 @@  discard block
 block discarded – undo
2448 2448
                 $res = "\n$id 0 obj\n<<";
2449 2449
 
2450 2450
                 foreach ($o["info"] as $k => $v) {
2451
-                    switch($k) {
2451
+                    switch ($k) {
2452 2452
                         case 'Fields':
2453 2453
                             $res .= " /Fields [";
2454 2454
                             foreach ($v as $i) {
@@ -2465,7 +2465,7 @@  discard block
 block discarded – undo
2465 2465
                 if (isset($o['fonts']) && count($o['fonts'])) {
2466 2466
                     $res .= "/Font << \n";
2467 2467
                     foreach ($o['fonts'] as $finfo) {
2468
-                        $res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
2468
+                        $res .= "/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R\n";
2469 2469
                     }
2470 2470
                     $res .= ">>\n";
2471 2471
                 }
@@ -2552,7 +2552,7 @@  discard block
 block discarded – undo
2552 2552
                             $res .= ">>\n";
2553 2553
                             break;
2554 2554
                         case 'T':
2555
-                            if($encrypted) {
2555
+                            if ($encrypted) {
2556 2556
                                 $v = $this->filterText($this->ARC4($v), false, false);
2557 2557
                             }
2558 2558
                             $res .= "/T ($v)\n";
@@ -2590,16 +2590,16 @@  discard block
 block discarded – undo
2590 2590
 
2591 2591
             case 'byterange':
2592 2592
                 $o = &$this->objects[$id];
2593
-                $content =& $options['content'];
2593
+                $content = & $options['content'];
2594 2594
                 $content_len = strlen($content);
2595 2595
                 $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2596 2596
                 $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
2597 2597
                 $rangeStartPos = $pos + $len + 1 + 10; // before '<'
2598
-                $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos ), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2598
+                $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2599 2599
 
2600 2600
                 $fuid = uniqid();
2601
-                $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2602
-                $tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
2601
+                $tmpInput = $this->tmp."/pkcs7.tmp.".$fuid.'.in';
2602
+                $tmpOutput = $this->tmp."/pkcs7.tmp.".$fuid.'.out';
2603 2603
 
2604 2604
                 if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
2605 2605
                     throw new \Exception("Unable to write temporary file for signing.");
@@ -2644,12 +2644,12 @@  discard block
 block discarded – undo
2644 2644
                     $this->encryptInit($id);
2645 2645
                 }
2646 2646
 
2647
-                $res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2648
-                $res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
2647
+                $res .= "/ByteRange ".sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2648
+                $res .= "/Contents <".str_pad('', $sign_maxlen, '0').">\n";
2649 2649
                 $res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
2650 2650
                 $res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
2651 2651
 
2652
-                $date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
2652
+                $date = "D:".substr_replace(date('YmdHisO'), '\'', -2, 0).'\'';
2653 2653
                 if ($encrypted) {
2654 2654
                     $date = $this->ARC4($date);
2655 2655
                 }
@@ -2659,14 +2659,14 @@  discard block
 block discarded – undo
2659 2659
 
2660 2660
                 $o = &$this->objects[$id];
2661 2661
                 foreach ($o['info'] as $k => $v) {
2662
-                    switch($k) {
2662
+                    switch ($k) {
2663 2663
                         case 'Name':
2664 2664
                         case 'Location':
2665 2665
                         case 'Reason':
2666 2666
                         case 'ContactInfo':
2667 2667
                             if ($v !== null && $v !== '') {
2668
-                                $res .= "/$k (" .
2669
-                                  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
2668
+                                $res .= "/$k (".
2669
+                                  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v).") \n";
2670 2670
                             }
2671 2671
                             break;
2672 2672
                     }
@@ -2698,10 +2698,10 @@  discard block
 block discarded – undo
2698 2698
 
2699 2699
             case 'keys':
2700 2700
                 // figure out the additional parameters required
2701
-                $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2702
-                    . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2703
-                    . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2704
-                    . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2701
+                $pad = chr(0x28).chr(0xBF).chr(0x4E).chr(0x5E).chr(0x4E).chr(0x75).chr(0x8A).chr(0x41)
2702
+                    . chr(0x64).chr(0x00).chr(0x4E).chr(0x56).chr(0xFF).chr(0xFA).chr(0x01).chr(0x08)
2703
+                    . chr(0x2E).chr(0x2E).chr(0x00).chr(0xB6).chr(0xD0).chr(0x68).chr(0x3E).chr(0x80)
2704
+                    . chr(0x2F).chr(0x0C).chr(0xA9).chr(0xFE).chr(0x64).chr(0x53).chr(0x69).chr(0x7A);
2705 2705
 
2706 2706
                 $info = $this->objects[$id]['info'];
2707 2707
 
@@ -2711,7 +2711,7 @@  discard block
 block discarded – undo
2711 2711
                     $owner = substr($info['owner'], 0, 32);
2712 2712
                 } else {
2713 2713
                     if ($len < 32) {
2714
-                        $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2714
+                        $owner = $info['owner'].substr($pad, 0, 32 - $len);
2715 2715
                     } else {
2716 2716
                         $owner = $info['owner'];
2717 2717
                     }
@@ -2722,7 +2722,7 @@  discard block
 block discarded – undo
2722 2722
                     $user = substr($info['user'], 0, 32);
2723 2723
                 } else {
2724 2724
                     if ($len < 32) {
2725
-                        $user = $info['user'] . substr($pad, 0, 32 - $len);
2725
+                        $user = $info['user'].substr($pad, 0, 32 - $len);
2726 2726
                     } else {
2727 2727
                         $user = $info['user'];
2728 2728
                     }
@@ -2736,7 +2736,7 @@  discard block
 block discarded – undo
2736 2736
 
2737 2737
                 // now make the u value, phew.
2738 2738
                 $tmp = $this->md5_16(
2739
-                    $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2739
+                    $user.$ovalue.chr($info['p']).chr(255).chr(255).chr(255).hex2bin($this->fileIdentifier)
2740 2740
                 );
2741 2741
 
2742 2742
                 $ukey = substr($tmp, 0, 5);
@@ -2755,11 +2755,11 @@  discard block
 block discarded – undo
2755 2755
                 $res .= "\n/Filter /Standard";
2756 2756
                 $res .= "\n/V 1";
2757 2757
                 $res .= "\n/R 2";
2758
-                $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2759
-                $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2758
+                $res .= "\n/O (".$this->filterText($o['info']['O'], false, false).')';
2759
+                $res .= "\n/U (".$this->filterText($o['info']['U'], false, false).')';
2760 2760
                 // and the p-value needs to be converted to account for the twos-complement approach
2761 2761
                 $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2762
-                $res .= "\n/P " . ($o['info']['p']);
2762
+                $res .= "\n/P ".($o['info']['p']);
2763 2763
                 $res .= "\n>>\nendobj";
2764 2764
 
2765 2765
                 return $res;
@@ -2785,7 +2785,7 @@  discard block
 block discarded – undo
2785 2785
             case 'out':
2786 2786
                 $res = "\n$id 0 obj << ";
2787 2787
 
2788
-                foreach($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2788
+                foreach ($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2789 2789
                     $res .= "/$referenceObjName $referenceObjId 0 R ";
2790 2790
                 }
2791 2791
 
@@ -2827,7 +2827,7 @@  discard block
 block discarded – undo
2827 2827
                             $filename = $entry['filename'];
2828 2828
                         }
2829 2829
 
2830
-                        $res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
2830
+                        $res .= "($filename) ".$entry['dict_reference']." 0 R ";
2831 2831
                     }
2832 2832
 
2833 2833
                     $res .= "] >> endobj";
@@ -2862,7 +2862,7 @@  discard block
 block discarded – undo
2862 2862
                 }
2863 2863
 
2864 2864
                 $res = "\n$id 0 obj <</Type /Filespec /EF";
2865
-                $res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
2865
+                $res .= " <</F ".$info['embedded_reference']." 0 R >>";
2866 2866
                 $res .= " /F ($filename) /UF ($filename) /Desc ($description)";
2867 2867
                 $res .= " >> endobj";
2868 2868
                 return $res;
@@ -2906,8 +2906,8 @@  discard block
 block discarded – undo
2906 2906
                 }
2907 2907
                 $file_size_compressed = mb_strlen($file_content_compressed, '8bit');
2908 2908
 
2909
-                $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
2910
-                    " /Type/EmbeddedFile /Filter/FlateDecode" .
2909
+                $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>".
2910
+                    " /Type/EmbeddedFile /Filter/FlateDecode".
2911 2911
                     " /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
2912 2912
 
2913 2913
                 return $res;
@@ -2948,7 +2948,7 @@  discard block
 block discarded – undo
2948 2948
         $tmp = $this->encryptionKey;
2949 2949
         $hex = dechex($id);
2950 2950
         if (mb_strlen($hex, '8bit') < 6) {
2951
-            $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
2951
+            $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')).$hex;
2952 2952
         }
2953 2953
         $tmp .= chr(hexdec(substr($hex, 4, 2)))
2954 2954
             . chr(hexdec(substr($hex, 2, 2)))
@@ -3127,7 +3127,7 @@  discard block
 block discarded – undo
3127 3127
 
3128 3128
         if ($this->fileIdentifier === '') {
3129 3129
             $tmp = implode('', $this->objects[$this->infoObject]['info']);
3130
-            $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
3130
+            $this->fileIdentifier = md5('DOMPDF'.__FILE__.$tmp.microtime().mt_rand());
3131 3131
         }
3132 3132
 
3133 3133
         if ($this->arc4_objnum) {
@@ -3138,7 +3138,7 @@  discard block
 block discarded – undo
3138 3138
         $this->checkAllHere();
3139 3139
 
3140 3140
         $xref = [];
3141
-        $content = '%PDF-' . self::PDF_VERSION;
3141
+        $content = '%PDF-'.self::PDF_VERSION;
3142 3142
         $pos = mb_strlen($content, '8bit');
3143 3143
 
3144 3144
         // pre-process o_font objects before output of all objects
@@ -3149,31 +3149,31 @@  discard block
 block discarded – undo
3149 3149
         }
3150 3150
 
3151 3151
         foreach ($this->objects as $k => $v) {
3152
-            $tmp = 'o_' . $v['t'];
3152
+            $tmp = 'o_'.$v['t'];
3153 3153
             $cont = $this->$tmp($k, 'out');
3154 3154
             $content .= $cont;
3155 3155
             $xref[] = $pos + 1; //+1 to account for \n at the start of each object
3156 3156
             $pos += mb_strlen($cont, '8bit');
3157 3157
         }
3158 3158
 
3159
-        $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
3159
+        $content .= "\nxref\n0 ".(count($xref) + 1)."\n0000000000 65535 f \n";
3160 3160
 
3161 3161
         foreach ($xref as $p) {
3162
-            $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
3162
+            $content .= str_pad($p, 10, "0", STR_PAD_LEFT)." 00000 n \n";
3163 3163
         }
3164 3164
 
3165
-        $content .= "trailer\n<<\n" .
3166
-            '/Size ' . (count($xref) + 1) . "\n" .
3167
-            '/Root 1 0 R' . "\n" .
3168
-            '/Info ' . $this->infoObject . " 0 R\n"
3165
+        $content .= "trailer\n<<\n".
3166
+            '/Size '.(count($xref) + 1)."\n".
3167
+            '/Root 1 0 R'."\n".
3168
+            '/Info '.$this->infoObject." 0 R\n"
3169 3169
         ;
3170 3170
 
3171 3171
         // if encryption has been applied to this document then add the marker for this dictionary
3172 3172
         if ($this->arc4_objnum > 0) {
3173
-            $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
3173
+            $content .= '/Encrypt '.$this->arc4_objnum." 0 R\n";
3174 3174
         }
3175 3175
 
3176
-        $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
3176
+        $content .= '/ID[<'.$this->fileIdentifier.'><'.$this->fileIdentifier.">]\n";
3177 3177
 
3178 3178
         // account for \n added at start of xref table
3179 3179
         $pos++;
@@ -3182,7 +3182,7 @@  discard block
 block discarded – undo
3182 3182
 
3183 3183
         if (count($this->byteRange) > 0) {
3184 3184
             foreach ($this->byteRange as $k => $v) {
3185
-                $tmp = 'o_' . $v['t'];
3185
+                $tmp = 'o_'.$v['t'];
3186 3186
                 $this->$tmp($k, 'byterange', ['content' => &$content]);
3187 3187
             }
3188 3188
         }
@@ -3241,7 +3241,7 @@  discard block
 block discarded – undo
3241 3241
     {
3242 3242
         // assume that $font contains the path and file but not the extension
3243 3243
         $name = basename($font);
3244
-        $dir = dirname($font) . '/';
3244
+        $dir = dirname($font).'/';
3245 3245
 
3246 3246
         $fontcache = $this->fontcache;
3247 3247
         if ($fontcache == '') {
@@ -3256,7 +3256,7 @@  discard block
 block discarded – undo
3256 3256
 
3257 3257
         $this->addMessage("openFont: $font - $name");
3258 3258
 
3259
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3259
+        if ( ! $this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3260 3260
             $metrics_name = "$name.afm";
3261 3261
         } else {
3262 3262
             $metrics_name = "$name.ufm";
@@ -3265,11 +3265,11 @@  discard block
 block discarded – undo
3265 3265
         $cache_name = "$metrics_name.php";
3266 3266
         $this->addMessage("metrics: $metrics_name, cache: $cache_name");
3267 3267
 
3268
-        if (file_exists($fontcache . '/' . $cache_name)) {
3268
+        if (file_exists($fontcache.'/'.$cache_name)) {
3269 3269
             $this->addMessage("openFont: php file exists $fontcache/$cache_name");
3270
-            $this->fonts[$font] = require($fontcache . '/' . $cache_name);
3270
+            $this->fonts[$font] = require($fontcache.'/'.$cache_name);
3271 3271
 
3272
-            if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
3272
+            if ( ! isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
3273 3273
                 // if the font file is old, then clear it out and prepare for re-creation
3274 3274
                 $this->addMessage('openFont: clear out, make way for new version.');
3275 3275
                 $this->fonts[$font] = null;
@@ -3277,19 +3277,19 @@  discard block
 block discarded – undo
3277 3277
             }
3278 3278
         } else {
3279 3279
             $old_cache_name = "php_$metrics_name";
3280
-            if (file_exists($fontcache . '/' . $old_cache_name)) {
3280
+            if (file_exists($fontcache.'/'.$old_cache_name)) {
3281 3281
                 $this->addMessage(
3282 3282
                     "openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format"
3283 3283
                 );
3284
-                $old_cache = file_get_contents($fontcache . '/' . $old_cache_name);
3285
-                file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';');
3284
+                $old_cache = file_get_contents($fontcache.'/'.$old_cache_name);
3285
+                file_put_contents($fontcache.'/'.$cache_name, '<?php return '.$old_cache.';');
3286 3286
 
3287 3287
                 $this->openFont($font);
3288 3288
                 return;
3289 3289
             }
3290 3290
         }
3291 3291
 
3292
-        if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) {
3292
+        if ( ! isset($this->fonts[$font]) && file_exists($dir.$metrics_name)) {
3293 3293
             // then rebuild the php_<font>.afm file from the <font>.afm file
3294 3294
             $this->addMessage("openFont: build php file from $dir$metrics_name");
3295 3295
             $data = [];
@@ -3306,7 +3306,7 @@  discard block
 block discarded – undo
3306 3306
                 $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
3307 3307
             }
3308 3308
 
3309
-            $file = file($dir . $metrics_name);
3309
+            $file = file($dir.$metrics_name);
3310 3310
 
3311 3311
             foreach ($file as $rowA) {
3312 3312
                 $row = trim($rowA);
@@ -3366,12 +3366,12 @@  discard block
 block discarded – undo
3366 3366
                                 }
3367 3367
                             }
3368 3368
 
3369
-                            $c = (int)$dtmp['C'];
3369
+                            $c = (int) $dtmp['C'];
3370 3370
                             $n = $dtmp['N'];
3371 3371
                             $width = floatval($dtmp['WX']);
3372 3372
 
3373 3373
                             if ($c >= 0) {
3374
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3374
+                                if ( ! ctype_xdigit($n) || $c != hexdec($n)) {
3375 3375
                                     $data['codeToName'][$c] = $n;
3376 3376
                                 }
3377 3377
                                 $data['C'][$c] = $width;
@@ -3379,7 +3379,7 @@  discard block
 block discarded – undo
3379 3379
                                 $data['C'][$n] = $width;
3380 3380
                             }
3381 3381
 
3382
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3382
+                            if ( ! isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3383 3383
                                 $data['MissingWidth'] = $width;
3384 3384
                             }
3385 3385
 
@@ -3387,7 +3387,7 @@  discard block
 block discarded – undo
3387 3387
 
3388 3388
                         // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
3389 3389
                         case 'U': // Found in UFM files
3390
-                            if (!$data['isUnicode']) {
3390
+                            if ( ! $data['isUnicode']) {
3391 3391
                                 break;
3392 3392
                             }
3393 3393
 
@@ -3412,7 +3412,7 @@  discard block
 block discarded – undo
3412 3412
                                 }
3413 3413
                             }
3414 3414
 
3415
-                            $c = (int)$dtmp['U'];
3415
+                            $c = (int) $dtmp['U'];
3416 3416
                             $n = $dtmp['N'];
3417 3417
                             $glyph = $dtmp['G'];
3418 3418
                             $width = floatval($dtmp['WX']);
@@ -3424,7 +3424,7 @@  discard block
 block discarded – undo
3424 3424
                                     $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
3425 3425
                                 }
3426 3426
 
3427
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3427
+                                if ( ! ctype_xdigit($n) || $c != hexdec($n)) {
3428 3428
                                     $data['codeToName'][$c] = $n;
3429 3429
                                 }
3430 3430
                                 $data['C'][$c] = $width;
@@ -3432,7 +3432,7 @@  discard block
 block discarded – undo
3432 3432
                                 $data['C'][$n] = $width;
3433 3433
                             }
3434 3434
 
3435
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3435
+                            if ( ! isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3436 3436
                                 $data['MissingWidth'] = $width;
3437 3437
                             }
3438 3438
 
@@ -3460,12 +3460,12 @@  discard block
 block discarded – undo
3460 3460
             //Because of potential trouble with php safe mode, expect that the folder already exists.
3461 3461
             //If not existing, this will hit performance because of missing cached results.
3462 3462
             if (is_dir($fontcache) && is_writable($fontcache)) {
3463
-                file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';');
3463
+                file_put_contents($fontcache.'/'.$cache_name, '<?php return '.var_export($data, true).';');
3464 3464
             }
3465 3465
             $data = null;
3466 3466
         }
3467 3467
 
3468
-        if (!isset($this->fonts[$font])) {
3468
+        if ( ! isset($this->fonts[$font])) {
3469 3469
             $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3470 3470
         }
3471 3471
 
@@ -3497,7 +3497,7 @@  discard block
 block discarded – undo
3497 3497
             $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
3498 3498
         }
3499 3499
 
3500
-        if (!isset($this->fonts[$fontName])) {
3500
+        if ( ! isset($this->fonts[$fontName])) {
3501 3501
             $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
3502 3502
 
3503 3503
             // load the file
@@ -3634,7 +3634,7 @@  discard block
 block discarded – undo
3634 3634
     {
3635 3635
         $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3636 3636
 
3637
-        if (!$force && $this->currentColor == $new_color) {
3637
+        if ( ! $force && $this->currentColor == $new_color) {
3638 3638
             return;
3639 3639
         }
3640 3640
 
@@ -3656,7 +3656,7 @@  discard block
 block discarded – undo
3656 3656
      */
3657 3657
     function setFillRule($fillRule)
3658 3658
     {
3659
-        if (!in_array($fillRule, ["nonzero", "evenodd"])) {
3659
+        if ( ! in_array($fillRule, ["nonzero", "evenodd"])) {
3660 3660
             return;
3661 3661
         }
3662 3662
 
@@ -3673,7 +3673,7 @@  discard block
 block discarded – undo
3673 3673
     {
3674 3674
         $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3675 3675
 
3676
-        if (!$force && $this->currentStrokeColor == $new_color) {
3676
+        if ( ! $force && $this->currentStrokeColor == $new_color) {
3677 3677
             return;
3678 3678
         }
3679 3679
 
@@ -3734,7 +3734,7 @@  discard block
 block discarded – undo
3734 3734
             "Exclusion"
3735 3735
         ];
3736 3736
 
3737
-        if (!in_array($mode, $blend_modes)) {
3737
+        if ( ! in_array($mode, $blend_modes)) {
3738 3738
             $mode = "Normal";
3739 3739
         }
3740 3740
 
@@ -3754,7 +3754,7 @@  discard block
 block discarded – undo
3754 3754
 
3755 3755
         $options = [
3756 3756
             "BM" => "/$mode",
3757
-            "CA" => (float)$opacity
3757
+            "CA" => (float) $opacity
3758 3758
         ];
3759 3759
 
3760 3760
         $this->setGraphicsState($options);
@@ -3789,7 +3789,7 @@  discard block
 block discarded – undo
3789 3789
             "Exclusion"
3790 3790
         ];
3791 3791
 
3792
-        if (!in_array($mode, $blend_modes)) {
3792
+        if ( ! in_array($mode, $blend_modes)) {
3793 3793
             $mode = "Normal";
3794 3794
         }
3795 3795
 
@@ -3809,7 +3809,7 @@  discard block
 block discarded – undo
3809 3809
 
3810 3810
         $options = [
3811 3811
             "BM" => "/$mode",
3812
-            "ca" => (float)$opacity,
3812
+            "ca" => (float) $opacity,
3813 3813
         ];
3814 3814
 
3815 3815
         $this->setGraphicsState($options);
@@ -3989,15 +3989,15 @@  discard block
 block discarded – undo
3989 3989
             $nSeg = 2;
3990 3990
         }
3991 3991
 
3992
-        $astart = deg2rad((float)$astart);
3993
-        $afinish = deg2rad((float)$afinish);
3992
+        $astart = deg2rad((float) $astart);
3993
+        $afinish = deg2rad((float) $afinish);
3994 3994
         $totalAngle = $afinish - $astart;
3995 3995
 
3996 3996
         $dt = $totalAngle / $nSeg;
3997 3997
         $dtm = $dt / 3;
3998 3998
 
3999 3999
         if ($angle != 0) {
4000
-            $a = -1 * deg2rad((float)$angle);
4000
+            $a = -1 * deg2rad((float) $angle);
4001 4001
 
4002 4002
             $this->addContent(
4003 4003
                 sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
@@ -4013,7 +4013,7 @@  discard block
 block discarded – undo
4013 4013
         $c0 = -$r1 * sin($t1);
4014 4014
         $d0 = $r2 * cos($t1);
4015 4015
 
4016
-        if (!$incomplete) {
4016
+        if ( ! $incomplete) {
4017 4017
             $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
4018 4018
         }
4019 4019
 
@@ -4043,7 +4043,7 @@  discard block
 block discarded – undo
4043 4043
             $d0 = $d1;
4044 4044
         }
4045 4045
 
4046
-        if (!$incomplete) {
4046
+        if ( ! $incomplete) {
4047 4047
             if ($fill) {
4048 4048
                 $this->addContent(' f');
4049 4049
             }
@@ -4103,7 +4103,7 @@  discard block
 block discarded – undo
4103 4103
         }
4104 4104
 
4105 4105
         if (is_array($dash)) {
4106
-            $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
4106
+            $string .= ' [ '.implode(' ', $dash)." ] $phase d";
4107 4107
         }
4108 4108
 
4109 4109
         $this->currentLineStyle = $string;
@@ -4176,17 +4176,17 @@  discard block
 block discarded – undo
4176 4176
 
4177 4177
     function stroke(bool $close = false)
4178 4178
     {
4179
-        $this->addContent("\n" . ($close ? "s" : "S"));
4179
+        $this->addContent("\n".($close ? "s" : "S"));
4180 4180
     }
4181 4181
 
4182 4182
     function fill()
4183 4183
     {
4184
-        $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
4184
+        $this->addContent("\nf".($this->fillRule === "evenodd" ? "*" : ""));
4185 4185
     }
4186 4186
 
4187 4187
     function fillStroke(bool $close = false)
4188 4188
     {
4189
-        $this->addContent("\n" . ($close ? "b" : "B") . ($this->fillRule === "evenodd" ? "*" : ""));
4189
+        $this->addContent("\n".($close ? "b" : "B").($this->fillRule === "evenodd" ? "*" : ""));
4190 4190
     }
4191 4191
 
4192 4192
     /**
@@ -4272,11 +4272,11 @@  discard block
 block discarded – undo
4272 4272
      */
4273 4273
     public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
4274 4274
     {
4275
-        if (!$this->numFonts) {
4275
+        if ( ! $this->numFonts) {
4276 4276
             $this->selectFont($this->defaultFont);
4277 4277
         }
4278 4278
 
4279
-        $color = implode(' ', $color) . ' rg';
4279
+        $color = implode(' ', $color).' rg';
4280 4280
 
4281 4281
         $currentFontNum = $this->currentFontNum;
4282 4282
         $font = array_filter($this->objects[$this->currentNode]['info']['fonts'],
@@ -4293,7 +4293,7 @@  discard block
 block discarded – undo
4293 4293
           'T' => $name,
4294 4294
           'Ff' => $ff,
4295 4295
           'pageid' => $this->currentPage,
4296
-          'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
4296
+          'da' => "$color /F$this->currentFontNum ".sprintf('%.1F Tf ', $size)
4297 4297
         ]);
4298 4298
 
4299 4299
         return $fieldId;
@@ -4641,17 +4641,17 @@  discard block
 block discarded – undo
4641 4641
             die("Unable to stream pdf: headers already sent");
4642 4642
         }
4643 4643
 
4644
-        if (!isset($options["compress"])) $options["compress"] = true;
4645
-        if (!isset($options["Attachment"])) $options["Attachment"] = true;
4644
+        if ( ! isset($options["compress"])) $options["compress"] = true;
4645
+        if ( ! isset($options["Attachment"])) $options["Attachment"] = true;
4646 4646
 
4647
-        $debug = !$options['compress'];
4647
+        $debug = ! $options['compress'];
4648 4648
         $tmp = ltrim($this->output($debug));
4649 4649
 
4650 4650
         header("Cache-Control: private");
4651 4651
         header("Content-Type: application/pdf");
4652
-        header("Content-Length: " . mb_strlen($tmp, "8bit"));
4652
+        header("Content-Length: ".mb_strlen($tmp, "8bit"));
4653 4653
 
4654
-        $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
4654
+        $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")).".pdf";
4655 4655
         $attachment = $options["Attachment"] ? "attachment" : "inline";
4656 4656
 
4657 4657
         $encoding = mb_detect_encoding($filename);
@@ -4677,7 +4677,7 @@  discard block
 block discarded – undo
4677 4677
      */
4678 4678
     function getFontHeight($size)
4679 4679
     {
4680
-        if (!$this->numFonts) {
4680
+        if ( ! $this->numFonts) {
4681 4681
             $this->selectFont($this->defaultFont);
4682 4682
         }
4683 4683
 
@@ -4700,7 +4700,7 @@  discard block
 block discarded – undo
4700 4700
             // Courier font.
4701 4701
             //
4702 4702
             // Both have been added manually to the .afm and .ufm files.
4703
-            $h += (int)$font['FontHeightOffset'];
4703
+            $h += (int) $font['FontHeightOffset'];
4704 4704
         }
4705 4705
 
4706 4706
         return $size * $h / 1000;
@@ -4712,7 +4712,7 @@  discard block
 block discarded – undo
4712 4712
      */
4713 4713
     function getFontXHeight($size)
4714 4714
     {
4715
-        if (!$this->numFonts) {
4715
+        if ( ! $this->numFonts) {
4716 4716
             $this->selectFont($this->defaultFont);
4717 4717
         }
4718 4718
 
@@ -4739,7 +4739,7 @@  discard block
 block discarded – undo
4739 4739
     function getFontDescender($size)
4740 4740
     {
4741 4741
         // note that this will most likely return a negative value
4742
-        if (!$this->numFonts) {
4742
+        if ( ! $this->numFonts) {
4743 4743
             $this->selectFont($this->defaultFont);
4744 4744
         }
4745 4745
 
@@ -4762,7 +4762,7 @@  discard block
 block discarded – undo
4762 4762
      */
4763 4763
     function filterText($text, $bom = true, $convert_encoding = true)
4764 4764
     {
4765
-        if (!$this->numFonts) {
4765
+        if ( ! $this->numFonts) {
4766 4766
             $this->selectFont($this->defaultFont);
4767 4767
         }
4768 4768
 
@@ -4882,12 +4882,12 @@  discard block
 block discarded – undo
4882 4882
             if ($c === 0xFFFD) {
4883 4883
                 $out .= "\xFF\xFD"; // replacement character
4884 4884
             } elseif ($c < 0x10000) {
4885
-                $out .= chr($c >> 0x08) . chr($c & 0xFF);
4885
+                $out .= chr($c >> 0x08).chr($c & 0xFF);
4886 4886
             } else {
4887 4887
                 $c -= 0x10000;
4888 4888
                 $w1 = 0xD800 | ($c >> 0x10);
4889 4889
                 $w2 = 0xDC00 | ($c & 0x3FF);
4890
-                $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4890
+                $out .= chr($w1 >> 0x08).chr($w1 & 0xFF).chr($w2 >> 0x08).chr($w2 & 0xFF);
4891 4891
             }
4892 4892
         }
4893 4893
 
@@ -4915,7 +4915,7 @@  discard block
 block discarded – undo
4915 4915
         $words = explode(' ', $text);
4916 4916
         $nspaces = count($words) - 1;
4917 4917
         $w += $wa * $nspaces;
4918
-        $a = deg2rad((float)$angle);
4918
+        $a = deg2rad((float) $angle);
4919 4919
 
4920 4920
         return [cos($a) * $w + $x, -sin($a) * $w + $y];
4921 4921
     }
@@ -4950,11 +4950,11 @@  discard block
 block discarded – undo
4950 4950
      */
4951 4951
     function registerText($font, $text)
4952 4952
     {
4953
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4953
+        if ( ! $this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
4954 4954
             return;
4955 4955
         }
4956 4956
 
4957
-        if (!isset($this->stringSubsets[$font])) {
4957
+        if ( ! isset($this->stringSubsets[$font])) {
4958 4958
             $this->stringSubsets[$font] = [];
4959 4959
         }
4960 4960
 
@@ -4977,7 +4977,7 @@  discard block
 block discarded – undo
4977 4977
      */
4978 4978
     function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
4979 4979
     {
4980
-        if (!$this->numFonts) {
4980
+        if ( ! $this->numFonts) {
4981 4981
             $this->selectFont($this->defaultFont);
4982 4982
         }
4983 4983
 
@@ -5018,7 +5018,7 @@  discard block
 block discarded – undo
5018 5018
         if ($angle == 0) {
5019 5019
             $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
5020 5020
         } else {
5021
-            $a = deg2rad((float)$angle);
5021
+            $a = deg2rad((float) $angle);
5022 5022
             $this->addContent(
5023 5023
                 sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
5024 5024
             );
@@ -5041,9 +5041,9 @@  discard block
 block discarded – undo
5041 5041
             // modify unicode text so that extra word spacing is manually implemented (bug #)
5042 5042
             if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
5043 5043
                 $space_scale = 1000 / $size;
5044
-                $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
5044
+                $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20".(-round($space_scale * $wordSpaceAdjust))."\x00\x20(", $place_text);
5045 5045
             }
5046
-            $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
5046
+            $this->addContent(" /F$this->currentFontNum ".sprintf('%.1F Tf ', $size));
5047 5047
             $this->addContent(" [($place_text)] TJ");
5048 5048
         }
5049 5049
 
@@ -5101,7 +5101,7 @@  discard block
 block discarded – undo
5101 5101
         // and put them back at the end.
5102 5102
         $store_currentTextState = $this->currentTextState;
5103 5103
 
5104
-        if (!$this->numFonts) {
5104
+        if ( ! $this->numFonts) {
5105 5105
             $this->selectFont($this->defaultFont);
5106 5106
         }
5107 5107
 
@@ -5205,7 +5205,7 @@  discard block
 block discarded – undo
5205 5205
             // ok to use this as stack starts numbering at 1
5206 5206
             $this->setColor($opt['col'], true);
5207 5207
             $this->setStrokeColor($opt['str'], true);
5208
-            $this->addContent("\n" . $opt['lin']);
5208
+            $this->addContent("\n".$opt['lin']);
5209 5209
             //    $this->currentLineStyle = $opt['lin'];
5210 5210
         } else {
5211 5211
             $this->nStateStack++;
@@ -5226,11 +5226,11 @@  discard block
 block discarded – undo
5226 5226
      */
5227 5227
     function restoreState($pageEnd = 0)
5228 5228
     {
5229
-        if (!$pageEnd) {
5229
+        if ( ! $pageEnd) {
5230 5230
             $n = $this->nStateStack;
5231 5231
             $this->currentColor = $this->stateStack[$n]['col'];
5232 5232
             $this->currentStrokeColor = $this->stateStack[$n]['str'];
5233
-            $this->addContent("\n" . $this->stateStack[$n]['lin']);
5233
+            $this->addContent("\n".$this->stateStack[$n]['lin']);
5234 5234
             $this->currentLineStyle = $this->stateStack[$n]['lin'];
5235 5235
             $this->stateStack[$n] = null;
5236 5236
             unset($this->stateStack[$n]);
@@ -5504,7 +5504,7 @@  discard block
 block discarded – undo
5504 5504
      */
5505 5505
     function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5506 5506
     {
5507
-        if (!function_exists("imagepng")) {
5507
+        if ( ! function_exists("imagepng")) {
5508 5508
             throw new \Exception("The PHP GD extension is required, but is not installed.");
5509 5509
         }
5510 5510
 
@@ -5530,7 +5530,7 @@  discard block
 block discarded – undo
5530 5530
             //DEBUG_IMG_TEMP
5531 5531
             //debugpng
5532 5532
             if (defined("DEBUGPNG") && DEBUGPNG) {
5533
-                print '[addImagePng ' . $file . ']';
5533
+                print '[addImagePng '.$file.']';
5534 5534
             }
5535 5535
 
5536 5536
             ob_start();
@@ -5548,7 +5548,7 @@  discard block
 block discarded – undo
5548 5548
             }
5549 5549
 
5550 5550
             if ($error) {
5551
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5551
+                $this->addMessage('PNG error - ('.$file.') '.$errormsg);
5552 5552
 
5553 5553
                 return;
5554 5554
             }
@@ -5699,9 +5699,9 @@  discard block
 block discarded – undo
5699 5699
                         // without gamma correction
5700 5700
                         $pixel = (127 - $alpha) * 2;
5701 5701
 
5702
-                        $key = $col['red'] . $col['green'] . $col['blue'];
5702
+                        $key = $col['red'].$col['green'].$col['blue'];
5703 5703
 
5704
-                        if (!isset($allocated_colors[$key])) {
5704
+                        if ( ! isset($allocated_colors[$key])) {
5705 5705
                             $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
5706 5706
                             $allocated_colors[$key] = $pixel_img;
5707 5707
                         } else {
@@ -5752,7 +5752,7 @@  discard block
 block discarded – undo
5752 5752
      */
5753 5753
     function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
5754 5754
     {
5755
-        if (!function_exists("imagecreatefrompng")) {
5755
+        if ( ! function_exists("imagecreatefrompng")) {
5756 5756
             throw new \Exception("The PHP GD extension is required, but is not installed.");
5757 5757
         }
5758 5758
 
@@ -5800,7 +5800,7 @@  discard block
 block discarded – undo
5800 5800
             //Therefore create an empty image with white background and merge the
5801 5801
             //image in with alpha blending.
5802 5802
             $imgtmp = @imagecreatefrompng($file);
5803
-            if (!$imgtmp) {
5803
+            if ( ! $imgtmp) {
5804 5804
                 return;
5805 5805
             }
5806 5806
             $sx = imagesx($imgtmp);
@@ -5857,21 +5857,21 @@  discard block
 block discarded – undo
5857 5857
 
5858 5858
             $error = 0;
5859 5859
 
5860
-            if (!$error) {
5861
-                $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5860
+            if ( ! $error) {
5861
+                $header = chr(137).chr(80).chr(78).chr(71).chr(13).chr(10).chr(26).chr(10);
5862 5862
 
5863 5863
                 if (mb_substr($data, 0, 8, '8bit') != $header) {
5864 5864
                     $error = 1;
5865 5865
 
5866 5866
                     if (defined("DEBUGPNG") && DEBUGPNG) {
5867
-                        print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5867
+                        print '[addPngFromFile this file does not have a valid header '.$file.']';
5868 5868
                     }
5869 5869
 
5870 5870
                     $errormsg = 'this file does not have a valid header';
5871 5871
                 }
5872 5872
             }
5873 5873
 
5874
-            if (!$error) {
5874
+            if ( ! $error) {
5875 5875
                 // set pointer
5876 5876
                 $p = 8;
5877 5877
                 $len = mb_strlen($data, '8bit');
@@ -5904,7 +5904,7 @@  discard block
 block discarded – undo
5904 5904
 
5905 5905
                                 //debugpng
5906 5906
                                 if (defined("DEBUGPNG") && DEBUGPNG) {
5907
-                                    print '[addPngFromFile unsupported compression method ' . $file . ']';
5907
+                                    print '[addPngFromFile unsupported compression method '.$file.']';
5908 5908
                                 }
5909 5909
 
5910 5910
                                 $errormsg = 'unsupported compression method';
@@ -5915,7 +5915,7 @@  discard block
 block discarded – undo
5915 5915
 
5916 5916
                                 //debugpng
5917 5917
                                 if (defined("DEBUGPNG") && DEBUGPNG) {
5918
-                                    print '[addPngFromFile unsupported filter method ' . $file . ']';
5918
+                                    print '[addPngFromFile unsupported filter method '.$file.']';
5919 5919
                                 }
5920 5920
 
5921 5921
                                 $errormsg = 'unsupported filter method';
@@ -5987,7 +5987,7 @@  discard block
 block discarded – undo
5987 5987
                                 //unsupported transparency type
5988 5988
                                 default:
5989 5989
                                     if (defined("DEBUGPNG") && DEBUGPNG) {
5990
-                                        print '[addPngFromFile unsupported transparency type ' . $file . ']';
5990
+                                        print '[addPngFromFile unsupported transparency type '.$file.']';
5991 5991
                                     }
5992 5992
                                     break;
5993 5993
                             }
@@ -6002,12 +6002,12 @@  discard block
 block discarded – undo
6002 6002
                     $p += $chunkLen + 12;
6003 6003
                 }
6004 6004
 
6005
-                if (!$haveHeader) {
6005
+                if ( ! $haveHeader) {
6006 6006
                     $error = 1;
6007 6007
 
6008 6008
                     //debugpng
6009 6009
                     if (defined("DEBUGPNG") && DEBUGPNG) {
6010
-                        print '[addPngFromFile information header is missing ' . $file . ']';
6010
+                        print '[addPngFromFile information header is missing '.$file.']';
6011 6011
                     }
6012 6012
 
6013 6013
                     $errormsg = 'information header is missing';
@@ -6018,25 +6018,25 @@  discard block
 block discarded – undo
6018 6018
 
6019 6019
                     //debugpng
6020 6020
                     if (defined("DEBUGPNG") && DEBUGPNG) {
6021
-                        print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
6021
+                        print '[addPngFromFile no support for interlaced images in pdf '.$file.']';
6022 6022
                     }
6023 6023
 
6024 6024
                     $errormsg = 'There appears to be no support for interlaced images in pdf.';
6025 6025
                 }
6026 6026
             }
6027 6027
 
6028
-            if (!$error && $info['bitDepth'] > 8) {
6028
+            if ( ! $error && $info['bitDepth'] > 8) {
6029 6029
                 $error = 1;
6030 6030
 
6031 6031
                 //debugpng
6032 6032
                 if (defined("DEBUGPNG") && DEBUGPNG) {
6033
-                    print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
6033
+                    print '[addPngFromFile bit depth of 8 or less is supported '.$file.']';
6034 6034
                 }
6035 6035
 
6036 6036
                 $errormsg = 'only bit depth of 8 or less is supported';
6037 6037
             }
6038 6038
 
6039
-            if (!$error) {
6039
+            if ( ! $error) {
6040 6040
                 switch ($info['colorType']) {
6041 6041
                     case 3:
6042 6042
                         $color = 'DeviceRGB';
@@ -6058,7 +6058,7 @@  discard block
 block discarded – undo
6058 6058
 
6059 6059
                         //debugpng
6060 6060
                         if (defined("DEBUGPNG") && DEBUGPNG) {
6061
-                            print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
6061
+                            print '[addPngFromFile alpha channel not supported: '.$info['colorType'].' '.$file.']';
6062 6062
                         }
6063 6063
 
6064 6064
                         $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
@@ -6066,7 +6066,7 @@  discard block
 block discarded – undo
6066 6066
             }
6067 6067
 
6068 6068
             if ($error) {
6069
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
6069
+                $this->addMessage('PNG error - ('.$file.') '.$errormsg);
6070 6070
 
6071 6071
                 return;
6072 6072
             }
@@ -6135,7 +6135,7 @@  discard block
 block discarded – undo
6135 6135
         // attempt to add a jpeg image straight from a file, using no GD commands
6136 6136
         // note that this function is unable to operate on a remote file.
6137 6137
 
6138
-        if (!file_exists($img)) {
6138
+        if ( ! file_exists($img)) {
6139 6139
             return;
6140 6140
         }
6141 6141
 
@@ -6203,7 +6203,7 @@  discard block
 block discarded – undo
6203 6203
 
6204 6204
         } else {
6205 6205
             if ($data == null) {
6206
-                $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
6206
+                $this->addMessage('addJpegImage_common error - ('.$imgname.') data not present!');
6207 6207
 
6208 6208
                 return;
6209 6209
             }
@@ -6314,7 +6314,7 @@  discard block
 block discarded – undo
6314 6314
      */
6315 6315
     function setFontFamily($family, $options = '')
6316 6316
     {
6317
-        if (!is_array($options)) {
6317
+        if ( ! is_array($options)) {
6318 6318
             if ($family === 'init') {
6319 6319
                 // set the known family groups
6320 6320
                 // these font families will be used to enable bold and italic markers to be included
@@ -6360,7 +6360,7 @@  discard block
 block discarded – undo
6360 6360
      */
6361 6361
     function addMessage($message)
6362 6362
     {
6363
-        $this->messages .= $message . "\n";
6363
+        $this->messages .= $message."\n";
6364 6364
     }
6365 6365
 
6366 6366
     /**
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Surface/SurfaceCpdf.php 3 patches
Indentation   +477 added lines, -477 removed lines patch added patch discarded remove patch
@@ -13,483 +13,483 @@
 block discarded – undo
13 13
 
14 14
 class SurfaceCpdf implements SurfaceInterface
15 15
 {
16
-    const DEBUG = false;
17
-
18
-    /** @var \Svg\Surface\CPdf */
19
-    private $canvas;
20
-
21
-    private $width;
22
-    private $height;
23
-
24
-    /** @var Style */
25
-    private $style;
26
-
27
-    public function __construct(Document $doc, $canvas = null)
28
-    {
29
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
30
-
31
-        $dimensions = $doc->getDimensions();
32
-        $w = $dimensions["width"];
33
-        $h = $dimensions["height"];
34
-
35
-        if (!$canvas) {
36
-            $canvas = new \Svg\Surface\CPdf(array(0, 0, $w, $h));
37
-            $refl = new \ReflectionClass($canvas);
38
-            $canvas->fontcache = realpath(dirname($refl->getFileName()) . "/../../fonts/")."/";
39
-        }
40
-
41
-        // Flip PDF coordinate system so that the origin is in
42
-        // the top left rather than the bottom left
43
-        $canvas->transform(array(
44
-            1,  0,
45
-            0, -1,
46
-            0, $h
47
-        ));
48
-
49
-        $this->width  = $w;
50
-        $this->height = $h;
51
-
52
-        $this->canvas = $canvas;
53
-    }
54
-
55
-    function out()
56
-    {
57
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
58
-        return $this->canvas->output();
59
-    }
60
-
61
-    public function save()
62
-    {
63
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
64
-        $this->canvas->save();
65
-    }
66
-
67
-    public function restore()
68
-    {
69
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
70
-        $this->canvas->restore();
71
-    }
72
-
73
-    public function scale($x, $y)
74
-    {
75
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
76
-
77
-        $this->transform($x, 0, 0, $y, 0, 0);
78
-    }
79
-
80
-    public function rotate($angle)
81
-    {
82
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
83
-
84
-        $a = deg2rad($angle);
85
-        $cos_a = cos($a);
86
-        $sin_a = sin($a);
87
-
88
-        $this->transform(
89
-            $cos_a,                         $sin_a,
90
-            -$sin_a,                         $cos_a,
91
-            0, 0
92
-        );
93
-    }
94
-
95
-    public function translate($x, $y)
96
-    {
97
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
98
-
99
-        $this->transform(
100
-            1,  0,
101
-            0,  1,
102
-            $x, $y
103
-        );
104
-    }
105
-
106
-    public function transform($a, $b, $c, $d, $e, $f)
107
-    {
108
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
109
-
110
-        $this->canvas->transform(array($a, $b, $c, $d, $e, $f));
111
-    }
112
-
113
-    public function beginPath()
114
-    {
115
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
116
-        // TODO: Implement beginPath() method.
117
-    }
118
-
119
-    public function closePath()
120
-    {
121
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
122
-        $this->canvas->closePath();
123
-    }
124
-
125
-    public function fillStroke(bool $close = false)
126
-    {
127
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
128
-        $this->canvas->fillStroke($close);
129
-    }
130
-
131
-    public function clip()
132
-    {
133
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
134
-        $this->canvas->clip();
135
-    }
136
-
137
-    public function fillText($text, $x, $y, $maxWidth = null)
138
-    {
139
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
140
-        $this->canvas->addText($x, $y, $this->style->fontSize, $text);
141
-    }
142
-
143
-    public function strokeText($text, $x, $y, $maxWidth = null)
144
-    {
145
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
146
-        $this->canvas->addText($x, $y, $this->style->fontSize, $text);
147
-    }
148
-
149
-    public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null)
150
-    {
151
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
152
-
153
-        if (strpos($image, "data:") === 0) {
154
-            $parts = explode(',', $image, 2);
155
-
156
-            $data = $parts[1];
157
-            $base64 = false;
158
-
159
-            $token = strtok($parts[0], ';');
160
-            while ($token !== false) {
161
-                if ($token == 'base64') {
162
-                    $base64 = true;
163
-                }
164
-
165
-                $token = strtok(';');
166
-            }
167
-
168
-            if ($base64) {
169
-                $data = base64_decode($data);
170
-            }
171
-        }
172
-        else {
173
-            $data = file_get_contents($image);
174
-        }
175
-
176
-        $image = tempnam(sys_get_temp_dir(), "svg");
177
-        file_put_contents($image, $data);
178
-
179
-        $img = $this->image($image, $sx, $sy, $sw, $sh, "normal");
180
-
181
-
182
-        unlink($image);
183
-    }
184
-
185
-    public static function getimagesize($filename)
186
-    {
187
-        static $cache = array();
188
-
189
-        if (isset($cache[$filename])) {
190
-            return $cache[$filename];
191
-        }
192
-
193
-        list($width, $height, $type) = getimagesize($filename);
194
-
195
-        if ($width == null || $height == null) {
196
-            $data = file_get_contents($filename, null, null, 0, 26);
197
-
198
-            if (substr($data, 0, 2) === "BM") {
199
-                $meta = unpack('vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight', $data);
200
-                $width = (int)$meta['width'];
201
-                $height = (int)$meta['height'];
202
-                $type = IMAGETYPE_BMP;
203
-            }
204
-        }
205
-
206
-        return $cache[$filename] = array($width, $height, $type);
207
-    }
208
-
209
-    function image($img, $x, $y, $w, $h, $resolution = "normal")
210
-    {
211
-        list($width, $height, $type) = $this->getimagesize($img);
212
-
213
-        switch ($type) {
214
-            case IMAGETYPE_JPEG:
215
-                $this->canvas->addJpegFromFile($img, $x, $y - $h, $w, $h);
216
-                break;
217
-
218
-            case IMAGETYPE_GIF:
219
-            case IMAGETYPE_BMP:
220
-                // @todo use cache for BMP and GIF
221
-                $img = $this->_convert_gif_bmp_to_png($img, $type);
222
-
223
-            case IMAGETYPE_PNG:
224
-                $this->canvas->addPngFromFile($img, $x, $y - $h, $w, $h);
225
-                break;
226
-
227
-            default:
228
-        }
229
-    }
230
-
231
-    public function lineTo($x, $y)
232
-    {
233
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
234
-        $this->canvas->lineTo($x, $y);
235
-    }
236
-
237
-    public function moveTo($x, $y)
238
-    {
239
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
240
-        $this->canvas->moveTo($x, $y);
241
-    }
242
-
243
-    public function quadraticCurveTo($cpx, $cpy, $x, $y)
244
-    {
245
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
246
-
247
-        // FIXME not accurate
248
-        $this->canvas->quadTo($cpx, $cpy, $x, $y);
249
-    }
250
-
251
-    public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y)
252
-    {
253
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
254
-        $this->canvas->curveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y);
255
-    }
256
-
257
-    public function arcTo($x1, $y1, $x2, $y2, $radius)
258
-    {
259
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
260
-    }
261
-
262
-    public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false)
263
-    {
264
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
265
-        $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, $startAngle, $endAngle, false, false, false, true);
266
-    }
267
-
268
-    public function circle($x, $y, $radius)
269
-    {
270
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
271
-        $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, 0, 360, true, false, false, false);
272
-    }
273
-
274
-    public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise)
275
-    {
276
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
277
-        $this->canvas->ellipse($x, $y, $radiusX, $radiusY, 0, 8, 0, 360, false, false, false, false);
278
-    }
279
-
280
-    public function fillRect($x, $y, $w, $h)
281
-    {
282
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
283
-        $this->rect($x, $y, $w, $h);
284
-        $this->fill();
285
-    }
286
-
287
-    public function rect($x, $y, $w, $h, $rx = 0, $ry = 0)
288
-    {
289
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
290
-
291
-        $canvas = $this->canvas;
292
-
293
-        if ($rx <= 0.000001/* && $ry <= 0.000001*/) {
294
-            $canvas->rect($x, $y, $w, $h);
295
-
296
-            return;
297
-        }
298
-
299
-        $rx = min($rx, $w / 2);
300
-        $rx = min($rx, $h / 2);
301
-
302
-        /* Define a path for a rectangle with corners rounded by a given radius.
16
+	const DEBUG = false;
17
+
18
+	/** @var \Svg\Surface\CPdf */
19
+	private $canvas;
20
+
21
+	private $width;
22
+	private $height;
23
+
24
+	/** @var Style */
25
+	private $style;
26
+
27
+	public function __construct(Document $doc, $canvas = null)
28
+	{
29
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
30
+
31
+		$dimensions = $doc->getDimensions();
32
+		$w = $dimensions["width"];
33
+		$h = $dimensions["height"];
34
+
35
+		if (!$canvas) {
36
+			$canvas = new \Svg\Surface\CPdf(array(0, 0, $w, $h));
37
+			$refl = new \ReflectionClass($canvas);
38
+			$canvas->fontcache = realpath(dirname($refl->getFileName()) . "/../../fonts/")."/";
39
+		}
40
+
41
+		// Flip PDF coordinate system so that the origin is in
42
+		// the top left rather than the bottom left
43
+		$canvas->transform(array(
44
+			1,  0,
45
+			0, -1,
46
+			0, $h
47
+		));
48
+
49
+		$this->width  = $w;
50
+		$this->height = $h;
51
+
52
+		$this->canvas = $canvas;
53
+	}
54
+
55
+	function out()
56
+	{
57
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
58
+		return $this->canvas->output();
59
+	}
60
+
61
+	public function save()
62
+	{
63
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
64
+		$this->canvas->save();
65
+	}
66
+
67
+	public function restore()
68
+	{
69
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
70
+		$this->canvas->restore();
71
+	}
72
+
73
+	public function scale($x, $y)
74
+	{
75
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
76
+
77
+		$this->transform($x, 0, 0, $y, 0, 0);
78
+	}
79
+
80
+	public function rotate($angle)
81
+	{
82
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
83
+
84
+		$a = deg2rad($angle);
85
+		$cos_a = cos($a);
86
+		$sin_a = sin($a);
87
+
88
+		$this->transform(
89
+			$cos_a,                         $sin_a,
90
+			-$sin_a,                         $cos_a,
91
+			0, 0
92
+		);
93
+	}
94
+
95
+	public function translate($x, $y)
96
+	{
97
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
98
+
99
+		$this->transform(
100
+			1,  0,
101
+			0,  1,
102
+			$x, $y
103
+		);
104
+	}
105
+
106
+	public function transform($a, $b, $c, $d, $e, $f)
107
+	{
108
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
109
+
110
+		$this->canvas->transform(array($a, $b, $c, $d, $e, $f));
111
+	}
112
+
113
+	public function beginPath()
114
+	{
115
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
116
+		// TODO: Implement beginPath() method.
117
+	}
118
+
119
+	public function closePath()
120
+	{
121
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
122
+		$this->canvas->closePath();
123
+	}
124
+
125
+	public function fillStroke(bool $close = false)
126
+	{
127
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
128
+		$this->canvas->fillStroke($close);
129
+	}
130
+
131
+	public function clip()
132
+	{
133
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
134
+		$this->canvas->clip();
135
+	}
136
+
137
+	public function fillText($text, $x, $y, $maxWidth = null)
138
+	{
139
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
140
+		$this->canvas->addText($x, $y, $this->style->fontSize, $text);
141
+	}
142
+
143
+	public function strokeText($text, $x, $y, $maxWidth = null)
144
+	{
145
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
146
+		$this->canvas->addText($x, $y, $this->style->fontSize, $text);
147
+	}
148
+
149
+	public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null)
150
+	{
151
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
152
+
153
+		if (strpos($image, "data:") === 0) {
154
+			$parts = explode(',', $image, 2);
155
+
156
+			$data = $parts[1];
157
+			$base64 = false;
158
+
159
+			$token = strtok($parts[0], ';');
160
+			while ($token !== false) {
161
+				if ($token == 'base64') {
162
+					$base64 = true;
163
+				}
164
+
165
+				$token = strtok(';');
166
+			}
167
+
168
+			if ($base64) {
169
+				$data = base64_decode($data);
170
+			}
171
+		}
172
+		else {
173
+			$data = file_get_contents($image);
174
+		}
175
+
176
+		$image = tempnam(sys_get_temp_dir(), "svg");
177
+		file_put_contents($image, $data);
178
+
179
+		$img = $this->image($image, $sx, $sy, $sw, $sh, "normal");
180
+
181
+
182
+		unlink($image);
183
+	}
184
+
185
+	public static function getimagesize($filename)
186
+	{
187
+		static $cache = array();
188
+
189
+		if (isset($cache[$filename])) {
190
+			return $cache[$filename];
191
+		}
192
+
193
+		list($width, $height, $type) = getimagesize($filename);
194
+
195
+		if ($width == null || $height == null) {
196
+			$data = file_get_contents($filename, null, null, 0, 26);
197
+
198
+			if (substr($data, 0, 2) === "BM") {
199
+				$meta = unpack('vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight', $data);
200
+				$width = (int)$meta['width'];
201
+				$height = (int)$meta['height'];
202
+				$type = IMAGETYPE_BMP;
203
+			}
204
+		}
205
+
206
+		return $cache[$filename] = array($width, $height, $type);
207
+	}
208
+
209
+	function image($img, $x, $y, $w, $h, $resolution = "normal")
210
+	{
211
+		list($width, $height, $type) = $this->getimagesize($img);
212
+
213
+		switch ($type) {
214
+			case IMAGETYPE_JPEG:
215
+				$this->canvas->addJpegFromFile($img, $x, $y - $h, $w, $h);
216
+				break;
217
+
218
+			case IMAGETYPE_GIF:
219
+			case IMAGETYPE_BMP:
220
+				// @todo use cache for BMP and GIF
221
+				$img = $this->_convert_gif_bmp_to_png($img, $type);
222
+
223
+			case IMAGETYPE_PNG:
224
+				$this->canvas->addPngFromFile($img, $x, $y - $h, $w, $h);
225
+				break;
226
+
227
+			default:
228
+		}
229
+	}
230
+
231
+	public function lineTo($x, $y)
232
+	{
233
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
234
+		$this->canvas->lineTo($x, $y);
235
+	}
236
+
237
+	public function moveTo($x, $y)
238
+	{
239
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
240
+		$this->canvas->moveTo($x, $y);
241
+	}
242
+
243
+	public function quadraticCurveTo($cpx, $cpy, $x, $y)
244
+	{
245
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
246
+
247
+		// FIXME not accurate
248
+		$this->canvas->quadTo($cpx, $cpy, $x, $y);
249
+	}
250
+
251
+	public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y)
252
+	{
253
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
254
+		$this->canvas->curveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y);
255
+	}
256
+
257
+	public function arcTo($x1, $y1, $x2, $y2, $radius)
258
+	{
259
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
260
+	}
261
+
262
+	public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false)
263
+	{
264
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
265
+		$this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, $startAngle, $endAngle, false, false, false, true);
266
+	}
267
+
268
+	public function circle($x, $y, $radius)
269
+	{
270
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
271
+		$this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, 0, 360, true, false, false, false);
272
+	}
273
+
274
+	public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise)
275
+	{
276
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
277
+		$this->canvas->ellipse($x, $y, $radiusX, $radiusY, 0, 8, 0, 360, false, false, false, false);
278
+	}
279
+
280
+	public function fillRect($x, $y, $w, $h)
281
+	{
282
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
283
+		$this->rect($x, $y, $w, $h);
284
+		$this->fill();
285
+	}
286
+
287
+	public function rect($x, $y, $w, $h, $rx = 0, $ry = 0)
288
+	{
289
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
290
+
291
+		$canvas = $this->canvas;
292
+
293
+		if ($rx <= 0.000001/* && $ry <= 0.000001*/) {
294
+			$canvas->rect($x, $y, $w, $h);
295
+
296
+			return;
297
+		}
298
+
299
+		$rx = min($rx, $w / 2);
300
+		$rx = min($rx, $h / 2);
301
+
302
+		/* Define a path for a rectangle with corners rounded by a given radius.
303 303
          * Start from the lower left corner and proceed counterclockwise.
304 304
          */
305
-        $this->moveTo($x + $rx, $y);
306
-
307
-        /* Start of the arc segment in the lower right corner */
308
-        $this->lineTo($x + $w - $rx, $y);
309
-
310
-        /* Arc segment in the lower right corner */
311
-        $this->arc($x + $w - $rx, $y + $rx, $rx, 270, 360);
312
-
313
-        /* Start of the arc segment in the upper right corner */
314
-        $this->lineTo($x + $w, $y + $h - $rx );
315
-
316
-        /* Arc segment in the upper right corner */
317
-        $this->arc($x + $w - $rx, $y + $h - $rx, $rx, 0, 90);
318
-
319
-        /* Start of the arc segment in the upper left corner */
320
-        $this->lineTo($x + $rx, $y + $h);
321
-
322
-        /* Arc segment in the upper left corner */
323
-        $this->arc($x + $rx, $y + $h - $rx, $rx, 90, 180);
324
-
325
-        /* Start of the arc segment in the lower left corner */
326
-        $this->lineTo($x , $y + $rx);
327
-
328
-        /* Arc segment in the lower left corner */
329
-        $this->arc($x + $rx, $y + $rx, $rx, 180, 270);
330
-    }
331
-
332
-    public function fill()
333
-    {
334
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
335
-        $this->canvas->fill();
336
-    }
337
-
338
-    public function strokeRect($x, $y, $w, $h)
339
-    {
340
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
341
-        $this->rect($x, $y, $w, $h);
342
-        $this->stroke();
343
-    }
344
-
345
-    public function stroke(bool $close = false)
346
-    {
347
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
348
-        $this->canvas->stroke($close);
349
-    }
350
-
351
-    public function endPath()
352
-    {
353
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
354
-        $this->canvas->endPath();
355
-    }
356
-
357
-    public function measureText($text)
358
-    {
359
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
360
-        $style = $this->getStyle();
361
-        $this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight);
362
-
363
-        return $this->canvas->getTextWidth($this->getStyle()->fontSize, $text);
364
-    }
365
-
366
-    public function getStyle()
367
-    {
368
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
369
-        return $this->style;
370
-    }
371
-
372
-    public function setStyle(Style $style)
373
-    {
374
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
375
-
376
-        $this->style = $style;
377
-        $canvas = $this->canvas;
378
-
379
-        if (is_array($style->stroke) && $stroke = $style->stroke) {
380
-            $canvas->setStrokeColor(array((float)$stroke[0]/255, (float)$stroke[1]/255, (float)$stroke[2]/255), true);
381
-        }
382
-
383
-        if (is_array($style->fill) && $fill = $style->fill) {
384
-            $canvas->setColor(array((float)$fill[0]/255, (float)$fill[1]/255, (float)$fill[2]/255), true);
385
-        }
386
-
387
-        if ($fillRule = strtolower($style->fillRule)) {
388
-            $canvas->setFillRule($fillRule);
389
-        }
390
-
391
-        $opacity = $style->opacity;
392
-        if ($opacity !== null && $opacity < 1.0) {
393
-            $canvas->setLineTransparency("Normal", $opacity);
394
-            $canvas->currentLineTransparency = null;
395
-
396
-            $canvas->setFillTransparency("Normal", $opacity);
397
-            $canvas->currentFillTransparency = null;
398
-        }
399
-        else {
400
-            $fillOpacity = $style->fillOpacity;
401
-            if ($fillOpacity !== null && $fillOpacity < 1.0) {
402
-                $canvas->setFillTransparency("Normal", $fillOpacity);
403
-                $canvas->currentFillTransparency = null;
404
-            }
405
-
406
-            $strokeOpacity = $style->strokeOpacity;
407
-            if ($strokeOpacity !== null && $strokeOpacity < 1.0) {
408
-                $canvas->setLineTransparency("Normal", $strokeOpacity);
409
-                $canvas->currentLineTransparency = null;
410
-            }
411
-        }
412
-
413
-        $dashArray = null;
414
-        if ($style->strokeDasharray) {
415
-            $dashArray = preg_split('/\s*,\s*/', $style->strokeDasharray);
416
-        }
417
-
418
-
419
-        $phase=0;
420
-        if ($style->strokeDashoffset) {
421
-           $phase = $style->strokeDashoffset;
422
-        }
423
-
424
-
425
-        $canvas->setLineStyle(
426
-            $style->strokeWidth,
427
-            $style->strokeLinecap,
428
-            $style->strokeLinejoin,
429
-            $dashArray,
430
-            $phase
431
-        );
432
-
433
-        $this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight);
434
-    }
435
-
436
-    public function setFont($family, $style, $weight)
437
-    {
438
-        $map = [
439
-            "serif"      => "times",
440
-            "sans-serif" => "helvetica",
441
-            "fantasy"    => "symbol",
442
-            "cursive"    => "times",
443
-            "monospace"  => "courier"
444
-        ];
445
-
446
-        $styleMap = [
447
-            "courier" => [
448
-                ""   => "Courier",
449
-                "b"  => "Courier-Bold",
450
-                "i"  => "Courier-Oblique",
451
-                "bi" => "Courier-BoldOblique",
452
-            ],
453
-            "helvetica" => [
454
-                ""   => "Helvetica",
455
-                "b"  => "Helvetica-Bold",
456
-                "i"  => "Helvetica-Oblique",
457
-                "bi" => "Helvetica-BoldOblique",
458
-            ],
459
-            "symbol" => [
460
-                "" => "Symbol"
461
-            ],
462
-            "times" => [
463
-                ""   => "Times-Roman",
464
-                "b"  => "Times-Bold",
465
-                "i"  => "Times-Italic",
466
-                "bi" => "Times-BoldItalic",
467
-            ],
468
-        ];
469
-
470
-        $family_lc = strtolower($family);
471
-        if (isset($map[$family_lc])) {
472
-            $family = $map[$family_lc];
473
-        }
474
-
475
-        if (isset($styleMap[$family])) {
476
-            $key = "";
477
-
478
-            $weight = strtolower($weight);
479
-            if ($weight === "bold" || $weight === "bolder" || (is_numeric($weight) && $weight >= 600)) {
480
-                $key .= "b";
481
-            }
482
-
483
-            $style = strtolower($style);
484
-            if ($style === "italic" || $style === "oblique") {
485
-                $key .= "i";
486
-            }
487
-
488
-            if (isset($styleMap[$family][$key])) {
489
-                $family = $styleMap[$family][$key];
490
-            }
491
-        }
492
-
493
-        $this->canvas->selectFont("$family.afm");
494
-    }
305
+		$this->moveTo($x + $rx, $y);
306
+
307
+		/* Start of the arc segment in the lower right corner */
308
+		$this->lineTo($x + $w - $rx, $y);
309
+
310
+		/* Arc segment in the lower right corner */
311
+		$this->arc($x + $w - $rx, $y + $rx, $rx, 270, 360);
312
+
313
+		/* Start of the arc segment in the upper right corner */
314
+		$this->lineTo($x + $w, $y + $h - $rx );
315
+
316
+		/* Arc segment in the upper right corner */
317
+		$this->arc($x + $w - $rx, $y + $h - $rx, $rx, 0, 90);
318
+
319
+		/* Start of the arc segment in the upper left corner */
320
+		$this->lineTo($x + $rx, $y + $h);
321
+
322
+		/* Arc segment in the upper left corner */
323
+		$this->arc($x + $rx, $y + $h - $rx, $rx, 90, 180);
324
+
325
+		/* Start of the arc segment in the lower left corner */
326
+		$this->lineTo($x , $y + $rx);
327
+
328
+		/* Arc segment in the lower left corner */
329
+		$this->arc($x + $rx, $y + $rx, $rx, 180, 270);
330
+	}
331
+
332
+	public function fill()
333
+	{
334
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
335
+		$this->canvas->fill();
336
+	}
337
+
338
+	public function strokeRect($x, $y, $w, $h)
339
+	{
340
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
341
+		$this->rect($x, $y, $w, $h);
342
+		$this->stroke();
343
+	}
344
+
345
+	public function stroke(bool $close = false)
346
+	{
347
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
348
+		$this->canvas->stroke($close);
349
+	}
350
+
351
+	public function endPath()
352
+	{
353
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
354
+		$this->canvas->endPath();
355
+	}
356
+
357
+	public function measureText($text)
358
+	{
359
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
360
+		$style = $this->getStyle();
361
+		$this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight);
362
+
363
+		return $this->canvas->getTextWidth($this->getStyle()->fontSize, $text);
364
+	}
365
+
366
+	public function getStyle()
367
+	{
368
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
369
+		return $this->style;
370
+	}
371
+
372
+	public function setStyle(Style $style)
373
+	{
374
+		if (self::DEBUG) echo __FUNCTION__ . "\n";
375
+
376
+		$this->style = $style;
377
+		$canvas = $this->canvas;
378
+
379
+		if (is_array($style->stroke) && $stroke = $style->stroke) {
380
+			$canvas->setStrokeColor(array((float)$stroke[0]/255, (float)$stroke[1]/255, (float)$stroke[2]/255), true);
381
+		}
382
+
383
+		if (is_array($style->fill) && $fill = $style->fill) {
384
+			$canvas->setColor(array((float)$fill[0]/255, (float)$fill[1]/255, (float)$fill[2]/255), true);
385
+		}
386
+
387
+		if ($fillRule = strtolower($style->fillRule)) {
388
+			$canvas->setFillRule($fillRule);
389
+		}
390
+
391
+		$opacity = $style->opacity;
392
+		if ($opacity !== null && $opacity < 1.0) {
393
+			$canvas->setLineTransparency("Normal", $opacity);
394
+			$canvas->currentLineTransparency = null;
395
+
396
+			$canvas->setFillTransparency("Normal", $opacity);
397
+			$canvas->currentFillTransparency = null;
398
+		}
399
+		else {
400
+			$fillOpacity = $style->fillOpacity;
401
+			if ($fillOpacity !== null && $fillOpacity < 1.0) {
402
+				$canvas->setFillTransparency("Normal", $fillOpacity);
403
+				$canvas->currentFillTransparency = null;
404
+			}
405
+
406
+			$strokeOpacity = $style->strokeOpacity;
407
+			if ($strokeOpacity !== null && $strokeOpacity < 1.0) {
408
+				$canvas->setLineTransparency("Normal", $strokeOpacity);
409
+				$canvas->currentLineTransparency = null;
410
+			}
411
+		}
412
+
413
+		$dashArray = null;
414
+		if ($style->strokeDasharray) {
415
+			$dashArray = preg_split('/\s*,\s*/', $style->strokeDasharray);
416
+		}
417
+
418
+
419
+		$phase=0;
420
+		if ($style->strokeDashoffset) {
421
+		   $phase = $style->strokeDashoffset;
422
+		}
423
+
424
+
425
+		$canvas->setLineStyle(
426
+			$style->strokeWidth,
427
+			$style->strokeLinecap,
428
+			$style->strokeLinejoin,
429
+			$dashArray,
430
+			$phase
431
+		);
432
+
433
+		$this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight);
434
+	}
435
+
436
+	public function setFont($family, $style, $weight)
437
+	{
438
+		$map = [
439
+			"serif"      => "times",
440
+			"sans-serif" => "helvetica",
441
+			"fantasy"    => "symbol",
442
+			"cursive"    => "times",
443
+			"monospace"  => "courier"
444
+		];
445
+
446
+		$styleMap = [
447
+			"courier" => [
448
+				""   => "Courier",
449
+				"b"  => "Courier-Bold",
450
+				"i"  => "Courier-Oblique",
451
+				"bi" => "Courier-BoldOblique",
452
+			],
453
+			"helvetica" => [
454
+				""   => "Helvetica",
455
+				"b"  => "Helvetica-Bold",
456
+				"i"  => "Helvetica-Oblique",
457
+				"bi" => "Helvetica-BoldOblique",
458
+			],
459
+			"symbol" => [
460
+				"" => "Symbol"
461
+			],
462
+			"times" => [
463
+				""   => "Times-Roman",
464
+				"b"  => "Times-Bold",
465
+				"i"  => "Times-Italic",
466
+				"bi" => "Times-BoldItalic",
467
+			],
468
+		];
469
+
470
+		$family_lc = strtolower($family);
471
+		if (isset($map[$family_lc])) {
472
+			$family = $map[$family_lc];
473
+		}
474
+
475
+		if (isset($styleMap[$family])) {
476
+			$key = "";
477
+
478
+			$weight = strtolower($weight);
479
+			if ($weight === "bold" || $weight === "bolder" || (is_numeric($weight) && $weight >= 600)) {
480
+				$key .= "b";
481
+			}
482
+
483
+			$style = strtolower($style);
484
+			if ($style === "italic" || $style === "oblique") {
485
+				$key .= "i";
486
+			}
487
+
488
+			if (isset($styleMap[$family][$key])) {
489
+				$family = $styleMap[$family][$key];
490
+			}
491
+		}
492
+
493
+		$this->canvas->selectFont("$family.afm");
494
+	}
495 495
 }
Please login to merge, or discard this patch.
Spacing   +46 added lines, -46 removed lines patch added patch discarded remove patch
@@ -26,22 +26,22 @@  discard block
 block discarded – undo
26 26
 
27 27
     public function __construct(Document $doc, $canvas = null)
28 28
     {
29
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
29
+        if (self::DEBUG) echo __FUNCTION__."\n";
30 30
 
31 31
         $dimensions = $doc->getDimensions();
32 32
         $w = $dimensions["width"];
33 33
         $h = $dimensions["height"];
34 34
 
35
-        if (!$canvas) {
35
+        if ( ! $canvas) {
36 36
             $canvas = new \Svg\Surface\CPdf(array(0, 0, $w, $h));
37 37
             $refl = new \ReflectionClass($canvas);
38
-            $canvas->fontcache = realpath(dirname($refl->getFileName()) . "/../../fonts/")."/";
38
+            $canvas->fontcache = realpath(dirname($refl->getFileName())."/../../fonts/")."/";
39 39
         }
40 40
 
41 41
         // Flip PDF coordinate system so that the origin is in
42 42
         // the top left rather than the bottom left
43 43
         $canvas->transform(array(
44
-            1,  0,
44
+            1, 0,
45 45
             0, -1,
46 46
             0, $h
47 47
         ));
@@ -54,101 +54,101 @@  discard block
 block discarded – undo
54 54
 
55 55
     function out()
56 56
     {
57
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
57
+        if (self::DEBUG) echo __FUNCTION__."\n";
58 58
         return $this->canvas->output();
59 59
     }
60 60
 
61 61
     public function save()
62 62
     {
63
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
63
+        if (self::DEBUG) echo __FUNCTION__."\n";
64 64
         $this->canvas->save();
65 65
     }
66 66
 
67 67
     public function restore()
68 68
     {
69
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
69
+        if (self::DEBUG) echo __FUNCTION__."\n";
70 70
         $this->canvas->restore();
71 71
     }
72 72
 
73 73
     public function scale($x, $y)
74 74
     {
75
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
75
+        if (self::DEBUG) echo __FUNCTION__."\n";
76 76
 
77 77
         $this->transform($x, 0, 0, $y, 0, 0);
78 78
     }
79 79
 
80 80
     public function rotate($angle)
81 81
     {
82
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
82
+        if (self::DEBUG) echo __FUNCTION__."\n";
83 83
 
84 84
         $a = deg2rad($angle);
85 85
         $cos_a = cos($a);
86 86
         $sin_a = sin($a);
87 87
 
88 88
         $this->transform(
89
-            $cos_a,                         $sin_a,
90
-            -$sin_a,                         $cos_a,
89
+            $cos_a, $sin_a,
90
+            -$sin_a, $cos_a,
91 91
             0, 0
92 92
         );
93 93
     }
94 94
 
95 95
     public function translate($x, $y)
96 96
     {
97
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
97
+        if (self::DEBUG) echo __FUNCTION__."\n";
98 98
 
99 99
         $this->transform(
100
-            1,  0,
101
-            0,  1,
100
+            1, 0,
101
+            0, 1,
102 102
             $x, $y
103 103
         );
104 104
     }
105 105
 
106 106
     public function transform($a, $b, $c, $d, $e, $f)
107 107
     {
108
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
108
+        if (self::DEBUG) echo __FUNCTION__."\n";
109 109
 
110 110
         $this->canvas->transform(array($a, $b, $c, $d, $e, $f));
111 111
     }
112 112
 
113 113
     public function beginPath()
114 114
     {
115
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
115
+        if (self::DEBUG) echo __FUNCTION__."\n";
116 116
         // TODO: Implement beginPath() method.
117 117
     }
118 118
 
119 119
     public function closePath()
120 120
     {
121
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
121
+        if (self::DEBUG) echo __FUNCTION__."\n";
122 122
         $this->canvas->closePath();
123 123
     }
124 124
 
125 125
     public function fillStroke(bool $close = false)
126 126
     {
127
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
127
+        if (self::DEBUG) echo __FUNCTION__."\n";
128 128
         $this->canvas->fillStroke($close);
129 129
     }
130 130
 
131 131
     public function clip()
132 132
     {
133
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
133
+        if (self::DEBUG) echo __FUNCTION__."\n";
134 134
         $this->canvas->clip();
135 135
     }
136 136
 
137 137
     public function fillText($text, $x, $y, $maxWidth = null)
138 138
     {
139
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
139
+        if (self::DEBUG) echo __FUNCTION__."\n";
140 140
         $this->canvas->addText($x, $y, $this->style->fontSize, $text);
141 141
     }
142 142
 
143 143
     public function strokeText($text, $x, $y, $maxWidth = null)
144 144
     {
145
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
145
+        if (self::DEBUG) echo __FUNCTION__."\n";
146 146
         $this->canvas->addText($x, $y, $this->style->fontSize, $text);
147 147
     }
148 148
 
149 149
     public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null)
150 150
     {
151
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
151
+        if (self::DEBUG) echo __FUNCTION__."\n";
152 152
 
153 153
         if (strpos($image, "data:") === 0) {
154 154
             $parts = explode(',', $image, 2);
@@ -197,8 +197,8 @@  discard block
 block discarded – undo
197 197
 
198 198
             if (substr($data, 0, 2) === "BM") {
199 199
                 $meta = unpack('vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight', $data);
200
-                $width = (int)$meta['width'];
201
-                $height = (int)$meta['height'];
200
+                $width = (int) $meta['width'];
201
+                $height = (int) $meta['height'];
202 202
                 $type = IMAGETYPE_BMP;
203 203
             }
204 204
         }
@@ -230,19 +230,19 @@  discard block
 block discarded – undo
230 230
 
231 231
     public function lineTo($x, $y)
232 232
     {
233
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
233
+        if (self::DEBUG) echo __FUNCTION__."\n";
234 234
         $this->canvas->lineTo($x, $y);
235 235
     }
236 236
 
237 237
     public function moveTo($x, $y)
238 238
     {
239
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
239
+        if (self::DEBUG) echo __FUNCTION__."\n";
240 240
         $this->canvas->moveTo($x, $y);
241 241
     }
242 242
 
243 243
     public function quadraticCurveTo($cpx, $cpy, $x, $y)
244 244
     {
245
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
245
+        if (self::DEBUG) echo __FUNCTION__."\n";
246 246
 
247 247
         // FIXME not accurate
248 248
         $this->canvas->quadTo($cpx, $cpy, $x, $y);
@@ -250,43 +250,43 @@  discard block
 block discarded – undo
250 250
 
251 251
     public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y)
252 252
     {
253
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
253
+        if (self::DEBUG) echo __FUNCTION__."\n";
254 254
         $this->canvas->curveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y);
255 255
     }
256 256
 
257 257
     public function arcTo($x1, $y1, $x2, $y2, $radius)
258 258
     {
259
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
259
+        if (self::DEBUG) echo __FUNCTION__."\n";
260 260
     }
261 261
 
262 262
     public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false)
263 263
     {
264
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
264
+        if (self::DEBUG) echo __FUNCTION__."\n";
265 265
         $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, $startAngle, $endAngle, false, false, false, true);
266 266
     }
267 267
 
268 268
     public function circle($x, $y, $radius)
269 269
     {
270
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
270
+        if (self::DEBUG) echo __FUNCTION__."\n";
271 271
         $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, 0, 360, true, false, false, false);
272 272
     }
273 273
 
274 274
     public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise)
275 275
     {
276
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
276
+        if (self::DEBUG) echo __FUNCTION__."\n";
277 277
         $this->canvas->ellipse($x, $y, $radiusX, $radiusY, 0, 8, 0, 360, false, false, false, false);
278 278
     }
279 279
 
280 280
     public function fillRect($x, $y, $w, $h)
281 281
     {
282
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
282
+        if (self::DEBUG) echo __FUNCTION__."\n";
283 283
         $this->rect($x, $y, $w, $h);
284 284
         $this->fill();
285 285
     }
286 286
 
287 287
     public function rect($x, $y, $w, $h, $rx = 0, $ry = 0)
288 288
     {
289
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
289
+        if (self::DEBUG) echo __FUNCTION__."\n";
290 290
 
291 291
         $canvas = $this->canvas;
292 292
 
@@ -311,7 +311,7 @@  discard block
 block discarded – undo
311 311
         $this->arc($x + $w - $rx, $y + $rx, $rx, 270, 360);
312 312
 
313 313
         /* Start of the arc segment in the upper right corner */
314
-        $this->lineTo($x + $w, $y + $h - $rx );
314
+        $this->lineTo($x + $w, $y + $h - $rx);
315 315
 
316 316
         /* Arc segment in the upper right corner */
317 317
         $this->arc($x + $w - $rx, $y + $h - $rx, $rx, 0, 90);
@@ -323,7 +323,7 @@  discard block
 block discarded – undo
323 323
         $this->arc($x + $rx, $y + $h - $rx, $rx, 90, 180);
324 324
 
325 325
         /* Start of the arc segment in the lower left corner */
326
-        $this->lineTo($x , $y + $rx);
326
+        $this->lineTo($x, $y + $rx);
327 327
 
328 328
         /* Arc segment in the lower left corner */
329 329
         $this->arc($x + $rx, $y + $rx, $rx, 180, 270);
@@ -331,32 +331,32 @@  discard block
 block discarded – undo
331 331
 
332 332
     public function fill()
333 333
     {
334
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
334
+        if (self::DEBUG) echo __FUNCTION__."\n";
335 335
         $this->canvas->fill();
336 336
     }
337 337
 
338 338
     public function strokeRect($x, $y, $w, $h)
339 339
     {
340
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
340
+        if (self::DEBUG) echo __FUNCTION__."\n";
341 341
         $this->rect($x, $y, $w, $h);
342 342
         $this->stroke();
343 343
     }
344 344
 
345 345
     public function stroke(bool $close = false)
346 346
     {
347
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
347
+        if (self::DEBUG) echo __FUNCTION__."\n";
348 348
         $this->canvas->stroke($close);
349 349
     }
350 350
 
351 351
     public function endPath()
352 352
     {
353
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
353
+        if (self::DEBUG) echo __FUNCTION__."\n";
354 354
         $this->canvas->endPath();
355 355
     }
356 356
 
357 357
     public function measureText($text)
358 358
     {
359
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
359
+        if (self::DEBUG) echo __FUNCTION__."\n";
360 360
         $style = $this->getStyle();
361 361
         $this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight);
362 362
 
@@ -365,23 +365,23 @@  discard block
 block discarded – undo
365 365
 
366 366
     public function getStyle()
367 367
     {
368
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
368
+        if (self::DEBUG) echo __FUNCTION__."\n";
369 369
         return $this->style;
370 370
     }
371 371
 
372 372
     public function setStyle(Style $style)
373 373
     {
374
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
374
+        if (self::DEBUG) echo __FUNCTION__."\n";
375 375
 
376 376
         $this->style = $style;
377 377
         $canvas = $this->canvas;
378 378
 
379 379
         if (is_array($style->stroke) && $stroke = $style->stroke) {
380
-            $canvas->setStrokeColor(array((float)$stroke[0]/255, (float)$stroke[1]/255, (float)$stroke[2]/255), true);
380
+            $canvas->setStrokeColor(array((float) $stroke[0] / 255, (float) $stroke[1] / 255, (float) $stroke[2] / 255), true);
381 381
         }
382 382
 
383 383
         if (is_array($style->fill) && $fill = $style->fill) {
384
-            $canvas->setColor(array((float)$fill[0]/255, (float)$fill[1]/255, (float)$fill[2]/255), true);
384
+            $canvas->setColor(array((float) $fill[0] / 255, (float) $fill[1] / 255, (float) $fill[2] / 255), true);
385 385
         }
386 386
 
387 387
         if ($fillRule = strtolower($style->fillRule)) {
@@ -416,7 +416,7 @@  discard block
 block discarded – undo
416 416
         }
417 417
 
418 418
 
419
-        $phase=0;
419
+        $phase = 0;
420 420
         if ($style->strokeDashoffset) {
421 421
            $phase = $style->strokeDashoffset;
422 422
         }
Please login to merge, or discard this patch.
Braces   +98 added lines, -36 removed lines patch added patch discarded remove patch
@@ -26,7 +26,9 @@  discard block
 block discarded – undo
26 26
 
27 27
     public function __construct(Document $doc, $canvas = null)
28 28
     {
29
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
29
+        if (self::DEBUG) {
30
+        	echo __FUNCTION__ . "\n";
31
+        }
30 32
 
31 33
         $dimensions = $doc->getDimensions();
32 34
         $w = $dimensions["width"];
@@ -54,32 +56,42 @@  discard block
 block discarded – undo
54 56
 
55 57
     function out()
56 58
     {
57
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
59
+        if (self::DEBUG) {
60
+        	echo __FUNCTION__ . "\n";
61
+        }
58 62
         return $this->canvas->output();
59 63
     }
60 64
 
61 65
     public function save()
62 66
     {
63
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
67
+        if (self::DEBUG) {
68
+        	echo __FUNCTION__ . "\n";
69
+        }
64 70
         $this->canvas->save();
65 71
     }
66 72
 
67 73
     public function restore()
68 74
     {
69
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
75
+        if (self::DEBUG) {
76
+        	echo __FUNCTION__ . "\n";
77
+        }
70 78
         $this->canvas->restore();
71 79
     }
72 80
 
73 81
     public function scale($x, $y)
74 82
     {
75
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
83
+        if (self::DEBUG) {
84
+        	echo __FUNCTION__ . "\n";
85
+        }
76 86
 
77 87
         $this->transform($x, 0, 0, $y, 0, 0);
78 88
     }
79 89
 
80 90
     public function rotate($angle)
81 91
     {
82
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
92
+        if (self::DEBUG) {
93
+        	echo __FUNCTION__ . "\n";
94
+        }
83 95
 
84 96
         $a = deg2rad($angle);
85 97
         $cos_a = cos($a);
@@ -94,7 +106,9 @@  discard block
 block discarded – undo
94 106
 
95 107
     public function translate($x, $y)
96 108
     {
97
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
109
+        if (self::DEBUG) {
110
+        	echo __FUNCTION__ . "\n";
111
+        }
98 112
 
99 113
         $this->transform(
100 114
             1,  0,
@@ -105,50 +119,66 @@  discard block
 block discarded – undo
105 119
 
106 120
     public function transform($a, $b, $c, $d, $e, $f)
107 121
     {
108
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
122
+        if (self::DEBUG) {
123
+        	echo __FUNCTION__ . "\n";
124
+        }
109 125
 
110 126
         $this->canvas->transform(array($a, $b, $c, $d, $e, $f));
111 127
     }
112 128
 
113 129
     public function beginPath()
114 130
     {
115
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
131
+        if (self::DEBUG) {
132
+        	echo __FUNCTION__ . "\n";
133
+        }
116 134
         // TODO: Implement beginPath() method.
117 135
     }
118 136
 
119 137
     public function closePath()
120 138
     {
121
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
139
+        if (self::DEBUG) {
140
+        	echo __FUNCTION__ . "\n";
141
+        }
122 142
         $this->canvas->closePath();
123 143
     }
124 144
 
125 145
     public function fillStroke(bool $close = false)
126 146
     {
127
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
147
+        if (self::DEBUG) {
148
+        	echo __FUNCTION__ . "\n";
149
+        }
128 150
         $this->canvas->fillStroke($close);
129 151
     }
130 152
 
131 153
     public function clip()
132 154
     {
133
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
155
+        if (self::DEBUG) {
156
+        	echo __FUNCTION__ . "\n";
157
+        }
134 158
         $this->canvas->clip();
135 159
     }
136 160
 
137 161
     public function fillText($text, $x, $y, $maxWidth = null)
138 162
     {
139
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
163
+        if (self::DEBUG) {
164
+        	echo __FUNCTION__ . "\n";
165
+        }
140 166
         $this->canvas->addText($x, $y, $this->style->fontSize, $text);
141 167
     }
142 168
 
143 169
     public function strokeText($text, $x, $y, $maxWidth = null)
144 170
     {
145
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
171
+        if (self::DEBUG) {
172
+        	echo __FUNCTION__ . "\n";
173
+        }
146 174
         $this->canvas->addText($x, $y, $this->style->fontSize, $text);
147 175
     }
148 176
 
149 177
     public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null)
150 178
     {
151
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
179
+        if (self::DEBUG) {
180
+        	echo __FUNCTION__ . "\n";
181
+        }
152 182
 
153 183
         if (strpos($image, "data:") === 0) {
154 184
             $parts = explode(',', $image, 2);
@@ -168,8 +198,7 @@  discard block
 block discarded – undo
168 198
             if ($base64) {
169 199
                 $data = base64_decode($data);
170 200
             }
171
-        }
172
-        else {
201
+        } else {
173 202
             $data = file_get_contents($image);
174 203
         }
175 204
 
@@ -230,19 +259,25 @@  discard block
 block discarded – undo
230 259
 
231 260
     public function lineTo($x, $y)
232 261
     {
233
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
262
+        if (self::DEBUG) {
263
+        	echo __FUNCTION__ . "\n";
264
+        }
234 265
         $this->canvas->lineTo($x, $y);
235 266
     }
236 267
 
237 268
     public function moveTo($x, $y)
238 269
     {
239
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
270
+        if (self::DEBUG) {
271
+        	echo __FUNCTION__ . "\n";
272
+        }
240 273
         $this->canvas->moveTo($x, $y);
241 274
     }
242 275
 
243 276
     public function quadraticCurveTo($cpx, $cpy, $x, $y)
244 277
     {
245
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
278
+        if (self::DEBUG) {
279
+        	echo __FUNCTION__ . "\n";
280
+        }
246 281
 
247 282
         // FIXME not accurate
248 283
         $this->canvas->quadTo($cpx, $cpy, $x, $y);
@@ -250,43 +285,57 @@  discard block
 block discarded – undo
250 285
 
251 286
     public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y)
252 287
     {
253
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
288
+        if (self::DEBUG) {
289
+        	echo __FUNCTION__ . "\n";
290
+        }
254 291
         $this->canvas->curveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y);
255 292
     }
256 293
 
257 294
     public function arcTo($x1, $y1, $x2, $y2, $radius)
258 295
     {
259
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
296
+        if (self::DEBUG) {
297
+        	echo __FUNCTION__ . "\n";
298
+        }
260 299
     }
261 300
 
262 301
     public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false)
263 302
     {
264
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
303
+        if (self::DEBUG) {
304
+        	echo __FUNCTION__ . "\n";
305
+        }
265 306
         $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, $startAngle, $endAngle, false, false, false, true);
266 307
     }
267 308
 
268 309
     public function circle($x, $y, $radius)
269 310
     {
270
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
311
+        if (self::DEBUG) {
312
+        	echo __FUNCTION__ . "\n";
313
+        }
271 314
         $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, 0, 360, true, false, false, false);
272 315
     }
273 316
 
274 317
     public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise)
275 318
     {
276
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
319
+        if (self::DEBUG) {
320
+        	echo __FUNCTION__ . "\n";
321
+        }
277 322
         $this->canvas->ellipse($x, $y, $radiusX, $radiusY, 0, 8, 0, 360, false, false, false, false);
278 323
     }
279 324
 
280 325
     public function fillRect($x, $y, $w, $h)
281 326
     {
282
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
327
+        if (self::DEBUG) {
328
+        	echo __FUNCTION__ . "\n";
329
+        }
283 330
         $this->rect($x, $y, $w, $h);
284 331
         $this->fill();
285 332
     }
286 333
 
287 334
     public function rect($x, $y, $w, $h, $rx = 0, $ry = 0)
288 335
     {
289
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
336
+        if (self::DEBUG) {
337
+        	echo __FUNCTION__ . "\n";
338
+        }
290 339
 
291 340
         $canvas = $this->canvas;
292 341
 
@@ -331,32 +380,42 @@  discard block
 block discarded – undo
331 380
 
332 381
     public function fill()
333 382
     {
334
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
383
+        if (self::DEBUG) {
384
+        	echo __FUNCTION__ . "\n";
385
+        }
335 386
         $this->canvas->fill();
336 387
     }
337 388
 
338 389
     public function strokeRect($x, $y, $w, $h)
339 390
     {
340
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
391
+        if (self::DEBUG) {
392
+        	echo __FUNCTION__ . "\n";
393
+        }
341 394
         $this->rect($x, $y, $w, $h);
342 395
         $this->stroke();
343 396
     }
344 397
 
345 398
     public function stroke(bool $close = false)
346 399
     {
347
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
400
+        if (self::DEBUG) {
401
+        	echo __FUNCTION__ . "\n";
402
+        }
348 403
         $this->canvas->stroke($close);
349 404
     }
350 405
 
351 406
     public function endPath()
352 407
     {
353
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
408
+        if (self::DEBUG) {
409
+        	echo __FUNCTION__ . "\n";
410
+        }
354 411
         $this->canvas->endPath();
355 412
     }
356 413
 
357 414
     public function measureText($text)
358 415
     {
359
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
416
+        if (self::DEBUG) {
417
+        	echo __FUNCTION__ . "\n";
418
+        }
360 419
         $style = $this->getStyle();
361 420
         $this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight);
362 421
 
@@ -365,13 +424,17 @@  discard block
 block discarded – undo
365 424
 
366 425
     public function getStyle()
367 426
     {
368
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
427
+        if (self::DEBUG) {
428
+        	echo __FUNCTION__ . "\n";
429
+        }
369 430
         return $this->style;
370 431
     }
371 432
 
372 433
     public function setStyle(Style $style)
373 434
     {
374
-        if (self::DEBUG) echo __FUNCTION__ . "\n";
435
+        if (self::DEBUG) {
436
+        	echo __FUNCTION__ . "\n";
437
+        }
375 438
 
376 439
         $this->style = $style;
377 440
         $canvas = $this->canvas;
@@ -395,8 +458,7 @@  discard block
 block discarded – undo
395 458
 
396 459
             $canvas->setFillTransparency("Normal", $opacity);
397 460
             $canvas->currentFillTransparency = null;
398
-        }
399
-        else {
461
+        } else {
400 462
             $fillOpacity = $style->fillOpacity;
401 463
             if ($fillOpacity !== null && $fillOpacity < 1.0) {
402 464
                 $canvas->setFillTransparency("Normal", $fillOpacity);
Please login to merge, or discard this patch.
vendor/phenx/php-svg-lib/src/Svg/Style.php 2 patches
Indentation   +526 added lines, -526 removed lines patch added patch discarded remove patch
@@ -12,530 +12,530 @@
 block discarded – undo
12 12
 
13 13
 class Style
14 14
 {
15
-    const TYPE_COLOR = 1;
16
-    const TYPE_LENGTH = 2;
17
-    const TYPE_NAME = 3;
18
-    const TYPE_ANGLE = 4;
19
-    const TYPE_NUMBER = 5;
20
-
21
-    private $_parentStyle;
22
-
23
-    public $color;
24
-    public $opacity;
25
-    public $display;
26
-
27
-    public $fill;
28
-    public $fillOpacity;
29
-    public $fillRule;
30
-
31
-    public $stroke;
32
-    public $strokeOpacity;
33
-    public $strokeLinecap;
34
-    public $strokeLinejoin;
35
-    public $strokeMiterlimit;
36
-    public $strokeWidth;
37
-    public $strokeDasharray;
38
-    public $strokeDashoffset;
39
-
40
-    public $fontFamily = 'serif';
41
-    public $fontSize = 12;
42
-    public $fontWeight = 'normal';
43
-    public $fontStyle = 'normal';
44
-    public $textAnchor = 'start';
45
-
46
-    protected function getStyleMap()
47
-    {
48
-        return array(
49
-            'color'             => array('color', self::TYPE_COLOR),
50
-            'opacity'           => array('opacity', self::TYPE_NUMBER),
51
-            'display'           => array('display', self::TYPE_NAME),
52
-
53
-            'fill'              => array('fill', self::TYPE_COLOR),
54
-            'fill-opacity'      => array('fillOpacity', self::TYPE_NUMBER),
55
-            'fill-rule'         => array('fillRule', self::TYPE_NAME),
56
-
57
-            'stroke'            => array('stroke', self::TYPE_COLOR),
58
-            'stroke-dasharray'  => array('strokeDasharray', self::TYPE_NAME),
59
-            'stroke-dashoffset' => array('strokeDashoffset', self::TYPE_NUMBER),
60
-            'stroke-linecap'    => array('strokeLinecap', self::TYPE_NAME),
61
-            'stroke-linejoin'   => array('strokeLinejoin', self::TYPE_NAME),
62
-            'stroke-miterlimit' => array('strokeMiterlimit', self::TYPE_NUMBER),
63
-            'stroke-opacity'    => array('strokeOpacity', self::TYPE_NUMBER),
64
-            'stroke-width'      => array('strokeWidth', self::TYPE_NUMBER),
65
-
66
-            'font-family'       => array('fontFamily', self::TYPE_NAME),
67
-            'font-size'         => array('fontSize', self::TYPE_NUMBER),
68
-            'font-weight'       => array('fontWeight', self::TYPE_NAME),
69
-            'font-style'        => array('fontStyle', self::TYPE_NAME),
70
-            'text-anchor'       => array('textAnchor', self::TYPE_NAME),
71
-        );
72
-    }
73
-
74
-    /**
75
-     * @param $attributes
76
-     *
77
-     * @return Style
78
-     */
79
-    public function fromAttributes($attributes)
80
-    {
81
-        $this->fillStyles($attributes);
82
-
83
-        if (isset($attributes["style"])) {
84
-            $styles = self::parseCssStyle($attributes["style"]);
85
-            $this->fillStyles($styles);
86
-        }
87
-    }
88
-
89
-    public function inherit(AbstractTag $tag) {
90
-        $group = $tag->getParentGroup();
91
-        if ($group) {
92
-            $parent_style = $group->getStyle();
93
-            $this->_parentStyle = $parent_style;
94
-            foreach ($parent_style as $_key => $_value) {
95
-                if ($_value !== null) {
96
-                    $this->$_key = $_value;
97
-                }
98
-            }
99
-        }
100
-    }
101
-
102
-    public function fromStyleSheets(AbstractTag $tag, $attributes) {
103
-        $class = isset($attributes["class"]) ? preg_split('/\s+/', trim($attributes["class"])) : null;
104
-
105
-        $stylesheets = $tag->getDocument()->getStyleSheets();
106
-
107
-        $styles = array();
108
-
109
-        foreach ($stylesheets as $_sc) {
110
-
111
-            /** @var \Sabberworm\CSS\RuleSet\DeclarationBlock $_decl */
112
-            foreach ($_sc->getAllDeclarationBlocks() as $_decl) {
113
-
114
-                /** @var \Sabberworm\CSS\Property\Selector $_selector */
115
-                foreach ($_decl->getSelectors() as $_selector) {
116
-                    $_selector = $_selector->getSelector();
117
-
118
-                    // Match class name
119
-                    if ($class !== null) {
120
-                        foreach ($class as $_class) {
121
-                            if ($_selector === ".$_class") {
122
-                                /** @var \Sabberworm\CSS\Rule\Rule $_rule */
123
-                                foreach ($_decl->getRules() as $_rule) {
124
-                                    $styles[$_rule->getRule()] = $_rule->getValue() . "";
125
-                                }
126
-
127
-                                break 2;
128
-                            }
129
-                        }
130
-                    }
131
-
132
-                    // Match tag name
133
-                    if ($_selector === $tag->tagName) {
134
-                        /** @var \Sabberworm\CSS\Rule\Rule $_rule */
135
-                        foreach ($_decl->getRules() as $_rule) {
136
-                            $styles[$_rule->getRule()] = $_rule->getValue() . "";
137
-                        }
138
-
139
-                        break;
140
-                    }
141
-                }
142
-            }
143
-        }
144
-
145
-        $this->fillStyles($styles);
146
-    }
147
-
148
-    protected function fillStyles($styles)
149
-    {
150
-        $style_map = $this->getStyleMap();
151
-        foreach ($style_map as $from => $spec) {
152
-            if (isset($styles[$from])) {
153
-                list($to, $type) = $spec;
154
-                $value = null;
155
-                switch ($type) {
156
-                    case self::TYPE_COLOR:
157
-                        $value = self::parseColor($styles[$from]);
158
-                        if ($value === "currentcolor") {
159
-                            if ($type === "color") {
160
-                                $value = $this->_parentStyle->color;
161
-                            } else {
162
-                                $value = $this->color;
163
-                            }
164
-                        }
165
-                        if ($value !== null && $value[3] !== 1 && array_key_exists("{$from}-opacity", $style_map) === true) {
166
-                            $styles["{$from}-opacity"] = $value[3];
167
-                        }
168
-                        break;
169
-
170
-                    case self::TYPE_NUMBER:
171
-                        $value = ($styles[$from] === null) ? null : (float)$styles[$from];
172
-                        break;
173
-
174
-                    default:
175
-                        $value = $styles[$from];
176
-                }
177
-
178
-                if ($value !== null) {
179
-                    $this->$to = $value;
180
-                }
181
-            }
182
-        }
183
-    }
184
-
185
-    static function parseColor($color)
186
-    {
187
-        $color = strtolower(trim($color));
188
-
189
-        $parts = preg_split('/[^,]\s+/', $color, 2);
190
-
191
-        if (count($parts) == 2) {
192
-            $color = $parts[1];
193
-        } else {
194
-            $color = $parts[0];
195
-        }
196
-
197
-        if ($color === "none") {
198
-            return "none";
199
-        }
200
-
201
-        if ($color === "currentcolor") {
202
-            return "currentcolor";
203
-        }
204
-
205
-        // SVG color name
206
-        if (isset(self::$colorNames[$color])) {
207
-            return self::parseHexColor(self::$colorNames[$color]);
208
-        }
209
-
210
-        // Hex color
211
-        if ($color[0] === "#") {
212
-            return self::parseHexColor($color);
213
-        }
214
-
215
-        // RGB color
216
-        if (strpos($color, "rgb") !== false) {
217
-            return self::getQuad($color);
218
-        }
219
-
220
-        // RGB color
221
-        if (strpos($color, "hsl") !== false) {
222
-            $quad = self::getQuad($color, true);
223
-
224
-            if ($quad == null) {
225
-                return null;
226
-            }
227
-
228
-            list($h, $s, $l, $a) = $quad;
229
-
230
-            $r = $l;
231
-            $g = $l;
232
-            $b = $l;
233
-            $v = ($l <= 0.5) ? ($l * (1.0 + $s)) : ($l + $s - $l * $s);
234
-            if ($v > 0) {
235
-                $m = $l + $l - $v;
236
-                $sv = ($v - $m) / $v;
237
-                $h *= 6.0;
238
-                $sextant = floor($h);
239
-                $fract = $h - $sextant;
240
-                $vsf = $v * $sv * $fract;
241
-                $mid1 = $m + $vsf;
242
-                $mid2 = $v - $vsf;
243
-
244
-                switch ($sextant) {
245
-                    case 0:
246
-                        $r = $v;
247
-                        $g = $mid1;
248
-                        $b = $m;
249
-                        break;
250
-                    case 1:
251
-                        $r = $mid2;
252
-                        $g = $v;
253
-                        $b = $m;
254
-                        break;
255
-                    case 2:
256
-                        $r = $m;
257
-                        $g = $v;
258
-                        $b = $mid1;
259
-                        break;
260
-                    case 3:
261
-                        $r = $m;
262
-                        $g = $mid2;
263
-                        $b = $v;
264
-                        break;
265
-                    case 4:
266
-                        $r = $mid1;
267
-                        $g = $m;
268
-                        $b = $v;
269
-                        break;
270
-                    case 5:
271
-                        $r = $v;
272
-                        $g = $m;
273
-                        $b = $mid2;
274
-                        break;
275
-                }
276
-            }
277
-            $a = $a * 255;
278
-
279
-            return array(
280
-                $r * 255.0,
281
-                $g * 255.0,
282
-                $b * 255.0,
283
-                $a
284
-            );
285
-        }
286
-
287
-        // Gradient
288
-        if (strpos($color, "url(#") !== false) {
289
-            $i = strpos($color, "(");
290
-            $j = strpos($color, ")");
291
-
292
-            // Bad url format
293
-            if ($i === false || $j === false) {
294
-                return null;
295
-            }
296
-
297
-            return trim(substr($color, $i + 1, $j - $i - 1));
298
-        }
299
-
300
-        return null;
301
-    }
302
-
303
-    static function getQuad($color, $percent = false) {
304
-        $i = strpos($color, "(");
305
-        $j = strpos($color, ")");
306
-
307
-        // Bad color value
308
-        if ($i === false || $j === false) {
309
-            return null;
310
-        }
311
-
312
-        $quad = preg_split("/\\s*[,\\/]\\s*/", trim(substr($color, $i + 1, $j - $i - 1)));
313
-        if (!isset($quad[3])) {
314
-            $quad[3] = 1;
315
-        }
316
-
317
-        if (count($quad) != 3 && count($quad) != 4) {
318
-            return null;
319
-        }
320
-
321
-        foreach (array_keys($quad) as $c) {
322
-            $quad[$c] = trim($quad[$c]);
323
-
324
-            if ($percent) {
325
-                if ($quad[$c][strlen($quad[$c]) - 1] === "%") {
326
-                    $quad[$c] = floatval($quad[$c]) / 100;
327
-                } else {
328
-                    $quad[$c] = $quad[$c] / 255;
329
-                }
330
-            } else {
331
-                if ($quad[$c][strlen($quad[$c]) - 1] === "%") {
332
-                    $quad[$c] = round(floatval($quad[$c]) * 2.55);
333
-                }
334
-            }
335
-        }
336
-
337
-        return $quad;
338
-    }
339
-
340
-    static function parseHexColor($hex)
341
-    {
342
-        $c = array(0, 0, 0, 1);
343
-
344
-        // #FFFFFF
345
-        if (isset($hex[6])) {
346
-            $c[0] = hexdec(substr($hex, 1, 2));
347
-            $c[1] = hexdec(substr($hex, 3, 2));
348
-            $c[2] = hexdec(substr($hex, 5, 2));
349
-
350
-            if (isset($hex[7])) {
351
-                $alpha = substr($hex, 7, 2);
352
-                if (ctype_xdigit($alpha)) {
353
-                    $c[3] = round(hexdec($alpha)/255, 2);
354
-                }
355
-            }
356
-        } else {
357
-            $c[0] = hexdec($hex[1] . $hex[1]);
358
-            $c[1] = hexdec($hex[2] . $hex[2]);
359
-            $c[2] = hexdec($hex[3] . $hex[3]);
360
-
361
-            if (isset($hex[4])) {
362
-                if (ctype_xdigit($hex[4])) {
363
-                    $c[3] = round(hexdec($hex[4] . $hex[4])/255, 2);
364
-                }
365
-            }
366
-        }
367
-
368
-        return $c;
369
-    }
370
-
371
-    /**
372
-     * Simple CSS parser
373
-     *
374
-     * @param $style
375
-     *
376
-     * @return array
377
-     */
378
-    static function parseCssStyle($style)
379
-    {
380
-        $matches = array();
381
-        preg_match_all("/([a-z-]+)\\s*:\\s*([^;$]+)/si", $style, $matches, PREG_SET_ORDER);
382
-
383
-        $styles = array();
384
-        foreach ($matches as $match) {
385
-            $styles[$match[1]] = $match[2];
386
-        }
387
-
388
-        return $styles;
389
-    }
390
-
391
-    static $colorNames = array(
392
-        'antiquewhite'         => '#FAEBD7',
393
-        'aqua'                 => '#00FFFF',
394
-        'aquamarine'           => '#7FFFD4',
395
-        'beige'                => '#F5F5DC',
396
-        'black'                => '#000000',
397
-        'blue'                 => '#0000FF',
398
-        'brown'                => '#A52A2A',
399
-        'cadetblue'            => '#5F9EA0',
400
-        'chocolate'            => '#D2691E',
401
-        'cornflowerblue'       => '#6495ED',
402
-        'crimson'              => '#DC143C',
403
-        'darkblue'             => '#00008B',
404
-        'darkgoldenrod'        => '#B8860B',
405
-        'darkgreen'            => '#006400',
406
-        'darkmagenta'          => '#8B008B',
407
-        'darkorange'           => '#FF8C00',
408
-        'darkred'              => '#8B0000',
409
-        'darkseagreen'         => '#8FBC8F',
410
-        'darkslategray'        => '#2F4F4F',
411
-        'darkviolet'           => '#9400D3',
412
-        'deepskyblue'          => '#00BFFF',
413
-        'dodgerblue'           => '#1E90FF',
414
-        'firebrick'            => '#B22222',
415
-        'forestgreen'          => '#228B22',
416
-        'fuchsia'              => '#FF00FF',
417
-        'gainsboro'            => '#DCDCDC',
418
-        'gold'                 => '#FFD700',
419
-        'gray'                 => '#808080',
420
-        'green'                => '#008000',
421
-        'greenyellow'          => '#ADFF2F',
422
-        'hotpink'              => '#FF69B4',
423
-        'indigo'               => '#4B0082',
424
-        'khaki'                => '#F0E68C',
425
-        'lavenderblush'        => '#FFF0F5',
426
-        'lemonchiffon'         => '#FFFACD',
427
-        'lightcoral'           => '#F08080',
428
-        'lightgoldenrodyellow' => '#FAFAD2',
429
-        'lightgreen'           => '#90EE90',
430
-        'lightsalmon'          => '#FFA07A',
431
-        'lightskyblue'         => '#87CEFA',
432
-        'lightslategray'       => '#778899',
433
-        'lightyellow'          => '#FFFFE0',
434
-        'lime'                 => '#00FF00',
435
-        'limegreen'            => '#32CD32',
436
-        'magenta'              => '#FF00FF',
437
-        'maroon'               => '#800000',
438
-        'mediumaquamarine'     => '#66CDAA',
439
-        'mediumorchid'         => '#BA55D3',
440
-        'mediumseagreen'       => '#3CB371',
441
-        'mediumspringgreen'    => '#00FA9A',
442
-        'mediumvioletred'      => '#C71585',
443
-        'midnightblue'         => '#191970',
444
-        'mintcream'            => '#F5FFFA',
445
-        'moccasin'             => '#FFE4B5',
446
-        'navy'                 => '#000080',
447
-        'olive'                => '#808000',
448
-        'orange'               => '#FFA500',
449
-        'orchid'               => '#DA70D6',
450
-        'palegreen'            => '#98FB98',
451
-        'palevioletred'        => '#D87093',
452
-        'peachpuff'            => '#FFDAB9',
453
-        'pink'                 => '#FFC0CB',
454
-        'powderblue'           => '#B0E0E6',
455
-        'purple'               => '#800080',
456
-        'red'                  => '#FF0000',
457
-        'royalblue'            => '#4169E1',
458
-        'salmon'               => '#FA8072',
459
-        'seagreen'             => '#2E8B57',
460
-        'sienna'               => '#A0522D',
461
-        'silver'               => '#C0C0C0',
462
-        'skyblue'              => '#87CEEB',
463
-        'slategray'            => '#708090',
464
-        'springgreen'          => '#00FF7F',
465
-        'steelblue'            => '#4682B4',
466
-        'tan'                  => '#D2B48C',
467
-        'teal'                 => '#008080',
468
-        'thistle'              => '#D8BFD8',
469
-        'turquoise'            => '#40E0D0',
470
-        'violetred'            => '#D02090',
471
-        'white'                => '#FFFFFF',
472
-        'yellow'               => '#FFFF00',
473
-        'aliceblue'            => '#f0f8ff',
474
-        'azure'                => '#f0ffff',
475
-        'bisque'               => '#ffe4c4',
476
-        'blanchedalmond'       => '#ffebcd',
477
-        'blueviolet'           => '#8a2be2',
478
-        'burlywood'            => '#deb887',
479
-        'chartreuse'           => '#7fff00',
480
-        'coral'                => '#ff7f50',
481
-        'cornsilk'             => '#fff8dc',
482
-        'cyan'                 => '#00ffff',
483
-        'darkcyan'             => '#008b8b',
484
-        'darkgray'             => '#a9a9a9',
485
-        'darkgrey'             => '#a9a9a9',
486
-        'darkkhaki'            => '#bdb76b',
487
-        'darkolivegreen'       => '#556b2f',
488
-        'darkorchid'           => '#9932cc',
489
-        'darksalmon'           => '#e9967a',
490
-        'darkslateblue'        => '#483d8b',
491
-        'darkslategrey'        => '#2f4f4f',
492
-        'darkturquoise'        => '#00ced1',
493
-        'deeppink'             => '#ff1493',
494
-        'dimgray'              => '#696969',
495
-        'dimgrey'              => '#696969',
496
-        'floralwhite'          => '#fffaf0',
497
-        'ghostwhite'           => '#f8f8ff',
498
-        'goldenrod'            => '#daa520',
499
-        'grey'                 => '#808080',
500
-        'honeydew'             => '#f0fff0',
501
-        'indianred'            => '#cd5c5c',
502
-        'ivory'                => '#fffff0',
503
-        'lavender'             => '#e6e6fa',
504
-        'lawngreen'            => '#7cfc00',
505
-        'lightblue'            => '#add8e6',
506
-        'lightcyan'            => '#e0ffff',
507
-        'lightgray'            => '#d3d3d3',
508
-        'lightgrey'            => '#d3d3d3',
509
-        'lightpink'            => '#ffb6c1',
510
-        'lightseagreen'        => '#20b2aa',
511
-        'lightslategrey'       => '#778899',
512
-        'lightsteelblue'       => '#b0c4de',
513
-        'linen'                => '#faf0e6',
514
-        'mediumblue'           => '#0000cd',
515
-        'mediumpurple'         => '#9370db',
516
-        'mediumslateblue'      => '#7b68ee',
517
-        'mediumturquoise'      => '#48d1cc',
518
-        'mistyrose'            => '#ffe4e1',
519
-        'navajowhite'          => '#ffdead',
520
-        'oldlace'              => '#fdf5e6',
521
-        'olivedrab'            => '#6b8e23',
522
-        'orangered'            => '#ff4500',
523
-        'palegoldenrod'        => '#eee8aa',
524
-        'paleturquoise'        => '#afeeee',
525
-        'papayawhip'           => '#ffefd5',
526
-        'peru'                 => '#cd853f',
527
-        'plum'                 => '#dda0dd',
528
-        'rosybrown'            => '#bc8f8f',
529
-        'saddlebrown'          => '#8b4513',
530
-        'sandybrown'           => '#f4a460',
531
-        'seashell'             => '#fff5ee',
532
-        'slateblue'            => '#6a5acd',
533
-        'slategrey'            => '#708090',
534
-        'snow'                 => '#fffafa',
535
-        'tomato'               => '#ff6347',
536
-        'violet'               => '#ee82ee',
537
-        'wheat'                => '#f5deb3',
538
-        'whitesmoke'           => '#f5f5f5',
539
-        'yellowgreen'          => '#9acd32',
540
-    );
15
+	const TYPE_COLOR = 1;
16
+	const TYPE_LENGTH = 2;
17
+	const TYPE_NAME = 3;
18
+	const TYPE_ANGLE = 4;
19
+	const TYPE_NUMBER = 5;
20
+
21
+	private $_parentStyle;
22
+
23
+	public $color;
24
+	public $opacity;
25
+	public $display;
26
+
27
+	public $fill;
28
+	public $fillOpacity;
29
+	public $fillRule;
30
+
31
+	public $stroke;
32
+	public $strokeOpacity;
33
+	public $strokeLinecap;
34
+	public $strokeLinejoin;
35
+	public $strokeMiterlimit;
36
+	public $strokeWidth;
37
+	public $strokeDasharray;
38
+	public $strokeDashoffset;
39
+
40
+	public $fontFamily = 'serif';
41
+	public $fontSize = 12;
42
+	public $fontWeight = 'normal';
43
+	public $fontStyle = 'normal';
44
+	public $textAnchor = 'start';
45
+
46
+	protected function getStyleMap()
47
+	{
48
+		return array(
49
+			'color'             => array('color', self::TYPE_COLOR),
50
+			'opacity'           => array('opacity', self::TYPE_NUMBER),
51
+			'display'           => array('display', self::TYPE_NAME),
52
+
53
+			'fill'              => array('fill', self::TYPE_COLOR),
54
+			'fill-opacity'      => array('fillOpacity', self::TYPE_NUMBER),
55
+			'fill-rule'         => array('fillRule', self::TYPE_NAME),
56
+
57
+			'stroke'            => array('stroke', self::TYPE_COLOR),
58
+			'stroke-dasharray'  => array('strokeDasharray', self::TYPE_NAME),
59
+			'stroke-dashoffset' => array('strokeDashoffset', self::TYPE_NUMBER),
60
+			'stroke-linecap'    => array('strokeLinecap', self::TYPE_NAME),
61
+			'stroke-linejoin'   => array('strokeLinejoin', self::TYPE_NAME),
62
+			'stroke-miterlimit' => array('strokeMiterlimit', self::TYPE_NUMBER),
63
+			'stroke-opacity'    => array('strokeOpacity', self::TYPE_NUMBER),
64
+			'stroke-width'      => array('strokeWidth', self::TYPE_NUMBER),
65
+
66
+			'font-family'       => array('fontFamily', self::TYPE_NAME),
67
+			'font-size'         => array('fontSize', self::TYPE_NUMBER),
68
+			'font-weight'       => array('fontWeight', self::TYPE_NAME),
69
+			'font-style'        => array('fontStyle', self::TYPE_NAME),
70
+			'text-anchor'       => array('textAnchor', self::TYPE_NAME),
71
+		);
72
+	}
73
+
74
+	/**
75
+	 * @param $attributes
76
+	 *
77
+	 * @return Style
78
+	 */
79
+	public function fromAttributes($attributes)
80
+	{
81
+		$this->fillStyles($attributes);
82
+
83
+		if (isset($attributes["style"])) {
84
+			$styles = self::parseCssStyle($attributes["style"]);
85
+			$this->fillStyles($styles);
86
+		}
87
+	}
88
+
89
+	public function inherit(AbstractTag $tag) {
90
+		$group = $tag->getParentGroup();
91
+		if ($group) {
92
+			$parent_style = $group->getStyle();
93
+			$this->_parentStyle = $parent_style;
94
+			foreach ($parent_style as $_key => $_value) {
95
+				if ($_value !== null) {
96
+					$this->$_key = $_value;
97
+				}
98
+			}
99
+		}
100
+	}
101
+
102
+	public function fromStyleSheets(AbstractTag $tag, $attributes) {
103
+		$class = isset($attributes["class"]) ? preg_split('/\s+/', trim($attributes["class"])) : null;
104
+
105
+		$stylesheets = $tag->getDocument()->getStyleSheets();
106
+
107
+		$styles = array();
108
+
109
+		foreach ($stylesheets as $_sc) {
110
+
111
+			/** @var \Sabberworm\CSS\RuleSet\DeclarationBlock $_decl */
112
+			foreach ($_sc->getAllDeclarationBlocks() as $_decl) {
113
+
114
+				/** @var \Sabberworm\CSS\Property\Selector $_selector */
115
+				foreach ($_decl->getSelectors() as $_selector) {
116
+					$_selector = $_selector->getSelector();
117
+
118
+					// Match class name
119
+					if ($class !== null) {
120
+						foreach ($class as $_class) {
121
+							if ($_selector === ".$_class") {
122
+								/** @var \Sabberworm\CSS\Rule\Rule $_rule */
123
+								foreach ($_decl->getRules() as $_rule) {
124
+									$styles[$_rule->getRule()] = $_rule->getValue() . "";
125
+								}
126
+
127
+								break 2;
128
+							}
129
+						}
130
+					}
131
+
132
+					// Match tag name
133
+					if ($_selector === $tag->tagName) {
134
+						/** @var \Sabberworm\CSS\Rule\Rule $_rule */
135
+						foreach ($_decl->getRules() as $_rule) {
136
+							$styles[$_rule->getRule()] = $_rule->getValue() . "";
137
+						}
138
+
139
+						break;
140
+					}
141
+				}
142
+			}
143
+		}
144
+
145
+		$this->fillStyles($styles);
146
+	}
147
+
148
+	protected function fillStyles($styles)
149
+	{
150
+		$style_map = $this->getStyleMap();
151
+		foreach ($style_map as $from => $spec) {
152
+			if (isset($styles[$from])) {
153
+				list($to, $type) = $spec;
154
+				$value = null;
155
+				switch ($type) {
156
+					case self::TYPE_COLOR:
157
+						$value = self::parseColor($styles[$from]);
158
+						if ($value === "currentcolor") {
159
+							if ($type === "color") {
160
+								$value = $this->_parentStyle->color;
161
+							} else {
162
+								$value = $this->color;
163
+							}
164
+						}
165
+						if ($value !== null && $value[3] !== 1 && array_key_exists("{$from}-opacity", $style_map) === true) {
166
+							$styles["{$from}-opacity"] = $value[3];
167
+						}
168
+						break;
169
+
170
+					case self::TYPE_NUMBER:
171
+						$value = ($styles[$from] === null) ? null : (float)$styles[$from];
172
+						break;
173
+
174
+					default:
175
+						$value = $styles[$from];
176
+				}
177
+
178
+				if ($value !== null) {
179
+					$this->$to = $value;
180
+				}
181
+			}
182
+		}
183
+	}
184
+
185
+	static function parseColor($color)
186
+	{
187
+		$color = strtolower(trim($color));
188
+
189
+		$parts = preg_split('/[^,]\s+/', $color, 2);
190
+
191
+		if (count($parts) == 2) {
192
+			$color = $parts[1];
193
+		} else {
194
+			$color = $parts[0];
195
+		}
196
+
197
+		if ($color === "none") {
198
+			return "none";
199
+		}
200
+
201
+		if ($color === "currentcolor") {
202
+			return "currentcolor";
203
+		}
204
+
205
+		// SVG color name
206
+		if (isset(self::$colorNames[$color])) {
207
+			return self::parseHexColor(self::$colorNames[$color]);
208
+		}
209
+
210
+		// Hex color
211
+		if ($color[0] === "#") {
212
+			return self::parseHexColor($color);
213
+		}
214
+
215
+		// RGB color
216
+		if (strpos($color, "rgb") !== false) {
217
+			return self::getQuad($color);
218
+		}
219
+
220
+		// RGB color
221
+		if (strpos($color, "hsl") !== false) {
222
+			$quad = self::getQuad($color, true);
223
+
224
+			if ($quad == null) {
225
+				return null;
226
+			}
227
+
228
+			list($h, $s, $l, $a) = $quad;
229
+
230
+			$r = $l;
231
+			$g = $l;
232
+			$b = $l;
233
+			$v = ($l <= 0.5) ? ($l * (1.0 + $s)) : ($l + $s - $l * $s);
234
+			if ($v > 0) {
235
+				$m = $l + $l - $v;
236
+				$sv = ($v - $m) / $v;
237
+				$h *= 6.0;
238
+				$sextant = floor($h);
239
+				$fract = $h - $sextant;
240
+				$vsf = $v * $sv * $fract;
241
+				$mid1 = $m + $vsf;
242
+				$mid2 = $v - $vsf;
243
+
244
+				switch ($sextant) {
245
+					case 0:
246
+						$r = $v;
247
+						$g = $mid1;
248
+						$b = $m;
249
+						break;
250
+					case 1:
251
+						$r = $mid2;
252
+						$g = $v;
253
+						$b = $m;
254
+						break;
255
+					case 2:
256
+						$r = $m;
257
+						$g = $v;
258
+						$b = $mid1;
259
+						break;
260
+					case 3:
261
+						$r = $m;
262
+						$g = $mid2;
263
+						$b = $v;
264
+						break;
265
+					case 4:
266
+						$r = $mid1;
267
+						$g = $m;
268
+						$b = $v;
269
+						break;
270
+					case 5:
271
+						$r = $v;
272
+						$g = $m;
273
+						$b = $mid2;
274
+						break;
275
+				}
276
+			}
277
+			$a = $a * 255;
278
+
279
+			return array(
280
+				$r * 255.0,
281
+				$g * 255.0,
282
+				$b * 255.0,
283
+				$a
284
+			);
285
+		}
286
+
287
+		// Gradient
288
+		if (strpos($color, "url(#") !== false) {
289
+			$i = strpos($color, "(");
290
+			$j = strpos($color, ")");
291
+
292
+			// Bad url format
293
+			if ($i === false || $j === false) {
294
+				return null;
295
+			}
296
+
297
+			return trim(substr($color, $i + 1, $j - $i - 1));
298
+		}
299
+
300
+		return null;
301
+	}
302
+
303
+	static function getQuad($color, $percent = false) {
304
+		$i = strpos($color, "(");
305
+		$j = strpos($color, ")");
306
+
307
+		// Bad color value
308
+		if ($i === false || $j === false) {
309
+			return null;
310
+		}
311
+
312
+		$quad = preg_split("/\\s*[,\\/]\\s*/", trim(substr($color, $i + 1, $j - $i - 1)));
313
+		if (!isset($quad[3])) {
314
+			$quad[3] = 1;
315
+		}
316
+
317
+		if (count($quad) != 3 && count($quad) != 4) {
318
+			return null;
319
+		}
320
+
321
+		foreach (array_keys($quad) as $c) {
322
+			$quad[$c] = trim($quad[$c]);
323
+
324
+			if ($percent) {
325
+				if ($quad[$c][strlen($quad[$c]) - 1] === "%") {
326
+					$quad[$c] = floatval($quad[$c]) / 100;
327
+				} else {
328
+					$quad[$c] = $quad[$c] / 255;
329
+				}
330
+			} else {
331
+				if ($quad[$c][strlen($quad[$c]) - 1] === "%") {
332
+					$quad[$c] = round(floatval($quad[$c]) * 2.55);
333
+				}
334
+			}
335
+		}
336
+
337
+		return $quad;
338
+	}
339
+
340
+	static function parseHexColor($hex)
341
+	{
342
+		$c = array(0, 0, 0, 1);
343
+
344
+		// #FFFFFF
345
+		if (isset($hex[6])) {
346
+			$c[0] = hexdec(substr($hex, 1, 2));
347
+			$c[1] = hexdec(substr($hex, 3, 2));
348
+			$c[2] = hexdec(substr($hex, 5, 2));
349
+
350
+			if (isset($hex[7])) {
351
+				$alpha = substr($hex, 7, 2);
352
+				if (ctype_xdigit($alpha)) {
353
+					$c[3] = round(hexdec($alpha)/255, 2);
354
+				}
355
+			}
356
+		} else {
357
+			$c[0] = hexdec($hex[1] . $hex[1]);
358
+			$c[1] = hexdec($hex[2] . $hex[2]);
359
+			$c[2] = hexdec($hex[3] . $hex[3]);
360
+
361
+			if (isset($hex[4])) {
362
+				if (ctype_xdigit($hex[4])) {
363
+					$c[3] = round(hexdec($hex[4] . $hex[4])/255, 2);
364
+				}
365
+			}
366
+		}
367
+
368
+		return $c;
369
+	}
370
+
371
+	/**
372
+	 * Simple CSS parser
373
+	 *
374
+	 * @param $style
375
+	 *
376
+	 * @return array
377
+	 */
378
+	static function parseCssStyle($style)
379
+	{
380
+		$matches = array();
381
+		preg_match_all("/([a-z-]+)\\s*:\\s*([^;$]+)/si", $style, $matches, PREG_SET_ORDER);
382
+
383
+		$styles = array();
384
+		foreach ($matches as $match) {
385
+			$styles[$match[1]] = $match[2];
386
+		}
387
+
388
+		return $styles;
389
+	}
390
+
391
+	static $colorNames = array(
392
+		'antiquewhite'         => '#FAEBD7',
393
+		'aqua'                 => '#00FFFF',
394
+		'aquamarine'           => '#7FFFD4',
395
+		'beige'                => '#F5F5DC',
396
+		'black'                => '#000000',
397
+		'blue'                 => '#0000FF',
398
+		'brown'                => '#A52A2A',
399
+		'cadetblue'            => '#5F9EA0',
400
+		'chocolate'            => '#D2691E',
401
+		'cornflowerblue'       => '#6495ED',
402
+		'crimson'              => '#DC143C',
403
+		'darkblue'             => '#00008B',
404
+		'darkgoldenrod'        => '#B8860B',
405
+		'darkgreen'            => '#006400',
406
+		'darkmagenta'          => '#8B008B',
407
+		'darkorange'           => '#FF8C00',
408
+		'darkred'              => '#8B0000',
409
+		'darkseagreen'         => '#8FBC8F',
410
+		'darkslategray'        => '#2F4F4F',
411
+		'darkviolet'           => '#9400D3',
412
+		'deepskyblue'          => '#00BFFF',
413
+		'dodgerblue'           => '#1E90FF',
414
+		'firebrick'            => '#B22222',
415
+		'forestgreen'          => '#228B22',
416
+		'fuchsia'              => '#FF00FF',
417
+		'gainsboro'            => '#DCDCDC',
418
+		'gold'                 => '#FFD700',
419
+		'gray'                 => '#808080',
420
+		'green'                => '#008000',
421
+		'greenyellow'          => '#ADFF2F',
422
+		'hotpink'              => '#FF69B4',
423
+		'indigo'               => '#4B0082',
424
+		'khaki'                => '#F0E68C',
425
+		'lavenderblush'        => '#FFF0F5',
426
+		'lemonchiffon'         => '#FFFACD',
427
+		'lightcoral'           => '#F08080',
428
+		'lightgoldenrodyellow' => '#FAFAD2',
429
+		'lightgreen'           => '#90EE90',
430
+		'lightsalmon'          => '#FFA07A',
431
+		'lightskyblue'         => '#87CEFA',
432
+		'lightslategray'       => '#778899',
433
+		'lightyellow'          => '#FFFFE0',
434
+		'lime'                 => '#00FF00',
435
+		'limegreen'            => '#32CD32',
436
+		'magenta'              => '#FF00FF',
437
+		'maroon'               => '#800000',
438
+		'mediumaquamarine'     => '#66CDAA',
439
+		'mediumorchid'         => '#BA55D3',
440
+		'mediumseagreen'       => '#3CB371',
441
+		'mediumspringgreen'    => '#00FA9A',
442
+		'mediumvioletred'      => '#C71585',
443
+		'midnightblue'         => '#191970',
444
+		'mintcream'            => '#F5FFFA',
445
+		'moccasin'             => '#FFE4B5',
446
+		'navy'                 => '#000080',
447
+		'olive'                => '#808000',
448
+		'orange'               => '#FFA500',
449
+		'orchid'               => '#DA70D6',
450
+		'palegreen'            => '#98FB98',
451
+		'palevioletred'        => '#D87093',
452
+		'peachpuff'            => '#FFDAB9',
453
+		'pink'                 => '#FFC0CB',
454
+		'powderblue'           => '#B0E0E6',
455
+		'purple'               => '#800080',
456
+		'red'                  => '#FF0000',
457
+		'royalblue'            => '#4169E1',
458
+		'salmon'               => '#FA8072',
459
+		'seagreen'             => '#2E8B57',
460
+		'sienna'               => '#A0522D',
461
+		'silver'               => '#C0C0C0',
462
+		'skyblue'              => '#87CEEB',
463
+		'slategray'            => '#708090',
464
+		'springgreen'          => '#00FF7F',
465
+		'steelblue'            => '#4682B4',
466
+		'tan'                  => '#D2B48C',
467
+		'teal'                 => '#008080',
468
+		'thistle'              => '#D8BFD8',
469
+		'turquoise'            => '#40E0D0',
470
+		'violetred'            => '#D02090',
471
+		'white'                => '#FFFFFF',
472
+		'yellow'               => '#FFFF00',
473
+		'aliceblue'            => '#f0f8ff',
474
+		'azure'                => '#f0ffff',
475
+		'bisque'               => '#ffe4c4',
476
+		'blanchedalmond'       => '#ffebcd',
477
+		'blueviolet'           => '#8a2be2',
478
+		'burlywood'            => '#deb887',
479
+		'chartreuse'           => '#7fff00',
480
+		'coral'                => '#ff7f50',
481
+		'cornsilk'             => '#fff8dc',
482
+		'cyan'                 => '#00ffff',
483
+		'darkcyan'             => '#008b8b',
484
+		'darkgray'             => '#a9a9a9',
485
+		'darkgrey'             => '#a9a9a9',
486
+		'darkkhaki'            => '#bdb76b',
487
+		'darkolivegreen'       => '#556b2f',
488
+		'darkorchid'           => '#9932cc',
489
+		'darksalmon'           => '#e9967a',
490
+		'darkslateblue'        => '#483d8b',
491
+		'darkslategrey'        => '#2f4f4f',
492
+		'darkturquoise'        => '#00ced1',
493
+		'deeppink'             => '#ff1493',
494
+		'dimgray'              => '#696969',
495
+		'dimgrey'              => '#696969',
496
+		'floralwhite'          => '#fffaf0',
497
+		'ghostwhite'           => '#f8f8ff',
498
+		'goldenrod'            => '#daa520',
499
+		'grey'                 => '#808080',
500
+		'honeydew'             => '#f0fff0',
501
+		'indianred'            => '#cd5c5c',
502
+		'ivory'                => '#fffff0',
503
+		'lavender'             => '#e6e6fa',
504
+		'lawngreen'            => '#7cfc00',
505
+		'lightblue'            => '#add8e6',
506
+		'lightcyan'            => '#e0ffff',
507
+		'lightgray'            => '#d3d3d3',
508
+		'lightgrey'            => '#d3d3d3',
509
+		'lightpink'            => '#ffb6c1',
510
+		'lightseagreen'        => '#20b2aa',
511
+		'lightslategrey'       => '#778899',
512
+		'lightsteelblue'       => '#b0c4de',
513
+		'linen'                => '#faf0e6',
514
+		'mediumblue'           => '#0000cd',
515
+		'mediumpurple'         => '#9370db',
516
+		'mediumslateblue'      => '#7b68ee',
517
+		'mediumturquoise'      => '#48d1cc',
518
+		'mistyrose'            => '#ffe4e1',
519
+		'navajowhite'          => '#ffdead',
520
+		'oldlace'              => '#fdf5e6',
521
+		'olivedrab'            => '#6b8e23',
522
+		'orangered'            => '#ff4500',
523
+		'palegoldenrod'        => '#eee8aa',
524
+		'paleturquoise'        => '#afeeee',
525
+		'papayawhip'           => '#ffefd5',
526
+		'peru'                 => '#cd853f',
527
+		'plum'                 => '#dda0dd',
528
+		'rosybrown'            => '#bc8f8f',
529
+		'saddlebrown'          => '#8b4513',
530
+		'sandybrown'           => '#f4a460',
531
+		'seashell'             => '#fff5ee',
532
+		'slateblue'            => '#6a5acd',
533
+		'slategrey'            => '#708090',
534
+		'snow'                 => '#fffafa',
535
+		'tomato'               => '#ff6347',
536
+		'violet'               => '#ee82ee',
537
+		'wheat'                => '#f5deb3',
538
+		'whitesmoke'           => '#f5f5f5',
539
+		'yellowgreen'          => '#9acd32',
540
+	);
541 541
 }
Please login to merge, or discard this patch.
Spacing   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -121,7 +121,7 @@  discard block
 block discarded – undo
121 121
                             if ($_selector === ".$_class") {
122 122
                                 /** @var \Sabberworm\CSS\Rule\Rule $_rule */
123 123
                                 foreach ($_decl->getRules() as $_rule) {
124
-                                    $styles[$_rule->getRule()] = $_rule->getValue() . "";
124
+                                    $styles[$_rule->getRule()] = $_rule->getValue()."";
125 125
                                 }
126 126
 
127 127
                                 break 2;
@@ -133,7 +133,7 @@  discard block
 block discarded – undo
133 133
                     if ($_selector === $tag->tagName) {
134 134
                         /** @var \Sabberworm\CSS\Rule\Rule $_rule */
135 135
                         foreach ($_decl->getRules() as $_rule) {
136
-                            $styles[$_rule->getRule()] = $_rule->getValue() . "";
136
+                            $styles[$_rule->getRule()] = $_rule->getValue()."";
137 137
                         }
138 138
 
139 139
                         break;
@@ -168,7 +168,7 @@  discard block
 block discarded – undo
168 168
                         break;
169 169
 
170 170
                     case self::TYPE_NUMBER:
171
-                        $value = ($styles[$from] === null) ? null : (float)$styles[$from];
171
+                        $value = ($styles[$from] === null) ? null : (float) $styles[$from];
172 172
                         break;
173 173
 
174 174
                     default:
@@ -310,7 +310,7 @@  discard block
 block discarded – undo
310 310
         }
311 311
 
312 312
         $quad = preg_split("/\\s*[,\\/]\\s*/", trim(substr($color, $i + 1, $j - $i - 1)));
313
-        if (!isset($quad[3])) {
313
+        if ( ! isset($quad[3])) {
314 314
             $quad[3] = 1;
315 315
         }
316 316
 
@@ -350,17 +350,17 @@  discard block
 block discarded – undo
350 350
             if (isset($hex[7])) {
351 351
                 $alpha = substr($hex, 7, 2);
352 352
                 if (ctype_xdigit($alpha)) {
353
-                    $c[3] = round(hexdec($alpha)/255, 2);
353
+                    $c[3] = round(hexdec($alpha) / 255, 2);
354 354
                 }
355 355
             }
356 356
         } else {
357
-            $c[0] = hexdec($hex[1] . $hex[1]);
358
-            $c[1] = hexdec($hex[2] . $hex[2]);
359
-            $c[2] = hexdec($hex[3] . $hex[3]);
357
+            $c[0] = hexdec($hex[1].$hex[1]);
358
+            $c[1] = hexdec($hex[2].$hex[2]);
359
+            $c[2] = hexdec($hex[3].$hex[3]);
360 360
 
361 361
             if (isset($hex[4])) {
362 362
                 if (ctype_xdigit($hex[4])) {
363
-                    $c[3] = round(hexdec($hex[4] . $hex[4])/255, 2);
363
+                    $c[3] = round(hexdec($hex[4].$hex[4]) / 255, 2);
364 364
                 }
365 365
             }
366 366
         }
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/lib/Cpdf.php 2 patches
Indentation   +6448 added lines, -6448 removed lines patch added patch discarded remove patch
@@ -15,1339 +15,1339 @@  discard block
 block discarded – undo
15 15
 
16 16
 class Cpdf
17 17
 {
18
-    const PDF_VERSION = '1.7';
19
-
20
-    const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
21
-    const ACROFORM_SIG_APPENDONLY =       0x0002;
22
-
23
-    const ACROFORM_FIELD_BUTTON =   'Btn';
24
-    const ACROFORM_FIELD_TEXT =     'Tx';
25
-    const ACROFORM_FIELD_CHOICE =   'Ch';
26
-    const ACROFORM_FIELD_SIG =      'Sig';
27
-
28
-    const ACROFORM_FIELD_READONLY =               0x0001;
29
-    const ACROFORM_FIELD_REQUIRED =               0x0002;
30
-
31
-    const ACROFORM_FIELD_TEXT_MULTILINE =         0x1000;
32
-    const ACROFORM_FIELD_TEXT_PASSWORD =          0x2000;
33
-    const ACROFORM_FIELD_TEXT_RICHTEXT =         0x10000;
34
-
35
-    const ACROFORM_FIELD_CHOICE_COMBO =          0x20000;
36
-    const ACROFORM_FIELD_CHOICE_EDIT =           0x40000;
37
-    const ACROFORM_FIELD_CHOICE_SORT =           0x80000;
38
-    const ACROFORM_FIELD_CHOICE_MULTISELECT =   0x200000;
39
-
40
-    const XOBJECT_SUBTYPE_FORM = 'Form';
41
-
42
-    /**
43
-     * @var integer The current number of pdf objects in the document
44
-     */
45
-    public $numObj = 0;
46
-
47
-    /**
48
-     * @var array This array contains all of the pdf objects, ready for final assembly
49
-     */
50
-    public $objects = [];
51
-
52
-    /**
53
-     * @var integer The objectId (number within the objects array) of the document catalog
54
-     */
55
-    public $catalogId;
56
-
57
-    /**
58
-     * @var integer The objectId (number within the objects array) of indirect references (Javascript EmbeddedFiles)
59
-     */
60
-    protected $indirectReferenceId = 0;
61
-
62
-    /**
63
-     * @var integer The objectId (number within the objects array)
64
-     */
65
-    protected $embeddedFilesId = 0;
66
-
67
-    /**
68
-     * AcroForm objectId
69
-     *
70
-     * @var integer
71
-     */
72
-    public $acroFormId;
73
-
74
-    /**
75
-     * @var int
76
-     */
77
-    public $signatureMaxLen = 5000;
78
-
79
-    /**
80
-     * @var array Array carrying information about the fonts that the system currently knows about
81
-     * Used to ensure that a font is not loaded twice, among other things
82
-     */
83
-    public $fonts = [];
84
-
85
-    /**
86
-     * @var string The default font metrics file to use if no other font has been loaded.
87
-     * The path to the directory containing the font metrics should be included
88
-     */
89
-    public $defaultFont = './fonts/Helvetica.afm';
90
-
91
-    /**
92
-     * @string A record of the current font
93
-     */
94
-    public $currentFont = '';
95
-
96
-    /**
97
-     * @var string The current base font
98
-     */
99
-    public $currentBaseFont = '';
100
-
101
-    /**
102
-     * @var integer The number of the current font within the font array
103
-     */
104
-    public $currentFontNum = 0;
105
-
106
-    /**
107
-     * @var integer
108
-     */
109
-    public $currentNode;
110
-
111
-    /**
112
-     * @var integer Object number of the current page
113
-     */
114
-    public $currentPage;
115
-
116
-    /**
117
-     * @var integer Object number of the currently active contents block
118
-     */
119
-    public $currentContents;
120
-
121
-    /**
122
-     * @var integer Number of fonts within the system
123
-     */
124
-    public $numFonts = 0;
125
-
126
-    /**
127
-     * @var integer Number of graphic state resources used
128
-     */
129
-    private $numStates = 0;
130
-
131
-    /**
132
-     * @var array Number of graphic state resources used
133
-     */
134
-    private $gstates = [];
135
-
136
-    /**
137
-     * @var array Current color for fill operations, defaults to inactive value,
138
-     * all three components should be between 0 and 1 inclusive when active
139
-     */
140
-    public $currentColor = null;
141
-
142
-    /**
143
-     * @var array Current color for stroke operations (lines etc.)
144
-     */
145
-    public $currentStrokeColor = null;
146
-
147
-    /**
148
-     * @var string Fill rule (nonzero or evenodd)
149
-     */
150
-    public $fillRule = "nonzero";
151
-
152
-    /**
153
-     * @var string Current style that lines are drawn in
154
-     */
155
-    public $currentLineStyle = '';
156
-
157
-    /**
158
-     * @var array Current line transparency (partial graphics state)
159
-     */
160
-    public $currentLineTransparency = ["mode" => "Normal", "opacity" => 1.0];
161
-
162
-    /**
163
-     * array Current fill transparency (partial graphics state)
164
-     */
165
-    public $currentFillTransparency = ["mode" => "Normal", "opacity" => 1.0];
166
-
167
-    /**
168
-     * @var array An array which is used to save the state of the document, mainly the colors and styles
169
-     * it is used to temporarily change to another state, then change back to what it was before
170
-     */
171
-    public $stateStack = [];
172
-
173
-    /**
174
-     * @var integer Number of elements within the state stack
175
-     */
176
-    public $nStateStack = 0;
177
-
178
-    /**
179
-     * @var integer Number of page objects within the document
180
-     */
181
-    public $numPages = 0;
182
-
183
-    /**
184
-     * @var array Object Id storage stack
185
-     */
186
-    public $stack = [];
187
-
188
-    /**
189
-     * @var integer Number of elements within the object Id storage stack
190
-     */
191
-    public $nStack = 0;
192
-
193
-    /**
194
-     * an array which contains information about the objects which are not firmly attached to pages
195
-     * these have been added with the addObject function
196
-     */
197
-    public $looseObjects = [];
198
-
199
-    /**
200
-     * array contains information about how the loose objects are to be added to the document
201
-     */
202
-    public $addLooseObjects = [];
203
-
204
-    /**
205
-     * @var integer The objectId of the information object for the document
206
-     * this contains authorship, title etc.
207
-     */
208
-    public $infoObject = 0;
209
-
210
-    /**
211
-     * @var integer Number of images being tracked within the document
212
-     */
213
-    public $numImages = 0;
214
-
215
-    /**
216
-     * @var array An array containing options about the document
217
-     * it defaults to turning on the compression of the objects
218
-     */
219
-    public $options = ['compression' => true];
220
-
221
-    /**
222
-     * @var integer The objectId of the first page of the document
223
-     */
224
-    public $firstPageId;
225
-
226
-    /**
227
-     * @var integer The object Id of the procset object
228
-     */
229
-    public $procsetObjectId;
230
-
231
-    /**
232
-     * @var array Store the information about the relationship between font families
233
-     * this used so that the code knows which font is the bold version of another font, etc.
234
-     * the value of this array is initialised in the constructor function.
235
-     */
236
-    public $fontFamilies = [];
237
-
238
-    /**
239
-     * @var string Folder for php serialized formats of font metrics files.
240
-     * If empty string, use same folder as original metrics files.
241
-     * This can be passed in from class creator.
242
-     * If this folder does not exist or is not writable, Cpdf will be **much** slower.
243
-     * Because of potential trouble with php safe mode, folder cannot be created at runtime.
244
-     */
245
-    public $fontcache = '';
246
-
247
-    /**
248
-     * @var integer The version of the font metrics cache file.
249
-     * This value must be manually incremented whenever the internal font data structure is modified.
250
-     */
251
-    public $fontcacheVersion = 6;
252
-
253
-    /**
254
-     * @var string Temporary folder.
255
-     * If empty string, will attempt system tmp folder.
256
-     * This can be passed in from class creator.
257
-     */
258
-    public $tmp = '';
259
-
260
-    /**
261
-     * @var string Track if the current font is bolded or italicised
262
-     */
263
-    public $currentTextState = '';
264
-
265
-    /**
266
-     * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
267
-     */
268
-    public $messages = '';
269
-
270
-    /**
271
-     * @var string The encryption array for the document encryption is stored here
272
-     */
273
-    public $arc4 = '';
274
-
275
-    /**
276
-     * @var integer The object Id of the encryption information
277
-     */
278
-    public $arc4_objnum = 0;
279
-
280
-    /**
281
-     * @var string The file identifier, used to uniquely identify a pdf document
282
-     */
283
-    public $fileIdentifier = '';
284
-
285
-    /**
286
-     * @var boolean A flag to say if a document is to be encrypted or not
287
-     */
288
-    public $encrypted = false;
289
-
290
-    /**
291
-     * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
292
-     */
293
-    public $encryptionKey = '';
294
-
295
-    /**
296
-     * @var array Array which forms a stack to keep track of nested callback functions
297
-     */
298
-    public $callback = [];
299
-
300
-    /**
301
-     * @var integer The number of callback functions in the callback array
302
-     */
303
-    public $nCallback = 0;
304
-
305
-    /**
306
-     * @var array Store label->id pairs for named destinations, these will be used to replace internal links
307
-     * done this way so that destinations can be defined after the location that links to them
308
-     */
309
-    public $destinations = [];
310
-
311
-    /**
312
-     * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
313
-     * publiciables within the class, so that the user can rollback at will (from each 'start' command)
314
-     * note that this includes the objects array, so these can be large.
315
-     */
316
-    public $checkpoint = '';
317
-
318
-    /**
319
-     * @var array Table of Image origin filenames and image labels which were already added with o_image().
320
-     * Allows to merge identical images
321
-     */
322
-    public $imagelist = [];
323
-
324
-    /**
325
-     * @var array Table of already added alpha and plain image files for transparent PNG images.
326
-     */
327
-    protected $imageAlphaList = [];
328
-
329
-    /**
330
-     * @var array List of temporary image files to be deleted after processing.
331
-     */
332
-    protected $imageCache = [];
333
-
334
-    /**
335
-     * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
336
-     */
337
-    public $isUnicode = false;
338
-
339
-    /**
340
-     * @var string the JavaScript code of the document
341
-     */
342
-    public $javascript = '';
343
-
344
-    /**
345
-     * @var boolean whether the compression is possible
346
-     */
347
-    protected $compressionReady = false;
348
-
349
-    /**
350
-     * @var array Current page size
351
-     */
352
-    protected $currentPageSize = ["width" => 0, "height" => 0];
353
-
354
-    /**
355
-     * @var array All the chars that will be required in the font subsets
356
-     */
357
-    protected $stringSubsets = [];
358
-
359
-    /**
360
-     * @var string The target internal encoding
361
-     */
362
-    protected static $targetEncoding = 'Windows-1252';
363
-
364
-    /**
365
-     * @var array
366
-     */
367
-    protected $byteRange = array();
368
-
369
-    /**
370
-     * @var array The list of the core fonts
371
-     */
372
-    protected static $coreFonts = [
373
-        'courier',
374
-        'courier-bold',
375
-        'courier-oblique',
376
-        'courier-boldoblique',
377
-        'helvetica',
378
-        'helvetica-bold',
379
-        'helvetica-oblique',
380
-        'helvetica-boldoblique',
381
-        'times-roman',
382
-        'times-bold',
383
-        'times-italic',
384
-        'times-bolditalic',
385
-        'symbol',
386
-        'zapfdingbats'
387
-    ];
388
-
389
-    /**
390
-     * Class constructor
391
-     * This will start a new document
392
-     *
393
-     * @param array   $pageSize  Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
394
-     * @param boolean $isUnicode Whether text will be treated as Unicode or not.
395
-     * @param string  $fontcache The font cache folder
396
-     * @param string  $tmp       The temporary folder
397
-     */
398
-    function __construct($pageSize = [0, 0, 612, 792], $isUnicode = false, $fontcache = '', $tmp = '')
399
-    {
400
-        $this->isUnicode = $isUnicode;
401
-        $this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
402
-        $this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
403
-        $this->newDocument($pageSize);
404
-
405
-        $this->compressionReady = function_exists('gzcompress');
406
-
407
-        if (in_array('Windows-1252', mb_list_encodings())) {
408
-            self::$targetEncoding = 'Windows-1252';
409
-        }
410
-
411
-        // also initialize the font families that are known about already
412
-        $this->setFontFamily('init');
413
-    }
414
-
415
-    public function __destruct()
416
-    {
417
-        foreach ($this->imageCache as $file) {
418
-            if (file_exists($file)) {
419
-                unlink($file);
420
-            }
421
-        }
422
-    }
423
-
424
-    /**
425
-     * Document object methods (internal use only)
426
-     *
427
-     * There is about one object method for each type of object in the pdf document
428
-     * Each function has the same call list ($id,$action,$options).
429
-     * $id = the object ID of the object, or what it is to be if it is being created
430
-     * $action = a string specifying the action to be performed, though ALL must support:
431
-     *           'new' - create the object with the id $id
432
-     *           'out' - produce the output for the pdf object
433
-     * $options = optional, a string or array containing the various parameters for the object
434
-     *
435
-     * These, in conjunction with the output function are the ONLY way for output to be produced
436
-     * within the pdf 'file'.
437
-     */
438
-
439
-    /**
440
-     * Destination object, used to specify the location for the user to jump to, presently on opening
441
-     *
442
-     * @param $id
443
-     * @param $action
444
-     * @param string $options
445
-     * @return string|null
446
-     */
447
-    protected function o_destination($id, $action, $options = '')
448
-    {
449
-        switch ($action) {
450
-            case 'new':
451
-                $this->objects[$id] = ['t' => 'destination', 'info' => []];
452
-                $tmp = '';
453
-                switch ($options['type']) {
454
-                    case 'XYZ':
455
-                    /** @noinspection PhpMissingBreakStatementInspection */
456
-                    case 'FitR':
457
-                        $tmp = ' ' . $options['p3'] . $tmp;
458
-                    case 'FitH':
459
-                    case 'FitV':
460
-                    case 'FitBH':
461
-                    /** @noinspection PhpMissingBreakStatementInspection */
462
-                    case 'FitBV':
463
-                        $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
464
-                    case 'Fit':
465
-                    case 'FitB':
466
-                        $tmp = $options['type'] . $tmp;
467
-                        $this->objects[$id]['info']['string'] = $tmp;
468
-                        $this->objects[$id]['info']['page'] = $options['page'];
469
-                }
470
-                break;
471
-
472
-            case 'out':
473
-                $o = &$this->objects[$id];
474
-
475
-                $tmp = $o['info'];
476
-                $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
477
-
478
-                return $res;
479
-        }
480
-
481
-        return null;
482
-    }
483
-
484
-    /**
485
-     * set the viewer preferences
486
-     *
487
-     * @param $id
488
-     * @param $action
489
-     * @param string|array $options
490
-     * @return string|null
491
-     */
492
-    protected function o_viewerPreferences($id, $action, $options = '')
493
-    {
494
-        switch ($action) {
495
-            case 'new':
496
-                $this->objects[$id] = ['t' => 'viewerPreferences', 'info' => []];
497
-                break;
498
-
499
-            case 'add':
500
-                $o = &$this->objects[$id];
501
-
502
-                foreach ($options as $k => $v) {
503
-                    switch ($k) {
504
-                        // Boolean keys
505
-                        case 'HideToolbar':
506
-                        case 'HideMenubar':
507
-                        case 'HideWindowUI':
508
-                        case 'FitWindow':
509
-                        case 'CenterWindow':
510
-                        case 'DisplayDocTitle':
511
-                        case 'PickTrayByPDFSize':
512
-                            $o['info'][$k] = (bool)$v;
513
-                            break;
514
-
515
-                        // Integer keys
516
-                        case 'NumCopies':
517
-                            $o['info'][$k] = (int)$v;
518
-                            break;
519
-
520
-                        // Name keys
521
-                        case 'ViewArea':
522
-                        case 'ViewClip':
523
-                        case 'PrintClip':
524
-                        case 'PrintArea':
525
-                            $o['info'][$k] = (string)$v;
526
-                            break;
527
-
528
-                        // Named with limited valid values
529
-                        case 'NonFullScreenPageMode':
530
-                            if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
531
-                                break;
532
-                            }
533
-                            $o['info'][$k] = $v;
534
-                            break;
535
-
536
-                        case 'Direction':
537
-                            if (!in_array($v, ['L2R', 'R2L'])) {
538
-                                break;
539
-                            }
540
-                            $o['info'][$k] = $v;
541
-                            break;
542
-
543
-                        case 'PrintScaling':
544
-                            if (!in_array($v, ['None', 'AppDefault'])) {
545
-                                break;
546
-                            }
547
-                            $o['info'][$k] = $v;
548
-                            break;
549
-
550
-                        case 'Duplex':
551
-                            if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
552
-                                break;
553
-                            }
554
-                            $o['info'][$k] = $v;
555
-                            break;
556
-
557
-                        // Integer array
558
-                        case 'PrintPageRange':
559
-                            // Cast to integer array
560
-                            foreach ($v as $vK => $vV) {
561
-                                $v[$vK] = (int)$vV;
562
-                            }
563
-                            $o['info'][$k] = array_values($v);
564
-                            break;
565
-                    }
566
-                }
567
-                break;
568
-
569
-            case 'out':
570
-                $o = &$this->objects[$id];
571
-                $res = "\n$id 0 obj\n<< ";
572
-
573
-                foreach ($o['info'] as $k => $v) {
574
-                    if (is_string($v)) {
575
-                        $v = '/' . $v;
576
-                    } elseif (is_int($v)) {
577
-                        $v = (string) $v;
578
-                    } elseif (is_bool($v)) {
579
-                        $v = ($v ? 'true' : 'false');
580
-                    } elseif (is_array($v)) {
581
-                        $v = '[' . implode(' ', $v) . ']';
582
-                    }
583
-                    $res .= "\n/$k $v";
584
-                }
585
-                $res .= "\n>>\nendobj";
586
-
587
-                return $res;
588
-        }
589
-
590
-        return null;
591
-    }
592
-
593
-    /**
594
-     * define the document catalog, the overall controller for the document
595
-     *
596
-     * @param $id
597
-     * @param $action
598
-     * @param string|array $options
599
-     * @return string|null
600
-     */
601
-    protected function o_catalog($id, $action, $options = '')
602
-    {
603
-        if ($action !== 'new') {
604
-            $o = &$this->objects[$id];
605
-        }
606
-
607
-        switch ($action) {
608
-            case 'new':
609
-                $this->objects[$id] = ['t' => 'catalog', 'info' => []];
610
-                $this->catalogId = $id;
611
-                break;
612
-
613
-            case 'acroform':
614
-            case 'outlines':
615
-            case 'pages':
616
-            case 'openHere':
617
-            case 'names':
618
-                $o['info'][$action] = $options;
619
-                break;
620
-
621
-            case 'viewerPreferences':
622
-                if (!isset($o['info']['viewerPreferences'])) {
623
-                    $this->numObj++;
624
-                    $this->o_viewerPreferences($this->numObj, 'new');
625
-                    $o['info']['viewerPreferences'] = $this->numObj;
626
-                }
627
-
628
-                $vp = $o['info']['viewerPreferences'];
629
-                $this->o_viewerPreferences($vp, 'add', $options);
630
-
631
-                break;
632
-
633
-            case 'out':
634
-                $res = "\n$id 0 obj\n<< /Type /Catalog";
635
-
636
-                foreach ($o['info'] as $k => $v) {
637
-                    switch ($k) {
638
-                        case 'outlines':
639
-                            $res .= "\n/Outlines $v 0 R";
640
-                            break;
641
-
642
-                        case 'pages':
643
-                            $res .= "\n/Pages $v 0 R";
644
-                            break;
645
-
646
-                        case 'viewerPreferences':
647
-                            $res .= "\n/ViewerPreferences $v 0 R";
648
-                            break;
649
-
650
-                        case 'openHere':
651
-                            $res .= "\n/OpenAction $v 0 R";
652
-                            break;
653
-
654
-                        case 'names':
655
-                            $res .= "\n/Names $v 0 R";
656
-                            break;
657
-
658
-                        case 'acroform':
659
-                            $res .= "\n/AcroForm $v 0 R";
660
-                            break;
661
-                    }
662
-                }
663
-
664
-                $res .= " >>\nendobj";
665
-
666
-                return $res;
667
-        }
668
-
669
-        return null;
670
-    }
671
-
672
-    /**
673
-     * object which is a parent to the pages in the document
674
-     *
675
-     * @param $id
676
-     * @param $action
677
-     * @param string $options
678
-     * @return string|null
679
-     */
680
-    protected function o_pages($id, $action, $options = '')
681
-    {
682
-        if ($action !== 'new') {
683
-            $o = &$this->objects[$id];
684
-        }
685
-
686
-        switch ($action) {
687
-            case 'new':
688
-                $this->objects[$id] = ['t' => 'pages', 'info' => []];
689
-                $this->o_catalog($this->catalogId, 'pages', $id);
690
-                break;
691
-
692
-            case 'page':
693
-                if (!is_array($options)) {
694
-                    // then it will just be the id of the new page
695
-                    $o['info']['pages'][] = $options;
696
-                } else {
697
-                    // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
698
-                    // and pos is either 'before' or 'after', saying where this page will fit.
699
-                    if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
700
-                        $i = array_search($options['rid'], $o['info']['pages']);
701
-                        if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
702
-
703
-                            // then there is a match
704
-                            // make a space
705
-                            switch ($options['pos']) {
706
-                                case 'before':
707
-                                    $k = $i;
708
-                                    break;
709
-
710
-                                case 'after':
711
-                                    $k = $i + 1;
712
-                                    break;
713
-
714
-                                default:
715
-                                    $k = -1;
716
-                                    break;
717
-                            }
718
-
719
-                            if ($k >= 0) {
720
-                                for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
721
-                                    $o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
722
-                                }
723
-
724
-                                $o['info']['pages'][$k] = $options['id'];
725
-                            }
726
-                        }
727
-                    }
728
-                }
729
-                break;
730
-
731
-            case 'procset':
732
-                $o['info']['procset'] = $options;
733
-                break;
734
-
735
-            case 'mediaBox':
736
-                $o['info']['mediaBox'] = $options;
737
-                // which should be an array of 4 numbers
738
-                $this->currentPageSize = ['width' => $options[2], 'height' => $options[3]];
739
-                break;
740
-
741
-            case 'font':
742
-                $o['info']['fonts'][] = ['objNum' => $options['objNum'], 'fontNum' => $options['fontNum']];
743
-                break;
744
-
745
-            case 'extGState':
746
-                $o['info']['extGStates'][] = ['objNum' => $options['objNum'], 'stateNum' => $options['stateNum']];
747
-                break;
748
-
749
-            case 'xObject':
750
-                $o['info']['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
751
-                break;
752
-
753
-            case 'out':
754
-                if (count($o['info']['pages'])) {
755
-                    $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
756
-                    foreach ($o['info']['pages'] as $v) {
757
-                        $res .= "$v 0 R\n";
758
-                    }
759
-
760
-                    $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
761
-
762
-                    if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
763
-                        isset($o['info']['procset']) ||
764
-                        (isset($o['info']['extGStates']) && count($o['info']['extGStates']))
765
-                    ) {
766
-                        $res .= "\n/Resources <<";
767
-
768
-                        if (isset($o['info']['procset'])) {
769
-                            $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
770
-                        }
771
-
772
-                        if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
773
-                            $res .= "\n/Font << ";
774
-                            foreach ($o['info']['fonts'] as $finfo) {
775
-                                $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
776
-                            }
777
-                            $res .= "\n>>";
778
-                        }
779
-
780
-                        if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
781
-                            $res .= "\n/XObject << ";
782
-                            foreach ($o['info']['xObjects'] as $finfo) {
783
-                                $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
784
-                            }
785
-                            $res .= "\n>>";
786
-                        }
787
-
788
-                        if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
789
-                            $res .= "\n/ExtGState << ";
790
-                            foreach ($o['info']['extGStates'] as $gstate) {
791
-                                $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
792
-                            }
793
-                            $res .= "\n>>";
794
-                        }
795
-
796
-                        $res .= "\n>>";
797
-                        if (isset($o['info']['mediaBox'])) {
798
-                            $tmp = $o['info']['mediaBox'];
799
-                            $res .= "\n/MediaBox [" . sprintf(
800
-                                    '%.3F %.3F %.3F %.3F',
801
-                                    $tmp[0],
802
-                                    $tmp[1],
803
-                                    $tmp[2],
804
-                                    $tmp[3]
805
-                                ) . ']';
806
-                        }
807
-                    }
808
-
809
-                    $res .= "\n >>\nendobj";
810
-                } else {
811
-                    $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
812
-                }
813
-
814
-                return $res;
815
-        }
816
-
817
-        return null;
818
-    }
819
-
820
-    /**
821
-     * define the outlines in the doc, empty for now
822
-     *
823
-     * @param $id
824
-     * @param $action
825
-     * @param string $options
826
-     * @return string|null
827
-     */
828
-    protected function o_outlines($id, $action, $options = '')
829
-    {
830
-        if ($action !== 'new') {
831
-            $o = &$this->objects[$id];
832
-        }
833
-
834
-        switch ($action) {
835
-            case 'new':
836
-                $this->objects[$id] = ['t' => 'outlines', 'info' => ['outlines' => []]];
837
-                $this->o_catalog($this->catalogId, 'outlines', $id);
838
-                break;
839
-
840
-            case 'outline':
841
-                $o['info']['outlines'][] = $options;
842
-                break;
843
-
844
-            case 'out':
845
-                if (count($o['info']['outlines'])) {
846
-                    $res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
847
-                    foreach ($o['info']['outlines'] as $v) {
848
-                        $res .= "$v 0 R ";
849
-                    }
850
-
851
-                    $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
852
-                } else {
853
-                    $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
854
-                }
855
-
856
-                return $res;
857
-        }
858
-
859
-        return null;
860
-    }
861
-
862
-    /**
863
-     * an object to hold the font description
864
-     *
865
-     * @param $id
866
-     * @param $action
867
-     * @param string|array $options
868
-     * @return string|null
869
-     * @throws FontNotFoundException
870
-     */
871
-    protected function o_font($id, $action, $options = '')
872
-    {
873
-        if ($action !== 'new') {
874
-            $o = &$this->objects[$id];
875
-        }
876
-
877
-        switch ($action) {
878
-            case 'new':
879
-                $this->objects[$id] = [
880
-                    't'    => 'font',
881
-                    'info' => [
882
-                        'name'         => $options['name'],
883
-                        'fontFileName' => $options['fontFileName'],
884
-                        'SubType'      => 'Type1',
885
-                        'isSubsetting'   => $options['isSubsetting']
886
-                    ]
887
-                ];
888
-                $fontNum = $this->numFonts;
889
-                $this->objects[$id]['info']['fontNum'] = $fontNum;
890
-
891
-                // deal with the encoding and the differences
892
-                if (isset($options['differences'])) {
893
-                    // then we'll need an encoding dictionary
894
-                    $this->numObj++;
895
-                    $this->o_fontEncoding($this->numObj, 'new', $options);
896
-                    $this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
897
-                } else {
898
-                    if (isset($options['encoding'])) {
899
-                        // we can specify encoding here
900
-                        switch ($options['encoding']) {
901
-                            case 'WinAnsiEncoding':
902
-                            case 'MacRomanEncoding':
903
-                            case 'MacExpertEncoding':
904
-                                $this->objects[$id]['info']['encoding'] = $options['encoding'];
905
-                                break;
906
-
907
-                            case 'none':
908
-                                break;
909
-
910
-                            default:
911
-                                $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
912
-                                break;
913
-                        }
914
-                    } else {
915
-                        $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
916
-                    }
917
-                }
918
-
919
-                if ($this->fonts[$options['fontFileName']]['isUnicode']) {
920
-                    // For Unicode fonts, we need to incorporate font data into
921
-                    // sub-sections that are linked from the primary font section.
922
-                    // Look at o_fontGIDtoCID and o_fontDescendentCID functions
923
-                    // for more information.
924
-                    //
925
-                    // All of this code is adapted from the excellent changes made to
926
-                    // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
927
-
928
-                    $toUnicodeId = ++$this->numObj;
929
-                    $this->o_toUnicode($toUnicodeId, 'new');
930
-                    $this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
931
-
932
-                    $cidFontId = ++$this->numObj;
933
-                    $this->o_fontDescendentCID($cidFontId, 'new', $options);
934
-                    $this->objects[$id]['info']['cidFont'] = $cidFontId;
935
-                }
936
-
937
-                // also tell the pages node about the new font
938
-                $this->o_pages($this->currentNode, 'font', ['fontNum' => $fontNum, 'objNum' => $id]);
939
-                break;
940
-
941
-            case 'add':
942
-                $font_options = $this->processFont($id, $o['info']);
943
-
944
-                if ($font_options !== false) {
945
-                    foreach ($font_options as $k => $v) {
946
-                        switch ($k) {
947
-                            case 'BaseFont':
948
-                                $o['info']['name'] = $v;
949
-                                break;
950
-                            case 'FirstChar':
951
-                            case 'LastChar':
952
-                            case 'Widths':
953
-                            case 'FontDescriptor':
954
-                            case 'SubType':
955
-                                $this->addMessage('o_font ' . $k . " : " . $v);
956
-                                $o['info'][$k] = $v;
957
-                                break;
958
-                        }
959
-                    }
960
-
961
-                    // pass values down to descendent font
962
-                    if (isset($o['info']['cidFont'])) {
963
-                        $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $font_options);
964
-                    }
965
-                }
966
-                break;
967
-
968
-            case 'out':
969
-                if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
970
-                    // For Unicode fonts, we need to incorporate font data into
971
-                    // sub-sections that are linked from the primary font section.
972
-                    // Look at o_fontGIDtoCID and o_fontDescendentCID functions
973
-                    // for more information.
974
-                    //
975
-                    // All of this code is adapted from the excellent changes made to
976
-                    // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
977
-
978
-                    $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
979
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
980
-
981
-                    // The horizontal identity mapping for 2-byte CIDs; may be used
982
-                    // with CIDFonts using any Registry, Ordering, and Supplement values.
983
-                    $res .= "/Encoding /Identity-H\n";
984
-                    $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
985
-                    $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
986
-                    $res .= ">>\n";
987
-                    $res .= "endobj";
988
-                } else {
989
-                    $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
990
-                    $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
991
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
992
-
993
-                    if (isset($o['info']['encodingDictionary'])) {
994
-                        // then place a reference to the dictionary
995
-                        $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
996
-                    } else {
997
-                        if (isset($o['info']['encoding'])) {
998
-                            // use the specified encoding
999
-                            $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
1000
-                        }
1001
-                    }
1002
-
1003
-                    if (isset($o['info']['FirstChar'])) {
1004
-                        $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
1005
-                    }
1006
-
1007
-                    if (isset($o['info']['LastChar'])) {
1008
-                        $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
1009
-                    }
1010
-
1011
-                    if (isset($o['info']['Widths'])) {
1012
-                        $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
1013
-                    }
1014
-
1015
-                    if (isset($o['info']['FontDescriptor'])) {
1016
-                        $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1017
-                    }
1018
-
1019
-                    $res .= ">>\n";
1020
-                    $res .= "endobj";
1021
-                }
1022
-
1023
-                return $res;
1024
-        }
1025
-
1026
-        return null;
1027
-    }
1028
-
1029
-    protected function getFontSubsettingTag(array $font): string
1030
-    {
1031
-        // convert font num to hexavigesimal numeral system letters A - Z only
1032
-        $base_26 = strtoupper(base_convert($font['fontNum'], 10, 26));
1033
-        for ($i = 0; $i < strlen($base_26); $i++) {
1034
-            $char = $base_26[$i];
1035
-            if ($char <= "9") {
1036
-                $base_26[$i] = chr(65 + intval($char));
1037
-            } else {
1038
-                $base_26[$i] = chr(ord($char) + 10);
1039
-            }
1040
-        }
1041
-
1042
-        return 'SUB' . str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1043
-    }
1044
-
1045
-    /**
1046
-     * @param int $fontObjId
1047
-     * @param array $object_info
1048
-     * @return array|false
1049
-     * @throws FontNotFoundException
1050
-     */
1051
-    private function processFont(int $fontObjId, array $object_info)
1052
-    {
1053
-        $fontFileName = $object_info['fontFileName'];
1054
-        if (!isset($this->fonts[$fontFileName])) {
1055
-            return false;
1056
-        }
1057
-
1058
-        $font = &$this->fonts[$fontFileName];
1059
-
1060
-        $fileSuffix = $font['fileSuffix'];
1061
-        $fileSuffixLower = strtolower($font['fileSuffix']);
1062
-        $fbfile = "$fontFileName.$fileSuffix";
1063
-        $isTtfFont = $fileSuffixLower === 'ttf';
1064
-        $isPfbFont = $fileSuffixLower === 'pfb';
1065
-
1066
-        $this->addMessage('selectFont: checking for - ' . $fbfile);
1067
-
1068
-        if (!$fileSuffix) {
1069
-            $this->addMessage(
1070
-                'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
1071
-            );
1072
-
1073
-            return false;
1074
-        } else {
1075
-            $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
1076
-            //        $fontObj = $this->numObj;
1077
-            $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
1078
-
1079
-            // find the array of font widths, and put that into an object.
1080
-            $firstChar = -1;
1081
-            $lastChar = 0;
1082
-            $widths = [];
1083
-            $cid_widths = [];
1084
-
1085
-            foreach ($font['C'] as $num => $d) {
1086
-                if (intval($num) > 0 || $num == '0') {
1087
-                    if (!$font['isUnicode']) {
1088
-                        // With Unicode, widths array isn't used
1089
-                        if ($lastChar > 0 && $num > $lastChar + 1) {
1090
-                            for ($i = $lastChar + 1; $i < $num; $i++) {
1091
-                                $widths[] = 0;
1092
-                            }
1093
-                        }
1094
-                    }
1095
-
1096
-                    $widths[] = $d;
1097
-
1098
-                    if ($font['isUnicode']) {
1099
-                        $cid_widths[$num] = $d;
1100
-                    }
1101
-
1102
-                    if ($firstChar == -1) {
1103
-                        $firstChar = $num;
1104
-                    }
1105
-
1106
-                    $lastChar = $num;
1107
-                }
1108
-            }
1109
-
1110
-            // also need to adjust the widths for the differences array
1111
-            if (isset($object['differences'])) {
1112
-                foreach ($object['differences'] as $charNum => $charName) {
1113
-                    if ($charNum > $lastChar) {
1114
-                        if (!$object['isUnicode']) {
1115
-                            // With Unicode, widths array isn't used
1116
-                            for ($i = $lastChar + 1; $i <= $charNum; $i++) {
1117
-                                $widths[] = 0;
1118
-                            }
1119
-                        }
1120
-
1121
-                        $lastChar = $charNum;
1122
-                    }
1123
-
1124
-                    if (isset($font['C'][$charName])) {
1125
-                        $widths[$charNum - $firstChar] = $font['C'][$charName];
1126
-                        if ($font['isUnicode']) {
1127
-                            $cid_widths[$charName] = $font['C'][$charName];
1128
-                        }
1129
-                    }
1130
-                }
1131
-            }
1132
-
1133
-            if ($font['isUnicode']) {
1134
-                $font['CIDWidths'] = $cid_widths;
1135
-            }
1136
-
1137
-            $this->addMessage('selectFont: FirstChar = ' . $firstChar);
1138
-            $this->addMessage('selectFont: LastChar = ' . $lastChar);
1139
-
1140
-            $widthid = -1;
1141
-
1142
-            if (!$font['isUnicode']) {
1143
-                // With Unicode, widths array isn't used
1144
-
1145
-                $this->numObj++;
1146
-                $this->o_contents($this->numObj, 'new', 'raw');
1147
-                $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
1148
-                $widthid = $this->numObj;
1149
-            }
1150
-
1151
-            $missing_width = 500;
1152
-            $stemV = 70;
1153
-
1154
-            if (isset($font['MissingWidth'])) {
1155
-                $missing_width = $font['MissingWidth'];
1156
-            }
1157
-            if (isset($font['StdVW'])) {
1158
-                $stemV = $font['StdVW'];
1159
-            } else {
1160
-                if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
1161
-                    $stemV = 120;
1162
-                }
1163
-            }
1164
-
1165
-            // load the pfb file, and put that into an object too.
1166
-            // note that pdf supports only binary format type 1 font files, though there is a
1167
-            // simple utility to convert them from pfa to pfb.
1168
-            if (!$font['isSubsetting']) {
1169
-                $data = file_get_contents($fbfile);
1170
-            } else {
1171
-                $adobeFontName = $this->getFontSubsettingTag($font) . '+' . $adobeFontName;
1172
-                $this->stringSubsets[$fontFileName][] = 32; // Force space if not in yet
1173
-
1174
-                $subset = $this->stringSubsets[$fontFileName];
1175
-                sort($subset);
1176
-
1177
-                // Load font
1178
-                $font_obj = Font::load($fbfile);
1179
-                $font_obj->parse();
1180
-
1181
-                // Define subset
1182
-                $font_obj->setSubset($subset);
1183
-                $font_obj->reduce();
1184
-
1185
-                // Write new font
1186
-                $tmp_name = @tempnam($this->tmp, "cpdf_subset_");
1187
-                $font_obj->open($tmp_name, BinaryStream::modeReadWrite);
1188
-                $font_obj->encode(["OS/2"]);
1189
-                $font_obj->close();
1190
-
1191
-                // Parse the new font to get cid2gid and widths
1192
-                $font_obj = Font::load($tmp_name);
1193
-
1194
-                // Find Unicode char map table
1195
-                $subtable = null;
1196
-                foreach ($font_obj->getData("cmap", "subtables") as $_subtable) {
1197
-                    if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
1198
-                        $subtable = $_subtable;
1199
-                        break;
1200
-                    }
1201
-                }
1202
-
1203
-                if ($subtable) {
1204
-                    $glyphIndexArray = $subtable["glyphIndexArray"];
1205
-                    $hmtx = $font_obj->getData("hmtx");
1206
-
1207
-                    unset($glyphIndexArray[0xFFFF]);
1208
-
1209
-                    $cidtogid = str_pad('', max(array_keys($glyphIndexArray)) * 2 + 1, "\x00");
1210
-                    $font['CIDWidths'] = [];
1211
-                    foreach ($glyphIndexArray as $cid => $gid) {
1212
-                        if ($cid >= 0 && $cid < 0xFFFF && $gid) {
1213
-                            $cidtogid[$cid * 2] = chr($gid >> 8);
1214
-                            $cidtogid[$cid * 2 + 1] = chr($gid & 0xFF);
1215
-                        }
1216
-
1217
-                        $width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]);
1218
-                        $font['CIDWidths'][$cid] = $width;
1219
-                    }
1220
-
1221
-                    $font['CIDtoGID'] = base64_encode(gzcompress($cidtogid));
1222
-                    $font['CIDtoGID_Compressed'] = true;
1223
-
1224
-                    $data = file_get_contents($tmp_name);
1225
-                } else {
1226
-                    $data = file_get_contents($fbfile);
1227
-                }
1228
-
1229
-                $font_obj->close();
1230
-                unlink($tmp_name);
1231
-            }
1232
-
1233
-            // create the font descriptor
1234
-            $this->numObj++;
1235
-            $fontDescriptorId = $this->numObj;
1236
-
1237
-            $this->numObj++;
1238
-            $pfbid = $this->numObj;
1239
-
1240
-            // determine flags (more than a little flakey, hopefully will not matter much)
1241
-            $flags = 0;
1242
-
1243
-            if ($font['ItalicAngle'] != 0) {
1244
-                $flags += pow(2, 6);
1245
-            }
1246
-
1247
-            if ($font['IsFixedPitch'] === 'true') {
1248
-                $flags += 1;
1249
-            }
1250
-
1251
-            $flags += pow(2, 5); // assume non-sybolic
1252
-            $list = [
1253
-                'Ascent'       => 'Ascender',
1254
-                'CapHeight'    => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
1255
-                'MissingWidth' => 'MissingWidth',
1256
-                'Descent'      => 'Descender',
1257
-                'FontBBox'     => 'FontBBox',
1258
-                'ItalicAngle'  => 'ItalicAngle'
1259
-            ];
1260
-            $fdopt = [
1261
-                'Flags'    => $flags,
1262
-                'FontName' => $adobeFontName,
1263
-                'StemV'    => $stemV
1264
-            ];
1265
-
1266
-            foreach ($list as $k => $v) {
1267
-                if (isset($font[$v])) {
1268
-                    $fdopt[$k] = $font[$v];
1269
-                }
1270
-            }
1271
-
1272
-            if ($isPfbFont) {
1273
-                $fdopt['FontFile'] = $pfbid;
1274
-            } elseif ($isTtfFont) {
1275
-                $fdopt['FontFile2'] = $pfbid;
1276
-            }
1277
-
1278
-            $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
1279
-
1280
-            // embed the font program
1281
-            $this->o_contents($this->numObj, 'new');
1282
-            $this->objects[$pfbid]['c'] .= $data;
1283
-
1284
-            // determine the cruicial lengths within this file
1285
-            if ($isPfbFont) {
1286
-                $l1 = strpos($data, 'eexec') + 6;
1287
-                $l2 = strpos($data, '00000000') - $l1;
1288
-                $l3 = mb_strlen($data, '8bit') - $l2 - $l1;
1289
-                $this->o_contents(
1290
-                    $this->numObj,
1291
-                    'add',
1292
-                    ['Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3]
1293
-                );
1294
-            } elseif ($isTtfFont) {
1295
-                $l1 = mb_strlen($data, '8bit');
1296
-                $this->o_contents($this->numObj, 'add', ['Length1' => $l1]);
1297
-            }
1298
-
1299
-            // tell the font object about all this new stuff
1300
-            $options = [
1301
-                'BaseFont'       => $adobeFontName,
1302
-                'MissingWidth'   => $missing_width,
1303
-                'Widths'         => $widthid,
1304
-                'FirstChar'      => $firstChar,
1305
-                'LastChar'       => $lastChar,
1306
-                'FontDescriptor' => $fontDescriptorId
1307
-            ];
1308
-
1309
-            if ($isTtfFont) {
1310
-                $options['SubType'] = 'TrueType';
1311
-            }
1312
-
1313
-            $this->addMessage("adding extra info to font.($fontObjId)");
1314
-
1315
-            foreach ($options as $fk => $fv) {
1316
-                $this->addMessage("$fk : $fv");
1317
-            }
1318
-        }
1319
-
1320
-        return $options;
1321
-    }
1322
-
1323
-    /**
1324
-     * A toUnicode section, needed for unicode fonts
1325
-     *
1326
-     * @param $id
1327
-     * @param $action
1328
-     * @return null|string
1329
-     */
1330
-    protected function o_toUnicode($id, $action)
1331
-    {
1332
-        switch ($action) {
1333
-            case 'new':
1334
-                $this->objects[$id] = [
1335
-                    't'    => 'toUnicode'
1336
-                ];
1337
-                break;
1338
-            case 'add':
1339
-                break;
1340
-            case 'out':
1341
-                $ordering = 'UCS';
1342
-                $registry = 'Adobe';
1343
-
1344
-                if ($this->encrypted) {
1345
-                    $this->encryptInit($id);
1346
-                    $ordering = $this->ARC4($ordering);
1347
-                    $registry = $this->filterText($this->ARC4($registry), false, false);
1348
-                }
1349
-
1350
-                $stream = <<<EOT
18
+	const PDF_VERSION = '1.7';
19
+
20
+	const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
21
+	const ACROFORM_SIG_APPENDONLY =       0x0002;
22
+
23
+	const ACROFORM_FIELD_BUTTON =   'Btn';
24
+	const ACROFORM_FIELD_TEXT =     'Tx';
25
+	const ACROFORM_FIELD_CHOICE =   'Ch';
26
+	const ACROFORM_FIELD_SIG =      'Sig';
27
+
28
+	const ACROFORM_FIELD_READONLY =               0x0001;
29
+	const ACROFORM_FIELD_REQUIRED =               0x0002;
30
+
31
+	const ACROFORM_FIELD_TEXT_MULTILINE =         0x1000;
32
+	const ACROFORM_FIELD_TEXT_PASSWORD =          0x2000;
33
+	const ACROFORM_FIELD_TEXT_RICHTEXT =         0x10000;
34
+
35
+	const ACROFORM_FIELD_CHOICE_COMBO =          0x20000;
36
+	const ACROFORM_FIELD_CHOICE_EDIT =           0x40000;
37
+	const ACROFORM_FIELD_CHOICE_SORT =           0x80000;
38
+	const ACROFORM_FIELD_CHOICE_MULTISELECT =   0x200000;
39
+
40
+	const XOBJECT_SUBTYPE_FORM = 'Form';
41
+
42
+	/**
43
+	 * @var integer The current number of pdf objects in the document
44
+	 */
45
+	public $numObj = 0;
46
+
47
+	/**
48
+	 * @var array This array contains all of the pdf objects, ready for final assembly
49
+	 */
50
+	public $objects = [];
51
+
52
+	/**
53
+	 * @var integer The objectId (number within the objects array) of the document catalog
54
+	 */
55
+	public $catalogId;
56
+
57
+	/**
58
+	 * @var integer The objectId (number within the objects array) of indirect references (Javascript EmbeddedFiles)
59
+	 */
60
+	protected $indirectReferenceId = 0;
61
+
62
+	/**
63
+	 * @var integer The objectId (number within the objects array)
64
+	 */
65
+	protected $embeddedFilesId = 0;
66
+
67
+	/**
68
+	 * AcroForm objectId
69
+	 *
70
+	 * @var integer
71
+	 */
72
+	public $acroFormId;
73
+
74
+	/**
75
+	 * @var int
76
+	 */
77
+	public $signatureMaxLen = 5000;
78
+
79
+	/**
80
+	 * @var array Array carrying information about the fonts that the system currently knows about
81
+	 * Used to ensure that a font is not loaded twice, among other things
82
+	 */
83
+	public $fonts = [];
84
+
85
+	/**
86
+	 * @var string The default font metrics file to use if no other font has been loaded.
87
+	 * The path to the directory containing the font metrics should be included
88
+	 */
89
+	public $defaultFont = './fonts/Helvetica.afm';
90
+
91
+	/**
92
+	 * @string A record of the current font
93
+	 */
94
+	public $currentFont = '';
95
+
96
+	/**
97
+	 * @var string The current base font
98
+	 */
99
+	public $currentBaseFont = '';
100
+
101
+	/**
102
+	 * @var integer The number of the current font within the font array
103
+	 */
104
+	public $currentFontNum = 0;
105
+
106
+	/**
107
+	 * @var integer
108
+	 */
109
+	public $currentNode;
110
+
111
+	/**
112
+	 * @var integer Object number of the current page
113
+	 */
114
+	public $currentPage;
115
+
116
+	/**
117
+	 * @var integer Object number of the currently active contents block
118
+	 */
119
+	public $currentContents;
120
+
121
+	/**
122
+	 * @var integer Number of fonts within the system
123
+	 */
124
+	public $numFonts = 0;
125
+
126
+	/**
127
+	 * @var integer Number of graphic state resources used
128
+	 */
129
+	private $numStates = 0;
130
+
131
+	/**
132
+	 * @var array Number of graphic state resources used
133
+	 */
134
+	private $gstates = [];
135
+
136
+	/**
137
+	 * @var array Current color for fill operations, defaults to inactive value,
138
+	 * all three components should be between 0 and 1 inclusive when active
139
+	 */
140
+	public $currentColor = null;
141
+
142
+	/**
143
+	 * @var array Current color for stroke operations (lines etc.)
144
+	 */
145
+	public $currentStrokeColor = null;
146
+
147
+	/**
148
+	 * @var string Fill rule (nonzero or evenodd)
149
+	 */
150
+	public $fillRule = "nonzero";
151
+
152
+	/**
153
+	 * @var string Current style that lines are drawn in
154
+	 */
155
+	public $currentLineStyle = '';
156
+
157
+	/**
158
+	 * @var array Current line transparency (partial graphics state)
159
+	 */
160
+	public $currentLineTransparency = ["mode" => "Normal", "opacity" => 1.0];
161
+
162
+	/**
163
+	 * array Current fill transparency (partial graphics state)
164
+	 */
165
+	public $currentFillTransparency = ["mode" => "Normal", "opacity" => 1.0];
166
+
167
+	/**
168
+	 * @var array An array which is used to save the state of the document, mainly the colors and styles
169
+	 * it is used to temporarily change to another state, then change back to what it was before
170
+	 */
171
+	public $stateStack = [];
172
+
173
+	/**
174
+	 * @var integer Number of elements within the state stack
175
+	 */
176
+	public $nStateStack = 0;
177
+
178
+	/**
179
+	 * @var integer Number of page objects within the document
180
+	 */
181
+	public $numPages = 0;
182
+
183
+	/**
184
+	 * @var array Object Id storage stack
185
+	 */
186
+	public $stack = [];
187
+
188
+	/**
189
+	 * @var integer Number of elements within the object Id storage stack
190
+	 */
191
+	public $nStack = 0;
192
+
193
+	/**
194
+	 * an array which contains information about the objects which are not firmly attached to pages
195
+	 * these have been added with the addObject function
196
+	 */
197
+	public $looseObjects = [];
198
+
199
+	/**
200
+	 * array contains information about how the loose objects are to be added to the document
201
+	 */
202
+	public $addLooseObjects = [];
203
+
204
+	/**
205
+	 * @var integer The objectId of the information object for the document
206
+	 * this contains authorship, title etc.
207
+	 */
208
+	public $infoObject = 0;
209
+
210
+	/**
211
+	 * @var integer Number of images being tracked within the document
212
+	 */
213
+	public $numImages = 0;
214
+
215
+	/**
216
+	 * @var array An array containing options about the document
217
+	 * it defaults to turning on the compression of the objects
218
+	 */
219
+	public $options = ['compression' => true];
220
+
221
+	/**
222
+	 * @var integer The objectId of the first page of the document
223
+	 */
224
+	public $firstPageId;
225
+
226
+	/**
227
+	 * @var integer The object Id of the procset object
228
+	 */
229
+	public $procsetObjectId;
230
+
231
+	/**
232
+	 * @var array Store the information about the relationship between font families
233
+	 * this used so that the code knows which font is the bold version of another font, etc.
234
+	 * the value of this array is initialised in the constructor function.
235
+	 */
236
+	public $fontFamilies = [];
237
+
238
+	/**
239
+	 * @var string Folder for php serialized formats of font metrics files.
240
+	 * If empty string, use same folder as original metrics files.
241
+	 * This can be passed in from class creator.
242
+	 * If this folder does not exist or is not writable, Cpdf will be **much** slower.
243
+	 * Because of potential trouble with php safe mode, folder cannot be created at runtime.
244
+	 */
245
+	public $fontcache = '';
246
+
247
+	/**
248
+	 * @var integer The version of the font metrics cache file.
249
+	 * This value must be manually incremented whenever the internal font data structure is modified.
250
+	 */
251
+	public $fontcacheVersion = 6;
252
+
253
+	/**
254
+	 * @var string Temporary folder.
255
+	 * If empty string, will attempt system tmp folder.
256
+	 * This can be passed in from class creator.
257
+	 */
258
+	public $tmp = '';
259
+
260
+	/**
261
+	 * @var string Track if the current font is bolded or italicised
262
+	 */
263
+	public $currentTextState = '';
264
+
265
+	/**
266
+	 * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information
267
+	 */
268
+	public $messages = '';
269
+
270
+	/**
271
+	 * @var string The encryption array for the document encryption is stored here
272
+	 */
273
+	public $arc4 = '';
274
+
275
+	/**
276
+	 * @var integer The object Id of the encryption information
277
+	 */
278
+	public $arc4_objnum = 0;
279
+
280
+	/**
281
+	 * @var string The file identifier, used to uniquely identify a pdf document
282
+	 */
283
+	public $fileIdentifier = '';
284
+
285
+	/**
286
+	 * @var boolean A flag to say if a document is to be encrypted or not
287
+	 */
288
+	public $encrypted = false;
289
+
290
+	/**
291
+	 * @var string The encryption key for the encryption of all the document content (structure is not encrypted)
292
+	 */
293
+	public $encryptionKey = '';
294
+
295
+	/**
296
+	 * @var array Array which forms a stack to keep track of nested callback functions
297
+	 */
298
+	public $callback = [];
299
+
300
+	/**
301
+	 * @var integer The number of callback functions in the callback array
302
+	 */
303
+	public $nCallback = 0;
304
+
305
+	/**
306
+	 * @var array Store label->id pairs for named destinations, these will be used to replace internal links
307
+	 * done this way so that destinations can be defined after the location that links to them
308
+	 */
309
+	public $destinations = [];
310
+
311
+	/**
312
+	 * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the
313
+	 * publiciables within the class, so that the user can rollback at will (from each 'start' command)
314
+	 * note that this includes the objects array, so these can be large.
315
+	 */
316
+	public $checkpoint = '';
317
+
318
+	/**
319
+	 * @var array Table of Image origin filenames and image labels which were already added with o_image().
320
+	 * Allows to merge identical images
321
+	 */
322
+	public $imagelist = [];
323
+
324
+	/**
325
+	 * @var array Table of already added alpha and plain image files for transparent PNG images.
326
+	 */
327
+	protected $imageAlphaList = [];
328
+
329
+	/**
330
+	 * @var array List of temporary image files to be deleted after processing.
331
+	 */
332
+	protected $imageCache = [];
333
+
334
+	/**
335
+	 * @var boolean Whether the text passed in should be treated as Unicode or just local character set.
336
+	 */
337
+	public $isUnicode = false;
338
+
339
+	/**
340
+	 * @var string the JavaScript code of the document
341
+	 */
342
+	public $javascript = '';
343
+
344
+	/**
345
+	 * @var boolean whether the compression is possible
346
+	 */
347
+	protected $compressionReady = false;
348
+
349
+	/**
350
+	 * @var array Current page size
351
+	 */
352
+	protected $currentPageSize = ["width" => 0, "height" => 0];
353
+
354
+	/**
355
+	 * @var array All the chars that will be required in the font subsets
356
+	 */
357
+	protected $stringSubsets = [];
358
+
359
+	/**
360
+	 * @var string The target internal encoding
361
+	 */
362
+	protected static $targetEncoding = 'Windows-1252';
363
+
364
+	/**
365
+	 * @var array
366
+	 */
367
+	protected $byteRange = array();
368
+
369
+	/**
370
+	 * @var array The list of the core fonts
371
+	 */
372
+	protected static $coreFonts = [
373
+		'courier',
374
+		'courier-bold',
375
+		'courier-oblique',
376
+		'courier-boldoblique',
377
+		'helvetica',
378
+		'helvetica-bold',
379
+		'helvetica-oblique',
380
+		'helvetica-boldoblique',
381
+		'times-roman',
382
+		'times-bold',
383
+		'times-italic',
384
+		'times-bolditalic',
385
+		'symbol',
386
+		'zapfdingbats'
387
+	];
388
+
389
+	/**
390
+	 * Class constructor
391
+	 * This will start a new document
392
+	 *
393
+	 * @param array   $pageSize  Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero.
394
+	 * @param boolean $isUnicode Whether text will be treated as Unicode or not.
395
+	 * @param string  $fontcache The font cache folder
396
+	 * @param string  $tmp       The temporary folder
397
+	 */
398
+	function __construct($pageSize = [0, 0, 612, 792], $isUnicode = false, $fontcache = '', $tmp = '')
399
+	{
400
+		$this->isUnicode = $isUnicode;
401
+		$this->fontcache = rtrim($fontcache, DIRECTORY_SEPARATOR."/\\");
402
+		$this->tmp = ($tmp !== '' ? $tmp : sys_get_temp_dir());
403
+		$this->newDocument($pageSize);
404
+
405
+		$this->compressionReady = function_exists('gzcompress');
406
+
407
+		if (in_array('Windows-1252', mb_list_encodings())) {
408
+			self::$targetEncoding = 'Windows-1252';
409
+		}
410
+
411
+		// also initialize the font families that are known about already
412
+		$this->setFontFamily('init');
413
+	}
414
+
415
+	public function __destruct()
416
+	{
417
+		foreach ($this->imageCache as $file) {
418
+			if (file_exists($file)) {
419
+				unlink($file);
420
+			}
421
+		}
422
+	}
423
+
424
+	/**
425
+	 * Document object methods (internal use only)
426
+	 *
427
+	 * There is about one object method for each type of object in the pdf document
428
+	 * Each function has the same call list ($id,$action,$options).
429
+	 * $id = the object ID of the object, or what it is to be if it is being created
430
+	 * $action = a string specifying the action to be performed, though ALL must support:
431
+	 *           'new' - create the object with the id $id
432
+	 *           'out' - produce the output for the pdf object
433
+	 * $options = optional, a string or array containing the various parameters for the object
434
+	 *
435
+	 * These, in conjunction with the output function are the ONLY way for output to be produced
436
+	 * within the pdf 'file'.
437
+	 */
438
+
439
+	/**
440
+	 * Destination object, used to specify the location for the user to jump to, presently on opening
441
+	 *
442
+	 * @param $id
443
+	 * @param $action
444
+	 * @param string $options
445
+	 * @return string|null
446
+	 */
447
+	protected function o_destination($id, $action, $options = '')
448
+	{
449
+		switch ($action) {
450
+			case 'new':
451
+				$this->objects[$id] = ['t' => 'destination', 'info' => []];
452
+				$tmp = '';
453
+				switch ($options['type']) {
454
+					case 'XYZ':
455
+					/** @noinspection PhpMissingBreakStatementInspection */
456
+					case 'FitR':
457
+						$tmp = ' ' . $options['p3'] . $tmp;
458
+					case 'FitH':
459
+					case 'FitV':
460
+					case 'FitBH':
461
+					/** @noinspection PhpMissingBreakStatementInspection */
462
+					case 'FitBV':
463
+						$tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
464
+					case 'Fit':
465
+					case 'FitB':
466
+						$tmp = $options['type'] . $tmp;
467
+						$this->objects[$id]['info']['string'] = $tmp;
468
+						$this->objects[$id]['info']['page'] = $options['page'];
469
+				}
470
+				break;
471
+
472
+			case 'out':
473
+				$o = &$this->objects[$id];
474
+
475
+				$tmp = $o['info'];
476
+				$res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
477
+
478
+				return $res;
479
+		}
480
+
481
+		return null;
482
+	}
483
+
484
+	/**
485
+	 * set the viewer preferences
486
+	 *
487
+	 * @param $id
488
+	 * @param $action
489
+	 * @param string|array $options
490
+	 * @return string|null
491
+	 */
492
+	protected function o_viewerPreferences($id, $action, $options = '')
493
+	{
494
+		switch ($action) {
495
+			case 'new':
496
+				$this->objects[$id] = ['t' => 'viewerPreferences', 'info' => []];
497
+				break;
498
+
499
+			case 'add':
500
+				$o = &$this->objects[$id];
501
+
502
+				foreach ($options as $k => $v) {
503
+					switch ($k) {
504
+						// Boolean keys
505
+						case 'HideToolbar':
506
+						case 'HideMenubar':
507
+						case 'HideWindowUI':
508
+						case 'FitWindow':
509
+						case 'CenterWindow':
510
+						case 'DisplayDocTitle':
511
+						case 'PickTrayByPDFSize':
512
+							$o['info'][$k] = (bool)$v;
513
+							break;
514
+
515
+						// Integer keys
516
+						case 'NumCopies':
517
+							$o['info'][$k] = (int)$v;
518
+							break;
519
+
520
+						// Name keys
521
+						case 'ViewArea':
522
+						case 'ViewClip':
523
+						case 'PrintClip':
524
+						case 'PrintArea':
525
+							$o['info'][$k] = (string)$v;
526
+							break;
527
+
528
+						// Named with limited valid values
529
+						case 'NonFullScreenPageMode':
530
+							if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
531
+								break;
532
+							}
533
+							$o['info'][$k] = $v;
534
+							break;
535
+
536
+						case 'Direction':
537
+							if (!in_array($v, ['L2R', 'R2L'])) {
538
+								break;
539
+							}
540
+							$o['info'][$k] = $v;
541
+							break;
542
+
543
+						case 'PrintScaling':
544
+							if (!in_array($v, ['None', 'AppDefault'])) {
545
+								break;
546
+							}
547
+							$o['info'][$k] = $v;
548
+							break;
549
+
550
+						case 'Duplex':
551
+							if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
552
+								break;
553
+							}
554
+							$o['info'][$k] = $v;
555
+							break;
556
+
557
+						// Integer array
558
+						case 'PrintPageRange':
559
+							// Cast to integer array
560
+							foreach ($v as $vK => $vV) {
561
+								$v[$vK] = (int)$vV;
562
+							}
563
+							$o['info'][$k] = array_values($v);
564
+							break;
565
+					}
566
+				}
567
+				break;
568
+
569
+			case 'out':
570
+				$o = &$this->objects[$id];
571
+				$res = "\n$id 0 obj\n<< ";
572
+
573
+				foreach ($o['info'] as $k => $v) {
574
+					if (is_string($v)) {
575
+						$v = '/' . $v;
576
+					} elseif (is_int($v)) {
577
+						$v = (string) $v;
578
+					} elseif (is_bool($v)) {
579
+						$v = ($v ? 'true' : 'false');
580
+					} elseif (is_array($v)) {
581
+						$v = '[' . implode(' ', $v) . ']';
582
+					}
583
+					$res .= "\n/$k $v";
584
+				}
585
+				$res .= "\n>>\nendobj";
586
+
587
+				return $res;
588
+		}
589
+
590
+		return null;
591
+	}
592
+
593
+	/**
594
+	 * define the document catalog, the overall controller for the document
595
+	 *
596
+	 * @param $id
597
+	 * @param $action
598
+	 * @param string|array $options
599
+	 * @return string|null
600
+	 */
601
+	protected function o_catalog($id, $action, $options = '')
602
+	{
603
+		if ($action !== 'new') {
604
+			$o = &$this->objects[$id];
605
+		}
606
+
607
+		switch ($action) {
608
+			case 'new':
609
+				$this->objects[$id] = ['t' => 'catalog', 'info' => []];
610
+				$this->catalogId = $id;
611
+				break;
612
+
613
+			case 'acroform':
614
+			case 'outlines':
615
+			case 'pages':
616
+			case 'openHere':
617
+			case 'names':
618
+				$o['info'][$action] = $options;
619
+				break;
620
+
621
+			case 'viewerPreferences':
622
+				if (!isset($o['info']['viewerPreferences'])) {
623
+					$this->numObj++;
624
+					$this->o_viewerPreferences($this->numObj, 'new');
625
+					$o['info']['viewerPreferences'] = $this->numObj;
626
+				}
627
+
628
+				$vp = $o['info']['viewerPreferences'];
629
+				$this->o_viewerPreferences($vp, 'add', $options);
630
+
631
+				break;
632
+
633
+			case 'out':
634
+				$res = "\n$id 0 obj\n<< /Type /Catalog";
635
+
636
+				foreach ($o['info'] as $k => $v) {
637
+					switch ($k) {
638
+						case 'outlines':
639
+							$res .= "\n/Outlines $v 0 R";
640
+							break;
641
+
642
+						case 'pages':
643
+							$res .= "\n/Pages $v 0 R";
644
+							break;
645
+
646
+						case 'viewerPreferences':
647
+							$res .= "\n/ViewerPreferences $v 0 R";
648
+							break;
649
+
650
+						case 'openHere':
651
+							$res .= "\n/OpenAction $v 0 R";
652
+							break;
653
+
654
+						case 'names':
655
+							$res .= "\n/Names $v 0 R";
656
+							break;
657
+
658
+						case 'acroform':
659
+							$res .= "\n/AcroForm $v 0 R";
660
+							break;
661
+					}
662
+				}
663
+
664
+				$res .= " >>\nendobj";
665
+
666
+				return $res;
667
+		}
668
+
669
+		return null;
670
+	}
671
+
672
+	/**
673
+	 * object which is a parent to the pages in the document
674
+	 *
675
+	 * @param $id
676
+	 * @param $action
677
+	 * @param string $options
678
+	 * @return string|null
679
+	 */
680
+	protected function o_pages($id, $action, $options = '')
681
+	{
682
+		if ($action !== 'new') {
683
+			$o = &$this->objects[$id];
684
+		}
685
+
686
+		switch ($action) {
687
+			case 'new':
688
+				$this->objects[$id] = ['t' => 'pages', 'info' => []];
689
+				$this->o_catalog($this->catalogId, 'pages', $id);
690
+				break;
691
+
692
+			case 'page':
693
+				if (!is_array($options)) {
694
+					// then it will just be the id of the new page
695
+					$o['info']['pages'][] = $options;
696
+				} else {
697
+					// then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative
698
+					// and pos is either 'before' or 'after', saying where this page will fit.
699
+					if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) {
700
+						$i = array_search($options['rid'], $o['info']['pages']);
701
+						if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) {
702
+
703
+							// then there is a match
704
+							// make a space
705
+							switch ($options['pos']) {
706
+								case 'before':
707
+									$k = $i;
708
+									break;
709
+
710
+								case 'after':
711
+									$k = $i + 1;
712
+									break;
713
+
714
+								default:
715
+									$k = -1;
716
+									break;
717
+							}
718
+
719
+							if ($k >= 0) {
720
+								for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) {
721
+									$o['info']['pages'][$j + 1] = $o['info']['pages'][$j];
722
+								}
723
+
724
+								$o['info']['pages'][$k] = $options['id'];
725
+							}
726
+						}
727
+					}
728
+				}
729
+				break;
730
+
731
+			case 'procset':
732
+				$o['info']['procset'] = $options;
733
+				break;
734
+
735
+			case 'mediaBox':
736
+				$o['info']['mediaBox'] = $options;
737
+				// which should be an array of 4 numbers
738
+				$this->currentPageSize = ['width' => $options[2], 'height' => $options[3]];
739
+				break;
740
+
741
+			case 'font':
742
+				$o['info']['fonts'][] = ['objNum' => $options['objNum'], 'fontNum' => $options['fontNum']];
743
+				break;
744
+
745
+			case 'extGState':
746
+				$o['info']['extGStates'][] = ['objNum' => $options['objNum'], 'stateNum' => $options['stateNum']];
747
+				break;
748
+
749
+			case 'xObject':
750
+				$o['info']['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
751
+				break;
752
+
753
+			case 'out':
754
+				if (count($o['info']['pages'])) {
755
+					$res = "\n$id 0 obj\n<< /Type /Pages\n/Kids [";
756
+					foreach ($o['info']['pages'] as $v) {
757
+						$res .= "$v 0 R\n";
758
+					}
759
+
760
+					$res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
761
+
762
+					if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
763
+						isset($o['info']['procset']) ||
764
+						(isset($o['info']['extGStates']) && count($o['info']['extGStates']))
765
+					) {
766
+						$res .= "\n/Resources <<";
767
+
768
+						if (isset($o['info']['procset'])) {
769
+							$res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
770
+						}
771
+
772
+						if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
773
+							$res .= "\n/Font << ";
774
+							foreach ($o['info']['fonts'] as $finfo) {
775
+								$res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
776
+							}
777
+							$res .= "\n>>";
778
+						}
779
+
780
+						if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
781
+							$res .= "\n/XObject << ";
782
+							foreach ($o['info']['xObjects'] as $finfo) {
783
+								$res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
784
+							}
785
+							$res .= "\n>>";
786
+						}
787
+
788
+						if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
789
+							$res .= "\n/ExtGState << ";
790
+							foreach ($o['info']['extGStates'] as $gstate) {
791
+								$res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
792
+							}
793
+							$res .= "\n>>";
794
+						}
795
+
796
+						$res .= "\n>>";
797
+						if (isset($o['info']['mediaBox'])) {
798
+							$tmp = $o['info']['mediaBox'];
799
+							$res .= "\n/MediaBox [" . sprintf(
800
+									'%.3F %.3F %.3F %.3F',
801
+									$tmp[0],
802
+									$tmp[1],
803
+									$tmp[2],
804
+									$tmp[3]
805
+								) . ']';
806
+						}
807
+					}
808
+
809
+					$res .= "\n >>\nendobj";
810
+				} else {
811
+					$res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj";
812
+				}
813
+
814
+				return $res;
815
+		}
816
+
817
+		return null;
818
+	}
819
+
820
+	/**
821
+	 * define the outlines in the doc, empty for now
822
+	 *
823
+	 * @param $id
824
+	 * @param $action
825
+	 * @param string $options
826
+	 * @return string|null
827
+	 */
828
+	protected function o_outlines($id, $action, $options = '')
829
+	{
830
+		if ($action !== 'new') {
831
+			$o = &$this->objects[$id];
832
+		}
833
+
834
+		switch ($action) {
835
+			case 'new':
836
+				$this->objects[$id] = ['t' => 'outlines', 'info' => ['outlines' => []]];
837
+				$this->o_catalog($this->catalogId, 'outlines', $id);
838
+				break;
839
+
840
+			case 'outline':
841
+				$o['info']['outlines'][] = $options;
842
+				break;
843
+
844
+			case 'out':
845
+				if (count($o['info']['outlines'])) {
846
+					$res = "\n$id 0 obj\n<< /Type /Outlines /Kids [";
847
+					foreach ($o['info']['outlines'] as $v) {
848
+						$res .= "$v 0 R ";
849
+					}
850
+
851
+					$res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
852
+				} else {
853
+					$res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
854
+				}
855
+
856
+				return $res;
857
+		}
858
+
859
+		return null;
860
+	}
861
+
862
+	/**
863
+	 * an object to hold the font description
864
+	 *
865
+	 * @param $id
866
+	 * @param $action
867
+	 * @param string|array $options
868
+	 * @return string|null
869
+	 * @throws FontNotFoundException
870
+	 */
871
+	protected function o_font($id, $action, $options = '')
872
+	{
873
+		if ($action !== 'new') {
874
+			$o = &$this->objects[$id];
875
+		}
876
+
877
+		switch ($action) {
878
+			case 'new':
879
+				$this->objects[$id] = [
880
+					't'    => 'font',
881
+					'info' => [
882
+						'name'         => $options['name'],
883
+						'fontFileName' => $options['fontFileName'],
884
+						'SubType'      => 'Type1',
885
+						'isSubsetting'   => $options['isSubsetting']
886
+					]
887
+				];
888
+				$fontNum = $this->numFonts;
889
+				$this->objects[$id]['info']['fontNum'] = $fontNum;
890
+
891
+				// deal with the encoding and the differences
892
+				if (isset($options['differences'])) {
893
+					// then we'll need an encoding dictionary
894
+					$this->numObj++;
895
+					$this->o_fontEncoding($this->numObj, 'new', $options);
896
+					$this->objects[$id]['info']['encodingDictionary'] = $this->numObj;
897
+				} else {
898
+					if (isset($options['encoding'])) {
899
+						// we can specify encoding here
900
+						switch ($options['encoding']) {
901
+							case 'WinAnsiEncoding':
902
+							case 'MacRomanEncoding':
903
+							case 'MacExpertEncoding':
904
+								$this->objects[$id]['info']['encoding'] = $options['encoding'];
905
+								break;
906
+
907
+							case 'none':
908
+								break;
909
+
910
+							default:
911
+								$this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
912
+								break;
913
+						}
914
+					} else {
915
+						$this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding';
916
+					}
917
+				}
918
+
919
+				if ($this->fonts[$options['fontFileName']]['isUnicode']) {
920
+					// For Unicode fonts, we need to incorporate font data into
921
+					// sub-sections that are linked from the primary font section.
922
+					// Look at o_fontGIDtoCID and o_fontDescendentCID functions
923
+					// for more information.
924
+					//
925
+					// All of this code is adapted from the excellent changes made to
926
+					// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
927
+
928
+					$toUnicodeId = ++$this->numObj;
929
+					$this->o_toUnicode($toUnicodeId, 'new');
930
+					$this->objects[$id]['info']['toUnicode'] = $toUnicodeId;
931
+
932
+					$cidFontId = ++$this->numObj;
933
+					$this->o_fontDescendentCID($cidFontId, 'new', $options);
934
+					$this->objects[$id]['info']['cidFont'] = $cidFontId;
935
+				}
936
+
937
+				// also tell the pages node about the new font
938
+				$this->o_pages($this->currentNode, 'font', ['fontNum' => $fontNum, 'objNum' => $id]);
939
+				break;
940
+
941
+			case 'add':
942
+				$font_options = $this->processFont($id, $o['info']);
943
+
944
+				if ($font_options !== false) {
945
+					foreach ($font_options as $k => $v) {
946
+						switch ($k) {
947
+							case 'BaseFont':
948
+								$o['info']['name'] = $v;
949
+								break;
950
+							case 'FirstChar':
951
+							case 'LastChar':
952
+							case 'Widths':
953
+							case 'FontDescriptor':
954
+							case 'SubType':
955
+								$this->addMessage('o_font ' . $k . " : " . $v);
956
+								$o['info'][$k] = $v;
957
+								break;
958
+						}
959
+					}
960
+
961
+					// pass values down to descendent font
962
+					if (isset($o['info']['cidFont'])) {
963
+						$this->o_fontDescendentCID($o['info']['cidFont'], 'add', $font_options);
964
+					}
965
+				}
966
+				break;
967
+
968
+			case 'out':
969
+				if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) {
970
+					// For Unicode fonts, we need to incorporate font data into
971
+					// sub-sections that are linked from the primary font section.
972
+					// Look at o_fontGIDtoCID and o_fontDescendentCID functions
973
+					// for more information.
974
+					//
975
+					// All of this code is adapted from the excellent changes made to
976
+					// transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
977
+
978
+					$res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
979
+					$res .= "/BaseFont /" . $o['info']['name'] . "\n";
980
+
981
+					// The horizontal identity mapping for 2-byte CIDs; may be used
982
+					// with CIDFonts using any Registry, Ordering, and Supplement values.
983
+					$res .= "/Encoding /Identity-H\n";
984
+					$res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
985
+					$res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
986
+					$res .= ">>\n";
987
+					$res .= "endobj";
988
+				} else {
989
+					$res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
990
+					$res .= "/Name /F" . $o['info']['fontNum'] . "\n";
991
+					$res .= "/BaseFont /" . $o['info']['name'] . "\n";
992
+
993
+					if (isset($o['info']['encodingDictionary'])) {
994
+						// then place a reference to the dictionary
995
+						$res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
996
+					} else {
997
+						if (isset($o['info']['encoding'])) {
998
+							// use the specified encoding
999
+							$res .= "/Encoding /" . $o['info']['encoding'] . "\n";
1000
+						}
1001
+					}
1002
+
1003
+					if (isset($o['info']['FirstChar'])) {
1004
+						$res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
1005
+					}
1006
+
1007
+					if (isset($o['info']['LastChar'])) {
1008
+						$res .= "/LastChar " . $o['info']['LastChar'] . "\n";
1009
+					}
1010
+
1011
+					if (isset($o['info']['Widths'])) {
1012
+						$res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
1013
+					}
1014
+
1015
+					if (isset($o['info']['FontDescriptor'])) {
1016
+						$res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1017
+					}
1018
+
1019
+					$res .= ">>\n";
1020
+					$res .= "endobj";
1021
+				}
1022
+
1023
+				return $res;
1024
+		}
1025
+
1026
+		return null;
1027
+	}
1028
+
1029
+	protected function getFontSubsettingTag(array $font): string
1030
+	{
1031
+		// convert font num to hexavigesimal numeral system letters A - Z only
1032
+		$base_26 = strtoupper(base_convert($font['fontNum'], 10, 26));
1033
+		for ($i = 0; $i < strlen($base_26); $i++) {
1034
+			$char = $base_26[$i];
1035
+			if ($char <= "9") {
1036
+				$base_26[$i] = chr(65 + intval($char));
1037
+			} else {
1038
+				$base_26[$i] = chr(ord($char) + 10);
1039
+			}
1040
+		}
1041
+
1042
+		return 'SUB' . str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1043
+	}
1044
+
1045
+	/**
1046
+	 * @param int $fontObjId
1047
+	 * @param array $object_info
1048
+	 * @return array|false
1049
+	 * @throws FontNotFoundException
1050
+	 */
1051
+	private function processFont(int $fontObjId, array $object_info)
1052
+	{
1053
+		$fontFileName = $object_info['fontFileName'];
1054
+		if (!isset($this->fonts[$fontFileName])) {
1055
+			return false;
1056
+		}
1057
+
1058
+		$font = &$this->fonts[$fontFileName];
1059
+
1060
+		$fileSuffix = $font['fileSuffix'];
1061
+		$fileSuffixLower = strtolower($font['fileSuffix']);
1062
+		$fbfile = "$fontFileName.$fileSuffix";
1063
+		$isTtfFont = $fileSuffixLower === 'ttf';
1064
+		$isPfbFont = $fileSuffixLower === 'pfb';
1065
+
1066
+		$this->addMessage('selectFont: checking for - ' . $fbfile);
1067
+
1068
+		if (!$fileSuffix) {
1069
+			$this->addMessage(
1070
+				'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
1071
+			);
1072
+
1073
+			return false;
1074
+		} else {
1075
+			$adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName'];
1076
+			//        $fontObj = $this->numObj;
1077
+			$this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName");
1078
+
1079
+			// find the array of font widths, and put that into an object.
1080
+			$firstChar = -1;
1081
+			$lastChar = 0;
1082
+			$widths = [];
1083
+			$cid_widths = [];
1084
+
1085
+			foreach ($font['C'] as $num => $d) {
1086
+				if (intval($num) > 0 || $num == '0') {
1087
+					if (!$font['isUnicode']) {
1088
+						// With Unicode, widths array isn't used
1089
+						if ($lastChar > 0 && $num > $lastChar + 1) {
1090
+							for ($i = $lastChar + 1; $i < $num; $i++) {
1091
+								$widths[] = 0;
1092
+							}
1093
+						}
1094
+					}
1095
+
1096
+					$widths[] = $d;
1097
+
1098
+					if ($font['isUnicode']) {
1099
+						$cid_widths[$num] = $d;
1100
+					}
1101
+
1102
+					if ($firstChar == -1) {
1103
+						$firstChar = $num;
1104
+					}
1105
+
1106
+					$lastChar = $num;
1107
+				}
1108
+			}
1109
+
1110
+			// also need to adjust the widths for the differences array
1111
+			if (isset($object['differences'])) {
1112
+				foreach ($object['differences'] as $charNum => $charName) {
1113
+					if ($charNum > $lastChar) {
1114
+						if (!$object['isUnicode']) {
1115
+							// With Unicode, widths array isn't used
1116
+							for ($i = $lastChar + 1; $i <= $charNum; $i++) {
1117
+								$widths[] = 0;
1118
+							}
1119
+						}
1120
+
1121
+						$lastChar = $charNum;
1122
+					}
1123
+
1124
+					if (isset($font['C'][$charName])) {
1125
+						$widths[$charNum - $firstChar] = $font['C'][$charName];
1126
+						if ($font['isUnicode']) {
1127
+							$cid_widths[$charName] = $font['C'][$charName];
1128
+						}
1129
+					}
1130
+				}
1131
+			}
1132
+
1133
+			if ($font['isUnicode']) {
1134
+				$font['CIDWidths'] = $cid_widths;
1135
+			}
1136
+
1137
+			$this->addMessage('selectFont: FirstChar = ' . $firstChar);
1138
+			$this->addMessage('selectFont: LastChar = ' . $lastChar);
1139
+
1140
+			$widthid = -1;
1141
+
1142
+			if (!$font['isUnicode']) {
1143
+				// With Unicode, widths array isn't used
1144
+
1145
+				$this->numObj++;
1146
+				$this->o_contents($this->numObj, 'new', 'raw');
1147
+				$this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
1148
+				$widthid = $this->numObj;
1149
+			}
1150
+
1151
+			$missing_width = 500;
1152
+			$stemV = 70;
1153
+
1154
+			if (isset($font['MissingWidth'])) {
1155
+				$missing_width = $font['MissingWidth'];
1156
+			}
1157
+			if (isset($font['StdVW'])) {
1158
+				$stemV = $font['StdVW'];
1159
+			} else {
1160
+				if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) {
1161
+					$stemV = 120;
1162
+				}
1163
+			}
1164
+
1165
+			// load the pfb file, and put that into an object too.
1166
+			// note that pdf supports only binary format type 1 font files, though there is a
1167
+			// simple utility to convert them from pfa to pfb.
1168
+			if (!$font['isSubsetting']) {
1169
+				$data = file_get_contents($fbfile);
1170
+			} else {
1171
+				$adobeFontName = $this->getFontSubsettingTag($font) . '+' . $adobeFontName;
1172
+				$this->stringSubsets[$fontFileName][] = 32; // Force space if not in yet
1173
+
1174
+				$subset = $this->stringSubsets[$fontFileName];
1175
+				sort($subset);
1176
+
1177
+				// Load font
1178
+				$font_obj = Font::load($fbfile);
1179
+				$font_obj->parse();
1180
+
1181
+				// Define subset
1182
+				$font_obj->setSubset($subset);
1183
+				$font_obj->reduce();
1184
+
1185
+				// Write new font
1186
+				$tmp_name = @tempnam($this->tmp, "cpdf_subset_");
1187
+				$font_obj->open($tmp_name, BinaryStream::modeReadWrite);
1188
+				$font_obj->encode(["OS/2"]);
1189
+				$font_obj->close();
1190
+
1191
+				// Parse the new font to get cid2gid and widths
1192
+				$font_obj = Font::load($tmp_name);
1193
+
1194
+				// Find Unicode char map table
1195
+				$subtable = null;
1196
+				foreach ($font_obj->getData("cmap", "subtables") as $_subtable) {
1197
+					if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) {
1198
+						$subtable = $_subtable;
1199
+						break;
1200
+					}
1201
+				}
1202
+
1203
+				if ($subtable) {
1204
+					$glyphIndexArray = $subtable["glyphIndexArray"];
1205
+					$hmtx = $font_obj->getData("hmtx");
1206
+
1207
+					unset($glyphIndexArray[0xFFFF]);
1208
+
1209
+					$cidtogid = str_pad('', max(array_keys($glyphIndexArray)) * 2 + 1, "\x00");
1210
+					$font['CIDWidths'] = [];
1211
+					foreach ($glyphIndexArray as $cid => $gid) {
1212
+						if ($cid >= 0 && $cid < 0xFFFF && $gid) {
1213
+							$cidtogid[$cid * 2] = chr($gid >> 8);
1214
+							$cidtogid[$cid * 2 + 1] = chr($gid & 0xFF);
1215
+						}
1216
+
1217
+						$width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]);
1218
+						$font['CIDWidths'][$cid] = $width;
1219
+					}
1220
+
1221
+					$font['CIDtoGID'] = base64_encode(gzcompress($cidtogid));
1222
+					$font['CIDtoGID_Compressed'] = true;
1223
+
1224
+					$data = file_get_contents($tmp_name);
1225
+				} else {
1226
+					$data = file_get_contents($fbfile);
1227
+				}
1228
+
1229
+				$font_obj->close();
1230
+				unlink($tmp_name);
1231
+			}
1232
+
1233
+			// create the font descriptor
1234
+			$this->numObj++;
1235
+			$fontDescriptorId = $this->numObj;
1236
+
1237
+			$this->numObj++;
1238
+			$pfbid = $this->numObj;
1239
+
1240
+			// determine flags (more than a little flakey, hopefully will not matter much)
1241
+			$flags = 0;
1242
+
1243
+			if ($font['ItalicAngle'] != 0) {
1244
+				$flags += pow(2, 6);
1245
+			}
1246
+
1247
+			if ($font['IsFixedPitch'] === 'true') {
1248
+				$flags += 1;
1249
+			}
1250
+
1251
+			$flags += pow(2, 5); // assume non-sybolic
1252
+			$list = [
1253
+				'Ascent'       => 'Ascender',
1254
+				'CapHeight'    => 'Ascender', //FIXME: php-font-lib is not grabbing this value, so we'll fake it and use the Ascender value // 'CapHeight'
1255
+				'MissingWidth' => 'MissingWidth',
1256
+				'Descent'      => 'Descender',
1257
+				'FontBBox'     => 'FontBBox',
1258
+				'ItalicAngle'  => 'ItalicAngle'
1259
+			];
1260
+			$fdopt = [
1261
+				'Flags'    => $flags,
1262
+				'FontName' => $adobeFontName,
1263
+				'StemV'    => $stemV
1264
+			];
1265
+
1266
+			foreach ($list as $k => $v) {
1267
+				if (isset($font[$v])) {
1268
+					$fdopt[$k] = $font[$v];
1269
+				}
1270
+			}
1271
+
1272
+			if ($isPfbFont) {
1273
+				$fdopt['FontFile'] = $pfbid;
1274
+			} elseif ($isTtfFont) {
1275
+				$fdopt['FontFile2'] = $pfbid;
1276
+			}
1277
+
1278
+			$this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt);
1279
+
1280
+			// embed the font program
1281
+			$this->o_contents($this->numObj, 'new');
1282
+			$this->objects[$pfbid]['c'] .= $data;
1283
+
1284
+			// determine the cruicial lengths within this file
1285
+			if ($isPfbFont) {
1286
+				$l1 = strpos($data, 'eexec') + 6;
1287
+				$l2 = strpos($data, '00000000') - $l1;
1288
+				$l3 = mb_strlen($data, '8bit') - $l2 - $l1;
1289
+				$this->o_contents(
1290
+					$this->numObj,
1291
+					'add',
1292
+					['Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3]
1293
+				);
1294
+			} elseif ($isTtfFont) {
1295
+				$l1 = mb_strlen($data, '8bit');
1296
+				$this->o_contents($this->numObj, 'add', ['Length1' => $l1]);
1297
+			}
1298
+
1299
+			// tell the font object about all this new stuff
1300
+			$options = [
1301
+				'BaseFont'       => $adobeFontName,
1302
+				'MissingWidth'   => $missing_width,
1303
+				'Widths'         => $widthid,
1304
+				'FirstChar'      => $firstChar,
1305
+				'LastChar'       => $lastChar,
1306
+				'FontDescriptor' => $fontDescriptorId
1307
+			];
1308
+
1309
+			if ($isTtfFont) {
1310
+				$options['SubType'] = 'TrueType';
1311
+			}
1312
+
1313
+			$this->addMessage("adding extra info to font.($fontObjId)");
1314
+
1315
+			foreach ($options as $fk => $fv) {
1316
+				$this->addMessage("$fk : $fv");
1317
+			}
1318
+		}
1319
+
1320
+		return $options;
1321
+	}
1322
+
1323
+	/**
1324
+	 * A toUnicode section, needed for unicode fonts
1325
+	 *
1326
+	 * @param $id
1327
+	 * @param $action
1328
+	 * @return null|string
1329
+	 */
1330
+	protected function o_toUnicode($id, $action)
1331
+	{
1332
+		switch ($action) {
1333
+			case 'new':
1334
+				$this->objects[$id] = [
1335
+					't'    => 'toUnicode'
1336
+				];
1337
+				break;
1338
+			case 'add':
1339
+				break;
1340
+			case 'out':
1341
+				$ordering = 'UCS';
1342
+				$registry = 'Adobe';
1343
+
1344
+				if ($this->encrypted) {
1345
+					$this->encryptInit($id);
1346
+					$ordering = $this->ARC4($ordering);
1347
+					$registry = $this->filterText($this->ARC4($registry), false, false);
1348
+				}
1349
+
1350
+				$stream = <<<EOT
1351 1351
 /CIDInit /ProcSet findresource begin
1352 1352
 12 dict begin
1353 1353
 begincmap
@@ -1370,5132 +1370,5132 @@  discard block
 block discarded – undo
1370 1370
 end
1371 1371
 EOT;
1372 1372
 
1373
-                $res = "\n$id 0 obj\n";
1374
-                $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1375
-                $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";
1376
-
1377
-                return $res;
1378
-        }
1379
-
1380
-        return null;
1381
-    }
1382
-
1383
-    /**
1384
-     * a font descriptor, needed for including additional fonts
1385
-     *
1386
-     * @param $id
1387
-     * @param $action
1388
-     * @param string $options
1389
-     * @return null|string
1390
-     */
1391
-    protected function o_fontDescriptor($id, $action, $options = '')
1392
-    {
1393
-        if ($action !== 'new') {
1394
-            $o = &$this->objects[$id];
1395
-        }
1396
-
1397
-        switch ($action) {
1398
-            case 'new':
1399
-                $this->objects[$id] = ['t' => 'fontDescriptor', 'info' => $options];
1400
-                break;
1401
-
1402
-            case 'out':
1403
-                $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1404
-                foreach ($o['info'] as $label => $value) {
1405
-                    switch ($label) {
1406
-                        case 'Ascent':
1407
-                        case 'CapHeight':
1408
-                        case 'Descent':
1409
-                        case 'Flags':
1410
-                        case 'ItalicAngle':
1411
-                        case 'StemV':
1412
-                        case 'AvgWidth':
1413
-                        case 'Leading':
1414
-                        case 'MaxWidth':
1415
-                        case 'MissingWidth':
1416
-                        case 'StemH':
1417
-                        case 'XHeight':
1418
-                        case 'CharSet':
1419
-                            if (mb_strlen($value, '8bit')) {
1420
-                                $res .= "/$label $value\n";
1421
-                            }
1422
-
1423
-                            break;
1424
-                        case 'FontFile':
1425
-                        case 'FontFile2':
1426
-                        case 'FontFile3':
1427
-                            $res .= "/$label $value 0 R\n";
1428
-                            break;
1429
-
1430
-                        case 'FontBBox':
1431
-                            $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1432
-                            break;
1433
-
1434
-                        case 'FontName':
1435
-                            $res .= "/$label /$value\n";
1436
-                            break;
1437
-                    }
1438
-                }
1439
-
1440
-                $res .= ">>\nendobj";
1441
-
1442
-                return $res;
1443
-        }
1444
-
1445
-        return null;
1446
-    }
1447
-
1448
-    /**
1449
-     * the font encoding
1450
-     *
1451
-     * @param $id
1452
-     * @param $action
1453
-     * @param string $options
1454
-     * @return null|string
1455
-     */
1456
-    protected function o_fontEncoding($id, $action, $options = '')
1457
-    {
1458
-        if ($action !== 'new') {
1459
-            $o = &$this->objects[$id];
1460
-        }
1461
-
1462
-        switch ($action) {
1463
-            case 'new':
1464
-                // the options array should contain 'differences' and maybe 'encoding'
1465
-                $this->objects[$id] = ['t' => 'fontEncoding', 'info' => $options];
1466
-                break;
1467
-
1468
-            case 'out':
1469
-                $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1470
-                if (!isset($o['info']['encoding'])) {
1471
-                    $o['info']['encoding'] = 'WinAnsiEncoding';
1472
-                }
1473
-
1474
-                if ($o['info']['encoding'] !== 'none') {
1475
-                    $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1476
-                }
1477
-
1478
-                $res .= "/Differences \n[";
1479
-
1480
-                $onum = -100;
1481
-
1482
-                foreach ($o['info']['differences'] as $num => $label) {
1483
-                    if ($num != $onum + 1) {
1484
-                        // we cannot make use of consecutive numbering
1485
-                        $res .= "\n$num /$label";
1486
-                    } else {
1487
-                        $res .= " /$label";
1488
-                    }
1489
-
1490
-                    $onum = $num;
1491
-                }
1492
-
1493
-                $res .= "\n]\n>>\nendobj";
1494
-
1495
-                return $res;
1496
-        }
1497
-
1498
-        return null;
1499
-    }
1500
-
1501
-    /**
1502
-     * a descendent cid font, needed for unicode fonts
1503
-     *
1504
-     * @param $id
1505
-     * @param $action
1506
-     * @param string|array $options
1507
-     * @return null|string
1508
-     */
1509
-    protected function o_fontDescendentCID($id, $action, $options = '')
1510
-    {
1511
-        if ($action !== 'new') {
1512
-            $o = &$this->objects[$id];
1513
-        }
1514
-
1515
-        switch ($action) {
1516
-            case 'new':
1517
-                $this->objects[$id] = ['t' => 'fontDescendentCID', 'info' => $options];
1518
-
1519
-                // we need a CID system info section
1520
-                $cidSystemInfoId = ++$this->numObj;
1521
-                $this->o_cidSystemInfo($cidSystemInfoId, 'new');
1522
-                $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1523
-
1524
-                // and a CID to GID map
1525
-                $cidToGidMapId = ++$this->numObj;
1526
-                $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1527
-                $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1528
-                break;
1529
-
1530
-            case 'add':
1531
-                foreach ($options as $k => $v) {
1532
-                    switch ($k) {
1533
-                        case 'BaseFont':
1534
-                            $o['info']['name'] = $v;
1535
-                            break;
1536
-
1537
-                        case 'FirstChar':
1538
-                        case 'LastChar':
1539
-                        case 'MissingWidth':
1540
-                        case 'FontDescriptor':
1541
-                        case 'SubType':
1542
-                            $this->addMessage("o_fontDescendentCID $k : $v");
1543
-                            $o['info'][$k] = $v;
1544
-                            break;
1545
-                    }
1546
-                }
1547
-
1548
-                // pass values down to cid to gid map
1549
-                $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1550
-                break;
1551
-
1552
-            case 'out':
1553
-                $res = "\n$id 0 obj\n";
1554
-                $res .= "<</Type /Font\n";
1555
-                $res .= "/Subtype /CIDFontType2\n";
1556
-                $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1557
-                $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1558
-                //      if (isset($o['info']['FirstChar'])) {
1559
-                //        $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1560
-                //      }
1561
-
1562
-                //      if (isset($o['info']['LastChar'])) {
1563
-                //        $res.= "/LastChar ".$o['info']['LastChar']."\n";
1564
-                //      }
1565
-                if (isset($o['info']['FontDescriptor'])) {
1566
-                    $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1567
-                }
1568
-
1569
-                if (isset($o['info']['MissingWidth'])) {
1570
-                    $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1571
-                }
1572
-
1573
-                if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1574
-                    $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1575
-                    $w = '';
1576
-                    foreach ($cid_widths as $cid => $width) {
1577
-                        $w .= "$cid [$width] ";
1578
-                    }
1579
-                    $res .= "/W [$w]\n";
1580
-                }
1581
-
1582
-                $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1583
-                $res .= ">>\n";
1584
-                $res .= "endobj";
1585
-
1586
-                return $res;
1587
-        }
1588
-
1589
-        return null;
1590
-    }
1591
-
1592
-    /**
1593
-     * CID system info section, needed for unicode fonts
1594
-     *
1595
-     * @param $id
1596
-     * @param $action
1597
-     * @return null|string
1598
-     */
1599
-    protected function o_cidSystemInfo($id, $action)
1600
-    {
1601
-        switch ($action) {
1602
-            case 'new':
1603
-                $this->objects[$id] = [
1604
-                    't' => 'cidSystemInfo'
1605
-                ];
1606
-                break;
1607
-            case 'add':
1608
-                break;
1609
-            case 'out':
1610
-                $ordering = 'UCS';
1611
-                $registry = 'Adobe';
1612
-
1613
-                if ($this->encrypted) {
1614
-                    $this->encryptInit($id);
1615
-                    $ordering = $this->ARC4($ordering);
1616
-                    $registry = $this->ARC4($registry);
1617
-                }
1618
-
1619
-
1620
-                $res = "\n$id 0 obj\n";
1621
-
1622
-                $res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
1623
-                $res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
1624
-                $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1625
-                $res .= ">>";
1626
-
1627
-                $res .= "\nendobj";
1628
-
1629
-                return $res;
1630
-        }
1631
-
1632
-        return null;
1633
-    }
1634
-
1635
-    /**
1636
-     * a font glyph to character map, needed for unicode fonts
1637
-     *
1638
-     * @param $id
1639
-     * @param $action
1640
-     * @param string $options
1641
-     * @return null|string
1642
-     */
1643
-    protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1644
-    {
1645
-        if ($action !== 'new') {
1646
-            $o = &$this->objects[$id];
1647
-        }
1648
-
1649
-        switch ($action) {
1650
-            case 'new':
1651
-                $this->objects[$id] = ['t' => 'fontGIDtoCIDMap', 'info' => $options];
1652
-                break;
1653
-
1654
-            case 'out':
1655
-                $res = "\n$id 0 obj\n";
1656
-                $fontFileName = $o['info']['fontFileName'];
1657
-                $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1658
-
1659
-                $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1660
-                    $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1661
-
1662
-                if (!$compressed && isset($o['raw'])) {
1663
-                    $res .= $tmp;
1664
-                } else {
1665
-                    $res .= "<<";
1666
-
1667
-                    if (!$compressed && $this->compressionReady && $this->options['compression']) {
1668
-                        // then implement ZLIB based compression on this content stream
1669
-                        $compressed = true;
1670
-                        $tmp = gzcompress($tmp, 6);
1671
-                    }
1672
-                    if ($compressed) {
1673
-                        $res .= "\n/Filter /FlateDecode";
1674
-                    }
1675
-
1676
-                    if ($this->encrypted) {
1677
-                        $this->encryptInit($id);
1678
-                        $tmp = $this->ARC4($tmp);
1679
-                    }
1680
-
1681
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1682
-                }
1683
-
1684
-                $res .= "\nendobj";
1685
-
1686
-                return $res;
1687
-        }
1688
-
1689
-        return null;
1690
-    }
1691
-
1692
-    /**
1693
-     * the document procset, solves some problems with printing to old PS printers
1694
-     *
1695
-     * @param $id
1696
-     * @param $action
1697
-     * @param string $options
1698
-     * @return null|string
1699
-     */
1700
-    protected function o_procset($id, $action, $options = '')
1701
-    {
1702
-        if ($action !== 'new') {
1703
-            $o = &$this->objects[$id];
1704
-        }
1705
-
1706
-        switch ($action) {
1707
-            case 'new':
1708
-                $this->objects[$id] = ['t' => 'procset', 'info' => ['PDF' => 1, 'Text' => 1]];
1709
-                $this->o_pages($this->currentNode, 'procset', $id);
1710
-                $this->procsetObjectId = $id;
1711
-                break;
1712
-
1713
-            case 'add':
1714
-                // this is to add new items to the procset list, despite the fact that this is considered
1715
-                // obsolete, the items are required for printing to some postscript printers
1716
-                switch ($options) {
1717
-                    case 'ImageB':
1718
-                    case 'ImageC':
1719
-                    case 'ImageI':
1720
-                        $o['info'][$options] = 1;
1721
-                        break;
1722
-                }
1723
-                break;
1724
-
1725
-            case 'out':
1726
-                $res = "\n$id 0 obj\n[";
1727
-                foreach ($o['info'] as $label => $val) {
1728
-                    $res .= "/$label ";
1729
-                }
1730
-                $res .= "]\nendobj";
1731
-
1732
-                return $res;
1733
-        }
1734
-
1735
-        return null;
1736
-    }
1737
-
1738
-    /**
1739
-     * define the document information
1740
-     *
1741
-     * @param $id
1742
-     * @param $action
1743
-     * @param string $options
1744
-     * @return null|string
1745
-     */
1746
-    protected function o_info($id, $action, $options = '')
1747
-    {
1748
-        switch ($action) {
1749
-            case 'new':
1750
-                $this->infoObject = $id;
1751
-                $date = 'D:' . @date('Ymd');
1752
-                $this->objects[$id] = [
1753
-                    't'    => 'info',
1754
-                    'info' => [
1755
-                        'Producer'      => 'CPDF (dompdf)',
1756
-                        'CreationDate' => $date
1757
-                    ]
1758
-                ];
1759
-                break;
1760
-            case 'Title':
1761
-            case 'Author':
1762
-            case 'Subject':
1763
-            case 'Keywords':
1764
-            case 'Creator':
1765
-            case 'Producer':
1766
-            case 'CreationDate':
1767
-            case 'ModDate':
1768
-            case 'Trapped':
1769
-                $this->objects[$id]['info'][$action] = $options;
1770
-                break;
1771
-
1772
-            case 'out':
1773
-                $encrypted = $this->encrypted;
1774
-                if ($encrypted) {
1775
-                    $this->encryptInit($id);
1776
-                }
1777
-
1778
-                $res = "\n$id 0 obj\n<<\n";
1779
-                $o = &$this->objects[$id];
1780
-                foreach ($o['info'] as $k => $v) {
1781
-                    $res .= "/$k (";
1782
-
1783
-                    // dates must be outputted as-is, without Unicode transformations
1784
-                    if ($k !== 'CreationDate' && $k !== 'ModDate') {
1785
-                        $v = $this->utf8toUtf16BE($v);
1786
-                    }
1787
-
1788
-                    if ($encrypted) {
1789
-                        $v = $this->ARC4($v);
1790
-                    }
1791
-
1792
-                    $res .= $this->filterText($v, false, false);
1793
-                    $res .= ")\n";
1794
-                }
1795
-
1796
-                $res .= ">>\nendobj";
1797
-
1798
-                return $res;
1799
-        }
1800
-
1801
-        return null;
1802
-    }
1803
-
1804
-    /**
1805
-     * an action object, used to link to URLS initially
1806
-     *
1807
-     * @param $id
1808
-     * @param $action
1809
-     * @param string $options
1810
-     * @return null|string
1811
-     */
1812
-    protected function o_action($id, $action, $options = '')
1813
-    {
1814
-        if ($action !== 'new') {
1815
-            $o = &$this->objects[$id];
1816
-        }
1817
-
1818
-        switch ($action) {
1819
-            case 'new':
1820
-                if (is_array($options)) {
1821
-                    $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => $options['type']];
1822
-                } else {
1823
-                    // then assume a URI action
1824
-                    $this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => 'URI'];
1825
-                }
1826
-                break;
1827
-
1828
-            case 'out':
1829
-                if ($this->encrypted) {
1830
-                    $this->encryptInit($id);
1831
-                }
1832
-
1833
-                $res = "\n$id 0 obj\n<< /Type /Action";
1834
-                switch ($o['type']) {
1835
-                    case 'ilink':
1836
-                        if (!isset($this->destinations[(string)$o['info']['label']])) {
1837
-                            break;
1838
-                        }
1839
-
1840
-                        // there will be an 'label' setting, this is the name of the destination
1841
-                        $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1842
-                        break;
1843
-
1844
-                    case 'URI':
1845
-                        $res .= "\n/S /URI\n/URI (";
1846
-                        if ($this->encrypted) {
1847
-                            $res .= $this->filterText($this->ARC4($o['info']), false, false);
1848
-                        } else {
1849
-                            $res .= $this->filterText($o['info'], false, false);
1850
-                        }
1851
-
1852
-                        $res .= ")";
1853
-                        break;
1854
-                }
1855
-
1856
-                $res .= "\n>>\nendobj";
1857
-
1858
-                return $res;
1859
-        }
1860
-
1861
-        return null;
1862
-    }
1863
-
1864
-    /**
1865
-     * an annotation object, this will add an annotation to the current page.
1866
-     * initially will support just link annotations
1867
-     *
1868
-     * @param $id
1869
-     * @param $action
1870
-     * @param string $options
1871
-     * @return null|string
1872
-     */
1873
-    protected function o_annotation($id, $action, $options = '')
1874
-    {
1875
-        if ($action !== 'new') {
1876
-            $o = &$this->objects[$id];
1877
-        }
1878
-
1879
-        switch ($action) {
1880
-            case 'new':
1881
-                // add the annotation to the current page
1882
-                $pageId = $this->currentPage;
1883
-                $this->o_page($pageId, 'annot', $id);
1884
-
1885
-                // and add the action object which is going to be required
1886
-                switch ($options['type']) {
1887
-                    case 'link':
1888
-                        $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1889
-                        $this->numObj++;
1890
-                        $this->o_action($this->numObj, 'new', $options['url']);
1891
-                        $this->objects[$id]['info']['actionId'] = $this->numObj;
1892
-                        break;
1893
-
1894
-                    case 'ilink':
1895
-                        // this is to a named internal link
1896
-                        $label = $options['label'];
1897
-                        $this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1898
-                        $this->numObj++;
1899
-                        $this->o_action($this->numObj, 'new', ['type' => 'ilink', 'label' => $label]);
1900
-                        $this->objects[$id]['info']['actionId'] = $this->numObj;
1901
-                        break;
1902
-                }
1903
-                break;
1904
-
1905
-            case 'out':
1906
-                $res = "\n$id 0 obj\n<< /Type /Annot";
1907
-                switch ($o['info']['type']) {
1908
-                    case 'link':
1909
-                    case 'ilink':
1910
-                        $res .= "\n/Subtype /Link";
1911
-                        break;
1912
-                }
1913
-                $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1914
-                $res .= "\n/Border [0 0 0]";
1915
-                $res .= "\n/H /I";
1916
-                $res .= "\n/Rect [ ";
1917
-
1918
-                foreach ($o['info']['rect'] as $v) {
1919
-                    $res .= sprintf("%.4F ", $v);
1920
-                }
1921
-
1922
-                $res .= "]";
1923
-                $res .= "\n>>\nendobj";
1924
-
1925
-                return $res;
1926
-        }
1927
-
1928
-        return null;
1929
-    }
1930
-
1931
-    /**
1932
-     * a page object, it also creates a contents object to hold its contents
1933
-     *
1934
-     * @param $id
1935
-     * @param $action
1936
-     * @param string $options
1937
-     * @return null|string
1938
-     */
1939
-    protected function o_page($id, $action, $options = '')
1940
-    {
1941
-        if ($action !== 'new') {
1942
-            $o = &$this->objects[$id];
1943
-        }
1944
-
1945
-        switch ($action) {
1946
-            case 'new':
1947
-                $this->numPages++;
1948
-                $this->objects[$id] = [
1949
-                    't'    => 'page',
1950
-                    'info' => [
1951
-                        'parent'  => $this->currentNode,
1952
-                        'pageNum' => $this->numPages,
1953
-                        'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1954
-                    ]
1955
-                ];
1956
-
1957
-                if (is_array($options)) {
1958
-                    // then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1959
-                    $options['id'] = $id;
1960
-                    $this->o_pages($this->currentNode, 'page', $options);
1961
-                } else {
1962
-                    $this->o_pages($this->currentNode, 'page', $id);
1963
-                }
1964
-
1965
-                $this->currentPage = $id;
1966
-                //make a contents object to go with this page
1967
-                $this->numObj++;
1968
-                $this->o_contents($this->numObj, 'new', $id);
1969
-                $this->currentContents = $this->numObj;
1970
-                $this->objects[$id]['info']['contents'] = [];
1971
-                $this->objects[$id]['info']['contents'][] = $this->numObj;
1972
-
1973
-                $match = ($this->numPages % 2 ? 'odd' : 'even');
1974
-                foreach ($this->addLooseObjects as $oId => $target) {
1975
-                    if ($target === 'all' || $match === $target) {
1976
-                        $this->objects[$id]['info']['contents'][] = $oId;
1977
-                    }
1978
-                }
1979
-                break;
1980
-
1981
-            case 'content':
1982
-                $o['info']['contents'][] = $options;
1983
-                break;
1984
-
1985
-            case 'annot':
1986
-                // add an annotation to this page
1987
-                if (!isset($o['info']['annot'])) {
1988
-                    $o['info']['annot'] = [];
1989
-                }
1990
-
1991
-                // $options should contain the id of the annotation dictionary
1992
-                $o['info']['annot'][] = $options;
1993
-                break;
1994
-
1995
-            case 'out':
1996
-                $res = "\n$id 0 obj\n<< /Type /Page";
1997
-                if (isset($o['info']['mediaBox'])) {
1998
-                    $tmp = $o['info']['mediaBox'];
1999
-                    $res .= "\n/MediaBox [" . sprintf(
2000
-                            '%.3F %.3F %.3F %.3F',
2001
-                            $tmp[0],
2002
-                            $tmp[1],
2003
-                            $tmp[2],
2004
-                            $tmp[3]
2005
-                        ) . ']';
2006
-                }
2007
-                $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
2008
-
2009
-                if (isset($o['info']['annot'])) {
2010
-                    $res .= "\n/Annots [";
2011
-                    foreach ($o['info']['annot'] as $aId) {
2012
-                        $res .= " $aId 0 R";
2013
-                    }
2014
-                    $res .= " ]";
2015
-                }
2016
-
2017
-                $count = count($o['info']['contents']);
2018
-                if ($count == 1) {
2019
-                    $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
2020
-                } else {
2021
-                    if ($count > 1) {
2022
-                        $res .= "\n/Contents [\n";
2023
-
2024
-                        // reverse the page contents so added objects are below normal content
2025
-                        //foreach (array_reverse($o['info']['contents']) as $cId) {
2026
-                        // Back to normal now that I've got transparency working --Benj
2027
-                        foreach ($o['info']['contents'] as $cId) {
2028
-                            $res .= "$cId 0 R\n";
2029
-                        }
2030
-                        $res .= "]";
2031
-                    }
2032
-                }
2033
-
2034
-                $res .= "\n>>\nendobj";
2035
-
2036
-                return $res;
2037
-        }
2038
-
2039
-        return null;
2040
-    }
2041
-
2042
-    /**
2043
-     * the contents objects hold all of the content which appears on pages
2044
-     *
2045
-     * @param $id
2046
-     * @param $action
2047
-     * @param string|array $options
2048
-     * @return null|string
2049
-     */
2050
-    protected function o_contents($id, $action, $options = '')
2051
-    {
2052
-        if ($action !== 'new') {
2053
-            $o = &$this->objects[$id];
2054
-        }
2055
-
2056
-        switch ($action) {
2057
-            case 'new':
2058
-                $this->objects[$id] = ['t' => 'contents', 'c' => '', 'info' => []];
2059
-                if (mb_strlen($options, '8bit') && intval($options)) {
2060
-                    // then this contents is the primary for a page
2061
-                    $this->objects[$id]['onPage'] = $options;
2062
-                } else {
2063
-                    if ($options === 'raw') {
2064
-                        // then this page contains some other type of system object
2065
-                        $this->objects[$id]['raw'] = 1;
2066
-                    }
2067
-                }
2068
-                break;
2069
-
2070
-            case 'add':
2071
-                // add more options to the declaration
2072
-                foreach ($options as $k => $v) {
2073
-                    $o['info'][$k] = $v;
2074
-                }
2075
-
2076
-            case 'out':
2077
-                $tmp = $o['c'];
2078
-                $res = "\n$id 0 obj\n";
2079
-
2080
-                if (isset($this->objects[$id]['raw'])) {
2081
-                    $res .= $tmp;
2082
-                } else {
2083
-                    $res .= "<<";
2084
-                    if ($this->compressionReady && $this->options['compression']) {
2085
-                        // then implement ZLIB based compression on this content stream
2086
-                        $res .= " /Filter /FlateDecode";
2087
-                        $tmp = gzcompress($tmp, 6);
2088
-                    }
2089
-
2090
-                    if ($this->encrypted) {
2091
-                        $this->encryptInit($id);
2092
-                        $tmp = $this->ARC4($tmp);
2093
-                    }
2094
-
2095
-                    foreach ($o['info'] as $k => $v) {
2096
-                        $res .= "\n/$k $v";
2097
-                    }
2098
-
2099
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
2100
-                }
2101
-
2102
-                $res .= "\nendobj";
2103
-
2104
-                return $res;
2105
-        }
2106
-
2107
-        return null;
2108
-    }
2109
-
2110
-    /**
2111
-     * @param $id
2112
-     * @param $action
2113
-     * @return string|null
2114
-     */
2115
-    protected function o_embedjs($id, $action)
2116
-    {
2117
-        switch ($action) {
2118
-            case 'new':
2119
-                $this->objects[$id] = [
2120
-                    't'    => 'embedjs',
2121
-                    'info' => [
2122
-                        'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
2123
-                    ]
2124
-                ];
2125
-                break;
2126
-
2127
-            case 'out':
2128
-                $o = &$this->objects[$id];
2129
-                $res = "\n$id 0 obj\n<< ";
2130
-                foreach ($o['info'] as $k => $v) {
2131
-                    $res .= "\n/$k $v";
2132
-                }
2133
-                $res .= "\n>>\nendobj";
2134
-
2135
-                return $res;
2136
-        }
2137
-
2138
-        return null;
2139
-    }
2140
-
2141
-    /**
2142
-     * @param $id
2143
-     * @param $action
2144
-     * @param string $code
2145
-     * @return null|string
2146
-     */
2147
-    protected function o_javascript($id, $action, $code = '')
2148
-    {
2149
-        switch ($action) {
2150
-            case 'new':
2151
-                $this->objects[$id] = [
2152
-                    't'    => 'javascript',
2153
-                    'info' => [
2154
-                        'S'  => '/JavaScript',
2155
-                        'JS' => '(' . $this->filterText($code, true, false) . ')',
2156
-                    ]
2157
-                ];
2158
-                break;
2159
-
2160
-            case 'out':
2161
-                $o = &$this->objects[$id];
2162
-                $res = "\n$id 0 obj\n<< ";
2163
-
2164
-                foreach ($o['info'] as $k => $v) {
2165
-                    $res .= "\n/$k $v";
2166
-                }
2167
-                $res .= "\n>>\nendobj";
2168
-
2169
-                return $res;
2170
-        }
2171
-
2172
-        return null;
2173
-    }
2174
-
2175
-    /**
2176
-     * an image object, will be an XObject in the document, includes description and data
2177
-     *
2178
-     * @param $id
2179
-     * @param $action
2180
-     * @param string $options
2181
-     * @return null|string
2182
-     */
2183
-    protected function o_image($id, $action, $options = '')
2184
-    {
2185
-        switch ($action) {
2186
-            case 'new':
2187
-                // make the new object
2188
-                $this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
2189
-
2190
-                $info =& $this->objects[$id]['info'];
2191
-
2192
-                $info['Type'] = '/XObject';
2193
-                $info['Subtype'] = '/Image';
2194
-                $info['Width'] = $options['iw'];
2195
-                $info['Height'] = $options['ih'];
2196
-
2197
-                if (isset($options['masked']) && $options['masked']) {
2198
-                    $info['SMask'] = ($this->numObj - 1) . ' 0 R';
2199
-                }
2200
-
2201
-                if (!isset($options['type']) || $options['type'] === 'jpg') {
2202
-                    if (!isset($options['channels'])) {
2203
-                        $options['channels'] = 3;
2204
-                    }
2205
-
2206
-                    switch ($options['channels']) {
2207
-                        case 1:
2208
-                            $info['ColorSpace'] = '/DeviceGray';
2209
-                            break;
2210
-                        case 4:
2211
-                            $info['ColorSpace'] = '/DeviceCMYK';
2212
-                            break;
2213
-                        default:
2214
-                            $info['ColorSpace'] = '/DeviceRGB';
2215
-                            break;
2216
-                    }
2217
-
2218
-                    if ($info['ColorSpace'] === '/DeviceCMYK') {
2219
-                        $info['Decode'] = '[1 0 1 0 1 0 1 0]';
2220
-                    }
2221
-
2222
-                    $info['Filter'] = '/DCTDecode';
2223
-                    $info['BitsPerComponent'] = 8;
2224
-                } else {
2225
-                    if ($options['type'] === 'png') {
2226
-                        $info['Filter'] = '/FlateDecode';
2227
-                        $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
2228
-
2229
-                        if ($options['isMask']) {
2230
-                            $info['ColorSpace'] = '/DeviceGray';
2231
-                        } else {
2232
-                            if (mb_strlen($options['pdata'], '8bit')) {
2233
-                                $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
2234
-                                $this->numObj++;
2235
-                                $this->o_contents($this->numObj, 'new');
2236
-                                $this->objects[$this->numObj]['c'] = $options['pdata'];
2237
-                                $tmp .= $this->numObj . ' 0 R';
2238
-                                $tmp .= ' ]';
2239
-                                $info['ColorSpace'] = $tmp;
2240
-
2241
-                                if (isset($options['transparency'])) {
2242
-                                    $transparency = $options['transparency'];
2243
-                                    switch ($transparency['type']) {
2244
-                                        case 'indexed':
2245
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2246
-                                            $info['Mask'] = $tmp;
2247
-                                            break;
2248
-
2249
-                                        case 'color-key':
2250
-                                            $tmp = ' [ ' .
2251
-                                                $transparency['r'] . ' ' . $transparency['r'] .
2252
-                                                $transparency['g'] . ' ' . $transparency['g'] .
2253
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2254
-                                                ' ] ';
2255
-                                            $info['Mask'] = $tmp;
2256
-                                            break;
2257
-                                    }
2258
-                                }
2259
-                            } else {
2260
-                                if (isset($options['transparency'])) {
2261
-                                    $transparency = $options['transparency'];
2262
-
2263
-                                    switch ($transparency['type']) {
2264
-                                        case 'indexed':
2265
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2266
-                                            $info['Mask'] = $tmp;
2267
-                                            break;
2268
-
2269
-                                        case 'color-key':
2270
-                                            $tmp = ' [ ' .
2271
-                                                $transparency['r'] . ' ' . $transparency['r'] . ' ' .
2272
-                                                $transparency['g'] . ' ' . $transparency['g'] . ' ' .
2273
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2274
-                                                ' ] ';
2275
-                                            $info['Mask'] = $tmp;
2276
-                                            break;
2277
-                                    }
2278
-                                }
2279
-                                $info['ColorSpace'] = '/' . $options['color'];
2280
-                            }
2281
-                        }
2282
-
2283
-                        $info['BitsPerComponent'] = $options['bitsPerComponent'];
2284
-                    }
2285
-                }
2286
-
2287
-                // assign it a place in the named resource dictionary as an external object, according to
2288
-                // the label passed in with it.
2289
-                $this->o_pages($this->currentNode, 'xObject', ['label' => $options['label'], 'objNum' => $id]);
2290
-
2291
-                // also make sure that we have the right procset object for it.
2292
-                $this->o_procset($this->procsetObjectId, 'add', 'ImageC');
2293
-                break;
2294
-
2295
-            case 'out':
2296
-                $o = &$this->objects[$id];
2297
-                $tmp = &$o['data'];
2298
-                $res = "\n$id 0 obj\n<<";
2299
-
2300
-                foreach ($o['info'] as $k => $v) {
2301
-                    $res .= "\n/$k $v";
2302
-                }
2303
-
2304
-                if ($this->encrypted) {
2305
-                    $this->encryptInit($id);
2306
-                    $tmp = $this->ARC4($tmp);
2307
-                }
2308
-
2309
-                $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
2310
-
2311
-                return $res;
2312
-        }
2313
-
2314
-        return null;
2315
-    }
2316
-
2317
-    /**
2318
-     * graphics state object
2319
-     *
2320
-     * @param $id
2321
-     * @param $action
2322
-     * @param string $options
2323
-     * @return null|string
2324
-     */
2325
-    protected function o_extGState($id, $action, $options = "")
2326
-    {
2327
-        static $valid_params = [
2328
-            "LW",
2329
-            "LC",
2330
-            "LC",
2331
-            "LJ",
2332
-            "ML",
2333
-            "D",
2334
-            "RI",
2335
-            "OP",
2336
-            "op",
2337
-            "OPM",
2338
-            "Font",
2339
-            "BG",
2340
-            "BG2",
2341
-            "UCR",
2342
-            "TR",
2343
-            "TR2",
2344
-            "HT",
2345
-            "FL",
2346
-            "SM",
2347
-            "SA",
2348
-            "BM",
2349
-            "SMask",
2350
-            "CA",
2351
-            "ca",
2352
-            "AIS",
2353
-            "TK"
2354
-        ];
2355
-
2356
-        switch ($action) {
2357
-            case "new":
2358
-                $this->objects[$id] = ['t' => 'extGState', 'info' => $options];
2359
-
2360
-                // Tell the pages about the new resource
2361
-                $this->numStates++;
2362
-                $this->o_pages($this->currentNode, 'extGState', ["objNum" => $id, "stateNum" => $this->numStates]);
2363
-                break;
2364
-
2365
-            case "out":
2366
-                $o = &$this->objects[$id];
2367
-                $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
2368
-
2369
-                foreach ($o["info"] as $k => $v) {
2370
-                    if (!in_array($k, $valid_params)) {
2371
-                        continue;
2372
-                    }
2373
-                    $res .= "/$k $v\n";
2374
-                }
2375
-
2376
-                $res .= ">>\nendobj";
2377
-
2378
-                return $res;
2379
-        }
2380
-
2381
-        return null;
2382
-    }
2383
-
2384
-    /**
2385
-     * @param integer $id
2386
-     * @param string $action
2387
-     * @param mixed $options
2388
-     * @return string
2389
-     */
2390
-    protected function o_xobject($id, $action, $options = '')
2391
-    {
2392
-        switch ($action) {
2393
-            case 'new':
2394
-                $this->objects[$id] = ['t' => 'xobject', 'info' => $options, 'c' => ''];
2395
-                break;
2396
-
2397
-            case 'procset':
2398
-                $this->objects[$id]['procset'] = $options;
2399
-                break;
2400
-
2401
-            case 'font':
2402
-                $this->objects[$id]['fonts'][$options['fontNum']] = [
2403
-                  'objNum' => $options['objNum'],
2404
-                  'fontNum' => $options['fontNum']
2405
-                ];
2406
-                break;
2407
-
2408
-            case 'xObject':
2409
-                $this->objects[$id]['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
2410
-                break;
2411
-
2412
-            case 'out':
2413
-                $o = &$this->objects[$id];
2414
-                $res = "\n$id 0 obj\n<< /Type /XObject\n";
2415
-
2416
-                foreach ($o["info"] as $k => $v) {
2417
-                    switch ($k) {
2418
-                        case 'Subtype':
2419
-                            $res .= "/Subtype /$v\n";
2420
-                            break;
2421
-                        case 'bbox':
2422
-                            $res .= "/BBox [";
2423
-                            foreach ($v as $value) {
2424
-                                $res .= sprintf("%.4F ", $value);
2425
-                            }
2426
-                            $res .= "]\n";
2427
-                            break;
2428
-                        default:
2429
-                            $res .= "/$k $v\n";
2430
-                            break;
2431
-                    }
2432
-                }
2433
-                $res .= "/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]\n";
2434
-
2435
-                $res .= "/Resources <<";
2436
-                if (isset($o['procset'])) {
2437
-                    $res .= "\n/ProcSet " . $o['procset'] . " 0 R";
2438
-                } else {
2439
-                    $res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
2440
-                }
2441
-                if (isset($o['fonts']) && count($o['fonts'])) {
2442
-                    $res .= "\n/Font << ";
2443
-                    foreach ($o['fonts'] as $finfo) {
2444
-                        $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
2445
-                    }
2446
-                    $res .= "\n>>";
2447
-                }
2448
-                if (isset($o['xObjects']) && count($o['xObjects'])) {
2449
-                    $res .= "\n/XObject << ";
2450
-                    foreach ($o['xObjects'] as $finfo) {
2451
-                        $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
2452
-                    }
2453
-                    $res .= "\n>>";
2454
-                }
2455
-                $res .= "\n>>\n";
2456
-
2457
-                $tmp = $o["c"];
2458
-                if ($this->compressionReady && $this->options['compression']) {
2459
-                    // then implement ZLIB based compression on this content stream
2460
-                    $res .= " /Filter /FlateDecode\n";
2461
-                    $tmp = gzcompress($tmp, 6);
2462
-                }
2463
-
2464
-                if ($this->encrypted) {
2465
-                    $this->encryptInit($id);
2466
-                    $tmp = $this->ARC4($tmp);
2467
-                }
2468
-
2469
-                $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2470
-                $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";
2471
-
2472
-                return $res;
2473
-        }
2474
-
2475
-        return null;
2476
-    }
2477
-
2478
-    /**
2479
-     * @param $id
2480
-     * @param $action
2481
-     * @param string $options
2482
-     * @return null|string
2483
-     */
2484
-    protected function o_acroform($id, $action, $options = '')
2485
-    {
2486
-        switch ($action) {
2487
-            case "new":
2488
-                $this->o_catalog($this->catalogId, 'acroform', $id);
2489
-                $this->objects[$id] = array('t' => 'acroform', 'info' => $options);
2490
-                break;
2491
-
2492
-            case 'addfield':
2493
-                $this->objects[$id]['info']['Fields'][] = $options;
2494
-                break;
2495
-
2496
-            case 'font':
2497
-                $this->objects[$id]['fonts'][$options['fontNum']] = [
2498
-                  'objNum' => $options['objNum'],
2499
-                  'fontNum' => $options['fontNum']
2500
-                ];
2501
-                break;
2502
-
2503
-            case "out":
2504
-                $o = &$this->objects[$id];
2505
-                $res = "\n$id 0 obj\n<<";
2506
-
2507
-                foreach ($o["info"] as $k => $v) {
2508
-                    switch ($k) {
2509
-                        case 'Fields':
2510
-                            $res .= " /Fields [";
2511
-                            foreach ($v as $i) {
2512
-                                $res .= "$i 0 R ";
2513
-                            }
2514
-                            $res .= "]\n";
2515
-                            break;
2516
-                        default:
2517
-                            $res .= "/$k $v\n";
2518
-                    }
2519
-                }
2520
-
2521
-                $res .= "/DR <<\n";
2522
-                if (isset($o['fonts']) && count($o['fonts'])) {
2523
-                    $res .= "/Font << \n";
2524
-                    foreach ($o['fonts'] as $finfo) {
2525
-                        $res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
2526
-                    }
2527
-                    $res .= ">>\n";
2528
-                }
2529
-                $res .= ">>\n";
2530
-
2531
-                $res .= ">>\nendobj";
2532
-
2533
-                return $res;
2534
-        }
2535
-
2536
-        return null;
2537
-    }
2538
-
2539
-    /**
2540
-     * @param $id
2541
-     * @param $action
2542
-     * @param mixed $options
2543
-     * @return null|string
2544
-     */
2545
-    protected function o_field($id, $action, $options = '')
2546
-    {
2547
-        switch ($action) {
2548
-            case "new":
2549
-                $this->o_page($options['pageid'], 'annot', $id);
2550
-                $this->o_acroform($this->acroFormId, 'addfield', $id);
2551
-                $this->objects[$id] = ['t' => 'field', 'info' => $options];
2552
-                break;
2553
-
2554
-            case 'set':
2555
-                $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2556
-                break;
2557
-
2558
-            case "out":
2559
-                $o = &$this->objects[$id];
2560
-                $res = "\n$id 0 obj\n<< /Type /Annot /Subtype /Widget \n";
2561
-
2562
-                $encrypted = $this->encrypted;
2563
-                if ($encrypted) {
2564
-                    $this->encryptInit($id);
2565
-                }
2566
-
2567
-                foreach ($o["info"] as $k => $v) {
2568
-                    switch ($k) {
2569
-                        case 'pageid':
2570
-                            $res .= "/P $v 0 R\n";
2571
-                            break;
2572
-                        case 'value':
2573
-                            if ($encrypted) {
2574
-                                $v = $this->filterText($this->ARC4($v), false, false);
2575
-                            }
2576
-                            $res .= "/V ($v)\n";
2577
-                            break;
2578
-                        case 'refvalue':
2579
-                            $res .= "/V $v 0 R\n";
2580
-                            break;
2581
-                        case 'da':
2582
-                            if ($encrypted) {
2583
-                                $v = $this->filterText($this->ARC4($v), false, false);
2584
-                            }
2585
-                            $res .= "/DA ($v)\n";
2586
-                            break;
2587
-                        case 'options':
2588
-                            $res .= "/Opt [\n";
2589
-                            foreach ($v as $opt) {
2590
-                                if ($encrypted) {
2591
-                                    $opt = $this->filterText($this->ARC4($opt), false, false);
2592
-                                }
2593
-                                $res .= "($opt)\n";
2594
-                            }
2595
-                            $res .= "]\n";
2596
-                            break;
2597
-                        case 'rect':
2598
-                            $res .= "/Rect [";
2599
-                            foreach ($v as $value) {
2600
-                                $res .= sprintf("%.4F ", $value);
2601
-                            }
2602
-                            $res .= "]\n";
2603
-                            break;
2604
-                        case 'appearance':
2605
-                            $res .= "/AP << ";
2606
-                            foreach ($v as $a => $ref) {
2607
-                                $res .= "/$a $ref 0 R ";
2608
-                            }
2609
-                            $res .= ">>\n";
2610
-                            break;
2611
-                        case 'T':
2612
-                            if ($encrypted) {
2613
-                                $v = $this->filterText($this->ARC4($v), false, false);
2614
-                            }
2615
-                            $res .= "/T ($v)\n";
2616
-                            break;
2617
-                        default:
2618
-                            $res .= "/$k $v\n";
2619
-                    }
2620
-
2621
-                }
2622
-
2623
-                $res .= ">>\nendobj";
2624
-
2625
-                return $res;
2626
-        }
2627
-
2628
-        return null;
2629
-    }
2630
-
2631
-    /**
2632
-     *
2633
-     * @param $id
2634
-     * @param $action
2635
-     * @param string $options
2636
-     * @return null|string
2637
-     */
2638
-    protected function o_sig($id, $action, $options = '')
2639
-    {
2640
-        $sign_maxlen = $this->signatureMaxLen;
2641
-
2642
-        switch ($action) {
2643
-            case "new":
2644
-                $this->objects[$id] = array('t' => 'sig', 'info' => $options);
2645
-                $this->byteRange[$id] = ['t' => 'sig'];
2646
-                break;
2647
-
2648
-            case 'byterange':
2649
-                $o = &$this->objects[$id];
2650
-                $content =& $options['content'];
2651
-                $content_len = strlen($content);
2652
-                $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2653
-                $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
2654
-                $rangeStartPos = $pos + $len + 1 + 10; // before '<'
2655
-                $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2656
-
2657
-                $fuid = uniqid();
2658
-                $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2659
-                $tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
2660
-
2661
-                if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
2662
-                    throw new \Exception("Unable to write temporary file for signing.");
2663
-                }
2664
-                if (file_put_contents($tmpInput, substr($content, $rangeStartPos + 2 + $sign_maxlen),
2665
-                    FILE_APPEND) === false) {
2666
-                    throw new \Exception("Unable to write temporary file for signing.");
2667
-                }
2668
-
2669
-                if (openssl_pkcs7_sign($tmpInput, $tmpOutput,
2670
-                    $o['info']['SignCert'],
2671
-                    array($o['info']['PrivKey'], $o['info']['Password']),
2672
-                    array(), PKCS7_BINARY | PKCS7_DETACHED) === false) {
2673
-                    throw new \Exception("Failed to prepare signature.");
2674
-                }
2675
-
2676
-                $signature = file_get_contents($tmpOutput);
2677
-
2678
-                unlink($tmpInput);
2679
-                unlink($tmpOutput);
2680
-
2681
-                $sign = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
2682
-                list($head, $signature) = explode("\n\n", $sign);
2683
-
2684
-                $signature = base64_decode(trim($signature));
2685
-
2686
-                $signature = current(unpack('H*', $signature));
2687
-                $signature = str_pad($signature, $sign_maxlen, '0');
2688
-                $siglen = strlen($signature);
2689
-                if (strlen($signature) > $sign_maxlen) {
2690
-                    throw new \Exception("Signature length ($siglen) exceeds the $sign_maxlen limit.");
2691
-                }
2692
-
2693
-                $content = substr_replace($content, $signature, $rangeStartPos + 1, $sign_maxlen);
2694
-                break;
2695
-
2696
-            case "out":
2697
-                $res = "\n$id 0 obj\n<<\n";
2698
-
2699
-                $encrypted = $this->encrypted;
2700
-                if ($encrypted) {
2701
-                    $this->encryptInit($id);
2702
-                }
2703
-
2704
-                $res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2705
-                $res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
2706
-                $res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
2707
-                $res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
2708
-
2709
-                $date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
2710
-                if ($encrypted) {
2711
-                    $date = $this->ARC4($date);
2712
-                }
2713
-
2714
-                $res .= "/M ($date)\n";
2715
-                $res .= "/Prop_Build << /App << /Name /DomPDF >> /Filter << /Name /Adobe.PPKLite >> >>\n";
2716
-
2717
-                $o = &$this->objects[$id];
2718
-                foreach ($o['info'] as $k => $v) {
2719
-                    switch ($k) {
2720
-                        case 'Name':
2721
-                        case 'Location':
2722
-                        case 'Reason':
2723
-                        case 'ContactInfo':
2724
-                            if ($v !== null && $v !== '') {
2725
-                                $res .= "/$k (" .
2726
-                                  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
2727
-                            }
2728
-                            break;
2729
-                    }
2730
-                }
2731
-                $res .= ">>\nendobj";
2732
-
2733
-                return $res;
2734
-        }
2735
-
2736
-        return null;
2737
-    }
2738
-
2739
-    /**
2740
-     * encryption object.
2741
-     *
2742
-     * @param $id
2743
-     * @param $action
2744
-     * @param string $options
2745
-     * @return string|null
2746
-     */
2747
-    protected function o_encryption($id, $action, $options = '')
2748
-    {
2749
-        switch ($action) {
2750
-            case 'new':
2751
-                // make the new object
2752
-                $this->objects[$id] = ['t' => 'encryption', 'info' => $options];
2753
-                $this->arc4_objnum = $id;
2754
-                break;
2755
-
2756
-            case 'keys':
2757
-                // figure out the additional parameters required
2758
-                $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2759
-                    . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2760
-                    . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2761
-                    . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2762
-
2763
-                $info = $this->objects[$id]['info'];
2764
-
2765
-                $len = mb_strlen($info['owner'], '8bit');
2766
-
2767
-                if ($len > 32) {
2768
-                    $owner = substr($info['owner'], 0, 32);
2769
-                } else {
2770
-                    if ($len < 32) {
2771
-                        $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2772
-                    } else {
2773
-                        $owner = $info['owner'];
2774
-                    }
2775
-                }
2776
-
2777
-                $len = mb_strlen($info['user'], '8bit');
2778
-                if ($len > 32) {
2779
-                    $user = substr($info['user'], 0, 32);
2780
-                } else {
2781
-                    if ($len < 32) {
2782
-                        $user = $info['user'] . substr($pad, 0, 32 - $len);
2783
-                    } else {
2784
-                        $user = $info['user'];
2785
-                    }
2786
-                }
2787
-
2788
-                $tmp = $this->md5_16($owner);
2789
-                $okey = substr($tmp, 0, 5);
2790
-                $this->ARC4_init($okey);
2791
-                $ovalue = $this->ARC4($user);
2792
-                $this->objects[$id]['info']['O'] = $ovalue;
2793
-
2794
-                // now make the u value, phew.
2795
-                $tmp = $this->md5_16(
2796
-                    $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2797
-                );
2798
-
2799
-                $ukey = substr($tmp, 0, 5);
2800
-                $this->ARC4_init($ukey);
2801
-                $this->encryptionKey = $ukey;
2802
-                $this->encrypted = true;
2803
-                $uvalue = $this->ARC4($pad);
2804
-                $this->objects[$id]['info']['U'] = $uvalue;
2805
-                // initialize the arc4 array
2806
-                break;
2807
-
2808
-            case 'out':
2809
-                $o = &$this->objects[$id];
2810
-
2811
-                $res = "\n$id 0 obj\n<<";
2812
-                $res .= "\n/Filter /Standard";
2813
-                $res .= "\n/V 1";
2814
-                $res .= "\n/R 2";
2815
-                $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2816
-                $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2817
-                // and the p-value needs to be converted to account for the twos-complement approach
2818
-                $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2819
-                $res .= "\n/P " . ($o['info']['p']);
2820
-                $res .= "\n>>\nendobj";
2821
-
2822
-                return $res;
2823
-        }
2824
-
2825
-        return null;
2826
-    }
2827
-
2828
-    protected function o_indirect_references($id, $action, $options = null)
2829
-    {
2830
-        switch ($action) {
2831
-            case 'new':
2832
-            case 'add':
2833
-                if ($id === 0) {
2834
-                    $id = ++$this->numObj;
2835
-                    $this->o_catalog($this->catalogId, 'names', $id);
2836
-                    $this->objects[$id] = ['t' => 'indirect_references', 'info' => $options];
2837
-                    $this->indirectReferenceId = $id;
2838
-                } else {
2839
-                    $this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2840
-                }
2841
-                break;
2842
-            case 'out':
2843
-                $res = "\n$id 0 obj << ";
2844
-
2845
-                foreach ($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2846
-                    $res .= "/$referenceObjName $referenceObjId 0 R ";
2847
-                }
2848
-
2849
-                $res .= ">> endobj";
2850
-                return $res;
2851
-        }
2852
-
2853
-        return null;
2854
-    }
2855
-
2856
-    protected function o_names($id, $action, $options = null)
2857
-    {
2858
-        switch ($action) {
2859
-            case 'new':
2860
-            case 'add':
2861
-                if ($id === 0) {
2862
-                    $id = ++$this->numObj;
2863
-                    $this->objects[$id] = ['t' => 'names', 'info' => [$options]];
2864
-                    $this->o_indirect_references($this->indirectReferenceId, 'add', ['EmbeddedFiles' => $id]);
2865
-                    $this->embeddedFilesId = $id;
2866
-                } else {
2867
-                    $this->objects[$id]['info'][] = $options;
2868
-                }
2869
-                break;
2870
-            case 'out':
2871
-                $info = &$this->objects[$id]['info'];
2872
-                $res = '';
2873
-                if (count($info) > 0) {
2874
-                    $res = "\n$id 0 obj << /Names [ ";
2875
-
2876
-                    if ($this->encrypted) {
2877
-                        $this->encryptInit($id);
2878
-                    }
2879
-
2880
-                    foreach ($info as $entry) {
2881
-                        if ($this->encrypted) {
2882
-                            $filename = $this->ARC4($entry['filename']);
2883
-                        } else {
2884
-                            $filename = $entry['filename'];
2885
-                        }
2886
-
2887
-                        $res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
2888
-                    }
2889
-
2890
-                    $res .= "] >> endobj";
2891
-                }
2892
-                return $res;
2893
-        }
2894
-
2895
-        return null;
2896
-    }
2897
-
2898
-    protected function o_embedded_file_dictionary($id, $action, $options = null)
2899
-    {
2900
-        switch ($action) {
2901
-            case 'new':
2902
-                $embeddedFileId = ++$this->numObj;
2903
-                $options['embedded_reference'] = $embeddedFileId;
2904
-                $this->objects[$id] = ['t' => 'embedded_file_dictionary', 'info' => $options];
2905
-                $this->o_embedded_file($embeddedFileId, 'new', $options);
2906
-                $options['dict_reference'] = $id;
2907
-                $this->o_names($this->embeddedFilesId, 'add', $options);
2908
-                break;
2909
-            case 'out':
2910
-                $info = &$this->objects[$id]['info'];
2911
-                $filename = $this->utf8toUtf16BE($info['filename']);
2912
-                $description = $this->utf8toUtf16BE($info['description']);
2913
-
2914
-                if ($this->encrypted) {
2915
-                    $this->encryptInit($id);
2916
-                    $filename = $this->ARC4($filename);
2917
-                    $description = $this->ARC4($description);
2918
-                }
2919
-
2920
-                $filename = $this->filterText($filename, false, false);
2921
-                $description = $this->filterText($description, false, false);
2922
-
2923
-                $res = "\n$id 0 obj <</Type /Filespec /EF";
2924
-                $res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
2925
-                $res .= " /F ($filename) /UF ($filename) /Desc ($description)";
2926
-                $res .= " >> endobj";
2927
-                return $res;
2928
-        }
2929
-
2930
-        return null;
2931
-    }
2932
-
2933
-    protected function o_embedded_file($id, $action, $options = null): ?string
2934
-    {
2935
-        switch ($action) {
2936
-            case 'new':
2937
-                $this->objects[$id] = ['t' => 'embedded_file', 'info' => $options];
2938
-                break;
2939
-            case 'out':
2940
-                $info = &$this->objects[$id]['info'];
2941
-
2942
-                if ($this->compressionReady) {
2943
-                    $filepath = $info['filepath'];
2944
-                    $checksum = md5_file($filepath);
2945
-                    $f = fopen($filepath, "rb");
2946
-
2947
-                    $file_content_compressed = '';
2948
-                    $deflateContext = deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => 6]);
2949
-                    while (($block = fread($f, 8192))) {
2950
-                        $file_content_compressed .= deflate_add($deflateContext, $block, ZLIB_NO_FLUSH);
2951
-                    }
2952
-                    $file_content_compressed .= deflate_add($deflateContext, '', ZLIB_FINISH);
2953
-                    $file_size_uncompressed = ftell($f);
2954
-                    fclose($f);
2955
-                } else {
2956
-                    $file_content = file_get_contents($info['filepath']);
2957
-                    $file_size_uncompressed = mb_strlen($file_content, '8bit');
2958
-                    $checksum = md5($file_content);
2959
-                }
2960
-
2961
-                if ($this->encrypted) {
2962
-                    $this->encryptInit($id);
2963
-                    $checksum = $this->ARC4($checksum);
2964
-                    $file_content_compressed = $this->ARC4($file_content_compressed);
2965
-                }
2966
-                $file_size_compressed = mb_strlen($file_content_compressed, '8bit');
2967
-
2968
-                $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
2969
-                    " /Type/EmbeddedFile /Filter/FlateDecode" .
2970
-                    " /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
2971
-
2972
-                return $res;
2973
-        }
2974
-
2975
-        return null;
2976
-    }
2977
-
2978
-    /**
2979
-     * ARC4 functions
2980
-     * A series of function to implement ARC4 encoding in PHP
2981
-     */
2982
-
2983
-    /**
2984
-     * calculate the 16 byte version of the 128 bit md5 digest of the string
2985
-     *
2986
-     * @param $string
2987
-     * @return string
2988
-     */
2989
-    function md5_16($string)
2990
-    {
2991
-        $tmp = md5($string);
2992
-        $out = '';
2993
-        for ($i = 0; $i <= 30; $i = $i + 2) {
2994
-            $out .= chr(hexdec(substr($tmp, $i, 2)));
2995
-        }
2996
-
2997
-        return $out;
2998
-    }
2999
-
3000
-    /**
3001
-     * initialize the encryption for processing a particular object
3002
-     *
3003
-     * @param $id
3004
-     */
3005
-    function encryptInit($id)
3006
-    {
3007
-        $tmp = $this->encryptionKey;
3008
-        $hex = dechex($id);
3009
-        if (mb_strlen($hex, '8bit') < 6) {
3010
-            $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
3011
-        }
3012
-        $tmp .= chr(hexdec(substr($hex, 4, 2)))
3013
-            . chr(hexdec(substr($hex, 2, 2)))
3014
-            . chr(hexdec(substr($hex, 0, 2)))
3015
-            . chr(0)
3016
-            . chr(0)
3017
-        ;
3018
-        $key = $this->md5_16($tmp);
3019
-        $this->ARC4_init(substr($key, 0, 10));
3020
-    }
3021
-
3022
-    /**
3023
-     * initialize the ARC4 encryption
3024
-     *
3025
-     * @param string $key
3026
-     */
3027
-    function ARC4_init($key = '')
3028
-    {
3029
-        $this->arc4 = '';
3030
-
3031
-        // setup the control array
3032
-        if (mb_strlen($key, '8bit') == 0) {
3033
-            return;
3034
-        }
3035
-
3036
-        $k = '';
3037
-        while (mb_strlen($k, '8bit') < 256) {
3038
-            $k .= $key;
3039
-        }
3040
-
3041
-        $k = substr($k, 0, 256);
3042
-        for ($i = 0; $i < 256; $i++) {
3043
-            $this->arc4 .= chr($i);
3044
-        }
3045
-
3046
-        $j = 0;
3047
-
3048
-        for ($i = 0; $i < 256; $i++) {
3049
-            $t = $this->arc4[$i];
3050
-            $j = ($j + ord($t) + ord($k[$i])) % 256;
3051
-            $this->arc4[$i] = $this->arc4[$j];
3052
-            $this->arc4[$j] = $t;
3053
-        }
3054
-    }
3055
-
3056
-    /**
3057
-     * ARC4 encrypt a text string
3058
-     *
3059
-     * @param $text
3060
-     * @return string
3061
-     */
3062
-    function ARC4($text)
3063
-    {
3064
-        $len = mb_strlen($text, '8bit');
3065
-        $a = 0;
3066
-        $b = 0;
3067
-        $c = $this->arc4;
3068
-        $out = '';
3069
-        for ($i = 0; $i < $len; $i++) {
3070
-            $a = ($a + 1) % 256;
3071
-            $t = $c[$a];
3072
-            $b = ($b + ord($t)) % 256;
3073
-            $c[$a] = $c[$b];
3074
-            $c[$b] = $t;
3075
-            $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
3076
-            $out .= chr(ord($text[$i]) ^ $k);
3077
-        }
3078
-
3079
-        return $out;
3080
-    }
3081
-
3082
-    /**
3083
-     * functions which can be called to adjust or add to the document
3084
-     */
3085
-
3086
-    /**
3087
-     * add a link in the document to an external URL
3088
-     *
3089
-     * @param $url
3090
-     * @param $x0
3091
-     * @param $y0
3092
-     * @param $x1
3093
-     * @param $y1
3094
-     */
3095
-    function addLink($url, $x0, $y0, $x1, $y1)
3096
-    {
3097
-        $this->numObj++;
3098
-        $info = ['type' => 'link', 'url' => $url, 'rect' => [$x0, $y0, $x1, $y1]];
3099
-        $this->o_annotation($this->numObj, 'new', $info);
3100
-    }
3101
-
3102
-    /**
3103
-     * add a link in the document to an internal destination (ie. within the document)
3104
-     *
3105
-     * @param $label
3106
-     * @param $x0
3107
-     * @param $y0
3108
-     * @param $x1
3109
-     * @param $y1
3110
-     */
3111
-    function addInternalLink($label, $x0, $y0, $x1, $y1)
3112
-    {
3113
-        $this->numObj++;
3114
-        $info = ['type' => 'ilink', 'label' => $label, 'rect' => [$x0, $y0, $x1, $y1]];
3115
-        $this->o_annotation($this->numObj, 'new', $info);
3116
-    }
3117
-
3118
-    /**
3119
-     * set the encryption of the document
3120
-     * can be used to turn it on and/or set the passwords which it will have.
3121
-     * also the functions that the user will have are set here, such as print, modify, add
3122
-     *
3123
-     * @param string $userPass
3124
-     * @param string $ownerPass
3125
-     * @param array $pc
3126
-     */
3127
-    function setEncryption($userPass = '', $ownerPass = '', $pc = [])
3128
-    {
3129
-        $p = bindec("11000000");
3130
-
3131
-        $options = ['print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32];
3132
-
3133
-        foreach ($pc as $k => $v) {
3134
-            if ($v && isset($options[$k])) {
3135
-                $p += $options[$k];
3136
-            } else {
3137
-                if (isset($options[$v])) {
3138
-                    $p += $options[$v];
3139
-                }
3140
-            }
3141
-        }
3142
-
3143
-        // implement encryption on the document
3144
-        if ($this->arc4_objnum == 0) {
3145
-            // then the block does not exist already, add it.
3146
-            $this->numObj++;
3147
-            if (mb_strlen($ownerPass) == 0) {
3148
-                $ownerPass = $userPass;
3149
-            }
3150
-
3151
-            $this->o_encryption($this->numObj, 'new', ['user' => $userPass, 'owner' => $ownerPass, 'p' => $p]);
3152
-        }
3153
-    }
3154
-
3155
-    /**
3156
-     * should be used for internal checks, not implemented as yet
3157
-     */
3158
-    function checkAllHere()
3159
-    {
3160
-    }
3161
-
3162
-    /**
3163
-     * return the pdf stream as a string returned from the function
3164
-     *
3165
-     * @param bool $debug
3166
-     * @return string
3167
-     */
3168
-    function output($debug = false)
3169
-    {
3170
-        if ($debug) {
3171
-            // turn compression off
3172
-            $this->options['compression'] = false;
3173
-        }
3174
-
3175
-        if ($this->javascript) {
3176
-            $this->numObj++;
3177
-
3178
-            $js_id = $this->numObj;
3179
-            $this->o_embedjs($js_id, 'new');
3180
-            $this->o_javascript(++$this->numObj, 'new', $this->javascript);
3181
-
3182
-            $id = $this->catalogId;
3183
-
3184
-            $this->o_indirect_references($this->indirectReferenceId, 'add', ['JavaScript' => $js_id]);
3185
-        }
3186
-
3187
-        if ($this->fileIdentifier === '') {
3188
-            $tmp = implode('', $this->objects[$this->infoObject]['info']);
3189
-            $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
3190
-        }
3191
-
3192
-        if ($this->arc4_objnum) {
3193
-            $this->o_encryption($this->arc4_objnum, 'keys');
3194
-            $this->ARC4_init($this->encryptionKey);
3195
-        }
3196
-
3197
-        $this->checkAllHere();
3198
-
3199
-        $xref = [];
3200
-        $content = '%PDF-' . self::PDF_VERSION;
3201
-        $pos = mb_strlen($content, '8bit');
3202
-
3203
-        // pre-process o_font objects before output of all objects
3204
-        foreach ($this->objects as $k => $v) {
3205
-            if ($v['t'] === 'font') {
3206
-                $this->o_font($k, 'add');
3207
-            }
3208
-        }
3209
-
3210
-        foreach ($this->objects as $k => $v) {
3211
-            $tmp = 'o_' . $v['t'];
3212
-            $cont = $this->$tmp($k, 'out');
3213
-            $content .= $cont;
3214
-            $xref[] = $pos + 1; //+1 to account for \n at the start of each object
3215
-            $pos += mb_strlen($cont, '8bit');
3216
-        }
3217
-
3218
-        $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
3219
-
3220
-        foreach ($xref as $p) {
3221
-            $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
3222
-        }
3223
-
3224
-        $content .= "trailer\n<<\n" .
3225
-            '/Size ' . (count($xref) + 1) . "\n" .
3226
-            '/Root 1 0 R' . "\n" .
3227
-            '/Info ' . $this->infoObject . " 0 R\n"
3228
-        ;
3229
-
3230
-        // if encryption has been applied to this document then add the marker for this dictionary
3231
-        if ($this->arc4_objnum > 0) {
3232
-            $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
3233
-        }
3234
-
3235
-        $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
3236
-
3237
-        // account for \n added at start of xref table
3238
-        $pos++;
3239
-
3240
-        $content .= ">>\nstartxref\n$pos\n%%EOF\n";
3241
-
3242
-        if (count($this->byteRange) > 0) {
3243
-            foreach ($this->byteRange as $k => $v) {
3244
-                $tmp = 'o_' . $v['t'];
3245
-                $this->$tmp($k, 'byterange', ['content' => &$content]);
3246
-            }
3247
-        }
3248
-
3249
-        return $content;
3250
-    }
3251
-
3252
-    /**
3253
-     * initialize a new document
3254
-     * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
3255
-     * this function is called automatically by the constructor function
3256
-     *
3257
-     * @param array $pageSize
3258
-     */
3259
-    private function newDocument($pageSize = [0, 0, 612, 792])
3260
-    {
3261
-        $this->numObj = 0;
3262
-        $this->objects = [];
3263
-
3264
-        $this->numObj++;
3265
-        $this->o_catalog($this->numObj, 'new');
3266
-
3267
-        $this->numObj++;
3268
-        $this->o_outlines($this->numObj, 'new');
3269
-
3270
-        $this->numObj++;
3271
-        $this->o_pages($this->numObj, 'new');
3272
-
3273
-        $this->o_pages($this->numObj, 'mediaBox', $pageSize);
3274
-        $this->currentNode = 3;
3275
-
3276
-        $this->numObj++;
3277
-        $this->o_procset($this->numObj, 'new');
3278
-
3279
-        $this->numObj++;
3280
-        $this->o_info($this->numObj, 'new');
3281
-
3282
-        $this->numObj++;
3283
-        $this->o_page($this->numObj, 'new');
3284
-
3285
-        // need to store the first page id as there is no way to get it to the user during
3286
-        // startup
3287
-        $this->firstPageId = $this->currentContents;
3288
-    }
3289
-
3290
-    /**
3291
-     * open the font file and return a php structure containing it.
3292
-     * first check if this one has been done before and saved in a form more suited to php
3293
-     * note that if a php serialized version does not exist it will try and make one, but will
3294
-     * require write access to the directory to do it... it is MUCH faster to have these serialized
3295
-     * files.
3296
-     *
3297
-     * @param $font
3298
-     */
3299
-    private function openFont($font)
3300
-    {
3301
-        // assume that $font contains the path and file but not the extension
3302
-        $name = basename($font);
3303
-        $dir = dirname($font);
3304
-
3305
-        $fontcache = $this->fontcache;
3306
-        if ($fontcache == '') {
3307
-            $fontcache = $dir;
3308
-        }
3309
-
3310
-        //$name       filename without folder and extension of font metrics
3311
-        //$dir        folder of font metrics
3312
-        //$fontcache  folder of runtime created php serialized version of font metrics.
3313
-        //            If this is not given, the same folder as the font metrics will be used.
3314
-        //            Storing and reusing serialized versions improves speed much
3315
-
3316
-        $this->addMessage("openFont: $font - $name");
3317
-
3318
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3319
-            $metrics_name = "$name.afm";
3320
-        } else {
3321
-            $metrics_name = "$name.ufm";
3322
-        }
3323
-
3324
-        $cache_name = "$metrics_name.json";
3325
-        $this->addMessage("metrics: $metrics_name, cache: $cache_name");
1373
+				$res = "\n$id 0 obj\n";
1374
+				$res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1375
+				$res .= "stream\n" . $stream . "\nendstream" . "\nendobj";
1376
+
1377
+				return $res;
1378
+		}
1379
+
1380
+		return null;
1381
+	}
1382
+
1383
+	/**
1384
+	 * a font descriptor, needed for including additional fonts
1385
+	 *
1386
+	 * @param $id
1387
+	 * @param $action
1388
+	 * @param string $options
1389
+	 * @return null|string
1390
+	 */
1391
+	protected function o_fontDescriptor($id, $action, $options = '')
1392
+	{
1393
+		if ($action !== 'new') {
1394
+			$o = &$this->objects[$id];
1395
+		}
1396
+
1397
+		switch ($action) {
1398
+			case 'new':
1399
+				$this->objects[$id] = ['t' => 'fontDescriptor', 'info' => $options];
1400
+				break;
1401
+
1402
+			case 'out':
1403
+				$res = "\n$id 0 obj\n<< /Type /FontDescriptor\n";
1404
+				foreach ($o['info'] as $label => $value) {
1405
+					switch ($label) {
1406
+						case 'Ascent':
1407
+						case 'CapHeight':
1408
+						case 'Descent':
1409
+						case 'Flags':
1410
+						case 'ItalicAngle':
1411
+						case 'StemV':
1412
+						case 'AvgWidth':
1413
+						case 'Leading':
1414
+						case 'MaxWidth':
1415
+						case 'MissingWidth':
1416
+						case 'StemH':
1417
+						case 'XHeight':
1418
+						case 'CharSet':
1419
+							if (mb_strlen($value, '8bit')) {
1420
+								$res .= "/$label $value\n";
1421
+							}
1422
+
1423
+							break;
1424
+						case 'FontFile':
1425
+						case 'FontFile2':
1426
+						case 'FontFile3':
1427
+							$res .= "/$label $value 0 R\n";
1428
+							break;
1429
+
1430
+						case 'FontBBox':
1431
+							$res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n";
1432
+							break;
1433
+
1434
+						case 'FontName':
1435
+							$res .= "/$label /$value\n";
1436
+							break;
1437
+					}
1438
+				}
1439
+
1440
+				$res .= ">>\nendobj";
1441
+
1442
+				return $res;
1443
+		}
1444
+
1445
+		return null;
1446
+	}
1447
+
1448
+	/**
1449
+	 * the font encoding
1450
+	 *
1451
+	 * @param $id
1452
+	 * @param $action
1453
+	 * @param string $options
1454
+	 * @return null|string
1455
+	 */
1456
+	protected function o_fontEncoding($id, $action, $options = '')
1457
+	{
1458
+		if ($action !== 'new') {
1459
+			$o = &$this->objects[$id];
1460
+		}
1461
+
1462
+		switch ($action) {
1463
+			case 'new':
1464
+				// the options array should contain 'differences' and maybe 'encoding'
1465
+				$this->objects[$id] = ['t' => 'fontEncoding', 'info' => $options];
1466
+				break;
1467
+
1468
+			case 'out':
1469
+				$res = "\n$id 0 obj\n<< /Type /Encoding\n";
1470
+				if (!isset($o['info']['encoding'])) {
1471
+					$o['info']['encoding'] = 'WinAnsiEncoding';
1472
+				}
1473
+
1474
+				if ($o['info']['encoding'] !== 'none') {
1475
+					$res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1476
+				}
1477
+
1478
+				$res .= "/Differences \n[";
1479
+
1480
+				$onum = -100;
1481
+
1482
+				foreach ($o['info']['differences'] as $num => $label) {
1483
+					if ($num != $onum + 1) {
1484
+						// we cannot make use of consecutive numbering
1485
+						$res .= "\n$num /$label";
1486
+					} else {
1487
+						$res .= " /$label";
1488
+					}
1489
+
1490
+					$onum = $num;
1491
+				}
1492
+
1493
+				$res .= "\n]\n>>\nendobj";
1494
+
1495
+				return $res;
1496
+		}
1497
+
1498
+		return null;
1499
+	}
1500
+
1501
+	/**
1502
+	 * a descendent cid font, needed for unicode fonts
1503
+	 *
1504
+	 * @param $id
1505
+	 * @param $action
1506
+	 * @param string|array $options
1507
+	 * @return null|string
1508
+	 */
1509
+	protected function o_fontDescendentCID($id, $action, $options = '')
1510
+	{
1511
+		if ($action !== 'new') {
1512
+			$o = &$this->objects[$id];
1513
+		}
1514
+
1515
+		switch ($action) {
1516
+			case 'new':
1517
+				$this->objects[$id] = ['t' => 'fontDescendentCID', 'info' => $options];
1518
+
1519
+				// we need a CID system info section
1520
+				$cidSystemInfoId = ++$this->numObj;
1521
+				$this->o_cidSystemInfo($cidSystemInfoId, 'new');
1522
+				$this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId;
1523
+
1524
+				// and a CID to GID map
1525
+				$cidToGidMapId = ++$this->numObj;
1526
+				$this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options);
1527
+				$this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId;
1528
+				break;
1529
+
1530
+			case 'add':
1531
+				foreach ($options as $k => $v) {
1532
+					switch ($k) {
1533
+						case 'BaseFont':
1534
+							$o['info']['name'] = $v;
1535
+							break;
1536
+
1537
+						case 'FirstChar':
1538
+						case 'LastChar':
1539
+						case 'MissingWidth':
1540
+						case 'FontDescriptor':
1541
+						case 'SubType':
1542
+							$this->addMessage("o_fontDescendentCID $k : $v");
1543
+							$o['info'][$k] = $v;
1544
+							break;
1545
+					}
1546
+				}
1547
+
1548
+				// pass values down to cid to gid map
1549
+				$this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options);
1550
+				break;
1551
+
1552
+			case 'out':
1553
+				$res = "\n$id 0 obj\n";
1554
+				$res .= "<</Type /Font\n";
1555
+				$res .= "/Subtype /CIDFontType2\n";
1556
+				$res .= "/BaseFont /" . $o['info']['name'] . "\n";
1557
+				$res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1558
+				//      if (isset($o['info']['FirstChar'])) {
1559
+				//        $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1560
+				//      }
1561
+
1562
+				//      if (isset($o['info']['LastChar'])) {
1563
+				//        $res.= "/LastChar ".$o['info']['LastChar']."\n";
1564
+				//      }
1565
+				if (isset($o['info']['FontDescriptor'])) {
1566
+					$res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1567
+				}
1568
+
1569
+				if (isset($o['info']['MissingWidth'])) {
1570
+					$res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1571
+				}
1572
+
1573
+				if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
1574
+					$cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths'];
1575
+					$w = '';
1576
+					foreach ($cid_widths as $cid => $width) {
1577
+						$w .= "$cid [$width] ";
1578
+					}
1579
+					$res .= "/W [$w]\n";
1580
+				}
1581
+
1582
+				$res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1583
+				$res .= ">>\n";
1584
+				$res .= "endobj";
1585
+
1586
+				return $res;
1587
+		}
1588
+
1589
+		return null;
1590
+	}
1591
+
1592
+	/**
1593
+	 * CID system info section, needed for unicode fonts
1594
+	 *
1595
+	 * @param $id
1596
+	 * @param $action
1597
+	 * @return null|string
1598
+	 */
1599
+	protected function o_cidSystemInfo($id, $action)
1600
+	{
1601
+		switch ($action) {
1602
+			case 'new':
1603
+				$this->objects[$id] = [
1604
+					't' => 'cidSystemInfo'
1605
+				];
1606
+				break;
1607
+			case 'add':
1608
+				break;
1609
+			case 'out':
1610
+				$ordering = 'UCS';
1611
+				$registry = 'Adobe';
1612
+
1613
+				if ($this->encrypted) {
1614
+					$this->encryptInit($id);
1615
+					$ordering = $this->ARC4($ordering);
1616
+					$registry = $this->ARC4($registry);
1617
+				}
1618
+
1619
+
1620
+				$res = "\n$id 0 obj\n";
1621
+
1622
+				$res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
1623
+				$res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
1624
+				$res .= "/Supplement 0\n"; // The supplement number of the character collection.
1625
+				$res .= ">>";
1626
+
1627
+				$res .= "\nendobj";
1628
+
1629
+				return $res;
1630
+		}
1631
+
1632
+		return null;
1633
+	}
1634
+
1635
+	/**
1636
+	 * a font glyph to character map, needed for unicode fonts
1637
+	 *
1638
+	 * @param $id
1639
+	 * @param $action
1640
+	 * @param string $options
1641
+	 * @return null|string
1642
+	 */
1643
+	protected function o_fontGIDtoCIDMap($id, $action, $options = '')
1644
+	{
1645
+		if ($action !== 'new') {
1646
+			$o = &$this->objects[$id];
1647
+		}
1648
+
1649
+		switch ($action) {
1650
+			case 'new':
1651
+				$this->objects[$id] = ['t' => 'fontGIDtoCIDMap', 'info' => $options];
1652
+				break;
1653
+
1654
+			case 'out':
1655
+				$res = "\n$id 0 obj\n";
1656
+				$fontFileName = $o['info']['fontFileName'];
1657
+				$tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']);
1658
+
1659
+				$compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1660
+					$this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1661
+
1662
+				if (!$compressed && isset($o['raw'])) {
1663
+					$res .= $tmp;
1664
+				} else {
1665
+					$res .= "<<";
1666
+
1667
+					if (!$compressed && $this->compressionReady && $this->options['compression']) {
1668
+						// then implement ZLIB based compression on this content stream
1669
+						$compressed = true;
1670
+						$tmp = gzcompress($tmp, 6);
1671
+					}
1672
+					if ($compressed) {
1673
+						$res .= "\n/Filter /FlateDecode";
1674
+					}
1675
+
1676
+					if ($this->encrypted) {
1677
+						$this->encryptInit($id);
1678
+						$tmp = $this->ARC4($tmp);
1679
+					}
1680
+
1681
+					$res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1682
+				}
1683
+
1684
+				$res .= "\nendobj";
1685
+
1686
+				return $res;
1687
+		}
1688
+
1689
+		return null;
1690
+	}
1691
+
1692
+	/**
1693
+	 * the document procset, solves some problems with printing to old PS printers
1694
+	 *
1695
+	 * @param $id
1696
+	 * @param $action
1697
+	 * @param string $options
1698
+	 * @return null|string
1699
+	 */
1700
+	protected function o_procset($id, $action, $options = '')
1701
+	{
1702
+		if ($action !== 'new') {
1703
+			$o = &$this->objects[$id];
1704
+		}
1705
+
1706
+		switch ($action) {
1707
+			case 'new':
1708
+				$this->objects[$id] = ['t' => 'procset', 'info' => ['PDF' => 1, 'Text' => 1]];
1709
+				$this->o_pages($this->currentNode, 'procset', $id);
1710
+				$this->procsetObjectId = $id;
1711
+				break;
1712
+
1713
+			case 'add':
1714
+				// this is to add new items to the procset list, despite the fact that this is considered
1715
+				// obsolete, the items are required for printing to some postscript printers
1716
+				switch ($options) {
1717
+					case 'ImageB':
1718
+					case 'ImageC':
1719
+					case 'ImageI':
1720
+						$o['info'][$options] = 1;
1721
+						break;
1722
+				}
1723
+				break;
1724
+
1725
+			case 'out':
1726
+				$res = "\n$id 0 obj\n[";
1727
+				foreach ($o['info'] as $label => $val) {
1728
+					$res .= "/$label ";
1729
+				}
1730
+				$res .= "]\nendobj";
1731
+
1732
+				return $res;
1733
+		}
1734
+
1735
+		return null;
1736
+	}
1737
+
1738
+	/**
1739
+	 * define the document information
1740
+	 *
1741
+	 * @param $id
1742
+	 * @param $action
1743
+	 * @param string $options
1744
+	 * @return null|string
1745
+	 */
1746
+	protected function o_info($id, $action, $options = '')
1747
+	{
1748
+		switch ($action) {
1749
+			case 'new':
1750
+				$this->infoObject = $id;
1751
+				$date = 'D:' . @date('Ymd');
1752
+				$this->objects[$id] = [
1753
+					't'    => 'info',
1754
+					'info' => [
1755
+						'Producer'      => 'CPDF (dompdf)',
1756
+						'CreationDate' => $date
1757
+					]
1758
+				];
1759
+				break;
1760
+			case 'Title':
1761
+			case 'Author':
1762
+			case 'Subject':
1763
+			case 'Keywords':
1764
+			case 'Creator':
1765
+			case 'Producer':
1766
+			case 'CreationDate':
1767
+			case 'ModDate':
1768
+			case 'Trapped':
1769
+				$this->objects[$id]['info'][$action] = $options;
1770
+				break;
1771
+
1772
+			case 'out':
1773
+				$encrypted = $this->encrypted;
1774
+				if ($encrypted) {
1775
+					$this->encryptInit($id);
1776
+				}
1777
+
1778
+				$res = "\n$id 0 obj\n<<\n";
1779
+				$o = &$this->objects[$id];
1780
+				foreach ($o['info'] as $k => $v) {
1781
+					$res .= "/$k (";
1782
+
1783
+					// dates must be outputted as-is, without Unicode transformations
1784
+					if ($k !== 'CreationDate' && $k !== 'ModDate') {
1785
+						$v = $this->utf8toUtf16BE($v);
1786
+					}
1787
+
1788
+					if ($encrypted) {
1789
+						$v = $this->ARC4($v);
1790
+					}
1791
+
1792
+					$res .= $this->filterText($v, false, false);
1793
+					$res .= ")\n";
1794
+				}
1795
+
1796
+				$res .= ">>\nendobj";
1797
+
1798
+				return $res;
1799
+		}
1800
+
1801
+		return null;
1802
+	}
1803
+
1804
+	/**
1805
+	 * an action object, used to link to URLS initially
1806
+	 *
1807
+	 * @param $id
1808
+	 * @param $action
1809
+	 * @param string $options
1810
+	 * @return null|string
1811
+	 */
1812
+	protected function o_action($id, $action, $options = '')
1813
+	{
1814
+		if ($action !== 'new') {
1815
+			$o = &$this->objects[$id];
1816
+		}
1817
+
1818
+		switch ($action) {
1819
+			case 'new':
1820
+				if (is_array($options)) {
1821
+					$this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => $options['type']];
1822
+				} else {
1823
+					// then assume a URI action
1824
+					$this->objects[$id] = ['t' => 'action', 'info' => $options, 'type' => 'URI'];
1825
+				}
1826
+				break;
1827
+
1828
+			case 'out':
1829
+				if ($this->encrypted) {
1830
+					$this->encryptInit($id);
1831
+				}
1832
+
1833
+				$res = "\n$id 0 obj\n<< /Type /Action";
1834
+				switch ($o['type']) {
1835
+					case 'ilink':
1836
+						if (!isset($this->destinations[(string)$o['info']['label']])) {
1837
+							break;
1838
+						}
1839
+
1840
+						// there will be an 'label' setting, this is the name of the destination
1841
+						$res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1842
+						break;
1843
+
1844
+					case 'URI':
1845
+						$res .= "\n/S /URI\n/URI (";
1846
+						if ($this->encrypted) {
1847
+							$res .= $this->filterText($this->ARC4($o['info']), false, false);
1848
+						} else {
1849
+							$res .= $this->filterText($o['info'], false, false);
1850
+						}
1851
+
1852
+						$res .= ")";
1853
+						break;
1854
+				}
1855
+
1856
+				$res .= "\n>>\nendobj";
1857
+
1858
+				return $res;
1859
+		}
1860
+
1861
+		return null;
1862
+	}
1863
+
1864
+	/**
1865
+	 * an annotation object, this will add an annotation to the current page.
1866
+	 * initially will support just link annotations
1867
+	 *
1868
+	 * @param $id
1869
+	 * @param $action
1870
+	 * @param string $options
1871
+	 * @return null|string
1872
+	 */
1873
+	protected function o_annotation($id, $action, $options = '')
1874
+	{
1875
+		if ($action !== 'new') {
1876
+			$o = &$this->objects[$id];
1877
+		}
1878
+
1879
+		switch ($action) {
1880
+			case 'new':
1881
+				// add the annotation to the current page
1882
+				$pageId = $this->currentPage;
1883
+				$this->o_page($pageId, 'annot', $id);
1884
+
1885
+				// and add the action object which is going to be required
1886
+				switch ($options['type']) {
1887
+					case 'link':
1888
+						$this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1889
+						$this->numObj++;
1890
+						$this->o_action($this->numObj, 'new', $options['url']);
1891
+						$this->objects[$id]['info']['actionId'] = $this->numObj;
1892
+						break;
1893
+
1894
+					case 'ilink':
1895
+						// this is to a named internal link
1896
+						$label = $options['label'];
1897
+						$this->objects[$id] = ['t' => 'annotation', 'info' => $options];
1898
+						$this->numObj++;
1899
+						$this->o_action($this->numObj, 'new', ['type' => 'ilink', 'label' => $label]);
1900
+						$this->objects[$id]['info']['actionId'] = $this->numObj;
1901
+						break;
1902
+				}
1903
+				break;
1904
+
1905
+			case 'out':
1906
+				$res = "\n$id 0 obj\n<< /Type /Annot";
1907
+				switch ($o['info']['type']) {
1908
+					case 'link':
1909
+					case 'ilink':
1910
+						$res .= "\n/Subtype /Link";
1911
+						break;
1912
+				}
1913
+				$res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1914
+				$res .= "\n/Border [0 0 0]";
1915
+				$res .= "\n/H /I";
1916
+				$res .= "\n/Rect [ ";
1917
+
1918
+				foreach ($o['info']['rect'] as $v) {
1919
+					$res .= sprintf("%.4F ", $v);
1920
+				}
1921
+
1922
+				$res .= "]";
1923
+				$res .= "\n>>\nendobj";
1924
+
1925
+				return $res;
1926
+		}
1927
+
1928
+		return null;
1929
+	}
1930
+
1931
+	/**
1932
+	 * a page object, it also creates a contents object to hold its contents
1933
+	 *
1934
+	 * @param $id
1935
+	 * @param $action
1936
+	 * @param string $options
1937
+	 * @return null|string
1938
+	 */
1939
+	protected function o_page($id, $action, $options = '')
1940
+	{
1941
+		if ($action !== 'new') {
1942
+			$o = &$this->objects[$id];
1943
+		}
1944
+
1945
+		switch ($action) {
1946
+			case 'new':
1947
+				$this->numPages++;
1948
+				$this->objects[$id] = [
1949
+					't'    => 'page',
1950
+					'info' => [
1951
+						'parent'  => $this->currentNode,
1952
+						'pageNum' => $this->numPages,
1953
+						'mediaBox' => $this->objects[$this->currentNode]['info']['mediaBox']
1954
+					]
1955
+				];
1956
+
1957
+				if (is_array($options)) {
1958
+					// then this must be a page insertion, array should contain 'rid','pos'=[before|after]
1959
+					$options['id'] = $id;
1960
+					$this->o_pages($this->currentNode, 'page', $options);
1961
+				} else {
1962
+					$this->o_pages($this->currentNode, 'page', $id);
1963
+				}
1964
+
1965
+				$this->currentPage = $id;
1966
+				//make a contents object to go with this page
1967
+				$this->numObj++;
1968
+				$this->o_contents($this->numObj, 'new', $id);
1969
+				$this->currentContents = $this->numObj;
1970
+				$this->objects[$id]['info']['contents'] = [];
1971
+				$this->objects[$id]['info']['contents'][] = $this->numObj;
1972
+
1973
+				$match = ($this->numPages % 2 ? 'odd' : 'even');
1974
+				foreach ($this->addLooseObjects as $oId => $target) {
1975
+					if ($target === 'all' || $match === $target) {
1976
+						$this->objects[$id]['info']['contents'][] = $oId;
1977
+					}
1978
+				}
1979
+				break;
1980
+
1981
+			case 'content':
1982
+				$o['info']['contents'][] = $options;
1983
+				break;
1984
+
1985
+			case 'annot':
1986
+				// add an annotation to this page
1987
+				if (!isset($o['info']['annot'])) {
1988
+					$o['info']['annot'] = [];
1989
+				}
1990
+
1991
+				// $options should contain the id of the annotation dictionary
1992
+				$o['info']['annot'][] = $options;
1993
+				break;
1994
+
1995
+			case 'out':
1996
+				$res = "\n$id 0 obj\n<< /Type /Page";
1997
+				if (isset($o['info']['mediaBox'])) {
1998
+					$tmp = $o['info']['mediaBox'];
1999
+					$res .= "\n/MediaBox [" . sprintf(
2000
+							'%.3F %.3F %.3F %.3F',
2001
+							$tmp[0],
2002
+							$tmp[1],
2003
+							$tmp[2],
2004
+							$tmp[3]
2005
+						) . ']';
2006
+				}
2007
+				$res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
2008
+
2009
+				if (isset($o['info']['annot'])) {
2010
+					$res .= "\n/Annots [";
2011
+					foreach ($o['info']['annot'] as $aId) {
2012
+						$res .= " $aId 0 R";
2013
+					}
2014
+					$res .= " ]";
2015
+				}
2016
+
2017
+				$count = count($o['info']['contents']);
2018
+				if ($count == 1) {
2019
+					$res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
2020
+				} else {
2021
+					if ($count > 1) {
2022
+						$res .= "\n/Contents [\n";
2023
+
2024
+						// reverse the page contents so added objects are below normal content
2025
+						//foreach (array_reverse($o['info']['contents']) as $cId) {
2026
+						// Back to normal now that I've got transparency working --Benj
2027
+						foreach ($o['info']['contents'] as $cId) {
2028
+							$res .= "$cId 0 R\n";
2029
+						}
2030
+						$res .= "]";
2031
+					}
2032
+				}
2033
+
2034
+				$res .= "\n>>\nendobj";
2035
+
2036
+				return $res;
2037
+		}
2038
+
2039
+		return null;
2040
+	}
2041
+
2042
+	/**
2043
+	 * the contents objects hold all of the content which appears on pages
2044
+	 *
2045
+	 * @param $id
2046
+	 * @param $action
2047
+	 * @param string|array $options
2048
+	 * @return null|string
2049
+	 */
2050
+	protected function o_contents($id, $action, $options = '')
2051
+	{
2052
+		if ($action !== 'new') {
2053
+			$o = &$this->objects[$id];
2054
+		}
2055
+
2056
+		switch ($action) {
2057
+			case 'new':
2058
+				$this->objects[$id] = ['t' => 'contents', 'c' => '', 'info' => []];
2059
+				if (mb_strlen($options, '8bit') && intval($options)) {
2060
+					// then this contents is the primary for a page
2061
+					$this->objects[$id]['onPage'] = $options;
2062
+				} else {
2063
+					if ($options === 'raw') {
2064
+						// then this page contains some other type of system object
2065
+						$this->objects[$id]['raw'] = 1;
2066
+					}
2067
+				}
2068
+				break;
2069
+
2070
+			case 'add':
2071
+				// add more options to the declaration
2072
+				foreach ($options as $k => $v) {
2073
+					$o['info'][$k] = $v;
2074
+				}
2075
+
2076
+			case 'out':
2077
+				$tmp = $o['c'];
2078
+				$res = "\n$id 0 obj\n";
2079
+
2080
+				if (isset($this->objects[$id]['raw'])) {
2081
+					$res .= $tmp;
2082
+				} else {
2083
+					$res .= "<<";
2084
+					if ($this->compressionReady && $this->options['compression']) {
2085
+						// then implement ZLIB based compression on this content stream
2086
+						$res .= " /Filter /FlateDecode";
2087
+						$tmp = gzcompress($tmp, 6);
2088
+					}
2089
+
2090
+					if ($this->encrypted) {
2091
+						$this->encryptInit($id);
2092
+						$tmp = $this->ARC4($tmp);
2093
+					}
2094
+
2095
+					foreach ($o['info'] as $k => $v) {
2096
+						$res .= "\n/$k $v";
2097
+					}
2098
+
2099
+					$res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
2100
+				}
2101
+
2102
+				$res .= "\nendobj";
2103
+
2104
+				return $res;
2105
+		}
2106
+
2107
+		return null;
2108
+	}
2109
+
2110
+	/**
2111
+	 * @param $id
2112
+	 * @param $action
2113
+	 * @return string|null
2114
+	 */
2115
+	protected function o_embedjs($id, $action)
2116
+	{
2117
+		switch ($action) {
2118
+			case 'new':
2119
+				$this->objects[$id] = [
2120
+					't'    => 'embedjs',
2121
+					'info' => [
2122
+						'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
2123
+					]
2124
+				];
2125
+				break;
2126
+
2127
+			case 'out':
2128
+				$o = &$this->objects[$id];
2129
+				$res = "\n$id 0 obj\n<< ";
2130
+				foreach ($o['info'] as $k => $v) {
2131
+					$res .= "\n/$k $v";
2132
+				}
2133
+				$res .= "\n>>\nendobj";
2134
+
2135
+				return $res;
2136
+		}
2137
+
2138
+		return null;
2139
+	}
2140
+
2141
+	/**
2142
+	 * @param $id
2143
+	 * @param $action
2144
+	 * @param string $code
2145
+	 * @return null|string
2146
+	 */
2147
+	protected function o_javascript($id, $action, $code = '')
2148
+	{
2149
+		switch ($action) {
2150
+			case 'new':
2151
+				$this->objects[$id] = [
2152
+					't'    => 'javascript',
2153
+					'info' => [
2154
+						'S'  => '/JavaScript',
2155
+						'JS' => '(' . $this->filterText($code, true, false) . ')',
2156
+					]
2157
+				];
2158
+				break;
2159
+
2160
+			case 'out':
2161
+				$o = &$this->objects[$id];
2162
+				$res = "\n$id 0 obj\n<< ";
2163
+
2164
+				foreach ($o['info'] as $k => $v) {
2165
+					$res .= "\n/$k $v";
2166
+				}
2167
+				$res .= "\n>>\nendobj";
2168
+
2169
+				return $res;
2170
+		}
2171
+
2172
+		return null;
2173
+	}
2174
+
2175
+	/**
2176
+	 * an image object, will be an XObject in the document, includes description and data
2177
+	 *
2178
+	 * @param $id
2179
+	 * @param $action
2180
+	 * @param string $options
2181
+	 * @return null|string
2182
+	 */
2183
+	protected function o_image($id, $action, $options = '')
2184
+	{
2185
+		switch ($action) {
2186
+			case 'new':
2187
+				// make the new object
2188
+				$this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
2189
+
2190
+				$info =& $this->objects[$id]['info'];
2191
+
2192
+				$info['Type'] = '/XObject';
2193
+				$info['Subtype'] = '/Image';
2194
+				$info['Width'] = $options['iw'];
2195
+				$info['Height'] = $options['ih'];
2196
+
2197
+				if (isset($options['masked']) && $options['masked']) {
2198
+					$info['SMask'] = ($this->numObj - 1) . ' 0 R';
2199
+				}
2200
+
2201
+				if (!isset($options['type']) || $options['type'] === 'jpg') {
2202
+					if (!isset($options['channels'])) {
2203
+						$options['channels'] = 3;
2204
+					}
2205
+
2206
+					switch ($options['channels']) {
2207
+						case 1:
2208
+							$info['ColorSpace'] = '/DeviceGray';
2209
+							break;
2210
+						case 4:
2211
+							$info['ColorSpace'] = '/DeviceCMYK';
2212
+							break;
2213
+						default:
2214
+							$info['ColorSpace'] = '/DeviceRGB';
2215
+							break;
2216
+					}
2217
+
2218
+					if ($info['ColorSpace'] === '/DeviceCMYK') {
2219
+						$info['Decode'] = '[1 0 1 0 1 0 1 0]';
2220
+					}
2221
+
2222
+					$info['Filter'] = '/DCTDecode';
2223
+					$info['BitsPerComponent'] = 8;
2224
+				} else {
2225
+					if ($options['type'] === 'png') {
2226
+						$info['Filter'] = '/FlateDecode';
2227
+						$info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
2228
+
2229
+						if ($options['isMask']) {
2230
+							$info['ColorSpace'] = '/DeviceGray';
2231
+						} else {
2232
+							if (mb_strlen($options['pdata'], '8bit')) {
2233
+								$tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
2234
+								$this->numObj++;
2235
+								$this->o_contents($this->numObj, 'new');
2236
+								$this->objects[$this->numObj]['c'] = $options['pdata'];
2237
+								$tmp .= $this->numObj . ' 0 R';
2238
+								$tmp .= ' ]';
2239
+								$info['ColorSpace'] = $tmp;
2240
+
2241
+								if (isset($options['transparency'])) {
2242
+									$transparency = $options['transparency'];
2243
+									switch ($transparency['type']) {
2244
+										case 'indexed':
2245
+											$tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2246
+											$info['Mask'] = $tmp;
2247
+											break;
2248
+
2249
+										case 'color-key':
2250
+											$tmp = ' [ ' .
2251
+												$transparency['r'] . ' ' . $transparency['r'] .
2252
+												$transparency['g'] . ' ' . $transparency['g'] .
2253
+												$transparency['b'] . ' ' . $transparency['b'] .
2254
+												' ] ';
2255
+											$info['Mask'] = $tmp;
2256
+											break;
2257
+									}
2258
+								}
2259
+							} else {
2260
+								if (isset($options['transparency'])) {
2261
+									$transparency = $options['transparency'];
2262
+
2263
+									switch ($transparency['type']) {
2264
+										case 'indexed':
2265
+											$tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2266
+											$info['Mask'] = $tmp;
2267
+											break;
2268
+
2269
+										case 'color-key':
2270
+											$tmp = ' [ ' .
2271
+												$transparency['r'] . ' ' . $transparency['r'] . ' ' .
2272
+												$transparency['g'] . ' ' . $transparency['g'] . ' ' .
2273
+												$transparency['b'] . ' ' . $transparency['b'] .
2274
+												' ] ';
2275
+											$info['Mask'] = $tmp;
2276
+											break;
2277
+									}
2278
+								}
2279
+								$info['ColorSpace'] = '/' . $options['color'];
2280
+							}
2281
+						}
2282
+
2283
+						$info['BitsPerComponent'] = $options['bitsPerComponent'];
2284
+					}
2285
+				}
2286
+
2287
+				// assign it a place in the named resource dictionary as an external object, according to
2288
+				// the label passed in with it.
2289
+				$this->o_pages($this->currentNode, 'xObject', ['label' => $options['label'], 'objNum' => $id]);
2290
+
2291
+				// also make sure that we have the right procset object for it.
2292
+				$this->o_procset($this->procsetObjectId, 'add', 'ImageC');
2293
+				break;
2294
+
2295
+			case 'out':
2296
+				$o = &$this->objects[$id];
2297
+				$tmp = &$o['data'];
2298
+				$res = "\n$id 0 obj\n<<";
2299
+
2300
+				foreach ($o['info'] as $k => $v) {
2301
+					$res .= "\n/$k $v";
2302
+				}
2303
+
2304
+				if ($this->encrypted) {
2305
+					$this->encryptInit($id);
2306
+					$tmp = $this->ARC4($tmp);
2307
+				}
2308
+
2309
+				$res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
2310
+
2311
+				return $res;
2312
+		}
2313
+
2314
+		return null;
2315
+	}
2316
+
2317
+	/**
2318
+	 * graphics state object
2319
+	 *
2320
+	 * @param $id
2321
+	 * @param $action
2322
+	 * @param string $options
2323
+	 * @return null|string
2324
+	 */
2325
+	protected function o_extGState($id, $action, $options = "")
2326
+	{
2327
+		static $valid_params = [
2328
+			"LW",
2329
+			"LC",
2330
+			"LC",
2331
+			"LJ",
2332
+			"ML",
2333
+			"D",
2334
+			"RI",
2335
+			"OP",
2336
+			"op",
2337
+			"OPM",
2338
+			"Font",
2339
+			"BG",
2340
+			"BG2",
2341
+			"UCR",
2342
+			"TR",
2343
+			"TR2",
2344
+			"HT",
2345
+			"FL",
2346
+			"SM",
2347
+			"SA",
2348
+			"BM",
2349
+			"SMask",
2350
+			"CA",
2351
+			"ca",
2352
+			"AIS",
2353
+			"TK"
2354
+		];
2355
+
2356
+		switch ($action) {
2357
+			case "new":
2358
+				$this->objects[$id] = ['t' => 'extGState', 'info' => $options];
2359
+
2360
+				// Tell the pages about the new resource
2361
+				$this->numStates++;
2362
+				$this->o_pages($this->currentNode, 'extGState', ["objNum" => $id, "stateNum" => $this->numStates]);
2363
+				break;
2364
+
2365
+			case "out":
2366
+				$o = &$this->objects[$id];
2367
+				$res = "\n$id 0 obj\n<< /Type /ExtGState\n";
2368
+
2369
+				foreach ($o["info"] as $k => $v) {
2370
+					if (!in_array($k, $valid_params)) {
2371
+						continue;
2372
+					}
2373
+					$res .= "/$k $v\n";
2374
+				}
2375
+
2376
+				$res .= ">>\nendobj";
2377
+
2378
+				return $res;
2379
+		}
2380
+
2381
+		return null;
2382
+	}
2383
+
2384
+	/**
2385
+	 * @param integer $id
2386
+	 * @param string $action
2387
+	 * @param mixed $options
2388
+	 * @return string
2389
+	 */
2390
+	protected function o_xobject($id, $action, $options = '')
2391
+	{
2392
+		switch ($action) {
2393
+			case 'new':
2394
+				$this->objects[$id] = ['t' => 'xobject', 'info' => $options, 'c' => ''];
2395
+				break;
2396
+
2397
+			case 'procset':
2398
+				$this->objects[$id]['procset'] = $options;
2399
+				break;
2400
+
2401
+			case 'font':
2402
+				$this->objects[$id]['fonts'][$options['fontNum']] = [
2403
+				  'objNum' => $options['objNum'],
2404
+				  'fontNum' => $options['fontNum']
2405
+				];
2406
+				break;
2407
+
2408
+			case 'xObject':
2409
+				$this->objects[$id]['xObjects'][] = ['objNum' => $options['objNum'], 'label' => $options['label']];
2410
+				break;
2411
+
2412
+			case 'out':
2413
+				$o = &$this->objects[$id];
2414
+				$res = "\n$id 0 obj\n<< /Type /XObject\n";
2415
+
2416
+				foreach ($o["info"] as $k => $v) {
2417
+					switch ($k) {
2418
+						case 'Subtype':
2419
+							$res .= "/Subtype /$v\n";
2420
+							break;
2421
+						case 'bbox':
2422
+							$res .= "/BBox [";
2423
+							foreach ($v as $value) {
2424
+								$res .= sprintf("%.4F ", $value);
2425
+							}
2426
+							$res .= "]\n";
2427
+							break;
2428
+						default:
2429
+							$res .= "/$k $v\n";
2430
+							break;
2431
+					}
2432
+				}
2433
+				$res .= "/Matrix[1.0 0.0 0.0 1.0 0.0 0.0]\n";
2434
+
2435
+				$res .= "/Resources <<";
2436
+				if (isset($o['procset'])) {
2437
+					$res .= "\n/ProcSet " . $o['procset'] . " 0 R";
2438
+				} else {
2439
+					$res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
2440
+				}
2441
+				if (isset($o['fonts']) && count($o['fonts'])) {
2442
+					$res .= "\n/Font << ";
2443
+					foreach ($o['fonts'] as $finfo) {
2444
+						$res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
2445
+					}
2446
+					$res .= "\n>>";
2447
+				}
2448
+				if (isset($o['xObjects']) && count($o['xObjects'])) {
2449
+					$res .= "\n/XObject << ";
2450
+					foreach ($o['xObjects'] as $finfo) {
2451
+						$res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
2452
+					}
2453
+					$res .= "\n>>";
2454
+				}
2455
+				$res .= "\n>>\n";
2456
+
2457
+				$tmp = $o["c"];
2458
+				if ($this->compressionReady && $this->options['compression']) {
2459
+					// then implement ZLIB based compression on this content stream
2460
+					$res .= " /Filter /FlateDecode\n";
2461
+					$tmp = gzcompress($tmp, 6);
2462
+				}
2463
+
2464
+				if ($this->encrypted) {
2465
+					$this->encryptInit($id);
2466
+					$tmp = $this->ARC4($tmp);
2467
+				}
2468
+
2469
+				$res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2470
+				$res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";
2471
+
2472
+				return $res;
2473
+		}
2474
+
2475
+		return null;
2476
+	}
2477
+
2478
+	/**
2479
+	 * @param $id
2480
+	 * @param $action
2481
+	 * @param string $options
2482
+	 * @return null|string
2483
+	 */
2484
+	protected function o_acroform($id, $action, $options = '')
2485
+	{
2486
+		switch ($action) {
2487
+			case "new":
2488
+				$this->o_catalog($this->catalogId, 'acroform', $id);
2489
+				$this->objects[$id] = array('t' => 'acroform', 'info' => $options);
2490
+				break;
2491
+
2492
+			case 'addfield':
2493
+				$this->objects[$id]['info']['Fields'][] = $options;
2494
+				break;
2495
+
2496
+			case 'font':
2497
+				$this->objects[$id]['fonts'][$options['fontNum']] = [
2498
+				  'objNum' => $options['objNum'],
2499
+				  'fontNum' => $options['fontNum']
2500
+				];
2501
+				break;
2502
+
2503
+			case "out":
2504
+				$o = &$this->objects[$id];
2505
+				$res = "\n$id 0 obj\n<<";
2506
+
2507
+				foreach ($o["info"] as $k => $v) {
2508
+					switch ($k) {
2509
+						case 'Fields':
2510
+							$res .= " /Fields [";
2511
+							foreach ($v as $i) {
2512
+								$res .= "$i 0 R ";
2513
+							}
2514
+							$res .= "]\n";
2515
+							break;
2516
+						default:
2517
+							$res .= "/$k $v\n";
2518
+					}
2519
+				}
2520
+
2521
+				$res .= "/DR <<\n";
2522
+				if (isset($o['fonts']) && count($o['fonts'])) {
2523
+					$res .= "/Font << \n";
2524
+					foreach ($o['fonts'] as $finfo) {
2525
+						$res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
2526
+					}
2527
+					$res .= ">>\n";
2528
+				}
2529
+				$res .= ">>\n";
2530
+
2531
+				$res .= ">>\nendobj";
2532
+
2533
+				return $res;
2534
+		}
2535
+
2536
+		return null;
2537
+	}
2538
+
2539
+	/**
2540
+	 * @param $id
2541
+	 * @param $action
2542
+	 * @param mixed $options
2543
+	 * @return null|string
2544
+	 */
2545
+	protected function o_field($id, $action, $options = '')
2546
+	{
2547
+		switch ($action) {
2548
+			case "new":
2549
+				$this->o_page($options['pageid'], 'annot', $id);
2550
+				$this->o_acroform($this->acroFormId, 'addfield', $id);
2551
+				$this->objects[$id] = ['t' => 'field', 'info' => $options];
2552
+				break;
2553
+
2554
+			case 'set':
2555
+				$this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2556
+				break;
2557
+
2558
+			case "out":
2559
+				$o = &$this->objects[$id];
2560
+				$res = "\n$id 0 obj\n<< /Type /Annot /Subtype /Widget \n";
2561
+
2562
+				$encrypted = $this->encrypted;
2563
+				if ($encrypted) {
2564
+					$this->encryptInit($id);
2565
+				}
2566
+
2567
+				foreach ($o["info"] as $k => $v) {
2568
+					switch ($k) {
2569
+						case 'pageid':
2570
+							$res .= "/P $v 0 R\n";
2571
+							break;
2572
+						case 'value':
2573
+							if ($encrypted) {
2574
+								$v = $this->filterText($this->ARC4($v), false, false);
2575
+							}
2576
+							$res .= "/V ($v)\n";
2577
+							break;
2578
+						case 'refvalue':
2579
+							$res .= "/V $v 0 R\n";
2580
+							break;
2581
+						case 'da':
2582
+							if ($encrypted) {
2583
+								$v = $this->filterText($this->ARC4($v), false, false);
2584
+							}
2585
+							$res .= "/DA ($v)\n";
2586
+							break;
2587
+						case 'options':
2588
+							$res .= "/Opt [\n";
2589
+							foreach ($v as $opt) {
2590
+								if ($encrypted) {
2591
+									$opt = $this->filterText($this->ARC4($opt), false, false);
2592
+								}
2593
+								$res .= "($opt)\n";
2594
+							}
2595
+							$res .= "]\n";
2596
+							break;
2597
+						case 'rect':
2598
+							$res .= "/Rect [";
2599
+							foreach ($v as $value) {
2600
+								$res .= sprintf("%.4F ", $value);
2601
+							}
2602
+							$res .= "]\n";
2603
+							break;
2604
+						case 'appearance':
2605
+							$res .= "/AP << ";
2606
+							foreach ($v as $a => $ref) {
2607
+								$res .= "/$a $ref 0 R ";
2608
+							}
2609
+							$res .= ">>\n";
2610
+							break;
2611
+						case 'T':
2612
+							if ($encrypted) {
2613
+								$v = $this->filterText($this->ARC4($v), false, false);
2614
+							}
2615
+							$res .= "/T ($v)\n";
2616
+							break;
2617
+						default:
2618
+							$res .= "/$k $v\n";
2619
+					}
2620
+
2621
+				}
2622
+
2623
+				$res .= ">>\nendobj";
2624
+
2625
+				return $res;
2626
+		}
2627
+
2628
+		return null;
2629
+	}
2630
+
2631
+	/**
2632
+	 *
2633
+	 * @param $id
2634
+	 * @param $action
2635
+	 * @param string $options
2636
+	 * @return null|string
2637
+	 */
2638
+	protected function o_sig($id, $action, $options = '')
2639
+	{
2640
+		$sign_maxlen = $this->signatureMaxLen;
2641
+
2642
+		switch ($action) {
2643
+			case "new":
2644
+				$this->objects[$id] = array('t' => 'sig', 'info' => $options);
2645
+				$this->byteRange[$id] = ['t' => 'sig'];
2646
+				break;
2647
+
2648
+			case 'byterange':
2649
+				$o = &$this->objects[$id];
2650
+				$content =& $options['content'];
2651
+				$content_len = strlen($content);
2652
+				$pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2653
+				$len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
2654
+				$rangeStartPos = $pos + $len + 1 + 10; // before '<'
2655
+				$content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2656
+
2657
+				$fuid = uniqid();
2658
+				$tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2659
+				$tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
2660
+
2661
+				if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
2662
+					throw new \Exception("Unable to write temporary file for signing.");
2663
+				}
2664
+				if (file_put_contents($tmpInput, substr($content, $rangeStartPos + 2 + $sign_maxlen),
2665
+					FILE_APPEND) === false) {
2666
+					throw new \Exception("Unable to write temporary file for signing.");
2667
+				}
2668
+
2669
+				if (openssl_pkcs7_sign($tmpInput, $tmpOutput,
2670
+					$o['info']['SignCert'],
2671
+					array($o['info']['PrivKey'], $o['info']['Password']),
2672
+					array(), PKCS7_BINARY | PKCS7_DETACHED) === false) {
2673
+					throw new \Exception("Failed to prepare signature.");
2674
+				}
2675
+
2676
+				$signature = file_get_contents($tmpOutput);
2677
+
2678
+				unlink($tmpInput);
2679
+				unlink($tmpOutput);
2680
+
2681
+				$sign = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
2682
+				list($head, $signature) = explode("\n\n", $sign);
2683
+
2684
+				$signature = base64_decode(trim($signature));
2685
+
2686
+				$signature = current(unpack('H*', $signature));
2687
+				$signature = str_pad($signature, $sign_maxlen, '0');
2688
+				$siglen = strlen($signature);
2689
+				if (strlen($signature) > $sign_maxlen) {
2690
+					throw new \Exception("Signature length ($siglen) exceeds the $sign_maxlen limit.");
2691
+				}
2692
+
2693
+				$content = substr_replace($content, $signature, $rangeStartPos + 1, $sign_maxlen);
2694
+				break;
2695
+
2696
+			case "out":
2697
+				$res = "\n$id 0 obj\n<<\n";
2698
+
2699
+				$encrypted = $this->encrypted;
2700
+				if ($encrypted) {
2701
+					$this->encryptInit($id);
2702
+				}
2703
+
2704
+				$res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2705
+				$res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
2706
+				$res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
2707
+				$res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
2708
+
2709
+				$date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
2710
+				if ($encrypted) {
2711
+					$date = $this->ARC4($date);
2712
+				}
2713
+
2714
+				$res .= "/M ($date)\n";
2715
+				$res .= "/Prop_Build << /App << /Name /DomPDF >> /Filter << /Name /Adobe.PPKLite >> >>\n";
2716
+
2717
+				$o = &$this->objects[$id];
2718
+				foreach ($o['info'] as $k => $v) {
2719
+					switch ($k) {
2720
+						case 'Name':
2721
+						case 'Location':
2722
+						case 'Reason':
2723
+						case 'ContactInfo':
2724
+							if ($v !== null && $v !== '') {
2725
+								$res .= "/$k (" .
2726
+								  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
2727
+							}
2728
+							break;
2729
+					}
2730
+				}
2731
+				$res .= ">>\nendobj";
2732
+
2733
+				return $res;
2734
+		}
2735
+
2736
+		return null;
2737
+	}
2738
+
2739
+	/**
2740
+	 * encryption object.
2741
+	 *
2742
+	 * @param $id
2743
+	 * @param $action
2744
+	 * @param string $options
2745
+	 * @return string|null
2746
+	 */
2747
+	protected function o_encryption($id, $action, $options = '')
2748
+	{
2749
+		switch ($action) {
2750
+			case 'new':
2751
+				// make the new object
2752
+				$this->objects[$id] = ['t' => 'encryption', 'info' => $options];
2753
+				$this->arc4_objnum = $id;
2754
+				break;
2755
+
2756
+			case 'keys':
2757
+				// figure out the additional parameters required
2758
+				$pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2759
+					. chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2760
+					. chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2761
+					. chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2762
+
2763
+				$info = $this->objects[$id]['info'];
2764
+
2765
+				$len = mb_strlen($info['owner'], '8bit');
2766
+
2767
+				if ($len > 32) {
2768
+					$owner = substr($info['owner'], 0, 32);
2769
+				} else {
2770
+					if ($len < 32) {
2771
+						$owner = $info['owner'] . substr($pad, 0, 32 - $len);
2772
+					} else {
2773
+						$owner = $info['owner'];
2774
+					}
2775
+				}
2776
+
2777
+				$len = mb_strlen($info['user'], '8bit');
2778
+				if ($len > 32) {
2779
+					$user = substr($info['user'], 0, 32);
2780
+				} else {
2781
+					if ($len < 32) {
2782
+						$user = $info['user'] . substr($pad, 0, 32 - $len);
2783
+					} else {
2784
+						$user = $info['user'];
2785
+					}
2786
+				}
2787
+
2788
+				$tmp = $this->md5_16($owner);
2789
+				$okey = substr($tmp, 0, 5);
2790
+				$this->ARC4_init($okey);
2791
+				$ovalue = $this->ARC4($user);
2792
+				$this->objects[$id]['info']['O'] = $ovalue;
2793
+
2794
+				// now make the u value, phew.
2795
+				$tmp = $this->md5_16(
2796
+					$user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2797
+				);
2798
+
2799
+				$ukey = substr($tmp, 0, 5);
2800
+				$this->ARC4_init($ukey);
2801
+				$this->encryptionKey = $ukey;
2802
+				$this->encrypted = true;
2803
+				$uvalue = $this->ARC4($pad);
2804
+				$this->objects[$id]['info']['U'] = $uvalue;
2805
+				// initialize the arc4 array
2806
+				break;
2807
+
2808
+			case 'out':
2809
+				$o = &$this->objects[$id];
2810
+
2811
+				$res = "\n$id 0 obj\n<<";
2812
+				$res .= "\n/Filter /Standard";
2813
+				$res .= "\n/V 1";
2814
+				$res .= "\n/R 2";
2815
+				$res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2816
+				$res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2817
+				// and the p-value needs to be converted to account for the twos-complement approach
2818
+				$o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2819
+				$res .= "\n/P " . ($o['info']['p']);
2820
+				$res .= "\n>>\nendobj";
2821
+
2822
+				return $res;
2823
+		}
2824
+
2825
+		return null;
2826
+	}
2827
+
2828
+	protected function o_indirect_references($id, $action, $options = null)
2829
+	{
2830
+		switch ($action) {
2831
+			case 'new':
2832
+			case 'add':
2833
+				if ($id === 0) {
2834
+					$id = ++$this->numObj;
2835
+					$this->o_catalog($this->catalogId, 'names', $id);
2836
+					$this->objects[$id] = ['t' => 'indirect_references', 'info' => $options];
2837
+					$this->indirectReferenceId = $id;
2838
+				} else {
2839
+					$this->objects[$id]['info'] = array_merge($this->objects[$id]['info'], $options);
2840
+				}
2841
+				break;
2842
+			case 'out':
2843
+				$res = "\n$id 0 obj << ";
2844
+
2845
+				foreach ($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2846
+					$res .= "/$referenceObjName $referenceObjId 0 R ";
2847
+				}
2848
+
2849
+				$res .= ">> endobj";
2850
+				return $res;
2851
+		}
2852
+
2853
+		return null;
2854
+	}
2855
+
2856
+	protected function o_names($id, $action, $options = null)
2857
+	{
2858
+		switch ($action) {
2859
+			case 'new':
2860
+			case 'add':
2861
+				if ($id === 0) {
2862
+					$id = ++$this->numObj;
2863
+					$this->objects[$id] = ['t' => 'names', 'info' => [$options]];
2864
+					$this->o_indirect_references($this->indirectReferenceId, 'add', ['EmbeddedFiles' => $id]);
2865
+					$this->embeddedFilesId = $id;
2866
+				} else {
2867
+					$this->objects[$id]['info'][] = $options;
2868
+				}
2869
+				break;
2870
+			case 'out':
2871
+				$info = &$this->objects[$id]['info'];
2872
+				$res = '';
2873
+				if (count($info) > 0) {
2874
+					$res = "\n$id 0 obj << /Names [ ";
2875
+
2876
+					if ($this->encrypted) {
2877
+						$this->encryptInit($id);
2878
+					}
2879
+
2880
+					foreach ($info as $entry) {
2881
+						if ($this->encrypted) {
2882
+							$filename = $this->ARC4($entry['filename']);
2883
+						} else {
2884
+							$filename = $entry['filename'];
2885
+						}
2886
+
2887
+						$res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
2888
+					}
2889
+
2890
+					$res .= "] >> endobj";
2891
+				}
2892
+				return $res;
2893
+		}
2894
+
2895
+		return null;
2896
+	}
2897
+
2898
+	protected function o_embedded_file_dictionary($id, $action, $options = null)
2899
+	{
2900
+		switch ($action) {
2901
+			case 'new':
2902
+				$embeddedFileId = ++$this->numObj;
2903
+				$options['embedded_reference'] = $embeddedFileId;
2904
+				$this->objects[$id] = ['t' => 'embedded_file_dictionary', 'info' => $options];
2905
+				$this->o_embedded_file($embeddedFileId, 'new', $options);
2906
+				$options['dict_reference'] = $id;
2907
+				$this->o_names($this->embeddedFilesId, 'add', $options);
2908
+				break;
2909
+			case 'out':
2910
+				$info = &$this->objects[$id]['info'];
2911
+				$filename = $this->utf8toUtf16BE($info['filename']);
2912
+				$description = $this->utf8toUtf16BE($info['description']);
2913
+
2914
+				if ($this->encrypted) {
2915
+					$this->encryptInit($id);
2916
+					$filename = $this->ARC4($filename);
2917
+					$description = $this->ARC4($description);
2918
+				}
2919
+
2920
+				$filename = $this->filterText($filename, false, false);
2921
+				$description = $this->filterText($description, false, false);
2922
+
2923
+				$res = "\n$id 0 obj <</Type /Filespec /EF";
2924
+				$res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
2925
+				$res .= " /F ($filename) /UF ($filename) /Desc ($description)";
2926
+				$res .= " >> endobj";
2927
+				return $res;
2928
+		}
2929
+
2930
+		return null;
2931
+	}
2932
+
2933
+	protected function o_embedded_file($id, $action, $options = null): ?string
2934
+	{
2935
+		switch ($action) {
2936
+			case 'new':
2937
+				$this->objects[$id] = ['t' => 'embedded_file', 'info' => $options];
2938
+				break;
2939
+			case 'out':
2940
+				$info = &$this->objects[$id]['info'];
2941
+
2942
+				if ($this->compressionReady) {
2943
+					$filepath = $info['filepath'];
2944
+					$checksum = md5_file($filepath);
2945
+					$f = fopen($filepath, "rb");
2946
+
2947
+					$file_content_compressed = '';
2948
+					$deflateContext = deflate_init(ZLIB_ENCODING_DEFLATE, ['level' => 6]);
2949
+					while (($block = fread($f, 8192))) {
2950
+						$file_content_compressed .= deflate_add($deflateContext, $block, ZLIB_NO_FLUSH);
2951
+					}
2952
+					$file_content_compressed .= deflate_add($deflateContext, '', ZLIB_FINISH);
2953
+					$file_size_uncompressed = ftell($f);
2954
+					fclose($f);
2955
+				} else {
2956
+					$file_content = file_get_contents($info['filepath']);
2957
+					$file_size_uncompressed = mb_strlen($file_content, '8bit');
2958
+					$checksum = md5($file_content);
2959
+				}
2960
+
2961
+				if ($this->encrypted) {
2962
+					$this->encryptInit($id);
2963
+					$checksum = $this->ARC4($checksum);
2964
+					$file_content_compressed = $this->ARC4($file_content_compressed);
2965
+				}
2966
+				$file_size_compressed = mb_strlen($file_content_compressed, '8bit');
2967
+
2968
+				$res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
2969
+					" /Type/EmbeddedFile /Filter/FlateDecode" .
2970
+					" /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
2971
+
2972
+				return $res;
2973
+		}
2974
+
2975
+		return null;
2976
+	}
2977
+
2978
+	/**
2979
+	 * ARC4 functions
2980
+	 * A series of function to implement ARC4 encoding in PHP
2981
+	 */
2982
+
2983
+	/**
2984
+	 * calculate the 16 byte version of the 128 bit md5 digest of the string
2985
+	 *
2986
+	 * @param $string
2987
+	 * @return string
2988
+	 */
2989
+	function md5_16($string)
2990
+	{
2991
+		$tmp = md5($string);
2992
+		$out = '';
2993
+		for ($i = 0; $i <= 30; $i = $i + 2) {
2994
+			$out .= chr(hexdec(substr($tmp, $i, 2)));
2995
+		}
2996
+
2997
+		return $out;
2998
+	}
2999
+
3000
+	/**
3001
+	 * initialize the encryption for processing a particular object
3002
+	 *
3003
+	 * @param $id
3004
+	 */
3005
+	function encryptInit($id)
3006
+	{
3007
+		$tmp = $this->encryptionKey;
3008
+		$hex = dechex($id);
3009
+		if (mb_strlen($hex, '8bit') < 6) {
3010
+			$hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
3011
+		}
3012
+		$tmp .= chr(hexdec(substr($hex, 4, 2)))
3013
+			. chr(hexdec(substr($hex, 2, 2)))
3014
+			. chr(hexdec(substr($hex, 0, 2)))
3015
+			. chr(0)
3016
+			. chr(0)
3017
+		;
3018
+		$key = $this->md5_16($tmp);
3019
+		$this->ARC4_init(substr($key, 0, 10));
3020
+	}
3021
+
3022
+	/**
3023
+	 * initialize the ARC4 encryption
3024
+	 *
3025
+	 * @param string $key
3026
+	 */
3027
+	function ARC4_init($key = '')
3028
+	{
3029
+		$this->arc4 = '';
3030
+
3031
+		// setup the control array
3032
+		if (mb_strlen($key, '8bit') == 0) {
3033
+			return;
3034
+		}
3035
+
3036
+		$k = '';
3037
+		while (mb_strlen($k, '8bit') < 256) {
3038
+			$k .= $key;
3039
+		}
3040
+
3041
+		$k = substr($k, 0, 256);
3042
+		for ($i = 0; $i < 256; $i++) {
3043
+			$this->arc4 .= chr($i);
3044
+		}
3045
+
3046
+		$j = 0;
3047
+
3048
+		for ($i = 0; $i < 256; $i++) {
3049
+			$t = $this->arc4[$i];
3050
+			$j = ($j + ord($t) + ord($k[$i])) % 256;
3051
+			$this->arc4[$i] = $this->arc4[$j];
3052
+			$this->arc4[$j] = $t;
3053
+		}
3054
+	}
3055
+
3056
+	/**
3057
+	 * ARC4 encrypt a text string
3058
+	 *
3059
+	 * @param $text
3060
+	 * @return string
3061
+	 */
3062
+	function ARC4($text)
3063
+	{
3064
+		$len = mb_strlen($text, '8bit');
3065
+		$a = 0;
3066
+		$b = 0;
3067
+		$c = $this->arc4;
3068
+		$out = '';
3069
+		for ($i = 0; $i < $len; $i++) {
3070
+			$a = ($a + 1) % 256;
3071
+			$t = $c[$a];
3072
+			$b = ($b + ord($t)) % 256;
3073
+			$c[$a] = $c[$b];
3074
+			$c[$b] = $t;
3075
+			$k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]);
3076
+			$out .= chr(ord($text[$i]) ^ $k);
3077
+		}
3078
+
3079
+		return $out;
3080
+	}
3081
+
3082
+	/**
3083
+	 * functions which can be called to adjust or add to the document
3084
+	 */
3085
+
3086
+	/**
3087
+	 * add a link in the document to an external URL
3088
+	 *
3089
+	 * @param $url
3090
+	 * @param $x0
3091
+	 * @param $y0
3092
+	 * @param $x1
3093
+	 * @param $y1
3094
+	 */
3095
+	function addLink($url, $x0, $y0, $x1, $y1)
3096
+	{
3097
+		$this->numObj++;
3098
+		$info = ['type' => 'link', 'url' => $url, 'rect' => [$x0, $y0, $x1, $y1]];
3099
+		$this->o_annotation($this->numObj, 'new', $info);
3100
+	}
3101
+
3102
+	/**
3103
+	 * add a link in the document to an internal destination (ie. within the document)
3104
+	 *
3105
+	 * @param $label
3106
+	 * @param $x0
3107
+	 * @param $y0
3108
+	 * @param $x1
3109
+	 * @param $y1
3110
+	 */
3111
+	function addInternalLink($label, $x0, $y0, $x1, $y1)
3112
+	{
3113
+		$this->numObj++;
3114
+		$info = ['type' => 'ilink', 'label' => $label, 'rect' => [$x0, $y0, $x1, $y1]];
3115
+		$this->o_annotation($this->numObj, 'new', $info);
3116
+	}
3117
+
3118
+	/**
3119
+	 * set the encryption of the document
3120
+	 * can be used to turn it on and/or set the passwords which it will have.
3121
+	 * also the functions that the user will have are set here, such as print, modify, add
3122
+	 *
3123
+	 * @param string $userPass
3124
+	 * @param string $ownerPass
3125
+	 * @param array $pc
3126
+	 */
3127
+	function setEncryption($userPass = '', $ownerPass = '', $pc = [])
3128
+	{
3129
+		$p = bindec("11000000");
3130
+
3131
+		$options = ['print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32];
3132
+
3133
+		foreach ($pc as $k => $v) {
3134
+			if ($v && isset($options[$k])) {
3135
+				$p += $options[$k];
3136
+			} else {
3137
+				if (isset($options[$v])) {
3138
+					$p += $options[$v];
3139
+				}
3140
+			}
3141
+		}
3142
+
3143
+		// implement encryption on the document
3144
+		if ($this->arc4_objnum == 0) {
3145
+			// then the block does not exist already, add it.
3146
+			$this->numObj++;
3147
+			if (mb_strlen($ownerPass) == 0) {
3148
+				$ownerPass = $userPass;
3149
+			}
3150
+
3151
+			$this->o_encryption($this->numObj, 'new', ['user' => $userPass, 'owner' => $ownerPass, 'p' => $p]);
3152
+		}
3153
+	}
3154
+
3155
+	/**
3156
+	 * should be used for internal checks, not implemented as yet
3157
+	 */
3158
+	function checkAllHere()
3159
+	{
3160
+	}
3161
+
3162
+	/**
3163
+	 * return the pdf stream as a string returned from the function
3164
+	 *
3165
+	 * @param bool $debug
3166
+	 * @return string
3167
+	 */
3168
+	function output($debug = false)
3169
+	{
3170
+		if ($debug) {
3171
+			// turn compression off
3172
+			$this->options['compression'] = false;
3173
+		}
3174
+
3175
+		if ($this->javascript) {
3176
+			$this->numObj++;
3177
+
3178
+			$js_id = $this->numObj;
3179
+			$this->o_embedjs($js_id, 'new');
3180
+			$this->o_javascript(++$this->numObj, 'new', $this->javascript);
3181
+
3182
+			$id = $this->catalogId;
3183
+
3184
+			$this->o_indirect_references($this->indirectReferenceId, 'add', ['JavaScript' => $js_id]);
3185
+		}
3186
+
3187
+		if ($this->fileIdentifier === '') {
3188
+			$tmp = implode('', $this->objects[$this->infoObject]['info']);
3189
+			$this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
3190
+		}
3191
+
3192
+		if ($this->arc4_objnum) {
3193
+			$this->o_encryption($this->arc4_objnum, 'keys');
3194
+			$this->ARC4_init($this->encryptionKey);
3195
+		}
3196
+
3197
+		$this->checkAllHere();
3198
+
3199
+		$xref = [];
3200
+		$content = '%PDF-' . self::PDF_VERSION;
3201
+		$pos = mb_strlen($content, '8bit');
3202
+
3203
+		// pre-process o_font objects before output of all objects
3204
+		foreach ($this->objects as $k => $v) {
3205
+			if ($v['t'] === 'font') {
3206
+				$this->o_font($k, 'add');
3207
+			}
3208
+		}
3209
+
3210
+		foreach ($this->objects as $k => $v) {
3211
+			$tmp = 'o_' . $v['t'];
3212
+			$cont = $this->$tmp($k, 'out');
3213
+			$content .= $cont;
3214
+			$xref[] = $pos + 1; //+1 to account for \n at the start of each object
3215
+			$pos += mb_strlen($cont, '8bit');
3216
+		}
3217
+
3218
+		$content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
3219
+
3220
+		foreach ($xref as $p) {
3221
+			$content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
3222
+		}
3223
+
3224
+		$content .= "trailer\n<<\n" .
3225
+			'/Size ' . (count($xref) + 1) . "\n" .
3226
+			'/Root 1 0 R' . "\n" .
3227
+			'/Info ' . $this->infoObject . " 0 R\n"
3228
+		;
3229
+
3230
+		// if encryption has been applied to this document then add the marker for this dictionary
3231
+		if ($this->arc4_objnum > 0) {
3232
+			$content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
3233
+		}
3234
+
3235
+		$content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
3236
+
3237
+		// account for \n added at start of xref table
3238
+		$pos++;
3239
+
3240
+		$content .= ">>\nstartxref\n$pos\n%%EOF\n";
3241
+
3242
+		if (count($this->byteRange) > 0) {
3243
+			foreach ($this->byteRange as $k => $v) {
3244
+				$tmp = 'o_' . $v['t'];
3245
+				$this->$tmp($k, 'byterange', ['content' => &$content]);
3246
+			}
3247
+		}
3248
+
3249
+		return $content;
3250
+	}
3251
+
3252
+	/**
3253
+	 * initialize a new document
3254
+	 * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum
3255
+	 * this function is called automatically by the constructor function
3256
+	 *
3257
+	 * @param array $pageSize
3258
+	 */
3259
+	private function newDocument($pageSize = [0, 0, 612, 792])
3260
+	{
3261
+		$this->numObj = 0;
3262
+		$this->objects = [];
3263
+
3264
+		$this->numObj++;
3265
+		$this->o_catalog($this->numObj, 'new');
3266
+
3267
+		$this->numObj++;
3268
+		$this->o_outlines($this->numObj, 'new');
3269
+
3270
+		$this->numObj++;
3271
+		$this->o_pages($this->numObj, 'new');
3272
+
3273
+		$this->o_pages($this->numObj, 'mediaBox', $pageSize);
3274
+		$this->currentNode = 3;
3275
+
3276
+		$this->numObj++;
3277
+		$this->o_procset($this->numObj, 'new');
3278
+
3279
+		$this->numObj++;
3280
+		$this->o_info($this->numObj, 'new');
3281
+
3282
+		$this->numObj++;
3283
+		$this->o_page($this->numObj, 'new');
3284
+
3285
+		// need to store the first page id as there is no way to get it to the user during
3286
+		// startup
3287
+		$this->firstPageId = $this->currentContents;
3288
+	}
3289
+
3290
+	/**
3291
+	 * open the font file and return a php structure containing it.
3292
+	 * first check if this one has been done before and saved in a form more suited to php
3293
+	 * note that if a php serialized version does not exist it will try and make one, but will
3294
+	 * require write access to the directory to do it... it is MUCH faster to have these serialized
3295
+	 * files.
3296
+	 *
3297
+	 * @param $font
3298
+	 */
3299
+	private function openFont($font)
3300
+	{
3301
+		// assume that $font contains the path and file but not the extension
3302
+		$name = basename($font);
3303
+		$dir = dirname($font);
3304
+
3305
+		$fontcache = $this->fontcache;
3306
+		if ($fontcache == '') {
3307
+			$fontcache = $dir;
3308
+		}
3309
+
3310
+		//$name       filename without folder and extension of font metrics
3311
+		//$dir        folder of font metrics
3312
+		//$fontcache  folder of runtime created php serialized version of font metrics.
3313
+		//            If this is not given, the same folder as the font metrics will be used.
3314
+		//            Storing and reusing serialized versions improves speed much
3315
+
3316
+		$this->addMessage("openFont: $font - $name");
3317
+
3318
+		if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3319
+			$metrics_name = "$name.afm";
3320
+		} else {
3321
+			$metrics_name = "$name.ufm";
3322
+		}
3323
+
3324
+		$cache_name = "$metrics_name.json";
3325
+		$this->addMessage("metrics: $metrics_name, cache: $cache_name");
3326 3326
         
3327
-        if (file_exists($fontcache . '/' . $cache_name)) {
3328
-            $this->addMessage("openFont: json metrics file exists $fontcache/$cache_name");
3329
-            $cached_font_info = json_decode(file_get_contents($fontcache . '/' . $cache_name), true);
3330
-            if (!isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
3331
-                $this->addMessage('openFont: font cache is out of date, regenerating');
3332
-            } else {
3333
-                $this->fonts[$font] = $cached_font_info;
3334
-            }
3335
-        }
3336
-
3337
-        if (!isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
3338
-            // then rebuild the php_<font>.afm file from the <font>.afm file
3339
-            $this->addMessage("openFont: build php file from $dir/$metrics_name");
3340
-            $data = [];
3341
-
3342
-            // 20 => 'space'
3343
-            $data['codeToName'] = [];
3344
-
3345
-            // Since we're not going to enable Unicode for the core fonts we need to use a font-based
3346
-            // setting for Unicode support rather than a global setting.
3347
-            $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
3348
-
3349
-            $cidtogid = '';
3350
-            if ($data['isUnicode']) {
3351
-                $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
3352
-            }
3353
-
3354
-            $file = file("$dir/$metrics_name");
3355
-
3356
-            foreach ($file as $rowA) {
3357
-                $row = trim($rowA);
3358
-                $pos = strpos($row, ' ');
3359
-
3360
-                if ($pos) {
3361
-                    // then there must be some keyword
3362
-                    $key = substr($row, 0, $pos);
3363
-                    switch ($key) {
3364
-                        case 'FontName':
3365
-                        case 'FullName':
3366
-                        case 'FamilyName':
3367
-                        case 'PostScriptName':
3368
-                        case 'Weight':
3369
-                        case 'ItalicAngle':
3370
-                        case 'IsFixedPitch':
3371
-                        case 'CharacterSet':
3372
-                        case 'UnderlinePosition':
3373
-                        case 'UnderlineThickness':
3374
-                        case 'Version':
3375
-                        case 'EncodingScheme':
3376
-                        case 'CapHeight':
3377
-                        case 'XHeight':
3378
-                        case 'Ascender':
3379
-                        case 'Descender':
3380
-                        case 'StdHW':
3381
-                        case 'StdVW':
3382
-                        case 'StartCharMetrics':
3383
-                        case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font.  Otherwise it's too big.
3384
-                            $data[$key] = trim(substr($row, $pos));
3385
-                            break;
3386
-
3387
-                        case 'FontBBox':
3388
-                            $data[$key] = explode(' ', trim(substr($row, $pos)));
3389
-                            break;
3390
-
3391
-                        //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
3392
-                        case 'C': // Found in AFM files
3393
-                            $bits = explode(';', trim($row));
3394
-                            $dtmp = ['C' => null, 'N' => null, 'WX' => null, 'B' => []];
3395
-
3396
-                            foreach ($bits as $bit) {
3397
-                                $bits2 = explode(' ', trim($bit));
3398
-                                if (mb_strlen($bits2[0], '8bit') == 0) {
3399
-                                    continue;
3400
-                                }
3401
-
3402
-                                if (count($bits2) > 2) {
3403
-                                    $dtmp[$bits2[0]] = [];
3404
-                                    for ($i = 1; $i < count($bits2); $i++) {
3405
-                                        $dtmp[$bits2[0]][] = $bits2[$i];
3406
-                                    }
3407
-                                } else {
3408
-                                    if (count($bits2) == 2) {
3409
-                                        $dtmp[$bits2[0]] = $bits2[1];
3410
-                                    }
3411
-                                }
3412
-                            }
3413
-
3414
-                            $c = (int)$dtmp['C'];
3415
-                            $n = $dtmp['N'];
3416
-                            $width = floatval($dtmp['WX']);
3417
-
3418
-                            if ($c >= 0) {
3419
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3420
-                                    $data['codeToName'][$c] = $n;
3421
-                                }
3422
-                                $data['C'][$c] = $width;
3423
-                            } elseif (isset($n)) {
3424
-                                $data['C'][$n] = $width;
3425
-                            }
3426
-
3427
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3428
-                                $data['MissingWidth'] = $width;
3429
-                            }
3430
-
3431
-                            break;
3432
-
3433
-                        // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
3434
-                        case 'U': // Found in UFM files
3435
-                            if (!$data['isUnicode']) {
3436
-                                break;
3437
-                            }
3438
-
3439
-                            $bits = explode(';', trim($row));
3440
-                            $dtmp = ['G' => null, 'N' => null, 'U' => null, 'WX' => null];
3441
-
3442
-                            foreach ($bits as $bit) {
3443
-                                $bits2 = explode(' ', trim($bit));
3444
-                                if (mb_strlen($bits2[0], '8bit') === 0) {
3445
-                                    continue;
3446
-                                }
3447
-
3448
-                                if (count($bits2) > 2) {
3449
-                                    $dtmp[$bits2[0]] = [];
3450
-                                    for ($i = 1; $i < count($bits2); $i++) {
3451
-                                        $dtmp[$bits2[0]][] = $bits2[$i];
3452
-                                    }
3453
-                                } else {
3454
-                                    if (count($bits2) == 2) {
3455
-                                        $dtmp[$bits2[0]] = $bits2[1];
3456
-                                    }
3457
-                                }
3458
-                            }
3459
-
3460
-                            $c = (int)$dtmp['U'];
3461
-                            $n = $dtmp['N'];
3462
-                            $glyph = $dtmp['G'];
3463
-                            $width = floatval($dtmp['WX']);
3464
-
3465
-                            if ($c >= 0) {
3466
-                                // Set values in CID to GID map
3467
-                                if ($c >= 0 && $c < 0xFFFF && $glyph) {
3468
-                                    $cidtogid[$c * 2] = chr($glyph >> 8);
3469
-                                    $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
3470
-                                }
3471
-
3472
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3473
-                                    $data['codeToName'][$c] = $n;
3474
-                                }
3475
-                                $data['C'][$c] = $width;
3476
-                            } elseif (isset($n)) {
3477
-                                $data['C'][$n] = $width;
3478
-                            }
3479
-
3480
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3481
-                                $data['MissingWidth'] = $width;
3482
-                            }
3483
-
3484
-                            break;
3485
-
3486
-                        case 'KPX':
3487
-                            break; // don't include them as they are not used yet
3488
-                            //KPX Adieresis yacute -40
3489
-                            /*$bits = explode(' ', trim($row));
3327
+		if (file_exists($fontcache . '/' . $cache_name)) {
3328
+			$this->addMessage("openFont: json metrics file exists $fontcache/$cache_name");
3329
+			$cached_font_info = json_decode(file_get_contents($fontcache . '/' . $cache_name), true);
3330
+			if (!isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
3331
+				$this->addMessage('openFont: font cache is out of date, regenerating');
3332
+			} else {
3333
+				$this->fonts[$font] = $cached_font_info;
3334
+			}
3335
+		}
3336
+
3337
+		if (!isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
3338
+			// then rebuild the php_<font>.afm file from the <font>.afm file
3339
+			$this->addMessage("openFont: build php file from $dir/$metrics_name");
3340
+			$data = [];
3341
+
3342
+			// 20 => 'space'
3343
+			$data['codeToName'] = [];
3344
+
3345
+			// Since we're not going to enable Unicode for the core fonts we need to use a font-based
3346
+			// setting for Unicode support rather than a global setting.
3347
+			$data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm');
3348
+
3349
+			$cidtogid = '';
3350
+			if ($data['isUnicode']) {
3351
+				$cidtogid = str_pad('', 256 * 256 * 2, "\x00");
3352
+			}
3353
+
3354
+			$file = file("$dir/$metrics_name");
3355
+
3356
+			foreach ($file as $rowA) {
3357
+				$row = trim($rowA);
3358
+				$pos = strpos($row, ' ');
3359
+
3360
+				if ($pos) {
3361
+					// then there must be some keyword
3362
+					$key = substr($row, 0, $pos);
3363
+					switch ($key) {
3364
+						case 'FontName':
3365
+						case 'FullName':
3366
+						case 'FamilyName':
3367
+						case 'PostScriptName':
3368
+						case 'Weight':
3369
+						case 'ItalicAngle':
3370
+						case 'IsFixedPitch':
3371
+						case 'CharacterSet':
3372
+						case 'UnderlinePosition':
3373
+						case 'UnderlineThickness':
3374
+						case 'Version':
3375
+						case 'EncodingScheme':
3376
+						case 'CapHeight':
3377
+						case 'XHeight':
3378
+						case 'Ascender':
3379
+						case 'Descender':
3380
+						case 'StdHW':
3381
+						case 'StdVW':
3382
+						case 'StartCharMetrics':
3383
+						case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font.  Otherwise it's too big.
3384
+							$data[$key] = trim(substr($row, $pos));
3385
+							break;
3386
+
3387
+						case 'FontBBox':
3388
+							$data[$key] = explode(' ', trim(substr($row, $pos)));
3389
+							break;
3390
+
3391
+						//C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
3392
+						case 'C': // Found in AFM files
3393
+							$bits = explode(';', trim($row));
3394
+							$dtmp = ['C' => null, 'N' => null, 'WX' => null, 'B' => []];
3395
+
3396
+							foreach ($bits as $bit) {
3397
+								$bits2 = explode(' ', trim($bit));
3398
+								if (mb_strlen($bits2[0], '8bit') == 0) {
3399
+									continue;
3400
+								}
3401
+
3402
+								if (count($bits2) > 2) {
3403
+									$dtmp[$bits2[0]] = [];
3404
+									for ($i = 1; $i < count($bits2); $i++) {
3405
+										$dtmp[$bits2[0]][] = $bits2[$i];
3406
+									}
3407
+								} else {
3408
+									if (count($bits2) == 2) {
3409
+										$dtmp[$bits2[0]] = $bits2[1];
3410
+									}
3411
+								}
3412
+							}
3413
+
3414
+							$c = (int)$dtmp['C'];
3415
+							$n = $dtmp['N'];
3416
+							$width = floatval($dtmp['WX']);
3417
+
3418
+							if ($c >= 0) {
3419
+								if (!ctype_xdigit($n) || $c != hexdec($n)) {
3420
+									$data['codeToName'][$c] = $n;
3421
+								}
3422
+								$data['C'][$c] = $width;
3423
+							} elseif (isset($n)) {
3424
+								$data['C'][$n] = $width;
3425
+							}
3426
+
3427
+							if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3428
+								$data['MissingWidth'] = $width;
3429
+							}
3430
+
3431
+							break;
3432
+
3433
+						// U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
3434
+						case 'U': // Found in UFM files
3435
+							if (!$data['isUnicode']) {
3436
+								break;
3437
+							}
3438
+
3439
+							$bits = explode(';', trim($row));
3440
+							$dtmp = ['G' => null, 'N' => null, 'U' => null, 'WX' => null];
3441
+
3442
+							foreach ($bits as $bit) {
3443
+								$bits2 = explode(' ', trim($bit));
3444
+								if (mb_strlen($bits2[0], '8bit') === 0) {
3445
+									continue;
3446
+								}
3447
+
3448
+								if (count($bits2) > 2) {
3449
+									$dtmp[$bits2[0]] = [];
3450
+									for ($i = 1; $i < count($bits2); $i++) {
3451
+										$dtmp[$bits2[0]][] = $bits2[$i];
3452
+									}
3453
+								} else {
3454
+									if (count($bits2) == 2) {
3455
+										$dtmp[$bits2[0]] = $bits2[1];
3456
+									}
3457
+								}
3458
+							}
3459
+
3460
+							$c = (int)$dtmp['U'];
3461
+							$n = $dtmp['N'];
3462
+							$glyph = $dtmp['G'];
3463
+							$width = floatval($dtmp['WX']);
3464
+
3465
+							if ($c >= 0) {
3466
+								// Set values in CID to GID map
3467
+								if ($c >= 0 && $c < 0xFFFF && $glyph) {
3468
+									$cidtogid[$c * 2] = chr($glyph >> 8);
3469
+									$cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
3470
+								}
3471
+
3472
+								if (!ctype_xdigit($n) || $c != hexdec($n)) {
3473
+									$data['codeToName'][$c] = $n;
3474
+								}
3475
+								$data['C'][$c] = $width;
3476
+							} elseif (isset($n)) {
3477
+								$data['C'][$n] = $width;
3478
+							}
3479
+
3480
+							if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3481
+								$data['MissingWidth'] = $width;
3482
+							}
3483
+
3484
+							break;
3485
+
3486
+						case 'KPX':
3487
+							break; // don't include them as they are not used yet
3488
+							//KPX Adieresis yacute -40
3489
+							/*$bits = explode(' ', trim($row));
3490 3490
                             $data['KPX'][$bits[1]][$bits[2]] = $bits[3];
3491 3491
                             break;*/
3492
-                    }
3493
-                }
3494
-            }
3495
-
3496
-            if ($this->compressionReady && $this->options['compression']) {
3497
-                // then implement ZLIB based compression on CIDtoGID string
3498
-                $data['CIDtoGID_Compressed'] = true;
3499
-                $cidtogid = gzcompress($cidtogid, 6);
3500
-            }
3501
-            $data['CIDtoGID'] = base64_encode($cidtogid);
3502
-            $data['_version_'] = $this->fontcacheVersion;
3503
-            $this->fonts[$font] = $data;
3504
-
3505
-            //Because of potential trouble with php safe mode, expect that the folder already exists.
3506
-            //If not existing, this will hit performance because of missing cached results.
3507
-            if (is_dir($fontcache) && is_writable($fontcache)) {
3508
-                file_put_contents("$fontcache/$cache_name", json_encode($data, JSON_PRETTY_PRINT));
3509
-            }
3510
-            $data = null;
3511
-        }
3512
-
3513
-        if (!isset($this->fonts[$font])) {
3514
-            $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3515
-        }
3516
-    }
3517
-
3518
-    /**
3519
-     * if the font is not loaded then load it and make the required object
3520
-     * else just make it the current font
3521
-     * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
3522
-     * note that encoding='none' will need to be used for symbolic fonts
3523
-     * and 'differences' => an array of mappings between numbers 0->255 and character names.
3524
-     *
3525
-     * @param string $fontName
3526
-     * @param string $encoding
3527
-     * @param bool $set
3528
-     * @param bool $isSubsetting
3529
-     * @return int
3530
-     * @throws FontNotFoundException
3531
-     */
3532
-    function selectFont($fontName, $encoding = '', $set = true, $isSubsetting = true)
3533
-    {
3534
-        if ($fontName === null || $fontName === '') {
3535
-            return $this->currentFontNum;
3536
-        }
3537
-
3538
-        $ext = substr($fontName, -4);
3539
-        if ($ext === '.afm' || $ext === '.ufm') {
3540
-            $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
3541
-        }
3542
-
3543
-        if (!isset($this->fonts[$fontName])) {
3544
-            $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
3545
-
3546
-            // load the file
3547
-            $this->openFont($fontName);
3548
-
3549
-            if (isset($this->fonts[$fontName])) {
3550
-                $this->numObj++;
3551
-                $this->numFonts++;
3552
-
3553
-                $font = &$this->fonts[$fontName];
3554
-
3555
-                $name = basename($fontName);
3556
-                $options = ['name' => $name, 'fontFileName' => $fontName, 'isSubsetting' => $isSubsetting];
3557
-
3558
-                if (is_array($encoding)) {
3559
-                    // then encoding and differences might be set
3560
-                    if (isset($encoding['encoding'])) {
3561
-                        $options['encoding'] = $encoding['encoding'];
3562
-                    }
3563
-
3564
-                    if (isset($encoding['differences'])) {
3565
-                        $options['differences'] = $encoding['differences'];
3566
-                    }
3567
-                } else {
3568
-                    if (mb_strlen($encoding, '8bit')) {
3569
-                        // then perhaps only the encoding has been set
3570
-                        $options['encoding'] = $encoding;
3571
-                    }
3572
-                }
3573
-
3574
-                $this->o_font($this->numObj, 'new', $options);
3575
-
3576
-                if (file_exists("$fontName.ttf")) {
3577
-                    $fileSuffix = 'ttf';
3578
-                } elseif (file_exists("$fontName.TTF")) {
3579
-                    $fileSuffix = 'TTF';
3580
-                } elseif (file_exists("$fontName.pfb")) {
3581
-                    $fileSuffix = 'pfb';
3582
-                } elseif (file_exists("$fontName.PFB")) {
3583
-                    $fileSuffix = 'PFB';
3584
-                } else {
3585
-                    $fileSuffix = '';
3586
-                }
3587
-
3588
-                $font['fileSuffix'] = $fileSuffix;
3589
-
3590
-                $font['fontNum'] = $this->numFonts;
3591
-                $font['isSubsetting'] = $isSubsetting && $font['isUnicode'] && strtolower($fileSuffix) === 'ttf';
3592
-
3593
-                // also set the differences here, note that this means that these will take effect only the
3594
-                //first time that a font is selected, else they are ignored
3595
-                if (isset($options['differences'])) {
3596
-                    $font['differences'] = $options['differences'];
3597
-                }
3598
-            }
3599
-        }
3600
-
3601
-        if ($set && isset($this->fonts[$fontName])) {
3602
-            // so if for some reason the font was not set in the last one then it will not be selected
3603
-            $this->currentBaseFont = $fontName;
3604
-
3605
-            // the next lines mean that if a new font is selected, then the current text state will be
3606
-            // applied to it as well.
3607
-            $this->currentFont = $this->currentBaseFont;
3608
-            $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3609
-        }
3610
-
3611
-        return $this->currentFontNum;
3612
-    }
3613
-
3614
-    /**
3615
-     * sets up the current font, based on the font families, and the current text state
3616
-     * note that this system is quite flexible, a bold-italic font can be completely different to a
3617
-     * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3618
-     * This function is to be called whenever the currentTextState is changed, it will update
3619
-     * the currentFont setting to whatever the appropriate family one is.
3620
-     * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3621
-     * This function will change the currentFont to whatever it should be, but will not change the
3622
-     * currentBaseFont.
3623
-     */
3624
-    private function setCurrentFont()
3625
-    {
3626
-        //   if (strlen($this->currentBaseFont) == 0){
3627
-        //     // then assume an initial font
3628
-        //     $this->selectFont($this->defaultFont);
3629
-        //   }
3630
-        //   $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3631
-        //   if (strlen($this->currentTextState)
3632
-        //     && isset($this->fontFamilies[$cf])
3633
-        //       && isset($this->fontFamilies[$cf][$this->currentTextState])){
3634
-        //     // then we are in some state or another
3635
-        //     // and this font has a family, and the current setting exists within it
3636
-        //     // select the font, then return it
3637
-        //     $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3638
-        //     $this->selectFont($nf,'',0);
3639
-        //     $this->currentFont = $nf;
3640
-        //     $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3641
-        //   } else {
3642
-        //     // the this font must not have the right family member for the current state
3643
-        //     // simply assume the base font
3644
-        $this->currentFont = $this->currentBaseFont;
3645
-        $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3646
-        //  }
3647
-    }
3648
-
3649
-    /**
3650
-     * function for the user to find out what the ID is of the first page that was created during
3651
-     * startup - useful if they wish to add something to it later.
3652
-     *
3653
-     * @return int
3654
-     */
3655
-    function getFirstPageId()
3656
-    {
3657
-        return $this->firstPageId;
3658
-    }
3659
-
3660
-    /**
3661
-     * add content to the currently active object
3662
-     *
3663
-     * @param $content
3664
-     */
3665
-    private function addContent($content)
3666
-    {
3667
-        $this->objects[$this->currentContents]['c'] .= $content;
3668
-    }
3669
-
3670
-    /**
3671
-     * sets the color for fill operations
3672
-     *
3673
-     * @param array $color
3674
-     * @param bool  $force
3675
-     */
3676
-    function setColor($color, $force = false)
3677
-    {
3678
-        $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3679
-
3680
-        if (!$force && $this->currentColor == $new_color) {
3681
-            return;
3682
-        }
3683
-
3684
-        if (isset($new_color[3])) {
3685
-            $this->currentColor = $new_color;
3686
-            $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3687
-        } else {
3688
-            if (isset($new_color[2])) {
3689
-                $this->currentColor = $new_color;
3690
-                $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3691
-            }
3692
-        }
3693
-    }
3694
-
3695
-    /**
3696
-     * @param string $fillRule
3697
-     */
3698
-    function setFillRule($fillRule)
3699
-    {
3700
-        if (!in_array($fillRule, ["nonzero", "evenodd"])) {
3701
-            return;
3702
-        }
3703
-
3704
-        $this->fillRule = $fillRule;
3705
-    }
3706
-
3707
-    /**
3708
-     * sets the color for stroke operations
3709
-     *
3710
-     * @param array $color
3711
-     * @param bool  $force
3712
-     */
3713
-    function setStrokeColor($color, $force = false)
3714
-    {
3715
-        $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3716
-
3717
-        if (!$force && $this->currentStrokeColor == $new_color) {
3718
-            return;
3719
-        }
3720
-
3721
-        if (isset($new_color[3])) {
3722
-            $this->currentStrokeColor = $new_color;
3723
-            $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3724
-        } else {
3725
-            if (isset($new_color[2])) {
3726
-                $this->currentStrokeColor = $new_color;
3727
-                $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3728
-            }
3729
-        }
3730
-    }
3731
-
3732
-    /**
3733
-     * Set the graphics state for compositions
3734
-     *
3735
-     * @param $parameters
3736
-     */
3737
-    function setGraphicsState($parameters)
3738
-    {
3739
-        // Create a new graphics state object if necessary
3740
-        if (($gstate = array_search($parameters, $this->gstates)) === false) {
3741
-            $this->numObj++;
3742
-            $this->o_extGState($this->numObj, 'new', $parameters);
3743
-            $gstate = $this->numStates;
3744
-            $this->gstates[$gstate] = $parameters;
3745
-        }
3746
-        $this->addContent("\n/GS$gstate gs");
3747
-    }
3748
-
3749
-    /**
3750
-     * Set current blend mode & opacity for lines.
3751
-     *
3752
-     * Valid blend modes are:
3753
-     *
3754
-     * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3755
-     * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3756
-     * Exclusion
3757
-     *
3758
-     * @param string $mode    the blend mode to use
3759
-     * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3760
-     */
3761
-    function setLineTransparency($mode, $opacity)
3762
-    {
3763
-        static $blend_modes = [
3764
-            "Normal",
3765
-            "Multiply",
3766
-            "Screen",
3767
-            "Overlay",
3768
-            "Darken",
3769
-            "Lighten",
3770
-            "ColorDogde",
3771
-            "ColorBurn",
3772
-            "HardLight",
3773
-            "SoftLight",
3774
-            "Difference",
3775
-            "Exclusion"
3776
-        ];
3777
-
3778
-        if (!in_array($mode, $blend_modes)) {
3779
-            $mode = "Normal";
3780
-        }
3781
-
3782
-        if (is_null($this->currentLineTransparency)) {
3783
-            $this->currentLineTransparency = [];
3784
-        }
3785
-
3786
-        if ($mode === (key_exists('mode', $this->currentLineTransparency) ?
3787
-            $this->currentLineTransparency['mode'] : '') &&
3788
-            $opacity === (key_exists('opacity', $this->currentLineTransparency) ?
3789
-            $this->currentLineTransparency["opacity"] : '')) {
3790
-            return;
3791
-        }
3792
-
3793
-        $this->currentLineTransparency["mode"] = $mode;
3794
-        $this->currentLineTransparency["opacity"] = $opacity;
3795
-
3796
-        $options = [
3797
-            "BM" => "/$mode",
3798
-            "CA" => (float)$opacity
3799
-        ];
3800
-
3801
-        $this->setGraphicsState($options);
3802
-    }
3803
-
3804
-    /**
3805
-     * Set current blend mode & opacity for filled objects.
3806
-     *
3807
-     * Valid blend modes are:
3808
-     *
3809
-     * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3810
-     * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3811
-     * Exclusion
3812
-     *
3813
-     * @param string $mode    the blend mode to use
3814
-     * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3815
-     */
3816
-    function setFillTransparency($mode, $opacity)
3817
-    {
3818
-        static $blend_modes = [
3819
-            "Normal",
3820
-            "Multiply",
3821
-            "Screen",
3822
-            "Overlay",
3823
-            "Darken",
3824
-            "Lighten",
3825
-            "ColorDogde",
3826
-            "ColorBurn",
3827
-            "HardLight",
3828
-            "SoftLight",
3829
-            "Difference",
3830
-            "Exclusion"
3831
-        ];
3832
-
3833
-        if (!in_array($mode, $blend_modes)) {
3834
-            $mode = "Normal";
3835
-        }
3836
-
3837
-        if (is_null($this->currentFillTransparency)) {
3838
-            $this->currentFillTransparency = [];
3839
-        }
3840
-
3841
-        if ($mode === (key_exists('mode', $this->currentFillTransparency) ?
3842
-            $this->currentFillTransparency['mode'] : '') &&
3843
-            $opacity === (key_exists('opacity', $this->currentFillTransparency) ?
3844
-            $this->currentFillTransparency["opacity"] : '')) {
3845
-            return;
3846
-        }
3847
-
3848
-        $this->currentFillTransparency["mode"] = $mode;
3849
-        $this->currentFillTransparency["opacity"] = $opacity;
3850
-
3851
-        $options = [
3852
-            "BM" => "/$mode",
3853
-            "ca" => (float)$opacity,
3854
-        ];
3855
-
3856
-        $this->setGraphicsState($options);
3857
-    }
3858
-
3859
-    /**
3860
-     * draw a line from one set of coordinates to another
3861
-     *
3862
-     * @param float $x1
3863
-     * @param float $y1
3864
-     * @param float $x2
3865
-     * @param float $y2
3866
-     * @param bool  $stroke
3867
-     */
3868
-    function line($x1, $y1, $x2, $y2, $stroke = true)
3869
-    {
3870
-        $this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3871
-
3872
-        if ($stroke) {
3873
-            $this->addContent(' S');
3874
-        }
3875
-    }
3876
-
3877
-    /**
3878
-     * draw a bezier curve based on 4 control points
3879
-     *
3880
-     * @param float $x0
3881
-     * @param float $y0
3882
-     * @param float $x1
3883
-     * @param float $y1
3884
-     * @param float $x2
3885
-     * @param float $y2
3886
-     * @param float $x3
3887
-     * @param float $y3
3888
-     */
3889
-    function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3890
-    {
3891
-        // in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3892
-        // as the control points for the curve.
3893
-        $this->addContent(
3894
-            sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3895
-        );
3896
-    }
3897
-
3898
-    /**
3899
-     * draw a part of an ellipse
3900
-     *
3901
-     * @param float $x0
3902
-     * @param float $y0
3903
-     * @param float $astart
3904
-     * @param float $afinish
3905
-     * @param float $r1
3906
-     * @param float $r2
3907
-     * @param float $angle
3908
-     * @param int $nSeg
3909
-     */
3910
-    function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3911
-    {
3912
-        $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3913
-    }
3914
-
3915
-    /**
3916
-     * draw a filled ellipse
3917
-     *
3918
-     * @param float $x0
3919
-     * @param float $y0
3920
-     * @param float $r1
3921
-     * @param float $r2
3922
-     * @param float $angle
3923
-     * @param int $nSeg
3924
-     * @param float $astart
3925
-     * @param float $afinish
3926
-     */
3927
-    function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3928
-    {
3929
-        $this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3930
-    }
3931
-
3932
-    /**
3933
-     * @param float $x
3934
-     * @param float $y
3935
-     */
3936
-    function lineTo($x, $y)
3937
-    {
3938
-        $this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3939
-    }
3940
-
3941
-    /**
3942
-     * @param float $x
3943
-     * @param float $y
3944
-     */
3945
-    function moveTo($x, $y)
3946
-    {
3947
-        $this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3948
-    }
3949
-
3950
-    /**
3951
-     * draw a bezier curve based on 4 control points
3952
-     *
3953
-     * @param float $x1
3954
-     * @param float $y1
3955
-     * @param float $x2
3956
-     * @param float $y2
3957
-     * @param float $x3
3958
-     * @param float $y3
3959
-     */
3960
-    function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3961
-    {
3962
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3963
-    }
3964
-
3965
-    /**
3966
-     * draw a bezier curve based on 4 control points
3967
-     *
3968
-     * @param float $cpx
3969
-     * @param float $cpy
3970
-     * @param float $x
3971
-     * @param float $y
3972
-     */
3973
-    function quadTo($cpx, $cpy, $x, $y)
3974
-    {
3975
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3976
-    }
3977
-
3978
-    function closePath()
3979
-    {
3980
-        $this->addContent(' h');
3981
-    }
3982
-
3983
-    function endPath()
3984
-    {
3985
-        $this->addContent(' n');
3986
-    }
3987
-
3988
-    /**
3989
-     * draw an ellipse
3990
-     * note that the part and filled ellipse are just special cases of this function
3991
-     *
3992
-     * draws an ellipse in the current line style
3993
-     * centered at $x0,$y0, radii $r1,$r2
3994
-     * if $r2 is not set, then a circle is drawn
3995
-     * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3996
-     * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3997
-     * pretty crappy shape at 2, as we are approximating with bezier curves.
3998
-     *
3999
-     * @param float $x0
4000
-     * @param float $y0
4001
-     * @param float $r1
4002
-     * @param float $r2
4003
-     * @param float $angle
4004
-     * @param int   $nSeg
4005
-     * @param float $astart
4006
-     * @param float $afinish
4007
-     * @param bool  $close
4008
-     * @param bool  $fill
4009
-     * @param bool  $stroke
4010
-     * @param bool  $incomplete
4011
-     */
4012
-    function ellipse(
4013
-        $x0,
4014
-        $y0,
4015
-        $r1,
4016
-        $r2 = 0,
4017
-        $angle = 0,
4018
-        $nSeg = 8,
4019
-        $astart = 0,
4020
-        $afinish = 360,
4021
-        $close = true,
4022
-        $fill = false,
4023
-        $stroke = true,
4024
-        $incomplete = false
4025
-    ) {
4026
-        if ($r1 == 0) {
4027
-            return;
4028
-        }
4029
-
4030
-        if ($r2 == 0) {
4031
-            $r2 = $r1;
4032
-        }
4033
-
4034
-        if ($nSeg < 2) {
4035
-            $nSeg = 2;
4036
-        }
4037
-
4038
-        $astart = deg2rad((float)$astart);
4039
-        $afinish = deg2rad((float)$afinish);
4040
-        $totalAngle = $afinish - $astart;
4041
-
4042
-        $dt = $totalAngle / $nSeg;
4043
-        $dtm = $dt / 3;
4044
-
4045
-        if ($angle != 0) {
4046
-            $a = -1 * deg2rad((float)$angle);
4047
-
4048
-            $this->addContent(
4049
-                sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
4050
-            );
4051
-
4052
-            $x0 = 0;
4053
-            $y0 = 0;
4054
-        }
4055
-
4056
-        $t1 = $astart;
4057
-        $a0 = $x0 + $r1 * cos($t1);
4058
-        $b0 = $y0 + $r2 * sin($t1);
4059
-        $c0 = -$r1 * sin($t1);
4060
-        $d0 = $r2 * cos($t1);
4061
-
4062
-        if (!$incomplete) {
4063
-            $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
4064
-        }
4065
-
4066
-        for ($i = 1; $i <= $nSeg; $i++) {
4067
-            // draw this bit of the total curve
4068
-            $t1 = $i * $dt + $astart;
4069
-            $a1 = $x0 + $r1 * cos($t1);
4070
-            $b1 = $y0 + $r2 * sin($t1);
4071
-            $c1 = -$r1 * sin($t1);
4072
-            $d1 = $r2 * cos($t1);
4073
-
4074
-            $this->addContent(
4075
-                sprintf(
4076
-                    "\n%.3F %.3F %.3F %.3F %.3F %.3F c",
4077
-                    ($a0 + $c0 * $dtm),
4078
-                    ($b0 + $d0 * $dtm),
4079
-                    ($a1 - $c1 * $dtm),
4080
-                    ($b1 - $d1 * $dtm),
4081
-                    $a1,
4082
-                    $b1
4083
-                )
4084
-            );
4085
-
4086
-            $a0 = $a1;
4087
-            $b0 = $b1;
4088
-            $c0 = $c1;
4089
-            $d0 = $d1;
4090
-        }
4091
-
4092
-        if (!$incomplete) {
4093
-            if ($fill) {
4094
-                $this->addContent(' f');
4095
-            }
4096
-
4097
-            if ($stroke) {
4098
-                if ($close) {
4099
-                    $this->addContent(' s'); // small 's' signifies closing the path as well
4100
-                } else {
4101
-                    $this->addContent(' S');
4102
-                }
4103
-            }
4104
-        }
4105
-
4106
-        if ($angle != 0) {
4107
-            $this->addContent(' Q');
4108
-        }
4109
-    }
4110
-
4111
-    /**
4112
-     * this sets the line drawing style.
4113
-     * width, is the thickness of the line in user units
4114
-     * cap is the type of cap to put on the line, values can be 'butt','round','square'
4115
-     *    where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
4116
-     *    end of the line.
4117
-     * join can be 'miter', 'round', 'bevel'
4118
-     * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
4119
-     *   on and off dashes.
4120
-     *   (2) represents 2 on, 2 off, 2 on , 2 off ...
4121
-     *   (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
4122
-     * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
4123
-     *
4124
-     * @param float  $width
4125
-     * @param string $cap
4126
-     * @param string $join
4127
-     * @param array  $dash
4128
-     * @param int    $phase
4129
-     */
4130
-    function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
4131
-    {
4132
-        // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
4133
-        $string = '';
4134
-
4135
-        if ($width > 0) {
4136
-            $string .= "$width w";
4137
-        }
4138
-
4139
-        $ca = ['butt' => 0, 'round' => 1, 'square' => 2];
4140
-
4141
-        if (isset($ca[$cap])) {
4142
-            $string .= " $ca[$cap] J";
4143
-        }
4144
-
4145
-        $ja = ['miter' => 0, 'round' => 1, 'bevel' => 2];
4146
-
4147
-        if (isset($ja[$join])) {
4148
-            $string .= " $ja[$join] j";
4149
-        }
4150
-
4151
-        if (is_array($dash)) {
4152
-            $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
4153
-        }
4154
-
4155
-        $this->currentLineStyle = $string;
4156
-        $this->addContent("\n$string");
4157
-    }
4158
-
4159
-    /**
4160
-     * draw a polygon, the syntax for this is similar to the GD polygon command
4161
-     *
4162
-     * @param float[] $p
4163
-     * @param bool    $fill
4164
-     */
4165
-    public function polygon(array $p, bool $fill = false): void
4166
-    {
4167
-        $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4168
-
4169
-        $n = count($p);
4170
-        for ($i = 2; $i < $n; $i = $i + 2) {
4171
-            $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4172
-        }
4173
-
4174
-        if ($fill) {
4175
-            $this->addContent(' f');
4176
-        } else {
4177
-            $this->addContent(' S');
4178
-        }
4179
-    }
4180
-
4181
-    /**
4182
-     * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4183
-     * the coordinates of the upper-right corner
4184
-     *
4185
-     * @param float $x1
4186
-     * @param float $y1
4187
-     * @param float $width
4188
-     * @param float $height
4189
-     */
4190
-    function filledRectangle($x1, $y1, $width, $height)
4191
-    {
4192
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
4193
-    }
4194
-
4195
-    /**
4196
-     * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4197
-     * the coordinates of the upper-right corner
4198
-     *
4199
-     * @param float $x1
4200
-     * @param float $y1
4201
-     * @param float $width
4202
-     * @param float $height
4203
-     */
4204
-    function rectangle($x1, $y1, $width, $height)
4205
-    {
4206
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
4207
-    }
4208
-
4209
-    /**
4210
-     * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4211
-     * the coordinates of the upper-right corner
4212
-     *
4213
-     * @param float $x1
4214
-     * @param float $y1
4215
-     * @param float $width
4216
-     * @param float $height
4217
-     */
4218
-    function rect($x1, $y1, $width, $height)
4219
-    {
4220
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
4221
-    }
4222
-
4223
-    function stroke()
4224
-    {
4225
-        $this->addContent("\nS");
4226
-    }
4227
-
4228
-    function fill()
4229
-    {
4230
-        $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
4231
-    }
4232
-
4233
-    function fillStroke()
4234
-    {
4235
-        $this->addContent("\nb" . ($this->fillRule === "evenodd" ? "*" : ""));
4236
-    }
4237
-
4238
-    /**
4239
-     * @param string $subtype
4240
-     * @param integer $x
4241
-     * @param integer $y
4242
-     * @param integer $w
4243
-     * @param integer $h
4244
-     * @return int
4245
-     */
4246
-    function addXObject($subtype, $x, $y, $w, $h)
4247
-    {
4248
-        $id = ++$this->numObj;
4249
-        $this->o_xobject($id, 'new', ['Subtype' => $subtype, 'bbox' => [$x, $y, $w, $h]]);
4250
-        return $id;
4251
-    }
4252
-
4253
-    /**
4254
-     * @param integer $numXObject
4255
-     * @param string $type
4256
-     * @param array $options
4257
-     */
4258
-    function setXObjectResource($numXObject, $type, $options)
4259
-    {
4260
-        if (in_array($type, ['procset', 'font', 'xObject'])) {
4261
-            $this->o_xobject($numXObject, $type, $options);
4262
-        }
4263
-    }
4264
-
4265
-    /**
4266
-     * add signature
4267
-     *
4268
-     * $fieldSigId = $cpdf->addFormField(Cpdf::ACROFORM_FIELD_SIG, 'Signature1', 0, 0, 0, 0, 0);
4269
-     *
4270
-     * $signatureId = $cpdf->addSignature([
4271
-     *   'signcert' => file_get_contents('dompdf.crt'),
4272
-     *   'privkey' => file_get_contents('dompdf.key'),
4273
-     *   'password' => 'password',
4274
-     *   'name' => 'DomPDF DEMO',
4275
-     *   'location' => 'Home',
4276
-     *   'reason' => 'First Form',
4277
-     *   'contactinfo' => 'info'
4278
-     * ]);
4279
-     * $cpdf->setFormFieldValue($fieldSigId, "$signatureId 0 R");
4280
-     *
4281
-     * @param string $signcert
4282
-     * @param string $privkey
4283
-     * @param string $password
4284
-     * @param string|null $name
4285
-     * @param string|null $location
4286
-     * @param string|null $reason
4287
-     * @param string|null $contactinfo
4288
-     * @return int
4289
-     */
4290
-    function addSignature($signcert, $privkey, $password = '', $name = null, $location = null, $reason = null, $contactinfo = null) {
4291
-        $sigId = ++$this->numObj;
4292
-        $this->o_sig($sigId, 'new', [
4293
-          'SignCert' => $signcert,
4294
-          'PrivKey' => $privkey,
4295
-          'Password' => $password,
4296
-          'Name' => $name,
4297
-          'Location' => $location,
4298
-          'Reason' => $reason,
4299
-          'ContactInfo' => $contactinfo
4300
-        ]);
4301
-
4302
-        return $sigId;
4303
-    }
4304
-
4305
-    /**
4306
-     * add field to form
4307
-     *
4308
-     * @param string $type ACROFORM_FIELD_*
4309
-     * @param string $name
4310
-     * @param $x0
4311
-     * @param $y0
4312
-     * @param $x1
4313
-     * @param $y1
4314
-     * @param integer $ff Field Flag ACROFORM_FIELD_*_*
4315
-     * @param float $size
4316
-     * @param array $color
4317
-     * @return int
4318
-     */
4319
-    public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
4320
-    {
4321
-        if (!$this->numFonts) {
4322
-            $this->selectFont($this->defaultFont);
4323
-        }
4324
-
4325
-        $color = implode(' ', $color) . ' rg';
4326
-
4327
-        $currentFontNum = $this->currentFontNum;
4328
-        $font = array_filter(
4329
-            $this->objects[$this->currentNode]['info']['fonts'],
4330
-            function ($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
4331
-        );
4332
-
4333
-        $this->o_acroform($this->acroFormId, 'font',
4334
-          ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
4335
-
4336
-        $fieldId = ++$this->numObj;
4337
-        $this->o_field($fieldId, 'new', [
4338
-          'rect' => [$x0, $y0, $x1, $y1],
4339
-          'F' => 4,
4340
-          'FT' => "/$type",
4341
-          'T' => $name,
4342
-          'Ff' => $ff,
4343
-          'pageid' => $this->currentPage,
4344
-          'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
4345
-        ]);
4346
-
4347
-        return $fieldId;
4348
-    }
4349
-
4350
-    /**
4351
-     * set Field value
4352
-     *
4353
-     * @param integer $numFieldObj
4354
-     * @param string $value
4355
-     */
4356
-    public function setFormFieldValue($numFieldObj, $value)
4357
-    {
4358
-        $this->o_field($numFieldObj, 'set', ['value' => $value]);
4359
-    }
4360
-
4361
-    /**
4362
-     * set Field value (reference)
4363
-     *
4364
-     * @param integer $numFieldObj
4365
-     * @param integer $numObj Object number
4366
-     */
4367
-    public function setFormFieldRefValue($numFieldObj, $numObj)
4368
-    {
4369
-        $this->o_field($numFieldObj, 'set', ['refvalue' => $numObj]);
4370
-    }
4371
-
4372
-    /**
4373
-     * set Field Appearanc (reference)
4374
-     *
4375
-     * @param integer $numFieldObj
4376
-     * @param integer $normalNumObj
4377
-     * @param integer|null $rolloverNumObj
4378
-     * @param integer|null $downNumObj
4379
-     */
4380
-    public function setFormFieldAppearance($numFieldObj, $normalNumObj, $rolloverNumObj = null, $downNumObj = null)
4381
-    {
4382
-        $appearance['N'] = $normalNumObj;
4383
-
4384
-        if ($rolloverNumObj !== null) {
4385
-            $appearance['R'] = $rolloverNumObj;
4386
-        }
4387
-
4388
-        if ($downNumObj !== null) {
4389
-            $appearance['D'] = $downNumObj;
4390
-        }
4391
-
4392
-        $this->o_field($numFieldObj, 'set', ['appearance' => $appearance]);
4393
-    }
4394
-
4395
-    /**
4396
-     * set Choice Field option values
4397
-     *
4398
-     * @param integer $numFieldObj
4399
-     * @param array $value
4400
-     */
4401
-    public function setFormFieldOpt($numFieldObj, $value)
4402
-    {
4403
-        $this->o_field($numFieldObj, 'set', ['options' => $value]);
4404
-    }
4405
-
4406
-    /**
4407
-     * add form to document
4408
-     *
4409
-     * @param integer $sigFlags
4410
-     * @param boolean $needAppearances
4411
-     */
4412
-    public function addForm($sigFlags = 0, $needAppearances = false)
4413
-    {
4414
-        $this->acroFormId = ++$this->numObj;
4415
-        $this->o_acroform($this->acroFormId, 'new', [
4416
-          'NeedAppearances' => $needAppearances ? 'true' : 'false',
4417
-          'SigFlags' => $sigFlags
4418
-        ]);
4419
-    }
4420
-
4421
-    /**
4422
-     * save the current graphic state
4423
-     */
4424
-    function save()
4425
-    {
4426
-        // we must reset the color cache or it will keep bad colors after clipping
4427
-        $this->currentColor = null;
4428
-        $this->currentStrokeColor = null;
4429
-        $this->addContent("\nq");
4430
-    }
4431
-
4432
-    /**
4433
-     * restore the last graphic state
4434
-     */
4435
-    function restore()
4436
-    {
4437
-        // we must reset the color cache or it will keep bad colors after clipping
4438
-        $this->currentColor = null;
4439
-        $this->currentStrokeColor = null;
4440
-        $this->addContent("\nQ");
4441
-    }
4442
-
4443
-    /**
4444
-     * draw a clipping rectangle, all the elements added after this will be clipped
4445
-     *
4446
-     * @param float $x1
4447
-     * @param float $y1
4448
-     * @param float $width
4449
-     * @param float $height
4450
-     */
4451
-    function clippingRectangle($x1, $y1, $width, $height)
4452
-    {
4453
-        $this->save();
4454
-        $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
4455
-    }
4456
-
4457
-    /**
4458
-     * draw a clipping rounded rectangle, all the elements added after this will be clipped
4459
-     *
4460
-     * @param float $x1
4461
-     * @param float $y1
4462
-     * @param float $w
4463
-     * @param float $h
4464
-     * @param float $rTL
4465
-     * @param float $rTR
4466
-     * @param float $rBR
4467
-     * @param float $rBL
4468
-     */
4469
-    function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
4470
-    {
4471
-        $this->save();
4472
-
4473
-        // start: top edge, left end
4474
-        $this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
4475
-
4476
-        // line: bottom edge, left end
4477
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
4478
-
4479
-        // curve: bottom-left corner
4480
-        $this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
4481
-
4482
-        // line: right edge, bottom end
4483
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
4484
-
4485
-        // curve: bottom-right corner
4486
-        $this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
4487
-
4488
-        // line: right edge, top end
4489
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
4490
-
4491
-        // curve: bottom-right corner
4492
-        $this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
4493
-
4494
-        // line: bottom edge, right end
4495
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
4496
-
4497
-        // curve: top-right corner
4498
-        $this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
4499
-
4500
-        // line: top edge, left end
4501
-        $this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
4502
-
4503
-        // Close & clip
4504
-        $this->addContent(" W n");
4505
-    }
4506
-
4507
-    /**
4508
-     * draw a clipping polygon, the syntax for this is similar to the GD polygon command
4509
-     *
4510
-     * @param float[] $p
4511
-     */
4512
-    public function clippingPolygon(array $p): void
4513
-    {
4514
-        $this->save();
4515
-
4516
-        $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4517
-
4518
-        $n = count($p);
4519
-        for ($i = 2; $i < $n; $i = $i + 2) {
4520
-            $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4521
-        }
4522
-
4523
-        $this->addContent("W n");
4524
-    }
4525
-
4526
-    /**
4527
-     * ends the last clipping shape
4528
-     */
4529
-    function clippingEnd()
4530
-    {
4531
-        $this->restore();
4532
-    }
4533
-
4534
-    /**
4535
-     * scale
4536
-     *
4537
-     * @param float $s_x scaling factor for width as percent
4538
-     * @param float $s_y scaling factor for height as percent
4539
-     * @param float $x   Origin abscissa
4540
-     * @param float $y   Origin ordinate
4541
-     */
4542
-    function scale($s_x, $s_y, $x, $y)
4543
-    {
4544
-        $y = $this->currentPageSize["height"] - $y;
4545
-
4546
-        $tm = [
4547
-            $s_x,
4548
-            0,
4549
-            0,
4550
-            $s_y,
4551
-            $x * (1 - $s_x),
4552
-            $y * (1 - $s_y)
4553
-        ];
4554
-
4555
-        $this->transform($tm);
4556
-    }
4557
-
4558
-    /**
4559
-     * translate
4560
-     *
4561
-     * @param float $t_x movement to the right
4562
-     * @param float $t_y movement to the bottom
4563
-     */
4564
-    function translate($t_x, $t_y)
4565
-    {
4566
-        $tm = [
4567
-            1,
4568
-            0,
4569
-            0,
4570
-            1,
4571
-            $t_x,
4572
-            -$t_y
4573
-        ];
4574
-
4575
-        $this->transform($tm);
4576
-    }
4577
-
4578
-    /**
4579
-     * rotate
4580
-     *
4581
-     * @param float $angle angle in degrees for counter-clockwise rotation
4582
-     * @param float $x     Origin abscissa
4583
-     * @param float $y     Origin ordinate
4584
-     */
4585
-    function rotate($angle, $x, $y)
4586
-    {
4587
-        $y = $this->currentPageSize["height"] - $y;
4588
-
4589
-        $a = deg2rad($angle);
4590
-        $cos_a = cos($a);
4591
-        $sin_a = sin($a);
4592
-
4593
-        $tm = [
4594
-            $cos_a,
4595
-            -$sin_a,
4596
-            $sin_a,
4597
-            $cos_a,
4598
-            $x - $sin_a * $y - $cos_a * $x,
4599
-            $y - $cos_a * $y + $sin_a * $x,
4600
-        ];
4601
-
4602
-        $this->transform($tm);
4603
-    }
4604
-
4605
-    /**
4606
-     * skew
4607
-     *
4608
-     * @param float $angle_x
4609
-     * @param float $angle_y
4610
-     * @param float $x Origin abscissa
4611
-     * @param float $y Origin ordinate
4612
-     */
4613
-    function skew($angle_x, $angle_y, $x, $y)
4614
-    {
4615
-        $y = $this->currentPageSize["height"] - $y;
4616
-
4617
-        $tan_x = tan(deg2rad($angle_x));
4618
-        $tan_y = tan(deg2rad($angle_y));
4619
-
4620
-        $tm = [
4621
-            1,
4622
-            -$tan_y,
4623
-            -$tan_x,
4624
-            1,
4625
-            $tan_x * $y,
4626
-            $tan_y * $x,
4627
-        ];
4628
-
4629
-        $this->transform($tm);
4630
-    }
4631
-
4632
-    /**
4633
-     * apply graphic transformations
4634
-     *
4635
-     * @param array $tm transformation matrix
4636
-     */
4637
-    function transform($tm)
4638
-    {
4639
-        $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
4640
-    }
4641
-
4642
-    /**
4643
-     * add a new page to the document
4644
-     * this also makes the new page the current active object
4645
-     *
4646
-     * @param int $insert
4647
-     * @param int $id
4648
-     * @param string $pos
4649
-     * @return int
4650
-     */
4651
-    function newPage($insert = 0, $id = 0, $pos = 'after')
4652
-    {
4653
-        // if there is a state saved, then go up the stack closing them
4654
-        // then on the new page, re-open them with the right setings
4655
-
4656
-        if ($this->nStateStack) {
4657
-            for ($i = $this->nStateStack; $i >= 1; $i--) {
4658
-                $this->restoreState($i);
4659
-            }
4660
-        }
4661
-
4662
-        $this->numObj++;
4663
-
4664
-        if ($insert) {
4665
-            // the id from the ezPdf class is the id of the contents of the page, not the page object itself
4666
-            // query that object to find the parent
4667
-            $rid = $this->objects[$id]['onPage'];
4668
-            $opt = ['rid' => $rid, 'pos' => $pos];
4669
-            $this->o_page($this->numObj, 'new', $opt);
4670
-        } else {
4671
-            $this->o_page($this->numObj, 'new');
4672
-        }
4673
-
4674
-        // if there is a stack saved, then put that onto the page
4675
-        if ($this->nStateStack) {
4676
-            for ($i = 1; $i <= $this->nStateStack; $i++) {
4677
-                $this->saveState($i);
4678
-            }
4679
-        }
4680
-
4681
-        // and if there has been a stroke or fill color set, then transfer them
4682
-        if (isset($this->currentColor)) {
4683
-            $this->setColor($this->currentColor, true);
4684
-        }
4685
-
4686
-        if (isset($this->currentStrokeColor)) {
4687
-            $this->setStrokeColor($this->currentStrokeColor, true);
4688
-        }
4689
-
4690
-        // if there is a line style set, then put this in too
4691
-        if (mb_strlen($this->currentLineStyle, '8bit')) {
4692
-            $this->addContent("\n$this->currentLineStyle");
4693
-        }
4694
-
4695
-        // the call to the o_page object set currentContents to the present page, so this can be returned as the page id
4696
-        return $this->currentContents;
4697
-    }
4698
-
4699
-    /**
4700
-     * Streams the PDF to the client.
4701
-     *
4702
-     * @param string $filename The filename to present to the client.
4703
-     * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
4704
-     */
4705
-    function stream($filename = "document.pdf", $options = [])
4706
-    {
4707
-        if (headers_sent()) {
4708
-            die("Unable to stream pdf: headers already sent");
4709
-        }
4710
-
4711
-        if (!isset($options["compress"])) $options["compress"] = true;
4712
-        if (!isset($options["Attachment"])) $options["Attachment"] = true;
4713
-
4714
-        $debug = !$options['compress'];
4715
-        $tmp = ltrim($this->output($debug));
4716
-
4717
-        header("Cache-Control: private");
4718
-        header("Content-Type: application/pdf");
4719
-        header("Content-Length: " . mb_strlen($tmp, "8bit"));
4720
-
4721
-        $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
4722
-        $attachment = $options["Attachment"] ? "attachment" : "inline";
4723
-
4724
-        $encoding = mb_detect_encoding($filename);
4725
-        $fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
4726
-        $fallbackfilename = str_replace("\"", "", $fallbackfilename);
4727
-        $encodedfilename = rawurlencode($filename);
4728
-
4729
-        $contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
4730
-        if ($fallbackfilename !== $filename) {
4731
-            $contentDisposition .= "; filename*=UTF-8''$encodedfilename";
4732
-        }
4733
-        header($contentDisposition);
4734
-
4735
-        echo $tmp;
4736
-        flush();
4737
-    }
4738
-
4739
-    /**
4740
-     * return the height in units of the current font in the given size
4741
-     *
4742
-     * @param float $size
4743
-     *
4744
-     * @return float
4745
-     */
4746
-    public function getFontHeight(float $size): float
4747
-    {
4748
-        if (!$this->numFonts) {
4749
-            $this->selectFont($this->defaultFont);
4750
-        }
4751
-
4752
-        $font = $this->fonts[$this->currentFont];
4753
-
4754
-        // for the current font, and the given size, what is the height of the font in user units
4755
-        if (isset($font['Ascender']) && isset($font['Descender'])) {
4756
-            $h = $font['Ascender'] - $font['Descender'];
4757
-        } else {
4758
-            $h = $font['FontBBox'][3] - $font['FontBBox'][1];
4759
-        }
4760
-
4761
-        // have to adjust by a font offset for Windows fonts.  unfortunately it looks like
4762
-        // the bounding box calculations are wrong and I don't know why.
4763
-        if (isset($font['FontHeightOffset'])) {
4764
-            // For CourierNew from Windows this needs to be -646 to match the
4765
-            // Adobe native Courier font.
4766
-            //
4767
-            // For FreeMono from GNU this needs to be -337 to match the
4768
-            // Courier font.
4769
-            //
4770
-            // Both have been added manually to the .afm and .ufm files.
4771
-            $h += (int)$font['FontHeightOffset'];
4772
-        }
4773
-
4774
-        return $size * $h / 1000;
4775
-    }
4776
-
4777
-    /**
4778
-     * @param float $size
4779
-     *
4780
-     * @return float
4781
-     */
4782
-    public function getFontXHeight(float $size): float
4783
-    {
4784
-        if (!$this->numFonts) {
4785
-            $this->selectFont($this->defaultFont);
4786
-        }
4787
-
4788
-        $font = $this->fonts[$this->currentFont];
4789
-
4790
-        // for the current font, and the given size, what is the height of the font in user units
4791
-        if (isset($font['XHeight'])) {
4792
-            $xh = $font['Ascender'] - $font['Descender'];
4793
-        } else {
4794
-            $xh = $this->getFontHeight($size) / 2;
4795
-        }
4796
-
4797
-        return $size * $xh / 1000;
4798
-    }
4799
-
4800
-    /**
4801
-     * return the font descender, this will normally return a negative number
4802
-     * if you add this number to the baseline, you get the level of the bottom of the font
4803
-     * it is in the pdf user units
4804
-     *
4805
-     * @param float $size
4806
-     *
4807
-     * @return float
4808
-     */
4809
-    public function getFontDescender(float $size): float
4810
-    {
4811
-        // note that this will most likely return a negative value
4812
-        if (!$this->numFonts) {
4813
-            $this->selectFont($this->defaultFont);
4814
-        }
4815
-
4816
-        //$h = $this->fonts[$this->currentFont]['FontBBox'][1];
4817
-        $h = $this->fonts[$this->currentFont]['Descender'];
4818
-
4819
-        return $size * $h / 1000;
4820
-    }
4821
-
4822
-    /**
4823
-     * filter the text, this is applied to all text just before being inserted into the pdf document
4824
-     * it escapes the various things that need to be escaped, and so on
4825
-     *
4826
-     * @param $text
4827
-     * @param bool $bom
4828
-     * @param bool $convert_encoding
4829
-     * @return string
4830
-     */
4831
-    function filterText($text, $bom = true, $convert_encoding = true)
4832
-    {
4833
-        if (!$this->numFonts) {
4834
-            $this->selectFont($this->defaultFont);
4835
-        }
4836
-
4837
-        if ($convert_encoding) {
4838
-            $cf = $this->currentFont;
4839
-            if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4840
-                $text = $this->utf8toUtf16BE($text, $bom);
4841
-            } else {
4842
-                //$text = html_entity_decode($text, ENT_QUOTES);
4843
-                $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4844
-            }
4845
-        } elseif ($bom) {
4846
-            $text = $this->utf8toUtf16BE($text, $bom);
4847
-        }
4848
-
4849
-        // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4850
-        return strtr($text, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
4851
-    }
4852
-
4853
-    /**
4854
-     * return array containing codepoints (UTF-8 character values) for the
4855
-     * string passed in.
4856
-     *
4857
-     * based on the excellent TCPDF code by Nicola Asuni and the
4858
-     * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4859
-     *
4860
-     * @param string $text UTF-8 string to process
4861
-     * @return array UTF-8 codepoints array for the string
4862
-     */
4863
-    function utf8toCodePointsArray(&$text)
4864
-    {
4865
-        $length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4866
-        $unicode = []; // array containing unicode values
4867
-        $bytes = []; // array containing single character byte sequences
4868
-        $numbytes = 1; // number of octets needed to represent the UTF-8 character
4869
-
4870
-        for ($i = 0; $i < $length; $i++) {
4871
-            $c = ord($text[$i]); // get one string character at time
4872
-            if (count($bytes) === 0) { // get starting octect
4873
-                if ($c <= 0x7F) {
4874
-                    $unicode[] = $c; // use the character "as is" because is ASCII
4875
-                    $numbytes = 1;
4876
-                } elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4877
-                    $bytes[] = ($c - 0xC0) << 0x06;
4878
-                    $numbytes = 2;
4879
-                } elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4880
-                    $bytes[] = ($c - 0xE0) << 0x0C;
4881
-                    $numbytes = 3;
4882
-                } elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4883
-                    $bytes[] = ($c - 0xF0) << 0x12;
4884
-                    $numbytes = 4;
4885
-                } else {
4886
-                    // use replacement character for other invalid sequences
4887
-                    $unicode[] = 0xFFFD;
4888
-                    $bytes = [];
4889
-                    $numbytes = 1;
4890
-                }
4891
-            } elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4892
-                $bytes[] = $c - 0x80;
4893
-                if (count($bytes) === $numbytes) {
4894
-                    // compose UTF-8 bytes to a single unicode value
4895
-                    $c = $bytes[0];
4896
-                    for ($j = 1; $j < $numbytes; $j++) {
4897
-                        $c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4898
-                    }
4899
-                    if ((($c >= 0xD800) and ($c <= 0xDFFF)) or ($c >= 0x10FFFF)) {
4900
-                        // The definition of UTF-8 prohibits encoding character numbers between
4901
-                        // U+D800 and U+DFFF, which are reserved for use with the UTF-16
4902
-                        // encoding form (as surrogate pairs) and do not directly represent
4903
-                        // characters.
4904
-                        $unicode[] = 0xFFFD; // use replacement character
4905
-                    } else {
4906
-                        $unicode[] = $c; // add char to array
4907
-                    }
4908
-                    // reset data for next char
4909
-                    $bytes = [];
4910
-                    $numbytes = 1;
4911
-                }
4912
-            } else {
4913
-                // use replacement character for other invalid sequences
4914
-                $unicode[] = 0xFFFD;
4915
-                $bytes = [];
4916
-                $numbytes = 1;
4917
-            }
4918
-        }
4919
-
4920
-        return $unicode;
4921
-    }
4922
-
4923
-    /**
4924
-     * convert UTF-8 to UTF-16 with an additional byte order marker
4925
-     * at the front if required.
4926
-     *
4927
-     * based on the excellent TCPDF code by Nicola Asuni and the
4928
-     * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4929
-     *
4930
-     * @param string  $text UTF-8 string to process
4931
-     * @param boolean $bom  whether to add the byte order marker
4932
-     * @return string UTF-16 result string
4933
-     */
4934
-    function utf8toUtf16BE(&$text, $bom = true)
4935
-    {
4936
-        $out = $bom ? "\xFE\xFF" : '';
4937
-
4938
-        $unicode = $this->utf8toCodePointsArray($text);
4939
-        foreach ($unicode as $c) {
4940
-            if ($c === 0xFFFD) {
4941
-                $out .= "\xFF\xFD"; // replacement character
4942
-            } elseif ($c < 0x10000) {
4943
-                $out .= chr($c >> 0x08) . chr($c & 0xFF);
4944
-            } else {
4945
-                $c -= 0x10000;
4946
-                $w1 = 0xD800 | ($c >> 0x10);
4947
-                $w2 = 0xDC00 | ($c & 0x3FF);
4948
-                $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4949
-            }
4950
-        }
4951
-
4952
-        return $out;
4953
-    }
4954
-
4955
-    /**
4956
-     * given a start position and information about how text is to be laid out, calculate where
4957
-     * on the page the text will end
4958
-     *
4959
-     * @param $x
4960
-     * @param $y
4961
-     * @param $angle
4962
-     * @param $size
4963
-     * @param $wa
4964
-     * @param $text
4965
-     * @return array
4966
-     */
4967
-    private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4968
-    {
4969
-        // given this information return an array containing x and y for the end position as elements 0 and 1
4970
-        $w = $this->getTextWidth($size, $text);
4971
-
4972
-        // need to adjust for the number of spaces in this text
4973
-        $words = explode(' ', $text);
4974
-        $nspaces = count($words) - 1;
4975
-        $w += $wa * $nspaces;
4976
-        $a = deg2rad((float)$angle);
4977
-
4978
-        return [cos($a) * $w + $x, -sin($a) * $w + $y];
4979
-    }
4980
-
4981
-    /**
4982
-     * Callback method used by smallCaps
4983
-     *
4984
-     * @param array $matches
4985
-     *
4986
-     * @return string
4987
-     */
4988
-    function toUpper($matches)
4989
-    {
4990
-        return mb_strtoupper($matches[0]);
4991
-    }
4992
-
4993
-    function concatMatches($matches)
4994
-    {
4995
-        $str = "";
4996
-        foreach ($matches as $match) {
4997
-            $str .= $match[0];
4998
-        }
4999
-
5000
-        return $str;
5001
-    }
5002
-
5003
-    /**
5004
-     * register text for font subsetting
5005
-     *
5006
-     * @param string $font
5007
-     * @param string $text
5008
-     */
5009
-    function registerText($font, $text)
5010
-    {
5011
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
5012
-            return;
5013
-        }
5014
-
5015
-        if (!isset($this->stringSubsets[$font])) {
5016
-            $base_subset = "\u{fffd}\u{fffe}\u{ffff}";
5017
-            $this->stringSubsets[$font] = $this->utf8toCodePointsArray($base_subset);
5018
-        }
5019
-
5020
-        $this->stringSubsets[$font] = array_unique(
5021
-            array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
5022
-        );
5023
-    }
5024
-
5025
-    /**
5026
-     * add text to the document, at a specified location, size and angle on the page
5027
-     *
5028
-     * @param float  $x
5029
-     * @param float  $y
5030
-     * @param float  $size
5031
-     * @param string $text
5032
-     * @param float  $angle
5033
-     * @param float  $wordSpaceAdjust
5034
-     * @param float  $charSpaceAdjust
5035
-     * @param bool   $smallCaps
5036
-     */
5037
-    function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
5038
-    {
5039
-        if (!$this->numFonts) {
5040
-            $this->selectFont($this->defaultFont);
5041
-        }
5042
-
5043
-        $text = str_replace(["\r", "\n"], "", $text);
5044
-
5045
-        // if ($smallCaps) {
5046
-        //     preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
5047
-        //     $lower = $this->concatMatches($matches);
5048
-        //     d($lower);
5049
-
5050
-        //     preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
5051
-        //     $other = $this->concatMatches($matches);
5052
-        //     d($other);
5053
-
5054
-        //     $text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
5055
-        // }
5056
-
5057
-        // if there are any open callbacks, then they should be called, to show the start of the line
5058
-        if ($this->nCallback > 0) {
5059
-            for ($i = $this->nCallback; $i > 0; $i--) {
5060
-                // call each function
5061
-                $info = [
5062
-                    'x'         => $x,
5063
-                    'y'         => $y,
5064
-                    'angle'     => $angle,
5065
-                    'status'    => 'sol',
5066
-                    'p'         => $this->callback[$i]['p'],
5067
-                    'nCallback' => $this->callback[$i]['nCallback'],
5068
-                    'height'    => $this->callback[$i]['height'],
5069
-                    'descender' => $this->callback[$i]['descender']
5070
-                ];
5071
-
5072
-                $func = $this->callback[$i]['f'];
5073
-                $this->$func($info);
5074
-            }
5075
-        }
5076
-
5077
-        if ($angle == 0) {
5078
-            $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
5079
-        } else {
5080
-            $a = deg2rad((float)$angle);
5081
-            $this->addContent(
5082
-                sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
5083
-            );
5084
-        }
5085
-
5086
-        if ($wordSpaceAdjust != 0) {
5087
-            $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
5088
-        }
5089
-
5090
-        if ($charSpaceAdjust != 0) {
5091
-            $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
5092
-        }
5093
-
5094
-        $len = mb_strlen($text);
5095
-        $start = 0;
5096
-
5097
-        if ($start < $len) {
5098
-            $part = $text; // OAR - Don't need this anymore, given that $start always equals zero.  substr($text, $start);
5099
-            $place_text = $this->filterText($part, false);
5100
-            // modify unicode text so that extra word spacing is manually implemented (bug #)
5101
-            if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
5102
-                $space_scale = 1000 / $size;
5103
-                $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
5104
-            }
5105
-            $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
5106
-            $this->addContent(" [($place_text)] TJ");
5107
-        }
5108
-
5109
-        if ($wordSpaceAdjust != 0) {
5110
-            $this->addContent(sprintf(" %.3F Tw", 0));
5111
-        }
5112
-
5113
-        if ($charSpaceAdjust != 0) {
5114
-            $this->addContent(sprintf(" %.3F Tc", 0));
5115
-        }
5116
-
5117
-        $this->addContent(' ET');
5118
-
5119
-        // if there are any open callbacks, then they should be called, to show the end of the line
5120
-        if ($this->nCallback > 0) {
5121
-            for ($i = $this->nCallback; $i > 0; $i--) {
5122
-                // call each function
5123
-                $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
5124
-                $info = [
5125
-                    'x'         => $tmp[0],
5126
-                    'y'         => $tmp[1],
5127
-                    'angle'     => $angle,
5128
-                    'status'    => 'eol',
5129
-                    'p'         => $this->callback[$i]['p'],
5130
-                    'nCallback' => $this->callback[$i]['nCallback'],
5131
-                    'height'    => $this->callback[$i]['height'],
5132
-                    'descender' => $this->callback[$i]['descender']
5133
-                ];
5134
-                $func = $this->callback[$i]['f'];
5135
-                $this->$func($info);
5136
-            }
5137
-        }
5138
-
5139
-        if ($this->fonts[$this->currentFont]['isSubsetting']) {
5140
-            $this->registerText($this->currentFont, $text);
5141
-        }
5142
-    }
5143
-
5144
-    /**
5145
-     * calculate how wide a given text string will be on a page, at a given size.
5146
-     * this can be called externally, but is also used by the other class functions
5147
-     *
5148
-     * @param float  $size
5149
-     * @param string $text
5150
-     * @param float  $wordSpacing
5151
-     * @param float  $charSpacing
5152
-     *
5153
-     * @return float
5154
-     */
5155
-    public function getTextWidth(float $size, string $text, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
5156
-    {
5157
-        static $ord_cache = [];
5158
-
5159
-        // this function should not change any of the settings, though it will need to
5160
-        // track any directives which change during calculation, so copy them at the start
5161
-        // and put them back at the end.
5162
-        $store_currentTextState = $this->currentTextState;
5163
-
5164
-        if (!$this->numFonts) {
5165
-            $this->selectFont($this->defaultFont);
5166
-        }
5167
-
5168
-        $text = str_replace(["\r", "\n"], "", $text);
5169
-
5170
-        // hmm, this is where it all starts to get tricky - use the font information to
5171
-        // calculate the width of each character, add them up and convert to user units
5172
-        $w = 0;
5173
-        $cf = $this->currentFont;
5174
-        $current_font = $this->fonts[$cf];
5175
-        $space_scale = 1000 / ($size > 0 ? $size : 1);
5176
-
5177
-        if ($current_font['isUnicode']) {
5178
-            // for Unicode, use the code points array to calculate width rather
5179
-            // than just the string itself
5180
-            $unicode = $this->utf8toCodePointsArray($text);
5181
-
5182
-            foreach ($unicode as $char) {
5183
-                // check if we have to replace character
5184
-                if (isset($current_font['differences'][$char])) {
5185
-                    $char = $current_font['differences'][$char];
5186
-                }
5187
-
5188
-                if (isset($current_font['C'][$char])) {
5189
-                    $char_width = $current_font['C'][$char];
5190
-
5191
-                    // add the character width
5192
-                    $w += $char_width;
5193
-
5194
-                    // add additional padding for space
5195
-                    if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5196
-                        $w += $wordSpacing * $space_scale;
5197
-                    }
5198
-                }
5199
-            }
5200
-
5201
-            // add additional char spacing
5202
-            if ($charSpacing != 0) {
5203
-                $w += $charSpacing * $space_scale * count($unicode);
5204
-            }
5205
-
5206
-        } else {
5207
-            // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
5208
-            if ($this->isUnicode) {
5209
-                $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
5210
-            }
5211
-
5212
-            $len = mb_strlen($text, 'Windows-1252');
5213
-
5214
-            for ($i = 0; $i < $len; $i++) {
5215
-                $c = $text[$i];
5216
-                $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
5217
-
5218
-                // check if we have to replace character
5219
-                if (isset($current_font['differences'][$char])) {
5220
-                    $char = $current_font['differences'][$char];
5221
-                }
5222
-
5223
-                if (isset($current_font['C'][$char])) {
5224
-                    $char_width = $current_font['C'][$char];
5225
-
5226
-                    // add the character width
5227
-                    $w += $char_width;
5228
-
5229
-                    // add additional padding for space
5230
-                    if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5231
-                        $w += $wordSpacing * $space_scale;
5232
-                    }
5233
-                }
5234
-            }
5235
-
5236
-            // add additional char spacing
5237
-            if ($charSpacing != 0) {
5238
-                $w += $charSpacing * $space_scale * $len;
5239
-            }
5240
-        }
5241
-
5242
-        $this->currentTextState = $store_currentTextState;
5243
-        $this->setCurrentFont();
5244
-
5245
-        return $w * $size / 1000;
5246
-    }
5247
-
5248
-    /**
5249
-     * this will be called at a new page to return the state to what it was on the
5250
-     * end of the previous page, before the stack was closed down
5251
-     * This is to get around not being able to have open 'q' across pages
5252
-     *
5253
-     * @param int $pageEnd
5254
-     */
5255
-    function saveState($pageEnd = 0)
5256
-    {
5257
-        if ($pageEnd) {
5258
-            // this will be called at a new page to return the state to what it was on the
5259
-            // end of the previous page, before the stack was closed down
5260
-            // This is to get around not being able to have open 'q' across pages
5261
-            $opt = $this->stateStack[$pageEnd];
5262
-            // ok to use this as stack starts numbering at 1
5263
-            $this->setColor($opt['col'], true);
5264
-            $this->setStrokeColor($opt['str'], true);
5265
-            $this->addContent("\n" . $opt['lin']);
5266
-            //    $this->currentLineStyle = $opt['lin'];
5267
-        } else {
5268
-            $this->nStateStack++;
5269
-            $this->stateStack[$this->nStateStack] = [
5270
-                'col' => $this->currentColor,
5271
-                'str' => $this->currentStrokeColor,
5272
-                'lin' => $this->currentLineStyle
5273
-            ];
5274
-        }
5275
-
5276
-        $this->save();
5277
-    }
5278
-
5279
-    /**
5280
-     * restore a previously saved state
5281
-     *
5282
-     * @param int $pageEnd
5283
-     */
5284
-    function restoreState($pageEnd = 0)
5285
-    {
5286
-        if (!$pageEnd) {
5287
-            $n = $this->nStateStack;
5288
-            $this->currentColor = $this->stateStack[$n]['col'];
5289
-            $this->currentStrokeColor = $this->stateStack[$n]['str'];
5290
-            $this->addContent("\n" . $this->stateStack[$n]['lin']);
5291
-            $this->currentLineStyle = $this->stateStack[$n]['lin'];
5292
-            $this->stateStack[$n] = null;
5293
-            unset($this->stateStack[$n]);
5294
-            $this->nStateStack--;
5295
-        }
5296
-
5297
-        $this->restore();
5298
-    }
5299
-
5300
-    /**
5301
-     * make a loose object, the output will go into this object, until it is closed, then will revert to
5302
-     * the current one.
5303
-     * this object will not appear until it is included within a page.
5304
-     * the function will return the object number
5305
-     *
5306
-     * @return int
5307
-     */
5308
-    function openObject()
5309
-    {
5310
-        $this->nStack++;
5311
-        $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5312
-        // add a new object of the content type, to hold the data flow
5313
-        $this->numObj++;
5314
-        $this->o_contents($this->numObj, 'new');
5315
-        $this->currentContents = $this->numObj;
5316
-        $this->looseObjects[$this->numObj] = 1;
5317
-
5318
-        return $this->numObj;
5319
-    }
5320
-
5321
-    /**
5322
-     * open an existing object for editing
5323
-     *
5324
-     * @param $id
5325
-     */
5326
-    function reopenObject($id)
5327
-    {
5328
-        $this->nStack++;
5329
-        $this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5330
-        $this->currentContents = $id;
5331
-
5332
-        // also if this object is the primary contents for a page, then set the current page to its parent
5333
-        if (isset($this->objects[$id]['onPage'])) {
5334
-            $this->currentPage = $this->objects[$id]['onPage'];
5335
-        }
5336
-    }
5337
-
5338
-    /**
5339
-     * close an object
5340
-     */
5341
-    function closeObject()
5342
-    {
5343
-        // close the object, as long as there was one open in the first place, which will be indicated by
5344
-        // an objectId on the stack.
5345
-        if ($this->nStack > 0) {
5346
-            $this->currentContents = $this->stack[$this->nStack]['c'];
5347
-            $this->currentPage = $this->stack[$this->nStack]['p'];
5348
-            $this->nStack--;
5349
-            // easier to probably not worry about removing the old entries, they will be overwritten
5350
-            // if there are new ones.
5351
-        }
5352
-    }
5353
-
5354
-    /**
5355
-     * stop an object from appearing on pages from this point on
5356
-     *
5357
-     * @param $id
5358
-     */
5359
-    function stopObject($id)
5360
-    {
5361
-        // if an object has been appearing on pages up to now, then stop it, this page will
5362
-        // be the last one that could contain it.
5363
-        if (isset($this->addLooseObjects[$id])) {
5364
-            $this->addLooseObjects[$id] = '';
5365
-        }
5366
-    }
5367
-
5368
-    /**
5369
-     * after an object has been created, it wil only show if it has been added, using this function.
5370
-     *
5371
-     * @param $id
5372
-     * @param string $options
5373
-     */
5374
-    function addObject($id, $options = 'add')
5375
-    {
5376
-        // add the specified object to the page
5377
-        if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
5378
-            // then it is a valid object, and it is not being added to itself
5379
-            switch ($options) {
5380
-                case 'all':
5381
-                    // then this object is to be added to this page (done in the next block) and
5382
-                    // all future new pages.
5383
-                    $this->addLooseObjects[$id] = 'all';
5384
-
5385
-                case 'add':
5386
-                    if (isset($this->objects[$this->currentContents]['onPage'])) {
5387
-                        // then the destination contents is the primary for the page
5388
-                        // (though this object is actually added to that page)
5389
-                        $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
5390
-                    }
5391
-                    break;
5392
-
5393
-                case 'even':
5394
-                    $this->addLooseObjects[$id] = 'even';
5395
-                    $pageObjectId = $this->objects[$this->currentContents]['onPage'];
5396
-                    if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
5397
-                        $this->addObject($id);
5398
-                        // hacky huh :)
5399
-                    }
5400
-                    break;
5401
-
5402
-                case 'odd':
5403
-                    $this->addLooseObjects[$id] = 'odd';
5404
-                    $pageObjectId = $this->objects[$this->currentContents]['onPage'];
5405
-                    if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
5406
-                        $this->addObject($id);
5407
-                        // hacky huh :)
5408
-                    }
5409
-                    break;
5410
-
5411
-                case 'next':
5412
-                    $this->addLooseObjects[$id] = 'all';
5413
-                    break;
5414
-
5415
-                case 'nexteven':
5416
-                    $this->addLooseObjects[$id] = 'even';
5417
-                    break;
5418
-
5419
-                case 'nextodd':
5420
-                    $this->addLooseObjects[$id] = 'odd';
5421
-                    break;
5422
-            }
5423
-        }
5424
-    }
5425
-
5426
-    /**
5427
-     * return a storable representation of a specific object
5428
-     *
5429
-     * @param $id
5430
-     * @return string|null
5431
-     */
5432
-    function serializeObject($id)
5433
-    {
5434
-        if (array_key_exists($id, $this->objects)) {
5435
-            return serialize($this->objects[$id]);
5436
-        }
5437
-
5438
-        return null;
5439
-    }
5440
-
5441
-    /**
5442
-     * restore an object from its stored representation. Returns its new object id.
5443
-     *
5444
-     * @param $obj
5445
-     * @return int
5446
-     */
5447
-    function restoreSerializedObject($obj)
5448
-    {
5449
-        $obj_id = $this->openObject();
5450
-        $this->objects[$obj_id] = unserialize($obj);
5451
-        $this->closeObject();
5452
-
5453
-        return $obj_id;
5454
-    }
5455
-
5456
-    /**
5457
-     * Embeds a file inside the PDF
5458
-     *
5459
-     * @param string $filepath path to the file to store inside the PDF
5460
-     * @param string $embeddedFilename the filename displayed in the list of embedded files
5461
-     * @param string $description a description in the list of embedded files
5462
-     */
5463
-    public function addEmbeddedFile(string $filepath, string $embeddedFilename, string $description): void
5464
-    {
5465
-        $this->numObj++;
5466
-        $this->o_embedded_file_dictionary(
5467
-            $this->numObj,
5468
-            'new',
5469
-            [
5470
-                'filepath' => $filepath,
5471
-                'filename' => $embeddedFilename,
5472
-                'description' => $description
5473
-            ]
5474
-        );
5475
-    }
5476
-
5477
-    /**
5478
-     * Add content to the documents info object
5479
-     *
5480
-     * @param string|array $label
5481
-     * @param string       $value
5482
-     */
5483
-    public function addInfo($label, string $value = ""): void
5484
-    {
5485
-        // this will only work if the label is one of the valid ones.
5486
-        // modify this so that arrays can be passed as well.
5487
-        // if $label is an array then assume that it is key => value pairs
5488
-        // else assume that they are both scalar, anything else will probably error
5489
-        if (is_array($label)) {
5490
-            foreach ($label as $l => $v) {
5491
-                $this->o_info($this->infoObject, $l, (string) $v);
5492
-            }
5493
-        } else {
5494
-            $this->o_info($this->infoObject, $label, $value);
5495
-        }
5496
-    }
5497
-
5498
-    /**
5499
-     * set the viewer preferences of the document, it is up to the browser to obey these.
5500
-     *
5501
-     * @param $label
5502
-     * @param int $value
5503
-     */
5504
-    function setPreferences($label, $value = 0)
5505
-    {
5506
-        // this will only work if the label is one of the valid ones.
5507
-        if (is_array($label)) {
5508
-            foreach ($label as $l => $v) {
5509
-                $this->o_catalog($this->catalogId, 'viewerPreferences', [$l => $v]);
5510
-            }
5511
-        } else {
5512
-            $this->o_catalog($this->catalogId, 'viewerPreferences', [$label => $value]);
5513
-        }
5514
-    }
5515
-
5516
-    /**
5517
-     * extract an integer from a position in a byte stream
5518
-     *
5519
-     * @param $data
5520
-     * @param $pos
5521
-     * @param $num
5522
-     * @return int
5523
-     */
5524
-    private function getBytes(&$data, $pos, $num)
5525
-    {
5526
-        // return the integer represented by $num bytes from $pos within $data
5527
-        $ret = 0;
5528
-        for ($i = 0; $i < $num; $i++) {
5529
-            $ret *= 256;
5530
-            $ret += ord($data[$pos + $i]);
5531
-        }
5532
-
5533
-        return $ret;
5534
-    }
5535
-
5536
-    /**
5537
-     * Check if image already added to pdf image directory.
5538
-     * If yes, need not to create again (pass empty data)
5539
-     *
5540
-     * @param string $imgname
5541
-     * @return bool
5542
-     */
5543
-    function image_iscached($imgname)
5544
-    {
5545
-        return isset($this->imagelist[$imgname]);
5546
-    }
5547
-
5548
-    /**
5549
-     * add a PNG image into the document, from a GD object
5550
-     * this should work with remote files
5551
-     *
5552
-     * @param \GdImage|resource $img A GD resource
5553
-     * @param string $file The PNG file
5554
-     * @param float $x X position
5555
-     * @param float $y Y position
5556
-     * @param float $w Width
5557
-     * @param float $h Height
5558
-     * @param bool $is_mask true if the image is a mask
5559
-     * @param bool $mask true if the image is masked
5560
-     * @throws Exception
5561
-     */
5562
-    function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5563
-    {
5564
-        if (!function_exists("imagepng")) {
5565
-            throw new \Exception("The PHP GD extension is required, but is not installed.");
5566
-        }
5567
-
5568
-        //if already cached, need not to read again
5569
-        if (isset($this->imagelist[$file])) {
5570
-            $data = null;
5571
-        } else {
5572
-            // Example for transparency handling on new image. Retain for current image
5573
-            // $tIndex = imagecolortransparent($img);
5574
-            // if ($tIndex > 0) {
5575
-            //   $tColor    = imagecolorsforindex($img, $tIndex);
5576
-            //   $new_tIndex    = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
5577
-            //   imagefill($new_img, 0, 0, $new_tIndex);
5578
-            //   imagecolortransparent($new_img, $new_tIndex);
5579
-            // }
5580
-            // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
5581
-            //imagealphablending($img, true);
5582
-
5583
-            //default, but explicitely set to ensure pdf compatibility
5584
-            imagesavealpha($img, false/*!$is_mask && !$mask*/);
5585
-
5586
-            $error = 0;
5587
-            //DEBUG_IMG_TEMP
5588
-            //debugpng
5589
-            if (defined("DEBUGPNG") && DEBUGPNG) {
5590
-                print '[addImagePng ' . $file . ']';
5591
-            }
5592
-
5593
-            ob_start();
5594
-            @imagepng($img);
5595
-            $data = ob_get_clean();
5596
-
5597
-            if ($data == '') {
5598
-                $error = 1;
5599
-                $errormsg = 'trouble writing file from GD';
5600
-                //DEBUG_IMG_TEMP
5601
-                //debugpng
5602
-                if (defined("DEBUGPNG") && DEBUGPNG) {
5603
-                    print 'trouble writing file from GD';
5604
-                }
5605
-            }
5606
-
5607
-            if ($error) {
5608
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5609
-
5610
-                return;
5611
-            }
5612
-        }  //End isset($this->imagelist[$file]) (png Duplicate removal)
5613
-
5614
-        $this->addPngFromBuf($data, $file, $x, $y, $w, $h, $is_mask, $mask);
5615
-    }
5616
-
5617
-    /**
5618
-     * @param $file
5619
-     * @param $x
5620
-     * @param $y
5621
-     * @param $w
5622
-     * @param $h
5623
-     * @param $byte
5624
-     */
5625
-    protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
5626
-    {
5627
-        // generate images
5628
-        $img = @imagecreatefrompng($file);
5629
-
5630
-        if ($img === false) {
5631
-            return;
5632
-        }
5633
-
5634
-        // FIXME The pixel transformation doesn't work well with 8bit PNGs
5635
-        $eight_bit = ($byte & 4) !== 4;
5636
-
5637
-        $wpx = imagesx($img);
5638
-        $hpx = imagesy($img);
5639
-
5640
-        imagesavealpha($img, false);
5641
-
5642
-        // create temp alpha file
5643
-        $tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
5644
-        @unlink($tempfile_alpha);
5645
-        $tempfile_alpha = "$tempfile_alpha.png";
5646
-
5647
-        // create temp plain file
5648
-        $tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
5649
-        @unlink($tempfile_plain);
5650
-        $tempfile_plain = "$tempfile_plain.png";
5651
-
5652
-        $imgalpha = imagecreate($wpx, $hpx);
5653
-        imagesavealpha($imgalpha, false);
5654
-
5655
-        // generate gray scale palette (0 -> 255)
5656
-        for ($c = 0; $c < 256; ++$c) {
5657
-            imagecolorallocate($imgalpha, $c, $c, $c);
5658
-        }
5659
-
5660
-        // Use PECL gmagick + Graphics Magic to process transparent PNG images
5661
-        if (extension_loaded("gmagick")) {
5662
-            $gmagick = new \Gmagick($file);
5663
-            $gmagick->setimageformat('png');
5664
-
5665
-            // Get opacity channel (negative of alpha channel)
5666
-            $alpha_channel_neg = clone $gmagick;
5667
-            $alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
5668
-
5669
-            // Negate opacity channel
5670
-            $alpha_channel = new \Gmagick();
5671
-            $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
5672
-            $alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
5673
-            $alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
5674
-            $alpha_channel->writeimage($tempfile_alpha);
5675
-
5676
-            // Cast to 8bit+palette
5677
-            $imgalpha_ = @imagecreatefrompng($tempfile_alpha);
5678
-            imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5679
-            imagedestroy($imgalpha_);
5680
-            imagepng($imgalpha, $tempfile_alpha);
5681
-
5682
-            // Make opaque image
5683
-            $color_channels = new \Gmagick();
5684
-            $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
5685
-            $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
5686
-            $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
5687
-            $color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
5688
-            $color_channels->writeimage($tempfile_plain);
5689
-
5690
-            $imgplain = @imagecreatefrompng($tempfile_plain);
5691
-        }
5692
-        // Use PECL imagick + ImageMagic to process transparent PNG images
5693
-        elseif (extension_loaded("imagick")) {
5694
-            // Native cloning was added to pecl-imagick in svn commit 263814
5695
-            // the first version containing it was 3.0.1RC1
5696
-            static $imagickClonable = null;
5697
-            if ($imagickClonable === null) {
5698
-                $imagickClonable = true;
5699
-                if (defined('Imagick::IMAGICK_EXTVER')) {
5700
-                    $imagickVersion = \Imagick::IMAGICK_EXTVER;
5701
-                } else {
5702
-                    $imagickVersion = '0';
5703
-                }
5704
-                if (version_compare($imagickVersion, '0.0.1', '>=')) {
5705
-                    $imagickClonable = version_compare($imagickVersion, '3.0.1rc1', '>=');
5706
-                }
5707
-            }
5708
-
5709
-            $imagick = new \Imagick($file);
5710
-            $imagick->setFormat('png');
5711
-
5712
-            // Get opacity channel (negative of alpha channel)
5713
-            if ($imagick->getImageAlphaChannel()) {
5714
-                $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
5715
-                $alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
5716
-                // Since ImageMagick7 negate invert transparency as default
5717
-                if (\Imagick::getVersion()['versionNumber'] < 1800) {
5718
-                    $alpha_channel->negateImage(true);
5719
-                }
5720
-                $alpha_channel->writeImage($tempfile_alpha);
5721
-
5722
-                // Cast to 8bit+palette
5723
-                $imgalpha_ = @imagecreatefrompng($tempfile_alpha);
5724
-                imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5725
-                imagedestroy($imgalpha_);
5726
-                imagepng($imgalpha, $tempfile_alpha);
5727
-            } else {
5728
-                $tempfile_alpha = null;
5729
-            }
5730
-
5731
-            // Make opaque image
5732
-            $color_channels = new \Imagick();
5733
-            $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
5734
-            $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
5735
-            $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
5736
-            $color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
5737
-            $color_channels->writeImage($tempfile_plain);
5738
-
5739
-            $imgplain = @imagecreatefrompng($tempfile_plain);
5740
-        } else {
5741
-            // allocated colors cache
5742
-            $allocated_colors = [];
5743
-
5744
-            // extract alpha channel
5745
-            for ($xpx = 0; $xpx < $wpx; ++$xpx) {
5746
-                for ($ypx = 0; $ypx < $hpx; ++$ypx) {
5747
-                    $color = imagecolorat($img, $xpx, $ypx);
5748
-                    $col = imagecolorsforindex($img, $color);
5749
-                    $alpha = $col['alpha'];
5750
-
5751
-                    if ($eight_bit) {
5752
-                        // with gamma correction
5753
-                        $gammacorr = 2.2;
5754
-                        $pixel = round(pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255);
5755
-                    } else {
5756
-                        // without gamma correction
5757
-                        $pixel = (127 - $alpha) * 2;
5758
-
5759
-                        $key = $col['red'] . $col['green'] . $col['blue'];
5760
-
5761
-                        if (!isset($allocated_colors[$key])) {
5762
-                            $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
5763
-                            $allocated_colors[$key] = $pixel_img;
5764
-                        } else {
5765
-                            $pixel_img = $allocated_colors[$key];
5766
-                        }
5767
-
5768
-                        imagesetpixel($img, $xpx, $ypx, $pixel_img);
5769
-                    }
5770
-
5771
-                    imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
5772
-                }
5773
-            }
5774
-
5775
-            // extract image without alpha channel
5776
-            $imgplain = imagecreatetruecolor($wpx, $hpx);
5777
-            imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
5778
-            imagedestroy($img);
5779
-
5780
-            imagepng($imgalpha, $tempfile_alpha);
5781
-            imagepng($imgplain, $tempfile_plain);
5782
-        }
5783
-
5784
-        $this->imageAlphaList[$file] = [$tempfile_alpha, $tempfile_plain];
5785
-
5786
-        // embed mask image
5787
-        if ($tempfile_alpha) {
5788
-            $this->addImagePng($imgalpha, $tempfile_alpha, $x, $y, $w, $h, true);
5789
-            imagedestroy($imgalpha);
5790
-            $this->imageCache[] = $tempfile_alpha;
5791
-        }
5792
-
5793
-        // embed image, masked with previously embedded mask
5794
-        $this->addImagePng($imgplain, $tempfile_plain, $x, $y, $w, $h, false, ($tempfile_alpha !== null));
5795
-        imagedestroy($imgplain);
5796
-        $this->imageCache[] = $tempfile_plain;
5797
-    }
5798
-
5799
-    /**
5800
-     * add a PNG image into the document, from a file
5801
-     * this should work with remote files
5802
-     *
5803
-     * @param $file
5804
-     * @param $x
5805
-     * @param $y
5806
-     * @param int $w
5807
-     * @param int $h
5808
-     * @throws Exception
5809
-     */
5810
-    function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
5811
-    {
5812
-        if (!function_exists("imagecreatefrompng")) {
5813
-            throw new \Exception("The PHP GD extension is required, but is not installed.");
5814
-        }
5815
-
5816
-        if (isset($this->imageAlphaList[$file])) {
5817
-            [$alphaFile, $plainFile] = $this->imageAlphaList[$file];
5818
-
5819
-            if ($alphaFile) {
5820
-                $img = null;
5821
-                $this->addImagePng($img, $alphaFile, $x, $y, $w, $h, true);
5822
-            }
5823
-
5824
-            $img = null;
5825
-            $this->addImagePng($img, $plainFile, $x, $y, $w, $h, false, ($plainFile !== null));
5826
-            return;
5827
-        }
5828
-
5829
-        //if already cached, need not to read again
5830
-        if (isset($this->imagelist[$file])) {
5831
-            $img = null;
5832
-        } else {
5833
-            $info = file_get_contents($file, false, null, 24, 5);
5834
-            $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
5835
-            $bit_depth = $meta["bitDepth"];
5836
-            $color_type = $meta["colorType"];
5837
-
5838
-            // http://www.w3.org/TR/PNG/#11IHDR
5839
-            // 3 => indexed
5840
-            // 4 => greyscale with alpha
5841
-            // 6 => fullcolor with alpha
5842
-            $is_alpha = in_array($color_type, [4, 6]) || ($color_type == 3 && $bit_depth != 4);
5843
-
5844
-            if ($is_alpha) { // exclude grayscale alpha
5845
-                $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
5846
-                return;
5847
-            }
5848
-
5849
-            //png files typically contain an alpha channel.
5850
-            //pdf file format or class.pdf does not support alpha blending.
5851
-            //on alpha blended images, more transparent areas have a color near black.
5852
-            //This appears in the result on not storing the alpha channel.
5853
-            //Correct would be the box background image or its parent when transparent.
5854
-            //But this would make the image dependent on the background.
5855
-            //Therefore create an image with white background and copy in
5856
-            //A more natural background than black is white.
5857
-            //Therefore create an empty image with white background and merge the
5858
-            //image in with alpha blending.
5859
-            $imgtmp = @imagecreatefrompng($file);
5860
-            if (!$imgtmp) {
5861
-                return;
5862
-            }
5863
-            $sx = imagesx($imgtmp);
5864
-            $sy = imagesy($imgtmp);
5865
-            $img = imagecreatetruecolor($sx, $sy);
5866
-            imagealphablending($img, true);
5867
-
5868
-            // @todo is it still needed ??
5869
-            $ti = imagecolortransparent($imgtmp);
5870
-            if ($ti >= 0) {
5871
-                $tc = imagecolorsforindex($imgtmp, $ti);
5872
-                $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5873
-                imagefill($img, 0, 0, $ti);
5874
-                imagecolortransparent($img, $ti);
5875
-            } else {
5876
-                imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5877
-            }
5878
-
5879
-            imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5880
-            imagedestroy($imgtmp);
5881
-        }
5882
-        $this->addImagePng($img, $file, $x, $y, $w, $h);
5883
-
5884
-        if ($img) {
5885
-            imagedestroy($img);
5886
-        }
5887
-    }
5888
-
5889
-    /**
5890
-     * add a PNG image into the document, from a file
5891
-     * this should work with remote files
5892
-     *
5893
-     * @param $file
5894
-     * @param $x
5895
-     * @param $y
5896
-     * @param int $w
5897
-     * @param int $h
5898
-     */
5899
-    function addSvgFromFile($file, $x, $y, $w = 0, $h = 0)
5900
-    {
5901
-        $doc = new \Svg\Document();
5902
-        $doc->loadFile($file);
5903
-        $dimensions = $doc->getDimensions();
5904
-
5905
-        $this->save();
5906
-
5907
-        $this->transform([$w / $dimensions["width"], 0, 0, $h / $dimensions["height"], $x, $y]);
5908
-
5909
-        $surface = new \Svg\Surface\SurfaceCpdf($doc, $this);
5910
-        $doc->render($surface);
5911
-
5912
-        $this->restore();
5913
-    }
5914
-
5915
-    /**
5916
-     * add a PNG image into the document, from a memory buffer of the file
5917
-     *
5918
-     * @param $data
5919
-     * @param $file
5920
-     * @param $x
5921
-     * @param $y
5922
-     * @param float $w
5923
-     * @param float $h
5924
-     * @param bool $is_mask
5925
-     * @param null $mask
5926
-     */
5927
-    function addPngFromBuf(&$data, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5928
-    {
5929
-        if (isset($this->imagelist[$file])) {
5930
-            $data = null;
5931
-            $info['width'] = $this->imagelist[$file]['w'];
5932
-            $info['height'] = $this->imagelist[$file]['h'];
5933
-            $label = $this->imagelist[$file]['label'];
5934
-        } else {
5935
-            if ($data == null) {
5936
-                $this->addMessage('addPngFromBuf error - data not present!');
5937
-
5938
-                return;
5939
-            }
5940
-
5941
-            $error = 0;
5942
-
5943
-            if (!$error) {
5944
-                $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5945
-
5946
-                if (mb_substr($data, 0, 8, '8bit') != $header) {
5947
-                    $error = 1;
5948
-
5949
-                    if (defined("DEBUGPNG") && DEBUGPNG) {
5950
-                        print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5951
-                    }
5952
-
5953
-                    $errormsg = 'this file does not have a valid header';
5954
-                }
5955
-            }
5956
-
5957
-            if (!$error) {
5958
-                // set pointer
5959
-                $p = 8;
5960
-                $len = mb_strlen($data, '8bit');
5961
-
5962
-                // cycle through the file, identifying chunks
5963
-                $haveHeader = 0;
5964
-                $info = [];
5965
-                $idata = '';
5966
-                $pdata = '';
5967
-
5968
-                while ($p < $len) {
5969
-                    $chunkLen = $this->getBytes($data, $p, 4);
5970
-                    $chunkType = mb_substr($data, $p + 4, 4, '8bit');
5971
-
5972
-                    switch ($chunkType) {
5973
-                        case 'IHDR':
5974
-                            // this is where all the file information comes from
5975
-                            $info['width'] = $this->getBytes($data, $p + 8, 4);
5976
-                            $info['height'] = $this->getBytes($data, $p + 12, 4);
5977
-                            $info['bitDepth'] = ord($data[$p + 16]);
5978
-                            $info['colorType'] = ord($data[$p + 17]);
5979
-                            $info['compressionMethod'] = ord($data[$p + 18]);
5980
-                            $info['filterMethod'] = ord($data[$p + 19]);
5981
-                            $info['interlaceMethod'] = ord($data[$p + 20]);
5982
-
5983
-                            //print_r($info);
5984
-                            $haveHeader = 1;
5985
-                            if ($info['compressionMethod'] != 0) {
5986
-                                $error = 1;
5987
-
5988
-                                //debugpng
5989
-                                if (defined("DEBUGPNG") && DEBUGPNG) {
5990
-                                    print '[addPngFromFile unsupported compression method ' . $file . ']';
5991
-                                }
5992
-
5993
-                                $errormsg = 'unsupported compression method';
5994
-                            }
5995
-
5996
-                            if ($info['filterMethod'] != 0) {
5997
-                                $error = 1;
5998
-
5999
-                                //debugpng
6000
-                                if (defined("DEBUGPNG") && DEBUGPNG) {
6001
-                                    print '[addPngFromFile unsupported filter method ' . $file . ']';
6002
-                                }
6003
-
6004
-                                $errormsg = 'unsupported filter method';
6005
-                            }
6006
-                            break;
6007
-
6008
-                        case 'PLTE':
6009
-                            $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
6010
-                            break;
6011
-
6012
-                        case 'IDAT':
6013
-                            $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
6014
-                            break;
6015
-
6016
-                        case 'tRNS':
6017
-                            //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
6018
-                            //print "tRNS found, color type = ".$info['colorType']."\n";
6019
-                            $transparency = [];
6020
-
6021
-                            switch ($info['colorType']) {
6022
-                                // indexed color, rbg
6023
-                                case 3:
6024
-                                    /* corresponding to entries in the plte chunk
3492
+					}
3493
+				}
3494
+			}
3495
+
3496
+			if ($this->compressionReady && $this->options['compression']) {
3497
+				// then implement ZLIB based compression on CIDtoGID string
3498
+				$data['CIDtoGID_Compressed'] = true;
3499
+				$cidtogid = gzcompress($cidtogid, 6);
3500
+			}
3501
+			$data['CIDtoGID'] = base64_encode($cidtogid);
3502
+			$data['_version_'] = $this->fontcacheVersion;
3503
+			$this->fonts[$font] = $data;
3504
+
3505
+			//Because of potential trouble with php safe mode, expect that the folder already exists.
3506
+			//If not existing, this will hit performance because of missing cached results.
3507
+			if (is_dir($fontcache) && is_writable($fontcache)) {
3508
+				file_put_contents("$fontcache/$cache_name", json_encode($data, JSON_PRETTY_PRINT));
3509
+			}
3510
+			$data = null;
3511
+		}
3512
+
3513
+		if (!isset($this->fonts[$font])) {
3514
+			$this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3515
+		}
3516
+	}
3517
+
3518
+	/**
3519
+	 * if the font is not loaded then load it and make the required object
3520
+	 * else just make it the current font
3521
+	 * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding'
3522
+	 * note that encoding='none' will need to be used for symbolic fonts
3523
+	 * and 'differences' => an array of mappings between numbers 0->255 and character names.
3524
+	 *
3525
+	 * @param string $fontName
3526
+	 * @param string $encoding
3527
+	 * @param bool $set
3528
+	 * @param bool $isSubsetting
3529
+	 * @return int
3530
+	 * @throws FontNotFoundException
3531
+	 */
3532
+	function selectFont($fontName, $encoding = '', $set = true, $isSubsetting = true)
3533
+	{
3534
+		if ($fontName === null || $fontName === '') {
3535
+			return $this->currentFontNum;
3536
+		}
3537
+
3538
+		$ext = substr($fontName, -4);
3539
+		if ($ext === '.afm' || $ext === '.ufm') {
3540
+			$fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
3541
+		}
3542
+
3543
+		if (!isset($this->fonts[$fontName])) {
3544
+			$this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
3545
+
3546
+			// load the file
3547
+			$this->openFont($fontName);
3548
+
3549
+			if (isset($this->fonts[$fontName])) {
3550
+				$this->numObj++;
3551
+				$this->numFonts++;
3552
+
3553
+				$font = &$this->fonts[$fontName];
3554
+
3555
+				$name = basename($fontName);
3556
+				$options = ['name' => $name, 'fontFileName' => $fontName, 'isSubsetting' => $isSubsetting];
3557
+
3558
+				if (is_array($encoding)) {
3559
+					// then encoding and differences might be set
3560
+					if (isset($encoding['encoding'])) {
3561
+						$options['encoding'] = $encoding['encoding'];
3562
+					}
3563
+
3564
+					if (isset($encoding['differences'])) {
3565
+						$options['differences'] = $encoding['differences'];
3566
+					}
3567
+				} else {
3568
+					if (mb_strlen($encoding, '8bit')) {
3569
+						// then perhaps only the encoding has been set
3570
+						$options['encoding'] = $encoding;
3571
+					}
3572
+				}
3573
+
3574
+				$this->o_font($this->numObj, 'new', $options);
3575
+
3576
+				if (file_exists("$fontName.ttf")) {
3577
+					$fileSuffix = 'ttf';
3578
+				} elseif (file_exists("$fontName.TTF")) {
3579
+					$fileSuffix = 'TTF';
3580
+				} elseif (file_exists("$fontName.pfb")) {
3581
+					$fileSuffix = 'pfb';
3582
+				} elseif (file_exists("$fontName.PFB")) {
3583
+					$fileSuffix = 'PFB';
3584
+				} else {
3585
+					$fileSuffix = '';
3586
+				}
3587
+
3588
+				$font['fileSuffix'] = $fileSuffix;
3589
+
3590
+				$font['fontNum'] = $this->numFonts;
3591
+				$font['isSubsetting'] = $isSubsetting && $font['isUnicode'] && strtolower($fileSuffix) === 'ttf';
3592
+
3593
+				// also set the differences here, note that this means that these will take effect only the
3594
+				//first time that a font is selected, else they are ignored
3595
+				if (isset($options['differences'])) {
3596
+					$font['differences'] = $options['differences'];
3597
+				}
3598
+			}
3599
+		}
3600
+
3601
+		if ($set && isset($this->fonts[$fontName])) {
3602
+			// so if for some reason the font was not set in the last one then it will not be selected
3603
+			$this->currentBaseFont = $fontName;
3604
+
3605
+			// the next lines mean that if a new font is selected, then the current text state will be
3606
+			// applied to it as well.
3607
+			$this->currentFont = $this->currentBaseFont;
3608
+			$this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3609
+		}
3610
+
3611
+		return $this->currentFontNum;
3612
+	}
3613
+
3614
+	/**
3615
+	 * sets up the current font, based on the font families, and the current text state
3616
+	 * note that this system is quite flexible, a bold-italic font can be completely different to a
3617
+	 * italic-bold font, and even bold-bold will have to be defined within the family to have meaning
3618
+	 * This function is to be called whenever the currentTextState is changed, it will update
3619
+	 * the currentFont setting to whatever the appropriate family one is.
3620
+	 * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont
3621
+	 * This function will change the currentFont to whatever it should be, but will not change the
3622
+	 * currentBaseFont.
3623
+	 */
3624
+	private function setCurrentFont()
3625
+	{
3626
+		//   if (strlen($this->currentBaseFont) == 0){
3627
+		//     // then assume an initial font
3628
+		//     $this->selectFont($this->defaultFont);
3629
+		//   }
3630
+		//   $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1);
3631
+		//   if (strlen($this->currentTextState)
3632
+		//     && isset($this->fontFamilies[$cf])
3633
+		//       && isset($this->fontFamilies[$cf][$this->currentTextState])){
3634
+		//     // then we are in some state or another
3635
+		//     // and this font has a family, and the current setting exists within it
3636
+		//     // select the font, then return it
3637
+		//     $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState];
3638
+		//     $this->selectFont($nf,'',0);
3639
+		//     $this->currentFont = $nf;
3640
+		//     $this->currentFontNum = $this->fonts[$nf]['fontNum'];
3641
+		//   } else {
3642
+		//     // the this font must not have the right family member for the current state
3643
+		//     // simply assume the base font
3644
+		$this->currentFont = $this->currentBaseFont;
3645
+		$this->currentFontNum = $this->fonts[$this->currentFont]['fontNum'];
3646
+		//  }
3647
+	}
3648
+
3649
+	/**
3650
+	 * function for the user to find out what the ID is of the first page that was created during
3651
+	 * startup - useful if they wish to add something to it later.
3652
+	 *
3653
+	 * @return int
3654
+	 */
3655
+	function getFirstPageId()
3656
+	{
3657
+		return $this->firstPageId;
3658
+	}
3659
+
3660
+	/**
3661
+	 * add content to the currently active object
3662
+	 *
3663
+	 * @param $content
3664
+	 */
3665
+	private function addContent($content)
3666
+	{
3667
+		$this->objects[$this->currentContents]['c'] .= $content;
3668
+	}
3669
+
3670
+	/**
3671
+	 * sets the color for fill operations
3672
+	 *
3673
+	 * @param array $color
3674
+	 * @param bool  $force
3675
+	 */
3676
+	function setColor($color, $force = false)
3677
+	{
3678
+		$new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3679
+
3680
+		if (!$force && $this->currentColor == $new_color) {
3681
+			return;
3682
+		}
3683
+
3684
+		if (isset($new_color[3])) {
3685
+			$this->currentColor = $new_color;
3686
+			$this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor));
3687
+		} else {
3688
+			if (isset($new_color[2])) {
3689
+				$this->currentColor = $new_color;
3690
+				$this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $this->currentColor));
3691
+			}
3692
+		}
3693
+	}
3694
+
3695
+	/**
3696
+	 * @param string $fillRule
3697
+	 */
3698
+	function setFillRule($fillRule)
3699
+	{
3700
+		if (!in_array($fillRule, ["nonzero", "evenodd"])) {
3701
+			return;
3702
+		}
3703
+
3704
+		$this->fillRule = $fillRule;
3705
+	}
3706
+
3707
+	/**
3708
+	 * sets the color for stroke operations
3709
+	 *
3710
+	 * @param array $color
3711
+	 * @param bool  $force
3712
+	 */
3713
+	function setStrokeColor($color, $force = false)
3714
+	{
3715
+		$new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3716
+
3717
+		if (!$force && $this->currentStrokeColor == $new_color) {
3718
+			return;
3719
+		}
3720
+
3721
+		if (isset($new_color[3])) {
3722
+			$this->currentStrokeColor = $new_color;
3723
+			$this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor));
3724
+		} else {
3725
+			if (isset($new_color[2])) {
3726
+				$this->currentStrokeColor = $new_color;
3727
+				$this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $this->currentStrokeColor));
3728
+			}
3729
+		}
3730
+	}
3731
+
3732
+	/**
3733
+	 * Set the graphics state for compositions
3734
+	 *
3735
+	 * @param $parameters
3736
+	 */
3737
+	function setGraphicsState($parameters)
3738
+	{
3739
+		// Create a new graphics state object if necessary
3740
+		if (($gstate = array_search($parameters, $this->gstates)) === false) {
3741
+			$this->numObj++;
3742
+			$this->o_extGState($this->numObj, 'new', $parameters);
3743
+			$gstate = $this->numStates;
3744
+			$this->gstates[$gstate] = $parameters;
3745
+		}
3746
+		$this->addContent("\n/GS$gstate gs");
3747
+	}
3748
+
3749
+	/**
3750
+	 * Set current blend mode & opacity for lines.
3751
+	 *
3752
+	 * Valid blend modes are:
3753
+	 *
3754
+	 * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3755
+	 * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3756
+	 * Exclusion
3757
+	 *
3758
+	 * @param string $mode    the blend mode to use
3759
+	 * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3760
+	 */
3761
+	function setLineTransparency($mode, $opacity)
3762
+	{
3763
+		static $blend_modes = [
3764
+			"Normal",
3765
+			"Multiply",
3766
+			"Screen",
3767
+			"Overlay",
3768
+			"Darken",
3769
+			"Lighten",
3770
+			"ColorDogde",
3771
+			"ColorBurn",
3772
+			"HardLight",
3773
+			"SoftLight",
3774
+			"Difference",
3775
+			"Exclusion"
3776
+		];
3777
+
3778
+		if (!in_array($mode, $blend_modes)) {
3779
+			$mode = "Normal";
3780
+		}
3781
+
3782
+		if (is_null($this->currentLineTransparency)) {
3783
+			$this->currentLineTransparency = [];
3784
+		}
3785
+
3786
+		if ($mode === (key_exists('mode', $this->currentLineTransparency) ?
3787
+			$this->currentLineTransparency['mode'] : '') &&
3788
+			$opacity === (key_exists('opacity', $this->currentLineTransparency) ?
3789
+			$this->currentLineTransparency["opacity"] : '')) {
3790
+			return;
3791
+		}
3792
+
3793
+		$this->currentLineTransparency["mode"] = $mode;
3794
+		$this->currentLineTransparency["opacity"] = $opacity;
3795
+
3796
+		$options = [
3797
+			"BM" => "/$mode",
3798
+			"CA" => (float)$opacity
3799
+		];
3800
+
3801
+		$this->setGraphicsState($options);
3802
+	}
3803
+
3804
+	/**
3805
+	 * Set current blend mode & opacity for filled objects.
3806
+	 *
3807
+	 * Valid blend modes are:
3808
+	 *
3809
+	 * Normal, Multiply, Screen, Overlay, Darken, Lighten,
3810
+	 * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
3811
+	 * Exclusion
3812
+	 *
3813
+	 * @param string $mode    the blend mode to use
3814
+	 * @param float  $opacity 0.0 fully transparent, 1.0 fully opaque
3815
+	 */
3816
+	function setFillTransparency($mode, $opacity)
3817
+	{
3818
+		static $blend_modes = [
3819
+			"Normal",
3820
+			"Multiply",
3821
+			"Screen",
3822
+			"Overlay",
3823
+			"Darken",
3824
+			"Lighten",
3825
+			"ColorDogde",
3826
+			"ColorBurn",
3827
+			"HardLight",
3828
+			"SoftLight",
3829
+			"Difference",
3830
+			"Exclusion"
3831
+		];
3832
+
3833
+		if (!in_array($mode, $blend_modes)) {
3834
+			$mode = "Normal";
3835
+		}
3836
+
3837
+		if (is_null($this->currentFillTransparency)) {
3838
+			$this->currentFillTransparency = [];
3839
+		}
3840
+
3841
+		if ($mode === (key_exists('mode', $this->currentFillTransparency) ?
3842
+			$this->currentFillTransparency['mode'] : '') &&
3843
+			$opacity === (key_exists('opacity', $this->currentFillTransparency) ?
3844
+			$this->currentFillTransparency["opacity"] : '')) {
3845
+			return;
3846
+		}
3847
+
3848
+		$this->currentFillTransparency["mode"] = $mode;
3849
+		$this->currentFillTransparency["opacity"] = $opacity;
3850
+
3851
+		$options = [
3852
+			"BM" => "/$mode",
3853
+			"ca" => (float)$opacity,
3854
+		];
3855
+
3856
+		$this->setGraphicsState($options);
3857
+	}
3858
+
3859
+	/**
3860
+	 * draw a line from one set of coordinates to another
3861
+	 *
3862
+	 * @param float $x1
3863
+	 * @param float $y1
3864
+	 * @param float $x2
3865
+	 * @param float $y2
3866
+	 * @param bool  $stroke
3867
+	 */
3868
+	function line($x1, $y1, $x2, $y2, $stroke = true)
3869
+	{
3870
+		$this->addContent(sprintf("\n%.3F %.3F m %.3F %.3F l", $x1, $y1, $x2, $y2));
3871
+
3872
+		if ($stroke) {
3873
+			$this->addContent(' S');
3874
+		}
3875
+	}
3876
+
3877
+	/**
3878
+	 * draw a bezier curve based on 4 control points
3879
+	 *
3880
+	 * @param float $x0
3881
+	 * @param float $y0
3882
+	 * @param float $x1
3883
+	 * @param float $y1
3884
+	 * @param float $x2
3885
+	 * @param float $y2
3886
+	 * @param float $x3
3887
+	 * @param float $y3
3888
+	 */
3889
+	function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3890
+	{
3891
+		// in the current line style, draw a bezier curve from (x0,y0) to (x3,y3) using the other two points
3892
+		// as the control points for the curve.
3893
+		$this->addContent(
3894
+			sprintf("\n%.3F %.3F m %.3F %.3F %.3F %.3F %.3F %.3F c S", $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3895
+		);
3896
+	}
3897
+
3898
+	/**
3899
+	 * draw a part of an ellipse
3900
+	 *
3901
+	 * @param float $x0
3902
+	 * @param float $y0
3903
+	 * @param float $astart
3904
+	 * @param float $afinish
3905
+	 * @param float $r1
3906
+	 * @param float $r2
3907
+	 * @param float $angle
3908
+	 * @param int $nSeg
3909
+	 */
3910
+	function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3911
+	{
3912
+		$this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, false);
3913
+	}
3914
+
3915
+	/**
3916
+	 * draw a filled ellipse
3917
+	 *
3918
+	 * @param float $x0
3919
+	 * @param float $y0
3920
+	 * @param float $r1
3921
+	 * @param float $r2
3922
+	 * @param float $angle
3923
+	 * @param int $nSeg
3924
+	 * @param float $astart
3925
+	 * @param float $afinish
3926
+	 */
3927
+	function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3928
+	{
3929
+		$this->ellipse($x0, $y0, $r1, $r2, $angle, $nSeg, $astart, $afinish, true, true);
3930
+	}
3931
+
3932
+	/**
3933
+	 * @param float $x
3934
+	 * @param float $y
3935
+	 */
3936
+	function lineTo($x, $y)
3937
+	{
3938
+		$this->addContent(sprintf("\n%.3F %.3F l", $x, $y));
3939
+	}
3940
+
3941
+	/**
3942
+	 * @param float $x
3943
+	 * @param float $y
3944
+	 */
3945
+	function moveTo($x, $y)
3946
+	{
3947
+		$this->addContent(sprintf("\n%.3F %.3F m", $x, $y));
3948
+	}
3949
+
3950
+	/**
3951
+	 * draw a bezier curve based on 4 control points
3952
+	 *
3953
+	 * @param float $x1
3954
+	 * @param float $y1
3955
+	 * @param float $x2
3956
+	 * @param float $y2
3957
+	 * @param float $x3
3958
+	 * @param float $y3
3959
+	 */
3960
+	function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3961
+	{
3962
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3));
3963
+	}
3964
+
3965
+	/**
3966
+	 * draw a bezier curve based on 4 control points
3967
+	 *
3968
+	 * @param float $cpx
3969
+	 * @param float $cpy
3970
+	 * @param float $x
3971
+	 * @param float $y
3972
+	 */
3973
+	function quadTo($cpx, $cpy, $x, $y)
3974
+	{
3975
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y));
3976
+	}
3977
+
3978
+	function closePath()
3979
+	{
3980
+		$this->addContent(' h');
3981
+	}
3982
+
3983
+	function endPath()
3984
+	{
3985
+		$this->addContent(' n');
3986
+	}
3987
+
3988
+	/**
3989
+	 * draw an ellipse
3990
+	 * note that the part and filled ellipse are just special cases of this function
3991
+	 *
3992
+	 * draws an ellipse in the current line style
3993
+	 * centered at $x0,$y0, radii $r1,$r2
3994
+	 * if $r2 is not set, then a circle is drawn
3995
+	 * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse.
3996
+	 * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
3997
+	 * pretty crappy shape at 2, as we are approximating with bezier curves.
3998
+	 *
3999
+	 * @param float $x0
4000
+	 * @param float $y0
4001
+	 * @param float $r1
4002
+	 * @param float $r2
4003
+	 * @param float $angle
4004
+	 * @param int   $nSeg
4005
+	 * @param float $astart
4006
+	 * @param float $afinish
4007
+	 * @param bool  $close
4008
+	 * @param bool  $fill
4009
+	 * @param bool  $stroke
4010
+	 * @param bool  $incomplete
4011
+	 */
4012
+	function ellipse(
4013
+		$x0,
4014
+		$y0,
4015
+		$r1,
4016
+		$r2 = 0,
4017
+		$angle = 0,
4018
+		$nSeg = 8,
4019
+		$astart = 0,
4020
+		$afinish = 360,
4021
+		$close = true,
4022
+		$fill = false,
4023
+		$stroke = true,
4024
+		$incomplete = false
4025
+	) {
4026
+		if ($r1 == 0) {
4027
+			return;
4028
+		}
4029
+
4030
+		if ($r2 == 0) {
4031
+			$r2 = $r1;
4032
+		}
4033
+
4034
+		if ($nSeg < 2) {
4035
+			$nSeg = 2;
4036
+		}
4037
+
4038
+		$astart = deg2rad((float)$astart);
4039
+		$afinish = deg2rad((float)$afinish);
4040
+		$totalAngle = $afinish - $astart;
4041
+
4042
+		$dt = $totalAngle / $nSeg;
4043
+		$dtm = $dt / 3;
4044
+
4045
+		if ($angle != 0) {
4046
+			$a = -1 * deg2rad((float)$angle);
4047
+
4048
+			$this->addContent(
4049
+				sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
4050
+			);
4051
+
4052
+			$x0 = 0;
4053
+			$y0 = 0;
4054
+		}
4055
+
4056
+		$t1 = $astart;
4057
+		$a0 = $x0 + $r1 * cos($t1);
4058
+		$b0 = $y0 + $r2 * sin($t1);
4059
+		$c0 = -$r1 * sin($t1);
4060
+		$d0 = $r2 * cos($t1);
4061
+
4062
+		if (!$incomplete) {
4063
+			$this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
4064
+		}
4065
+
4066
+		for ($i = 1; $i <= $nSeg; $i++) {
4067
+			// draw this bit of the total curve
4068
+			$t1 = $i * $dt + $astart;
4069
+			$a1 = $x0 + $r1 * cos($t1);
4070
+			$b1 = $y0 + $r2 * sin($t1);
4071
+			$c1 = -$r1 * sin($t1);
4072
+			$d1 = $r2 * cos($t1);
4073
+
4074
+			$this->addContent(
4075
+				sprintf(
4076
+					"\n%.3F %.3F %.3F %.3F %.3F %.3F c",
4077
+					($a0 + $c0 * $dtm),
4078
+					($b0 + $d0 * $dtm),
4079
+					($a1 - $c1 * $dtm),
4080
+					($b1 - $d1 * $dtm),
4081
+					$a1,
4082
+					$b1
4083
+				)
4084
+			);
4085
+
4086
+			$a0 = $a1;
4087
+			$b0 = $b1;
4088
+			$c0 = $c1;
4089
+			$d0 = $d1;
4090
+		}
4091
+
4092
+		if (!$incomplete) {
4093
+			if ($fill) {
4094
+				$this->addContent(' f');
4095
+			}
4096
+
4097
+			if ($stroke) {
4098
+				if ($close) {
4099
+					$this->addContent(' s'); // small 's' signifies closing the path as well
4100
+				} else {
4101
+					$this->addContent(' S');
4102
+				}
4103
+			}
4104
+		}
4105
+
4106
+		if ($angle != 0) {
4107
+			$this->addContent(' Q');
4108
+		}
4109
+	}
4110
+
4111
+	/**
4112
+	 * this sets the line drawing style.
4113
+	 * width, is the thickness of the line in user units
4114
+	 * cap is the type of cap to put on the line, values can be 'butt','round','square'
4115
+	 *    where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the
4116
+	 *    end of the line.
4117
+	 * join can be 'miter', 'round', 'bevel'
4118
+	 * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the
4119
+	 *   on and off dashes.
4120
+	 *   (2) represents 2 on, 2 off, 2 on , 2 off ...
4121
+	 *   (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
4122
+	 * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
4123
+	 *
4124
+	 * @param float  $width
4125
+	 * @param string $cap
4126
+	 * @param string $join
4127
+	 * @param array  $dash
4128
+	 * @param int    $phase
4129
+	 */
4130
+	function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
4131
+	{
4132
+		// this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day
4133
+		$string = '';
4134
+
4135
+		if ($width > 0) {
4136
+			$string .= "$width w";
4137
+		}
4138
+
4139
+		$ca = ['butt' => 0, 'round' => 1, 'square' => 2];
4140
+
4141
+		if (isset($ca[$cap])) {
4142
+			$string .= " $ca[$cap] J";
4143
+		}
4144
+
4145
+		$ja = ['miter' => 0, 'round' => 1, 'bevel' => 2];
4146
+
4147
+		if (isset($ja[$join])) {
4148
+			$string .= " $ja[$join] j";
4149
+		}
4150
+
4151
+		if (is_array($dash)) {
4152
+			$string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
4153
+		}
4154
+
4155
+		$this->currentLineStyle = $string;
4156
+		$this->addContent("\n$string");
4157
+	}
4158
+
4159
+	/**
4160
+	 * draw a polygon, the syntax for this is similar to the GD polygon command
4161
+	 *
4162
+	 * @param float[] $p
4163
+	 * @param bool    $fill
4164
+	 */
4165
+	public function polygon(array $p, bool $fill = false): void
4166
+	{
4167
+		$this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4168
+
4169
+		$n = count($p);
4170
+		for ($i = 2; $i < $n; $i = $i + 2) {
4171
+			$this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4172
+		}
4173
+
4174
+		if ($fill) {
4175
+			$this->addContent(' f');
4176
+		} else {
4177
+			$this->addContent(' S');
4178
+		}
4179
+	}
4180
+
4181
+	/**
4182
+	 * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4183
+	 * the coordinates of the upper-right corner
4184
+	 *
4185
+	 * @param float $x1
4186
+	 * @param float $y1
4187
+	 * @param float $width
4188
+	 * @param float $height
4189
+	 */
4190
+	function filledRectangle($x1, $y1, $width, $height)
4191
+	{
4192
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re f", $x1, $y1, $width, $height));
4193
+	}
4194
+
4195
+	/**
4196
+	 * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4197
+	 * the coordinates of the upper-right corner
4198
+	 *
4199
+	 * @param float $x1
4200
+	 * @param float $y1
4201
+	 * @param float $width
4202
+	 * @param float $height
4203
+	 */
4204
+	function rectangle($x1, $y1, $width, $height)
4205
+	{
4206
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re S", $x1, $y1, $width, $height));
4207
+	}
4208
+
4209
+	/**
4210
+	 * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4211
+	 * the coordinates of the upper-right corner
4212
+	 *
4213
+	 * @param float $x1
4214
+	 * @param float $y1
4215
+	 * @param float $width
4216
+	 * @param float $height
4217
+	 */
4218
+	function rect($x1, $y1, $width, $height)
4219
+	{
4220
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height));
4221
+	}
4222
+
4223
+	function stroke()
4224
+	{
4225
+		$this->addContent("\nS");
4226
+	}
4227
+
4228
+	function fill()
4229
+	{
4230
+		$this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
4231
+	}
4232
+
4233
+	function fillStroke()
4234
+	{
4235
+		$this->addContent("\nb" . ($this->fillRule === "evenodd" ? "*" : ""));
4236
+	}
4237
+
4238
+	/**
4239
+	 * @param string $subtype
4240
+	 * @param integer $x
4241
+	 * @param integer $y
4242
+	 * @param integer $w
4243
+	 * @param integer $h
4244
+	 * @return int
4245
+	 */
4246
+	function addXObject($subtype, $x, $y, $w, $h)
4247
+	{
4248
+		$id = ++$this->numObj;
4249
+		$this->o_xobject($id, 'new', ['Subtype' => $subtype, 'bbox' => [$x, $y, $w, $h]]);
4250
+		return $id;
4251
+	}
4252
+
4253
+	/**
4254
+	 * @param integer $numXObject
4255
+	 * @param string $type
4256
+	 * @param array $options
4257
+	 */
4258
+	function setXObjectResource($numXObject, $type, $options)
4259
+	{
4260
+		if (in_array($type, ['procset', 'font', 'xObject'])) {
4261
+			$this->o_xobject($numXObject, $type, $options);
4262
+		}
4263
+	}
4264
+
4265
+	/**
4266
+	 * add signature
4267
+	 *
4268
+	 * $fieldSigId = $cpdf->addFormField(Cpdf::ACROFORM_FIELD_SIG, 'Signature1', 0, 0, 0, 0, 0);
4269
+	 *
4270
+	 * $signatureId = $cpdf->addSignature([
4271
+	 *   'signcert' => file_get_contents('dompdf.crt'),
4272
+	 *   'privkey' => file_get_contents('dompdf.key'),
4273
+	 *   'password' => 'password',
4274
+	 *   'name' => 'DomPDF DEMO',
4275
+	 *   'location' => 'Home',
4276
+	 *   'reason' => 'First Form',
4277
+	 *   'contactinfo' => 'info'
4278
+	 * ]);
4279
+	 * $cpdf->setFormFieldValue($fieldSigId, "$signatureId 0 R");
4280
+	 *
4281
+	 * @param string $signcert
4282
+	 * @param string $privkey
4283
+	 * @param string $password
4284
+	 * @param string|null $name
4285
+	 * @param string|null $location
4286
+	 * @param string|null $reason
4287
+	 * @param string|null $contactinfo
4288
+	 * @return int
4289
+	 */
4290
+	function addSignature($signcert, $privkey, $password = '', $name = null, $location = null, $reason = null, $contactinfo = null) {
4291
+		$sigId = ++$this->numObj;
4292
+		$this->o_sig($sigId, 'new', [
4293
+		  'SignCert' => $signcert,
4294
+		  'PrivKey' => $privkey,
4295
+		  'Password' => $password,
4296
+		  'Name' => $name,
4297
+		  'Location' => $location,
4298
+		  'Reason' => $reason,
4299
+		  'ContactInfo' => $contactinfo
4300
+		]);
4301
+
4302
+		return $sigId;
4303
+	}
4304
+
4305
+	/**
4306
+	 * add field to form
4307
+	 *
4308
+	 * @param string $type ACROFORM_FIELD_*
4309
+	 * @param string $name
4310
+	 * @param $x0
4311
+	 * @param $y0
4312
+	 * @param $x1
4313
+	 * @param $y1
4314
+	 * @param integer $ff Field Flag ACROFORM_FIELD_*_*
4315
+	 * @param float $size
4316
+	 * @param array $color
4317
+	 * @return int
4318
+	 */
4319
+	public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
4320
+	{
4321
+		if (!$this->numFonts) {
4322
+			$this->selectFont($this->defaultFont);
4323
+		}
4324
+
4325
+		$color = implode(' ', $color) . ' rg';
4326
+
4327
+		$currentFontNum = $this->currentFontNum;
4328
+		$font = array_filter(
4329
+			$this->objects[$this->currentNode]['info']['fonts'],
4330
+			function ($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
4331
+		);
4332
+
4333
+		$this->o_acroform($this->acroFormId, 'font',
4334
+		  ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
4335
+
4336
+		$fieldId = ++$this->numObj;
4337
+		$this->o_field($fieldId, 'new', [
4338
+		  'rect' => [$x0, $y0, $x1, $y1],
4339
+		  'F' => 4,
4340
+		  'FT' => "/$type",
4341
+		  'T' => $name,
4342
+		  'Ff' => $ff,
4343
+		  'pageid' => $this->currentPage,
4344
+		  'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
4345
+		]);
4346
+
4347
+		return $fieldId;
4348
+	}
4349
+
4350
+	/**
4351
+	 * set Field value
4352
+	 *
4353
+	 * @param integer $numFieldObj
4354
+	 * @param string $value
4355
+	 */
4356
+	public function setFormFieldValue($numFieldObj, $value)
4357
+	{
4358
+		$this->o_field($numFieldObj, 'set', ['value' => $value]);
4359
+	}
4360
+
4361
+	/**
4362
+	 * set Field value (reference)
4363
+	 *
4364
+	 * @param integer $numFieldObj
4365
+	 * @param integer $numObj Object number
4366
+	 */
4367
+	public function setFormFieldRefValue($numFieldObj, $numObj)
4368
+	{
4369
+		$this->o_field($numFieldObj, 'set', ['refvalue' => $numObj]);
4370
+	}
4371
+
4372
+	/**
4373
+	 * set Field Appearanc (reference)
4374
+	 *
4375
+	 * @param integer $numFieldObj
4376
+	 * @param integer $normalNumObj
4377
+	 * @param integer|null $rolloverNumObj
4378
+	 * @param integer|null $downNumObj
4379
+	 */
4380
+	public function setFormFieldAppearance($numFieldObj, $normalNumObj, $rolloverNumObj = null, $downNumObj = null)
4381
+	{
4382
+		$appearance['N'] = $normalNumObj;
4383
+
4384
+		if ($rolloverNumObj !== null) {
4385
+			$appearance['R'] = $rolloverNumObj;
4386
+		}
4387
+
4388
+		if ($downNumObj !== null) {
4389
+			$appearance['D'] = $downNumObj;
4390
+		}
4391
+
4392
+		$this->o_field($numFieldObj, 'set', ['appearance' => $appearance]);
4393
+	}
4394
+
4395
+	/**
4396
+	 * set Choice Field option values
4397
+	 *
4398
+	 * @param integer $numFieldObj
4399
+	 * @param array $value
4400
+	 */
4401
+	public function setFormFieldOpt($numFieldObj, $value)
4402
+	{
4403
+		$this->o_field($numFieldObj, 'set', ['options' => $value]);
4404
+	}
4405
+
4406
+	/**
4407
+	 * add form to document
4408
+	 *
4409
+	 * @param integer $sigFlags
4410
+	 * @param boolean $needAppearances
4411
+	 */
4412
+	public function addForm($sigFlags = 0, $needAppearances = false)
4413
+	{
4414
+		$this->acroFormId = ++$this->numObj;
4415
+		$this->o_acroform($this->acroFormId, 'new', [
4416
+		  'NeedAppearances' => $needAppearances ? 'true' : 'false',
4417
+		  'SigFlags' => $sigFlags
4418
+		]);
4419
+	}
4420
+
4421
+	/**
4422
+	 * save the current graphic state
4423
+	 */
4424
+	function save()
4425
+	{
4426
+		// we must reset the color cache or it will keep bad colors after clipping
4427
+		$this->currentColor = null;
4428
+		$this->currentStrokeColor = null;
4429
+		$this->addContent("\nq");
4430
+	}
4431
+
4432
+	/**
4433
+	 * restore the last graphic state
4434
+	 */
4435
+	function restore()
4436
+	{
4437
+		// we must reset the color cache or it will keep bad colors after clipping
4438
+		$this->currentColor = null;
4439
+		$this->currentStrokeColor = null;
4440
+		$this->addContent("\nQ");
4441
+	}
4442
+
4443
+	/**
4444
+	 * draw a clipping rectangle, all the elements added after this will be clipped
4445
+	 *
4446
+	 * @param float $x1
4447
+	 * @param float $y1
4448
+	 * @param float $width
4449
+	 * @param float $height
4450
+	 */
4451
+	function clippingRectangle($x1, $y1, $width, $height)
4452
+	{
4453
+		$this->save();
4454
+		$this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re W n", $x1, $y1, $width, $height));
4455
+	}
4456
+
4457
+	/**
4458
+	 * draw a clipping rounded rectangle, all the elements added after this will be clipped
4459
+	 *
4460
+	 * @param float $x1
4461
+	 * @param float $y1
4462
+	 * @param float $w
4463
+	 * @param float $h
4464
+	 * @param float $rTL
4465
+	 * @param float $rTR
4466
+	 * @param float $rBR
4467
+	 * @param float $rBL
4468
+	 */
4469
+	function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
4470
+	{
4471
+		$this->save();
4472
+
4473
+		// start: top edge, left end
4474
+		$this->addContent(sprintf("\n%.3F %.3F m ", $x1, $y1 - $rTL + $h));
4475
+
4476
+		// line: bottom edge, left end
4477
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1, $y1 + $rBL));
4478
+
4479
+		// curve: bottom-left corner
4480
+		$this->ellipse($x1 + $rBL, $y1 + $rBL, $rBL, 0, 0, 8, 180, 270, false, false, false, true);
4481
+
4482
+		// line: right edge, bottom end
4483
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w - $rBR, $y1));
4484
+
4485
+		// curve: bottom-right corner
4486
+		$this->ellipse($x1 + $w - $rBR, $y1 + $rBR, $rBR, 0, 0, 8, 270, 360, false, false, false, true);
4487
+
4488
+		// line: right edge, top end
4489
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $w, $y1 + $h - $rTR));
4490
+
4491
+		// curve: bottom-right corner
4492
+		$this->ellipse($x1 + $w - $rTR, $y1 + $h - $rTR, $rTR, 0, 0, 8, 0, 90, false, false, false, true);
4493
+
4494
+		// line: bottom edge, right end
4495
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rTL, $y1 + $h));
4496
+
4497
+		// curve: top-right corner
4498
+		$this->ellipse($x1 + $rTL, $y1 + $h - $rTL, $rTL, 0, 0, 8, 90, 180, false, false, false, true);
4499
+
4500
+		// line: top edge, left end
4501
+		$this->addContent(sprintf("\n%.3F %.3F l ", $x1 + $rBL, $y1));
4502
+
4503
+		// Close & clip
4504
+		$this->addContent(" W n");
4505
+	}
4506
+
4507
+	/**
4508
+	 * draw a clipping polygon, the syntax for this is similar to the GD polygon command
4509
+	 *
4510
+	 * @param float[] $p
4511
+	 */
4512
+	public function clippingPolygon(array $p): void
4513
+	{
4514
+		$this->save();
4515
+
4516
+		$this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4517
+
4518
+		$n = count($p);
4519
+		for ($i = 2; $i < $n; $i = $i + 2) {
4520
+			$this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4521
+		}
4522
+
4523
+		$this->addContent("W n");
4524
+	}
4525
+
4526
+	/**
4527
+	 * ends the last clipping shape
4528
+	 */
4529
+	function clippingEnd()
4530
+	{
4531
+		$this->restore();
4532
+	}
4533
+
4534
+	/**
4535
+	 * scale
4536
+	 *
4537
+	 * @param float $s_x scaling factor for width as percent
4538
+	 * @param float $s_y scaling factor for height as percent
4539
+	 * @param float $x   Origin abscissa
4540
+	 * @param float $y   Origin ordinate
4541
+	 */
4542
+	function scale($s_x, $s_y, $x, $y)
4543
+	{
4544
+		$y = $this->currentPageSize["height"] - $y;
4545
+
4546
+		$tm = [
4547
+			$s_x,
4548
+			0,
4549
+			0,
4550
+			$s_y,
4551
+			$x * (1 - $s_x),
4552
+			$y * (1 - $s_y)
4553
+		];
4554
+
4555
+		$this->transform($tm);
4556
+	}
4557
+
4558
+	/**
4559
+	 * translate
4560
+	 *
4561
+	 * @param float $t_x movement to the right
4562
+	 * @param float $t_y movement to the bottom
4563
+	 */
4564
+	function translate($t_x, $t_y)
4565
+	{
4566
+		$tm = [
4567
+			1,
4568
+			0,
4569
+			0,
4570
+			1,
4571
+			$t_x,
4572
+			-$t_y
4573
+		];
4574
+
4575
+		$this->transform($tm);
4576
+	}
4577
+
4578
+	/**
4579
+	 * rotate
4580
+	 *
4581
+	 * @param float $angle angle in degrees for counter-clockwise rotation
4582
+	 * @param float $x     Origin abscissa
4583
+	 * @param float $y     Origin ordinate
4584
+	 */
4585
+	function rotate($angle, $x, $y)
4586
+	{
4587
+		$y = $this->currentPageSize["height"] - $y;
4588
+
4589
+		$a = deg2rad($angle);
4590
+		$cos_a = cos($a);
4591
+		$sin_a = sin($a);
4592
+
4593
+		$tm = [
4594
+			$cos_a,
4595
+			-$sin_a,
4596
+			$sin_a,
4597
+			$cos_a,
4598
+			$x - $sin_a * $y - $cos_a * $x,
4599
+			$y - $cos_a * $y + $sin_a * $x,
4600
+		];
4601
+
4602
+		$this->transform($tm);
4603
+	}
4604
+
4605
+	/**
4606
+	 * skew
4607
+	 *
4608
+	 * @param float $angle_x
4609
+	 * @param float $angle_y
4610
+	 * @param float $x Origin abscissa
4611
+	 * @param float $y Origin ordinate
4612
+	 */
4613
+	function skew($angle_x, $angle_y, $x, $y)
4614
+	{
4615
+		$y = $this->currentPageSize["height"] - $y;
4616
+
4617
+		$tan_x = tan(deg2rad($angle_x));
4618
+		$tan_y = tan(deg2rad($angle_y));
4619
+
4620
+		$tm = [
4621
+			1,
4622
+			-$tan_y,
4623
+			-$tan_x,
4624
+			1,
4625
+			$tan_x * $y,
4626
+			$tan_y * $x,
4627
+		];
4628
+
4629
+		$this->transform($tm);
4630
+	}
4631
+
4632
+	/**
4633
+	 * apply graphic transformations
4634
+	 *
4635
+	 * @param array $tm transformation matrix
4636
+	 */
4637
+	function transform($tm)
4638
+	{
4639
+		$this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm));
4640
+	}
4641
+
4642
+	/**
4643
+	 * add a new page to the document
4644
+	 * this also makes the new page the current active object
4645
+	 *
4646
+	 * @param int $insert
4647
+	 * @param int $id
4648
+	 * @param string $pos
4649
+	 * @return int
4650
+	 */
4651
+	function newPage($insert = 0, $id = 0, $pos = 'after')
4652
+	{
4653
+		// if there is a state saved, then go up the stack closing them
4654
+		// then on the new page, re-open them with the right setings
4655
+
4656
+		if ($this->nStateStack) {
4657
+			for ($i = $this->nStateStack; $i >= 1; $i--) {
4658
+				$this->restoreState($i);
4659
+			}
4660
+		}
4661
+
4662
+		$this->numObj++;
4663
+
4664
+		if ($insert) {
4665
+			// the id from the ezPdf class is the id of the contents of the page, not the page object itself
4666
+			// query that object to find the parent
4667
+			$rid = $this->objects[$id]['onPage'];
4668
+			$opt = ['rid' => $rid, 'pos' => $pos];
4669
+			$this->o_page($this->numObj, 'new', $opt);
4670
+		} else {
4671
+			$this->o_page($this->numObj, 'new');
4672
+		}
4673
+
4674
+		// if there is a stack saved, then put that onto the page
4675
+		if ($this->nStateStack) {
4676
+			for ($i = 1; $i <= $this->nStateStack; $i++) {
4677
+				$this->saveState($i);
4678
+			}
4679
+		}
4680
+
4681
+		// and if there has been a stroke or fill color set, then transfer them
4682
+		if (isset($this->currentColor)) {
4683
+			$this->setColor($this->currentColor, true);
4684
+		}
4685
+
4686
+		if (isset($this->currentStrokeColor)) {
4687
+			$this->setStrokeColor($this->currentStrokeColor, true);
4688
+		}
4689
+
4690
+		// if there is a line style set, then put this in too
4691
+		if (mb_strlen($this->currentLineStyle, '8bit')) {
4692
+			$this->addContent("\n$this->currentLineStyle");
4693
+		}
4694
+
4695
+		// the call to the o_page object set currentContents to the present page, so this can be returned as the page id
4696
+		return $this->currentContents;
4697
+	}
4698
+
4699
+	/**
4700
+	 * Streams the PDF to the client.
4701
+	 *
4702
+	 * @param string $filename The filename to present to the client.
4703
+	 * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
4704
+	 */
4705
+	function stream($filename = "document.pdf", $options = [])
4706
+	{
4707
+		if (headers_sent()) {
4708
+			die("Unable to stream pdf: headers already sent");
4709
+		}
4710
+
4711
+		if (!isset($options["compress"])) $options["compress"] = true;
4712
+		if (!isset($options["Attachment"])) $options["Attachment"] = true;
4713
+
4714
+		$debug = !$options['compress'];
4715
+		$tmp = ltrim($this->output($debug));
4716
+
4717
+		header("Cache-Control: private");
4718
+		header("Content-Type: application/pdf");
4719
+		header("Content-Length: " . mb_strlen($tmp, "8bit"));
4720
+
4721
+		$filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
4722
+		$attachment = $options["Attachment"] ? "attachment" : "inline";
4723
+
4724
+		$encoding = mb_detect_encoding($filename);
4725
+		$fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
4726
+		$fallbackfilename = str_replace("\"", "", $fallbackfilename);
4727
+		$encodedfilename = rawurlencode($filename);
4728
+
4729
+		$contentDisposition = "Content-Disposition: $attachment; filename=\"$fallbackfilename\"";
4730
+		if ($fallbackfilename !== $filename) {
4731
+			$contentDisposition .= "; filename*=UTF-8''$encodedfilename";
4732
+		}
4733
+		header($contentDisposition);
4734
+
4735
+		echo $tmp;
4736
+		flush();
4737
+	}
4738
+
4739
+	/**
4740
+	 * return the height in units of the current font in the given size
4741
+	 *
4742
+	 * @param float $size
4743
+	 *
4744
+	 * @return float
4745
+	 */
4746
+	public function getFontHeight(float $size): float
4747
+	{
4748
+		if (!$this->numFonts) {
4749
+			$this->selectFont($this->defaultFont);
4750
+		}
4751
+
4752
+		$font = $this->fonts[$this->currentFont];
4753
+
4754
+		// for the current font, and the given size, what is the height of the font in user units
4755
+		if (isset($font['Ascender']) && isset($font['Descender'])) {
4756
+			$h = $font['Ascender'] - $font['Descender'];
4757
+		} else {
4758
+			$h = $font['FontBBox'][3] - $font['FontBBox'][1];
4759
+		}
4760
+
4761
+		// have to adjust by a font offset for Windows fonts.  unfortunately it looks like
4762
+		// the bounding box calculations are wrong and I don't know why.
4763
+		if (isset($font['FontHeightOffset'])) {
4764
+			// For CourierNew from Windows this needs to be -646 to match the
4765
+			// Adobe native Courier font.
4766
+			//
4767
+			// For FreeMono from GNU this needs to be -337 to match the
4768
+			// Courier font.
4769
+			//
4770
+			// Both have been added manually to the .afm and .ufm files.
4771
+			$h += (int)$font['FontHeightOffset'];
4772
+		}
4773
+
4774
+		return $size * $h / 1000;
4775
+	}
4776
+
4777
+	/**
4778
+	 * @param float $size
4779
+	 *
4780
+	 * @return float
4781
+	 */
4782
+	public function getFontXHeight(float $size): float
4783
+	{
4784
+		if (!$this->numFonts) {
4785
+			$this->selectFont($this->defaultFont);
4786
+		}
4787
+
4788
+		$font = $this->fonts[$this->currentFont];
4789
+
4790
+		// for the current font, and the given size, what is the height of the font in user units
4791
+		if (isset($font['XHeight'])) {
4792
+			$xh = $font['Ascender'] - $font['Descender'];
4793
+		} else {
4794
+			$xh = $this->getFontHeight($size) / 2;
4795
+		}
4796
+
4797
+		return $size * $xh / 1000;
4798
+	}
4799
+
4800
+	/**
4801
+	 * return the font descender, this will normally return a negative number
4802
+	 * if you add this number to the baseline, you get the level of the bottom of the font
4803
+	 * it is in the pdf user units
4804
+	 *
4805
+	 * @param float $size
4806
+	 *
4807
+	 * @return float
4808
+	 */
4809
+	public function getFontDescender(float $size): float
4810
+	{
4811
+		// note that this will most likely return a negative value
4812
+		if (!$this->numFonts) {
4813
+			$this->selectFont($this->defaultFont);
4814
+		}
4815
+
4816
+		//$h = $this->fonts[$this->currentFont]['FontBBox'][1];
4817
+		$h = $this->fonts[$this->currentFont]['Descender'];
4818
+
4819
+		return $size * $h / 1000;
4820
+	}
4821
+
4822
+	/**
4823
+	 * filter the text, this is applied to all text just before being inserted into the pdf document
4824
+	 * it escapes the various things that need to be escaped, and so on
4825
+	 *
4826
+	 * @param $text
4827
+	 * @param bool $bom
4828
+	 * @param bool $convert_encoding
4829
+	 * @return string
4830
+	 */
4831
+	function filterText($text, $bom = true, $convert_encoding = true)
4832
+	{
4833
+		if (!$this->numFonts) {
4834
+			$this->selectFont($this->defaultFont);
4835
+		}
4836
+
4837
+		if ($convert_encoding) {
4838
+			$cf = $this->currentFont;
4839
+			if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) {
4840
+				$text = $this->utf8toUtf16BE($text, $bom);
4841
+			} else {
4842
+				//$text = html_entity_decode($text, ENT_QUOTES);
4843
+				$text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4844
+			}
4845
+		} elseif ($bom) {
4846
+			$text = $this->utf8toUtf16BE($text, $bom);
4847
+		}
4848
+
4849
+		// the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290)
4850
+		return strtr($text, [')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r']);
4851
+	}
4852
+
4853
+	/**
4854
+	 * return array containing codepoints (UTF-8 character values) for the
4855
+	 * string passed in.
4856
+	 *
4857
+	 * based on the excellent TCPDF code by Nicola Asuni and the
4858
+	 * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4859
+	 *
4860
+	 * @param string $text UTF-8 string to process
4861
+	 * @return array UTF-8 codepoints array for the string
4862
+	 */
4863
+	function utf8toCodePointsArray(&$text)
4864
+	{
4865
+		$length = mb_strlen($text, '8bit'); // http://www.php.net/manual/en/function.mb-strlen.php#77040
4866
+		$unicode = []; // array containing unicode values
4867
+		$bytes = []; // array containing single character byte sequences
4868
+		$numbytes = 1; // number of octets needed to represent the UTF-8 character
4869
+
4870
+		for ($i = 0; $i < $length; $i++) {
4871
+			$c = ord($text[$i]); // get one string character at time
4872
+			if (count($bytes) === 0) { // get starting octect
4873
+				if ($c <= 0x7F) {
4874
+					$unicode[] = $c; // use the character "as is" because is ASCII
4875
+					$numbytes = 1;
4876
+				} elseif (($c >> 0x05) === 0x06) { // 2 bytes character (0x06 = 110 BIN)
4877
+					$bytes[] = ($c - 0xC0) << 0x06;
4878
+					$numbytes = 2;
4879
+				} elseif (($c >> 0x04) === 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
4880
+					$bytes[] = ($c - 0xE0) << 0x0C;
4881
+					$numbytes = 3;
4882
+				} elseif (($c >> 0x03) === 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
4883
+					$bytes[] = ($c - 0xF0) << 0x12;
4884
+					$numbytes = 4;
4885
+				} else {
4886
+					// use replacement character for other invalid sequences
4887
+					$unicode[] = 0xFFFD;
4888
+					$bytes = [];
4889
+					$numbytes = 1;
4890
+				}
4891
+			} elseif (($c >> 0x06) === 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
4892
+				$bytes[] = $c - 0x80;
4893
+				if (count($bytes) === $numbytes) {
4894
+					// compose UTF-8 bytes to a single unicode value
4895
+					$c = $bytes[0];
4896
+					for ($j = 1; $j < $numbytes; $j++) {
4897
+						$c += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
4898
+					}
4899
+					if ((($c >= 0xD800) and ($c <= 0xDFFF)) or ($c >= 0x10FFFF)) {
4900
+						// The definition of UTF-8 prohibits encoding character numbers between
4901
+						// U+D800 and U+DFFF, which are reserved for use with the UTF-16
4902
+						// encoding form (as surrogate pairs) and do not directly represent
4903
+						// characters.
4904
+						$unicode[] = 0xFFFD; // use replacement character
4905
+					} else {
4906
+						$unicode[] = $c; // add char to array
4907
+					}
4908
+					// reset data for next char
4909
+					$bytes = [];
4910
+					$numbytes = 1;
4911
+				}
4912
+			} else {
4913
+				// use replacement character for other invalid sequences
4914
+				$unicode[] = 0xFFFD;
4915
+				$bytes = [];
4916
+				$numbytes = 1;
4917
+			}
4918
+		}
4919
+
4920
+		return $unicode;
4921
+	}
4922
+
4923
+	/**
4924
+	 * convert UTF-8 to UTF-16 with an additional byte order marker
4925
+	 * at the front if required.
4926
+	 *
4927
+	 * based on the excellent TCPDF code by Nicola Asuni and the
4928
+	 * RFC for UTF-8 at http://www.faqs.org/rfcs/rfc3629.html
4929
+	 *
4930
+	 * @param string  $text UTF-8 string to process
4931
+	 * @param boolean $bom  whether to add the byte order marker
4932
+	 * @return string UTF-16 result string
4933
+	 */
4934
+	function utf8toUtf16BE(&$text, $bom = true)
4935
+	{
4936
+		$out = $bom ? "\xFE\xFF" : '';
4937
+
4938
+		$unicode = $this->utf8toCodePointsArray($text);
4939
+		foreach ($unicode as $c) {
4940
+			if ($c === 0xFFFD) {
4941
+				$out .= "\xFF\xFD"; // replacement character
4942
+			} elseif ($c < 0x10000) {
4943
+				$out .= chr($c >> 0x08) . chr($c & 0xFF);
4944
+			} else {
4945
+				$c -= 0x10000;
4946
+				$w1 = 0xD800 | ($c >> 0x10);
4947
+				$w2 = 0xDC00 | ($c & 0x3FF);
4948
+				$out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4949
+			}
4950
+		}
4951
+
4952
+		return $out;
4953
+	}
4954
+
4955
+	/**
4956
+	 * given a start position and information about how text is to be laid out, calculate where
4957
+	 * on the page the text will end
4958
+	 *
4959
+	 * @param $x
4960
+	 * @param $y
4961
+	 * @param $angle
4962
+	 * @param $size
4963
+	 * @param $wa
4964
+	 * @param $text
4965
+	 * @return array
4966
+	 */
4967
+	private function getTextPosition($x, $y, $angle, $size, $wa, $text)
4968
+	{
4969
+		// given this information return an array containing x and y for the end position as elements 0 and 1
4970
+		$w = $this->getTextWidth($size, $text);
4971
+
4972
+		// need to adjust for the number of spaces in this text
4973
+		$words = explode(' ', $text);
4974
+		$nspaces = count($words) - 1;
4975
+		$w += $wa * $nspaces;
4976
+		$a = deg2rad((float)$angle);
4977
+
4978
+		return [cos($a) * $w + $x, -sin($a) * $w + $y];
4979
+	}
4980
+
4981
+	/**
4982
+	 * Callback method used by smallCaps
4983
+	 *
4984
+	 * @param array $matches
4985
+	 *
4986
+	 * @return string
4987
+	 */
4988
+	function toUpper($matches)
4989
+	{
4990
+		return mb_strtoupper($matches[0]);
4991
+	}
4992
+
4993
+	function concatMatches($matches)
4994
+	{
4995
+		$str = "";
4996
+		foreach ($matches as $match) {
4997
+			$str .= $match[0];
4998
+		}
4999
+
5000
+		return $str;
5001
+	}
5002
+
5003
+	/**
5004
+	 * register text for font subsetting
5005
+	 *
5006
+	 * @param string $font
5007
+	 * @param string $text
5008
+	 */
5009
+	function registerText($font, $text)
5010
+	{
5011
+		if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
5012
+			return;
5013
+		}
5014
+
5015
+		if (!isset($this->stringSubsets[$font])) {
5016
+			$base_subset = "\u{fffd}\u{fffe}\u{ffff}";
5017
+			$this->stringSubsets[$font] = $this->utf8toCodePointsArray($base_subset);
5018
+		}
5019
+
5020
+		$this->stringSubsets[$font] = array_unique(
5021
+			array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text))
5022
+		);
5023
+	}
5024
+
5025
+	/**
5026
+	 * add text to the document, at a specified location, size and angle on the page
5027
+	 *
5028
+	 * @param float  $x
5029
+	 * @param float  $y
5030
+	 * @param float  $size
5031
+	 * @param string $text
5032
+	 * @param float  $angle
5033
+	 * @param float  $wordSpaceAdjust
5034
+	 * @param float  $charSpaceAdjust
5035
+	 * @param bool   $smallCaps
5036
+	 */
5037
+	function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
5038
+	{
5039
+		if (!$this->numFonts) {
5040
+			$this->selectFont($this->defaultFont);
5041
+		}
5042
+
5043
+		$text = str_replace(["\r", "\n"], "", $text);
5044
+
5045
+		// if ($smallCaps) {
5046
+		//     preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
5047
+		//     $lower = $this->concatMatches($matches);
5048
+		//     d($lower);
5049
+
5050
+		//     preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER);
5051
+		//     $other = $this->concatMatches($matches);
5052
+		//     d($other);
5053
+
5054
+		//     $text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text);
5055
+		// }
5056
+
5057
+		// if there are any open callbacks, then they should be called, to show the start of the line
5058
+		if ($this->nCallback > 0) {
5059
+			for ($i = $this->nCallback; $i > 0; $i--) {
5060
+				// call each function
5061
+				$info = [
5062
+					'x'         => $x,
5063
+					'y'         => $y,
5064
+					'angle'     => $angle,
5065
+					'status'    => 'sol',
5066
+					'p'         => $this->callback[$i]['p'],
5067
+					'nCallback' => $this->callback[$i]['nCallback'],
5068
+					'height'    => $this->callback[$i]['height'],
5069
+					'descender' => $this->callback[$i]['descender']
5070
+				];
5071
+
5072
+				$func = $this->callback[$i]['f'];
5073
+				$this->$func($info);
5074
+			}
5075
+		}
5076
+
5077
+		if ($angle == 0) {
5078
+			$this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
5079
+		} else {
5080
+			$a = deg2rad((float)$angle);
5081
+			$this->addContent(
5082
+				sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
5083
+			);
5084
+		}
5085
+
5086
+		if ($wordSpaceAdjust != 0) {
5087
+			$this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust));
5088
+		}
5089
+
5090
+		if ($charSpaceAdjust != 0) {
5091
+			$this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust));
5092
+		}
5093
+
5094
+		$len = mb_strlen($text);
5095
+		$start = 0;
5096
+
5097
+		if ($start < $len) {
5098
+			$part = $text; // OAR - Don't need this anymore, given that $start always equals zero.  substr($text, $start);
5099
+			$place_text = $this->filterText($part, false);
5100
+			// modify unicode text so that extra word spacing is manually implemented (bug #)
5101
+			if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
5102
+				$space_scale = 1000 / $size;
5103
+				$place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
5104
+			}
5105
+			$this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
5106
+			$this->addContent(" [($place_text)] TJ");
5107
+		}
5108
+
5109
+		if ($wordSpaceAdjust != 0) {
5110
+			$this->addContent(sprintf(" %.3F Tw", 0));
5111
+		}
5112
+
5113
+		if ($charSpaceAdjust != 0) {
5114
+			$this->addContent(sprintf(" %.3F Tc", 0));
5115
+		}
5116
+
5117
+		$this->addContent(' ET');
5118
+
5119
+		// if there are any open callbacks, then they should be called, to show the end of the line
5120
+		if ($this->nCallback > 0) {
5121
+			for ($i = $this->nCallback; $i > 0; $i--) {
5122
+				// call each function
5123
+				$tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text);
5124
+				$info = [
5125
+					'x'         => $tmp[0],
5126
+					'y'         => $tmp[1],
5127
+					'angle'     => $angle,
5128
+					'status'    => 'eol',
5129
+					'p'         => $this->callback[$i]['p'],
5130
+					'nCallback' => $this->callback[$i]['nCallback'],
5131
+					'height'    => $this->callback[$i]['height'],
5132
+					'descender' => $this->callback[$i]['descender']
5133
+				];
5134
+				$func = $this->callback[$i]['f'];
5135
+				$this->$func($info);
5136
+			}
5137
+		}
5138
+
5139
+		if ($this->fonts[$this->currentFont]['isSubsetting']) {
5140
+			$this->registerText($this->currentFont, $text);
5141
+		}
5142
+	}
5143
+
5144
+	/**
5145
+	 * calculate how wide a given text string will be on a page, at a given size.
5146
+	 * this can be called externally, but is also used by the other class functions
5147
+	 *
5148
+	 * @param float  $size
5149
+	 * @param string $text
5150
+	 * @param float  $wordSpacing
5151
+	 * @param float  $charSpacing
5152
+	 *
5153
+	 * @return float
5154
+	 */
5155
+	public function getTextWidth(float $size, string $text, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
5156
+	{
5157
+		static $ord_cache = [];
5158
+
5159
+		// this function should not change any of the settings, though it will need to
5160
+		// track any directives which change during calculation, so copy them at the start
5161
+		// and put them back at the end.
5162
+		$store_currentTextState = $this->currentTextState;
5163
+
5164
+		if (!$this->numFonts) {
5165
+			$this->selectFont($this->defaultFont);
5166
+		}
5167
+
5168
+		$text = str_replace(["\r", "\n"], "", $text);
5169
+
5170
+		// hmm, this is where it all starts to get tricky - use the font information to
5171
+		// calculate the width of each character, add them up and convert to user units
5172
+		$w = 0;
5173
+		$cf = $this->currentFont;
5174
+		$current_font = $this->fonts[$cf];
5175
+		$space_scale = 1000 / ($size > 0 ? $size : 1);
5176
+
5177
+		if ($current_font['isUnicode']) {
5178
+			// for Unicode, use the code points array to calculate width rather
5179
+			// than just the string itself
5180
+			$unicode = $this->utf8toCodePointsArray($text);
5181
+
5182
+			foreach ($unicode as $char) {
5183
+				// check if we have to replace character
5184
+				if (isset($current_font['differences'][$char])) {
5185
+					$char = $current_font['differences'][$char];
5186
+				}
5187
+
5188
+				if (isset($current_font['C'][$char])) {
5189
+					$char_width = $current_font['C'][$char];
5190
+
5191
+					// add the character width
5192
+					$w += $char_width;
5193
+
5194
+					// add additional padding for space
5195
+					if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5196
+						$w += $wordSpacing * $space_scale;
5197
+					}
5198
+				}
5199
+			}
5200
+
5201
+			// add additional char spacing
5202
+			if ($charSpacing != 0) {
5203
+				$w += $charSpacing * $space_scale * count($unicode);
5204
+			}
5205
+
5206
+		} else {
5207
+			// If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252
5208
+			if ($this->isUnicode) {
5209
+				$text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8');
5210
+			}
5211
+
5212
+			$len = mb_strlen($text, 'Windows-1252');
5213
+
5214
+			for ($i = 0; $i < $len; $i++) {
5215
+				$c = $text[$i];
5216
+				$char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c));
5217
+
5218
+				// check if we have to replace character
5219
+				if (isset($current_font['differences'][$char])) {
5220
+					$char = $current_font['differences'][$char];
5221
+				}
5222
+
5223
+				if (isset($current_font['C'][$char])) {
5224
+					$char_width = $current_font['C'][$char];
5225
+
5226
+					// add the character width
5227
+					$w += $char_width;
5228
+
5229
+					// add additional padding for space
5230
+					if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') {  // Space
5231
+						$w += $wordSpacing * $space_scale;
5232
+					}
5233
+				}
5234
+			}
5235
+
5236
+			// add additional char spacing
5237
+			if ($charSpacing != 0) {
5238
+				$w += $charSpacing * $space_scale * $len;
5239
+			}
5240
+		}
5241
+
5242
+		$this->currentTextState = $store_currentTextState;
5243
+		$this->setCurrentFont();
5244
+
5245
+		return $w * $size / 1000;
5246
+	}
5247
+
5248
+	/**
5249
+	 * this will be called at a new page to return the state to what it was on the
5250
+	 * end of the previous page, before the stack was closed down
5251
+	 * This is to get around not being able to have open 'q' across pages
5252
+	 *
5253
+	 * @param int $pageEnd
5254
+	 */
5255
+	function saveState($pageEnd = 0)
5256
+	{
5257
+		if ($pageEnd) {
5258
+			// this will be called at a new page to return the state to what it was on the
5259
+			// end of the previous page, before the stack was closed down
5260
+			// This is to get around not being able to have open 'q' across pages
5261
+			$opt = $this->stateStack[$pageEnd];
5262
+			// ok to use this as stack starts numbering at 1
5263
+			$this->setColor($opt['col'], true);
5264
+			$this->setStrokeColor($opt['str'], true);
5265
+			$this->addContent("\n" . $opt['lin']);
5266
+			//    $this->currentLineStyle = $opt['lin'];
5267
+		} else {
5268
+			$this->nStateStack++;
5269
+			$this->stateStack[$this->nStateStack] = [
5270
+				'col' => $this->currentColor,
5271
+				'str' => $this->currentStrokeColor,
5272
+				'lin' => $this->currentLineStyle
5273
+			];
5274
+		}
5275
+
5276
+		$this->save();
5277
+	}
5278
+
5279
+	/**
5280
+	 * restore a previously saved state
5281
+	 *
5282
+	 * @param int $pageEnd
5283
+	 */
5284
+	function restoreState($pageEnd = 0)
5285
+	{
5286
+		if (!$pageEnd) {
5287
+			$n = $this->nStateStack;
5288
+			$this->currentColor = $this->stateStack[$n]['col'];
5289
+			$this->currentStrokeColor = $this->stateStack[$n]['str'];
5290
+			$this->addContent("\n" . $this->stateStack[$n]['lin']);
5291
+			$this->currentLineStyle = $this->stateStack[$n]['lin'];
5292
+			$this->stateStack[$n] = null;
5293
+			unset($this->stateStack[$n]);
5294
+			$this->nStateStack--;
5295
+		}
5296
+
5297
+		$this->restore();
5298
+	}
5299
+
5300
+	/**
5301
+	 * make a loose object, the output will go into this object, until it is closed, then will revert to
5302
+	 * the current one.
5303
+	 * this object will not appear until it is included within a page.
5304
+	 * the function will return the object number
5305
+	 *
5306
+	 * @return int
5307
+	 */
5308
+	function openObject()
5309
+	{
5310
+		$this->nStack++;
5311
+		$this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5312
+		// add a new object of the content type, to hold the data flow
5313
+		$this->numObj++;
5314
+		$this->o_contents($this->numObj, 'new');
5315
+		$this->currentContents = $this->numObj;
5316
+		$this->looseObjects[$this->numObj] = 1;
5317
+
5318
+		return $this->numObj;
5319
+	}
5320
+
5321
+	/**
5322
+	 * open an existing object for editing
5323
+	 *
5324
+	 * @param $id
5325
+	 */
5326
+	function reopenObject($id)
5327
+	{
5328
+		$this->nStack++;
5329
+		$this->stack[$this->nStack] = ['c' => $this->currentContents, 'p' => $this->currentPage];
5330
+		$this->currentContents = $id;
5331
+
5332
+		// also if this object is the primary contents for a page, then set the current page to its parent
5333
+		if (isset($this->objects[$id]['onPage'])) {
5334
+			$this->currentPage = $this->objects[$id]['onPage'];
5335
+		}
5336
+	}
5337
+
5338
+	/**
5339
+	 * close an object
5340
+	 */
5341
+	function closeObject()
5342
+	{
5343
+		// close the object, as long as there was one open in the first place, which will be indicated by
5344
+		// an objectId on the stack.
5345
+		if ($this->nStack > 0) {
5346
+			$this->currentContents = $this->stack[$this->nStack]['c'];
5347
+			$this->currentPage = $this->stack[$this->nStack]['p'];
5348
+			$this->nStack--;
5349
+			// easier to probably not worry about removing the old entries, they will be overwritten
5350
+			// if there are new ones.
5351
+		}
5352
+	}
5353
+
5354
+	/**
5355
+	 * stop an object from appearing on pages from this point on
5356
+	 *
5357
+	 * @param $id
5358
+	 */
5359
+	function stopObject($id)
5360
+	{
5361
+		// if an object has been appearing on pages up to now, then stop it, this page will
5362
+		// be the last one that could contain it.
5363
+		if (isset($this->addLooseObjects[$id])) {
5364
+			$this->addLooseObjects[$id] = '';
5365
+		}
5366
+	}
5367
+
5368
+	/**
5369
+	 * after an object has been created, it wil only show if it has been added, using this function.
5370
+	 *
5371
+	 * @param $id
5372
+	 * @param string $options
5373
+	 */
5374
+	function addObject($id, $options = 'add')
5375
+	{
5376
+		// add the specified object to the page
5377
+		if (isset($this->looseObjects[$id]) && $this->currentContents != $id) {
5378
+			// then it is a valid object, and it is not being added to itself
5379
+			switch ($options) {
5380
+				case 'all':
5381
+					// then this object is to be added to this page (done in the next block) and
5382
+					// all future new pages.
5383
+					$this->addLooseObjects[$id] = 'all';
5384
+
5385
+				case 'add':
5386
+					if (isset($this->objects[$this->currentContents]['onPage'])) {
5387
+						// then the destination contents is the primary for the page
5388
+						// (though this object is actually added to that page)
5389
+						$this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id);
5390
+					}
5391
+					break;
5392
+
5393
+				case 'even':
5394
+					$this->addLooseObjects[$id] = 'even';
5395
+					$pageObjectId = $this->objects[$this->currentContents]['onPage'];
5396
+					if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) {
5397
+						$this->addObject($id);
5398
+						// hacky huh :)
5399
+					}
5400
+					break;
5401
+
5402
+				case 'odd':
5403
+					$this->addLooseObjects[$id] = 'odd';
5404
+					$pageObjectId = $this->objects[$this->currentContents]['onPage'];
5405
+					if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) {
5406
+						$this->addObject($id);
5407
+						// hacky huh :)
5408
+					}
5409
+					break;
5410
+
5411
+				case 'next':
5412
+					$this->addLooseObjects[$id] = 'all';
5413
+					break;
5414
+
5415
+				case 'nexteven':
5416
+					$this->addLooseObjects[$id] = 'even';
5417
+					break;
5418
+
5419
+				case 'nextodd':
5420
+					$this->addLooseObjects[$id] = 'odd';
5421
+					break;
5422
+			}
5423
+		}
5424
+	}
5425
+
5426
+	/**
5427
+	 * return a storable representation of a specific object
5428
+	 *
5429
+	 * @param $id
5430
+	 * @return string|null
5431
+	 */
5432
+	function serializeObject($id)
5433
+	{
5434
+		if (array_key_exists($id, $this->objects)) {
5435
+			return serialize($this->objects[$id]);
5436
+		}
5437
+
5438
+		return null;
5439
+	}
5440
+
5441
+	/**
5442
+	 * restore an object from its stored representation. Returns its new object id.
5443
+	 *
5444
+	 * @param $obj
5445
+	 * @return int
5446
+	 */
5447
+	function restoreSerializedObject($obj)
5448
+	{
5449
+		$obj_id = $this->openObject();
5450
+		$this->objects[$obj_id] = unserialize($obj);
5451
+		$this->closeObject();
5452
+
5453
+		return $obj_id;
5454
+	}
5455
+
5456
+	/**
5457
+	 * Embeds a file inside the PDF
5458
+	 *
5459
+	 * @param string $filepath path to the file to store inside the PDF
5460
+	 * @param string $embeddedFilename the filename displayed in the list of embedded files
5461
+	 * @param string $description a description in the list of embedded files
5462
+	 */
5463
+	public function addEmbeddedFile(string $filepath, string $embeddedFilename, string $description): void
5464
+	{
5465
+		$this->numObj++;
5466
+		$this->o_embedded_file_dictionary(
5467
+			$this->numObj,
5468
+			'new',
5469
+			[
5470
+				'filepath' => $filepath,
5471
+				'filename' => $embeddedFilename,
5472
+				'description' => $description
5473
+			]
5474
+		);
5475
+	}
5476
+
5477
+	/**
5478
+	 * Add content to the documents info object
5479
+	 *
5480
+	 * @param string|array $label
5481
+	 * @param string       $value
5482
+	 */
5483
+	public function addInfo($label, string $value = ""): void
5484
+	{
5485
+		// this will only work if the label is one of the valid ones.
5486
+		// modify this so that arrays can be passed as well.
5487
+		// if $label is an array then assume that it is key => value pairs
5488
+		// else assume that they are both scalar, anything else will probably error
5489
+		if (is_array($label)) {
5490
+			foreach ($label as $l => $v) {
5491
+				$this->o_info($this->infoObject, $l, (string) $v);
5492
+			}
5493
+		} else {
5494
+			$this->o_info($this->infoObject, $label, $value);
5495
+		}
5496
+	}
5497
+
5498
+	/**
5499
+	 * set the viewer preferences of the document, it is up to the browser to obey these.
5500
+	 *
5501
+	 * @param $label
5502
+	 * @param int $value
5503
+	 */
5504
+	function setPreferences($label, $value = 0)
5505
+	{
5506
+		// this will only work if the label is one of the valid ones.
5507
+		if (is_array($label)) {
5508
+			foreach ($label as $l => $v) {
5509
+				$this->o_catalog($this->catalogId, 'viewerPreferences', [$l => $v]);
5510
+			}
5511
+		} else {
5512
+			$this->o_catalog($this->catalogId, 'viewerPreferences', [$label => $value]);
5513
+		}
5514
+	}
5515
+
5516
+	/**
5517
+	 * extract an integer from a position in a byte stream
5518
+	 *
5519
+	 * @param $data
5520
+	 * @param $pos
5521
+	 * @param $num
5522
+	 * @return int
5523
+	 */
5524
+	private function getBytes(&$data, $pos, $num)
5525
+	{
5526
+		// return the integer represented by $num bytes from $pos within $data
5527
+		$ret = 0;
5528
+		for ($i = 0; $i < $num; $i++) {
5529
+			$ret *= 256;
5530
+			$ret += ord($data[$pos + $i]);
5531
+		}
5532
+
5533
+		return $ret;
5534
+	}
5535
+
5536
+	/**
5537
+	 * Check if image already added to pdf image directory.
5538
+	 * If yes, need not to create again (pass empty data)
5539
+	 *
5540
+	 * @param string $imgname
5541
+	 * @return bool
5542
+	 */
5543
+	function image_iscached($imgname)
5544
+	{
5545
+		return isset($this->imagelist[$imgname]);
5546
+	}
5547
+
5548
+	/**
5549
+	 * add a PNG image into the document, from a GD object
5550
+	 * this should work with remote files
5551
+	 *
5552
+	 * @param \GdImage|resource $img A GD resource
5553
+	 * @param string $file The PNG file
5554
+	 * @param float $x X position
5555
+	 * @param float $y Y position
5556
+	 * @param float $w Width
5557
+	 * @param float $h Height
5558
+	 * @param bool $is_mask true if the image is a mask
5559
+	 * @param bool $mask true if the image is masked
5560
+	 * @throws Exception
5561
+	 */
5562
+	function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5563
+	{
5564
+		if (!function_exists("imagepng")) {
5565
+			throw new \Exception("The PHP GD extension is required, but is not installed.");
5566
+		}
5567
+
5568
+		//if already cached, need not to read again
5569
+		if (isset($this->imagelist[$file])) {
5570
+			$data = null;
5571
+		} else {
5572
+			// Example for transparency handling on new image. Retain for current image
5573
+			// $tIndex = imagecolortransparent($img);
5574
+			// if ($tIndex > 0) {
5575
+			//   $tColor    = imagecolorsforindex($img, $tIndex);
5576
+			//   $new_tIndex    = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']);
5577
+			//   imagefill($new_img, 0, 0, $new_tIndex);
5578
+			//   imagecolortransparent($new_img, $new_tIndex);
5579
+			// }
5580
+			// blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn
5581
+			//imagealphablending($img, true);
5582
+
5583
+			//default, but explicitely set to ensure pdf compatibility
5584
+			imagesavealpha($img, false/*!$is_mask && !$mask*/);
5585
+
5586
+			$error = 0;
5587
+			//DEBUG_IMG_TEMP
5588
+			//debugpng
5589
+			if (defined("DEBUGPNG") && DEBUGPNG) {
5590
+				print '[addImagePng ' . $file . ']';
5591
+			}
5592
+
5593
+			ob_start();
5594
+			@imagepng($img);
5595
+			$data = ob_get_clean();
5596
+
5597
+			if ($data == '') {
5598
+				$error = 1;
5599
+				$errormsg = 'trouble writing file from GD';
5600
+				//DEBUG_IMG_TEMP
5601
+				//debugpng
5602
+				if (defined("DEBUGPNG") && DEBUGPNG) {
5603
+					print 'trouble writing file from GD';
5604
+				}
5605
+			}
5606
+
5607
+			if ($error) {
5608
+				$this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5609
+
5610
+				return;
5611
+			}
5612
+		}  //End isset($this->imagelist[$file]) (png Duplicate removal)
5613
+
5614
+		$this->addPngFromBuf($data, $file, $x, $y, $w, $h, $is_mask, $mask);
5615
+	}
5616
+
5617
+	/**
5618
+	 * @param $file
5619
+	 * @param $x
5620
+	 * @param $y
5621
+	 * @param $w
5622
+	 * @param $h
5623
+	 * @param $byte
5624
+	 */
5625
+	protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte)
5626
+	{
5627
+		// generate images
5628
+		$img = @imagecreatefrompng($file);
5629
+
5630
+		if ($img === false) {
5631
+			return;
5632
+		}
5633
+
5634
+		// FIXME The pixel transformation doesn't work well with 8bit PNGs
5635
+		$eight_bit = ($byte & 4) !== 4;
5636
+
5637
+		$wpx = imagesx($img);
5638
+		$hpx = imagesy($img);
5639
+
5640
+		imagesavealpha($img, false);
5641
+
5642
+		// create temp alpha file
5643
+		$tempfile_alpha = @tempnam($this->tmp, "cpdf_img_");
5644
+		@unlink($tempfile_alpha);
5645
+		$tempfile_alpha = "$tempfile_alpha.png";
5646
+
5647
+		// create temp plain file
5648
+		$tempfile_plain = @tempnam($this->tmp, "cpdf_img_");
5649
+		@unlink($tempfile_plain);
5650
+		$tempfile_plain = "$tempfile_plain.png";
5651
+
5652
+		$imgalpha = imagecreate($wpx, $hpx);
5653
+		imagesavealpha($imgalpha, false);
5654
+
5655
+		// generate gray scale palette (0 -> 255)
5656
+		for ($c = 0; $c < 256; ++$c) {
5657
+			imagecolorallocate($imgalpha, $c, $c, $c);
5658
+		}
5659
+
5660
+		// Use PECL gmagick + Graphics Magic to process transparent PNG images
5661
+		if (extension_loaded("gmagick")) {
5662
+			$gmagick = new \Gmagick($file);
5663
+			$gmagick->setimageformat('png');
5664
+
5665
+			// Get opacity channel (negative of alpha channel)
5666
+			$alpha_channel_neg = clone $gmagick;
5667
+			$alpha_channel_neg->separateimagechannel(\Gmagick::CHANNEL_OPACITY);
5668
+
5669
+			// Negate opacity channel
5670
+			$alpha_channel = new \Gmagick();
5671
+			$alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png");
5672
+			$alpha_channel->compositeimage($alpha_channel_neg, \Gmagick::COMPOSITE_DIFFERENCE, 0, 0);
5673
+			$alpha_channel->separateimagechannel(\Gmagick::CHANNEL_RED);
5674
+			$alpha_channel->writeimage($tempfile_alpha);
5675
+
5676
+			// Cast to 8bit+palette
5677
+			$imgalpha_ = @imagecreatefrompng($tempfile_alpha);
5678
+			imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5679
+			imagedestroy($imgalpha_);
5680
+			imagepng($imgalpha, $tempfile_alpha);
5681
+
5682
+			// Make opaque image
5683
+			$color_channels = new \Gmagick();
5684
+			$color_channels->newimage($wpx, $hpx, "#FFFFFF", "png");
5685
+			$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYRED, 0, 0);
5686
+			$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYGREEN, 0, 0);
5687
+			$color_channels->compositeimage($gmagick, \Gmagick::COMPOSITE_COPYBLUE, 0, 0);
5688
+			$color_channels->writeimage($tempfile_plain);
5689
+
5690
+			$imgplain = @imagecreatefrompng($tempfile_plain);
5691
+		}
5692
+		// Use PECL imagick + ImageMagic to process transparent PNG images
5693
+		elseif (extension_loaded("imagick")) {
5694
+			// Native cloning was added to pecl-imagick in svn commit 263814
5695
+			// the first version containing it was 3.0.1RC1
5696
+			static $imagickClonable = null;
5697
+			if ($imagickClonable === null) {
5698
+				$imagickClonable = true;
5699
+				if (defined('Imagick::IMAGICK_EXTVER')) {
5700
+					$imagickVersion = \Imagick::IMAGICK_EXTVER;
5701
+				} else {
5702
+					$imagickVersion = '0';
5703
+				}
5704
+				if (version_compare($imagickVersion, '0.0.1', '>=')) {
5705
+					$imagickClonable = version_compare($imagickVersion, '3.0.1rc1', '>=');
5706
+				}
5707
+			}
5708
+
5709
+			$imagick = new \Imagick($file);
5710
+			$imagick->setFormat('png');
5711
+
5712
+			// Get opacity channel (negative of alpha channel)
5713
+			if ($imagick->getImageAlphaChannel()) {
5714
+				$alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone();
5715
+				$alpha_channel->separateImageChannel(\Imagick::CHANNEL_ALPHA);
5716
+				// Since ImageMagick7 negate invert transparency as default
5717
+				if (\Imagick::getVersion()['versionNumber'] < 1800) {
5718
+					$alpha_channel->negateImage(true);
5719
+				}
5720
+				$alpha_channel->writeImage($tempfile_alpha);
5721
+
5722
+				// Cast to 8bit+palette
5723
+				$imgalpha_ = @imagecreatefrompng($tempfile_alpha);
5724
+				imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx);
5725
+				imagedestroy($imgalpha_);
5726
+				imagepng($imgalpha, $tempfile_alpha);
5727
+			} else {
5728
+				$tempfile_alpha = null;
5729
+			}
5730
+
5731
+			// Make opaque image
5732
+			$color_channels = new \Imagick();
5733
+			$color_channels->newImage($wpx, $hpx, "#FFFFFF", "png");
5734
+			$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYRED, 0, 0);
5735
+			$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYGREEN, 0, 0);
5736
+			$color_channels->compositeImage($imagick, \Imagick::COMPOSITE_COPYBLUE, 0, 0);
5737
+			$color_channels->writeImage($tempfile_plain);
5738
+
5739
+			$imgplain = @imagecreatefrompng($tempfile_plain);
5740
+		} else {
5741
+			// allocated colors cache
5742
+			$allocated_colors = [];
5743
+
5744
+			// extract alpha channel
5745
+			for ($xpx = 0; $xpx < $wpx; ++$xpx) {
5746
+				for ($ypx = 0; $ypx < $hpx; ++$ypx) {
5747
+					$color = imagecolorat($img, $xpx, $ypx);
5748
+					$col = imagecolorsforindex($img, $color);
5749
+					$alpha = $col['alpha'];
5750
+
5751
+					if ($eight_bit) {
5752
+						// with gamma correction
5753
+						$gammacorr = 2.2;
5754
+						$pixel = round(pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255);
5755
+					} else {
5756
+						// without gamma correction
5757
+						$pixel = (127 - $alpha) * 2;
5758
+
5759
+						$key = $col['red'] . $col['green'] . $col['blue'];
5760
+
5761
+						if (!isset($allocated_colors[$key])) {
5762
+							$pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
5763
+							$allocated_colors[$key] = $pixel_img;
5764
+						} else {
5765
+							$pixel_img = $allocated_colors[$key];
5766
+						}
5767
+
5768
+						imagesetpixel($img, $xpx, $ypx, $pixel_img);
5769
+					}
5770
+
5771
+					imagesetpixel($imgalpha, $xpx, $ypx, $pixel);
5772
+				}
5773
+			}
5774
+
5775
+			// extract image without alpha channel
5776
+			$imgplain = imagecreatetruecolor($wpx, $hpx);
5777
+			imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
5778
+			imagedestroy($img);
5779
+
5780
+			imagepng($imgalpha, $tempfile_alpha);
5781
+			imagepng($imgplain, $tempfile_plain);
5782
+		}
5783
+
5784
+		$this->imageAlphaList[$file] = [$tempfile_alpha, $tempfile_plain];
5785
+
5786
+		// embed mask image
5787
+		if ($tempfile_alpha) {
5788
+			$this->addImagePng($imgalpha, $tempfile_alpha, $x, $y, $w, $h, true);
5789
+			imagedestroy($imgalpha);
5790
+			$this->imageCache[] = $tempfile_alpha;
5791
+		}
5792
+
5793
+		// embed image, masked with previously embedded mask
5794
+		$this->addImagePng($imgplain, $tempfile_plain, $x, $y, $w, $h, false, ($tempfile_alpha !== null));
5795
+		imagedestroy($imgplain);
5796
+		$this->imageCache[] = $tempfile_plain;
5797
+	}
5798
+
5799
+	/**
5800
+	 * add a PNG image into the document, from a file
5801
+	 * this should work with remote files
5802
+	 *
5803
+	 * @param $file
5804
+	 * @param $x
5805
+	 * @param $y
5806
+	 * @param int $w
5807
+	 * @param int $h
5808
+	 * @throws Exception
5809
+	 */
5810
+	function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
5811
+	{
5812
+		if (!function_exists("imagecreatefrompng")) {
5813
+			throw new \Exception("The PHP GD extension is required, but is not installed.");
5814
+		}
5815
+
5816
+		if (isset($this->imageAlphaList[$file])) {
5817
+			[$alphaFile, $plainFile] = $this->imageAlphaList[$file];
5818
+
5819
+			if ($alphaFile) {
5820
+				$img = null;
5821
+				$this->addImagePng($img, $alphaFile, $x, $y, $w, $h, true);
5822
+			}
5823
+
5824
+			$img = null;
5825
+			$this->addImagePng($img, $plainFile, $x, $y, $w, $h, false, ($plainFile !== null));
5826
+			return;
5827
+		}
5828
+
5829
+		//if already cached, need not to read again
5830
+		if (isset($this->imagelist[$file])) {
5831
+			$img = null;
5832
+		} else {
5833
+			$info = file_get_contents($file, false, null, 24, 5);
5834
+			$meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info);
5835
+			$bit_depth = $meta["bitDepth"];
5836
+			$color_type = $meta["colorType"];
5837
+
5838
+			// http://www.w3.org/TR/PNG/#11IHDR
5839
+			// 3 => indexed
5840
+			// 4 => greyscale with alpha
5841
+			// 6 => fullcolor with alpha
5842
+			$is_alpha = in_array($color_type, [4, 6]) || ($color_type == 3 && $bit_depth != 4);
5843
+
5844
+			if ($is_alpha) { // exclude grayscale alpha
5845
+				$this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type);
5846
+				return;
5847
+			}
5848
+
5849
+			//png files typically contain an alpha channel.
5850
+			//pdf file format or class.pdf does not support alpha blending.
5851
+			//on alpha blended images, more transparent areas have a color near black.
5852
+			//This appears in the result on not storing the alpha channel.
5853
+			//Correct would be the box background image or its parent when transparent.
5854
+			//But this would make the image dependent on the background.
5855
+			//Therefore create an image with white background and copy in
5856
+			//A more natural background than black is white.
5857
+			//Therefore create an empty image with white background and merge the
5858
+			//image in with alpha blending.
5859
+			$imgtmp = @imagecreatefrompng($file);
5860
+			if (!$imgtmp) {
5861
+				return;
5862
+			}
5863
+			$sx = imagesx($imgtmp);
5864
+			$sy = imagesy($imgtmp);
5865
+			$img = imagecreatetruecolor($sx, $sy);
5866
+			imagealphablending($img, true);
5867
+
5868
+			// @todo is it still needed ??
5869
+			$ti = imagecolortransparent($imgtmp);
5870
+			if ($ti >= 0) {
5871
+				$tc = imagecolorsforindex($imgtmp, $ti);
5872
+				$ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']);
5873
+				imagefill($img, 0, 0, $ti);
5874
+				imagecolortransparent($img, $ti);
5875
+			} else {
5876
+				imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255));
5877
+			}
5878
+
5879
+			imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy);
5880
+			imagedestroy($imgtmp);
5881
+		}
5882
+		$this->addImagePng($img, $file, $x, $y, $w, $h);
5883
+
5884
+		if ($img) {
5885
+			imagedestroy($img);
5886
+		}
5887
+	}
5888
+
5889
+	/**
5890
+	 * add a PNG image into the document, from a file
5891
+	 * this should work with remote files
5892
+	 *
5893
+	 * @param $file
5894
+	 * @param $x
5895
+	 * @param $y
5896
+	 * @param int $w
5897
+	 * @param int $h
5898
+	 */
5899
+	function addSvgFromFile($file, $x, $y, $w = 0, $h = 0)
5900
+	{
5901
+		$doc = new \Svg\Document();
5902
+		$doc->loadFile($file);
5903
+		$dimensions = $doc->getDimensions();
5904
+
5905
+		$this->save();
5906
+
5907
+		$this->transform([$w / $dimensions["width"], 0, 0, $h / $dimensions["height"], $x, $y]);
5908
+
5909
+		$surface = new \Svg\Surface\SurfaceCpdf($doc, $this);
5910
+		$doc->render($surface);
5911
+
5912
+		$this->restore();
5913
+	}
5914
+
5915
+	/**
5916
+	 * add a PNG image into the document, from a memory buffer of the file
5917
+	 *
5918
+	 * @param $data
5919
+	 * @param $file
5920
+	 * @param $x
5921
+	 * @param $y
5922
+	 * @param float $w
5923
+	 * @param float $h
5924
+	 * @param bool $is_mask
5925
+	 * @param null $mask
5926
+	 */
5927
+	function addPngFromBuf(&$data, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5928
+	{
5929
+		if (isset($this->imagelist[$file])) {
5930
+			$data = null;
5931
+			$info['width'] = $this->imagelist[$file]['w'];
5932
+			$info['height'] = $this->imagelist[$file]['h'];
5933
+			$label = $this->imagelist[$file]['label'];
5934
+		} else {
5935
+			if ($data == null) {
5936
+				$this->addMessage('addPngFromBuf error - data not present!');
5937
+
5938
+				return;
5939
+			}
5940
+
5941
+			$error = 0;
5942
+
5943
+			if (!$error) {
5944
+				$header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5945
+
5946
+				if (mb_substr($data, 0, 8, '8bit') != $header) {
5947
+					$error = 1;
5948
+
5949
+					if (defined("DEBUGPNG") && DEBUGPNG) {
5950
+						print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5951
+					}
5952
+
5953
+					$errormsg = 'this file does not have a valid header';
5954
+				}
5955
+			}
5956
+
5957
+			if (!$error) {
5958
+				// set pointer
5959
+				$p = 8;
5960
+				$len = mb_strlen($data, '8bit');
5961
+
5962
+				// cycle through the file, identifying chunks
5963
+				$haveHeader = 0;
5964
+				$info = [];
5965
+				$idata = '';
5966
+				$pdata = '';
5967
+
5968
+				while ($p < $len) {
5969
+					$chunkLen = $this->getBytes($data, $p, 4);
5970
+					$chunkType = mb_substr($data, $p + 4, 4, '8bit');
5971
+
5972
+					switch ($chunkType) {
5973
+						case 'IHDR':
5974
+							// this is where all the file information comes from
5975
+							$info['width'] = $this->getBytes($data, $p + 8, 4);
5976
+							$info['height'] = $this->getBytes($data, $p + 12, 4);
5977
+							$info['bitDepth'] = ord($data[$p + 16]);
5978
+							$info['colorType'] = ord($data[$p + 17]);
5979
+							$info['compressionMethod'] = ord($data[$p + 18]);
5980
+							$info['filterMethod'] = ord($data[$p + 19]);
5981
+							$info['interlaceMethod'] = ord($data[$p + 20]);
5982
+
5983
+							//print_r($info);
5984
+							$haveHeader = 1;
5985
+							if ($info['compressionMethod'] != 0) {
5986
+								$error = 1;
5987
+
5988
+								//debugpng
5989
+								if (defined("DEBUGPNG") && DEBUGPNG) {
5990
+									print '[addPngFromFile unsupported compression method ' . $file . ']';
5991
+								}
5992
+
5993
+								$errormsg = 'unsupported compression method';
5994
+							}
5995
+
5996
+							if ($info['filterMethod'] != 0) {
5997
+								$error = 1;
5998
+
5999
+								//debugpng
6000
+								if (defined("DEBUGPNG") && DEBUGPNG) {
6001
+									print '[addPngFromFile unsupported filter method ' . $file . ']';
6002
+								}
6003
+
6004
+								$errormsg = 'unsupported filter method';
6005
+							}
6006
+							break;
6007
+
6008
+						case 'PLTE':
6009
+							$pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
6010
+							break;
6011
+
6012
+						case 'IDAT':
6013
+							$idata .= mb_substr($data, $p + 8, $chunkLen, '8bit');
6014
+							break;
6015
+
6016
+						case 'tRNS':
6017
+							//this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk
6018
+							//print "tRNS found, color type = ".$info['colorType']."\n";
6019
+							$transparency = [];
6020
+
6021
+							switch ($info['colorType']) {
6022
+								// indexed color, rbg
6023
+								case 3:
6024
+									/* corresponding to entries in the plte chunk
6025 6025
                                      Alpha for palette index 0: 1 byte
6026 6026
                                      Alpha for palette index 1: 1 byte
6027 6027
                                      ...etc...
6028 6028
                                     */
6029
-                                    // there will be one entry for each palette entry. up until the last non-opaque entry.
6030
-                                    // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
6031
-                                    $transparency['type'] = 'indexed';
6032
-                                    $trans = 0;
6033
-
6034
-                                    for ($i = $chunkLen; $i >= 0; $i--) {
6035
-                                        if (ord($data[$p + 8 + $i]) == 0) {
6036
-                                            $trans = $i;
6037
-                                        }
6038
-                                    }
6039
-
6040
-                                    $transparency['data'] = $trans;
6041
-                                    break;
6042
-
6043
-                                // grayscale
6044
-                                case 0:
6045
-                                    /* corresponding to entries in the plte chunk
6029
+									// there will be one entry for each palette entry. up until the last non-opaque entry.
6030
+									// set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent)
6031
+									$transparency['type'] = 'indexed';
6032
+									$trans = 0;
6033
+
6034
+									for ($i = $chunkLen; $i >= 0; $i--) {
6035
+										if (ord($data[$p + 8 + $i]) == 0) {
6036
+											$trans = $i;
6037
+										}
6038
+									}
6039
+
6040
+									$transparency['data'] = $trans;
6041
+									break;
6042
+
6043
+								// grayscale
6044
+								case 0:
6045
+									/* corresponding to entries in the plte chunk
6046 6046
                                      Gray: 2 bytes, range 0 .. (2^bitdepth)-1
6047 6047
                                     */
6048
-                                    //            $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
6049
-                                    $transparency['type'] = 'indexed';
6050
-                                    $transparency['data'] = ord($data[$p + 8 + 1]);
6051
-                                    break;
6052
-
6053
-                                // truecolor
6054
-                                case 2:
6055
-                                    /* corresponding to entries in the plte chunk
6048
+									//            $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale
6049
+									$transparency['type'] = 'indexed';
6050
+									$transparency['data'] = ord($data[$p + 8 + 1]);
6051
+									break;
6052
+
6053
+								// truecolor
6054
+								case 2:
6055
+									/* corresponding to entries in the plte chunk
6056 6056
                                      Red: 2 bytes, range 0 .. (2^bitdepth)-1
6057 6057
                                      Green: 2 bytes, range 0 .. (2^bitdepth)-1
6058 6058
                                      Blue: 2 bytes, range 0 .. (2^bitdepth)-1
6059 6059
                                     */
6060
-                                    $transparency['r'] = $this->getBytes($data, $p + 8, 2);
6061
-                                    // r from truecolor
6062
-                                    $transparency['g'] = $this->getBytes($data, $p + 10, 2);
6063
-                                    // g from truecolor
6064
-                                    $transparency['b'] = $this->getBytes($data, $p + 12, 2);
6065
-                                    // b from truecolor
6066
-
6067
-                                    $transparency['type'] = 'color-key';
6068
-                                    break;
6069
-
6070
-                                //unsupported transparency type
6071
-                                default:
6072
-                                    if (defined("DEBUGPNG") && DEBUGPNG) {
6073
-                                        print '[addPngFromFile unsupported transparency type ' . $file . ']';
6074
-                                    }
6075
-                                    break;
6076
-                            }
6077
-
6078
-                            // KS End new code
6079
-                            break;
6080
-
6081
-                        default:
6082
-                            break;
6083
-                    }
6084
-
6085
-                    $p += $chunkLen + 12;
6086
-                }
6087
-
6088
-                if (!$haveHeader) {
6089
-                    $error = 1;
6090
-
6091
-                    //debugpng
6092
-                    if (defined("DEBUGPNG") && DEBUGPNG) {
6093
-                        print '[addPngFromFile information header is missing ' . $file . ']';
6094
-                    }
6095
-
6096
-                    $errormsg = 'information header is missing';
6097
-                }
6098
-
6099
-                if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
6100
-                    $error = 1;
6101
-
6102
-                    //debugpng
6103
-                    if (defined("DEBUGPNG") && DEBUGPNG) {
6104
-                        print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
6105
-                    }
6106
-
6107
-                    $errormsg = 'There appears to be no support for interlaced images in pdf.';
6108
-                }
6109
-            }
6110
-
6111
-            if (!$error && $info['bitDepth'] > 8) {
6112
-                $error = 1;
6113
-
6114
-                //debugpng
6115
-                if (defined("DEBUGPNG") && DEBUGPNG) {
6116
-                    print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
6117
-                }
6118
-
6119
-                $errormsg = 'only bit depth of 8 or less is supported';
6120
-            }
6121
-
6122
-            if (!$error) {
6123
-                switch ($info['colorType']) {
6124
-                    case 3:
6125
-                        $color = 'DeviceRGB';
6126
-                        $ncolor = 1;
6127
-                        break;
6128
-
6129
-                    case 2:
6130
-                        $color = 'DeviceRGB';
6131
-                        $ncolor = 3;
6132
-                        break;
6133
-
6134
-                    case 0:
6135
-                        $color = 'DeviceGray';
6136
-                        $ncolor = 1;
6137
-                        break;
6138
-
6139
-                    default:
6140
-                        $error = 1;
6141
-
6142
-                        //debugpng
6143
-                        if (defined("DEBUGPNG") && DEBUGPNG) {
6144
-                            print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
6145
-                        }
6146
-
6147
-                        $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
6148
-                }
6149
-            }
6150
-
6151
-            if ($error) {
6152
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
6153
-
6154
-                return;
6155
-            }
6156
-
6157
-            //print_r($info);
6158
-            // so this image is ok... add it in.
6159
-            $this->numImages++;
6160
-            $im = $this->numImages;
6161
-            $label = "I$im";
6162
-            $this->numObj++;
6163
-
6164
-            //  $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
6165
-            $options = [
6166
-                'label'            => $label,
6167
-                'data'             => $idata,
6168
-                'bitsPerComponent' => $info['bitDepth'],
6169
-                'pdata'            => $pdata,
6170
-                'iw'               => $info['width'],
6171
-                'ih'               => $info['height'],
6172
-                'type'             => 'png',
6173
-                'color'            => $color,
6174
-                'ncolor'           => $ncolor,
6175
-                'masked'           => $mask,
6176
-                'isMask'           => $is_mask
6177
-            ];
6178
-
6179
-            if (isset($transparency)) {
6180
-                $options['transparency'] = $transparency;
6181
-            }
6182
-
6183
-            $this->o_image($this->numObj, 'new', $options);
6184
-            $this->imagelist[$file] = ['label' => $label, 'w' => $info['width'], 'h' => $info['height']];
6185
-        }
6186
-
6187
-        if ($is_mask) {
6188
-            return;
6189
-        }
6190
-
6191
-        if ($w <= 0 && $h <= 0) {
6192
-            $w = $info['width'];
6193
-            $h = $info['height'];
6194
-        }
6195
-
6196
-        if ($w <= 0) {
6197
-            $w = $h / $info['height'] * $info['width'];
6198
-        }
6199
-
6200
-        if ($h <= 0) {
6201
-            $h = $w * $info['height'] / $info['width'];
6202
-        }
6203
-
6204
-        $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
6205
-    }
6206
-
6207
-    /**
6208
-     * add a JPEG image into the document, from a file
6209
-     *
6210
-     * @param $img
6211
-     * @param $x
6212
-     * @param $y
6213
-     * @param int $w
6214
-     * @param int $h
6215
-     */
6216
-    function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
6217
-    {
6218
-        // attempt to add a jpeg image straight from a file, using no GD commands
6219
-        // note that this function is unable to operate on a remote file.
6220
-
6221
-        if (!file_exists($img)) {
6222
-            return;
6223
-        }
6224
-
6225
-        if ($this->image_iscached($img)) {
6226
-            $data = null;
6227
-            $imageWidth = $this->imagelist[$img]['w'];
6228
-            $imageHeight = $this->imagelist[$img]['h'];
6229
-            $channels = $this->imagelist[$img]['c'];
6230
-        } else {
6231
-            $tmp = getimagesize($img);
6232
-            $imageWidth = $tmp[0];
6233
-            $imageHeight = $tmp[1];
6234
-
6235
-            if (isset($tmp['channels'])) {
6236
-                $channels = $tmp['channels'];
6237
-            } else {
6238
-                $channels = 3;
6239
-            }
6240
-
6241
-            $data = file_get_contents($img);
6242
-        }
6243
-
6244
-        if ($w <= 0 && $h <= 0) {
6245
-            $w = $imageWidth;
6246
-        }
6247
-
6248
-        if ($w == 0) {
6249
-            $w = $h / $imageHeight * $imageWidth;
6250
-        }
6251
-
6252
-        if ($h == 0) {
6253
-            $h = $w * $imageHeight / $imageWidth;
6254
-        }
6255
-
6256
-        $this->addJpegImage_common($data, $img, $imageWidth, $imageHeight, $x, $y, $w, $h, $channels);
6257
-    }
6258
-
6259
-    /**
6260
-     * common code used by the two JPEG adding functions
6261
-     * @param $data
6262
-     * @param $imgname
6263
-     * @param $imageWidth
6264
-     * @param $imageHeight
6265
-     * @param $x
6266
-     * @param $y
6267
-     * @param int $w
6268
-     * @param int $h
6269
-     * @param int $channels
6270
-     */
6271
-    private function addJpegImage_common(
6272
-        &$data,
6273
-        $imgname,
6274
-        $imageWidth,
6275
-        $imageHeight,
6276
-        $x,
6277
-        $y,
6278
-        $w = 0,
6279
-        $h = 0,
6280
-        $channels = 3
6281
-    ) {
6282
-        if ($this->image_iscached($imgname)) {
6283
-            $label = $this->imagelist[$imgname]['label'];
6284
-            //debugpng
6285
-            //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
6286
-
6287
-        } else {
6288
-            if ($data == null) {
6289
-                $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
6290
-
6291
-                return;
6292
-            }
6293
-
6294
-            // note that this function is not to be called externally
6295
-            // it is just the common code between the GD and the file options
6296
-            $this->numImages++;
6297
-            $im = $this->numImages;
6298
-            $label = "I$im";
6299
-            $this->numObj++;
6300
-
6301
-            $this->o_image(
6302
-                $this->numObj,
6303
-                'new',
6304
-                [
6305
-                    'label'    => $label,
6306
-                    'data'     => &$data,
6307
-                    'iw'       => $imageWidth,
6308
-                    'ih'       => $imageHeight,
6309
-                    'channels' => $channels
6310
-                ]
6311
-            );
6312
-
6313
-            $this->imagelist[$imgname] = [
6314
-                'label' => $label,
6315
-                'w'     => $imageWidth,
6316
-                'h'     => $imageHeight,
6317
-                'c'     => $channels
6318
-            ];
6319
-        }
6320
-
6321
-        $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
6322
-    }
6323
-
6324
-    /**
6325
-     * specify where the document should open when it first starts
6326
-     *
6327
-     * @param $style
6328
-     * @param int $a
6329
-     * @param int $b
6330
-     * @param int $c
6331
-     */
6332
-    function openHere($style, $a = 0, $b = 0, $c = 0)
6333
-    {
6334
-        // this function will open the document at a specified page, in a specified style
6335
-        // the values for style, and the required parameters are:
6336
-        // 'XYZ'  left, top, zoom
6337
-        // 'Fit'
6338
-        // 'FitH' top
6339
-        // 'FitV' left
6340
-        // 'FitR' left,bottom,right
6341
-        // 'FitB'
6342
-        // 'FitBH' top
6343
-        // 'FitBV' left
6344
-        $this->numObj++;
6345
-        $this->o_destination(
6346
-            $this->numObj,
6347
-            'new',
6348
-            ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6349
-        );
6350
-        $id = $this->catalogId;
6351
-        $this->o_catalog($id, 'openHere', $this->numObj);
6352
-    }
6353
-
6354
-    /**
6355
-     * Add JavaScript code to the PDF document
6356
-     *
6357
-     * @param string $code
6358
-     */
6359
-    function addJavascript($code)
6360
-    {
6361
-        $this->javascript .= $code;
6362
-    }
6363
-
6364
-    /**
6365
-     * create a labelled destination within the document
6366
-     *
6367
-     * @param $label
6368
-     * @param $style
6369
-     * @param int $a
6370
-     * @param int $b
6371
-     * @param int $c
6372
-     */
6373
-    function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
6374
-    {
6375
-        // associates the given label with the destination, it is done this way so that a destination can be specified after
6376
-        // it has been linked to
6377
-        // styles are the same as the 'openHere' function
6378
-        $this->numObj++;
6379
-        $this->o_destination(
6380
-            $this->numObj,
6381
-            'new',
6382
-            ['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6383
-        );
6384
-        $id = $this->numObj;
6385
-
6386
-        // store the label->idf relationship, note that this means that labels can be used only once
6387
-        $this->destinations["$label"] = $id;
6388
-    }
6389
-
6390
-    /**
6391
-     * define font families, this is used to initialize the font families for the default fonts
6392
-     * and for the user to add new ones for their fonts. The default bahavious can be overridden should
6393
-     * that be desired.
6394
-     *
6395
-     * @param $family
6396
-     * @param string $options
6397
-     */
6398
-    function setFontFamily($family, $options = '')
6399
-    {
6400
-        if (!is_array($options)) {
6401
-            if ($family === 'init') {
6402
-                // set the known family groups
6403
-                // these font families will be used to enable bold and italic markers to be included
6404
-                // within text streams. html forms will be used... <b></b> <i></i>
6405
-                $this->fontFamilies['Helvetica.afm'] =
6406
-                    [
6407
-                        'b'  => 'Helvetica-Bold.afm',
6408
-                        'i'  => 'Helvetica-Oblique.afm',
6409
-                        'bi' => 'Helvetica-BoldOblique.afm',
6410
-                        'ib' => 'Helvetica-BoldOblique.afm'
6411
-                    ];
6412
-
6413
-                $this->fontFamilies['Courier.afm'] =
6414
-                    [
6415
-                        'b'  => 'Courier-Bold.afm',
6416
-                        'i'  => 'Courier-Oblique.afm',
6417
-                        'bi' => 'Courier-BoldOblique.afm',
6418
-                        'ib' => 'Courier-BoldOblique.afm'
6419
-                    ];
6420
-
6421
-                $this->fontFamilies['Times-Roman.afm'] =
6422
-                    [
6423
-                        'b'  => 'Times-Bold.afm',
6424
-                        'i'  => 'Times-Italic.afm',
6425
-                        'bi' => 'Times-BoldItalic.afm',
6426
-                        'ib' => 'Times-BoldItalic.afm'
6427
-                    ];
6428
-            }
6429
-        } else {
6430
-
6431
-            // the user is trying to set a font family
6432
-            // note that this can also be used to set the base ones to something else
6433
-            if (mb_strlen($family)) {
6434
-                $this->fontFamilies[$family] = $options;
6435
-            }
6436
-        }
6437
-    }
6438
-
6439
-    /**
6440
-     * used to add messages for use in debugging
6441
-     *
6442
-     * @param $message
6443
-     */
6444
-    function addMessage($message)
6445
-    {
6446
-        $this->messages .= $message . "\n";
6447
-    }
6448
-
6449
-    /**
6450
-     * a few functions which should allow the document to be treated transactionally.
6451
-     *
6452
-     * @param $action
6453
-     */
6454
-    function transaction($action)
6455
-    {
6456
-        switch ($action) {
6457
-            case 'start':
6458
-                // store all the data away into the checkpoint variable
6459
-                $data = get_object_vars($this);
6460
-                $this->checkpoint = $data;
6461
-                unset($data);
6462
-                break;
6463
-
6464
-            case 'commit':
6465
-                if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
6466
-                    $tmp = $this->checkpoint['checkpoint'];
6467
-                    $this->checkpoint = $tmp;
6468
-                    unset($tmp);
6469
-                } else {
6470
-                    $this->checkpoint = '';
6471
-                }
6472
-                break;
6473
-
6474
-            case 'rewind':
6475
-                // do not destroy the current checkpoint, but move us back to the state then, so that we can try again
6476
-                if (is_array($this->checkpoint)) {
6477
-                    // can only abort if were inside a checkpoint
6478
-                    $tmp = $this->checkpoint;
6479
-
6480
-                    foreach ($tmp as $k => $v) {
6481
-                        if ($k !== 'checkpoint') {
6482
-                            $this->$k = $v;
6483
-                        }
6484
-                    }
6485
-                    unset($tmp);
6486
-                }
6487
-                break;
6488
-
6489
-            case 'abort':
6490
-                if (is_array($this->checkpoint)) {
6491
-                    // can only abort if were inside a checkpoint
6492
-                    $tmp = $this->checkpoint;
6493
-                    foreach ($tmp as $k => $v) {
6494
-                        $this->$k = $v;
6495
-                    }
6496
-                    unset($tmp);
6497
-                }
6498
-                break;
6499
-        }
6500
-    }
6060
+									$transparency['r'] = $this->getBytes($data, $p + 8, 2);
6061
+									// r from truecolor
6062
+									$transparency['g'] = $this->getBytes($data, $p + 10, 2);
6063
+									// g from truecolor
6064
+									$transparency['b'] = $this->getBytes($data, $p + 12, 2);
6065
+									// b from truecolor
6066
+
6067
+									$transparency['type'] = 'color-key';
6068
+									break;
6069
+
6070
+								//unsupported transparency type
6071
+								default:
6072
+									if (defined("DEBUGPNG") && DEBUGPNG) {
6073
+										print '[addPngFromFile unsupported transparency type ' . $file . ']';
6074
+									}
6075
+									break;
6076
+							}
6077
+
6078
+							// KS End new code
6079
+							break;
6080
+
6081
+						default:
6082
+							break;
6083
+					}
6084
+
6085
+					$p += $chunkLen + 12;
6086
+				}
6087
+
6088
+				if (!$haveHeader) {
6089
+					$error = 1;
6090
+
6091
+					//debugpng
6092
+					if (defined("DEBUGPNG") && DEBUGPNG) {
6093
+						print '[addPngFromFile information header is missing ' . $file . ']';
6094
+					}
6095
+
6096
+					$errormsg = 'information header is missing';
6097
+				}
6098
+
6099
+				if (isset($info['interlaceMethod']) && $info['interlaceMethod']) {
6100
+					$error = 1;
6101
+
6102
+					//debugpng
6103
+					if (defined("DEBUGPNG") && DEBUGPNG) {
6104
+						print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
6105
+					}
6106
+
6107
+					$errormsg = 'There appears to be no support for interlaced images in pdf.';
6108
+				}
6109
+			}
6110
+
6111
+			if (!$error && $info['bitDepth'] > 8) {
6112
+				$error = 1;
6113
+
6114
+				//debugpng
6115
+				if (defined("DEBUGPNG") && DEBUGPNG) {
6116
+					print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
6117
+				}
6118
+
6119
+				$errormsg = 'only bit depth of 8 or less is supported';
6120
+			}
6121
+
6122
+			if (!$error) {
6123
+				switch ($info['colorType']) {
6124
+					case 3:
6125
+						$color = 'DeviceRGB';
6126
+						$ncolor = 1;
6127
+						break;
6128
+
6129
+					case 2:
6130
+						$color = 'DeviceRGB';
6131
+						$ncolor = 3;
6132
+						break;
6133
+
6134
+					case 0:
6135
+						$color = 'DeviceGray';
6136
+						$ncolor = 1;
6137
+						break;
6138
+
6139
+					default:
6140
+						$error = 1;
6141
+
6142
+						//debugpng
6143
+						if (defined("DEBUGPNG") && DEBUGPNG) {
6144
+							print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
6145
+						}
6146
+
6147
+						$errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
6148
+				}
6149
+			}
6150
+
6151
+			if ($error) {
6152
+				$this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
6153
+
6154
+				return;
6155
+			}
6156
+
6157
+			//print_r($info);
6158
+			// so this image is ok... add it in.
6159
+			$this->numImages++;
6160
+			$im = $this->numImages;
6161
+			$label = "I$im";
6162
+			$this->numObj++;
6163
+
6164
+			//  $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width']));
6165
+			$options = [
6166
+				'label'            => $label,
6167
+				'data'             => $idata,
6168
+				'bitsPerComponent' => $info['bitDepth'],
6169
+				'pdata'            => $pdata,
6170
+				'iw'               => $info['width'],
6171
+				'ih'               => $info['height'],
6172
+				'type'             => 'png',
6173
+				'color'            => $color,
6174
+				'ncolor'           => $ncolor,
6175
+				'masked'           => $mask,
6176
+				'isMask'           => $is_mask
6177
+			];
6178
+
6179
+			if (isset($transparency)) {
6180
+				$options['transparency'] = $transparency;
6181
+			}
6182
+
6183
+			$this->o_image($this->numObj, 'new', $options);
6184
+			$this->imagelist[$file] = ['label' => $label, 'w' => $info['width'], 'h' => $info['height']];
6185
+		}
6186
+
6187
+		if ($is_mask) {
6188
+			return;
6189
+		}
6190
+
6191
+		if ($w <= 0 && $h <= 0) {
6192
+			$w = $info['width'];
6193
+			$h = $info['height'];
6194
+		}
6195
+
6196
+		if ($w <= 0) {
6197
+			$w = $h / $info['height'] * $info['width'];
6198
+		}
6199
+
6200
+		if ($h <= 0) {
6201
+			$h = $w * $info['height'] / $info['width'];
6202
+		}
6203
+
6204
+		$this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label));
6205
+	}
6206
+
6207
+	/**
6208
+	 * add a JPEG image into the document, from a file
6209
+	 *
6210
+	 * @param $img
6211
+	 * @param $x
6212
+	 * @param $y
6213
+	 * @param int $w
6214
+	 * @param int $h
6215
+	 */
6216
+	function addJpegFromFile($img, $x, $y, $w = 0, $h = 0)
6217
+	{
6218
+		// attempt to add a jpeg image straight from a file, using no GD commands
6219
+		// note that this function is unable to operate on a remote file.
6220
+
6221
+		if (!file_exists($img)) {
6222
+			return;
6223
+		}
6224
+
6225
+		if ($this->image_iscached($img)) {
6226
+			$data = null;
6227
+			$imageWidth = $this->imagelist[$img]['w'];
6228
+			$imageHeight = $this->imagelist[$img]['h'];
6229
+			$channels = $this->imagelist[$img]['c'];
6230
+		} else {
6231
+			$tmp = getimagesize($img);
6232
+			$imageWidth = $tmp[0];
6233
+			$imageHeight = $tmp[1];
6234
+
6235
+			if (isset($tmp['channels'])) {
6236
+				$channels = $tmp['channels'];
6237
+			} else {
6238
+				$channels = 3;
6239
+			}
6240
+
6241
+			$data = file_get_contents($img);
6242
+		}
6243
+
6244
+		if ($w <= 0 && $h <= 0) {
6245
+			$w = $imageWidth;
6246
+		}
6247
+
6248
+		if ($w == 0) {
6249
+			$w = $h / $imageHeight * $imageWidth;
6250
+		}
6251
+
6252
+		if ($h == 0) {
6253
+			$h = $w * $imageHeight / $imageWidth;
6254
+		}
6255
+
6256
+		$this->addJpegImage_common($data, $img, $imageWidth, $imageHeight, $x, $y, $w, $h, $channels);
6257
+	}
6258
+
6259
+	/**
6260
+	 * common code used by the two JPEG adding functions
6261
+	 * @param $data
6262
+	 * @param $imgname
6263
+	 * @param $imageWidth
6264
+	 * @param $imageHeight
6265
+	 * @param $x
6266
+	 * @param $y
6267
+	 * @param int $w
6268
+	 * @param int $h
6269
+	 * @param int $channels
6270
+	 */
6271
+	private function addJpegImage_common(
6272
+		&$data,
6273
+		$imgname,
6274
+		$imageWidth,
6275
+		$imageHeight,
6276
+		$x,
6277
+		$y,
6278
+		$w = 0,
6279
+		$h = 0,
6280
+		$channels = 3
6281
+	) {
6282
+		if ($this->image_iscached($imgname)) {
6283
+			$label = $this->imagelist[$imgname]['label'];
6284
+			//debugpng
6285
+			//if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']';
6286
+
6287
+		} else {
6288
+			if ($data == null) {
6289
+				$this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
6290
+
6291
+				return;
6292
+			}
6293
+
6294
+			// note that this function is not to be called externally
6295
+			// it is just the common code between the GD and the file options
6296
+			$this->numImages++;
6297
+			$im = $this->numImages;
6298
+			$label = "I$im";
6299
+			$this->numObj++;
6300
+
6301
+			$this->o_image(
6302
+				$this->numObj,
6303
+				'new',
6304
+				[
6305
+					'label'    => $label,
6306
+					'data'     => &$data,
6307
+					'iw'       => $imageWidth,
6308
+					'ih'       => $imageHeight,
6309
+					'channels' => $channels
6310
+				]
6311
+			);
6312
+
6313
+			$this->imagelist[$imgname] = [
6314
+				'label' => $label,
6315
+				'w'     => $imageWidth,
6316
+				'h'     => $imageHeight,
6317
+				'c'     => $channels
6318
+			];
6319
+		}
6320
+
6321
+		$this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label));
6322
+	}
6323
+
6324
+	/**
6325
+	 * specify where the document should open when it first starts
6326
+	 *
6327
+	 * @param $style
6328
+	 * @param int $a
6329
+	 * @param int $b
6330
+	 * @param int $c
6331
+	 */
6332
+	function openHere($style, $a = 0, $b = 0, $c = 0)
6333
+	{
6334
+		// this function will open the document at a specified page, in a specified style
6335
+		// the values for style, and the required parameters are:
6336
+		// 'XYZ'  left, top, zoom
6337
+		// 'Fit'
6338
+		// 'FitH' top
6339
+		// 'FitV' left
6340
+		// 'FitR' left,bottom,right
6341
+		// 'FitB'
6342
+		// 'FitBH' top
6343
+		// 'FitBV' left
6344
+		$this->numObj++;
6345
+		$this->o_destination(
6346
+			$this->numObj,
6347
+			'new',
6348
+			['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6349
+		);
6350
+		$id = $this->catalogId;
6351
+		$this->o_catalog($id, 'openHere', $this->numObj);
6352
+	}
6353
+
6354
+	/**
6355
+	 * Add JavaScript code to the PDF document
6356
+	 *
6357
+	 * @param string $code
6358
+	 */
6359
+	function addJavascript($code)
6360
+	{
6361
+		$this->javascript .= $code;
6362
+	}
6363
+
6364
+	/**
6365
+	 * create a labelled destination within the document
6366
+	 *
6367
+	 * @param $label
6368
+	 * @param $style
6369
+	 * @param int $a
6370
+	 * @param int $b
6371
+	 * @param int $c
6372
+	 */
6373
+	function addDestination($label, $style, $a = 0, $b = 0, $c = 0)
6374
+	{
6375
+		// associates the given label with the destination, it is done this way so that a destination can be specified after
6376
+		// it has been linked to
6377
+		// styles are the same as the 'openHere' function
6378
+		$this->numObj++;
6379
+		$this->o_destination(
6380
+			$this->numObj,
6381
+			'new',
6382
+			['page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c]
6383
+		);
6384
+		$id = $this->numObj;
6385
+
6386
+		// store the label->idf relationship, note that this means that labels can be used only once
6387
+		$this->destinations["$label"] = $id;
6388
+	}
6389
+
6390
+	/**
6391
+	 * define font families, this is used to initialize the font families for the default fonts
6392
+	 * and for the user to add new ones for their fonts. The default bahavious can be overridden should
6393
+	 * that be desired.
6394
+	 *
6395
+	 * @param $family
6396
+	 * @param string $options
6397
+	 */
6398
+	function setFontFamily($family, $options = '')
6399
+	{
6400
+		if (!is_array($options)) {
6401
+			if ($family === 'init') {
6402
+				// set the known family groups
6403
+				// these font families will be used to enable bold and italic markers to be included
6404
+				// within text streams. html forms will be used... <b></b> <i></i>
6405
+				$this->fontFamilies['Helvetica.afm'] =
6406
+					[
6407
+						'b'  => 'Helvetica-Bold.afm',
6408
+						'i'  => 'Helvetica-Oblique.afm',
6409
+						'bi' => 'Helvetica-BoldOblique.afm',
6410
+						'ib' => 'Helvetica-BoldOblique.afm'
6411
+					];
6412
+
6413
+				$this->fontFamilies['Courier.afm'] =
6414
+					[
6415
+						'b'  => 'Courier-Bold.afm',
6416
+						'i'  => 'Courier-Oblique.afm',
6417
+						'bi' => 'Courier-BoldOblique.afm',
6418
+						'ib' => 'Courier-BoldOblique.afm'
6419
+					];
6420
+
6421
+				$this->fontFamilies['Times-Roman.afm'] =
6422
+					[
6423
+						'b'  => 'Times-Bold.afm',
6424
+						'i'  => 'Times-Italic.afm',
6425
+						'bi' => 'Times-BoldItalic.afm',
6426
+						'ib' => 'Times-BoldItalic.afm'
6427
+					];
6428
+			}
6429
+		} else {
6430
+
6431
+			// the user is trying to set a font family
6432
+			// note that this can also be used to set the base ones to something else
6433
+			if (mb_strlen($family)) {
6434
+				$this->fontFamilies[$family] = $options;
6435
+			}
6436
+		}
6437
+	}
6438
+
6439
+	/**
6440
+	 * used to add messages for use in debugging
6441
+	 *
6442
+	 * @param $message
6443
+	 */
6444
+	function addMessage($message)
6445
+	{
6446
+		$this->messages .= $message . "\n";
6447
+	}
6448
+
6449
+	/**
6450
+	 * a few functions which should allow the document to be treated transactionally.
6451
+	 *
6452
+	 * @param $action
6453
+	 */
6454
+	function transaction($action)
6455
+	{
6456
+		switch ($action) {
6457
+			case 'start':
6458
+				// store all the data away into the checkpoint variable
6459
+				$data = get_object_vars($this);
6460
+				$this->checkpoint = $data;
6461
+				unset($data);
6462
+				break;
6463
+
6464
+			case 'commit':
6465
+				if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) {
6466
+					$tmp = $this->checkpoint['checkpoint'];
6467
+					$this->checkpoint = $tmp;
6468
+					unset($tmp);
6469
+				} else {
6470
+					$this->checkpoint = '';
6471
+				}
6472
+				break;
6473
+
6474
+			case 'rewind':
6475
+				// do not destroy the current checkpoint, but move us back to the state then, so that we can try again
6476
+				if (is_array($this->checkpoint)) {
6477
+					// can only abort if were inside a checkpoint
6478
+					$tmp = $this->checkpoint;
6479
+
6480
+					foreach ($tmp as $k => $v) {
6481
+						if ($k !== 'checkpoint') {
6482
+							$this->$k = $v;
6483
+						}
6484
+					}
6485
+					unset($tmp);
6486
+				}
6487
+				break;
6488
+
6489
+			case 'abort':
6490
+				if (is_array($this->checkpoint)) {
6491
+					// can only abort if were inside a checkpoint
6492
+					$tmp = $this->checkpoint;
6493
+					foreach ($tmp as $k => $v) {
6494
+						$this->$k = $v;
6495
+					}
6496
+					unset($tmp);
6497
+				}
6498
+				break;
6499
+		}
6500
+	}
6501 6501
 }
Please login to merge, or discard this patch.
Spacing   +232 added lines, -232 removed lines patch added patch discarded remove patch
@@ -18,24 +18,24 @@  discard block
 block discarded – undo
18 18
     const PDF_VERSION = '1.7';
19 19
 
20 20
     const ACROFORM_SIG_SIGNATURESEXISTS = 0x0001;
21
-    const ACROFORM_SIG_APPENDONLY =       0x0002;
21
+    const ACROFORM_SIG_APPENDONLY = 0x0002;
22 22
 
23
-    const ACROFORM_FIELD_BUTTON =   'Btn';
24
-    const ACROFORM_FIELD_TEXT =     'Tx';
25
-    const ACROFORM_FIELD_CHOICE =   'Ch';
26
-    const ACROFORM_FIELD_SIG =      'Sig';
23
+    const ACROFORM_FIELD_BUTTON = 'Btn';
24
+    const ACROFORM_FIELD_TEXT = 'Tx';
25
+    const ACROFORM_FIELD_CHOICE = 'Ch';
26
+    const ACROFORM_FIELD_SIG = 'Sig';
27 27
 
28
-    const ACROFORM_FIELD_READONLY =               0x0001;
29
-    const ACROFORM_FIELD_REQUIRED =               0x0002;
28
+    const ACROFORM_FIELD_READONLY = 0x0001;
29
+    const ACROFORM_FIELD_REQUIRED = 0x0002;
30 30
 
31
-    const ACROFORM_FIELD_TEXT_MULTILINE =         0x1000;
32
-    const ACROFORM_FIELD_TEXT_PASSWORD =          0x2000;
33
-    const ACROFORM_FIELD_TEXT_RICHTEXT =         0x10000;
31
+    const ACROFORM_FIELD_TEXT_MULTILINE = 0x1000;
32
+    const ACROFORM_FIELD_TEXT_PASSWORD = 0x2000;
33
+    const ACROFORM_FIELD_TEXT_RICHTEXT = 0x10000;
34 34
 
35
-    const ACROFORM_FIELD_CHOICE_COMBO =          0x20000;
36
-    const ACROFORM_FIELD_CHOICE_EDIT =           0x40000;
37
-    const ACROFORM_FIELD_CHOICE_SORT =           0x80000;
38
-    const ACROFORM_FIELD_CHOICE_MULTISELECT =   0x200000;
35
+    const ACROFORM_FIELD_CHOICE_COMBO = 0x20000;
36
+    const ACROFORM_FIELD_CHOICE_EDIT = 0x40000;
37
+    const ACROFORM_FIELD_CHOICE_SORT = 0x80000;
38
+    const ACROFORM_FIELD_CHOICE_MULTISELECT = 0x200000;
39 39
 
40 40
     const XOBJECT_SUBTYPE_FORM = 'Form';
41 41
 
@@ -454,16 +454,16 @@  discard block
 block discarded – undo
454 454
                     case 'XYZ':
455 455
                     /** @noinspection PhpMissingBreakStatementInspection */
456 456
                     case 'FitR':
457
-                        $tmp = ' ' . $options['p3'] . $tmp;
457
+                        $tmp = ' '.$options['p3'].$tmp;
458 458
                     case 'FitH':
459 459
                     case 'FitV':
460 460
                     case 'FitBH':
461 461
                     /** @noinspection PhpMissingBreakStatementInspection */
462 462
                     case 'FitBV':
463
-                        $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp;
463
+                        $tmp = ' '.$options['p1'].' '.$options['p2'].$tmp;
464 464
                     case 'Fit':
465 465
                     case 'FitB':
466
-                        $tmp = $options['type'] . $tmp;
466
+                        $tmp = $options['type'].$tmp;
467 467
                         $this->objects[$id]['info']['string'] = $tmp;
468 468
                         $this->objects[$id]['info']['page'] = $options['page'];
469 469
                 }
@@ -473,7 +473,7 @@  discard block
 block discarded – undo
473 473
                 $o = &$this->objects[$id];
474 474
 
475 475
                 $tmp = $o['info'];
476
-                $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj";
476
+                $res = "\n$id 0 obj\n".'['.$tmp['page'].' 0 R /'.$tmp['string']."]\nendobj";
477 477
 
478 478
                 return $res;
479 479
         }
@@ -509,12 +509,12 @@  discard block
 block discarded – undo
509 509
                         case 'CenterWindow':
510 510
                         case 'DisplayDocTitle':
511 511
                         case 'PickTrayByPDFSize':
512
-                            $o['info'][$k] = (bool)$v;
512
+                            $o['info'][$k] = (bool) $v;
513 513
                             break;
514 514
 
515 515
                         // Integer keys
516 516
                         case 'NumCopies':
517
-                            $o['info'][$k] = (int)$v;
517
+                            $o['info'][$k] = (int) $v;
518 518
                             break;
519 519
 
520 520
                         // Name keys
@@ -522,33 +522,33 @@  discard block
 block discarded – undo
522 522
                         case 'ViewClip':
523 523
                         case 'PrintClip':
524 524
                         case 'PrintArea':
525
-                            $o['info'][$k] = (string)$v;
525
+                            $o['info'][$k] = (string) $v;
526 526
                             break;
527 527
 
528 528
                         // Named with limited valid values
529 529
                         case 'NonFullScreenPageMode':
530
-                            if (!in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
530
+                            if ( ! in_array($v, ['UseNone', 'UseOutlines', 'UseThumbs', 'UseOC'])) {
531 531
                                 break;
532 532
                             }
533 533
                             $o['info'][$k] = $v;
534 534
                             break;
535 535
 
536 536
                         case 'Direction':
537
-                            if (!in_array($v, ['L2R', 'R2L'])) {
537
+                            if ( ! in_array($v, ['L2R', 'R2L'])) {
538 538
                                 break;
539 539
                             }
540 540
                             $o['info'][$k] = $v;
541 541
                             break;
542 542
 
543 543
                         case 'PrintScaling':
544
-                            if (!in_array($v, ['None', 'AppDefault'])) {
544
+                            if ( ! in_array($v, ['None', 'AppDefault'])) {
545 545
                                 break;
546 546
                             }
547 547
                             $o['info'][$k] = $v;
548 548
                             break;
549 549
 
550 550
                         case 'Duplex':
551
-                            if (!in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
551
+                            if ( ! in_array($v, ['None', 'Simplex', 'DuplexFlipShortEdge', 'DuplexFlipLongEdge'])) {
552 552
                                 break;
553 553
                             }
554 554
                             $o['info'][$k] = $v;
@@ -558,7 +558,7 @@  discard block
 block discarded – undo
558 558
                         case 'PrintPageRange':
559 559
                             // Cast to integer array
560 560
                             foreach ($v as $vK => $vV) {
561
-                                $v[$vK] = (int)$vV;
561
+                                $v[$vK] = (int) $vV;
562 562
                             }
563 563
                             $o['info'][$k] = array_values($v);
564 564
                             break;
@@ -572,13 +572,13 @@  discard block
 block discarded – undo
572 572
 
573 573
                 foreach ($o['info'] as $k => $v) {
574 574
                     if (is_string($v)) {
575
-                        $v = '/' . $v;
575
+                        $v = '/'.$v;
576 576
                     } elseif (is_int($v)) {
577 577
                         $v = (string) $v;
578 578
                     } elseif (is_bool($v)) {
579 579
                         $v = ($v ? 'true' : 'false');
580 580
                     } elseif (is_array($v)) {
581
-                        $v = '[' . implode(' ', $v) . ']';
581
+                        $v = '['.implode(' ', $v).']';
582 582
                     }
583 583
                     $res .= "\n/$k $v";
584 584
                 }
@@ -619,7 +619,7 @@  discard block
 block discarded – undo
619 619
                 break;
620 620
 
621 621
             case 'viewerPreferences':
622
-                if (!isset($o['info']['viewerPreferences'])) {
622
+                if ( ! isset($o['info']['viewerPreferences'])) {
623 623
                     $this->numObj++;
624 624
                     $this->o_viewerPreferences($this->numObj, 'new');
625 625
                     $o['info']['viewerPreferences'] = $this->numObj;
@@ -690,7 +690,7 @@  discard block
 block discarded – undo
690 690
                 break;
691 691
 
692 692
             case 'page':
693
-                if (!is_array($options)) {
693
+                if ( ! is_array($options)) {
694 694
                     // then it will just be the id of the new page
695 695
                     $o['info']['pages'][] = $options;
696 696
                 } else {
@@ -757,7 +757,7 @@  discard block
 block discarded – undo
757 757
                         $res .= "$v 0 R\n";
758 758
                     }
759 759
 
760
-                    $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']);
760
+                    $res .= "]\n/Count ".count($this->objects[$id]['info']['pages']);
761 761
 
762 762
                     if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) ||
763 763
                         isset($o['info']['procset']) ||
@@ -766,13 +766,13 @@  discard block
 block discarded – undo
766 766
                         $res .= "\n/Resources <<";
767 767
 
768 768
                         if (isset($o['info']['procset'])) {
769
-                            $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R";
769
+                            $res .= "\n/ProcSet ".$o['info']['procset']." 0 R";
770 770
                         }
771 771
 
772 772
                         if (isset($o['info']['fonts']) && count($o['info']['fonts'])) {
773 773
                             $res .= "\n/Font << ";
774 774
                             foreach ($o['info']['fonts'] as $finfo) {
775
-                                $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
775
+                                $res .= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
776 776
                             }
777 777
                             $res .= "\n>>";
778 778
                         }
@@ -780,7 +780,7 @@  discard block
 block discarded – undo
780 780
                         if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) {
781 781
                             $res .= "\n/XObject << ";
782 782
                             foreach ($o['info']['xObjects'] as $finfo) {
783
-                                $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
783
+                                $res .= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
784 784
                             }
785 785
                             $res .= "\n>>";
786 786
                         }
@@ -788,7 +788,7 @@  discard block
 block discarded – undo
788 788
                         if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) {
789 789
                             $res .= "\n/ExtGState << ";
790 790
                             foreach ($o['info']['extGStates'] as $gstate) {
791
-                                $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R";
791
+                                $res .= "\n/GS".$gstate['stateNum']." ".$gstate['objNum']." 0 R";
792 792
                             }
793 793
                             $res .= "\n>>";
794 794
                         }
@@ -796,13 +796,13 @@  discard block
 block discarded – undo
796 796
                         $res .= "\n>>";
797 797
                         if (isset($o['info']['mediaBox'])) {
798 798
                             $tmp = $o['info']['mediaBox'];
799
-                            $res .= "\n/MediaBox [" . sprintf(
799
+                            $res .= "\n/MediaBox [".sprintf(
800 800
                                     '%.3F %.3F %.3F %.3F',
801 801
                                     $tmp[0],
802 802
                                     $tmp[1],
803 803
                                     $tmp[2],
804 804
                                     $tmp[3]
805
-                                ) . ']';
805
+                                ).']';
806 806
                         }
807 807
                     }
808 808
 
@@ -848,7 +848,7 @@  discard block
 block discarded – undo
848 848
                         $res .= "$v 0 R ";
849 849
                     }
850 850
 
851
-                    $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj";
851
+                    $res .= "] /Count ".count($o['info']['outlines'])." >>\nendobj";
852 852
                 } else {
853 853
                     $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj";
854 854
                 }
@@ -952,7 +952,7 @@  discard block
 block discarded – undo
952 952
                             case 'Widths':
953 953
                             case 'FontDescriptor':
954 954
                             case 'SubType':
955
-                                $this->addMessage('o_font ' . $k . " : " . $v);
955
+                                $this->addMessage('o_font '.$k." : ".$v);
956 956
                                 $o['info'][$k] = $v;
957 957
                                 break;
958 958
                         }
@@ -976,44 +976,44 @@  discard block
 block discarded – undo
976 976
                     // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/)
977 977
 
978 978
                     $res = "\n$id 0 obj\n<</Type /Font\n/Subtype /Type0\n";
979
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
979
+                    $res .= "/BaseFont /".$o['info']['name']."\n";
980 980
 
981 981
                     // The horizontal identity mapping for 2-byte CIDs; may be used
982 982
                     // with CIDFonts using any Registry, Ordering, and Supplement values.
983 983
                     $res .= "/Encoding /Identity-H\n";
984
-                    $res .= "/DescendantFonts [" . $o['info']['cidFont'] . " 0 R]\n";
985
-                    $res .= "/ToUnicode " . $o['info']['toUnicode'] . " 0 R\n";
984
+                    $res .= "/DescendantFonts [".$o['info']['cidFont']." 0 R]\n";
985
+                    $res .= "/ToUnicode ".$o['info']['toUnicode']." 0 R\n";
986 986
                     $res .= ">>\n";
987 987
                     $res .= "endobj";
988 988
                 } else {
989
-                    $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /" . $o['info']['SubType'] . "\n";
990
-                    $res .= "/Name /F" . $o['info']['fontNum'] . "\n";
991
-                    $res .= "/BaseFont /" . $o['info']['name'] . "\n";
989
+                    $res = "\n$id 0 obj\n<< /Type /Font\n/Subtype /".$o['info']['SubType']."\n";
990
+                    $res .= "/Name /F".$o['info']['fontNum']."\n";
991
+                    $res .= "/BaseFont /".$o['info']['name']."\n";
992 992
 
993 993
                     if (isset($o['info']['encodingDictionary'])) {
994 994
                         // then place a reference to the dictionary
995
-                        $res .= "/Encoding " . $o['info']['encodingDictionary'] . " 0 R\n";
995
+                        $res .= "/Encoding ".$o['info']['encodingDictionary']." 0 R\n";
996 996
                     } else {
997 997
                         if (isset($o['info']['encoding'])) {
998 998
                             // use the specified encoding
999
-                            $res .= "/Encoding /" . $o['info']['encoding'] . "\n";
999
+                            $res .= "/Encoding /".$o['info']['encoding']."\n";
1000 1000
                         }
1001 1001
                     }
1002 1002
 
1003 1003
                     if (isset($o['info']['FirstChar'])) {
1004
-                        $res .= "/FirstChar " . $o['info']['FirstChar'] . "\n";
1004
+                        $res .= "/FirstChar ".$o['info']['FirstChar']."\n";
1005 1005
                     }
1006 1006
 
1007 1007
                     if (isset($o['info']['LastChar'])) {
1008
-                        $res .= "/LastChar " . $o['info']['LastChar'] . "\n";
1008
+                        $res .= "/LastChar ".$o['info']['LastChar']."\n";
1009 1009
                     }
1010 1010
 
1011 1011
                     if (isset($o['info']['Widths'])) {
1012
-                        $res .= "/Widths " . $o['info']['Widths'] . " 0 R\n";
1012
+                        $res .= "/Widths ".$o['info']['Widths']." 0 R\n";
1013 1013
                     }
1014 1014
 
1015 1015
                     if (isset($o['info']['FontDescriptor'])) {
1016
-                        $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1016
+                        $res .= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
1017 1017
                     }
1018 1018
 
1019 1019
                     $res .= ">>\n";
@@ -1039,7 +1039,7 @@  discard block
 block discarded – undo
1039 1039
             }
1040 1040
         }
1041 1041
 
1042
-        return 'SUB' . str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1042
+        return 'SUB'.str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1043 1043
     }
1044 1044
 
1045 1045
     /**
@@ -1051,7 +1051,7 @@  discard block
 block discarded – undo
1051 1051
     private function processFont(int $fontObjId, array $object_info)
1052 1052
     {
1053 1053
         $fontFileName = $object_info['fontFileName'];
1054
-        if (!isset($this->fonts[$fontFileName])) {
1054
+        if ( ! isset($this->fonts[$fontFileName])) {
1055 1055
             return false;
1056 1056
         }
1057 1057
 
@@ -1063,9 +1063,9 @@  discard block
 block discarded – undo
1063 1063
         $isTtfFont = $fileSuffixLower === 'ttf';
1064 1064
         $isPfbFont = $fileSuffixLower === 'pfb';
1065 1065
 
1066
-        $this->addMessage('selectFont: checking for - ' . $fbfile);
1066
+        $this->addMessage('selectFont: checking for - '.$fbfile);
1067 1067
 
1068
-        if (!$fileSuffix) {
1068
+        if ( ! $fileSuffix) {
1069 1069
             $this->addMessage(
1070 1070
                 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts'
1071 1071
             );
@@ -1084,7 +1084,7 @@  discard block
 block discarded – undo
1084 1084
 
1085 1085
             foreach ($font['C'] as $num => $d) {
1086 1086
                 if (intval($num) > 0 || $num == '0') {
1087
-                    if (!$font['isUnicode']) {
1087
+                    if ( ! $font['isUnicode']) {
1088 1088
                         // With Unicode, widths array isn't used
1089 1089
                         if ($lastChar > 0 && $num > $lastChar + 1) {
1090 1090
                             for ($i = $lastChar + 1; $i < $num; $i++) {
@@ -1111,7 +1111,7 @@  discard block
 block discarded – undo
1111 1111
             if (isset($object['differences'])) {
1112 1112
                 foreach ($object['differences'] as $charNum => $charName) {
1113 1113
                     if ($charNum > $lastChar) {
1114
-                        if (!$object['isUnicode']) {
1114
+                        if ( ! $object['isUnicode']) {
1115 1115
                             // With Unicode, widths array isn't used
1116 1116
                             for ($i = $lastChar + 1; $i <= $charNum; $i++) {
1117 1117
                                 $widths[] = 0;
@@ -1134,17 +1134,17 @@  discard block
 block discarded – undo
1134 1134
                 $font['CIDWidths'] = $cid_widths;
1135 1135
             }
1136 1136
 
1137
-            $this->addMessage('selectFont: FirstChar = ' . $firstChar);
1138
-            $this->addMessage('selectFont: LastChar = ' . $lastChar);
1137
+            $this->addMessage('selectFont: FirstChar = '.$firstChar);
1138
+            $this->addMessage('selectFont: LastChar = '.$lastChar);
1139 1139
 
1140 1140
             $widthid = -1;
1141 1141
 
1142
-            if (!$font['isUnicode']) {
1142
+            if ( ! $font['isUnicode']) {
1143 1143
                 // With Unicode, widths array isn't used
1144 1144
 
1145 1145
                 $this->numObj++;
1146 1146
                 $this->o_contents($this->numObj, 'new', 'raw');
1147
-                $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']';
1147
+                $this->objects[$this->numObj]['c'] .= '['.implode(' ', $widths).']';
1148 1148
                 $widthid = $this->numObj;
1149 1149
             }
1150 1150
 
@@ -1165,10 +1165,10 @@  discard block
 block discarded – undo
1165 1165
             // load the pfb file, and put that into an object too.
1166 1166
             // note that pdf supports only binary format type 1 font files, though there is a
1167 1167
             // simple utility to convert them from pfa to pfb.
1168
-            if (!$font['isSubsetting']) {
1168
+            if ( ! $font['isSubsetting']) {
1169 1169
                 $data = file_get_contents($fbfile);
1170 1170
             } else {
1171
-                $adobeFontName = $this->getFontSubsettingTag($font) . '+' . $adobeFontName;
1171
+                $adobeFontName = $this->getFontSubsettingTag($font).'+'.$adobeFontName;
1172 1172
                 $this->stringSubsets[$fontFileName][] = 32; // Force space if not in yet
1173 1173
 
1174 1174
                 $subset = $this->stringSubsets[$fontFileName];
@@ -1371,8 +1371,8 @@  discard block
 block discarded – undo
1371 1371
 EOT;
1372 1372
 
1373 1373
                 $res = "\n$id 0 obj\n";
1374
-                $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1375
-                $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";
1374
+                $res .= "<</Length ".mb_strlen($stream, '8bit')." >>\n";
1375
+                $res .= "stream\n".$stream."\nendstream"."\nendobj";
1376 1376
 
1377 1377
                 return $res;
1378 1378
         }
@@ -1467,12 +1467,12 @@  discard block
 block discarded – undo
1467 1467
 
1468 1468
             case 'out':
1469 1469
                 $res = "\n$id 0 obj\n<< /Type /Encoding\n";
1470
-                if (!isset($o['info']['encoding'])) {
1470
+                if ( ! isset($o['info']['encoding'])) {
1471 1471
                     $o['info']['encoding'] = 'WinAnsiEncoding';
1472 1472
                 }
1473 1473
 
1474 1474
                 if ($o['info']['encoding'] !== 'none') {
1475
-                    $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n";
1475
+                    $res .= "/BaseEncoding /".$o['info']['encoding']."\n";
1476 1476
                 }
1477 1477
 
1478 1478
                 $res .= "/Differences \n[";
@@ -1553,8 +1553,8 @@  discard block
 block discarded – undo
1553 1553
                 $res = "\n$id 0 obj\n";
1554 1554
                 $res .= "<</Type /Font\n";
1555 1555
                 $res .= "/Subtype /CIDFontType2\n";
1556
-                $res .= "/BaseFont /" . $o['info']['name'] . "\n";
1557
-                $res .= "/CIDSystemInfo " . $o['info']['cidSystemInfo'] . " 0 R\n";
1556
+                $res .= "/BaseFont /".$o['info']['name']."\n";
1557
+                $res .= "/CIDSystemInfo ".$o['info']['cidSystemInfo']." 0 R\n";
1558 1558
                 //      if (isset($o['info']['FirstChar'])) {
1559 1559
                 //        $res.= "/FirstChar ".$o['info']['FirstChar']."\n";
1560 1560
                 //      }
@@ -1563,11 +1563,11 @@  discard block
 block discarded – undo
1563 1563
                 //        $res.= "/LastChar ".$o['info']['LastChar']."\n";
1564 1564
                 //      }
1565 1565
                 if (isset($o['info']['FontDescriptor'])) {
1566
-                    $res .= "/FontDescriptor " . $o['info']['FontDescriptor'] . " 0 R\n";
1566
+                    $res .= "/FontDescriptor ".$o['info']['FontDescriptor']." 0 R\n";
1567 1567
                 }
1568 1568
 
1569 1569
                 if (isset($o['info']['MissingWidth'])) {
1570
-                    $res .= "/DW " . $o['info']['MissingWidth'] . "\n";
1570
+                    $res .= "/DW ".$o['info']['MissingWidth']."\n";
1571 1571
                 }
1572 1572
 
1573 1573
                 if (isset($o['info']['fontFileName']) && isset($this->fonts[$o['info']['fontFileName']]['CIDWidths'])) {
@@ -1579,7 +1579,7 @@  discard block
 block discarded – undo
1579 1579
                     $res .= "/W [$w]\n";
1580 1580
                 }
1581 1581
 
1582
-                $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n";
1582
+                $res .= "/CIDToGIDMap ".$o['info']['cidToGidMap']." 0 R\n";
1583 1583
                 $res .= ">>\n";
1584 1584
                 $res .= "endobj";
1585 1585
 
@@ -1619,8 +1619,8 @@  discard block
 block discarded – undo
1619 1619
 
1620 1620
                 $res = "\n$id 0 obj\n";
1621 1621
 
1622
-                $res .= '<</Registry (' . $registry . ")\n"; // A string identifying an issuer of character collections
1623
-                $res .= '/Ordering (' . $ordering . ")\n"; // A string that uniquely names a character collection issued by a specific registry
1622
+                $res .= '<</Registry ('.$registry.")\n"; // A string identifying an issuer of character collections
1623
+                $res .= '/Ordering ('.$ordering.")\n"; // A string that uniquely names a character collection issued by a specific registry
1624 1624
                 $res .= "/Supplement 0\n"; // The supplement number of the character collection.
1625 1625
                 $res .= ">>";
1626 1626
 
@@ -1659,12 +1659,12 @@  discard block
 block discarded – undo
1659 1659
                 $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) &&
1660 1660
                     $this->fonts[$fontFileName]['CIDtoGID_Compressed'];
1661 1661
 
1662
-                if (!$compressed && isset($o['raw'])) {
1662
+                if ( ! $compressed && isset($o['raw'])) {
1663 1663
                     $res .= $tmp;
1664 1664
                 } else {
1665 1665
                     $res .= "<<";
1666 1666
 
1667
-                    if (!$compressed && $this->compressionReady && $this->options['compression']) {
1667
+                    if ( ! $compressed && $this->compressionReady && $this->options['compression']) {
1668 1668
                         // then implement ZLIB based compression on this content stream
1669 1669
                         $compressed = true;
1670 1670
                         $tmp = gzcompress($tmp, 6);
@@ -1678,7 +1678,7 @@  discard block
 block discarded – undo
1678 1678
                         $tmp = $this->ARC4($tmp);
1679 1679
                     }
1680 1680
 
1681
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream";
1681
+                    $res .= "\n/Length ".mb_strlen($tmp, '8bit').">>\nstream\n$tmp\nendstream";
1682 1682
                 }
1683 1683
 
1684 1684
                 $res .= "\nendobj";
@@ -1748,7 +1748,7 @@  discard block
 block discarded – undo
1748 1748
         switch ($action) {
1749 1749
             case 'new':
1750 1750
                 $this->infoObject = $id;
1751
-                $date = 'D:' . @date('Ymd');
1751
+                $date = 'D:'.@date('Ymd');
1752 1752
                 $this->objects[$id] = [
1753 1753
                     't'    => 'info',
1754 1754
                     'info' => [
@@ -1833,12 +1833,12 @@  discard block
 block discarded – undo
1833 1833
                 $res = "\n$id 0 obj\n<< /Type /Action";
1834 1834
                 switch ($o['type']) {
1835 1835
                     case 'ilink':
1836
-                        if (!isset($this->destinations[(string)$o['info']['label']])) {
1836
+                        if ( ! isset($this->destinations[(string) $o['info']['label']])) {
1837 1837
                             break;
1838 1838
                         }
1839 1839
 
1840 1840
                         // there will be an 'label' setting, this is the name of the destination
1841
-                        $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R";
1841
+                        $res .= "\n/S /GoTo\n/D ".$this->destinations[(string) $o['info']['label']]." 0 R";
1842 1842
                         break;
1843 1843
 
1844 1844
                     case 'URI':
@@ -1910,7 +1910,7 @@  discard block
 block discarded – undo
1910 1910
                         $res .= "\n/Subtype /Link";
1911 1911
                         break;
1912 1912
                 }
1913
-                $res .= "\n/A " . $o['info']['actionId'] . " 0 R";
1913
+                $res .= "\n/A ".$o['info']['actionId']." 0 R";
1914 1914
                 $res .= "\n/Border [0 0 0]";
1915 1915
                 $res .= "\n/H /I";
1916 1916
                 $res .= "\n/Rect [ ";
@@ -1984,7 +1984,7 @@  discard block
 block discarded – undo
1984 1984
 
1985 1985
             case 'annot':
1986 1986
                 // add an annotation to this page
1987
-                if (!isset($o['info']['annot'])) {
1987
+                if ( ! isset($o['info']['annot'])) {
1988 1988
                     $o['info']['annot'] = [];
1989 1989
                 }
1990 1990
 
@@ -1996,15 +1996,15 @@  discard block
 block discarded – undo
1996 1996
                 $res = "\n$id 0 obj\n<< /Type /Page";
1997 1997
                 if (isset($o['info']['mediaBox'])) {
1998 1998
                     $tmp = $o['info']['mediaBox'];
1999
-                    $res .= "\n/MediaBox [" . sprintf(
1999
+                    $res .= "\n/MediaBox [".sprintf(
2000 2000
                             '%.3F %.3F %.3F %.3F',
2001 2001
                             $tmp[0],
2002 2002
                             $tmp[1],
2003 2003
                             $tmp[2],
2004 2004
                             $tmp[3]
2005
-                        ) . ']';
2005
+                        ).']';
2006 2006
                 }
2007
-                $res .= "\n/Parent " . $o['info']['parent'] . " 0 R";
2007
+                $res .= "\n/Parent ".$o['info']['parent']." 0 R";
2008 2008
 
2009 2009
                 if (isset($o['info']['annot'])) {
2010 2010
                     $res .= "\n/Annots [";
@@ -2016,7 +2016,7 @@  discard block
 block discarded – undo
2016 2016
 
2017 2017
                 $count = count($o['info']['contents']);
2018 2018
                 if ($count == 1) {
2019
-                    $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R";
2019
+                    $res .= "\n/Contents ".$o['info']['contents'][0]." 0 R";
2020 2020
                 } else {
2021 2021
                     if ($count > 1) {
2022 2022
                         $res .= "\n/Contents [\n";
@@ -2096,7 +2096,7 @@  discard block
 block discarded – undo
2096 2096
                         $res .= "\n/$k $v";
2097 2097
                     }
2098 2098
 
2099
-                    $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream";
2099
+                    $res .= "\n/Length ".mb_strlen($tmp, '8bit')." >>\nstream\n$tmp\nendstream";
2100 2100
                 }
2101 2101
 
2102 2102
                 $res .= "\nendobj";
@@ -2119,7 +2119,7 @@  discard block
 block discarded – undo
2119 2119
                 $this->objects[$id] = [
2120 2120
                     't'    => 'embedjs',
2121 2121
                     'info' => [
2122
-                        'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]'
2122
+                        'Names' => '[(EmbeddedJS) '.($id + 1).' 0 R]'
2123 2123
                     ]
2124 2124
                 ];
2125 2125
                 break;
@@ -2152,7 +2152,7 @@  discard block
 block discarded – undo
2152 2152
                     't'    => 'javascript',
2153 2153
                     'info' => [
2154 2154
                         'S'  => '/JavaScript',
2155
-                        'JS' => '(' . $this->filterText($code, true, false) . ')',
2155
+                        'JS' => '('.$this->filterText($code, true, false).')',
2156 2156
                     ]
2157 2157
                 ];
2158 2158
                 break;
@@ -2187,7 +2187,7 @@  discard block
 block discarded – undo
2187 2187
                 // make the new object
2188 2188
                 $this->objects[$id] = ['t' => 'image', 'data' => &$options['data'], 'info' => []];
2189 2189
 
2190
-                $info =& $this->objects[$id]['info'];
2190
+                $info = & $this->objects[$id]['info'];
2191 2191
 
2192 2192
                 $info['Type'] = '/XObject';
2193 2193
                 $info['Subtype'] = '/Image';
@@ -2195,11 +2195,11 @@  discard block
 block discarded – undo
2195 2195
                 $info['Height'] = $options['ih'];
2196 2196
 
2197 2197
                 if (isset($options['masked']) && $options['masked']) {
2198
-                    $info['SMask'] = ($this->numObj - 1) . ' 0 R';
2198
+                    $info['SMask'] = ($this->numObj - 1).' 0 R';
2199 2199
                 }
2200 2200
 
2201
-                if (!isset($options['type']) || $options['type'] === 'jpg') {
2202
-                    if (!isset($options['channels'])) {
2201
+                if ( ! isset($options['type']) || $options['type'] === 'jpg') {
2202
+                    if ( ! isset($options['channels'])) {
2203 2203
                         $options['channels'] = 3;
2204 2204
                     }
2205 2205
 
@@ -2224,17 +2224,17 @@  discard block
 block discarded – undo
2224 2224
                 } else {
2225 2225
                     if ($options['type'] === 'png') {
2226 2226
                         $info['Filter'] = '/FlateDecode';
2227
-                        $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>';
2227
+                        $info['DecodeParms'] = '<< /Predictor 15 /Colors '.$options['ncolor'].' /Columns '.$options['iw'].' /BitsPerComponent '.$options['bitsPerComponent'].'>>';
2228 2228
 
2229 2229
                         if ($options['isMask']) {
2230 2230
                             $info['ColorSpace'] = '/DeviceGray';
2231 2231
                         } else {
2232 2232
                             if (mb_strlen($options['pdata'], '8bit')) {
2233
-                                $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' ';
2233
+                                $tmp = ' [ /Indexed /DeviceRGB '.(mb_strlen($options['pdata'], '8bit') / 3 - 1).' ';
2234 2234
                                 $this->numObj++;
2235 2235
                                 $this->o_contents($this->numObj, 'new');
2236 2236
                                 $this->objects[$this->numObj]['c'] = $options['pdata'];
2237
-                                $tmp .= $this->numObj . ' 0 R';
2237
+                                $tmp .= $this->numObj.' 0 R';
2238 2238
                                 $tmp .= ' ]';
2239 2239
                                 $info['ColorSpace'] = $tmp;
2240 2240
 
@@ -2242,15 +2242,15 @@  discard block
 block discarded – undo
2242 2242
                                     $transparency = $options['transparency'];
2243 2243
                                     switch ($transparency['type']) {
2244 2244
                                         case 'indexed':
2245
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2245
+                                            $tmp = ' [ '.$transparency['data'].' '.$transparency['data'].'] ';
2246 2246
                                             $info['Mask'] = $tmp;
2247 2247
                                             break;
2248 2248
 
2249 2249
                                         case 'color-key':
2250
-                                            $tmp = ' [ ' .
2251
-                                                $transparency['r'] . ' ' . $transparency['r'] .
2252
-                                                $transparency['g'] . ' ' . $transparency['g'] .
2253
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2250
+                                            $tmp = ' [ '.
2251
+                                                $transparency['r'].' '.$transparency['r'].
2252
+                                                $transparency['g'].' '.$transparency['g'].
2253
+                                                $transparency['b'].' '.$transparency['b'].
2254 2254
                                                 ' ] ';
2255 2255
                                             $info['Mask'] = $tmp;
2256 2256
                                             break;
@@ -2262,21 +2262,21 @@  discard block
 block discarded – undo
2262 2262
 
2263 2263
                                     switch ($transparency['type']) {
2264 2264
                                         case 'indexed':
2265
-                                            $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] ';
2265
+                                            $tmp = ' [ '.$transparency['data'].' '.$transparency['data'].'] ';
2266 2266
                                             $info['Mask'] = $tmp;
2267 2267
                                             break;
2268 2268
 
2269 2269
                                         case 'color-key':
2270
-                                            $tmp = ' [ ' .
2271
-                                                $transparency['r'] . ' ' . $transparency['r'] . ' ' .
2272
-                                                $transparency['g'] . ' ' . $transparency['g'] . ' ' .
2273
-                                                $transparency['b'] . ' ' . $transparency['b'] .
2270
+                                            $tmp = ' [ '.
2271
+                                                $transparency['r'].' '.$transparency['r'].' '.
2272
+                                                $transparency['g'].' '.$transparency['g'].' '.
2273
+                                                $transparency['b'].' '.$transparency['b'].
2274 2274
                                                 ' ] ';
2275 2275
                                             $info['Mask'] = $tmp;
2276 2276
                                             break;
2277 2277
                                     }
2278 2278
                                 }
2279
-                                $info['ColorSpace'] = '/' . $options['color'];
2279
+                                $info['ColorSpace'] = '/'.$options['color'];
2280 2280
                             }
2281 2281
                         }
2282 2282
 
@@ -2306,7 +2306,7 @@  discard block
 block discarded – undo
2306 2306
                     $tmp = $this->ARC4($tmp);
2307 2307
                 }
2308 2308
 
2309
-                $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj";
2309
+                $res .= "\n/Length ".mb_strlen($tmp, '8bit').">>\nstream\n$tmp\nendstream\nendobj";
2310 2310
 
2311 2311
                 return $res;
2312 2312
         }
@@ -2367,7 +2367,7 @@  discard block
 block discarded – undo
2367 2367
                 $res = "\n$id 0 obj\n<< /Type /ExtGState\n";
2368 2368
 
2369 2369
                 foreach ($o["info"] as $k => $v) {
2370
-                    if (!in_array($k, $valid_params)) {
2370
+                    if ( ! in_array($k, $valid_params)) {
2371 2371
                         continue;
2372 2372
                     }
2373 2373
                     $res .= "/$k $v\n";
@@ -2434,21 +2434,21 @@  discard block
 block discarded – undo
2434 2434
 
2435 2435
                 $res .= "/Resources <<";
2436 2436
                 if (isset($o['procset'])) {
2437
-                    $res .= "\n/ProcSet " . $o['procset'] . " 0 R";
2437
+                    $res .= "\n/ProcSet ".$o['procset']." 0 R";
2438 2438
                 } else {
2439 2439
                     $res .= "\n/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]";
2440 2440
                 }
2441 2441
                 if (isset($o['fonts']) && count($o['fonts'])) {
2442 2442
                     $res .= "\n/Font << ";
2443 2443
                     foreach ($o['fonts'] as $finfo) {
2444
-                        $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R";
2444
+                        $res .= "\n/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R";
2445 2445
                     }
2446 2446
                     $res .= "\n>>";
2447 2447
                 }
2448 2448
                 if (isset($o['xObjects']) && count($o['xObjects'])) {
2449 2449
                     $res .= "\n/XObject << ";
2450 2450
                     foreach ($o['xObjects'] as $finfo) {
2451
-                        $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R";
2451
+                        $res .= "\n/".$finfo['label']." ".$finfo['objNum']." 0 R";
2452 2452
                     }
2453 2453
                     $res .= "\n>>";
2454 2454
                 }
@@ -2466,8 +2466,8 @@  discard block
 block discarded – undo
2466 2466
                     $tmp = $this->ARC4($tmp);
2467 2467
                 }
2468 2468
 
2469
-                $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2470
-                $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";
2469
+                $res .= "/Length ".mb_strlen($tmp, '8bit')." >>\n";
2470
+                $res .= "stream\n".$tmp."\nendstream"."\nendobj";
2471 2471
 
2472 2472
                 return $res;
2473 2473
         }
@@ -2522,7 +2522,7 @@  discard block
 block discarded – undo
2522 2522
                 if (isset($o['fonts']) && count($o['fonts'])) {
2523 2523
                     $res .= "/Font << \n";
2524 2524
                     foreach ($o['fonts'] as $finfo) {
2525
-                        $res .= "/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R\n";
2525
+                        $res .= "/F".$finfo['fontNum']." ".$finfo['objNum']." 0 R\n";
2526 2526
                     }
2527 2527
                     $res .= ">>\n";
2528 2528
                 }
@@ -2647,7 +2647,7 @@  discard block
 block discarded – undo
2647 2647
 
2648 2648
             case 'byterange':
2649 2649
                 $o = &$this->objects[$id];
2650
-                $content =& $options['content'];
2650
+                $content = & $options['content'];
2651 2651
                 $content_len = strlen($content);
2652 2652
                 $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2653 2653
                 $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
@@ -2655,8 +2655,8 @@  discard block
 block discarded – undo
2655 2655
                 $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2656 2656
 
2657 2657
                 $fuid = uniqid();
2658
-                $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2659
-                $tmpOutput = $this->tmp . "/pkcs7.tmp." . $fuid . '.out';
2658
+                $tmpInput = $this->tmp."/pkcs7.tmp.".$fuid.'.in';
2659
+                $tmpOutput = $this->tmp."/pkcs7.tmp.".$fuid.'.out';
2660 2660
 
2661 2661
                 if (file_put_contents($tmpInput, substr($content, 0, $rangeStartPos)) === false) {
2662 2662
                     throw new \Exception("Unable to write temporary file for signing.");
@@ -2701,12 +2701,12 @@  discard block
 block discarded – undo
2701 2701
                     $this->encryptInit($id);
2702 2702
                 }
2703 2703
 
2704
-                $res .= "/ByteRange " .sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2705
-                $res .= "/Contents <" . str_pad('', $sign_maxlen, '0') . ">\n";
2704
+                $res .= "/ByteRange ".sprintf("[ %'.010d ********** ********** ********** ]\n", $id);
2705
+                $res .= "/Contents <".str_pad('', $sign_maxlen, '0').">\n";
2706 2706
                 $res .= "/Filter/Adobe.PPKLite\n"; //PPKMS \n";
2707 2707
                 $res .= "/Type/Sig/SubFilter/adbe.pkcs7.detached \n";
2708 2708
 
2709
-                $date = "D:" . substr_replace(date('YmdHisO'), '\'', -2, 0) . '\'';
2709
+                $date = "D:".substr_replace(date('YmdHisO'), '\'', -2, 0).'\'';
2710 2710
                 if ($encrypted) {
2711 2711
                     $date = $this->ARC4($date);
2712 2712
                 }
@@ -2722,8 +2722,8 @@  discard block
 block discarded – undo
2722 2722
                         case 'Reason':
2723 2723
                         case 'ContactInfo':
2724 2724
                             if ($v !== null && $v !== '') {
2725
-                                $res .= "/$k (" .
2726
-                                  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v) . ") \n";
2725
+                                $res .= "/$k (".
2726
+                                  ($encrypted ? $this->filterText($this->ARC4($v), false, false) : $v).") \n";
2727 2727
                             }
2728 2728
                             break;
2729 2729
                     }
@@ -2755,10 +2755,10 @@  discard block
 block discarded – undo
2755 2755
 
2756 2756
             case 'keys':
2757 2757
                 // figure out the additional parameters required
2758
-                $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41)
2759
-                    . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08)
2760
-                    . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80)
2761
-                    . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A);
2758
+                $pad = chr(0x28).chr(0xBF).chr(0x4E).chr(0x5E).chr(0x4E).chr(0x75).chr(0x8A).chr(0x41)
2759
+                    . chr(0x64).chr(0x00).chr(0x4E).chr(0x56).chr(0xFF).chr(0xFA).chr(0x01).chr(0x08)
2760
+                    . chr(0x2E).chr(0x2E).chr(0x00).chr(0xB6).chr(0xD0).chr(0x68).chr(0x3E).chr(0x80)
2761
+                    . chr(0x2F).chr(0x0C).chr(0xA9).chr(0xFE).chr(0x64).chr(0x53).chr(0x69).chr(0x7A);
2762 2762
 
2763 2763
                 $info = $this->objects[$id]['info'];
2764 2764
 
@@ -2768,7 +2768,7 @@  discard block
 block discarded – undo
2768 2768
                     $owner = substr($info['owner'], 0, 32);
2769 2769
                 } else {
2770 2770
                     if ($len < 32) {
2771
-                        $owner = $info['owner'] . substr($pad, 0, 32 - $len);
2771
+                        $owner = $info['owner'].substr($pad, 0, 32 - $len);
2772 2772
                     } else {
2773 2773
                         $owner = $info['owner'];
2774 2774
                     }
@@ -2779,7 +2779,7 @@  discard block
 block discarded – undo
2779 2779
                     $user = substr($info['user'], 0, 32);
2780 2780
                 } else {
2781 2781
                     if ($len < 32) {
2782
-                        $user = $info['user'] . substr($pad, 0, 32 - $len);
2782
+                        $user = $info['user'].substr($pad, 0, 32 - $len);
2783 2783
                     } else {
2784 2784
                         $user = $info['user'];
2785 2785
                     }
@@ -2793,7 +2793,7 @@  discard block
 block discarded – undo
2793 2793
 
2794 2794
                 // now make the u value, phew.
2795 2795
                 $tmp = $this->md5_16(
2796
-                    $user . $ovalue . chr($info['p']) . chr(255) . chr(255) . chr(255) . hex2bin($this->fileIdentifier)
2796
+                    $user.$ovalue.chr($info['p']).chr(255).chr(255).chr(255).hex2bin($this->fileIdentifier)
2797 2797
                 );
2798 2798
 
2799 2799
                 $ukey = substr($tmp, 0, 5);
@@ -2812,11 +2812,11 @@  discard block
 block discarded – undo
2812 2812
                 $res .= "\n/Filter /Standard";
2813 2813
                 $res .= "\n/V 1";
2814 2814
                 $res .= "\n/R 2";
2815
-                $res .= "\n/O (" . $this->filterText($o['info']['O'], false, false) . ')';
2816
-                $res .= "\n/U (" . $this->filterText($o['info']['U'], false, false) . ')';
2815
+                $res .= "\n/O (".$this->filterText($o['info']['O'], false, false).')';
2816
+                $res .= "\n/U (".$this->filterText($o['info']['U'], false, false).')';
2817 2817
                 // and the p-value needs to be converted to account for the twos-complement approach
2818 2818
                 $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1;
2819
-                $res .= "\n/P " . ($o['info']['p']);
2819
+                $res .= "\n/P ".($o['info']['p']);
2820 2820
                 $res .= "\n>>\nendobj";
2821 2821
 
2822 2822
                 return $res;
@@ -2884,7 +2884,7 @@  discard block
 block discarded – undo
2884 2884
                             $filename = $entry['filename'];
2885 2885
                         }
2886 2886
 
2887
-                        $res .= "($filename) " . $entry['dict_reference'] . " 0 R ";
2887
+                        $res .= "($filename) ".$entry['dict_reference']." 0 R ";
2888 2888
                     }
2889 2889
 
2890 2890
                     $res .= "] >> endobj";
@@ -2921,7 +2921,7 @@  discard block
 block discarded – undo
2921 2921
                 $description = $this->filterText($description, false, false);
2922 2922
 
2923 2923
                 $res = "\n$id 0 obj <</Type /Filespec /EF";
2924
-                $res .= " <</F " . $info['embedded_reference'] . " 0 R >>";
2924
+                $res .= " <</F ".$info['embedded_reference']." 0 R >>";
2925 2925
                 $res .= " /F ($filename) /UF ($filename) /Desc ($description)";
2926 2926
                 $res .= " >> endobj";
2927 2927
                 return $res;
@@ -2965,8 +2965,8 @@  discard block
 block discarded – undo
2965 2965
                 }
2966 2966
                 $file_size_compressed = mb_strlen($file_content_compressed, '8bit');
2967 2967
 
2968
-                $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>" .
2969
-                    " /Type/EmbeddedFile /Filter/FlateDecode" .
2968
+                $res = "\n$id 0 obj <</Params <</Size $file_size_uncompressed /CheckSum ($checksum) >>".
2969
+                    " /Type/EmbeddedFile /Filter/FlateDecode".
2970 2970
                     " /Length $file_size_compressed >> stream\n$file_content_compressed\nendstream\nendobj";
2971 2971
 
2972 2972
                 return $res;
@@ -3007,7 +3007,7 @@  discard block
 block discarded – undo
3007 3007
         $tmp = $this->encryptionKey;
3008 3008
         $hex = dechex($id);
3009 3009
         if (mb_strlen($hex, '8bit') < 6) {
3010
-            $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex;
3010
+            $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')).$hex;
3011 3011
         }
3012 3012
         $tmp .= chr(hexdec(substr($hex, 4, 2)))
3013 3013
             . chr(hexdec(substr($hex, 2, 2)))
@@ -3186,7 +3186,7 @@  discard block
 block discarded – undo
3186 3186
 
3187 3187
         if ($this->fileIdentifier === '') {
3188 3188
             $tmp = implode('', $this->objects[$this->infoObject]['info']);
3189
-            $this->fileIdentifier = md5('DOMPDF' . __FILE__ . $tmp . microtime() . mt_rand());
3189
+            $this->fileIdentifier = md5('DOMPDF'.__FILE__.$tmp.microtime().mt_rand());
3190 3190
         }
3191 3191
 
3192 3192
         if ($this->arc4_objnum) {
@@ -3197,7 +3197,7 @@  discard block
 block discarded – undo
3197 3197
         $this->checkAllHere();
3198 3198
 
3199 3199
         $xref = [];
3200
-        $content = '%PDF-' . self::PDF_VERSION;
3200
+        $content = '%PDF-'.self::PDF_VERSION;
3201 3201
         $pos = mb_strlen($content, '8bit');
3202 3202
 
3203 3203
         // pre-process o_font objects before output of all objects
@@ -3208,31 +3208,31 @@  discard block
 block discarded – undo
3208 3208
         }
3209 3209
 
3210 3210
         foreach ($this->objects as $k => $v) {
3211
-            $tmp = 'o_' . $v['t'];
3211
+            $tmp = 'o_'.$v['t'];
3212 3212
             $cont = $this->$tmp($k, 'out');
3213 3213
             $content .= $cont;
3214 3214
             $xref[] = $pos + 1; //+1 to account for \n at the start of each object
3215 3215
             $pos += mb_strlen($cont, '8bit');
3216 3216
         }
3217 3217
 
3218
-        $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n";
3218
+        $content .= "\nxref\n0 ".(count($xref) + 1)."\n0000000000 65535 f \n";
3219 3219
 
3220 3220
         foreach ($xref as $p) {
3221
-            $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n";
3221
+            $content .= str_pad($p, 10, "0", STR_PAD_LEFT)." 00000 n \n";
3222 3222
         }
3223 3223
 
3224
-        $content .= "trailer\n<<\n" .
3225
-            '/Size ' . (count($xref) + 1) . "\n" .
3226
-            '/Root 1 0 R' . "\n" .
3227
-            '/Info ' . $this->infoObject . " 0 R\n"
3224
+        $content .= "trailer\n<<\n".
3225
+            '/Size '.(count($xref) + 1)."\n".
3226
+            '/Root 1 0 R'."\n".
3227
+            '/Info '.$this->infoObject." 0 R\n"
3228 3228
         ;
3229 3229
 
3230 3230
         // if encryption has been applied to this document then add the marker for this dictionary
3231 3231
         if ($this->arc4_objnum > 0) {
3232
-            $content .= '/Encrypt ' . $this->arc4_objnum . " 0 R\n";
3232
+            $content .= '/Encrypt '.$this->arc4_objnum." 0 R\n";
3233 3233
         }
3234 3234
 
3235
-        $content .= '/ID[<' . $this->fileIdentifier . '><' . $this->fileIdentifier . ">]\n";
3235
+        $content .= '/ID[<'.$this->fileIdentifier.'><'.$this->fileIdentifier.">]\n";
3236 3236
 
3237 3237
         // account for \n added at start of xref table
3238 3238
         $pos++;
@@ -3241,7 +3241,7 @@  discard block
 block discarded – undo
3241 3241
 
3242 3242
         if (count($this->byteRange) > 0) {
3243 3243
             foreach ($this->byteRange as $k => $v) {
3244
-                $tmp = 'o_' . $v['t'];
3244
+                $tmp = 'o_'.$v['t'];
3245 3245
                 $this->$tmp($k, 'byterange', ['content' => &$content]);
3246 3246
             }
3247 3247
         }
@@ -3315,7 +3315,7 @@  discard block
 block discarded – undo
3315 3315
 
3316 3316
         $this->addMessage("openFont: $font - $name");
3317 3317
 
3318
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3318
+        if ( ! $this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) {
3319 3319
             $metrics_name = "$name.afm";
3320 3320
         } else {
3321 3321
             $metrics_name = "$name.ufm";
@@ -3324,17 +3324,17 @@  discard block
 block discarded – undo
3324 3324
         $cache_name = "$metrics_name.json";
3325 3325
         $this->addMessage("metrics: $metrics_name, cache: $cache_name");
3326 3326
         
3327
-        if (file_exists($fontcache . '/' . $cache_name)) {
3327
+        if (file_exists($fontcache.'/'.$cache_name)) {
3328 3328
             $this->addMessage("openFont: json metrics file exists $fontcache/$cache_name");
3329
-            $cached_font_info = json_decode(file_get_contents($fontcache . '/' . $cache_name), true);
3330
-            if (!isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
3329
+            $cached_font_info = json_decode(file_get_contents($fontcache.'/'.$cache_name), true);
3330
+            if ( ! isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
3331 3331
                 $this->addMessage('openFont: font cache is out of date, regenerating');
3332 3332
             } else {
3333 3333
                 $this->fonts[$font] = $cached_font_info;
3334 3334
             }
3335 3335
         }
3336 3336
 
3337
-        if (!isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
3337
+        if ( ! isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
3338 3338
             // then rebuild the php_<font>.afm file from the <font>.afm file
3339 3339
             $this->addMessage("openFont: build php file from $dir/$metrics_name");
3340 3340
             $data = [];
@@ -3411,12 +3411,12 @@  discard block
 block discarded – undo
3411 3411
                                 }
3412 3412
                             }
3413 3413
 
3414
-                            $c = (int)$dtmp['C'];
3414
+                            $c = (int) $dtmp['C'];
3415 3415
                             $n = $dtmp['N'];
3416 3416
                             $width = floatval($dtmp['WX']);
3417 3417
 
3418 3418
                             if ($c >= 0) {
3419
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3419
+                                if ( ! ctype_xdigit($n) || $c != hexdec($n)) {
3420 3420
                                     $data['codeToName'][$c] = $n;
3421 3421
                                 }
3422 3422
                                 $data['C'][$c] = $width;
@@ -3424,7 +3424,7 @@  discard block
 block discarded – undo
3424 3424
                                 $data['C'][$n] = $width;
3425 3425
                             }
3426 3426
 
3427
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3427
+                            if ( ! isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3428 3428
                                 $data['MissingWidth'] = $width;
3429 3429
                             }
3430 3430
 
@@ -3432,7 +3432,7 @@  discard block
 block discarded – undo
3432 3432
 
3433 3433
                         // U 827 ; WX 0 ; N squaresubnosp ; G 675 ;
3434 3434
                         case 'U': // Found in UFM files
3435
-                            if (!$data['isUnicode']) {
3435
+                            if ( ! $data['isUnicode']) {
3436 3436
                                 break;
3437 3437
                             }
3438 3438
 
@@ -3457,7 +3457,7 @@  discard block
 block discarded – undo
3457 3457
                                 }
3458 3458
                             }
3459 3459
 
3460
-                            $c = (int)$dtmp['U'];
3460
+                            $c = (int) $dtmp['U'];
3461 3461
                             $n = $dtmp['N'];
3462 3462
                             $glyph = $dtmp['G'];
3463 3463
                             $width = floatval($dtmp['WX']);
@@ -3469,7 +3469,7 @@  discard block
 block discarded – undo
3469 3469
                                     $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF);
3470 3470
                                 }
3471 3471
 
3472
-                                if (!ctype_xdigit($n) || $c != hexdec($n)) {
3472
+                                if ( ! ctype_xdigit($n) || $c != hexdec($n)) {
3473 3473
                                     $data['codeToName'][$c] = $n;
3474 3474
                                 }
3475 3475
                                 $data['C'][$c] = $width;
@@ -3477,7 +3477,7 @@  discard block
 block discarded – undo
3477 3477
                                 $data['C'][$n] = $width;
3478 3478
                             }
3479 3479
 
3480
-                            if (!isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3480
+                            if ( ! isset($data['MissingWidth']) && $c === -1 && $n === '.notdef') {
3481 3481
                                 $data['MissingWidth'] = $width;
3482 3482
                             }
3483 3483
 
@@ -3510,7 +3510,7 @@  discard block
 block discarded – undo
3510 3510
             $data = null;
3511 3511
         }
3512 3512
 
3513
-        if (!isset($this->fonts[$font])) {
3513
+        if ( ! isset($this->fonts[$font])) {
3514 3514
             $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3515 3515
         }
3516 3516
     }
@@ -3540,7 +3540,7 @@  discard block
 block discarded – undo
3540 3540
             $fontName = substr($fontName, 0, mb_strlen($fontName) - 4);
3541 3541
         }
3542 3542
 
3543
-        if (!isset($this->fonts[$fontName])) {
3543
+        if ( ! isset($this->fonts[$fontName])) {
3544 3544
             $this->addMessage("selectFont: selecting - $fontName - $encoding, $set");
3545 3545
 
3546 3546
             // load the file
@@ -3677,7 +3677,7 @@  discard block
 block discarded – undo
3677 3677
     {
3678 3678
         $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3679 3679
 
3680
-        if (!$force && $this->currentColor == $new_color) {
3680
+        if ( ! $force && $this->currentColor == $new_color) {
3681 3681
             return;
3682 3682
         }
3683 3683
 
@@ -3697,7 +3697,7 @@  discard block
 block discarded – undo
3697 3697
      */
3698 3698
     function setFillRule($fillRule)
3699 3699
     {
3700
-        if (!in_array($fillRule, ["nonzero", "evenodd"])) {
3700
+        if ( ! in_array($fillRule, ["nonzero", "evenodd"])) {
3701 3701
             return;
3702 3702
         }
3703 3703
 
@@ -3714,7 +3714,7 @@  discard block
 block discarded – undo
3714 3714
     {
3715 3715
         $new_color = [$color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null];
3716 3716
 
3717
-        if (!$force && $this->currentStrokeColor == $new_color) {
3717
+        if ( ! $force && $this->currentStrokeColor == $new_color) {
3718 3718
             return;
3719 3719
         }
3720 3720
 
@@ -3775,7 +3775,7 @@  discard block
 block discarded – undo
3775 3775
             "Exclusion"
3776 3776
         ];
3777 3777
 
3778
-        if (!in_array($mode, $blend_modes)) {
3778
+        if ( ! in_array($mode, $blend_modes)) {
3779 3779
             $mode = "Normal";
3780 3780
         }
3781 3781
 
@@ -3795,7 +3795,7 @@  discard block
 block discarded – undo
3795 3795
 
3796 3796
         $options = [
3797 3797
             "BM" => "/$mode",
3798
-            "CA" => (float)$opacity
3798
+            "CA" => (float) $opacity
3799 3799
         ];
3800 3800
 
3801 3801
         $this->setGraphicsState($options);
@@ -3830,7 +3830,7 @@  discard block
 block discarded – undo
3830 3830
             "Exclusion"
3831 3831
         ];
3832 3832
 
3833
-        if (!in_array($mode, $blend_modes)) {
3833
+        if ( ! in_array($mode, $blend_modes)) {
3834 3834
             $mode = "Normal";
3835 3835
         }
3836 3836
 
@@ -3850,7 +3850,7 @@  discard block
 block discarded – undo
3850 3850
 
3851 3851
         $options = [
3852 3852
             "BM" => "/$mode",
3853
-            "ca" => (float)$opacity,
3853
+            "ca" => (float) $opacity,
3854 3854
         ];
3855 3855
 
3856 3856
         $this->setGraphicsState($options);
@@ -4035,15 +4035,15 @@  discard block
 block discarded – undo
4035 4035
             $nSeg = 2;
4036 4036
         }
4037 4037
 
4038
-        $astart = deg2rad((float)$astart);
4039
-        $afinish = deg2rad((float)$afinish);
4038
+        $astart = deg2rad((float) $astart);
4039
+        $afinish = deg2rad((float) $afinish);
4040 4040
         $totalAngle = $afinish - $astart;
4041 4041
 
4042 4042
         $dt = $totalAngle / $nSeg;
4043 4043
         $dtm = $dt / 3;
4044 4044
 
4045 4045
         if ($angle != 0) {
4046
-            $a = -1 * deg2rad((float)$angle);
4046
+            $a = -1 * deg2rad((float) $angle);
4047 4047
 
4048 4048
             $this->addContent(
4049 4049
                 sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0)
@@ -4059,7 +4059,7 @@  discard block
 block discarded – undo
4059 4059
         $c0 = -$r1 * sin($t1);
4060 4060
         $d0 = $r2 * cos($t1);
4061 4061
 
4062
-        if (!$incomplete) {
4062
+        if ( ! $incomplete) {
4063 4063
             $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0));
4064 4064
         }
4065 4065
 
@@ -4089,7 +4089,7 @@  discard block
 block discarded – undo
4089 4089
             $d0 = $d1;
4090 4090
         }
4091 4091
 
4092
-        if (!$incomplete) {
4092
+        if ( ! $incomplete) {
4093 4093
             if ($fill) {
4094 4094
                 $this->addContent(' f');
4095 4095
             }
@@ -4149,7 +4149,7 @@  discard block
 block discarded – undo
4149 4149
         }
4150 4150
 
4151 4151
         if (is_array($dash)) {
4152
-            $string .= ' [ ' . implode(' ', $dash) . " ] $phase d";
4152
+            $string .= ' [ '.implode(' ', $dash)." ] $phase d";
4153 4153
         }
4154 4154
 
4155 4155
         $this->currentLineStyle = $string;
@@ -4227,12 +4227,12 @@  discard block
 block discarded – undo
4227 4227
 
4228 4228
     function fill()
4229 4229
     {
4230
-        $this->addContent("\nf" . ($this->fillRule === "evenodd" ? "*" : ""));
4230
+        $this->addContent("\nf".($this->fillRule === "evenodd" ? "*" : ""));
4231 4231
     }
4232 4232
 
4233 4233
     function fillStroke()
4234 4234
     {
4235
-        $this->addContent("\nb" . ($this->fillRule === "evenodd" ? "*" : ""));
4235
+        $this->addContent("\nb".($this->fillRule === "evenodd" ? "*" : ""));
4236 4236
     }
4237 4237
 
4238 4238
     /**
@@ -4318,16 +4318,16 @@  discard block
 block discarded – undo
4318 4318
      */
4319 4319
     public function addFormField($type, $name, $x0, $y0, $x1, $y1, $ff = 0, $size = 10.0, $color = [0, 0, 0])
4320 4320
     {
4321
-        if (!$this->numFonts) {
4321
+        if ( ! $this->numFonts) {
4322 4322
             $this->selectFont($this->defaultFont);
4323 4323
         }
4324 4324
 
4325
-        $color = implode(' ', $color) . ' rg';
4325
+        $color = implode(' ', $color).' rg';
4326 4326
 
4327 4327
         $currentFontNum = $this->currentFontNum;
4328 4328
         $font = array_filter(
4329 4329
             $this->objects[$this->currentNode]['info']['fonts'],
4330
-            function ($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
4330
+            function($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
4331 4331
         );
4332 4332
 
4333 4333
         $this->o_acroform($this->acroFormId, 'font',
@@ -4341,7 +4341,7 @@  discard block
 block discarded – undo
4341 4341
           'T' => $name,
4342 4342
           'Ff' => $ff,
4343 4343
           'pageid' => $this->currentPage,
4344
-          'da' => "$color /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)
4344
+          'da' => "$color /F$this->currentFontNum ".sprintf('%.1F Tf ', $size)
4345 4345
         ]);
4346 4346
 
4347 4347
         return $fieldId;
@@ -4708,17 +4708,17 @@  discard block
 block discarded – undo
4708 4708
             die("Unable to stream pdf: headers already sent");
4709 4709
         }
4710 4710
 
4711
-        if (!isset($options["compress"])) $options["compress"] = true;
4712
-        if (!isset($options["Attachment"])) $options["Attachment"] = true;
4711
+        if ( ! isset($options["compress"])) $options["compress"] = true;
4712
+        if ( ! isset($options["Attachment"])) $options["Attachment"] = true;
4713 4713
 
4714
-        $debug = !$options['compress'];
4714
+        $debug = ! $options['compress'];
4715 4715
         $tmp = ltrim($this->output($debug));
4716 4716
 
4717 4717
         header("Cache-Control: private");
4718 4718
         header("Content-Type: application/pdf");
4719
-        header("Content-Length: " . mb_strlen($tmp, "8bit"));
4719
+        header("Content-Length: ".mb_strlen($tmp, "8bit"));
4720 4720
 
4721
-        $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")) . ".pdf";
4721
+        $filename = str_replace(["\n", "'"], "", basename($filename, ".pdf")).".pdf";
4722 4722
         $attachment = $options["Attachment"] ? "attachment" : "inline";
4723 4723
 
4724 4724
         $encoding = mb_detect_encoding($filename);
@@ -4745,7 +4745,7 @@  discard block
 block discarded – undo
4745 4745
      */
4746 4746
     public function getFontHeight(float $size): float
4747 4747
     {
4748
-        if (!$this->numFonts) {
4748
+        if ( ! $this->numFonts) {
4749 4749
             $this->selectFont($this->defaultFont);
4750 4750
         }
4751 4751
 
@@ -4768,7 +4768,7 @@  discard block
 block discarded – undo
4768 4768
             // Courier font.
4769 4769
             //
4770 4770
             // Both have been added manually to the .afm and .ufm files.
4771
-            $h += (int)$font['FontHeightOffset'];
4771
+            $h += (int) $font['FontHeightOffset'];
4772 4772
         }
4773 4773
 
4774 4774
         return $size * $h / 1000;
@@ -4781,7 +4781,7 @@  discard block
 block discarded – undo
4781 4781
      */
4782 4782
     public function getFontXHeight(float $size): float
4783 4783
     {
4784
-        if (!$this->numFonts) {
4784
+        if ( ! $this->numFonts) {
4785 4785
             $this->selectFont($this->defaultFont);
4786 4786
         }
4787 4787
 
@@ -4809,7 +4809,7 @@  discard block
 block discarded – undo
4809 4809
     public function getFontDescender(float $size): float
4810 4810
     {
4811 4811
         // note that this will most likely return a negative value
4812
-        if (!$this->numFonts) {
4812
+        if ( ! $this->numFonts) {
4813 4813
             $this->selectFont($this->defaultFont);
4814 4814
         }
4815 4815
 
@@ -4830,7 +4830,7 @@  discard block
 block discarded – undo
4830 4830
      */
4831 4831
     function filterText($text, $bom = true, $convert_encoding = true)
4832 4832
     {
4833
-        if (!$this->numFonts) {
4833
+        if ( ! $this->numFonts) {
4834 4834
             $this->selectFont($this->defaultFont);
4835 4835
         }
4836 4836
 
@@ -4940,12 +4940,12 @@  discard block
 block discarded – undo
4940 4940
             if ($c === 0xFFFD) {
4941 4941
                 $out .= "\xFF\xFD"; // replacement character
4942 4942
             } elseif ($c < 0x10000) {
4943
-                $out .= chr($c >> 0x08) . chr($c & 0xFF);
4943
+                $out .= chr($c >> 0x08).chr($c & 0xFF);
4944 4944
             } else {
4945 4945
                 $c -= 0x10000;
4946 4946
                 $w1 = 0xD800 | ($c >> 0x10);
4947 4947
                 $w2 = 0xDC00 | ($c & 0x3FF);
4948
-                $out .= chr($w1 >> 0x08) . chr($w1 & 0xFF) . chr($w2 >> 0x08) . chr($w2 & 0xFF);
4948
+                $out .= chr($w1 >> 0x08).chr($w1 & 0xFF).chr($w2 >> 0x08).chr($w2 & 0xFF);
4949 4949
             }
4950 4950
         }
4951 4951
 
@@ -4973,7 +4973,7 @@  discard block
 block discarded – undo
4973 4973
         $words = explode(' ', $text);
4974 4974
         $nspaces = count($words) - 1;
4975 4975
         $w += $wa * $nspaces;
4976
-        $a = deg2rad((float)$angle);
4976
+        $a = deg2rad((float) $angle);
4977 4977
 
4978 4978
         return [cos($a) * $w + $x, -sin($a) * $w + $y];
4979 4979
     }
@@ -5008,11 +5008,11 @@  discard block
 block discarded – undo
5008 5008
      */
5009 5009
     function registerText($font, $text)
5010 5010
     {
5011
-        if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
5011
+        if ( ! $this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) {
5012 5012
             return;
5013 5013
         }
5014 5014
 
5015
-        if (!isset($this->stringSubsets[$font])) {
5015
+        if ( ! isset($this->stringSubsets[$font])) {
5016 5016
             $base_subset = "\u{fffd}\u{fffe}\u{ffff}";
5017 5017
             $this->stringSubsets[$font] = $this->utf8toCodePointsArray($base_subset);
5018 5018
         }
@@ -5036,7 +5036,7 @@  discard block
 block discarded – undo
5036 5036
      */
5037 5037
     function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
5038 5038
     {
5039
-        if (!$this->numFonts) {
5039
+        if ( ! $this->numFonts) {
5040 5040
             $this->selectFont($this->defaultFont);
5041 5041
         }
5042 5042
 
@@ -5077,7 +5077,7 @@  discard block
 block discarded – undo
5077 5077
         if ($angle == 0) {
5078 5078
             $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y));
5079 5079
         } else {
5080
-            $a = deg2rad((float)$angle);
5080
+            $a = deg2rad((float) $angle);
5081 5081
             $this->addContent(
5082 5082
                 sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y)
5083 5083
             );
@@ -5100,9 +5100,9 @@  discard block
 block discarded – undo
5100 5100
             // modify unicode text so that extra word spacing is manually implemented (bug #)
5101 5101
             if ($this->fonts[$this->currentFont]['isUnicode'] && $wordSpaceAdjust != 0) {
5102 5102
                 $space_scale = 1000 / $size;
5103
-                $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20" . (-round($space_scale * $wordSpaceAdjust)) . "\x00\x20(", $place_text);
5103
+                $place_text = str_replace("\x00\x20", "\x00\x20)\x00\x20".(-round($space_scale * $wordSpaceAdjust))."\x00\x20(", $place_text);
5104 5104
             }
5105
-            $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size));
5105
+            $this->addContent(" /F$this->currentFontNum ".sprintf('%.1F Tf ', $size));
5106 5106
             $this->addContent(" [($place_text)] TJ");
5107 5107
         }
5108 5108
 
@@ -5161,7 +5161,7 @@  discard block
 block discarded – undo
5161 5161
         // and put them back at the end.
5162 5162
         $store_currentTextState = $this->currentTextState;
5163 5163
 
5164
-        if (!$this->numFonts) {
5164
+        if ( ! $this->numFonts) {
5165 5165
             $this->selectFont($this->defaultFont);
5166 5166
         }
5167 5167
 
@@ -5262,7 +5262,7 @@  discard block
 block discarded – undo
5262 5262
             // ok to use this as stack starts numbering at 1
5263 5263
             $this->setColor($opt['col'], true);
5264 5264
             $this->setStrokeColor($opt['str'], true);
5265
-            $this->addContent("\n" . $opt['lin']);
5265
+            $this->addContent("\n".$opt['lin']);
5266 5266
             //    $this->currentLineStyle = $opt['lin'];
5267 5267
         } else {
5268 5268
             $this->nStateStack++;
@@ -5283,11 +5283,11 @@  discard block
 block discarded – undo
5283 5283
      */
5284 5284
     function restoreState($pageEnd = 0)
5285 5285
     {
5286
-        if (!$pageEnd) {
5286
+        if ( ! $pageEnd) {
5287 5287
             $n = $this->nStateStack;
5288 5288
             $this->currentColor = $this->stateStack[$n]['col'];
5289 5289
             $this->currentStrokeColor = $this->stateStack[$n]['str'];
5290
-            $this->addContent("\n" . $this->stateStack[$n]['lin']);
5290
+            $this->addContent("\n".$this->stateStack[$n]['lin']);
5291 5291
             $this->currentLineStyle = $this->stateStack[$n]['lin'];
5292 5292
             $this->stateStack[$n] = null;
5293 5293
             unset($this->stateStack[$n]);
@@ -5561,7 +5561,7 @@  discard block
 block discarded – undo
5561 5561
      */
5562 5562
     function addImagePng(&$img, $file, $x, $y, $w = 0.0, $h = 0.0, $is_mask = false, $mask = null)
5563 5563
     {
5564
-        if (!function_exists("imagepng")) {
5564
+        if ( ! function_exists("imagepng")) {
5565 5565
             throw new \Exception("The PHP GD extension is required, but is not installed.");
5566 5566
         }
5567 5567
 
@@ -5587,7 +5587,7 @@  discard block
 block discarded – undo
5587 5587
             //DEBUG_IMG_TEMP
5588 5588
             //debugpng
5589 5589
             if (defined("DEBUGPNG") && DEBUGPNG) {
5590
-                print '[addImagePng ' . $file . ']';
5590
+                print '[addImagePng '.$file.']';
5591 5591
             }
5592 5592
 
5593 5593
             ob_start();
@@ -5605,7 +5605,7 @@  discard block
 block discarded – undo
5605 5605
             }
5606 5606
 
5607 5607
             if ($error) {
5608
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
5608
+                $this->addMessage('PNG error - ('.$file.') '.$errormsg);
5609 5609
 
5610 5610
                 return;
5611 5611
             }
@@ -5756,9 +5756,9 @@  discard block
 block discarded – undo
5756 5756
                         // without gamma correction
5757 5757
                         $pixel = (127 - $alpha) * 2;
5758 5758
 
5759
-                        $key = $col['red'] . $col['green'] . $col['blue'];
5759
+                        $key = $col['red'].$col['green'].$col['blue'];
5760 5760
 
5761
-                        if (!isset($allocated_colors[$key])) {
5761
+                        if ( ! isset($allocated_colors[$key])) {
5762 5762
                             $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']);
5763 5763
                             $allocated_colors[$key] = $pixel_img;
5764 5764
                         } else {
@@ -5809,7 +5809,7 @@  discard block
 block discarded – undo
5809 5809
      */
5810 5810
     function addPngFromFile($file, $x, $y, $w = 0, $h = 0)
5811 5811
     {
5812
-        if (!function_exists("imagecreatefrompng")) {
5812
+        if ( ! function_exists("imagecreatefrompng")) {
5813 5813
             throw new \Exception("The PHP GD extension is required, but is not installed.");
5814 5814
         }
5815 5815
 
@@ -5857,7 +5857,7 @@  discard block
 block discarded – undo
5857 5857
             //Therefore create an empty image with white background and merge the
5858 5858
             //image in with alpha blending.
5859 5859
             $imgtmp = @imagecreatefrompng($file);
5860
-            if (!$imgtmp) {
5860
+            if ( ! $imgtmp) {
5861 5861
                 return;
5862 5862
             }
5863 5863
             $sx = imagesx($imgtmp);
@@ -5940,21 +5940,21 @@  discard block
 block discarded – undo
5940 5940
 
5941 5941
             $error = 0;
5942 5942
 
5943
-            if (!$error) {
5944
-                $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10);
5943
+            if ( ! $error) {
5944
+                $header = chr(137).chr(80).chr(78).chr(71).chr(13).chr(10).chr(26).chr(10);
5945 5945
 
5946 5946
                 if (mb_substr($data, 0, 8, '8bit') != $header) {
5947 5947
                     $error = 1;
5948 5948
 
5949 5949
                     if (defined("DEBUGPNG") && DEBUGPNG) {
5950
-                        print '[addPngFromFile this file does not have a valid header ' . $file . ']';
5950
+                        print '[addPngFromFile this file does not have a valid header '.$file.']';
5951 5951
                     }
5952 5952
 
5953 5953
                     $errormsg = 'this file does not have a valid header';
5954 5954
                 }
5955 5955
             }
5956 5956
 
5957
-            if (!$error) {
5957
+            if ( ! $error) {
5958 5958
                 // set pointer
5959 5959
                 $p = 8;
5960 5960
                 $len = mb_strlen($data, '8bit');
@@ -5987,7 +5987,7 @@  discard block
 block discarded – undo
5987 5987
 
5988 5988
                                 //debugpng
5989 5989
                                 if (defined("DEBUGPNG") && DEBUGPNG) {
5990
-                                    print '[addPngFromFile unsupported compression method ' . $file . ']';
5990
+                                    print '[addPngFromFile unsupported compression method '.$file.']';
5991 5991
                                 }
5992 5992
 
5993 5993
                                 $errormsg = 'unsupported compression method';
@@ -5998,7 +5998,7 @@  discard block
 block discarded – undo
5998 5998
 
5999 5999
                                 //debugpng
6000 6000
                                 if (defined("DEBUGPNG") && DEBUGPNG) {
6001
-                                    print '[addPngFromFile unsupported filter method ' . $file . ']';
6001
+                                    print '[addPngFromFile unsupported filter method '.$file.']';
6002 6002
                                 }
6003 6003
 
6004 6004
                                 $errormsg = 'unsupported filter method';
@@ -6070,7 +6070,7 @@  discard block
 block discarded – undo
6070 6070
                                 //unsupported transparency type
6071 6071
                                 default:
6072 6072
                                     if (defined("DEBUGPNG") && DEBUGPNG) {
6073
-                                        print '[addPngFromFile unsupported transparency type ' . $file . ']';
6073
+                                        print '[addPngFromFile unsupported transparency type '.$file.']';
6074 6074
                                     }
6075 6075
                                     break;
6076 6076
                             }
@@ -6085,12 +6085,12 @@  discard block
 block discarded – undo
6085 6085
                     $p += $chunkLen + 12;
6086 6086
                 }
6087 6087
 
6088
-                if (!$haveHeader) {
6088
+                if ( ! $haveHeader) {
6089 6089
                     $error = 1;
6090 6090
 
6091 6091
                     //debugpng
6092 6092
                     if (defined("DEBUGPNG") && DEBUGPNG) {
6093
-                        print '[addPngFromFile information header is missing ' . $file . ']';
6093
+                        print '[addPngFromFile information header is missing '.$file.']';
6094 6094
                     }
6095 6095
 
6096 6096
                     $errormsg = 'information header is missing';
@@ -6101,25 +6101,25 @@  discard block
 block discarded – undo
6101 6101
 
6102 6102
                     //debugpng
6103 6103
                     if (defined("DEBUGPNG") && DEBUGPNG) {
6104
-                        print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']';
6104
+                        print '[addPngFromFile no support for interlaced images in pdf '.$file.']';
6105 6105
                     }
6106 6106
 
6107 6107
                     $errormsg = 'There appears to be no support for interlaced images in pdf.';
6108 6108
                 }
6109 6109
             }
6110 6110
 
6111
-            if (!$error && $info['bitDepth'] > 8) {
6111
+            if ( ! $error && $info['bitDepth'] > 8) {
6112 6112
                 $error = 1;
6113 6113
 
6114 6114
                 //debugpng
6115 6115
                 if (defined("DEBUGPNG") && DEBUGPNG) {
6116
-                    print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']';
6116
+                    print '[addPngFromFile bit depth of 8 or less is supported '.$file.']';
6117 6117
                 }
6118 6118
 
6119 6119
                 $errormsg = 'only bit depth of 8 or less is supported';
6120 6120
             }
6121 6121
 
6122
-            if (!$error) {
6122
+            if ( ! $error) {
6123 6123
                 switch ($info['colorType']) {
6124 6124
                     case 3:
6125 6125
                         $color = 'DeviceRGB';
@@ -6141,7 +6141,7 @@  discard block
 block discarded – undo
6141 6141
 
6142 6142
                         //debugpng
6143 6143
                         if (defined("DEBUGPNG") && DEBUGPNG) {
6144
-                            print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']';
6144
+                            print '[addPngFromFile alpha channel not supported: '.$info['colorType'].' '.$file.']';
6145 6145
                         }
6146 6146
 
6147 6147
                         $errormsg = 'transparency alpha channel not supported, transparency only supported for palette images.';
@@ -6149,7 +6149,7 @@  discard block
 block discarded – undo
6149 6149
             }
6150 6150
 
6151 6151
             if ($error) {
6152
-                $this->addMessage('PNG error - (' . $file . ') ' . $errormsg);
6152
+                $this->addMessage('PNG error - ('.$file.') '.$errormsg);
6153 6153
 
6154 6154
                 return;
6155 6155
             }
@@ -6218,7 +6218,7 @@  discard block
 block discarded – undo
6218 6218
         // attempt to add a jpeg image straight from a file, using no GD commands
6219 6219
         // note that this function is unable to operate on a remote file.
6220 6220
 
6221
-        if (!file_exists($img)) {
6221
+        if ( ! file_exists($img)) {
6222 6222
             return;
6223 6223
         }
6224 6224
 
@@ -6286,7 +6286,7 @@  discard block
 block discarded – undo
6286 6286
 
6287 6287
         } else {
6288 6288
             if ($data == null) {
6289
-                $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!');
6289
+                $this->addMessage('addJpegImage_common error - ('.$imgname.') data not present!');
6290 6290
 
6291 6291
                 return;
6292 6292
             }
@@ -6397,7 +6397,7 @@  discard block
 block discarded – undo
6397 6397
      */
6398 6398
     function setFontFamily($family, $options = '')
6399 6399
     {
6400
-        if (!is_array($options)) {
6400
+        if ( ! is_array($options)) {
6401 6401
             if ($family === 'init') {
6402 6402
                 // set the known family groups
6403 6403
                 // these font families will be used to enable bold and italic markers to be included
@@ -6443,7 +6443,7 @@  discard block
 block discarded – undo
6443 6443
      */
6444 6444
     function addMessage($message)
6445 6445
     {
6446
-        $this->messages .= $message . "\n";
6446
+        $this->messages .= $message."\n";
6447 6447
     }
6448 6448
 
6449 6449
     /**
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Dompdf.php 1 patch
Indentation   +1401 added lines, -1401 removed lines patch added patch discarded remove patch
@@ -66,1405 +66,1405 @@
 block discarded – undo
66 66
  */
67 67
 class Dompdf
68 68
 {
69
-    /**
70
-     * Version string for dompdf
71
-     *
72
-     * @var string
73
-     */
74
-    private $version = 'dompdf';
75
-
76
-    /**
77
-     * DomDocument representing the HTML document
78
-     *
79
-     * @var DOMDocument
80
-     */
81
-    private $dom;
82
-
83
-    /**
84
-     * FrameTree derived from the DOM tree
85
-     *
86
-     * @var FrameTree
87
-     */
88
-    private $tree;
89
-
90
-    /**
91
-     * Stylesheet for the document
92
-     *
93
-     * @var Stylesheet
94
-     */
95
-    private $css;
96
-
97
-    /**
98
-     * Actual PDF renderer
99
-     *
100
-     * @var Canvas
101
-     */
102
-    private $canvas;
103
-
104
-    /**
105
-     * Desired paper size ('letter', 'legal', 'A4', etc.)
106
-     *
107
-     * @var string|array
108
-     */
109
-    private $paperSize;
110
-
111
-    /**
112
-     * Paper orientation ('portrait' or 'landscape')
113
-     *
114
-     * @var string
115
-     */
116
-    private $paperOrientation = "portrait";
117
-
118
-    /**
119
-     * Callbacks on new page and new element
120
-     *
121
-     * @var array
122
-     */
123
-    private $callbacks = [];
124
-
125
-    /**
126
-     * Experimental caching capability
127
-     *
128
-     * @var string
129
-     */
130
-    private $cacheId;
131
-
132
-    /**
133
-     * Base hostname
134
-     *
135
-     * Used for relative paths/urls
136
-     * @var string
137
-     */
138
-    private $baseHost = "";
139
-
140
-    /**
141
-     * Absolute base path
142
-     *
143
-     * Used for relative paths/urls
144
-     * @var string
145
-     */
146
-    private $basePath = "";
147
-
148
-    /**
149
-     * Protocol used to request file (file://, http://, etc)
150
-     *
151
-     * @var string
152
-     */
153
-    private $protocol = "";
154
-
155
-    /**
156
-     * The system's locale
157
-     *
158
-     * @var string
159
-     */
160
-    private $systemLocale = null;
161
-
162
-    /**
163
-     * The system's mbstring internal encoding
164
-     *
165
-     * @var string
166
-     */
167
-    private $mbstringEncoding = null;
168
-
169
-    /**
170
-     * The system's PCRE JIT configuration
171
-     *
172
-     * @var string
173
-     */
174
-    private $pcreJit = null;
175
-
176
-    /**
177
-     * The default view of the PDF in the viewer
178
-     *
179
-     * @var string
180
-     */
181
-    private $defaultView = "Fit";
182
-
183
-    /**
184
-     * The default view options of the PDF in the viewer
185
-     *
186
-     * @var array
187
-     */
188
-    private $defaultViewOptions = [];
189
-
190
-    /**
191
-     * Tells whether the DOM document is in quirksmode (experimental)
192
-     *
193
-     * @var bool
194
-     */
195
-    private $quirksmode = false;
196
-
197
-    /**
198
-    * Local file extension whitelist
199
-    *
200
-    * File extensions supported by dompdf for local files.
201
-    *
202
-    * @var array
203
-    */
204
-    private $allowedLocalFileExtensions = ["htm", "html"];
205
-
206
-    /**
207
-     * @var array
208
-     */
209
-    private $messages = [];
210
-
211
-    /**
212
-     * @var Options
213
-     */
214
-    private $options;
215
-
216
-    /**
217
-     * @var FontMetrics
218
-     */
219
-    private $fontMetrics;
220
-
221
-    /**
222
-     * The list of built-in fonts
223
-     *
224
-     * @var array
225
-     * @deprecated
226
-     */
227
-    public static $native_fonts = [
228
-        "courier", "courier-bold", "courier-oblique", "courier-boldoblique",
229
-        "helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique",
230
-        "times-roman", "times-bold", "times-italic", "times-bolditalic",
231
-        "symbol", "zapfdinbats"
232
-    ];
233
-
234
-    /**
235
-     * The list of built-in fonts
236
-     *
237
-     * @var array
238
-     */
239
-    public static $nativeFonts = [
240
-        "courier", "courier-bold", "courier-oblique", "courier-boldoblique",
241
-        "helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique",
242
-        "times-roman", "times-bold", "times-italic", "times-bolditalic",
243
-        "symbol", "zapfdinbats"
244
-    ];
245
-
246
-    /**
247
-     * Class constructor
248
-     *
249
-     * @param Options|array|null $options
250
-     */
251
-    public function __construct($options = null)
252
-    {
253
-        if (isset($options) && $options instanceof Options) {
254
-            $this->setOptions($options);
255
-        } elseif (is_array($options)) {
256
-            $this->setOptions(new Options($options));
257
-        } else {
258
-            $this->setOptions(new Options());
259
-        }
260
-
261
-        $versionFile = realpath(__DIR__ . '/../VERSION');
262
-        if (($version = file_get_contents($versionFile)) !== false) {
263
-            $version = trim($version);
264
-            if ($version !== '$Format:<%h>$') {
265
-                $this->version = sprintf('dompdf %s', $version);
266
-            }
267
-        }
268
-
269
-        $this->setPhpConfig();
270
-
271
-        $this->paperSize = $this->options->getDefaultPaperSize();
272
-        $this->paperOrientation = $this->options->getDefaultPaperOrientation();
273
-
274
-        $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
275
-        $this->fontMetrics = new FontMetrics($this->canvas, $this->options);
276
-        $this->css = new Stylesheet($this);
277
-
278
-        $this->restorePhpConfig();
279
-    }
280
-
281
-    /**
282
-     * Save the system's existing locale, PCRE JIT, and MBString encoding
283
-     * configuration and configure the system for Dompdf processing
284
-     */
285
-    private function setPhpConfig()
286
-    {
287
-        if (sprintf('%.1f', 1.0) !== '1.0') {
288
-            $this->systemLocale = setlocale(LC_NUMERIC, "0");
289
-            setlocale(LC_NUMERIC, "C");
290
-        }
291
-
292
-        $this->pcreJit = @ini_get('pcre.jit');
293
-        @ini_set('pcre.jit', '0');
294
-
295
-        $this->mbstringEncoding = mb_internal_encoding();
296
-        mb_internal_encoding('UTF-8');
297
-    }
298
-
299
-    /**
300
-     * Restore the system's locale configuration
301
-     */
302
-    private function restorePhpConfig()
303
-    {
304
-        if ($this->systemLocale !== null) {
305
-            setlocale(LC_NUMERIC, $this->systemLocale);
306
-            $this->systemLocale = null;
307
-        }
308
-
309
-        if ($this->pcreJit !== null) {
310
-            @ini_set('pcre.jit', $this->pcreJit);
311
-            $this->pcreJit = null;
312
-        }
313
-
314
-        if ($this->mbstringEncoding !== null) {
315
-            mb_internal_encoding($this->mbstringEncoding);
316
-            $this->mbstringEncoding = null;
317
-        }
318
-    }
319
-
320
-    /**
321
-     * @param $file
322
-     * @deprecated
323
-     */
324
-    public function load_html_file($file)
325
-    {
326
-        $this->loadHtmlFile($file);
327
-    }
328
-
329
-    /**
330
-     * Loads an HTML file
331
-     * Parse errors are stored in the global array _dompdf_warnings.
332
-     *
333
-     * @param string $file a filename or url to load
334
-     * @param string $encoding Encoding of $file
335
-     *
336
-     * @throws Exception
337
-     */
338
-    public function loadHtmlFile($file, $encoding = null)
339
-    {
340
-        $this->setPhpConfig();
341
-
342
-        if (!$this->protocol && !$this->baseHost && !$this->basePath) {
343
-            [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($file);
344
-        }
345
-        $protocol = strtolower($this->protocol);
346
-        $uri = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $file);
347
-
348
-        $allowed_protocols = $this->options->getAllowedProtocols();
349
-        if (!array_key_exists($protocol, $allowed_protocols)) {
350
-            throw new Exception("Permission denied on $file. The communication protocol is not supported.");
351
-        }
352
-
353
-        if ($protocol === "file://") {
354
-            $ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION));
355
-            if (!in_array($ext, $this->allowedLocalFileExtensions)) {
356
-                throw new Exception("Permission denied on $file: The file extension is forbidden.");
357
-            }
358
-        }
359
-
360
-        foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
361
-            [$result, $message] = $rule($uri);
362
-            if (!$result) {
363
-                throw new Exception("Error loading $file: $message");
364
-            }
365
-        }
366
-
367
-        [$contents, $http_response_header] = Helpers::getFileContent($uri, $this->options->getHttpContext());
368
-        if ($contents === null) {
369
-            throw new Exception("File '$file' not found.");
370
-        }
371
-
372
-        // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
373
-        if (isset($http_response_header)) {
374
-            foreach ($http_response_header as $_header) {
375
-                if (preg_match("@Content-Type:\s*[\w/]+;\s*?charset=([^\s]+)@i", $_header, $matches)) {
376
-                    $encoding = strtoupper($matches[1]);
377
-                    break;
378
-                }
379
-            }
380
-        }
381
-
382
-        $this->restorePhpConfig();
383
-
384
-        $this->loadHtml($contents, $encoding);
385
-    }
386
-
387
-    /**
388
-     * @param string $str
389
-     * @param string $encoding
390
-     * @deprecated
391
-     */
392
-    public function load_html($str, $encoding = null)
393
-    {
394
-        $this->loadHtml($str, $encoding);
395
-    }
396
-
397
-    public function loadDOM($doc, $quirksmode = false) {
398
-        // Remove #text children nodes in nodes that shouldn't have
399
-        $tag_names = ["html", "head", "table", "tbody", "thead", "tfoot", "tr"];
400
-        foreach ($tag_names as $tag_name) {
401
-            $nodes = $doc->getElementsByTagName($tag_name);
402
-
403
-            foreach ($nodes as $node) {
404
-                self::removeTextNodes($node);
405
-            }
406
-        }
407
-
408
-        $this->dom = $doc;
409
-        $this->quirksmode = $quirksmode;
410
-        $this->tree = new FrameTree($this->dom);
411
-    }
412
-
413
-    /**
414
-     * Loads an HTML string
415
-     * Parse errors are stored in the global array _dompdf_warnings.
416
-     *
417
-     * @param string $str HTML text to load
418
-     * @param string $encoding Encoding of $str
419
-     */
420
-    public function loadHtml($str, $encoding = null)
421
-    {
422
-        $this->setPhpConfig();
423
-
424
-        // Determine character encoding when $encoding parameter not used
425
-        if ($encoding === null) {
426
-            mb_detect_order('auto');
427
-            if (($encoding = mb_detect_encoding($str, null, true)) === false) {
428
-
429
-                //"auto" is expanded to "ASCII,JIS,UTF-8,EUC-JP,SJIS"
430
-                $encoding = "auto";
431
-            }
432
-        }
433
-
434
-        if (in_array(strtoupper($encoding), array('UTF-8','UTF8')) === false) {
435
-            $str = mb_convert_encoding($str, 'UTF-8', $encoding);
436
-
437
-            //Update encoding after converting
438
-            $encoding = 'UTF-8';
439
-        }
440
-
441
-        $metatags = [
442
-            '@<meta\s+http-equiv="Content-Type"\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))?@i',
443
-            '@<meta\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))"?\s+http-equiv="Content-Type"@i',
444
-            '@<meta [^>]*charset\s*=\s*["\']?\s*([^"\' ]+)@i',
445
-        ];
446
-        foreach ($metatags as $metatag) {
447
-            if (preg_match($metatag, $str, $matches)) {
448
-                if (isset($matches[1]) && in_array($matches[1], mb_list_encodings())) {
449
-                    $document_encoding = $matches[1];
450
-                    break;
451
-                }
452
-            }
453
-        }
454
-        if (isset($document_encoding) && in_array(strtoupper($document_encoding), ['UTF-8','UTF8']) === false) {
455
-            $str = preg_replace('/charset=([^\s"]+)/i', 'charset=UTF-8', $str);
456
-        } elseif (isset($document_encoding) === false && strpos($str, '<head>') !== false) {
457
-            $str = str_replace('<head>', '<head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">', $str);
458
-        } elseif (isset($document_encoding) === false) {
459
-            $str = '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">' . $str;
460
-        }
461
-
462
-        // remove BOM mark from UTF-8, it's treated as document text by DOMDocument
463
-        // FIXME: roll this into the encoding detection using UTF-8/16/32 BOM (http://us2.php.net/manual/en/function.mb-detect-encoding.php#91051)?
464
-        if (substr($str, 0, 3) == chr(0xEF) . chr(0xBB) . chr(0xBF)) {
465
-            $str = substr($str, 3);
466
-        }
467
-
468
-        // Store parsing warnings as messages
469
-        set_error_handler([Helpers::class, 'record_warnings']);
470
-
471
-        try {
472
-            // @todo Take the quirksmode into account
473
-            // https://quirks.spec.whatwg.org/
474
-            // http://hsivonen.iki.fi/doctype/
475
-            $quirksmode = false;
476
-
477
-            $html5 = new HTML5(['encoding' => $encoding, 'disable_html_ns' => true]);
478
-            $dom = $html5->loadHTML($str);
479
-
480
-            // extra step to normalize the HTML document structure
481
-            // see Masterminds/html5-php#166
482
-            $doc = new DOMDocument("1.0", $encoding);
483
-            $doc->preserveWhiteSpace = true;
484
-            $doc->loadHTML($html5->saveHTML($dom), LIBXML_NOWARNING | LIBXML_NOERROR);
485
-
486
-            $this->loadDOM($doc, $quirksmode);
487
-        } finally {
488
-            restore_error_handler();
489
-            $this->restorePhpConfig();
490
-        }
491
-    }
492
-
493
-    /**
494
-     * @param DOMNode $node
495
-     * @deprecated
496
-     */
497
-    public static function remove_text_nodes(DOMNode $node)
498
-    {
499
-        self::removeTextNodes($node);
500
-    }
501
-
502
-    /**
503
-     * @param DOMNode $node
504
-     */
505
-    public static function removeTextNodes(DOMNode $node)
506
-    {
507
-        $children = [];
508
-        for ($i = 0; $i < $node->childNodes->length; $i++) {
509
-            $child = $node->childNodes->item($i);
510
-            if ($child->nodeName === "#text") {
511
-                $children[] = $child;
512
-            }
513
-        }
514
-
515
-        foreach ($children as $child) {
516
-            $node->removeChild($child);
517
-        }
518
-    }
519
-
520
-    /**
521
-     * Builds the {@link FrameTree}, loads any CSS and applies the styles to
522
-     * the {@link FrameTree}
523
-     */
524
-    private function processHtml()
525
-    {
526
-        $this->tree->build_tree();
527
-
528
-        $this->css->load_css_file($this->css->getDefaultStylesheet(), Stylesheet::ORIG_UA);
529
-
530
-        $acceptedmedia = Stylesheet::$ACCEPTED_GENERIC_MEDIA_TYPES;
531
-        $acceptedmedia[] = $this->options->getDefaultMediaType();
532
-
533
-        // <base href="" />
534
-        /** @var \DOMElement|null */
535
-        $baseNode = $this->dom->getElementsByTagName("base")->item(0);
536
-        $baseHref = $baseNode ? $baseNode->getAttribute("href") : "";
537
-        if ($baseHref !== "") {
538
-            [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($baseHref);
539
-        }
540
-
541
-        // Set the base path of the Stylesheet to that of the file being processed
542
-        $this->css->set_protocol($this->protocol);
543
-        $this->css->set_host($this->baseHost);
544
-        $this->css->set_base_path($this->basePath);
545
-
546
-        // Get all the stylesheets so that they are processed in document order
547
-        $xpath = new DOMXPath($this->dom);
548
-        $stylesheets = $xpath->query("//*[name() = 'link' or name() = 'style']");
549
-
550
-        /** @var \DOMElement $tag */
551
-        foreach ($stylesheets as $tag) {
552
-            switch (strtolower($tag->nodeName)) {
553
-                // load <link rel="STYLESHEET" ... /> tags
554
-                case "link":
555
-                    if (mb_strtolower(stripos($tag->getAttribute("rel"), "stylesheet") !== false) || // may be "appendix stylesheet"
556
-                        mb_strtolower($tag->getAttribute("type")) === "text/css"
557
-                    ) {
558
-                        //Check if the css file is for an accepted media type
559
-                        //media not given then always valid
560
-                        $formedialist = preg_split("/[\s\n,]/", $tag->getAttribute("media"), -1, PREG_SPLIT_NO_EMPTY);
561
-                        if (count($formedialist) > 0) {
562
-                            $accept = false;
563
-                            foreach ($formedialist as $type) {
564
-                                if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) {
565
-                                    $accept = true;
566
-                                    break;
567
-                                }
568
-                            }
569
-
570
-                            if (!$accept) {
571
-                                //found at least one mediatype, but none of the accepted ones
572
-                                //Skip this css file.
573
-                                break;
574
-                            }
575
-                        }
576
-
577
-                        $url = $tag->getAttribute("href");
578
-                        $url = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $url);
579
-
580
-                        if ($url !== null) {
581
-                            $this->css->load_css_file($url, Stylesheet::ORIG_AUTHOR);
582
-                        }
583
-                    }
584
-                    break;
585
-
586
-                // load <style> tags
587
-                case "style":
588
-                    // Accept all <style> tags by default (note this is contrary to W3C
589
-                    // HTML 4.0 spec:
590
-                    // http://www.w3.org/TR/REC-html40/present/styles.html#adef-media
591
-                    // which states that the default media type is 'screen'
592
-                    if ($tag->hasAttributes() &&
593
-                        ($media = $tag->getAttribute("media")) &&
594
-                        !in_array($media, $acceptedmedia)
595
-                    ) {
596
-                        break;
597
-                    }
598
-
599
-                    $css = "";
600
-                    if ($tag->hasChildNodes()) {
601
-                        $child = $tag->firstChild;
602
-                        while ($child) {
603
-                            $css .= $child->nodeValue; // Handle <style><!-- blah --></style>
604
-                            $child = $child->nextSibling;
605
-                        }
606
-                    } else {
607
-                        $css = $tag->nodeValue;
608
-                    }
609
-
610
-                    // Set the base path of the Stylesheet to that of the file being processed
611
-                    $this->css->set_protocol($this->protocol);
612
-                    $this->css->set_host($this->baseHost);
613
-                    $this->css->set_base_path($this->basePath);
614
-
615
-                    $this->css->load_css($css, Stylesheet::ORIG_AUTHOR);
616
-                    break;
617
-            }
618
-
619
-            // Set the base path of the Stylesheet to that of the file being processed
620
-            $this->css->set_protocol($this->protocol);
621
-            $this->css->set_host($this->baseHost);
622
-            $this->css->set_base_path($this->basePath);
623
-        }
624
-    }
625
-
626
-    /**
627
-     * @param string $cacheId
628
-     * @deprecated
629
-     */
630
-    public function enable_caching($cacheId)
631
-    {
632
-        $this->enableCaching($cacheId);
633
-    }
634
-
635
-    /**
636
-     * Enable experimental caching capability
637
-     *
638
-     * @param string $cacheId
639
-     */
640
-    public function enableCaching($cacheId)
641
-    {
642
-        $this->cacheId = $cacheId;
643
-    }
644
-
645
-    /**
646
-     * @param string $value
647
-     * @return bool
648
-     * @deprecated
649
-     */
650
-    public function parse_default_view($value)
651
-    {
652
-        return $this->parseDefaultView($value);
653
-    }
654
-
655
-    /**
656
-     * @param string $value
657
-     * @return bool
658
-     */
659
-    public function parseDefaultView($value)
660
-    {
661
-        $valid = ["XYZ", "Fit", "FitH", "FitV", "FitR", "FitB", "FitBH", "FitBV"];
662
-
663
-        $options = preg_split("/\s*,\s*/", trim($value));
664
-        $defaultView = array_shift($options);
665
-
666
-        if (!in_array($defaultView, $valid)) {
667
-            return false;
668
-        }
669
-
670
-        $this->setDefaultView($defaultView, $options);
671
-        return true;
672
-    }
673
-
674
-    /**
675
-     * Renders the HTML to PDF
676
-     */
677
-    public function render()
678
-    {
679
-        $this->setPhpConfig();
680
-
681
-        $logOutputFile = $this->options->getLogOutputFile();
682
-        if ($logOutputFile) {
683
-            if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
684
-                touch($logOutputFile);
685
-            }
686
-
687
-            $startTime = microtime(true);
688
-            if (is_writable($logOutputFile)) {
689
-                ob_start();
690
-            }
691
-        }
692
-
693
-        $this->processHtml();
694
-
695
-        $this->css->apply_styles($this->tree);
696
-
697
-        // @page style rules : size, margins
698
-        $pageStyles = $this->css->get_page_styles();
699
-        $basePageStyle = $pageStyles["base"];
700
-        unset($pageStyles["base"]);
701
-
702
-        foreach ($pageStyles as $pageStyle) {
703
-            $pageStyle->inherit($basePageStyle);
704
-        }
705
-
706
-        // Set paper size if defined via CSS
707
-        if (is_array($basePageStyle->size)) {
708
-            [$width, $height] = $basePageStyle->size;
709
-            $this->setPaper([0, 0, $width, $height]);
710
-        }
711
-
712
-        // Create a new canvas instance if the current one does not match the
713
-        // desired paper size
714
-        $canvasWidth = $this->canvas->get_width();
715
-        $canvasHeight = $this->canvas->get_height();
716
-        $size = $this->getPaperSize();
717
-
718
-        if ($canvasWidth !== $size[2] || $canvasHeight !== $size[3]) {
719
-            $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
720
-            $this->fontMetrics->setCanvas($this->canvas);
721
-        }
722
-
723
-        $canvas = $this->canvas;
724
-
725
-        $root_frame = $this->tree->get_root();
726
-        $root = Factory::decorate_root($root_frame, $this);
727
-        foreach ($this->tree as $frame) {
728
-            if ($frame === $root_frame) {
729
-                continue;
730
-            }
731
-            Factory::decorate_frame($frame, $this, $root);
732
-        }
733
-
734
-        // Add meta information
735
-        $title = $this->dom->getElementsByTagName("title");
736
-        if ($title->length) {
737
-            $canvas->add_info("Title", trim($title->item(0)->nodeValue));
738
-        }
739
-
740
-        $metas = $this->dom->getElementsByTagName("meta");
741
-        $labels = [
742
-            "author" => "Author",
743
-            "keywords" => "Keywords",
744
-            "description" => "Subject",
745
-        ];
746
-        /** @var \DOMElement $meta */
747
-        foreach ($metas as $meta) {
748
-            $name = mb_strtolower($meta->getAttribute("name"));
749
-            $value = trim($meta->getAttribute("content"));
750
-
751
-            if (isset($labels[$name])) {
752
-                $canvas->add_info($labels[$name], $value);
753
-                continue;
754
-            }
755
-
756
-            if ($name === "dompdf.view" && $this->parseDefaultView($value)) {
757
-                $canvas->set_default_view($this->defaultView, $this->defaultViewOptions);
758
-            }
759
-        }
760
-
761
-        $root->set_containing_block(0, 0, $canvas->get_width(), $canvas->get_height());
762
-        $root->set_renderer(new Renderer($this));
763
-
764
-        // This is where the magic happens:
765
-        $root->reflow();
766
-
767
-        if (isset($this->callbacks["end_document"])) {
768
-            $fs = $this->callbacks["end_document"];
769
-
770
-            foreach ($fs as $f) {
771
-                $canvas->page_script($f);
772
-            }
773
-        }
774
-
775
-        // Clean up cached images
776
-        if (!$this->options->getDebugKeepTemp()) {
777
-            Cache::clear($this->options->getDebugPng());
778
-        }
779
-
780
-        global $_dompdf_warnings, $_dompdf_show_warnings;
781
-        if ($_dompdf_show_warnings && isset($_dompdf_warnings)) {
782
-            echo '<b>Dompdf Warnings</b><br><pre>';
783
-            foreach ($_dompdf_warnings as $msg) {
784
-                echo $msg . "\n";
785
-            }
786
-
787
-            if ($canvas instanceof CPDF) {
788
-                echo $canvas->get_cpdf()->messages;
789
-            }
790
-            echo '</pre>';
791
-            flush();
792
-        }
793
-
794
-        if ($logOutputFile && is_writable($logOutputFile)) {
795
-            $this->writeLog($logOutputFile, $startTime);
796
-            ob_end_clean();
797
-        }
798
-
799
-        $this->restorePhpConfig();
800
-    }
801
-
802
-    /**
803
-     * Writes the output buffer in the log file
804
-     *
805
-     * @param string $logOutputFile
806
-     * @param float $startTime
807
-     */
808
-    private function writeLog(string $logOutputFile, float $startTime): void
809
-    {
810
-        $frames = Frame::$ID_COUNTER;
811
-        $memory = memory_get_peak_usage(true) / 1024;
812
-        $time = (microtime(true) - $startTime) * 1000;
813
-
814
-        $out = sprintf(
815
-            "<span style='color: #000' title='Frames'>%6d</span>" .
816
-            "<span style='color: #009' title='Memory'>%10.2f KB</span>" .
817
-            "<span style='color: #900' title='Time'>%10.2f ms</span>" .
818
-            "<span  title='Quirksmode'>  " .
819
-            ($this->quirksmode ? "<span style='color: #d00'> ON</span>" : "<span style='color: #0d0'>OFF</span>") .
820
-            "</span><br />", $frames, $memory, $time);
821
-
822
-        $out .= ob_get_contents();
823
-        ob_clean();
824
-
825
-        file_put_contents($logOutputFile, $out);
826
-    }
827
-
828
-    /**
829
-     * Add meta information to the PDF after rendering.
830
-     *
831
-     * @deprecated
832
-     */
833
-    public function add_info($label, $value)
834
-    {
835
-        $this->addInfo($label, $value);
836
-    }
837
-
838
-    /**
839
-     * Add meta information to the PDF after rendering.
840
-     *
841
-     * @param string $label Label of the value (Creator, Producer, etc.)
842
-     * @param string $value The text to set
843
-     */
844
-    public function addInfo(string $label, string $value): void
845
-    {
846
-        $this->canvas->add_info($label, $value);
847
-    }
848
-
849
-    /**
850
-     * Streams the PDF to the client.
851
-     *
852
-     * The file will open a download dialog by default. The options
853
-     * parameter controls the output. Accepted options (array keys) are:
854
-     *
855
-     * 'compress' = > 1 (=default) or 0:
856
-     *   Apply content stream compression
857
-     *
858
-     * 'Attachment' => 1 (=default) or 0:
859
-     *   Set the 'Content-Disposition:' HTTP header to 'attachment'
860
-     *   (thereby causing the browser to open a download dialog)
861
-     *
862
-     * @param string $filename the name of the streamed file
863
-     * @param array $options header options (see above)
864
-     */
865
-    public function stream($filename = "document.pdf", $options = [])
866
-    {
867
-        $this->setPhpConfig();
868
-
869
-        $this->canvas->stream($filename, $options);
870
-
871
-        $this->restorePhpConfig();
872
-    }
873
-
874
-    /**
875
-     * Returns the PDF as a string.
876
-     *
877
-     * The options parameter controls the output. Accepted options are:
878
-     *
879
-     * 'compress' = > 1 or 0 - apply content stream compression, this is
880
-     *    on (1) by default
881
-     *
882
-     * @param array $options options (see above)
883
-     *
884
-     * @return string|null
885
-     */
886
-    public function output($options = [])
887
-    {
888
-        $this->setPhpConfig();
889
-
890
-        $output = $this->canvas->output($options);
891
-
892
-        $this->restorePhpConfig();
893
-
894
-        return $output;
895
-    }
896
-
897
-    /**
898
-     * @return string
899
-     * @deprecated
900
-     */
901
-    public function output_html()
902
-    {
903
-        return $this->outputHtml();
904
-    }
905
-
906
-    /**
907
-     * Returns the underlying HTML document as a string
908
-     *
909
-     * @return string
910
-     */
911
-    public function outputHtml()
912
-    {
913
-        return $this->dom->saveHTML();
914
-    }
915
-
916
-    /**
917
-     * Get the dompdf option value
918
-     *
919
-     * @param string $key
920
-     * @return mixed
921
-     * @deprecated
922
-     */
923
-    public function get_option($key)
924
-    {
925
-        return $this->options->get($key);
926
-    }
927
-
928
-    /**
929
-     * @param string $key
930
-     * @param mixed $value
931
-     * @return $this
932
-     * @deprecated
933
-     */
934
-    public function set_option($key, $value)
935
-    {
936
-        $this->options->set($key, $value);
937
-        return $this;
938
-    }
939
-
940
-    /**
941
-     * @param array $options
942
-     * @return $this
943
-     * @deprecated
944
-     */
945
-    public function set_options(array $options)
946
-    {
947
-        $this->options->set($options);
948
-        return $this;
949
-    }
950
-
951
-    /**
952
-     * @param string $size
953
-     * @param string $orientation
954
-     * @deprecated
955
-     */
956
-    public function set_paper($size, $orientation = "portrait")
957
-    {
958
-        $this->setPaper($size, $orientation);
959
-    }
960
-
961
-    /**
962
-     * Sets the paper size & orientation
963
-     *
964
-     * @param string|float[] $size 'letter', 'legal', 'A4', etc. {@link Dompdf\Adapter\CPDF::$PAPER_SIZES}
965
-     * @param string $orientation 'portrait' or 'landscape'
966
-     * @return $this
967
-     */
968
-    public function setPaper($size, $orientation = "portrait")
969
-    {
970
-        $this->paperSize = $size;
971
-        $this->paperOrientation = $orientation;
972
-        return $this;
973
-    }
974
-
975
-    /**
976
-     * Gets the paper size
977
-     *
978
-     * @return float[] A four-element float array
979
-     */
980
-    public function getPaperSize()
981
-    {
982
-        $paper = $this->paperSize;
983
-        $orientation = $this->paperOrientation;
984
-
985
-        if (is_array($paper)) {
986
-            $size = array_map("floatval", $paper);
987
-        } else {
988
-            $paper = strtolower($paper);
989
-            $size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"];
990
-        }
991
-
992
-        if (strtolower($orientation) === "landscape") {
993
-            [$size[2], $size[3]] = [$size[3], $size[2]];
994
-        }
995
-
996
-        return $size;
997
-    }
998
-
999
-    /**
1000
-     * Gets the paper orientation
1001
-     *
1002
-     * @return string Either "portrait" or "landscape"
1003
-     */
1004
-    public function getPaperOrientation()
1005
-    {
1006
-        return $this->paperOrientation;
1007
-    }
1008
-
1009
-    /**
1010
-     * @param FrameTree $tree
1011
-     * @return $this
1012
-     */
1013
-    public function setTree(FrameTree $tree)
1014
-    {
1015
-        $this->tree = $tree;
1016
-        return $this;
1017
-    }
1018
-
1019
-    /**
1020
-     * @return FrameTree
1021
-     * @deprecated
1022
-     */
1023
-    public function get_tree()
1024
-    {
1025
-        return $this->getTree();
1026
-    }
1027
-
1028
-    /**
1029
-     * Returns the underlying {@link FrameTree} object
1030
-     *
1031
-     * @return FrameTree
1032
-     */
1033
-    public function getTree()
1034
-    {
1035
-        return $this->tree;
1036
-    }
1037
-
1038
-    /**
1039
-     * @param string $protocol
1040
-     * @return $this
1041
-     * @deprecated
1042
-     */
1043
-    public function set_protocol($protocol)
1044
-    {
1045
-        return $this->setProtocol($protocol);
1046
-    }
1047
-
1048
-    /**
1049
-     * Sets the protocol to use
1050
-     * FIXME validate these
1051
-     *
1052
-     * @param string $protocol
1053
-     * @return $this
1054
-     */
1055
-    public function setProtocol(string $protocol)
1056
-    {
1057
-        $this->protocol = $protocol;
1058
-        return $this;
1059
-    }
1060
-
1061
-    /**
1062
-     * @return string
1063
-     * @deprecated
1064
-     */
1065
-    public function get_protocol()
1066
-    {
1067
-        return $this->getProtocol();
1068
-    }
1069
-
1070
-    /**
1071
-     * Returns the protocol in use
1072
-     *
1073
-     * @return string
1074
-     */
1075
-    public function getProtocol()
1076
-    {
1077
-        return $this->protocol;
1078
-    }
1079
-
1080
-    /**
1081
-     * @param string $host
1082
-     * @deprecated
1083
-     */
1084
-    public function set_host($host)
1085
-    {
1086
-        $this->setBaseHost($host);
1087
-    }
1088
-
1089
-    /**
1090
-     * Sets the base hostname
1091
-     *
1092
-     * @param string $baseHost
1093
-     * @return $this
1094
-     */
1095
-    public function setBaseHost(string $baseHost)
1096
-    {
1097
-        $this->baseHost = $baseHost;
1098
-        return $this;
1099
-    }
1100
-
1101
-    /**
1102
-     * @return string
1103
-     * @deprecated
1104
-     */
1105
-    public function get_host()
1106
-    {
1107
-        return $this->getBaseHost();
1108
-    }
1109
-
1110
-    /**
1111
-     * Returns the base hostname
1112
-     *
1113
-     * @return string
1114
-     */
1115
-    public function getBaseHost()
1116
-    {
1117
-        return $this->baseHost;
1118
-    }
1119
-
1120
-    /**
1121
-     * Sets the base path
1122
-     *
1123
-     * @param string $path
1124
-     * @deprecated
1125
-     */
1126
-    public function set_base_path($path)
1127
-    {
1128
-        $this->setBasePath($path);
1129
-    }
1130
-
1131
-    /**
1132
-     * Sets the base path
1133
-     *
1134
-     * @param string $basePath
1135
-     * @return $this
1136
-     */
1137
-    public function setBasePath(string $basePath)
1138
-    {
1139
-        $this->basePath = $basePath;
1140
-        return $this;
1141
-    }
1142
-
1143
-    /**
1144
-     * @return string
1145
-     * @deprecated
1146
-     */
1147
-    public function get_base_path()
1148
-    {
1149
-        return $this->getBasePath();
1150
-    }
1151
-
1152
-    /**
1153
-     * Returns the base path
1154
-     *
1155
-     * @return string
1156
-     */
1157
-    public function getBasePath()
1158
-    {
1159
-        return $this->basePath;
1160
-    }
1161
-
1162
-    /**
1163
-     * @param string $default_view The default document view
1164
-     * @param array $options The view's options
1165
-     * @return $this
1166
-     * @deprecated
1167
-     */
1168
-    public function set_default_view($default_view, $options)
1169
-    {
1170
-        return $this->setDefaultView($default_view, $options);
1171
-    }
1172
-
1173
-    /**
1174
-     * Sets the default view
1175
-     *
1176
-     * @param string $defaultView The default document view
1177
-     * @param array $options The view's options
1178
-     * @return $this
1179
-     */
1180
-    public function setDefaultView($defaultView, $options)
1181
-    {
1182
-        $this->defaultView = $defaultView;
1183
-        $this->defaultViewOptions = $options;
1184
-        return $this;
1185
-    }
1186
-
1187
-    /**
1188
-     * @param resource $http_context
1189
-     * @return $this
1190
-     * @deprecated
1191
-     */
1192
-    public function set_http_context($http_context)
1193
-    {
1194
-        return $this->setHttpContext($http_context);
1195
-    }
1196
-
1197
-    /**
1198
-     * Sets the HTTP context
1199
-     *
1200
-     * @param resource|array $httpContext
1201
-     * @return $this
1202
-     */
1203
-    public function setHttpContext($httpContext)
1204
-    {
1205
-        $this->options->setHttpContext($httpContext);
1206
-        return $this;
1207
-    }
1208
-
1209
-    /**
1210
-     * @return resource
1211
-     * @deprecated
1212
-     */
1213
-    public function get_http_context()
1214
-    {
1215
-        return $this->getHttpContext();
1216
-    }
1217
-
1218
-    /**
1219
-     * Returns the HTTP context
1220
-     *
1221
-     * @return resource
1222
-     */
1223
-    public function getHttpContext()
1224
-    {
1225
-        return $this->options->getHttpContext();
1226
-    }
1227
-
1228
-    /**
1229
-     * Set a custom `Canvas` instance to render the document to.
1230
-     *
1231
-     * Be aware that the instance will be replaced on render if the document
1232
-     * defines a paper size different from the canvas.
1233
-     *
1234
-     * @param Canvas $canvas
1235
-     * @return $this
1236
-     */
1237
-    public function setCanvas(Canvas $canvas)
1238
-    {
1239
-        $this->canvas = $canvas;
1240
-        return $this;
1241
-    }
1242
-
1243
-    /**
1244
-     * @return Canvas
1245
-     * @deprecated
1246
-     */
1247
-    public function get_canvas()
1248
-    {
1249
-        return $this->getCanvas();
1250
-    }
1251
-
1252
-    /**
1253
-     * Return the underlying Canvas instance (e.g. Dompdf\Adapter\CPDF, Dompdf\Adapter\GD)
1254
-     *
1255
-     * @return Canvas
1256
-     */
1257
-    public function getCanvas()
1258
-    {
1259
-        return $this->canvas;
1260
-    }
1261
-
1262
-    /**
1263
-     * @param Stylesheet $css
1264
-     * @return $this
1265
-     */
1266
-    public function setCss(Stylesheet $css)
1267
-    {
1268
-        $this->css = $css;
1269
-        return $this;
1270
-    }
1271
-
1272
-    /**
1273
-     * @return Stylesheet
1274
-     * @deprecated
1275
-     */
1276
-    public function get_css()
1277
-    {
1278
-        return $this->getCss();
1279
-    }
1280
-
1281
-    /**
1282
-     * Returns the stylesheet
1283
-     *
1284
-     * @return Stylesheet
1285
-     */
1286
-    public function getCss()
1287
-    {
1288
-        return $this->css;
1289
-    }
1290
-
1291
-    /**
1292
-     * @param DOMDocument $dom
1293
-     * @return $this
1294
-     */
1295
-    public function setDom(DOMDocument $dom)
1296
-    {
1297
-        $this->dom = $dom;
1298
-        return $this;
1299
-    }
1300
-
1301
-    /**
1302
-     * @return DOMDocument
1303
-     * @deprecated
1304
-     */
1305
-    public function get_dom()
1306
-    {
1307
-        return $this->getDom();
1308
-    }
1309
-
1310
-    /**
1311
-     * @return DOMDocument
1312
-     */
1313
-    public function getDom()
1314
-    {
1315
-        return $this->dom;
1316
-    }
1317
-
1318
-    /**
1319
-     * @param Options $options
1320
-     * @return $this
1321
-     */
1322
-    public function setOptions(Options $options)
1323
-    {
1324
-        // For backwards compatibility
1325
-        if ($this->options && $this->options->getHttpContext() && !$options->getHttpContext()) {
1326
-            $options->setHttpContext($this->options->getHttpContext());
1327
-        }
1328
-
1329
-        $this->options = $options;
1330
-        $fontMetrics = $this->fontMetrics;
1331
-        if (isset($fontMetrics)) {
1332
-            $fontMetrics->setOptions($options);
1333
-        }
1334
-        return $this;
1335
-    }
1336
-
1337
-    /**
1338
-     * @return Options
1339
-     */
1340
-    public function getOptions()
1341
-    {
1342
-        return $this->options;
1343
-    }
1344
-
1345
-    /**
1346
-     * @return array
1347
-     * @deprecated
1348
-     */
1349
-    public function get_callbacks()
1350
-    {
1351
-        return $this->getCallbacks();
1352
-    }
1353
-
1354
-    /**
1355
-     * Returns the callbacks array
1356
-     *
1357
-     * @return array
1358
-     */
1359
-    public function getCallbacks()
1360
-    {
1361
-        return $this->callbacks;
1362
-    }
1363
-
1364
-    /**
1365
-     * @param array $callbacks the set of callbacks to set
1366
-     * @return $this
1367
-     * @deprecated
1368
-     */
1369
-    public function set_callbacks($callbacks)
1370
-    {
1371
-        return $this->setCallbacks($callbacks);
1372
-    }
1373
-
1374
-    /**
1375
-     * Define callbacks that allow modifying the document during render.
1376
-     *
1377
-     * The callbacks array should contain arrays with `event` set to a callback
1378
-     * event name and `f` set to a function or any other callable.
1379
-     *
1380
-     * The available callback events are:
1381
-     * * `begin_page_reflow`: called before page reflow
1382
-     * * `begin_frame`: called before a frame is rendered
1383
-     * * `end_frame`: called after frame rendering is complete
1384
-     * * `begin_page_render`: called before a page is rendered
1385
-     * * `end_page_render`: called after page rendering is complete
1386
-     * * `end_document`: called for every page after rendering is complete
1387
-     *
1388
-     * The function `f` receives three arguments `Frame $frame`, `Canvas $canvas`,
1389
-     * and `FontMetrics $fontMetrics` for all events but `end_document`. For
1390
-     * `end_document`, the function receives four arguments `int $pageNumber`,
1391
-     * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics` instead.
1392
-     *
1393
-     * @param array $callbacks The set of callbacks to set.
1394
-     * @return $this
1395
-     */
1396
-    public function setCallbacks(array $callbacks): self
1397
-    {
1398
-        $this->callbacks = [];
1399
-
1400
-        foreach ($callbacks as $c) {
1401
-            if (is_array($c) && isset($c["event"]) && isset($c["f"])) {
1402
-                $event = $c["event"];
1403
-                $f = $c["f"];
1404
-                if (is_string($event) && is_callable($f)) {
1405
-                    $this->callbacks[$event][] = $f;
1406
-                }
1407
-            }
1408
-        }
1409
-
1410
-        return $this;
1411
-    }
1412
-
1413
-    /**
1414
-     * @return boolean
1415
-     * @deprecated
1416
-     */
1417
-    public function get_quirksmode()
1418
-    {
1419
-        return $this->getQuirksmode();
1420
-    }
1421
-
1422
-    /**
1423
-     * Get the quirks mode
1424
-     *
1425
-     * @return boolean true if quirks mode is active
1426
-     */
1427
-    public function getQuirksmode()
1428
-    {
1429
-        return $this->quirksmode;
1430
-    }
1431
-
1432
-    /**
1433
-     * @param FontMetrics $fontMetrics
1434
-     * @return $this
1435
-     */
1436
-    public function setFontMetrics(FontMetrics $fontMetrics)
1437
-    {
1438
-        $this->fontMetrics = $fontMetrics;
1439
-        return $this;
1440
-    }
1441
-
1442
-    /**
1443
-     * @return FontMetrics
1444
-     */
1445
-    public function getFontMetrics()
1446
-    {
1447
-        return $this->fontMetrics;
1448
-    }
1449
-
1450
-    /**
1451
-     * PHP5 overloaded getter
1452
-     * Along with {@link Dompdf::__set()} __get() provides access to all
1453
-     * properties directly.  Typically __get() is not called directly outside
1454
-     * of this class.
1455
-     *
1456
-     * @param string $prop
1457
-     *
1458
-     * @throws Exception
1459
-     * @return mixed
1460
-     */
1461
-    function __get($prop)
1462
-    {
1463
-        switch ($prop) {
1464
-            case 'version':
1465
-                return $this->version;
1466
-            default:
1467
-                throw new Exception('Invalid property: ' . $prop);
1468
-        }
1469
-    }
69
+	/**
70
+	 * Version string for dompdf
71
+	 *
72
+	 * @var string
73
+	 */
74
+	private $version = 'dompdf';
75
+
76
+	/**
77
+	 * DomDocument representing the HTML document
78
+	 *
79
+	 * @var DOMDocument
80
+	 */
81
+	private $dom;
82
+
83
+	/**
84
+	 * FrameTree derived from the DOM tree
85
+	 *
86
+	 * @var FrameTree
87
+	 */
88
+	private $tree;
89
+
90
+	/**
91
+	 * Stylesheet for the document
92
+	 *
93
+	 * @var Stylesheet
94
+	 */
95
+	private $css;
96
+
97
+	/**
98
+	 * Actual PDF renderer
99
+	 *
100
+	 * @var Canvas
101
+	 */
102
+	private $canvas;
103
+
104
+	/**
105
+	 * Desired paper size ('letter', 'legal', 'A4', etc.)
106
+	 *
107
+	 * @var string|array
108
+	 */
109
+	private $paperSize;
110
+
111
+	/**
112
+	 * Paper orientation ('portrait' or 'landscape')
113
+	 *
114
+	 * @var string
115
+	 */
116
+	private $paperOrientation = "portrait";
117
+
118
+	/**
119
+	 * Callbacks on new page and new element
120
+	 *
121
+	 * @var array
122
+	 */
123
+	private $callbacks = [];
124
+
125
+	/**
126
+	 * Experimental caching capability
127
+	 *
128
+	 * @var string
129
+	 */
130
+	private $cacheId;
131
+
132
+	/**
133
+	 * Base hostname
134
+	 *
135
+	 * Used for relative paths/urls
136
+	 * @var string
137
+	 */
138
+	private $baseHost = "";
139
+
140
+	/**
141
+	 * Absolute base path
142
+	 *
143
+	 * Used for relative paths/urls
144
+	 * @var string
145
+	 */
146
+	private $basePath = "";
147
+
148
+	/**
149
+	 * Protocol used to request file (file://, http://, etc)
150
+	 *
151
+	 * @var string
152
+	 */
153
+	private $protocol = "";
154
+
155
+	/**
156
+	 * The system's locale
157
+	 *
158
+	 * @var string
159
+	 */
160
+	private $systemLocale = null;
161
+
162
+	/**
163
+	 * The system's mbstring internal encoding
164
+	 *
165
+	 * @var string
166
+	 */
167
+	private $mbstringEncoding = null;
168
+
169
+	/**
170
+	 * The system's PCRE JIT configuration
171
+	 *
172
+	 * @var string
173
+	 */
174
+	private $pcreJit = null;
175
+
176
+	/**
177
+	 * The default view of the PDF in the viewer
178
+	 *
179
+	 * @var string
180
+	 */
181
+	private $defaultView = "Fit";
182
+
183
+	/**
184
+	 * The default view options of the PDF in the viewer
185
+	 *
186
+	 * @var array
187
+	 */
188
+	private $defaultViewOptions = [];
189
+
190
+	/**
191
+	 * Tells whether the DOM document is in quirksmode (experimental)
192
+	 *
193
+	 * @var bool
194
+	 */
195
+	private $quirksmode = false;
196
+
197
+	/**
198
+	 * Local file extension whitelist
199
+	 *
200
+	 * File extensions supported by dompdf for local files.
201
+	 *
202
+	 * @var array
203
+	 */
204
+	private $allowedLocalFileExtensions = ["htm", "html"];
205
+
206
+	/**
207
+	 * @var array
208
+	 */
209
+	private $messages = [];
210
+
211
+	/**
212
+	 * @var Options
213
+	 */
214
+	private $options;
215
+
216
+	/**
217
+	 * @var FontMetrics
218
+	 */
219
+	private $fontMetrics;
220
+
221
+	/**
222
+	 * The list of built-in fonts
223
+	 *
224
+	 * @var array
225
+	 * @deprecated
226
+	 */
227
+	public static $native_fonts = [
228
+		"courier", "courier-bold", "courier-oblique", "courier-boldoblique",
229
+		"helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique",
230
+		"times-roman", "times-bold", "times-italic", "times-bolditalic",
231
+		"symbol", "zapfdinbats"
232
+	];
233
+
234
+	/**
235
+	 * The list of built-in fonts
236
+	 *
237
+	 * @var array
238
+	 */
239
+	public static $nativeFonts = [
240
+		"courier", "courier-bold", "courier-oblique", "courier-boldoblique",
241
+		"helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique",
242
+		"times-roman", "times-bold", "times-italic", "times-bolditalic",
243
+		"symbol", "zapfdinbats"
244
+	];
245
+
246
+	/**
247
+	 * Class constructor
248
+	 *
249
+	 * @param Options|array|null $options
250
+	 */
251
+	public function __construct($options = null)
252
+	{
253
+		if (isset($options) && $options instanceof Options) {
254
+			$this->setOptions($options);
255
+		} elseif (is_array($options)) {
256
+			$this->setOptions(new Options($options));
257
+		} else {
258
+			$this->setOptions(new Options());
259
+		}
260
+
261
+		$versionFile = realpath(__DIR__ . '/../VERSION');
262
+		if (($version = file_get_contents($versionFile)) !== false) {
263
+			$version = trim($version);
264
+			if ($version !== '$Format:<%h>$') {
265
+				$this->version = sprintf('dompdf %s', $version);
266
+			}
267
+		}
268
+
269
+		$this->setPhpConfig();
270
+
271
+		$this->paperSize = $this->options->getDefaultPaperSize();
272
+		$this->paperOrientation = $this->options->getDefaultPaperOrientation();
273
+
274
+		$this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
275
+		$this->fontMetrics = new FontMetrics($this->canvas, $this->options);
276
+		$this->css = new Stylesheet($this);
277
+
278
+		$this->restorePhpConfig();
279
+	}
280
+
281
+	/**
282
+	 * Save the system's existing locale, PCRE JIT, and MBString encoding
283
+	 * configuration and configure the system for Dompdf processing
284
+	 */
285
+	private function setPhpConfig()
286
+	{
287
+		if (sprintf('%.1f', 1.0) !== '1.0') {
288
+			$this->systemLocale = setlocale(LC_NUMERIC, "0");
289
+			setlocale(LC_NUMERIC, "C");
290
+		}
291
+
292
+		$this->pcreJit = @ini_get('pcre.jit');
293
+		@ini_set('pcre.jit', '0');
294
+
295
+		$this->mbstringEncoding = mb_internal_encoding();
296
+		mb_internal_encoding('UTF-8');
297
+	}
298
+
299
+	/**
300
+	 * Restore the system's locale configuration
301
+	 */
302
+	private function restorePhpConfig()
303
+	{
304
+		if ($this->systemLocale !== null) {
305
+			setlocale(LC_NUMERIC, $this->systemLocale);
306
+			$this->systemLocale = null;
307
+		}
308
+
309
+		if ($this->pcreJit !== null) {
310
+			@ini_set('pcre.jit', $this->pcreJit);
311
+			$this->pcreJit = null;
312
+		}
313
+
314
+		if ($this->mbstringEncoding !== null) {
315
+			mb_internal_encoding($this->mbstringEncoding);
316
+			$this->mbstringEncoding = null;
317
+		}
318
+	}
319
+
320
+	/**
321
+	 * @param $file
322
+	 * @deprecated
323
+	 */
324
+	public function load_html_file($file)
325
+	{
326
+		$this->loadHtmlFile($file);
327
+	}
328
+
329
+	/**
330
+	 * Loads an HTML file
331
+	 * Parse errors are stored in the global array _dompdf_warnings.
332
+	 *
333
+	 * @param string $file a filename or url to load
334
+	 * @param string $encoding Encoding of $file
335
+	 *
336
+	 * @throws Exception
337
+	 */
338
+	public function loadHtmlFile($file, $encoding = null)
339
+	{
340
+		$this->setPhpConfig();
341
+
342
+		if (!$this->protocol && !$this->baseHost && !$this->basePath) {
343
+			[$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($file);
344
+		}
345
+		$protocol = strtolower($this->protocol);
346
+		$uri = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $file);
347
+
348
+		$allowed_protocols = $this->options->getAllowedProtocols();
349
+		if (!array_key_exists($protocol, $allowed_protocols)) {
350
+			throw new Exception("Permission denied on $file. The communication protocol is not supported.");
351
+		}
352
+
353
+		if ($protocol === "file://") {
354
+			$ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION));
355
+			if (!in_array($ext, $this->allowedLocalFileExtensions)) {
356
+				throw new Exception("Permission denied on $file: The file extension is forbidden.");
357
+			}
358
+		}
359
+
360
+		foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
361
+			[$result, $message] = $rule($uri);
362
+			if (!$result) {
363
+				throw new Exception("Error loading $file: $message");
364
+			}
365
+		}
366
+
367
+		[$contents, $http_response_header] = Helpers::getFileContent($uri, $this->options->getHttpContext());
368
+		if ($contents === null) {
369
+			throw new Exception("File '$file' not found.");
370
+		}
371
+
372
+		// See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
373
+		if (isset($http_response_header)) {
374
+			foreach ($http_response_header as $_header) {
375
+				if (preg_match("@Content-Type:\s*[\w/]+;\s*?charset=([^\s]+)@i", $_header, $matches)) {
376
+					$encoding = strtoupper($matches[1]);
377
+					break;
378
+				}
379
+			}
380
+		}
381
+
382
+		$this->restorePhpConfig();
383
+
384
+		$this->loadHtml($contents, $encoding);
385
+	}
386
+
387
+	/**
388
+	 * @param string $str
389
+	 * @param string $encoding
390
+	 * @deprecated
391
+	 */
392
+	public function load_html($str, $encoding = null)
393
+	{
394
+		$this->loadHtml($str, $encoding);
395
+	}
396
+
397
+	public function loadDOM($doc, $quirksmode = false) {
398
+		// Remove #text children nodes in nodes that shouldn't have
399
+		$tag_names = ["html", "head", "table", "tbody", "thead", "tfoot", "tr"];
400
+		foreach ($tag_names as $tag_name) {
401
+			$nodes = $doc->getElementsByTagName($tag_name);
402
+
403
+			foreach ($nodes as $node) {
404
+				self::removeTextNodes($node);
405
+			}
406
+		}
407
+
408
+		$this->dom = $doc;
409
+		$this->quirksmode = $quirksmode;
410
+		$this->tree = new FrameTree($this->dom);
411
+	}
412
+
413
+	/**
414
+	 * Loads an HTML string
415
+	 * Parse errors are stored in the global array _dompdf_warnings.
416
+	 *
417
+	 * @param string $str HTML text to load
418
+	 * @param string $encoding Encoding of $str
419
+	 */
420
+	public function loadHtml($str, $encoding = null)
421
+	{
422
+		$this->setPhpConfig();
423
+
424
+		// Determine character encoding when $encoding parameter not used
425
+		if ($encoding === null) {
426
+			mb_detect_order('auto');
427
+			if (($encoding = mb_detect_encoding($str, null, true)) === false) {
428
+
429
+				//"auto" is expanded to "ASCII,JIS,UTF-8,EUC-JP,SJIS"
430
+				$encoding = "auto";
431
+			}
432
+		}
433
+
434
+		if (in_array(strtoupper($encoding), array('UTF-8','UTF8')) === false) {
435
+			$str = mb_convert_encoding($str, 'UTF-8', $encoding);
436
+
437
+			//Update encoding after converting
438
+			$encoding = 'UTF-8';
439
+		}
440
+
441
+		$metatags = [
442
+			'@<meta\s+http-equiv="Content-Type"\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))?@i',
443
+			'@<meta\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))"?\s+http-equiv="Content-Type"@i',
444
+			'@<meta [^>]*charset\s*=\s*["\']?\s*([^"\' ]+)@i',
445
+		];
446
+		foreach ($metatags as $metatag) {
447
+			if (preg_match($metatag, $str, $matches)) {
448
+				if (isset($matches[1]) && in_array($matches[1], mb_list_encodings())) {
449
+					$document_encoding = $matches[1];
450
+					break;
451
+				}
452
+			}
453
+		}
454
+		if (isset($document_encoding) && in_array(strtoupper($document_encoding), ['UTF-8','UTF8']) === false) {
455
+			$str = preg_replace('/charset=([^\s"]+)/i', 'charset=UTF-8', $str);
456
+		} elseif (isset($document_encoding) === false && strpos($str, '<head>') !== false) {
457
+			$str = str_replace('<head>', '<head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">', $str);
458
+		} elseif (isset($document_encoding) === false) {
459
+			$str = '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">' . $str;
460
+		}
461
+
462
+		// remove BOM mark from UTF-8, it's treated as document text by DOMDocument
463
+		// FIXME: roll this into the encoding detection using UTF-8/16/32 BOM (http://us2.php.net/manual/en/function.mb-detect-encoding.php#91051)?
464
+		if (substr($str, 0, 3) == chr(0xEF) . chr(0xBB) . chr(0xBF)) {
465
+			$str = substr($str, 3);
466
+		}
467
+
468
+		// Store parsing warnings as messages
469
+		set_error_handler([Helpers::class, 'record_warnings']);
470
+
471
+		try {
472
+			// @todo Take the quirksmode into account
473
+			// https://quirks.spec.whatwg.org/
474
+			// http://hsivonen.iki.fi/doctype/
475
+			$quirksmode = false;
476
+
477
+			$html5 = new HTML5(['encoding' => $encoding, 'disable_html_ns' => true]);
478
+			$dom = $html5->loadHTML($str);
479
+
480
+			// extra step to normalize the HTML document structure
481
+			// see Masterminds/html5-php#166
482
+			$doc = new DOMDocument("1.0", $encoding);
483
+			$doc->preserveWhiteSpace = true;
484
+			$doc->loadHTML($html5->saveHTML($dom), LIBXML_NOWARNING | LIBXML_NOERROR);
485
+
486
+			$this->loadDOM($doc, $quirksmode);
487
+		} finally {
488
+			restore_error_handler();
489
+			$this->restorePhpConfig();
490
+		}
491
+	}
492
+
493
+	/**
494
+	 * @param DOMNode $node
495
+	 * @deprecated
496
+	 */
497
+	public static function remove_text_nodes(DOMNode $node)
498
+	{
499
+		self::removeTextNodes($node);
500
+	}
501
+
502
+	/**
503
+	 * @param DOMNode $node
504
+	 */
505
+	public static function removeTextNodes(DOMNode $node)
506
+	{
507
+		$children = [];
508
+		for ($i = 0; $i < $node->childNodes->length; $i++) {
509
+			$child = $node->childNodes->item($i);
510
+			if ($child->nodeName === "#text") {
511
+				$children[] = $child;
512
+			}
513
+		}
514
+
515
+		foreach ($children as $child) {
516
+			$node->removeChild($child);
517
+		}
518
+	}
519
+
520
+	/**
521
+	 * Builds the {@link FrameTree}, loads any CSS and applies the styles to
522
+	 * the {@link FrameTree}
523
+	 */
524
+	private function processHtml()
525
+	{
526
+		$this->tree->build_tree();
527
+
528
+		$this->css->load_css_file($this->css->getDefaultStylesheet(), Stylesheet::ORIG_UA);
529
+
530
+		$acceptedmedia = Stylesheet::$ACCEPTED_GENERIC_MEDIA_TYPES;
531
+		$acceptedmedia[] = $this->options->getDefaultMediaType();
532
+
533
+		// <base href="" />
534
+		/** @var \DOMElement|null */
535
+		$baseNode = $this->dom->getElementsByTagName("base")->item(0);
536
+		$baseHref = $baseNode ? $baseNode->getAttribute("href") : "";
537
+		if ($baseHref !== "") {
538
+			[$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($baseHref);
539
+		}
540
+
541
+		// Set the base path of the Stylesheet to that of the file being processed
542
+		$this->css->set_protocol($this->protocol);
543
+		$this->css->set_host($this->baseHost);
544
+		$this->css->set_base_path($this->basePath);
545
+
546
+		// Get all the stylesheets so that they are processed in document order
547
+		$xpath = new DOMXPath($this->dom);
548
+		$stylesheets = $xpath->query("//*[name() = 'link' or name() = 'style']");
549
+
550
+		/** @var \DOMElement $tag */
551
+		foreach ($stylesheets as $tag) {
552
+			switch (strtolower($tag->nodeName)) {
553
+				// load <link rel="STYLESHEET" ... /> tags
554
+				case "link":
555
+					if (mb_strtolower(stripos($tag->getAttribute("rel"), "stylesheet") !== false) || // may be "appendix stylesheet"
556
+						mb_strtolower($tag->getAttribute("type")) === "text/css"
557
+					) {
558
+						//Check if the css file is for an accepted media type
559
+						//media not given then always valid
560
+						$formedialist = preg_split("/[\s\n,]/", $tag->getAttribute("media"), -1, PREG_SPLIT_NO_EMPTY);
561
+						if (count($formedialist) > 0) {
562
+							$accept = false;
563
+							foreach ($formedialist as $type) {
564
+								if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) {
565
+									$accept = true;
566
+									break;
567
+								}
568
+							}
569
+
570
+							if (!$accept) {
571
+								//found at least one mediatype, but none of the accepted ones
572
+								//Skip this css file.
573
+								break;
574
+							}
575
+						}
576
+
577
+						$url = $tag->getAttribute("href");
578
+						$url = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $url);
579
+
580
+						if ($url !== null) {
581
+							$this->css->load_css_file($url, Stylesheet::ORIG_AUTHOR);
582
+						}
583
+					}
584
+					break;
585
+
586
+				// load <style> tags
587
+				case "style":
588
+					// Accept all <style> tags by default (note this is contrary to W3C
589
+					// HTML 4.0 spec:
590
+					// http://www.w3.org/TR/REC-html40/present/styles.html#adef-media
591
+					// which states that the default media type is 'screen'
592
+					if ($tag->hasAttributes() &&
593
+						($media = $tag->getAttribute("media")) &&
594
+						!in_array($media, $acceptedmedia)
595
+					) {
596
+						break;
597
+					}
598
+
599
+					$css = "";
600
+					if ($tag->hasChildNodes()) {
601
+						$child = $tag->firstChild;
602
+						while ($child) {
603
+							$css .= $child->nodeValue; // Handle <style><!-- blah --></style>
604
+							$child = $child->nextSibling;
605
+						}
606
+					} else {
607
+						$css = $tag->nodeValue;
608
+					}
609
+
610
+					// Set the base path of the Stylesheet to that of the file being processed
611
+					$this->css->set_protocol($this->protocol);
612
+					$this->css->set_host($this->baseHost);
613
+					$this->css->set_base_path($this->basePath);
614
+
615
+					$this->css->load_css($css, Stylesheet::ORIG_AUTHOR);
616
+					break;
617
+			}
618
+
619
+			// Set the base path of the Stylesheet to that of the file being processed
620
+			$this->css->set_protocol($this->protocol);
621
+			$this->css->set_host($this->baseHost);
622
+			$this->css->set_base_path($this->basePath);
623
+		}
624
+	}
625
+
626
+	/**
627
+	 * @param string $cacheId
628
+	 * @deprecated
629
+	 */
630
+	public function enable_caching($cacheId)
631
+	{
632
+		$this->enableCaching($cacheId);
633
+	}
634
+
635
+	/**
636
+	 * Enable experimental caching capability
637
+	 *
638
+	 * @param string $cacheId
639
+	 */
640
+	public function enableCaching($cacheId)
641
+	{
642
+		$this->cacheId = $cacheId;
643
+	}
644
+
645
+	/**
646
+	 * @param string $value
647
+	 * @return bool
648
+	 * @deprecated
649
+	 */
650
+	public function parse_default_view($value)
651
+	{
652
+		return $this->parseDefaultView($value);
653
+	}
654
+
655
+	/**
656
+	 * @param string $value
657
+	 * @return bool
658
+	 */
659
+	public function parseDefaultView($value)
660
+	{
661
+		$valid = ["XYZ", "Fit", "FitH", "FitV", "FitR", "FitB", "FitBH", "FitBV"];
662
+
663
+		$options = preg_split("/\s*,\s*/", trim($value));
664
+		$defaultView = array_shift($options);
665
+
666
+		if (!in_array($defaultView, $valid)) {
667
+			return false;
668
+		}
669
+
670
+		$this->setDefaultView($defaultView, $options);
671
+		return true;
672
+	}
673
+
674
+	/**
675
+	 * Renders the HTML to PDF
676
+	 */
677
+	public function render()
678
+	{
679
+		$this->setPhpConfig();
680
+
681
+		$logOutputFile = $this->options->getLogOutputFile();
682
+		if ($logOutputFile) {
683
+			if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
684
+				touch($logOutputFile);
685
+			}
686
+
687
+			$startTime = microtime(true);
688
+			if (is_writable($logOutputFile)) {
689
+				ob_start();
690
+			}
691
+		}
692
+
693
+		$this->processHtml();
694
+
695
+		$this->css->apply_styles($this->tree);
696
+
697
+		// @page style rules : size, margins
698
+		$pageStyles = $this->css->get_page_styles();
699
+		$basePageStyle = $pageStyles["base"];
700
+		unset($pageStyles["base"]);
701
+
702
+		foreach ($pageStyles as $pageStyle) {
703
+			$pageStyle->inherit($basePageStyle);
704
+		}
705
+
706
+		// Set paper size if defined via CSS
707
+		if (is_array($basePageStyle->size)) {
708
+			[$width, $height] = $basePageStyle->size;
709
+			$this->setPaper([0, 0, $width, $height]);
710
+		}
711
+
712
+		// Create a new canvas instance if the current one does not match the
713
+		// desired paper size
714
+		$canvasWidth = $this->canvas->get_width();
715
+		$canvasHeight = $this->canvas->get_height();
716
+		$size = $this->getPaperSize();
717
+
718
+		if ($canvasWidth !== $size[2] || $canvasHeight !== $size[3]) {
719
+			$this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
720
+			$this->fontMetrics->setCanvas($this->canvas);
721
+		}
722
+
723
+		$canvas = $this->canvas;
724
+
725
+		$root_frame = $this->tree->get_root();
726
+		$root = Factory::decorate_root($root_frame, $this);
727
+		foreach ($this->tree as $frame) {
728
+			if ($frame === $root_frame) {
729
+				continue;
730
+			}
731
+			Factory::decorate_frame($frame, $this, $root);
732
+		}
733
+
734
+		// Add meta information
735
+		$title = $this->dom->getElementsByTagName("title");
736
+		if ($title->length) {
737
+			$canvas->add_info("Title", trim($title->item(0)->nodeValue));
738
+		}
739
+
740
+		$metas = $this->dom->getElementsByTagName("meta");
741
+		$labels = [
742
+			"author" => "Author",
743
+			"keywords" => "Keywords",
744
+			"description" => "Subject",
745
+		];
746
+		/** @var \DOMElement $meta */
747
+		foreach ($metas as $meta) {
748
+			$name = mb_strtolower($meta->getAttribute("name"));
749
+			$value = trim($meta->getAttribute("content"));
750
+
751
+			if (isset($labels[$name])) {
752
+				$canvas->add_info($labels[$name], $value);
753
+				continue;
754
+			}
755
+
756
+			if ($name === "dompdf.view" && $this->parseDefaultView($value)) {
757
+				$canvas->set_default_view($this->defaultView, $this->defaultViewOptions);
758
+			}
759
+		}
760
+
761
+		$root->set_containing_block(0, 0, $canvas->get_width(), $canvas->get_height());
762
+		$root->set_renderer(new Renderer($this));
763
+
764
+		// This is where the magic happens:
765
+		$root->reflow();
766
+
767
+		if (isset($this->callbacks["end_document"])) {
768
+			$fs = $this->callbacks["end_document"];
769
+
770
+			foreach ($fs as $f) {
771
+				$canvas->page_script($f);
772
+			}
773
+		}
774
+
775
+		// Clean up cached images
776
+		if (!$this->options->getDebugKeepTemp()) {
777
+			Cache::clear($this->options->getDebugPng());
778
+		}
779
+
780
+		global $_dompdf_warnings, $_dompdf_show_warnings;
781
+		if ($_dompdf_show_warnings && isset($_dompdf_warnings)) {
782
+			echo '<b>Dompdf Warnings</b><br><pre>';
783
+			foreach ($_dompdf_warnings as $msg) {
784
+				echo $msg . "\n";
785
+			}
786
+
787
+			if ($canvas instanceof CPDF) {
788
+				echo $canvas->get_cpdf()->messages;
789
+			}
790
+			echo '</pre>';
791
+			flush();
792
+		}
793
+
794
+		if ($logOutputFile && is_writable($logOutputFile)) {
795
+			$this->writeLog($logOutputFile, $startTime);
796
+			ob_end_clean();
797
+		}
798
+
799
+		$this->restorePhpConfig();
800
+	}
801
+
802
+	/**
803
+	 * Writes the output buffer in the log file
804
+	 *
805
+	 * @param string $logOutputFile
806
+	 * @param float $startTime
807
+	 */
808
+	private function writeLog(string $logOutputFile, float $startTime): void
809
+	{
810
+		$frames = Frame::$ID_COUNTER;
811
+		$memory = memory_get_peak_usage(true) / 1024;
812
+		$time = (microtime(true) - $startTime) * 1000;
813
+
814
+		$out = sprintf(
815
+			"<span style='color: #000' title='Frames'>%6d</span>" .
816
+			"<span style='color: #009' title='Memory'>%10.2f KB</span>" .
817
+			"<span style='color: #900' title='Time'>%10.2f ms</span>" .
818
+			"<span  title='Quirksmode'>  " .
819
+			($this->quirksmode ? "<span style='color: #d00'> ON</span>" : "<span style='color: #0d0'>OFF</span>") .
820
+			"</span><br />", $frames, $memory, $time);
821
+
822
+		$out .= ob_get_contents();
823
+		ob_clean();
824
+
825
+		file_put_contents($logOutputFile, $out);
826
+	}
827
+
828
+	/**
829
+	 * Add meta information to the PDF after rendering.
830
+	 *
831
+	 * @deprecated
832
+	 */
833
+	public function add_info($label, $value)
834
+	{
835
+		$this->addInfo($label, $value);
836
+	}
837
+
838
+	/**
839
+	 * Add meta information to the PDF after rendering.
840
+	 *
841
+	 * @param string $label Label of the value (Creator, Producer, etc.)
842
+	 * @param string $value The text to set
843
+	 */
844
+	public function addInfo(string $label, string $value): void
845
+	{
846
+		$this->canvas->add_info($label, $value);
847
+	}
848
+
849
+	/**
850
+	 * Streams the PDF to the client.
851
+	 *
852
+	 * The file will open a download dialog by default. The options
853
+	 * parameter controls the output. Accepted options (array keys) are:
854
+	 *
855
+	 * 'compress' = > 1 (=default) or 0:
856
+	 *   Apply content stream compression
857
+	 *
858
+	 * 'Attachment' => 1 (=default) or 0:
859
+	 *   Set the 'Content-Disposition:' HTTP header to 'attachment'
860
+	 *   (thereby causing the browser to open a download dialog)
861
+	 *
862
+	 * @param string $filename the name of the streamed file
863
+	 * @param array $options header options (see above)
864
+	 */
865
+	public function stream($filename = "document.pdf", $options = [])
866
+	{
867
+		$this->setPhpConfig();
868
+
869
+		$this->canvas->stream($filename, $options);
870
+
871
+		$this->restorePhpConfig();
872
+	}
873
+
874
+	/**
875
+	 * Returns the PDF as a string.
876
+	 *
877
+	 * The options parameter controls the output. Accepted options are:
878
+	 *
879
+	 * 'compress' = > 1 or 0 - apply content stream compression, this is
880
+	 *    on (1) by default
881
+	 *
882
+	 * @param array $options options (see above)
883
+	 *
884
+	 * @return string|null
885
+	 */
886
+	public function output($options = [])
887
+	{
888
+		$this->setPhpConfig();
889
+
890
+		$output = $this->canvas->output($options);
891
+
892
+		$this->restorePhpConfig();
893
+
894
+		return $output;
895
+	}
896
+
897
+	/**
898
+	 * @return string
899
+	 * @deprecated
900
+	 */
901
+	public function output_html()
902
+	{
903
+		return $this->outputHtml();
904
+	}
905
+
906
+	/**
907
+	 * Returns the underlying HTML document as a string
908
+	 *
909
+	 * @return string
910
+	 */
911
+	public function outputHtml()
912
+	{
913
+		return $this->dom->saveHTML();
914
+	}
915
+
916
+	/**
917
+	 * Get the dompdf option value
918
+	 *
919
+	 * @param string $key
920
+	 * @return mixed
921
+	 * @deprecated
922
+	 */
923
+	public function get_option($key)
924
+	{
925
+		return $this->options->get($key);
926
+	}
927
+
928
+	/**
929
+	 * @param string $key
930
+	 * @param mixed $value
931
+	 * @return $this
932
+	 * @deprecated
933
+	 */
934
+	public function set_option($key, $value)
935
+	{
936
+		$this->options->set($key, $value);
937
+		return $this;
938
+	}
939
+
940
+	/**
941
+	 * @param array $options
942
+	 * @return $this
943
+	 * @deprecated
944
+	 */
945
+	public function set_options(array $options)
946
+	{
947
+		$this->options->set($options);
948
+		return $this;
949
+	}
950
+
951
+	/**
952
+	 * @param string $size
953
+	 * @param string $orientation
954
+	 * @deprecated
955
+	 */
956
+	public function set_paper($size, $orientation = "portrait")
957
+	{
958
+		$this->setPaper($size, $orientation);
959
+	}
960
+
961
+	/**
962
+	 * Sets the paper size & orientation
963
+	 *
964
+	 * @param string|float[] $size 'letter', 'legal', 'A4', etc. {@link Dompdf\Adapter\CPDF::$PAPER_SIZES}
965
+	 * @param string $orientation 'portrait' or 'landscape'
966
+	 * @return $this
967
+	 */
968
+	public function setPaper($size, $orientation = "portrait")
969
+	{
970
+		$this->paperSize = $size;
971
+		$this->paperOrientation = $orientation;
972
+		return $this;
973
+	}
974
+
975
+	/**
976
+	 * Gets the paper size
977
+	 *
978
+	 * @return float[] A four-element float array
979
+	 */
980
+	public function getPaperSize()
981
+	{
982
+		$paper = $this->paperSize;
983
+		$orientation = $this->paperOrientation;
984
+
985
+		if (is_array($paper)) {
986
+			$size = array_map("floatval", $paper);
987
+		} else {
988
+			$paper = strtolower($paper);
989
+			$size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"];
990
+		}
991
+
992
+		if (strtolower($orientation) === "landscape") {
993
+			[$size[2], $size[3]] = [$size[3], $size[2]];
994
+		}
995
+
996
+		return $size;
997
+	}
998
+
999
+	/**
1000
+	 * Gets the paper orientation
1001
+	 *
1002
+	 * @return string Either "portrait" or "landscape"
1003
+	 */
1004
+	public function getPaperOrientation()
1005
+	{
1006
+		return $this->paperOrientation;
1007
+	}
1008
+
1009
+	/**
1010
+	 * @param FrameTree $tree
1011
+	 * @return $this
1012
+	 */
1013
+	public function setTree(FrameTree $tree)
1014
+	{
1015
+		$this->tree = $tree;
1016
+		return $this;
1017
+	}
1018
+
1019
+	/**
1020
+	 * @return FrameTree
1021
+	 * @deprecated
1022
+	 */
1023
+	public function get_tree()
1024
+	{
1025
+		return $this->getTree();
1026
+	}
1027
+
1028
+	/**
1029
+	 * Returns the underlying {@link FrameTree} object
1030
+	 *
1031
+	 * @return FrameTree
1032
+	 */
1033
+	public function getTree()
1034
+	{
1035
+		return $this->tree;
1036
+	}
1037
+
1038
+	/**
1039
+	 * @param string $protocol
1040
+	 * @return $this
1041
+	 * @deprecated
1042
+	 */
1043
+	public function set_protocol($protocol)
1044
+	{
1045
+		return $this->setProtocol($protocol);
1046
+	}
1047
+
1048
+	/**
1049
+	 * Sets the protocol to use
1050
+	 * FIXME validate these
1051
+	 *
1052
+	 * @param string $protocol
1053
+	 * @return $this
1054
+	 */
1055
+	public function setProtocol(string $protocol)
1056
+	{
1057
+		$this->protocol = $protocol;
1058
+		return $this;
1059
+	}
1060
+
1061
+	/**
1062
+	 * @return string
1063
+	 * @deprecated
1064
+	 */
1065
+	public function get_protocol()
1066
+	{
1067
+		return $this->getProtocol();
1068
+	}
1069
+
1070
+	/**
1071
+	 * Returns the protocol in use
1072
+	 *
1073
+	 * @return string
1074
+	 */
1075
+	public function getProtocol()
1076
+	{
1077
+		return $this->protocol;
1078
+	}
1079
+
1080
+	/**
1081
+	 * @param string $host
1082
+	 * @deprecated
1083
+	 */
1084
+	public function set_host($host)
1085
+	{
1086
+		$this->setBaseHost($host);
1087
+	}
1088
+
1089
+	/**
1090
+	 * Sets the base hostname
1091
+	 *
1092
+	 * @param string $baseHost
1093
+	 * @return $this
1094
+	 */
1095
+	public function setBaseHost(string $baseHost)
1096
+	{
1097
+		$this->baseHost = $baseHost;
1098
+		return $this;
1099
+	}
1100
+
1101
+	/**
1102
+	 * @return string
1103
+	 * @deprecated
1104
+	 */
1105
+	public function get_host()
1106
+	{
1107
+		return $this->getBaseHost();
1108
+	}
1109
+
1110
+	/**
1111
+	 * Returns the base hostname
1112
+	 *
1113
+	 * @return string
1114
+	 */
1115
+	public function getBaseHost()
1116
+	{
1117
+		return $this->baseHost;
1118
+	}
1119
+
1120
+	/**
1121
+	 * Sets the base path
1122
+	 *
1123
+	 * @param string $path
1124
+	 * @deprecated
1125
+	 */
1126
+	public function set_base_path($path)
1127
+	{
1128
+		$this->setBasePath($path);
1129
+	}
1130
+
1131
+	/**
1132
+	 * Sets the base path
1133
+	 *
1134
+	 * @param string $basePath
1135
+	 * @return $this
1136
+	 */
1137
+	public function setBasePath(string $basePath)
1138
+	{
1139
+		$this->basePath = $basePath;
1140
+		return $this;
1141
+	}
1142
+
1143
+	/**
1144
+	 * @return string
1145
+	 * @deprecated
1146
+	 */
1147
+	public function get_base_path()
1148
+	{
1149
+		return $this->getBasePath();
1150
+	}
1151
+
1152
+	/**
1153
+	 * Returns the base path
1154
+	 *
1155
+	 * @return string
1156
+	 */
1157
+	public function getBasePath()
1158
+	{
1159
+		return $this->basePath;
1160
+	}
1161
+
1162
+	/**
1163
+	 * @param string $default_view The default document view
1164
+	 * @param array $options The view's options
1165
+	 * @return $this
1166
+	 * @deprecated
1167
+	 */
1168
+	public function set_default_view($default_view, $options)
1169
+	{
1170
+		return $this->setDefaultView($default_view, $options);
1171
+	}
1172
+
1173
+	/**
1174
+	 * Sets the default view
1175
+	 *
1176
+	 * @param string $defaultView The default document view
1177
+	 * @param array $options The view's options
1178
+	 * @return $this
1179
+	 */
1180
+	public function setDefaultView($defaultView, $options)
1181
+	{
1182
+		$this->defaultView = $defaultView;
1183
+		$this->defaultViewOptions = $options;
1184
+		return $this;
1185
+	}
1186
+
1187
+	/**
1188
+	 * @param resource $http_context
1189
+	 * @return $this
1190
+	 * @deprecated
1191
+	 */
1192
+	public function set_http_context($http_context)
1193
+	{
1194
+		return $this->setHttpContext($http_context);
1195
+	}
1196
+
1197
+	/**
1198
+	 * Sets the HTTP context
1199
+	 *
1200
+	 * @param resource|array $httpContext
1201
+	 * @return $this
1202
+	 */
1203
+	public function setHttpContext($httpContext)
1204
+	{
1205
+		$this->options->setHttpContext($httpContext);
1206
+		return $this;
1207
+	}
1208
+
1209
+	/**
1210
+	 * @return resource
1211
+	 * @deprecated
1212
+	 */
1213
+	public function get_http_context()
1214
+	{
1215
+		return $this->getHttpContext();
1216
+	}
1217
+
1218
+	/**
1219
+	 * Returns the HTTP context
1220
+	 *
1221
+	 * @return resource
1222
+	 */
1223
+	public function getHttpContext()
1224
+	{
1225
+		return $this->options->getHttpContext();
1226
+	}
1227
+
1228
+	/**
1229
+	 * Set a custom `Canvas` instance to render the document to.
1230
+	 *
1231
+	 * Be aware that the instance will be replaced on render if the document
1232
+	 * defines a paper size different from the canvas.
1233
+	 *
1234
+	 * @param Canvas $canvas
1235
+	 * @return $this
1236
+	 */
1237
+	public function setCanvas(Canvas $canvas)
1238
+	{
1239
+		$this->canvas = $canvas;
1240
+		return $this;
1241
+	}
1242
+
1243
+	/**
1244
+	 * @return Canvas
1245
+	 * @deprecated
1246
+	 */
1247
+	public function get_canvas()
1248
+	{
1249
+		return $this->getCanvas();
1250
+	}
1251
+
1252
+	/**
1253
+	 * Return the underlying Canvas instance (e.g. Dompdf\Adapter\CPDF, Dompdf\Adapter\GD)
1254
+	 *
1255
+	 * @return Canvas
1256
+	 */
1257
+	public function getCanvas()
1258
+	{
1259
+		return $this->canvas;
1260
+	}
1261
+
1262
+	/**
1263
+	 * @param Stylesheet $css
1264
+	 * @return $this
1265
+	 */
1266
+	public function setCss(Stylesheet $css)
1267
+	{
1268
+		$this->css = $css;
1269
+		return $this;
1270
+	}
1271
+
1272
+	/**
1273
+	 * @return Stylesheet
1274
+	 * @deprecated
1275
+	 */
1276
+	public function get_css()
1277
+	{
1278
+		return $this->getCss();
1279
+	}
1280
+
1281
+	/**
1282
+	 * Returns the stylesheet
1283
+	 *
1284
+	 * @return Stylesheet
1285
+	 */
1286
+	public function getCss()
1287
+	{
1288
+		return $this->css;
1289
+	}
1290
+
1291
+	/**
1292
+	 * @param DOMDocument $dom
1293
+	 * @return $this
1294
+	 */
1295
+	public function setDom(DOMDocument $dom)
1296
+	{
1297
+		$this->dom = $dom;
1298
+		return $this;
1299
+	}
1300
+
1301
+	/**
1302
+	 * @return DOMDocument
1303
+	 * @deprecated
1304
+	 */
1305
+	public function get_dom()
1306
+	{
1307
+		return $this->getDom();
1308
+	}
1309
+
1310
+	/**
1311
+	 * @return DOMDocument
1312
+	 */
1313
+	public function getDom()
1314
+	{
1315
+		return $this->dom;
1316
+	}
1317
+
1318
+	/**
1319
+	 * @param Options $options
1320
+	 * @return $this
1321
+	 */
1322
+	public function setOptions(Options $options)
1323
+	{
1324
+		// For backwards compatibility
1325
+		if ($this->options && $this->options->getHttpContext() && !$options->getHttpContext()) {
1326
+			$options->setHttpContext($this->options->getHttpContext());
1327
+		}
1328
+
1329
+		$this->options = $options;
1330
+		$fontMetrics = $this->fontMetrics;
1331
+		if (isset($fontMetrics)) {
1332
+			$fontMetrics->setOptions($options);
1333
+		}
1334
+		return $this;
1335
+	}
1336
+
1337
+	/**
1338
+	 * @return Options
1339
+	 */
1340
+	public function getOptions()
1341
+	{
1342
+		return $this->options;
1343
+	}
1344
+
1345
+	/**
1346
+	 * @return array
1347
+	 * @deprecated
1348
+	 */
1349
+	public function get_callbacks()
1350
+	{
1351
+		return $this->getCallbacks();
1352
+	}
1353
+
1354
+	/**
1355
+	 * Returns the callbacks array
1356
+	 *
1357
+	 * @return array
1358
+	 */
1359
+	public function getCallbacks()
1360
+	{
1361
+		return $this->callbacks;
1362
+	}
1363
+
1364
+	/**
1365
+	 * @param array $callbacks the set of callbacks to set
1366
+	 * @return $this
1367
+	 * @deprecated
1368
+	 */
1369
+	public function set_callbacks($callbacks)
1370
+	{
1371
+		return $this->setCallbacks($callbacks);
1372
+	}
1373
+
1374
+	/**
1375
+	 * Define callbacks that allow modifying the document during render.
1376
+	 *
1377
+	 * The callbacks array should contain arrays with `event` set to a callback
1378
+	 * event name and `f` set to a function or any other callable.
1379
+	 *
1380
+	 * The available callback events are:
1381
+	 * * `begin_page_reflow`: called before page reflow
1382
+	 * * `begin_frame`: called before a frame is rendered
1383
+	 * * `end_frame`: called after frame rendering is complete
1384
+	 * * `begin_page_render`: called before a page is rendered
1385
+	 * * `end_page_render`: called after page rendering is complete
1386
+	 * * `end_document`: called for every page after rendering is complete
1387
+	 *
1388
+	 * The function `f` receives three arguments `Frame $frame`, `Canvas $canvas`,
1389
+	 * and `FontMetrics $fontMetrics` for all events but `end_document`. For
1390
+	 * `end_document`, the function receives four arguments `int $pageNumber`,
1391
+	 * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics` instead.
1392
+	 *
1393
+	 * @param array $callbacks The set of callbacks to set.
1394
+	 * @return $this
1395
+	 */
1396
+	public function setCallbacks(array $callbacks): self
1397
+	{
1398
+		$this->callbacks = [];
1399
+
1400
+		foreach ($callbacks as $c) {
1401
+			if (is_array($c) && isset($c["event"]) && isset($c["f"])) {
1402
+				$event = $c["event"];
1403
+				$f = $c["f"];
1404
+				if (is_string($event) && is_callable($f)) {
1405
+					$this->callbacks[$event][] = $f;
1406
+				}
1407
+			}
1408
+		}
1409
+
1410
+		return $this;
1411
+	}
1412
+
1413
+	/**
1414
+	 * @return boolean
1415
+	 * @deprecated
1416
+	 */
1417
+	public function get_quirksmode()
1418
+	{
1419
+		return $this->getQuirksmode();
1420
+	}
1421
+
1422
+	/**
1423
+	 * Get the quirks mode
1424
+	 *
1425
+	 * @return boolean true if quirks mode is active
1426
+	 */
1427
+	public function getQuirksmode()
1428
+	{
1429
+		return $this->quirksmode;
1430
+	}
1431
+
1432
+	/**
1433
+	 * @param FontMetrics $fontMetrics
1434
+	 * @return $this
1435
+	 */
1436
+	public function setFontMetrics(FontMetrics $fontMetrics)
1437
+	{
1438
+		$this->fontMetrics = $fontMetrics;
1439
+		return $this;
1440
+	}
1441
+
1442
+	/**
1443
+	 * @return FontMetrics
1444
+	 */
1445
+	public function getFontMetrics()
1446
+	{
1447
+		return $this->fontMetrics;
1448
+	}
1449
+
1450
+	/**
1451
+	 * PHP5 overloaded getter
1452
+	 * Along with {@link Dompdf::__set()} __get() provides access to all
1453
+	 * properties directly.  Typically __get() is not called directly outside
1454
+	 * of this class.
1455
+	 *
1456
+	 * @param string $prop
1457
+	 *
1458
+	 * @throws Exception
1459
+	 * @return mixed
1460
+	 */
1461
+	function __get($prop)
1462
+	{
1463
+		switch ($prop) {
1464
+			case 'version':
1465
+				return $this->version;
1466
+			default:
1467
+				throw new Exception('Invalid property: ' . $prop);
1468
+		}
1469
+	}
1470 1470
 }
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Css/Stylesheet.php 1 patch
Indentation   +1617 added lines, -1617 removed lines patch added patch discarded remove patch
@@ -27,33 +27,33 @@  discard block
 block discarded – undo
27 27
  */
28 28
 class Stylesheet
29 29
 {
30
-    /**
31
-     * The location of the default built-in CSS file.
32
-     */
33
-    const DEFAULT_STYLESHEET = "/lib/res/html.css";
34
-
35
-    /**
36
-     * User agent stylesheet origin
37
-     *
38
-     * @var int
39
-     */
40
-    const ORIG_UA = 1;
41
-
42
-    /**
43
-     * User normal stylesheet origin
44
-     *
45
-     * @var int
46
-     */
47
-    const ORIG_USER = 2;
48
-
49
-    /**
50
-     * Author normal stylesheet origin
51
-     *
52
-     * @var int
53
-     */
54
-    const ORIG_AUTHOR = 3;
55
-
56
-    /*
30
+	/**
31
+	 * The location of the default built-in CSS file.
32
+	 */
33
+	const DEFAULT_STYLESHEET = "/lib/res/html.css";
34
+
35
+	/**
36
+	 * User agent stylesheet origin
37
+	 *
38
+	 * @var int
39
+	 */
40
+	const ORIG_UA = 1;
41
+
42
+	/**
43
+	 * User normal stylesheet origin
44
+	 *
45
+	 * @var int
46
+	 */
47
+	const ORIG_USER = 2;
48
+
49
+	/**
50
+	 * Author normal stylesheet origin
51
+	 *
52
+	 * @var int
53
+	 */
54
+	const ORIG_AUTHOR = 3;
55
+
56
+	/*
57 57
      * The highest possible specificity is 0x01000000 (and that is only for author
58 58
      * stylesheets, as it is for inline styles). Origin precedence can be achieved by
59 59
      * adding multiples of 0x10000000 to the actual specificity. Important
@@ -63,797 +63,797 @@  discard block
 block discarded – undo
63 63
      * not support user stylesheets, and user agent stylesheets can not include
64 64
      * important declarations.
65 65
      */
66
-    private static $_stylesheet_origins = [
67
-        self::ORIG_UA => 0x00000000, // user agent declarations
68
-        self::ORIG_USER => 0x10000000, // user normal declarations
69
-        self::ORIG_AUTHOR => 0x30000000, // author normal declarations
70
-    ];
71
-
72
-    /**
73
-     * Non-CSS presentational hints (i.e. HTML 4 attributes) are handled as if added
74
-     * to the beginning of an author stylesheet, i.e. anything in author stylesheets
75
-     * should override them.
76
-     */
77
-    const SPEC_NON_CSS = 0x20000000;
78
-
79
-    /**
80
-     * Current dompdf instance
81
-     *
82
-     * @var Dompdf
83
-     */
84
-    private $_dompdf;
85
-
86
-    /**
87
-     * Array of currently defined styles
88
-     *
89
-     * @var Style[]
90
-     */
91
-    private $_styles;
92
-
93
-    /**
94
-     * Base protocol of the document being parsed
95
-     * Used to handle relative urls.
96
-     *
97
-     * @var string
98
-     */
99
-    private $_protocol = "";
100
-
101
-    /**
102
-     * Base hostname of the document being parsed
103
-     * Used to handle relative urls.
104
-     *
105
-     * @var string
106
-     */
107
-    private $_base_host = "";
108
-
109
-    /**
110
-     * Base path of the document being parsed
111
-     * Used to handle relative urls.
112
-     *
113
-     * @var string
114
-     */
115
-    private $_base_path = "";
116
-
117
-    /**
118
-     * The styles defined by @page rules
119
-     *
120
-     * @var array<Style>
121
-     */
122
-    private $_page_styles;
123
-
124
-    /**
125
-     * List of loaded files, used to prevent recursion
126
-     *
127
-     * @var array
128
-     */
129
-    private $_loaded_files;
130
-
131
-    /**
132
-     * Current stylesheet origin
133
-     *
134
-     * @var int
135
-     */
136
-    private $_current_origin = self::ORIG_UA;
137
-
138
-    /**
139
-     * Accepted CSS media types
140
-     * List of types and parsing rules for future extensions:
141
-     * http://www.w3.org/TR/REC-html40/types.html
142
-     *   screen, tty, tv, projection, handheld, print, braille, aural, all
143
-     * The following are non standard extensions for undocumented specific environments.
144
-     *   static, visual, bitmap, paged, dompdf
145
-     * Note, even though the generated pdf file is intended for print output,
146
-     * the desired content might be different (e.g. screen or projection view of html file).
147
-     * Therefore allow specification of content by dompdf setting Options::defaultMediaType.
148
-     * If given, replace media "print" by Options::defaultMediaType.
149
-     * (Previous version $ACCEPTED_MEDIA_TYPES = $ACCEPTED_GENERIC_MEDIA_TYPES + $ACCEPTED_DEFAULT_MEDIA_TYPE)
150
-     */
151
-    static $ACCEPTED_DEFAULT_MEDIA_TYPE = "print";
152
-    static $ACCEPTED_GENERIC_MEDIA_TYPES = ["all", "static", "visual", "bitmap", "paged", "dompdf"];
153
-    static $VALID_MEDIA_TYPES = ["all", "aural", "bitmap", "braille", "dompdf", "embossed", "handheld", "paged", "print", "projection", "screen", "speech", "static", "tty", "tv", "visual"];
154
-
155
-    /**
156
-     * @var FontMetrics
157
-     */
158
-    private $fontMetrics;
159
-
160
-    /**
161
-     * The class constructor.
162
-     *
163
-     * The base protocol, host & path are initialized to those of
164
-     * the current script.
165
-     */
166
-    function __construct(Dompdf $dompdf)
167
-    {
168
-        $this->_dompdf = $dompdf;
169
-        $this->setFontMetrics($dompdf->getFontMetrics());
170
-        $this->_styles = [];
171
-        $this->_loaded_files = [];
172
-        $script = __FILE__;
173
-        if (isset($_SERVER["SCRIPT_FILENAME"])) {
174
-            $script = $_SERVER["SCRIPT_FILENAME"];
175
-        }
176
-        list($this->_protocol, $this->_base_host, $this->_base_path) = Helpers::explode_url($script);
177
-        $this->_page_styles = ["base" => new Style($this)];
178
-    }
179
-
180
-    /**
181
-     * Set the base protocol
182
-     *
183
-     * @param string $protocol
184
-     */
185
-    function set_protocol(string $protocol)
186
-    {
187
-        $this->_protocol = $protocol;
188
-    }
189
-
190
-    /**
191
-     * Set the base host
192
-     *
193
-     * @param string $host
194
-     */
195
-    function set_host(string $host)
196
-    {
197
-        $this->_base_host = $host;
198
-    }
199
-
200
-    /**
201
-     * Set the base path
202
-     *
203
-     * @param string $path
204
-     */
205
-    function set_base_path(string $path)
206
-    {
207
-        $this->_base_path = $path;
208
-    }
209
-
210
-    /**
211
-     * Return the Dompdf object
212
-     *
213
-     * @return Dompdf
214
-     */
215
-    function get_dompdf()
216
-    {
217
-        return $this->_dompdf;
218
-    }
219
-
220
-    /**
221
-     * Return the base protocol for this stylesheet
222
-     *
223
-     * @return string
224
-     */
225
-    function get_protocol()
226
-    {
227
-        return $this->_protocol;
228
-    }
229
-
230
-    /**
231
-     * Return the base host for this stylesheet
232
-     *
233
-     * @return string
234
-     */
235
-    function get_host()
236
-    {
237
-        return $this->_base_host;
238
-    }
239
-
240
-    /**
241
-     * Return the base path for this stylesheet
242
-     *
243
-     * @return string
244
-     */
245
-    function get_base_path()
246
-    {
247
-        return $this->_base_path;
248
-    }
249
-
250
-    /**
251
-     * Return the array of page styles
252
-     *
253
-     * @return Style[]
254
-     */
255
-    function get_page_styles()
256
-    {
257
-        return $this->_page_styles;
258
-    }
259
-
260
-    /**
261
-     * Create a new Style object associated with this stylesheet
262
-     *
263
-     * @return Style
264
-     */
265
-    function create_style(): Style
266
-    {
267
-        return new Style($this, $this->_current_origin);
268
-    }
269
-
270
-    /**
271
-     * Add a new Style object to the stylesheet
272
-     *
273
-     * The style's origin is changed to the current origin of the stylesheet.
274
-     *
275
-     * @param string $key the Style's selector
276
-     * @param Style $style the Style to be added
277
-     */
278
-    function add_style(string $key, Style $style): void
279
-    {
280
-        if (!isset($this->_styles[$key])) {
281
-            $this->_styles[$key] = [];
282
-        }
283
-
284
-        $style->set_origin($this->_current_origin);
285
-        $this->_styles[$key][] = $style;
286
-    }
287
-
288
-    /**
289
-     * load and parse a CSS string
290
-     *
291
-     * @param string $css
292
-     * @param int $origin
293
-     */
294
-    function load_css(&$css, $origin = self::ORIG_AUTHOR)
295
-    {
296
-        if ($origin) {
297
-            $this->_current_origin = $origin;
298
-        }
299
-        $this->_parse_css($css);
300
-    }
301
-
302
-    /**
303
-     * load and parse a CSS file
304
-     *
305
-     * @param string $file
306
-     * @param int $origin
307
-     */
308
-    function load_css_file($file, $origin = self::ORIG_AUTHOR)
309
-    {
310
-        if ($origin) {
311
-            $this->_current_origin = $origin;
312
-        }
313
-
314
-        // Prevent circular references
315
-        if (isset($this->_loaded_files[$file])) {
316
-            return;
317
-        }
318
-
319
-        $this->_loaded_files[$file] = true;
320
-
321
-        if (strpos($file, "data:") === 0) {
322
-            $parsed = Helpers::parse_data_uri($file);
323
-            $css = $parsed["data"];
324
-        } else {
325
-            $options = $this->_dompdf->getOptions();
326
-
327
-            $parsed_url = Helpers::explode_url($file);
328
-            $protocol = $parsed_url["protocol"];
329
-
330
-            if ($file !== $this->getDefaultStylesheet()) {
331
-                $allowed_protocols = $options->getAllowedProtocols();
332
-                if (!array_key_exists($protocol, $allowed_protocols)) {
333
-                    Helpers::record_warnings(E_USER_WARNING, "Permission denied on $file. The communication protocol is not supported.", __FILE__, __LINE__);
334
-                    return;
335
-                }
336
-                foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
337
-                    [$result, $message] = $rule($file);
338
-                    if (!$result) {
339
-                        Helpers::record_warnings(E_USER_WARNING, "Error loading $file: $message", __FILE__, __LINE__);
340
-                        return;
341
-                    }
342
-                }
343
-            }
344
-
345
-            [$css, $http_response_header] = Helpers::getFileContent($file, $this->_dompdf->getHttpContext());
346
-
347
-            $good_mime_type = true;
348
-
349
-            // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
350
-            if (isset($http_response_header) && !$this->_dompdf->getQuirksmode()) {
351
-                foreach ($http_response_header as $_header) {
352
-                    if (preg_match("@Content-Type:\s*([\w/]+)@i", $_header, $matches) &&
353
-                        ($matches[1] !== "text/css")
354
-                    ) {
355
-                        $good_mime_type = false;
356
-                    }
357
-                }
358
-            }
359
-            if (!$good_mime_type || $css === null) {
360
-                Helpers::record_warnings(E_USER_WARNING, "Unable to load css file $file", __FILE__, __LINE__);
361
-                return;
362
-            }
363
-
364
-            [$this->_protocol, $this->_base_host, $this->_base_path] = $parsed_url;
365
-        }
366
-
367
-        $this->_parse_css($css);
368
-    }
369
-
370
-    /**
371
-     * @link http://www.w3.org/TR/CSS21/cascade.html#specificity
372
-     *
373
-     * @param string $selector
374
-     * @param int $origin :
375
-     *    - Stylesheet::ORIG_UA: user agent style sheet
376
-     *    - Stylesheet::ORIG_USER: user style sheet
377
-     *    - Stylesheet::ORIG_AUTHOR: author style sheet
378
-     *
379
-     * @return int
380
-     */
381
-    private function _specificity($selector, $origin = self::ORIG_AUTHOR)
382
-    {
383
-        // http://www.w3.org/TR/CSS21/cascade.html#specificity
384
-        // ignoring the ":" pseudoclass modifiers
385
-        // also ignored in _css_selector_to_xpath
386
-
387
-        $a = ($selector === "!attr") ? 1 : 0;
388
-
389
-        $b = min(mb_substr_count($selector, "#"), 255);
390
-
391
-        $c = min(mb_substr_count($selector, ".") +
392
-            mb_substr_count($selector, "["), 255);
393
-
394
-        $d = min(mb_substr_count($selector, " ") +
395
-            mb_substr_count($selector, ">") +
396
-            mb_substr_count($selector, "+") +
397
-            mb_substr_count($selector, "~") -
398
-            mb_substr_count($selector, "~="), 255);
399
-
400
-        //If a normal element name is at the beginning of the string,
401
-        //a leading whitespace might have been removed on whitespace collapsing and removal
402
-        //therefore there might be one whitespace less as selected element names
403
-        //this can lead to a too small specificity
404
-        //see _css_selector_to_xpath
405
-
406
-        if (!in_array($selector[0], [" ", ">", ".", "#", "+", "~", ":", "["]) && $selector !== "*") {
407
-            $d++;
408
-        }
409
-
410
-        if ($this->_dompdf->getOptions()->getDebugCss()) {
411
-            /*DEBUGCSS*/
412
-            print "<pre>\n";
413
-            /*DEBUGCSS*/
414
-            printf("_specificity(): 0x%08x \"%s\"\n", self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d)), $selector);
415
-            /*DEBUGCSS*/
416
-            print "</pre>";
417
-        }
418
-
419
-        return self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d));
420
-    }
421
-
422
-    /**
423
-     * Converts a CSS selector to an XPath query.
424
-     *
425
-     * @param string $selector
426
-     * @param bool $first_pass
427
-     *
428
-     * @throws Exception
429
-     * @return array
430
-     */
431
-    private function _css_selector_to_xpath(string $selector, bool $first_pass = false): array
432
-    {
433
-        // Collapse white space and strip whitespace around delimiters
434
-        //$search = array("/\\s+/", "/\\s+([.>#+:])\\s+/");
435
-        //$replace = array(" ", "\\1");
436
-        //$selector = preg_replace($search, $replace, trim($selector));
437
-
438
-        // Initial query (non-absolute)
439
-        $query = "//";
440
-
441
-        // Will contain :before and :after
442
-        $pseudo_elements = [];
443
-
444
-        // Will contain :link, etc
445
-        $pseudo_classes = [];
446
-
447
-        // Parse the selector
448
-        //$s = preg_split("/([ :>.#+])/", $selector, -1, PREG_SPLIT_DELIM_CAPTURE);
449
-
450
-        $delimiters = [" ", ">", ".", "#", "+", "~", ":", "[", "("];
451
-
452
-        // Add an implicit * at the beginning of the selector
453
-        // if it begins with an attribute selector
454
-        if ($selector[0] === "[") {
455
-            $selector = "*$selector";
456
-        }
457
-
458
-        // Add an implicit space at the beginning of the selector if there is no
459
-        // delimiter there already.
460
-        if (!in_array($selector[0], $delimiters)) {
461
-            $selector = " $selector";
462
-        }
463
-
464
-        $tok = "";
465
-        $len = mb_strlen($selector);
466
-        $i = 0;
467
-
468
-        while ($i < $len) {
469
-
470
-            $s = $selector[$i];
471
-            $i++;
472
-
473
-            // Eat characters up to the next delimiter
474
-            $tok = "";
475
-            $in_attr = false;
476
-            $in_func = false;
477
-
478
-            while ($i < $len) {
479
-                $c = $selector[$i];
480
-                $c_prev = $selector[$i - 1];
481
-
482
-                if (!$in_func && !$in_attr && in_array($c, $delimiters) && !(($c == $c_prev) == ":")) {
483
-                    break;
484
-                }
485
-
486
-                if ($c_prev === "[") {
487
-                    $in_attr = true;
488
-                }
489
-                if ($c_prev === "(") {
490
-                    $in_func = true;
491
-                }
492
-
493
-                $tok .= $selector[$i++];
494
-
495
-                if ($in_attr && $c === "]") {
496
-                    $in_attr = false;
497
-                    break;
498
-                }
499
-                if ($in_func && $c === ")") {
500
-                    $in_func = false;
501
-                    break;
502
-                }
503
-            }
504
-
505
-            switch ($s) {
506
-
507
-                case " ":
508
-                case ">":
509
-                    // All elements matching the next token that are direct children of
510
-                    // the current token
511
-                    $expr = $s === " " ? "descendant" : "child";
512
-
513
-                    if (mb_substr($query, -1, 1) !== "/") {
514
-                        $query .= "/";
515
-                    }
516
-
517
-                    // Tag names are case-insensitive
518
-                    $tok = strtolower($tok);
519
-
520
-                    if (!$tok) {
521
-                        $tok = "*";
522
-                    }
523
-
524
-                    $query .= "$expr::$tok";
525
-                    $tok = "";
526
-                    break;
527
-
528
-                case ".":
529
-                case "#":
530
-                    // All elements matching the current token with a class/id equal to
531
-                    // the _next_ token.
532
-
533
-                    $attr = $s === "." ? "class" : "id";
534
-
535
-                    // empty class/id == *
536
-                    if (mb_substr($query, -1, 1) === "/") {
537
-                        $query .= "*";
538
-                    }
539
-
540
-                    // Match multiple classes: $tok contains the current selected
541
-                    // class.  Search for class attributes with class="$tok",
542
-                    // class=".* $tok .*" and class=".* $tok"
543
-
544
-                    // This doesn't work because libxml only supports XPath 1.0...
545
-                    //$query .= "[matches(@$attr,\"^{$tok}\$|^{$tok}[ ]+|[ ]+{$tok}\$|[ ]+{$tok}[ ]+\")]";
546
-
547
-                    $query .= "[contains(concat(' ', normalize-space(@$attr), ' '), concat(' ', '$tok', ' '))]";
548
-                    $tok = "";
549
-                    break;
550
-
551
-                case "+":
552
-                case "~":
553
-                    // Next-sibling combinator
554
-                    // Subsequent-sibling combinator
555
-                    // https://www.w3.org/TR/selectors-3/#sibling-combinators
556
-                    if (mb_substr($query, -1, 1) !== "/") {
557
-                        $query .= "/";
558
-                    }
559
-
560
-                    // Tag names are case-insensitive
561
-                    $tok = strtolower($tok);
562
-
563
-                    if (!$tok) {
564
-                        $tok = "*";
565
-                    }
566
-
567
-                    $query .= "following-sibling::$tok";
568
-
569
-                    if ($s === "+") {
570
-                        $query .= "[1]";
571
-                    }
572
-
573
-                    $tok = "";
574
-                    break;
575
-
576
-                case ":":
577
-                    $i2 = $i - strlen($tok) - 2; // the char before ":"
578
-                    if (($i2 < 0 || !isset($selector[$i2]) || (in_array($selector[$i2], $delimiters) && $selector[$i2] != ":")) && substr($query, -1) != "*") {
579
-                        $query .= "*";
580
-                    }
581
-
582
-                    $last = false;
583
-
584
-                    // Pseudo-classes
585
-                    switch ($tok) {
586
-
587
-                        case "first-child":
588
-                            $query .= "[not(preceding-sibling::*)]";
589
-                            $tok = "";
590
-                            break;
591
-
592
-                        case "last-child":
593
-                            $query .= "[not(following-sibling::*)]";
594
-                            $tok = "";
595
-                            break;
596
-
597
-                        case "first-of-type":
598
-                            $query .= "[position() = 1]";
599
-                            $tok = "";
600
-                            break;
601
-
602
-                        case "last-of-type":
603
-                            $query .= "[position() = last()]";
604
-                            $tok = "";
605
-                            break;
606
-
607
-                        // an+b, n, odd, and even
608
-                        /** @noinspection PhpMissingBreakStatementInspection */
609
-                        case "nth-last-of-type":
610
-                            $last = true;
611
-                        case "nth-of-type":
612
-                            //FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
613
-                            $descendant_delimeter = strrpos($query, "::");
614
-                            $isChild = substr($query, $descendant_delimeter-5, 5) == "child";
615
-                            $el = substr($query, $descendant_delimeter+2);
616
-                            $query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . $el;
617
-
618
-                            $pseudo_classes[$tok] = true;
619
-                            $p = $i + 1;
620
-                            $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
621
-                            $position = $last ? "(last()-position()+1)" : "position()";
622
-
623
-                            // 1
624
-                            if (preg_match("/^\d+$/", $nth)) {
625
-                                $condition = "$position = $nth";
626
-                            } // odd
627
-                            elseif ($nth === "odd") {
628
-                                $condition = "($position mod 2) = 1";
629
-                            } // even
630
-                            elseif ($nth === "even") {
631
-                                $condition = "($position mod 2) = 0";
632
-                            } // an+b
633
-                            else {
634
-                                $condition = $this->_selector_an_plus_b($nth, $last);
635
-                            }
636
-
637
-                            $query .= "[$condition]";
638
-                            $tok = "";
639
-                            break;
640
-                        /** @noinspection PhpMissingBreakStatementInspection */
641
-                        case "nth-last-child":
642
-                            $last = true;
643
-                        case "nth-child":
644
-                            //FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
645
-                            $descendant_delimeter = strrpos($query, "::");
646
-                            $isChild = substr($query, $descendant_delimeter-5, 5) == "child";
647
-                            $el = substr($query, $descendant_delimeter+2);
648
-                            $query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . "*";
649
-
650
-                            $pseudo_classes[$tok] = true;
651
-                            $p = $i + 1;
652
-                            $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
653
-                            $position = $last ? "(last()-position()+1)" : "position()";
654
-
655
-                            // 1
656
-                            if (preg_match("/^\d+$/", $nth)) {
657
-                                $condition = "$position = $nth";
658
-                            } // odd
659
-                            elseif ($nth === "odd") {
660
-                                $condition = "($position mod 2) = 1";
661
-                            } // even
662
-                            elseif ($nth === "even") {
663
-                                $condition = "($position mod 2) = 0";
664
-                            } // an+b
665
-                            else {
666
-                                $condition = $this->_selector_an_plus_b($nth, $last);
667
-                            }
668
-
669
-                            $query .= "[$condition]";
670
-                            if ($el != "*") {
671
-                                $query .= "[name() = '$el']";
672
-                            }
673
-                            $tok = "";
674
-                            break;
675
-
676
-                        //TODO: bit of a hack attempt at matches support, currently only matches against elements
677
-                        case "matches":
678
-                            $pseudo_classes[$tok] = true;
679
-                            $p = $i + 1;
680
-                            $matchList = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
681
-
682
-                            // Tag names are case-insensitive
683
-                            $elements = array_map("trim", explode(",", strtolower($matchList)));
684
-                            foreach ($elements as &$element) {
685
-                                $element = "name() = '$element'";
686
-                            }
687
-
688
-                            $query .= "[" . implode(" or ", $elements) . "]";
689
-                            $tok = "";
690
-                            break;
691
-
692
-                        case "link":
693
-                            $query .= "[@href]";
694
-                            $tok = "";
695
-                            break;
696
-
697
-                        case "first-line":
698
-                        case ":first-line":
699
-                        case "first-letter":
700
-                        case ":first-letter":
701
-                            // TODO
702
-                            $el = trim($tok, ":");
703
-                            $pseudo_elements[$el] = true;
704
-                            break;
705
-
706
-                            // N/A
707
-                        case "focus":
708
-                        case "active":
709
-                        case "hover":
710
-                        case "visited":
711
-                            $query .= "[false()]";
712
-                            $tok = "";
713
-                            break;
714
-
715
-                        /* Pseudo-elements */
716
-                        case "before":
717
-                        case ":before":
718
-                        case "after":
719
-                        case ":after":
720
-                            $pos = trim($tok, ":");
721
-                            $pseudo_elements[$pos] = true;
722
-                            if (!$first_pass) {
723
-                                $query .= "/*[@$pos]";
724
-                            }
725
-
726
-                            $tok = "";
727
-                            break;
728
-
729
-                        case "empty":
730
-                            $query .= "[not(*) and not(normalize-space())]";
731
-                            $tok = "";
732
-                            break;
733
-
734
-                        case "disabled":
735
-                        case "checked":
736
-                            $query .= "[@$tok]";
737
-                            $tok = "";
738
-                            break;
739
-
740
-                        case "enabled":
741
-                            $query .= "[not(@disabled)]";
742
-                            $tok = "";
743
-                            break;
744
-
745
-                        // the selector is not handled, until we support all possible selectors force an empty set (silent failure)
746
-                        default:
747
-                            $query = "/../.."; // go up two levels because generated content starts on the body element
748
-                            $tok = "";
749
-                            break;
750
-                    }
751
-
752
-                    break;
753
-
754
-                case "[":
755
-                    // Attribute selectors.  All with an attribute matching the following token(s)
756
-                    // https://www.w3.org/TR/selectors-3/#attribute-selectors
757
-                    $attr_delimiters = ["=", "]", "~", "|", "$", "^", "*"];
758
-                    $tok_len = mb_strlen($tok);
759
-                    $j = 0;
760
-
761
-                    $attr = "";
762
-                    $op = "";
763
-                    $value = "";
764
-
765
-                    while ($j < $tok_len) {
766
-                        if (in_array($tok[$j], $attr_delimiters)) {
767
-                            break;
768
-                        }
769
-                        $attr .= $tok[$j++];
770
-                    }
771
-
772
-                    switch ($tok[$j]) {
773
-
774
-                        case "~":
775
-                        case "|":
776
-                        case "$":
777
-                        case "^":
778
-                        case "*":
779
-                            $op .= $tok[$j++];
780
-
781
-                            if ($tok[$j] !== "=") {
782
-                                throw new Exception("Invalid CSS selector syntax: invalid attribute selector: $selector");
783
-                            }
784
-
785
-                            $op .= $tok[$j];
786
-                            break;
787
-
788
-                        case "=":
789
-                            $op = "=";
790
-                            break;
791
-
792
-                    }
793
-
794
-                    // Read the attribute value, if required
795
-                    if ($op != "") {
796
-                        $j++;
797
-                        while ($j < $tok_len) {
798
-                            if ($tok[$j] === "]") {
799
-                                break;
800
-                            }
801
-                            $value .= $tok[$j++];
802
-                        }
803
-                    }
804
-
805
-                    if ($attr == "") {
806
-                        throw new Exception("Invalid CSS selector syntax: missing attribute name");
807
-                    }
808
-
809
-                    $value = trim($value, "\"'");
810
-
811
-                    switch ($op) {
812
-
813
-                        case "":
814
-                            $query .= "[@$attr]";
815
-                            break;
816
-
817
-                        case "=":
818
-                            $query .= "[@$attr=\"$value\"]";
819
-                            break;
820
-
821
-                        case "~=":
822
-                            // FIXME: this will break if $value contains quoted strings
823
-                            // (e.g. [type~="a b c" "d e f"])
824
-                            // FIXME: Don't match anything if value contains
825
-                            // whitespace or is the empty string
826
-                            $query .= "[contains(concat(' ', normalize-space(@$attr), ' '), concat(' ', '$value', ' '))]";
827
-                            break;
828
-
829
-                        case "|=":
830
-                            $values = explode("-", $value);
831
-                            $query .= "[";
832
-
833
-                            foreach ($values as $val) {
834
-                                $query .= "starts-with(@$attr, \"$val\") or ";
835
-                            }
836
-
837
-                            $query = rtrim($query, " or ") . "]";
838
-                            break;
839
-
840
-                        case "$=":
841
-                            $query .= "[substring(@$attr, string-length(@$attr)-" . (strlen($value) - 1) . ")=\"$value\"]";
842
-                            break;
843
-
844
-                        case "^=":
845
-                            $query .= "[starts-with(@$attr,\"$value\")]";
846
-                            break;
847
-
848
-                        case "*=":
849
-                            $query .= "[contains(@$attr,\"$value\")]";
850
-                            break;
851
-                    }
852
-
853
-                    break;
854
-            }
855
-        }
856
-        $i++;
66
+	private static $_stylesheet_origins = [
67
+		self::ORIG_UA => 0x00000000, // user agent declarations
68
+		self::ORIG_USER => 0x10000000, // user normal declarations
69
+		self::ORIG_AUTHOR => 0x30000000, // author normal declarations
70
+	];
71
+
72
+	/**
73
+	 * Non-CSS presentational hints (i.e. HTML 4 attributes) are handled as if added
74
+	 * to the beginning of an author stylesheet, i.e. anything in author stylesheets
75
+	 * should override them.
76
+	 */
77
+	const SPEC_NON_CSS = 0x20000000;
78
+
79
+	/**
80
+	 * Current dompdf instance
81
+	 *
82
+	 * @var Dompdf
83
+	 */
84
+	private $_dompdf;
85
+
86
+	/**
87
+	 * Array of currently defined styles
88
+	 *
89
+	 * @var Style[]
90
+	 */
91
+	private $_styles;
92
+
93
+	/**
94
+	 * Base protocol of the document being parsed
95
+	 * Used to handle relative urls.
96
+	 *
97
+	 * @var string
98
+	 */
99
+	private $_protocol = "";
100
+
101
+	/**
102
+	 * Base hostname of the document being parsed
103
+	 * Used to handle relative urls.
104
+	 *
105
+	 * @var string
106
+	 */
107
+	private $_base_host = "";
108
+
109
+	/**
110
+	 * Base path of the document being parsed
111
+	 * Used to handle relative urls.
112
+	 *
113
+	 * @var string
114
+	 */
115
+	private $_base_path = "";
116
+
117
+	/**
118
+	 * The styles defined by @page rules
119
+	 *
120
+	 * @var array<Style>
121
+	 */
122
+	private $_page_styles;
123
+
124
+	/**
125
+	 * List of loaded files, used to prevent recursion
126
+	 *
127
+	 * @var array
128
+	 */
129
+	private $_loaded_files;
130
+
131
+	/**
132
+	 * Current stylesheet origin
133
+	 *
134
+	 * @var int
135
+	 */
136
+	private $_current_origin = self::ORIG_UA;
137
+
138
+	/**
139
+	 * Accepted CSS media types
140
+	 * List of types and parsing rules for future extensions:
141
+	 * http://www.w3.org/TR/REC-html40/types.html
142
+	 *   screen, tty, tv, projection, handheld, print, braille, aural, all
143
+	 * The following are non standard extensions for undocumented specific environments.
144
+	 *   static, visual, bitmap, paged, dompdf
145
+	 * Note, even though the generated pdf file is intended for print output,
146
+	 * the desired content might be different (e.g. screen or projection view of html file).
147
+	 * Therefore allow specification of content by dompdf setting Options::defaultMediaType.
148
+	 * If given, replace media "print" by Options::defaultMediaType.
149
+	 * (Previous version $ACCEPTED_MEDIA_TYPES = $ACCEPTED_GENERIC_MEDIA_TYPES + $ACCEPTED_DEFAULT_MEDIA_TYPE)
150
+	 */
151
+	static $ACCEPTED_DEFAULT_MEDIA_TYPE = "print";
152
+	static $ACCEPTED_GENERIC_MEDIA_TYPES = ["all", "static", "visual", "bitmap", "paged", "dompdf"];
153
+	static $VALID_MEDIA_TYPES = ["all", "aural", "bitmap", "braille", "dompdf", "embossed", "handheld", "paged", "print", "projection", "screen", "speech", "static", "tty", "tv", "visual"];
154
+
155
+	/**
156
+	 * @var FontMetrics
157
+	 */
158
+	private $fontMetrics;
159
+
160
+	/**
161
+	 * The class constructor.
162
+	 *
163
+	 * The base protocol, host & path are initialized to those of
164
+	 * the current script.
165
+	 */
166
+	function __construct(Dompdf $dompdf)
167
+	{
168
+		$this->_dompdf = $dompdf;
169
+		$this->setFontMetrics($dompdf->getFontMetrics());
170
+		$this->_styles = [];
171
+		$this->_loaded_files = [];
172
+		$script = __FILE__;
173
+		if (isset($_SERVER["SCRIPT_FILENAME"])) {
174
+			$script = $_SERVER["SCRIPT_FILENAME"];
175
+		}
176
+		list($this->_protocol, $this->_base_host, $this->_base_path) = Helpers::explode_url($script);
177
+		$this->_page_styles = ["base" => new Style($this)];
178
+	}
179
+
180
+	/**
181
+	 * Set the base protocol
182
+	 *
183
+	 * @param string $protocol
184
+	 */
185
+	function set_protocol(string $protocol)
186
+	{
187
+		$this->_protocol = $protocol;
188
+	}
189
+
190
+	/**
191
+	 * Set the base host
192
+	 *
193
+	 * @param string $host
194
+	 */
195
+	function set_host(string $host)
196
+	{
197
+		$this->_base_host = $host;
198
+	}
199
+
200
+	/**
201
+	 * Set the base path
202
+	 *
203
+	 * @param string $path
204
+	 */
205
+	function set_base_path(string $path)
206
+	{
207
+		$this->_base_path = $path;
208
+	}
209
+
210
+	/**
211
+	 * Return the Dompdf object
212
+	 *
213
+	 * @return Dompdf
214
+	 */
215
+	function get_dompdf()
216
+	{
217
+		return $this->_dompdf;
218
+	}
219
+
220
+	/**
221
+	 * Return the base protocol for this stylesheet
222
+	 *
223
+	 * @return string
224
+	 */
225
+	function get_protocol()
226
+	{
227
+		return $this->_protocol;
228
+	}
229
+
230
+	/**
231
+	 * Return the base host for this stylesheet
232
+	 *
233
+	 * @return string
234
+	 */
235
+	function get_host()
236
+	{
237
+		return $this->_base_host;
238
+	}
239
+
240
+	/**
241
+	 * Return the base path for this stylesheet
242
+	 *
243
+	 * @return string
244
+	 */
245
+	function get_base_path()
246
+	{
247
+		return $this->_base_path;
248
+	}
249
+
250
+	/**
251
+	 * Return the array of page styles
252
+	 *
253
+	 * @return Style[]
254
+	 */
255
+	function get_page_styles()
256
+	{
257
+		return $this->_page_styles;
258
+	}
259
+
260
+	/**
261
+	 * Create a new Style object associated with this stylesheet
262
+	 *
263
+	 * @return Style
264
+	 */
265
+	function create_style(): Style
266
+	{
267
+		return new Style($this, $this->_current_origin);
268
+	}
269
+
270
+	/**
271
+	 * Add a new Style object to the stylesheet
272
+	 *
273
+	 * The style's origin is changed to the current origin of the stylesheet.
274
+	 *
275
+	 * @param string $key the Style's selector
276
+	 * @param Style $style the Style to be added
277
+	 */
278
+	function add_style(string $key, Style $style): void
279
+	{
280
+		if (!isset($this->_styles[$key])) {
281
+			$this->_styles[$key] = [];
282
+		}
283
+
284
+		$style->set_origin($this->_current_origin);
285
+		$this->_styles[$key][] = $style;
286
+	}
287
+
288
+	/**
289
+	 * load and parse a CSS string
290
+	 *
291
+	 * @param string $css
292
+	 * @param int $origin
293
+	 */
294
+	function load_css(&$css, $origin = self::ORIG_AUTHOR)
295
+	{
296
+		if ($origin) {
297
+			$this->_current_origin = $origin;
298
+		}
299
+		$this->_parse_css($css);
300
+	}
301
+
302
+	/**
303
+	 * load and parse a CSS file
304
+	 *
305
+	 * @param string $file
306
+	 * @param int $origin
307
+	 */
308
+	function load_css_file($file, $origin = self::ORIG_AUTHOR)
309
+	{
310
+		if ($origin) {
311
+			$this->_current_origin = $origin;
312
+		}
313
+
314
+		// Prevent circular references
315
+		if (isset($this->_loaded_files[$file])) {
316
+			return;
317
+		}
318
+
319
+		$this->_loaded_files[$file] = true;
320
+
321
+		if (strpos($file, "data:") === 0) {
322
+			$parsed = Helpers::parse_data_uri($file);
323
+			$css = $parsed["data"];
324
+		} else {
325
+			$options = $this->_dompdf->getOptions();
326
+
327
+			$parsed_url = Helpers::explode_url($file);
328
+			$protocol = $parsed_url["protocol"];
329
+
330
+			if ($file !== $this->getDefaultStylesheet()) {
331
+				$allowed_protocols = $options->getAllowedProtocols();
332
+				if (!array_key_exists($protocol, $allowed_protocols)) {
333
+					Helpers::record_warnings(E_USER_WARNING, "Permission denied on $file. The communication protocol is not supported.", __FILE__, __LINE__);
334
+					return;
335
+				}
336
+				foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
337
+					[$result, $message] = $rule($file);
338
+					if (!$result) {
339
+						Helpers::record_warnings(E_USER_WARNING, "Error loading $file: $message", __FILE__, __LINE__);
340
+						return;
341
+					}
342
+				}
343
+			}
344
+
345
+			[$css, $http_response_header] = Helpers::getFileContent($file, $this->_dompdf->getHttpContext());
346
+
347
+			$good_mime_type = true;
348
+
349
+			// See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
350
+			if (isset($http_response_header) && !$this->_dompdf->getQuirksmode()) {
351
+				foreach ($http_response_header as $_header) {
352
+					if (preg_match("@Content-Type:\s*([\w/]+)@i", $_header, $matches) &&
353
+						($matches[1] !== "text/css")
354
+					) {
355
+						$good_mime_type = false;
356
+					}
357
+				}
358
+			}
359
+			if (!$good_mime_type || $css === null) {
360
+				Helpers::record_warnings(E_USER_WARNING, "Unable to load css file $file", __FILE__, __LINE__);
361
+				return;
362
+			}
363
+
364
+			[$this->_protocol, $this->_base_host, $this->_base_path] = $parsed_url;
365
+		}
366
+
367
+		$this->_parse_css($css);
368
+	}
369
+
370
+	/**
371
+	 * @link http://www.w3.org/TR/CSS21/cascade.html#specificity
372
+	 *
373
+	 * @param string $selector
374
+	 * @param int $origin :
375
+	 *    - Stylesheet::ORIG_UA: user agent style sheet
376
+	 *    - Stylesheet::ORIG_USER: user style sheet
377
+	 *    - Stylesheet::ORIG_AUTHOR: author style sheet
378
+	 *
379
+	 * @return int
380
+	 */
381
+	private function _specificity($selector, $origin = self::ORIG_AUTHOR)
382
+	{
383
+		// http://www.w3.org/TR/CSS21/cascade.html#specificity
384
+		// ignoring the ":" pseudoclass modifiers
385
+		// also ignored in _css_selector_to_xpath
386
+
387
+		$a = ($selector === "!attr") ? 1 : 0;
388
+
389
+		$b = min(mb_substr_count($selector, "#"), 255);
390
+
391
+		$c = min(mb_substr_count($selector, ".") +
392
+			mb_substr_count($selector, "["), 255);
393
+
394
+		$d = min(mb_substr_count($selector, " ") +
395
+			mb_substr_count($selector, ">") +
396
+			mb_substr_count($selector, "+") +
397
+			mb_substr_count($selector, "~") -
398
+			mb_substr_count($selector, "~="), 255);
399
+
400
+		//If a normal element name is at the beginning of the string,
401
+		//a leading whitespace might have been removed on whitespace collapsing and removal
402
+		//therefore there might be one whitespace less as selected element names
403
+		//this can lead to a too small specificity
404
+		//see _css_selector_to_xpath
405
+
406
+		if (!in_array($selector[0], [" ", ">", ".", "#", "+", "~", ":", "["]) && $selector !== "*") {
407
+			$d++;
408
+		}
409
+
410
+		if ($this->_dompdf->getOptions()->getDebugCss()) {
411
+			/*DEBUGCSS*/
412
+			print "<pre>\n";
413
+			/*DEBUGCSS*/
414
+			printf("_specificity(): 0x%08x \"%s\"\n", self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d)), $selector);
415
+			/*DEBUGCSS*/
416
+			print "</pre>";
417
+		}
418
+
419
+		return self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d));
420
+	}
421
+
422
+	/**
423
+	 * Converts a CSS selector to an XPath query.
424
+	 *
425
+	 * @param string $selector
426
+	 * @param bool $first_pass
427
+	 *
428
+	 * @throws Exception
429
+	 * @return array
430
+	 */
431
+	private function _css_selector_to_xpath(string $selector, bool $first_pass = false): array
432
+	{
433
+		// Collapse white space and strip whitespace around delimiters
434
+		//$search = array("/\\s+/", "/\\s+([.>#+:])\\s+/");
435
+		//$replace = array(" ", "\\1");
436
+		//$selector = preg_replace($search, $replace, trim($selector));
437
+
438
+		// Initial query (non-absolute)
439
+		$query = "//";
440
+
441
+		// Will contain :before and :after
442
+		$pseudo_elements = [];
443
+
444
+		// Will contain :link, etc
445
+		$pseudo_classes = [];
446
+
447
+		// Parse the selector
448
+		//$s = preg_split("/([ :>.#+])/", $selector, -1, PREG_SPLIT_DELIM_CAPTURE);
449
+
450
+		$delimiters = [" ", ">", ".", "#", "+", "~", ":", "[", "("];
451
+
452
+		// Add an implicit * at the beginning of the selector
453
+		// if it begins with an attribute selector
454
+		if ($selector[0] === "[") {
455
+			$selector = "*$selector";
456
+		}
457
+
458
+		// Add an implicit space at the beginning of the selector if there is no
459
+		// delimiter there already.
460
+		if (!in_array($selector[0], $delimiters)) {
461
+			$selector = " $selector";
462
+		}
463
+
464
+		$tok = "";
465
+		$len = mb_strlen($selector);
466
+		$i = 0;
467
+
468
+		while ($i < $len) {
469
+
470
+			$s = $selector[$i];
471
+			$i++;
472
+
473
+			// Eat characters up to the next delimiter
474
+			$tok = "";
475
+			$in_attr = false;
476
+			$in_func = false;
477
+
478
+			while ($i < $len) {
479
+				$c = $selector[$i];
480
+				$c_prev = $selector[$i - 1];
481
+
482
+				if (!$in_func && !$in_attr && in_array($c, $delimiters) && !(($c == $c_prev) == ":")) {
483
+					break;
484
+				}
485
+
486
+				if ($c_prev === "[") {
487
+					$in_attr = true;
488
+				}
489
+				if ($c_prev === "(") {
490
+					$in_func = true;
491
+				}
492
+
493
+				$tok .= $selector[$i++];
494
+
495
+				if ($in_attr && $c === "]") {
496
+					$in_attr = false;
497
+					break;
498
+				}
499
+				if ($in_func && $c === ")") {
500
+					$in_func = false;
501
+					break;
502
+				}
503
+			}
504
+
505
+			switch ($s) {
506
+
507
+				case " ":
508
+				case ">":
509
+					// All elements matching the next token that are direct children of
510
+					// the current token
511
+					$expr = $s === " " ? "descendant" : "child";
512
+
513
+					if (mb_substr($query, -1, 1) !== "/") {
514
+						$query .= "/";
515
+					}
516
+
517
+					// Tag names are case-insensitive
518
+					$tok = strtolower($tok);
519
+
520
+					if (!$tok) {
521
+						$tok = "*";
522
+					}
523
+
524
+					$query .= "$expr::$tok";
525
+					$tok = "";
526
+					break;
527
+
528
+				case ".":
529
+				case "#":
530
+					// All elements matching the current token with a class/id equal to
531
+					// the _next_ token.
532
+
533
+					$attr = $s === "." ? "class" : "id";
534
+
535
+					// empty class/id == *
536
+					if (mb_substr($query, -1, 1) === "/") {
537
+						$query .= "*";
538
+					}
539
+
540
+					// Match multiple classes: $tok contains the current selected
541
+					// class.  Search for class attributes with class="$tok",
542
+					// class=".* $tok .*" and class=".* $tok"
543
+
544
+					// This doesn't work because libxml only supports XPath 1.0...
545
+					//$query .= "[matches(@$attr,\"^{$tok}\$|^{$tok}[ ]+|[ ]+{$tok}\$|[ ]+{$tok}[ ]+\")]";
546
+
547
+					$query .= "[contains(concat(' ', normalize-space(@$attr), ' '), concat(' ', '$tok', ' '))]";
548
+					$tok = "";
549
+					break;
550
+
551
+				case "+":
552
+				case "~":
553
+					// Next-sibling combinator
554
+					// Subsequent-sibling combinator
555
+					// https://www.w3.org/TR/selectors-3/#sibling-combinators
556
+					if (mb_substr($query, -1, 1) !== "/") {
557
+						$query .= "/";
558
+					}
559
+
560
+					// Tag names are case-insensitive
561
+					$tok = strtolower($tok);
562
+
563
+					if (!$tok) {
564
+						$tok = "*";
565
+					}
566
+
567
+					$query .= "following-sibling::$tok";
568
+
569
+					if ($s === "+") {
570
+						$query .= "[1]";
571
+					}
572
+
573
+					$tok = "";
574
+					break;
575
+
576
+				case ":":
577
+					$i2 = $i - strlen($tok) - 2; // the char before ":"
578
+					if (($i2 < 0 || !isset($selector[$i2]) || (in_array($selector[$i2], $delimiters) && $selector[$i2] != ":")) && substr($query, -1) != "*") {
579
+						$query .= "*";
580
+					}
581
+
582
+					$last = false;
583
+
584
+					// Pseudo-classes
585
+					switch ($tok) {
586
+
587
+						case "first-child":
588
+							$query .= "[not(preceding-sibling::*)]";
589
+							$tok = "";
590
+							break;
591
+
592
+						case "last-child":
593
+							$query .= "[not(following-sibling::*)]";
594
+							$tok = "";
595
+							break;
596
+
597
+						case "first-of-type":
598
+							$query .= "[position() = 1]";
599
+							$tok = "";
600
+							break;
601
+
602
+						case "last-of-type":
603
+							$query .= "[position() = last()]";
604
+							$tok = "";
605
+							break;
606
+
607
+						// an+b, n, odd, and even
608
+						/** @noinspection PhpMissingBreakStatementInspection */
609
+						case "nth-last-of-type":
610
+							$last = true;
611
+						case "nth-of-type":
612
+							//FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
613
+							$descendant_delimeter = strrpos($query, "::");
614
+							$isChild = substr($query, $descendant_delimeter-5, 5) == "child";
615
+							$el = substr($query, $descendant_delimeter+2);
616
+							$query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . $el;
617
+
618
+							$pseudo_classes[$tok] = true;
619
+							$p = $i + 1;
620
+							$nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
621
+							$position = $last ? "(last()-position()+1)" : "position()";
622
+
623
+							// 1
624
+							if (preg_match("/^\d+$/", $nth)) {
625
+								$condition = "$position = $nth";
626
+							} // odd
627
+							elseif ($nth === "odd") {
628
+								$condition = "($position mod 2) = 1";
629
+							} // even
630
+							elseif ($nth === "even") {
631
+								$condition = "($position mod 2) = 0";
632
+							} // an+b
633
+							else {
634
+								$condition = $this->_selector_an_plus_b($nth, $last);
635
+							}
636
+
637
+							$query .= "[$condition]";
638
+							$tok = "";
639
+							break;
640
+						/** @noinspection PhpMissingBreakStatementInspection */
641
+						case "nth-last-child":
642
+							$last = true;
643
+						case "nth-child":
644
+							//FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
645
+							$descendant_delimeter = strrpos($query, "::");
646
+							$isChild = substr($query, $descendant_delimeter-5, 5) == "child";
647
+							$el = substr($query, $descendant_delimeter+2);
648
+							$query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . "*";
649
+
650
+							$pseudo_classes[$tok] = true;
651
+							$p = $i + 1;
652
+							$nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
653
+							$position = $last ? "(last()-position()+1)" : "position()";
654
+
655
+							// 1
656
+							if (preg_match("/^\d+$/", $nth)) {
657
+								$condition = "$position = $nth";
658
+							} // odd
659
+							elseif ($nth === "odd") {
660
+								$condition = "($position mod 2) = 1";
661
+							} // even
662
+							elseif ($nth === "even") {
663
+								$condition = "($position mod 2) = 0";
664
+							} // an+b
665
+							else {
666
+								$condition = $this->_selector_an_plus_b($nth, $last);
667
+							}
668
+
669
+							$query .= "[$condition]";
670
+							if ($el != "*") {
671
+								$query .= "[name() = '$el']";
672
+							}
673
+							$tok = "";
674
+							break;
675
+
676
+						//TODO: bit of a hack attempt at matches support, currently only matches against elements
677
+						case "matches":
678
+							$pseudo_classes[$tok] = true;
679
+							$p = $i + 1;
680
+							$matchList = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
681
+
682
+							// Tag names are case-insensitive
683
+							$elements = array_map("trim", explode(",", strtolower($matchList)));
684
+							foreach ($elements as &$element) {
685
+								$element = "name() = '$element'";
686
+							}
687
+
688
+							$query .= "[" . implode(" or ", $elements) . "]";
689
+							$tok = "";
690
+							break;
691
+
692
+						case "link":
693
+							$query .= "[@href]";
694
+							$tok = "";
695
+							break;
696
+
697
+						case "first-line":
698
+						case ":first-line":
699
+						case "first-letter":
700
+						case ":first-letter":
701
+							// TODO
702
+							$el = trim($tok, ":");
703
+							$pseudo_elements[$el] = true;
704
+							break;
705
+
706
+							// N/A
707
+						case "focus":
708
+						case "active":
709
+						case "hover":
710
+						case "visited":
711
+							$query .= "[false()]";
712
+							$tok = "";
713
+							break;
714
+
715
+						/* Pseudo-elements */
716
+						case "before":
717
+						case ":before":
718
+						case "after":
719
+						case ":after":
720
+							$pos = trim($tok, ":");
721
+							$pseudo_elements[$pos] = true;
722
+							if (!$first_pass) {
723
+								$query .= "/*[@$pos]";
724
+							}
725
+
726
+							$tok = "";
727
+							break;
728
+
729
+						case "empty":
730
+							$query .= "[not(*) and not(normalize-space())]";
731
+							$tok = "";
732
+							break;
733
+
734
+						case "disabled":
735
+						case "checked":
736
+							$query .= "[@$tok]";
737
+							$tok = "";
738
+							break;
739
+
740
+						case "enabled":
741
+							$query .= "[not(@disabled)]";
742
+							$tok = "";
743
+							break;
744
+
745
+						// the selector is not handled, until we support all possible selectors force an empty set (silent failure)
746
+						default:
747
+							$query = "/../.."; // go up two levels because generated content starts on the body element
748
+							$tok = "";
749
+							break;
750
+					}
751
+
752
+					break;
753
+
754
+				case "[":
755
+					// Attribute selectors.  All with an attribute matching the following token(s)
756
+					// https://www.w3.org/TR/selectors-3/#attribute-selectors
757
+					$attr_delimiters = ["=", "]", "~", "|", "$", "^", "*"];
758
+					$tok_len = mb_strlen($tok);
759
+					$j = 0;
760
+
761
+					$attr = "";
762
+					$op = "";
763
+					$value = "";
764
+
765
+					while ($j < $tok_len) {
766
+						if (in_array($tok[$j], $attr_delimiters)) {
767
+							break;
768
+						}
769
+						$attr .= $tok[$j++];
770
+					}
771
+
772
+					switch ($tok[$j]) {
773
+
774
+						case "~":
775
+						case "|":
776
+						case "$":
777
+						case "^":
778
+						case "*":
779
+							$op .= $tok[$j++];
780
+
781
+							if ($tok[$j] !== "=") {
782
+								throw new Exception("Invalid CSS selector syntax: invalid attribute selector: $selector");
783
+							}
784
+
785
+							$op .= $tok[$j];
786
+							break;
787
+
788
+						case "=":
789
+							$op = "=";
790
+							break;
791
+
792
+					}
793
+
794
+					// Read the attribute value, if required
795
+					if ($op != "") {
796
+						$j++;
797
+						while ($j < $tok_len) {
798
+							if ($tok[$j] === "]") {
799
+								break;
800
+							}
801
+							$value .= $tok[$j++];
802
+						}
803
+					}
804
+
805
+					if ($attr == "") {
806
+						throw new Exception("Invalid CSS selector syntax: missing attribute name");
807
+					}
808
+
809
+					$value = trim($value, "\"'");
810
+
811
+					switch ($op) {
812
+
813
+						case "":
814
+							$query .= "[@$attr]";
815
+							break;
816
+
817
+						case "=":
818
+							$query .= "[@$attr=\"$value\"]";
819
+							break;
820
+
821
+						case "~=":
822
+							// FIXME: this will break if $value contains quoted strings
823
+							// (e.g. [type~="a b c" "d e f"])
824
+							// FIXME: Don't match anything if value contains
825
+							// whitespace or is the empty string
826
+							$query .= "[contains(concat(' ', normalize-space(@$attr), ' '), concat(' ', '$value', ' '))]";
827
+							break;
828
+
829
+						case "|=":
830
+							$values = explode("-", $value);
831
+							$query .= "[";
832
+
833
+							foreach ($values as $val) {
834
+								$query .= "starts-with(@$attr, \"$val\") or ";
835
+							}
836
+
837
+							$query = rtrim($query, " or ") . "]";
838
+							break;
839
+
840
+						case "$=":
841
+							$query .= "[substring(@$attr, string-length(@$attr)-" . (strlen($value) - 1) . ")=\"$value\"]";
842
+							break;
843
+
844
+						case "^=":
845
+							$query .= "[starts-with(@$attr,\"$value\")]";
846
+							break;
847
+
848
+						case "*=":
849
+							$query .= "[contains(@$attr,\"$value\")]";
850
+							break;
851
+					}
852
+
853
+					break;
854
+			}
855
+		}
856
+		$i++;
857 857
 
858 858
 //       case ":":
859 859
 //         // Pseudo selectors: ignore for now.  Partially handled directly
@@ -876,814 +876,814 @@  discard block
 block discarded – undo
876 876
 //    }
877 877
 
878 878
 
879
-        // Trim the trailing '/' from the query
880
-        if (mb_strlen($query) > 2) {
881
-            $query = rtrim($query, "/");
882
-        }
883
-
884
-        return ['query' => $query, 'pseudo_elements' => $pseudo_elements];
885
-    }
886
-
887
-    /**
888
-     * https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb
889
-     *
890
-     * @param string $expr
891
-     * @param bool $last
892
-     *
893
-     * @return string
894
-     */
895
-    protected function _selector_an_plus_b(string $expr, bool $last = false): string
896
-    {
897
-        $expr = preg_replace("/\s/", "", $expr);
898
-        if (!preg_match("/^(?P<a>-?[0-9]*)?n(?P<b>[-+]?[0-9]+)?$/", $expr, $matches)) {
899
-            return "false()";
900
-        }
901
-
902
-        $a = (isset($matches["a"]) && $matches["a"] !== "") ? ($matches["a"] !== "-" ? intval($matches["a"]) : -1) : 1;
903
-        $b = (isset($matches["b"]) && $matches["b"] !== "") ? intval($matches["b"]) : 0;
904
-
905
-        $position = $last ? "(last()-position()+1)" : "position()";
906
-
907
-        if ($b == 0) {
908
-            return "($position mod $a) = 0";
909
-        } else {
910
-            $compare = ($a < 0) ? "<=" : ">=";
911
-            $b2 = -$b;
912
-            if ($b2 >= 0) {
913
-                $b2 = "+$b2";
914
-            }
915
-            return "($position $compare $b) and ((($position $b2) mod " . abs($a) . ") = 0)";
916
-        }
917
-    }
918
-
919
-    /**
920
-     * applies all current styles to a particular document tree
921
-     *
922
-     * apply_styles() applies all currently loaded styles to the provided
923
-     * {@link FrameTree}.  Aside from parsing CSS, this is the main purpose
924
-     * of this class.
925
-     *
926
-     * @param \Dompdf\Frame\FrameTree $tree
927
-     */
928
-    function apply_styles(FrameTree $tree)
929
-    {
930
-        // Use XPath to select nodes.  This would be easier if we could attach
931
-        // Frame objects directly to DOMNodes using the setUserData() method, but
932
-        // we can't do that just yet.  Instead, we set a _node attribute_ in
933
-        // Frame->set_id() and use that as a handle on the Frame object via
934
-        // FrameTree::$_registry.
935
-
936
-        // We create a scratch array of styles indexed by frame id.  Once all
937
-        // styles have been assigned, we order the cached styles by specificity
938
-        // and create a final style object to assign to the frame.
939
-
940
-        // FIXME: this is not particularly robust...
941
-
942
-        $styles = [];
943
-        $xp = new DOMXPath($tree->get_dom());
944
-        $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
945
-
946
-        // Add generated content
947
-        foreach ($this->_styles as $selector => $selector_styles) {
948
-            /** @var Style $style */
949
-            foreach ($selector_styles as $style) {
950
-                if (strpos($selector, ":before") === false && strpos($selector, ":after") === false) {
951
-                    continue;
952
-                }
953
-
954
-                $query = $this->_css_selector_to_xpath($selector, true);
955
-
956
-                // Retrieve the nodes, limit to body for generated content
957
-                //TODO: If we use a context node can we remove the leading dot?
958
-                $nodes = @$xp->query('.' . $query["query"]);
959
-                if ($nodes === false) {
960
-                    Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
961
-                    continue;
962
-                }
963
-
964
-                /** @var \DOMElement $node */
965
-                foreach ($nodes as $node) {
966
-                    // Only DOMElements get styles
967
-                    if ($node->nodeType != XML_ELEMENT_NODE) {
968
-                        continue;
969
-                    }
970
-
971
-                    foreach (array_keys($query["pseudo_elements"], true, true) as $pos) {
972
-                        // Do not add a new pseudo element if another one already matched
973
-                        if ($node->hasAttribute("dompdf_{$pos}_frame_id")) {
974
-                            continue;
975
-                        }
976
-
977
-                        $content = $style->get_specified("content");
978
-
979
-                        // Do not create non-displayed before/after pseudo elements
980
-                        // https://www.w3.org/TR/CSS21/generate.html#content
981
-                        // https://www.w3.org/TR/CSS21/generate.html#undisplayed-counters
982
-                        if ($content === "normal" || $content === "none") {
983
-                            continue;
984
-                        }
985
-
986
-                        if (($src = $this->resolve_url($content)) !== "none") {
987
-                            $new_node = $node->ownerDocument->createElement("img_generated");
988
-                            $new_node->setAttribute("src", $src);
989
-                        } else {
990
-                            $new_node = $node->ownerDocument->createElement("dompdf_generated");
991
-                        }
992
-
993
-                        $new_node->setAttribute($pos, $pos);
994
-                        $new_frame_id = $tree->insert_node($node, $new_node, $pos);
995
-                        $node->setAttribute("dompdf_{$pos}_frame_id", $new_frame_id);
996
-                    }
997
-                }
998
-            }
999
-        }
1000
-
1001
-        // Apply all styles in stylesheet
1002
-        foreach ($this->_styles as $selector => $selector_styles) {
1003
-            /** @var Style $style */
1004
-            foreach ($selector_styles as $style) {
1005
-                $query = $this->_css_selector_to_xpath($selector);
1006
-
1007
-                // Retrieve the nodes
1008
-                $nodes = @$xp->query($query["query"]);
1009
-                if ($nodes === false) {
1010
-                    Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
1011
-                    continue;
1012
-                }
1013
-
1014
-                $spec = $this->_specificity($selector, $style->get_origin());
1015
-
1016
-                foreach ($nodes as $node) {
1017
-                    // Retrieve the node id
1018
-                    // Only DOMElements get styles
1019
-                    if ($node->nodeType != XML_ELEMENT_NODE) {
1020
-                        continue;
1021
-                    }
1022
-
1023
-                    $id = $node->getAttribute("frame_id");
1024
-
1025
-                    // Assign the current style to the scratch array
1026
-                    $styles[$id][$spec][] = $style;
1027
-                }
1028
-            }
1029
-        }
1030
-
1031
-        // Set the page width, height, and orientation based on the canvas paper size
1032
-        $canvas = $this->_dompdf->getCanvas();
1033
-        $paper_width = $canvas->get_width();
1034
-        $paper_height = $canvas->get_height();
1035
-        $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
1036
-
1037
-        if ($this->_page_styles["base"] && is_array($this->_page_styles["base"]->size)) {
1038
-            $paper_width = $this->_page_styles['base']->size[0];
1039
-            $paper_height = $this->_page_styles['base']->size[1];
1040
-            $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
1041
-        }
1042
-
1043
-        // Now create the styles and assign them to the appropriate frames. (We
1044
-        // iterate over the tree using an implicit FrameTree iterator.)
1045
-        $root_flg = false;
1046
-        foreach ($tree as $frame) {
1047
-            // Helpers::pre_r($frame->get_node()->nodeName . ":");
1048
-            if (!$root_flg && $this->_page_styles["base"]) {
1049
-                $style = $this->_page_styles["base"];
1050
-            } else {
1051
-                $style = $this->create_style();
1052
-            }
1053
-
1054
-            // Find nearest DOMElement parent
1055
-            $p = $frame;
1056
-            while ($p = $p->get_parent()) {
1057
-                if ($p->get_node()->nodeType === XML_ELEMENT_NODE) {
1058
-                    break;
1059
-                }
1060
-            }
1061
-
1062
-            // Styles can only be applied directly to DOMElements; anonymous
1063
-            // frames inherit from their parent
1064
-            if ($frame->get_node()->nodeType !== XML_ELEMENT_NODE) {
1065
-                $style->inherit($p ? $p->get_style() : null);
1066
-                $frame->set_style($style);
1067
-                continue;
1068
-            }
1069
-
1070
-            $id = $frame->get_id();
1071
-
1072
-            // Handle HTML 4.0 attributes
1073
-            AttributeTranslator::translate_attributes($frame);
1074
-            if (($str = $frame->get_node()->getAttribute(AttributeTranslator::$_style_attr)) !== "") {
1075
-                $styles[$id][self::SPEC_NON_CSS][] = $this->_parse_properties($str);
1076
-            }
1077
-
1078
-            // Locate any additional style attributes
1079
-            if (($str = $frame->get_node()->getAttribute("style")) !== "") {
1080
-                // Destroy CSS comments
1081
-                $str = preg_replace("'/\*.*?\*/'si", "", $str);
1082
-
1083
-                $spec = $this->_specificity("!attr", self::ORIG_AUTHOR);
1084
-                $styles[$id][$spec][] = $this->_parse_properties($str);
1085
-            }
1086
-
1087
-            // Grab the applicable styles
1088
-            if (isset($styles[$id])) {
1089
-
1090
-                /** @var array[][] $applied_styles */
1091
-                $applied_styles = $styles[$id];
1092
-
1093
-                // Sort by specificity
1094
-                ksort($applied_styles);
1095
-
1096
-                if ($DEBUGCSS) {
1097
-                    $debug_nodename = $frame->get_node()->nodeName;
1098
-                    print "<pre>\n$debug_nodename [\n";
1099
-                    foreach ($applied_styles as $spec => $arr) {
1100
-                        printf("  specificity 0x%08x\n", $spec);
1101
-                        /** @var Style $s */
1102
-                        foreach ($arr as $s) {
1103
-                            print "  [\n";
1104
-                            $s->debug_print();
1105
-                            print "  ]\n";
1106
-                        }
1107
-                    }
1108
-                }
1109
-
1110
-                // Merge the new styles with the inherited styles
1111
-                $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
1112
-                $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
1113
-                foreach ($applied_styles as $arr) {
1114
-                    /** @var Style $s */
1115
-                    foreach ($arr as $s) {
1116
-                        $media_queries = $s->get_media_queries();
1117
-                        foreach ($media_queries as $media_query) {
1118
-                            list($media_query_feature, $media_query_value) = $media_query;
1119
-                            // if any of the Style's media queries fail then do not apply the style
1120
-                            //TODO: When the media query logic is fully developed we should not apply the Style when any of the media queries fail or are bad, per https://www.w3.org/TR/css3-mediaqueries/#error-handling
1121
-                            if (in_array($media_query_feature, self::$VALID_MEDIA_TYPES)) {
1122
-                                if ((strlen($media_query_feature) === 0 && !in_array($media_query, $acceptedmedia)) || (in_array($media_query, $acceptedmedia) && $media_query_value == "not")) {
1123
-                                    continue (3);
1124
-                                }
1125
-                            } else {
1126
-                                switch ($media_query_feature) {
1127
-                                    case "height":
1128
-                                        if ($paper_height !== (float)$style->length_in_pt($media_query_value)) {
1129
-                                            continue (3);
1130
-                                        }
1131
-                                        break;
1132
-                                    case "min-height":
1133
-                                        if ($paper_height < (float)$style->length_in_pt($media_query_value)) {
1134
-                                            continue (3);
1135
-                                        }
1136
-                                        break;
1137
-                                    case "max-height":
1138
-                                        if ($paper_height > (float)$style->length_in_pt($media_query_value)) {
1139
-                                            continue (3);
1140
-                                        }
1141
-                                        break;
1142
-                                    case "width":
1143
-                                        if ($paper_width !== (float)$style->length_in_pt($media_query_value)) {
1144
-                                            continue (3);
1145
-                                        }
1146
-                                        break;
1147
-                                    case "min-width":
1148
-                                        //if (min($paper_width, $media_query_width) === $paper_width) {
1149
-                                        if ($paper_width < (float)$style->length_in_pt($media_query_value)) {
1150
-                                            continue (3);
1151
-                                        }
1152
-                                        break;
1153
-                                    case "max-width":
1154
-                                        //if (max($paper_width, $media_query_width) === $paper_width) {
1155
-                                        if ($paper_width > (float)$style->length_in_pt($media_query_value)) {
1156
-                                            continue (3);
1157
-                                        }
1158
-                                        break;
1159
-                                    case "orientation":
1160
-                                        if ($paper_orientation !== $media_query_value) {
1161
-                                            continue (3);
1162
-                                        }
1163
-                                        break;
1164
-                                    default:
1165
-                                        Helpers::record_warnings(E_USER_WARNING, "Unknown media query: $media_query_feature", __FILE__, __LINE__);
1166
-                                        break;
1167
-                                }
1168
-                            }
1169
-                        }
1170
-
1171
-                        $style->merge($s);
1172
-                    }
1173
-                }
1174
-            }
1175
-
1176
-            // Handle inheritance
1177
-            if ($p && $DEBUGCSS) {
1178
-                print "  inherit [\n";
1179
-                $p->get_style()->debug_print();
1180
-                print "  ]\n";
1181
-            }
1182
-
1183
-            $style->inherit($p ? $p->get_style() : null);
1184
-
1185
-            if ($DEBUGCSS) {
1186
-                print "  DomElementStyle [\n";
1187
-                $style->debug_print();
1188
-                print "  ]\n";
1189
-                print "]\n</pre>";
1190
-            }
1191
-
1192
-            $style->clear_important();
1193
-            $frame->set_style($style);
1194
-
1195
-            if (!$root_flg && $this->_page_styles["base"]) {
1196
-                $root_flg = true;
1197
-
1198
-                // set the page width, height, and orientation based on the parsed page style
1199
-                if ($style->size !== "auto") {
1200
-                    list($paper_width, $paper_height) = $style->size;
1201
-                }
1202
-                $paper_width = $paper_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->margin_right);
1203
-                $paper_height = $paper_height - (float)$style->length_in_pt($style->margin_top) - (float)$style->length_in_pt($style->margin_bottom);
1204
-                $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
1205
-            }
1206
-        }
1207
-
1208
-        // We're done!  Clean out the registry of all styles since we
1209
-        // won't be needing this later.
1210
-        foreach (array_keys($this->_styles) as $key) {
1211
-            $this->_styles[$key] = null;
1212
-            unset($this->_styles[$key]);
1213
-        }
1214
-    }
1215
-
1216
-    /**
1217
-     * parse a CSS string using a regex parser
1218
-     * Called by {@link Stylesheet::parse_css()}
1219
-     *
1220
-     * @param string $str
1221
-     *
1222
-     * @throws Exception
1223
-     */
1224
-    private function _parse_css($str)
1225
-    {
1226
-        $str = trim($str);
1227
-
1228
-        // Destroy comments and remove HTML comments
1229
-        $css = preg_replace([
1230
-            "'/\*.*?\*/'si",
1231
-            "/^<!--/",
1232
-            "/-->$/"
1233
-        ], "", $str);
1234
-
1235
-        // FIXME: handle '{' within strings, e.g. [attr="string {}"]
1236
-
1237
-        // Something more legible:
1238
-        $re =
1239
-            "/\s*                                   # Skip leading whitespace                             \n" .
1240
-            "( @([^\s{]+)\s*([^{;]*) (?:;|({)) )?   # Match @rules followed by ';' or '{'                 \n" .
1241
-            "(?(1)                                  # Only parse sub-sections if we're in an @rule...     \n" .
1242
-            "  (?(4)                                # ...and if there was a leading '{'                   \n" .
1243
-            "    \s*( (?:(?>[^{}]+) ({)?            # Parse rulesets and individual @page rules           \n" .
1244
-            "            (?(6) (?>[^}]*) }) \s*)+?                                                        \n" .
1245
-            "       )                                                                                     \n" .
1246
-            "   })                                  # Balancing '}'                                       \n" .
1247
-            "|                                      # Branch to match regular rules (not preceded by '@') \n" .
1248
-            "([^{]*{[^}]*}))                        # Parse normal rulesets                               \n" .
1249
-            "/xs";
1250
-
1251
-        if (preg_match_all($re, $css, $matches, PREG_SET_ORDER) === false) {
1252
-            // An error occurred
1253
-            throw new Exception("Error parsing css file: preg_match_all() failed.");
1254
-        }
1255
-
1256
-        // After matching, the array indices are set as follows:
1257
-        //
1258
-        // [0] => complete text of match
1259
-        // [1] => contains '@import ...;' or '@media {' if applicable
1260
-        // [2] => text following @ for cases where [1] is set
1261
-        // [3] => media types or full text following '@import ...;'
1262
-        // [4] => '{', if present
1263
-        // [5] => rulesets within media rules
1264
-        // [6] => '{', within media rules
1265
-        // [7] => individual rules, outside of media rules
1266
-        //
1267
-
1268
-        $media_query_regex = "/(?:((only|not)?\s*(" . implode("|", self::$VALID_MEDIA_TYPES) . "))|(\s*\(\s*((?:(min|max)-)?([\w\-]+))\s*(?:\:\s*(.*?)\s*)?\)))/isx";
1269
-
1270
-        //Helpers::pre_r($matches);
1271
-        foreach ($matches as $match) {
1272
-            $match[2] = trim($match[2]);
1273
-
1274
-            if ($match[2] !== "") {
1275
-                // Handle @rules
1276
-                switch ($match[2]) {
1277
-
1278
-                    case "import":
1279
-                        $this->_parse_import($match[3]);
1280
-                        break;
1281
-
1282
-                    case "media":
1283
-                        $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
1284
-                        $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
1285
-
1286
-                        $media_queries = preg_split("/\s*,\s*/", mb_strtolower(trim($match[3])));
1287
-                        foreach ($media_queries as $media_query) {
1288
-                            if (in_array($media_query, $acceptedmedia)) {
1289
-                                //if we have a media type match go ahead and parse the stylesheet
1290
-                                $this->_parse_sections($match[5]);
1291
-                                break;
1292
-                            } elseif (!in_array($media_query, self::$VALID_MEDIA_TYPES)) {
1293
-                                // otherwise conditionally parse the stylesheet assuming there are parseable media queries
1294
-                                if (preg_match_all($media_query_regex, $media_query, $media_query_matches, PREG_SET_ORDER) !== false) {
1295
-                                    $mq = [];
1296
-                                    foreach ($media_query_matches as $media_query_match) {
1297
-                                        if (empty($media_query_match[1]) === false) {
1298
-                                            $media_query_feature = strtolower($media_query_match[3]);
1299
-                                            $media_query_value = strtolower($media_query_match[2]);
1300
-                                            $mq[] = [$media_query_feature, $media_query_value];
1301
-                                        } elseif (empty($media_query_match[4]) === false) {
1302
-                                            $media_query_feature = strtolower($media_query_match[5]);
1303
-                                            $media_query_value = (array_key_exists(8, $media_query_match) ? strtolower($media_query_match[8]) : null);
1304
-                                            $mq[] = [$media_query_feature, $media_query_value];
1305
-                                        }
1306
-                                    }
1307
-                                    $this->_parse_sections($match[5], $mq);
1308
-                                    break;
1309
-                                }
1310
-                            }
1311
-                        }
1312
-                        break;
1313
-
1314
-                    case "page":
1315
-                        //This handles @page to be applied to page oriented media
1316
-                        //Note: This has a reduced syntax:
1317
-                        //@page { margin:1cm; color:blue; }
1318
-                        //Not a sequence of styles like a full.css, but only the properties
1319
-                        //of a single style, which is applied to the very first "root" frame before
1320
-                        //processing other styles of the frame.
1321
-                        //Working properties:
1322
-                        // margin (for margin around edge of paper)
1323
-                        // font-family (default font of pages)
1324
-                        // color (default text color of pages)
1325
-                        //Non working properties:
1326
-                        // border
1327
-                        // padding
1328
-                        // background-color
1329
-                        //Todo:Reason is unknown
1330
-                        //Other properties (like further font or border attributes) not tested.
1331
-                        //If a border or background color around each paper sheet is desired,
1332
-                        //assign it to the <body> tag, possibly only for the css of the correct media type.
1333
-
1334
-                        // If the page has a name, skip the style.
1335
-                        $page_selector = trim($match[3]);
1336
-
1337
-                        $key = null;
1338
-                        switch ($page_selector) {
1339
-                            case "":
1340
-                                $key = "base";
1341
-                                break;
1342
-
1343
-                            case ":left":
1344
-                            case ":right":
1345
-                            case ":odd":
1346
-                            case ":even":
1347
-                            /** @noinspection PhpMissingBreakStatementInspection */
1348
-                            case ":first":
1349
-                                $key = $page_selector;
1350
-                                break;
1351
-
1352
-                            default:
1353
-                                break 2;
1354
-                        }
1355
-
1356
-                        // Store the style for later...
1357
-                        if (empty($this->_page_styles[$key])) {
1358
-                            $this->_page_styles[$key] = $this->_parse_properties($match[5]);
1359
-                        } else {
1360
-                            $this->_page_styles[$key]->merge($this->_parse_properties($match[5]));
1361
-                        }
1362
-                        break;
1363
-
1364
-                    case "font-face":
1365
-                        $this->_parse_font_face($match[5]);
1366
-                        break;
1367
-
1368
-                    default:
1369
-                        // ignore everything else
1370
-                        break;
1371
-                }
1372
-
1373
-                continue;
1374
-            }
1375
-
1376
-            if ($match[7] !== "") {
1377
-                $this->_parse_sections($match[7]);
1378
-            }
1379
-        }
1380
-    }
1381
-
1382
-    /**
1383
-     * Resolve the given `url()` declaration to an absolute URL.
1384
-     *
1385
-     * @param string|null $val The declaration to resolve in the context of the stylesheet.
1386
-     * @return string The resolved URL, or `none`, if the value is `none`,
1387
-     *         invalid, or points to a non-existent local file.
1388
-     */
1389
-    public function resolve_url($val): string
1390
-    {
1391
-        $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
1392
-        $parsed_url = "none";
1393
-
1394
-        if (empty($val) || $val === "none") {
1395
-            $path = "none";
1396
-        } elseif (mb_strpos($val, "url") === false) {
1397
-            $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none
1398
-        } else {
1399
-            $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val));
1400
-
1401
-            // Resolve the url now in the context of the current stylesheet
1402
-            $path = Helpers::build_url($this->_protocol,
1403
-                $this->_base_host,
1404
-                $this->_base_path,
1405
-                $val);
1406
-            if ($path === null) {
1407
-                $path = "none";
1408
-            }
1409
-        }
1410
-        if ($DEBUGCSS) {
1411
-            $parsed_url = Helpers::explode_url($path);
1412
-            print "<pre>[_image\n";
1413
-            print_r($parsed_url);
1414
-            print $this->_protocol . "\n" . $this->_base_path . "\n" . $path . "\n";
1415
-            print "_image]</pre>";
1416
-        }
1417
-        return $path;
1418
-    }
1419
-
1420
-    /**
1421
-     * parse @import{} sections
1422
-     *
1423
-     * @param string $url the url of the imported CSS file
1424
-     */
1425
-    private function _parse_import($url)
1426
-    {
1427
-        $arr = preg_split("/[\s\n,]/", $url, -1, PREG_SPLIT_NO_EMPTY);
1428
-        $url = array_shift($arr);
1429
-        $accept = false;
1430
-
1431
-        if (count($arr) > 0) {
1432
-            $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
1433
-            $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
1434
-
1435
-            // @import url media_type [media_type...]
1436
-            foreach ($arr as $type) {
1437
-                if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) {
1438
-                    $accept = true;
1439
-                    break;
1440
-                }
1441
-            }
1442
-
1443
-        } else {
1444
-            // unconditional import
1445
-            $accept = true;
1446
-        }
1447
-
1448
-        if ($accept) {
1449
-            // Store our current base url properties in case the new url is elsewhere
1450
-            $protocol = $this->_protocol;
1451
-            $host = $this->_base_host;
1452
-            $path = $this->_base_path;
1453
-
1454
-            // $url = str_replace(array('"',"url", "(", ")"), "", $url);
1455
-            // If the protocol is php, assume that we will import using file://
1456
-            // $url = Helpers::build_url($protocol === "php://" ? "file://" : $protocol, $host, $path, $url);
1457
-            // Above does not work for subfolders and absolute urls.
1458
-            // Todo: As above, do we need to replace php or file to an empty protocol for local files?
1459
-
1460
-            if (($url = $this->resolve_url($url)) !== "none") {
1461
-                $this->load_css_file($url);
1462
-            }
1463
-
1464
-            // Restore the current base url
1465
-            $this->_protocol = $protocol;
1466
-            $this->_base_host = $host;
1467
-            $this->_base_path = $path;
1468
-        }
1469
-    }
1470
-
1471
-    /**
1472
-     * parse @font-face{} sections
1473
-     * http://www.w3.org/TR/css3-fonts/#the-font-face-rule
1474
-     *
1475
-     * @param string $str CSS @font-face rules
1476
-     */
1477
-    private function _parse_font_face($str)
1478
-    {
1479
-        $descriptors = $this->_parse_properties($str);
1480
-
1481
-        preg_match_all("/(url|local)\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\)\s*(format\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\))?/i", $descriptors->src, $src);
1482
-
1483
-        $valid_sources = [];
1484
-        foreach ($src[0] as $i => $value) {
1485
-            $source = [
1486
-                "local" => strtolower($src[1][$i]) === "local",
1487
-                "uri" => $src[2][$i],
1488
-                "format" => strtolower($src[4][$i]),
1489
-                "path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]),
1490
-            ];
1491
-
1492
-            if (!$source["local"] && in_array($source["format"], ["", "truetype"]) && $source["path"] !== null) {
1493
-                $valid_sources[] = $source;
1494
-            }
1495
-        }
1496
-
1497
-        // No valid sources
1498
-        if (empty($valid_sources)) {
1499
-            return;
1500
-        }
1501
-
1502
-        $style = [
1503
-            "family" => $descriptors->get_font_family_raw(),
1504
-            "weight" => $descriptors->font_weight,
1505
-            "style" => $descriptors->font_style,
1506
-        ];
1507
-
1508
-        $this->getFontMetrics()->registerFont($style, $valid_sources[0]["path"], $this->_dompdf->getHttpContext());
1509
-    }
1510
-
1511
-    /**
1512
-     * parse regular CSS blocks
1513
-     *
1514
-     * _parse_properties() creates a new Style object based on the provided
1515
-     * CSS rules.
1516
-     *
1517
-     * @param string $str CSS rules
1518
-     * @return Style
1519
-     */
1520
-    private function _parse_properties($str)
1521
-    {
1522
-        $properties = preg_split("/;(?=(?:[^\(]*\([^\)]*\))*(?![^\)]*\)))/", $str);
1523
-        $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
1524
-
1525
-        if ($DEBUGCSS) {
1526
-            print '[_parse_properties';
1527
-        }
1528
-
1529
-        // Create the style
1530
-        $style = new Style($this, Stylesheet::ORIG_AUTHOR);
1531
-
1532
-        foreach ($properties as $prop) {
1533
-            // If the $prop contains an url, the regex may be wrong
1534
-            // @todo: fix the regex so that it works every time
1535
-            /*if (strpos($prop, "url(") === false) {
879
+		// Trim the trailing '/' from the query
880
+		if (mb_strlen($query) > 2) {
881
+			$query = rtrim($query, "/");
882
+		}
883
+
884
+		return ['query' => $query, 'pseudo_elements' => $pseudo_elements];
885
+	}
886
+
887
+	/**
888
+	 * https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb
889
+	 *
890
+	 * @param string $expr
891
+	 * @param bool $last
892
+	 *
893
+	 * @return string
894
+	 */
895
+	protected function _selector_an_plus_b(string $expr, bool $last = false): string
896
+	{
897
+		$expr = preg_replace("/\s/", "", $expr);
898
+		if (!preg_match("/^(?P<a>-?[0-9]*)?n(?P<b>[-+]?[0-9]+)?$/", $expr, $matches)) {
899
+			return "false()";
900
+		}
901
+
902
+		$a = (isset($matches["a"]) && $matches["a"] !== "") ? ($matches["a"] !== "-" ? intval($matches["a"]) : -1) : 1;
903
+		$b = (isset($matches["b"]) && $matches["b"] !== "") ? intval($matches["b"]) : 0;
904
+
905
+		$position = $last ? "(last()-position()+1)" : "position()";
906
+
907
+		if ($b == 0) {
908
+			return "($position mod $a) = 0";
909
+		} else {
910
+			$compare = ($a < 0) ? "<=" : ">=";
911
+			$b2 = -$b;
912
+			if ($b2 >= 0) {
913
+				$b2 = "+$b2";
914
+			}
915
+			return "($position $compare $b) and ((($position $b2) mod " . abs($a) . ") = 0)";
916
+		}
917
+	}
918
+
919
+	/**
920
+	 * applies all current styles to a particular document tree
921
+	 *
922
+	 * apply_styles() applies all currently loaded styles to the provided
923
+	 * {@link FrameTree}.  Aside from parsing CSS, this is the main purpose
924
+	 * of this class.
925
+	 *
926
+	 * @param \Dompdf\Frame\FrameTree $tree
927
+	 */
928
+	function apply_styles(FrameTree $tree)
929
+	{
930
+		// Use XPath to select nodes.  This would be easier if we could attach
931
+		// Frame objects directly to DOMNodes using the setUserData() method, but
932
+		// we can't do that just yet.  Instead, we set a _node attribute_ in
933
+		// Frame->set_id() and use that as a handle on the Frame object via
934
+		// FrameTree::$_registry.
935
+
936
+		// We create a scratch array of styles indexed by frame id.  Once all
937
+		// styles have been assigned, we order the cached styles by specificity
938
+		// and create a final style object to assign to the frame.
939
+
940
+		// FIXME: this is not particularly robust...
941
+
942
+		$styles = [];
943
+		$xp = new DOMXPath($tree->get_dom());
944
+		$DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
945
+
946
+		// Add generated content
947
+		foreach ($this->_styles as $selector => $selector_styles) {
948
+			/** @var Style $style */
949
+			foreach ($selector_styles as $style) {
950
+				if (strpos($selector, ":before") === false && strpos($selector, ":after") === false) {
951
+					continue;
952
+				}
953
+
954
+				$query = $this->_css_selector_to_xpath($selector, true);
955
+
956
+				// Retrieve the nodes, limit to body for generated content
957
+				//TODO: If we use a context node can we remove the leading dot?
958
+				$nodes = @$xp->query('.' . $query["query"]);
959
+				if ($nodes === false) {
960
+					Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
961
+					continue;
962
+				}
963
+
964
+				/** @var \DOMElement $node */
965
+				foreach ($nodes as $node) {
966
+					// Only DOMElements get styles
967
+					if ($node->nodeType != XML_ELEMENT_NODE) {
968
+						continue;
969
+					}
970
+
971
+					foreach (array_keys($query["pseudo_elements"], true, true) as $pos) {
972
+						// Do not add a new pseudo element if another one already matched
973
+						if ($node->hasAttribute("dompdf_{$pos}_frame_id")) {
974
+							continue;
975
+						}
976
+
977
+						$content = $style->get_specified("content");
978
+
979
+						// Do not create non-displayed before/after pseudo elements
980
+						// https://www.w3.org/TR/CSS21/generate.html#content
981
+						// https://www.w3.org/TR/CSS21/generate.html#undisplayed-counters
982
+						if ($content === "normal" || $content === "none") {
983
+							continue;
984
+						}
985
+
986
+						if (($src = $this->resolve_url($content)) !== "none") {
987
+							$new_node = $node->ownerDocument->createElement("img_generated");
988
+							$new_node->setAttribute("src", $src);
989
+						} else {
990
+							$new_node = $node->ownerDocument->createElement("dompdf_generated");
991
+						}
992
+
993
+						$new_node->setAttribute($pos, $pos);
994
+						$new_frame_id = $tree->insert_node($node, $new_node, $pos);
995
+						$node->setAttribute("dompdf_{$pos}_frame_id", $new_frame_id);
996
+					}
997
+				}
998
+			}
999
+		}
1000
+
1001
+		// Apply all styles in stylesheet
1002
+		foreach ($this->_styles as $selector => $selector_styles) {
1003
+			/** @var Style $style */
1004
+			foreach ($selector_styles as $style) {
1005
+				$query = $this->_css_selector_to_xpath($selector);
1006
+
1007
+				// Retrieve the nodes
1008
+				$nodes = @$xp->query($query["query"]);
1009
+				if ($nodes === false) {
1010
+					Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
1011
+					continue;
1012
+				}
1013
+
1014
+				$spec = $this->_specificity($selector, $style->get_origin());
1015
+
1016
+				foreach ($nodes as $node) {
1017
+					// Retrieve the node id
1018
+					// Only DOMElements get styles
1019
+					if ($node->nodeType != XML_ELEMENT_NODE) {
1020
+						continue;
1021
+					}
1022
+
1023
+					$id = $node->getAttribute("frame_id");
1024
+
1025
+					// Assign the current style to the scratch array
1026
+					$styles[$id][$spec][] = $style;
1027
+				}
1028
+			}
1029
+		}
1030
+
1031
+		// Set the page width, height, and orientation based on the canvas paper size
1032
+		$canvas = $this->_dompdf->getCanvas();
1033
+		$paper_width = $canvas->get_width();
1034
+		$paper_height = $canvas->get_height();
1035
+		$paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
1036
+
1037
+		if ($this->_page_styles["base"] && is_array($this->_page_styles["base"]->size)) {
1038
+			$paper_width = $this->_page_styles['base']->size[0];
1039
+			$paper_height = $this->_page_styles['base']->size[1];
1040
+			$paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
1041
+		}
1042
+
1043
+		// Now create the styles and assign them to the appropriate frames. (We
1044
+		// iterate over the tree using an implicit FrameTree iterator.)
1045
+		$root_flg = false;
1046
+		foreach ($tree as $frame) {
1047
+			// Helpers::pre_r($frame->get_node()->nodeName . ":");
1048
+			if (!$root_flg && $this->_page_styles["base"]) {
1049
+				$style = $this->_page_styles["base"];
1050
+			} else {
1051
+				$style = $this->create_style();
1052
+			}
1053
+
1054
+			// Find nearest DOMElement parent
1055
+			$p = $frame;
1056
+			while ($p = $p->get_parent()) {
1057
+				if ($p->get_node()->nodeType === XML_ELEMENT_NODE) {
1058
+					break;
1059
+				}
1060
+			}
1061
+
1062
+			// Styles can only be applied directly to DOMElements; anonymous
1063
+			// frames inherit from their parent
1064
+			if ($frame->get_node()->nodeType !== XML_ELEMENT_NODE) {
1065
+				$style->inherit($p ? $p->get_style() : null);
1066
+				$frame->set_style($style);
1067
+				continue;
1068
+			}
1069
+
1070
+			$id = $frame->get_id();
1071
+
1072
+			// Handle HTML 4.0 attributes
1073
+			AttributeTranslator::translate_attributes($frame);
1074
+			if (($str = $frame->get_node()->getAttribute(AttributeTranslator::$_style_attr)) !== "") {
1075
+				$styles[$id][self::SPEC_NON_CSS][] = $this->_parse_properties($str);
1076
+			}
1077
+
1078
+			// Locate any additional style attributes
1079
+			if (($str = $frame->get_node()->getAttribute("style")) !== "") {
1080
+				// Destroy CSS comments
1081
+				$str = preg_replace("'/\*.*?\*/'si", "", $str);
1082
+
1083
+				$spec = $this->_specificity("!attr", self::ORIG_AUTHOR);
1084
+				$styles[$id][$spec][] = $this->_parse_properties($str);
1085
+			}
1086
+
1087
+			// Grab the applicable styles
1088
+			if (isset($styles[$id])) {
1089
+
1090
+				/** @var array[][] $applied_styles */
1091
+				$applied_styles = $styles[$id];
1092
+
1093
+				// Sort by specificity
1094
+				ksort($applied_styles);
1095
+
1096
+				if ($DEBUGCSS) {
1097
+					$debug_nodename = $frame->get_node()->nodeName;
1098
+					print "<pre>\n$debug_nodename [\n";
1099
+					foreach ($applied_styles as $spec => $arr) {
1100
+						printf("  specificity 0x%08x\n", $spec);
1101
+						/** @var Style $s */
1102
+						foreach ($arr as $s) {
1103
+							print "  [\n";
1104
+							$s->debug_print();
1105
+							print "  ]\n";
1106
+						}
1107
+					}
1108
+				}
1109
+
1110
+				// Merge the new styles with the inherited styles
1111
+				$acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
1112
+				$acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
1113
+				foreach ($applied_styles as $arr) {
1114
+					/** @var Style $s */
1115
+					foreach ($arr as $s) {
1116
+						$media_queries = $s->get_media_queries();
1117
+						foreach ($media_queries as $media_query) {
1118
+							list($media_query_feature, $media_query_value) = $media_query;
1119
+							// if any of the Style's media queries fail then do not apply the style
1120
+							//TODO: When the media query logic is fully developed we should not apply the Style when any of the media queries fail or are bad, per https://www.w3.org/TR/css3-mediaqueries/#error-handling
1121
+							if (in_array($media_query_feature, self::$VALID_MEDIA_TYPES)) {
1122
+								if ((strlen($media_query_feature) === 0 && !in_array($media_query, $acceptedmedia)) || (in_array($media_query, $acceptedmedia) && $media_query_value == "not")) {
1123
+									continue (3);
1124
+								}
1125
+							} else {
1126
+								switch ($media_query_feature) {
1127
+									case "height":
1128
+										if ($paper_height !== (float)$style->length_in_pt($media_query_value)) {
1129
+											continue (3);
1130
+										}
1131
+										break;
1132
+									case "min-height":
1133
+										if ($paper_height < (float)$style->length_in_pt($media_query_value)) {
1134
+											continue (3);
1135
+										}
1136
+										break;
1137
+									case "max-height":
1138
+										if ($paper_height > (float)$style->length_in_pt($media_query_value)) {
1139
+											continue (3);
1140
+										}
1141
+										break;
1142
+									case "width":
1143
+										if ($paper_width !== (float)$style->length_in_pt($media_query_value)) {
1144
+											continue (3);
1145
+										}
1146
+										break;
1147
+									case "min-width":
1148
+										//if (min($paper_width, $media_query_width) === $paper_width) {
1149
+										if ($paper_width < (float)$style->length_in_pt($media_query_value)) {
1150
+											continue (3);
1151
+										}
1152
+										break;
1153
+									case "max-width":
1154
+										//if (max($paper_width, $media_query_width) === $paper_width) {
1155
+										if ($paper_width > (float)$style->length_in_pt($media_query_value)) {
1156
+											continue (3);
1157
+										}
1158
+										break;
1159
+									case "orientation":
1160
+										if ($paper_orientation !== $media_query_value) {
1161
+											continue (3);
1162
+										}
1163
+										break;
1164
+									default:
1165
+										Helpers::record_warnings(E_USER_WARNING, "Unknown media query: $media_query_feature", __FILE__, __LINE__);
1166
+										break;
1167
+								}
1168
+							}
1169
+						}
1170
+
1171
+						$style->merge($s);
1172
+					}
1173
+				}
1174
+			}
1175
+
1176
+			// Handle inheritance
1177
+			if ($p && $DEBUGCSS) {
1178
+				print "  inherit [\n";
1179
+				$p->get_style()->debug_print();
1180
+				print "  ]\n";
1181
+			}
1182
+
1183
+			$style->inherit($p ? $p->get_style() : null);
1184
+
1185
+			if ($DEBUGCSS) {
1186
+				print "  DomElementStyle [\n";
1187
+				$style->debug_print();
1188
+				print "  ]\n";
1189
+				print "]\n</pre>";
1190
+			}
1191
+
1192
+			$style->clear_important();
1193
+			$frame->set_style($style);
1194
+
1195
+			if (!$root_flg && $this->_page_styles["base"]) {
1196
+				$root_flg = true;
1197
+
1198
+				// set the page width, height, and orientation based on the parsed page style
1199
+				if ($style->size !== "auto") {
1200
+					list($paper_width, $paper_height) = $style->size;
1201
+				}
1202
+				$paper_width = $paper_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->margin_right);
1203
+				$paper_height = $paper_height - (float)$style->length_in_pt($style->margin_top) - (float)$style->length_in_pt($style->margin_bottom);
1204
+				$paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
1205
+			}
1206
+		}
1207
+
1208
+		// We're done!  Clean out the registry of all styles since we
1209
+		// won't be needing this later.
1210
+		foreach (array_keys($this->_styles) as $key) {
1211
+			$this->_styles[$key] = null;
1212
+			unset($this->_styles[$key]);
1213
+		}
1214
+	}
1215
+
1216
+	/**
1217
+	 * parse a CSS string using a regex parser
1218
+	 * Called by {@link Stylesheet::parse_css()}
1219
+	 *
1220
+	 * @param string $str
1221
+	 *
1222
+	 * @throws Exception
1223
+	 */
1224
+	private function _parse_css($str)
1225
+	{
1226
+		$str = trim($str);
1227
+
1228
+		// Destroy comments and remove HTML comments
1229
+		$css = preg_replace([
1230
+			"'/\*.*?\*/'si",
1231
+			"/^<!--/",
1232
+			"/-->$/"
1233
+		], "", $str);
1234
+
1235
+		// FIXME: handle '{' within strings, e.g. [attr="string {}"]
1236
+
1237
+		// Something more legible:
1238
+		$re =
1239
+			"/\s*                                   # Skip leading whitespace                             \n" .
1240
+			"( @([^\s{]+)\s*([^{;]*) (?:;|({)) )?   # Match @rules followed by ';' or '{'                 \n" .
1241
+			"(?(1)                                  # Only parse sub-sections if we're in an @rule...     \n" .
1242
+			"  (?(4)                                # ...and if there was a leading '{'                   \n" .
1243
+			"    \s*( (?:(?>[^{}]+) ({)?            # Parse rulesets and individual @page rules           \n" .
1244
+			"            (?(6) (?>[^}]*) }) \s*)+?                                                        \n" .
1245
+			"       )                                                                                     \n" .
1246
+			"   })                                  # Balancing '}'                                       \n" .
1247
+			"|                                      # Branch to match regular rules (not preceded by '@') \n" .
1248
+			"([^{]*{[^}]*}))                        # Parse normal rulesets                               \n" .
1249
+			"/xs";
1250
+
1251
+		if (preg_match_all($re, $css, $matches, PREG_SET_ORDER) === false) {
1252
+			// An error occurred
1253
+			throw new Exception("Error parsing css file: preg_match_all() failed.");
1254
+		}
1255
+
1256
+		// After matching, the array indices are set as follows:
1257
+		//
1258
+		// [0] => complete text of match
1259
+		// [1] => contains '@import ...;' or '@media {' if applicable
1260
+		// [2] => text following @ for cases where [1] is set
1261
+		// [3] => media types or full text following '@import ...;'
1262
+		// [4] => '{', if present
1263
+		// [5] => rulesets within media rules
1264
+		// [6] => '{', within media rules
1265
+		// [7] => individual rules, outside of media rules
1266
+		//
1267
+
1268
+		$media_query_regex = "/(?:((only|not)?\s*(" . implode("|", self::$VALID_MEDIA_TYPES) . "))|(\s*\(\s*((?:(min|max)-)?([\w\-]+))\s*(?:\:\s*(.*?)\s*)?\)))/isx";
1269
+
1270
+		//Helpers::pre_r($matches);
1271
+		foreach ($matches as $match) {
1272
+			$match[2] = trim($match[2]);
1273
+
1274
+			if ($match[2] !== "") {
1275
+				// Handle @rules
1276
+				switch ($match[2]) {
1277
+
1278
+					case "import":
1279
+						$this->_parse_import($match[3]);
1280
+						break;
1281
+
1282
+					case "media":
1283
+						$acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
1284
+						$acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
1285
+
1286
+						$media_queries = preg_split("/\s*,\s*/", mb_strtolower(trim($match[3])));
1287
+						foreach ($media_queries as $media_query) {
1288
+							if (in_array($media_query, $acceptedmedia)) {
1289
+								//if we have a media type match go ahead and parse the stylesheet
1290
+								$this->_parse_sections($match[5]);
1291
+								break;
1292
+							} elseif (!in_array($media_query, self::$VALID_MEDIA_TYPES)) {
1293
+								// otherwise conditionally parse the stylesheet assuming there are parseable media queries
1294
+								if (preg_match_all($media_query_regex, $media_query, $media_query_matches, PREG_SET_ORDER) !== false) {
1295
+									$mq = [];
1296
+									foreach ($media_query_matches as $media_query_match) {
1297
+										if (empty($media_query_match[1]) === false) {
1298
+											$media_query_feature = strtolower($media_query_match[3]);
1299
+											$media_query_value = strtolower($media_query_match[2]);
1300
+											$mq[] = [$media_query_feature, $media_query_value];
1301
+										} elseif (empty($media_query_match[4]) === false) {
1302
+											$media_query_feature = strtolower($media_query_match[5]);
1303
+											$media_query_value = (array_key_exists(8, $media_query_match) ? strtolower($media_query_match[8]) : null);
1304
+											$mq[] = [$media_query_feature, $media_query_value];
1305
+										}
1306
+									}
1307
+									$this->_parse_sections($match[5], $mq);
1308
+									break;
1309
+								}
1310
+							}
1311
+						}
1312
+						break;
1313
+
1314
+					case "page":
1315
+						//This handles @page to be applied to page oriented media
1316
+						//Note: This has a reduced syntax:
1317
+						//@page { margin:1cm; color:blue; }
1318
+						//Not a sequence of styles like a full.css, but only the properties
1319
+						//of a single style, which is applied to the very first "root" frame before
1320
+						//processing other styles of the frame.
1321
+						//Working properties:
1322
+						// margin (for margin around edge of paper)
1323
+						// font-family (default font of pages)
1324
+						// color (default text color of pages)
1325
+						//Non working properties:
1326
+						// border
1327
+						// padding
1328
+						// background-color
1329
+						//Todo:Reason is unknown
1330
+						//Other properties (like further font or border attributes) not tested.
1331
+						//If a border or background color around each paper sheet is desired,
1332
+						//assign it to the <body> tag, possibly only for the css of the correct media type.
1333
+
1334
+						// If the page has a name, skip the style.
1335
+						$page_selector = trim($match[3]);
1336
+
1337
+						$key = null;
1338
+						switch ($page_selector) {
1339
+							case "":
1340
+								$key = "base";
1341
+								break;
1342
+
1343
+							case ":left":
1344
+							case ":right":
1345
+							case ":odd":
1346
+							case ":even":
1347
+							/** @noinspection PhpMissingBreakStatementInspection */
1348
+							case ":first":
1349
+								$key = $page_selector;
1350
+								break;
1351
+
1352
+							default:
1353
+								break 2;
1354
+						}
1355
+
1356
+						// Store the style for later...
1357
+						if (empty($this->_page_styles[$key])) {
1358
+							$this->_page_styles[$key] = $this->_parse_properties($match[5]);
1359
+						} else {
1360
+							$this->_page_styles[$key]->merge($this->_parse_properties($match[5]));
1361
+						}
1362
+						break;
1363
+
1364
+					case "font-face":
1365
+						$this->_parse_font_face($match[5]);
1366
+						break;
1367
+
1368
+					default:
1369
+						// ignore everything else
1370
+						break;
1371
+				}
1372
+
1373
+				continue;
1374
+			}
1375
+
1376
+			if ($match[7] !== "") {
1377
+				$this->_parse_sections($match[7]);
1378
+			}
1379
+		}
1380
+	}
1381
+
1382
+	/**
1383
+	 * Resolve the given `url()` declaration to an absolute URL.
1384
+	 *
1385
+	 * @param string|null $val The declaration to resolve in the context of the stylesheet.
1386
+	 * @return string The resolved URL, or `none`, if the value is `none`,
1387
+	 *         invalid, or points to a non-existent local file.
1388
+	 */
1389
+	public function resolve_url($val): string
1390
+	{
1391
+		$DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
1392
+		$parsed_url = "none";
1393
+
1394
+		if (empty($val) || $val === "none") {
1395
+			$path = "none";
1396
+		} elseif (mb_strpos($val, "url") === false) {
1397
+			$path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none
1398
+		} else {
1399
+			$val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val));
1400
+
1401
+			// Resolve the url now in the context of the current stylesheet
1402
+			$path = Helpers::build_url($this->_protocol,
1403
+				$this->_base_host,
1404
+				$this->_base_path,
1405
+				$val);
1406
+			if ($path === null) {
1407
+				$path = "none";
1408
+			}
1409
+		}
1410
+		if ($DEBUGCSS) {
1411
+			$parsed_url = Helpers::explode_url($path);
1412
+			print "<pre>[_image\n";
1413
+			print_r($parsed_url);
1414
+			print $this->_protocol . "\n" . $this->_base_path . "\n" . $path . "\n";
1415
+			print "_image]</pre>";
1416
+		}
1417
+		return $path;
1418
+	}
1419
+
1420
+	/**
1421
+	 * parse @import{} sections
1422
+	 *
1423
+	 * @param string $url the url of the imported CSS file
1424
+	 */
1425
+	private function _parse_import($url)
1426
+	{
1427
+		$arr = preg_split("/[\s\n,]/", $url, -1, PREG_SPLIT_NO_EMPTY);
1428
+		$url = array_shift($arr);
1429
+		$accept = false;
1430
+
1431
+		if (count($arr) > 0) {
1432
+			$acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
1433
+			$acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
1434
+
1435
+			// @import url media_type [media_type...]
1436
+			foreach ($arr as $type) {
1437
+				if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) {
1438
+					$accept = true;
1439
+					break;
1440
+				}
1441
+			}
1442
+
1443
+		} else {
1444
+			// unconditional import
1445
+			$accept = true;
1446
+		}
1447
+
1448
+		if ($accept) {
1449
+			// Store our current base url properties in case the new url is elsewhere
1450
+			$protocol = $this->_protocol;
1451
+			$host = $this->_base_host;
1452
+			$path = $this->_base_path;
1453
+
1454
+			// $url = str_replace(array('"',"url", "(", ")"), "", $url);
1455
+			// If the protocol is php, assume that we will import using file://
1456
+			// $url = Helpers::build_url($protocol === "php://" ? "file://" : $protocol, $host, $path, $url);
1457
+			// Above does not work for subfolders and absolute urls.
1458
+			// Todo: As above, do we need to replace php or file to an empty protocol for local files?
1459
+
1460
+			if (($url = $this->resolve_url($url)) !== "none") {
1461
+				$this->load_css_file($url);
1462
+			}
1463
+
1464
+			// Restore the current base url
1465
+			$this->_protocol = $protocol;
1466
+			$this->_base_host = $host;
1467
+			$this->_base_path = $path;
1468
+		}
1469
+	}
1470
+
1471
+	/**
1472
+	 * parse @font-face{} sections
1473
+	 * http://www.w3.org/TR/css3-fonts/#the-font-face-rule
1474
+	 *
1475
+	 * @param string $str CSS @font-face rules
1476
+	 */
1477
+	private function _parse_font_face($str)
1478
+	{
1479
+		$descriptors = $this->_parse_properties($str);
1480
+
1481
+		preg_match_all("/(url|local)\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\)\s*(format\s*\(\s*[\"\']?([^\"\'\)]+)[\"\']?\s*\))?/i", $descriptors->src, $src);
1482
+
1483
+		$valid_sources = [];
1484
+		foreach ($src[0] as $i => $value) {
1485
+			$source = [
1486
+				"local" => strtolower($src[1][$i]) === "local",
1487
+				"uri" => $src[2][$i],
1488
+				"format" => strtolower($src[4][$i]),
1489
+				"path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]),
1490
+			];
1491
+
1492
+			if (!$source["local"] && in_array($source["format"], ["", "truetype"]) && $source["path"] !== null) {
1493
+				$valid_sources[] = $source;
1494
+			}
1495
+		}
1496
+
1497
+		// No valid sources
1498
+		if (empty($valid_sources)) {
1499
+			return;
1500
+		}
1501
+
1502
+		$style = [
1503
+			"family" => $descriptors->get_font_family_raw(),
1504
+			"weight" => $descriptors->font_weight,
1505
+			"style" => $descriptors->font_style,
1506
+		];
1507
+
1508
+		$this->getFontMetrics()->registerFont($style, $valid_sources[0]["path"], $this->_dompdf->getHttpContext());
1509
+	}
1510
+
1511
+	/**
1512
+	 * parse regular CSS blocks
1513
+	 *
1514
+	 * _parse_properties() creates a new Style object based on the provided
1515
+	 * CSS rules.
1516
+	 *
1517
+	 * @param string $str CSS rules
1518
+	 * @return Style
1519
+	 */
1520
+	private function _parse_properties($str)
1521
+	{
1522
+		$properties = preg_split("/;(?=(?:[^\(]*\([^\)]*\))*(?![^\)]*\)))/", $str);
1523
+		$DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
1524
+
1525
+		if ($DEBUGCSS) {
1526
+			print '[_parse_properties';
1527
+		}
1528
+
1529
+		// Create the style
1530
+		$style = new Style($this, Stylesheet::ORIG_AUTHOR);
1531
+
1532
+		foreach ($properties as $prop) {
1533
+			// If the $prop contains an url, the regex may be wrong
1534
+			// @todo: fix the regex so that it works every time
1535
+			/*if (strpos($prop, "url(") === false) {
1536 1536
               if (preg_match("/([a-z-]+)\s*:\s*[^:]+$/i", $prop, $m))
1537 1537
                 $prop = $m[0];
1538 1538
             }*/
1539 1539
 
1540
-            //A css property can have " ! important" appended (whitespace optional)
1541
-            //strip this off to decode core of the property correctly.
1540
+			//A css property can have " ! important" appended (whitespace optional)
1541
+			//strip this off to decode core of the property correctly.
1542 1542
 
1543
-            /* Instead of short code, prefer the typical case with fast code
1543
+			/* Instead of short code, prefer the typical case with fast code
1544 1544
           $important = preg_match("/(.*?)!\s*important/",$prop,$match);
1545 1545
             if ( $important ) {
1546 1546
               $prop = $match[1];
1547 1547
             }
1548 1548
             $prop = trim($prop);
1549 1549
             */
1550
-            if ($DEBUGCSS) print '(';
1551
-
1552
-            $important = false;
1553
-            $prop = trim($prop);
1554
-
1555
-            if (substr($prop, -9) === 'important') {
1556
-                $prop_tmp = rtrim(substr($prop, 0, -9));
1557
-
1558
-                if (substr($prop_tmp, -1) === '!') {
1559
-                    $prop = rtrim(substr($prop_tmp, 0, -1));
1560
-                    $important = true;
1561
-                }
1562
-            }
1563
-
1564
-            if ($prop === "") {
1565
-                if ($DEBUGCSS) print 'empty)';
1566
-                continue;
1567
-            }
1568
-
1569
-            $i = mb_strpos($prop, ":");
1570
-            if ($i === false) {
1571
-                if ($DEBUGCSS) print 'novalue' . $prop . ')';
1572
-                continue;
1573
-            }
1574
-
1575
-            $prop_name = rtrim(mb_strtolower(mb_substr($prop, 0, $i)));
1576
-            $value = ltrim(mb_substr($prop, $i + 1));
1577
-
1578
-            if ($DEBUGCSS) print $prop_name . ':=' . $value . ($important ? '!IMPORTANT' : '') . ')';
1579
-
1580
-            $style->set_prop($prop_name, $value, $important, false);
1581
-        }
1582
-        if ($DEBUGCSS) print '_parse_properties]';
1583
-
1584
-        return $style;
1585
-    }
1586
-
1587
-    /**
1588
-     * parse selector + rulesets
1589
-     *
1590
-     * @param string $str CSS selectors and rulesets
1591
-     * @param array $media_queries
1592
-     */
1593
-    private function _parse_sections($str, $media_queries = [])
1594
-    {
1595
-        // Pre-process selectors: collapse all whitespace and strip whitespace
1596
-        // around '>', '.', ':', '+', '~', '#'
1597
-        $patterns = ["/\s+/", "/\s+([>.:+~#])\s+/"];
1598
-        $replacements = [" ", "\\1"];
1599
-        $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
1600
-
1601
-        $sections = explode("}", $str);
1602
-        if ($DEBUGCSS) print '[_parse_sections';
1603
-        foreach ($sections as $sect) {
1604
-            $i = mb_strpos($sect, "{");
1605
-            if ($i === false) { continue; }
1606
-
1607
-            if ($DEBUGCSS) print '[section';
1608
-
1609
-            $selector_str = preg_replace($patterns, $replacements, mb_substr($sect, 0, $i));
1610
-            $selectors = preg_split("/,(?![^\(]*\))/", $selector_str, 0, PREG_SPLIT_NO_EMPTY);
1611
-            $style = $this->_parse_properties(trim(mb_substr($sect, $i + 1)));
1612
-
1613
-            // Assign it to the selected elements
1614
-            foreach ($selectors as $selector) {
1615
-                $selector = trim($selector);
1616
-
1617
-                if ($selector == "") {
1618
-                    if ($DEBUGCSS) print '#empty#';
1619
-                    continue;
1620
-                }
1621
-                if ($DEBUGCSS) print '#' . $selector . '#';
1622
-                //if ($DEBUGCSS) { if (strpos($selector,'p') !== false) print '!!!p!!!#'; }
1623
-
1624
-                //FIXME: tag the selector with a hash of the media query to separate it from non-conditional styles (?), xpath comments are probably not what we want to do here
1625
-                if (count($media_queries) > 0) {
1626
-                    $style->set_media_queries($media_queries);
1627
-                }
1628
-                $this->add_style($selector, $style);
1629
-            }
1630
-
1631
-            if ($DEBUGCSS) {
1632
-                print 'section]';
1633
-            }
1634
-        }
1635
-
1636
-        if ($DEBUGCSS) {
1637
-            print "_parse_sections]\n";
1638
-        }
1639
-    }
1640
-
1641
-    /**
1642
-     * @return string
1643
-     */
1644
-    public function getDefaultStylesheet()
1645
-    {
1646
-        $options = $this->_dompdf->getOptions();
1647
-        $rootDir = realpath($options->getRootDir());
1648
-        return Helpers::build_url("file://", "", $rootDir, $rootDir . self::DEFAULT_STYLESHEET);
1649
-    }
1650
-
1651
-    /**
1652
-     * @param FontMetrics $fontMetrics
1653
-     * @return $this
1654
-     */
1655
-    public function setFontMetrics(FontMetrics $fontMetrics)
1656
-    {
1657
-        $this->fontMetrics = $fontMetrics;
1658
-        return $this;
1659
-    }
1660
-
1661
-    /**
1662
-     * @return FontMetrics
1663
-     */
1664
-    public function getFontMetrics()
1665
-    {
1666
-        return $this->fontMetrics;
1667
-    }
1668
-
1669
-    /**
1670
-     * dumps the entire stylesheet as a string
1671
-     *
1672
-     * Generates a string of each selector and associated style in the
1673
-     * Stylesheet.  Useful for debugging.
1674
-     *
1675
-     * @return string
1676
-     */
1677
-    function __toString()
1678
-    {
1679
-        $str = "";
1680
-        foreach ($this->_styles as $selector => $selector_styles) {
1681
-            /** @var Style $style */
1682
-            foreach ($selector_styles as $style) {
1683
-                $str .= "$selector => " . $style->__toString() . "\n";
1684
-            }
1685
-        }
1686
-
1687
-        return $str;
1688
-    }
1550
+			if ($DEBUGCSS) print '(';
1551
+
1552
+			$important = false;
1553
+			$prop = trim($prop);
1554
+
1555
+			if (substr($prop, -9) === 'important') {
1556
+				$prop_tmp = rtrim(substr($prop, 0, -9));
1557
+
1558
+				if (substr($prop_tmp, -1) === '!') {
1559
+					$prop = rtrim(substr($prop_tmp, 0, -1));
1560
+					$important = true;
1561
+				}
1562
+			}
1563
+
1564
+			if ($prop === "") {
1565
+				if ($DEBUGCSS) print 'empty)';
1566
+				continue;
1567
+			}
1568
+
1569
+			$i = mb_strpos($prop, ":");
1570
+			if ($i === false) {
1571
+				if ($DEBUGCSS) print 'novalue' . $prop . ')';
1572
+				continue;
1573
+			}
1574
+
1575
+			$prop_name = rtrim(mb_strtolower(mb_substr($prop, 0, $i)));
1576
+			$value = ltrim(mb_substr($prop, $i + 1));
1577
+
1578
+			if ($DEBUGCSS) print $prop_name . ':=' . $value . ($important ? '!IMPORTANT' : '') . ')';
1579
+
1580
+			$style->set_prop($prop_name, $value, $important, false);
1581
+		}
1582
+		if ($DEBUGCSS) print '_parse_properties]';
1583
+
1584
+		return $style;
1585
+	}
1586
+
1587
+	/**
1588
+	 * parse selector + rulesets
1589
+	 *
1590
+	 * @param string $str CSS selectors and rulesets
1591
+	 * @param array $media_queries
1592
+	 */
1593
+	private function _parse_sections($str, $media_queries = [])
1594
+	{
1595
+		// Pre-process selectors: collapse all whitespace and strip whitespace
1596
+		// around '>', '.', ':', '+', '~', '#'
1597
+		$patterns = ["/\s+/", "/\s+([>.:+~#])\s+/"];
1598
+		$replacements = [" ", "\\1"];
1599
+		$DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
1600
+
1601
+		$sections = explode("}", $str);
1602
+		if ($DEBUGCSS) print '[_parse_sections';
1603
+		foreach ($sections as $sect) {
1604
+			$i = mb_strpos($sect, "{");
1605
+			if ($i === false) { continue; }
1606
+
1607
+			if ($DEBUGCSS) print '[section';
1608
+
1609
+			$selector_str = preg_replace($patterns, $replacements, mb_substr($sect, 0, $i));
1610
+			$selectors = preg_split("/,(?![^\(]*\))/", $selector_str, 0, PREG_SPLIT_NO_EMPTY);
1611
+			$style = $this->_parse_properties(trim(mb_substr($sect, $i + 1)));
1612
+
1613
+			// Assign it to the selected elements
1614
+			foreach ($selectors as $selector) {
1615
+				$selector = trim($selector);
1616
+
1617
+				if ($selector == "") {
1618
+					if ($DEBUGCSS) print '#empty#';
1619
+					continue;
1620
+				}
1621
+				if ($DEBUGCSS) print '#' . $selector . '#';
1622
+				//if ($DEBUGCSS) { if (strpos($selector,'p') !== false) print '!!!p!!!#'; }
1623
+
1624
+				//FIXME: tag the selector with a hash of the media query to separate it from non-conditional styles (?), xpath comments are probably not what we want to do here
1625
+				if (count($media_queries) > 0) {
1626
+					$style->set_media_queries($media_queries);
1627
+				}
1628
+				$this->add_style($selector, $style);
1629
+			}
1630
+
1631
+			if ($DEBUGCSS) {
1632
+				print 'section]';
1633
+			}
1634
+		}
1635
+
1636
+		if ($DEBUGCSS) {
1637
+			print "_parse_sections]\n";
1638
+		}
1639
+	}
1640
+
1641
+	/**
1642
+	 * @return string
1643
+	 */
1644
+	public function getDefaultStylesheet()
1645
+	{
1646
+		$options = $this->_dompdf->getOptions();
1647
+		$rootDir = realpath($options->getRootDir());
1648
+		return Helpers::build_url("file://", "", $rootDir, $rootDir . self::DEFAULT_STYLESHEET);
1649
+	}
1650
+
1651
+	/**
1652
+	 * @param FontMetrics $fontMetrics
1653
+	 * @return $this
1654
+	 */
1655
+	public function setFontMetrics(FontMetrics $fontMetrics)
1656
+	{
1657
+		$this->fontMetrics = $fontMetrics;
1658
+		return $this;
1659
+	}
1660
+
1661
+	/**
1662
+	 * @return FontMetrics
1663
+	 */
1664
+	public function getFontMetrics()
1665
+	{
1666
+		return $this->fontMetrics;
1667
+	}
1668
+
1669
+	/**
1670
+	 * dumps the entire stylesheet as a string
1671
+	 *
1672
+	 * Generates a string of each selector and associated style in the
1673
+	 * Stylesheet.  Useful for debugging.
1674
+	 *
1675
+	 * @return string
1676
+	 */
1677
+	function __toString()
1678
+	{
1679
+		$str = "";
1680
+		foreach ($this->_styles as $selector => $selector_styles) {
1681
+			/** @var Style $style */
1682
+			foreach ($selector_styles as $style) {
1683
+				$str .= "$selector => " . $style->__toString() . "\n";
1684
+			}
1685
+		}
1686
+
1687
+		return $str;
1688
+	}
1689 1689
 }
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Image/Cache.php 1 patch
Indentation   +224 added lines, -224 removed lines patch added patch discarded remove patch
@@ -18,237 +18,237 @@
 block discarded – undo
18 18
  */
19 19
 class Cache
20 20
 {
21
-    /**
22
-     * Array of downloaded images.  Cached so that identical images are
23
-     * not needlessly downloaded.
24
-     *
25
-     * @var array
26
-     */
27
-    protected static $_cache = [];
28
-
29
-    /**
30
-     * @var array
31
-     */
32
-    protected static $tempImages = [];
33
-
34
-    /**
35
-     * The url to the "broken image" used when images can't be loaded
36
-     *
37
-     * @var string
38
-     */
39
-    public static $broken_image = "data:image/svg+xml;charset=utf8,%3C?xml version='1.0'?%3E%3Csvg width='64' height='64' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Crect stroke='%23666666' id='svg_1' height='60.499994' width='60.166667' y='1.666669' x='1.999998' stroke-width='1.5' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_3' y2='59.333253' x2='59.749916' y1='4.333415' x1='4.250079' stroke-width='1.5' stroke='%23999999' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_4' y2='59.999665' x2='4.062838' y1='3.750342' x1='60.062164' stroke-width='1.5' stroke='%23999999' fill='none'/%3E%3C/g%3E%3C/svg%3E";
40
-
41
-    public static $error_message = "Image not found or type unknown";
21
+	/**
22
+	 * Array of downloaded images.  Cached so that identical images are
23
+	 * not needlessly downloaded.
24
+	 *
25
+	 * @var array
26
+	 */
27
+	protected static $_cache = [];
28
+
29
+	/**
30
+	 * @var array
31
+	 */
32
+	protected static $tempImages = [];
33
+
34
+	/**
35
+	 * The url to the "broken image" used when images can't be loaded
36
+	 *
37
+	 * @var string
38
+	 */
39
+	public static $broken_image = "data:image/svg+xml;charset=utf8,%3C?xml version='1.0'?%3E%3Csvg width='64' height='64' xmlns='http://www.w3.org/2000/svg'%3E%3Cg%3E%3Crect stroke='%23666666' id='svg_1' height='60.499994' width='60.166667' y='1.666669' x='1.999998' stroke-width='1.5' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_3' y2='59.333253' x2='59.749916' y1='4.333415' x1='4.250079' stroke-width='1.5' stroke='%23999999' fill='none'/%3E%3Cline stroke-linecap='null' stroke-linejoin='null' id='svg_4' y2='59.999665' x2='4.062838' y1='3.750342' x1='60.062164' stroke-width='1.5' stroke='%23999999' fill='none'/%3E%3C/g%3E%3C/svg%3E";
40
+
41
+	public static $error_message = "Image not found or type unknown";
42 42
     
43
-    /**
44
-     * Resolve and fetch an image for use.
45
-     *
46
-     * @param string $url       The url of the image
47
-     * @param string $protocol  Default protocol if none specified in $url
48
-     * @param string $host      Default host if none specified in $url
49
-     * @param string $base_path Default path if none specified in $url
50
-     * @param Options $options  An instance of Dompdf\Options
51
-     *
52
-     * @return array            An array with three elements: The local path to the image, the image
53
-     *                          extension, and an error message if the image could not be cached
54
-     */
55
-    static function resolve_url($url, $protocol, $host, $base_path, Options $options)
56
-    {
57
-        $tempfile = null;
58
-        $resolved_url = null;
59
-        $type = null;
60
-        $message = null;
43
+	/**
44
+	 * Resolve and fetch an image for use.
45
+	 *
46
+	 * @param string $url       The url of the image
47
+	 * @param string $protocol  Default protocol if none specified in $url
48
+	 * @param string $host      Default host if none specified in $url
49
+	 * @param string $base_path Default path if none specified in $url
50
+	 * @param Options $options  An instance of Dompdf\Options
51
+	 *
52
+	 * @return array            An array with three elements: The local path to the image, the image
53
+	 *                          extension, and an error message if the image could not be cached
54
+	 */
55
+	static function resolve_url($url, $protocol, $host, $base_path, Options $options)
56
+	{
57
+		$tempfile = null;
58
+		$resolved_url = null;
59
+		$type = null;
60
+		$message = null;
61 61
         
62
-        try {
63
-            $full_url = Helpers::build_url($protocol, $host, $base_path, $url);
62
+		try {
63
+			$full_url = Helpers::build_url($protocol, $host, $base_path, $url);
64 64
 
65
-            if ($full_url === null) {
66
-                throw new ImageException("Unable to parse image URL $url.", E_WARNING);
67
-            }
65
+			if ($full_url === null) {
66
+				throw new ImageException("Unable to parse image URL $url.", E_WARNING);
67
+			}
68 68
 
69
-            $parsed_url = Helpers::explode_url($full_url);
70
-            $protocol = strtolower($parsed_url["protocol"]);
71
-            $is_data_uri = strpos($protocol, "data:") === 0;
69
+			$parsed_url = Helpers::explode_url($full_url);
70
+			$protocol = strtolower($parsed_url["protocol"]);
71
+			$is_data_uri = strpos($protocol, "data:") === 0;
72 72
             
73
-            if (!$is_data_uri) {
74
-                $allowed_protocols = $options->getAllowedProtocols();
75
-                if (!array_key_exists($protocol, $allowed_protocols)) {
76
-                    throw new ImageException("Permission denied on $url. The communication protocol is not supported.", E_WARNING);
77
-                }
78
-                foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
79
-                    [$result, $message] = $rule($full_url);
80
-                    if (!$result) {
81
-                        throw new ImageException("Error loading $url: $message", E_WARNING);
82
-                    }
83
-                }
84
-            }
85
-
86
-            if ($protocol === "file://") {
87
-                $resolved_url = $full_url;
88
-            } elseif (isset(self::$_cache[$full_url])) {
89
-                $resolved_url = self::$_cache[$full_url];
90
-            } else {
91
-                $tmp_dir = $options->getTempDir();
92
-                if (($resolved_url = @tempnam($tmp_dir, "ca_dompdf_img_")) === false) {
93
-                    throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
94
-                }
95
-                $tempfile = $resolved_url;
96
-
97
-                $image = null;
98
-                if ($is_data_uri) {
99
-                    if (($parsed_data_uri = Helpers::parse_data_uri($url)) !== false) {
100
-                        $image = $parsed_data_uri["data"];
101
-                    }
102
-                } else {
103
-                    list($image, $http_response_header) = Helpers::getFileContent($full_url, $options->getHttpContext());
104
-                }
105
-
106
-                // Image not found or invalid
107
-                if ($image === null) {
108
-                    $msg = ($is_data_uri ? "Data-URI could not be parsed" : "Image not found");
109
-                    throw new ImageException($msg, E_WARNING);
110
-                }
111
-
112
-                if (@file_put_contents($resolved_url, $image) === false) {
113
-                    throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
114
-                }
115
-
116
-                self::$_cache[$full_url] = $resolved_url;
117
-            }
118
-
119
-            // Check if the local file is readable
120
-            if (!is_readable($resolved_url) || !filesize($resolved_url)) {
121
-                throw new ImageException("Image not readable or empty", E_WARNING);
122
-            }
123
-
124
-            list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext());
125
-
126
-            if (($width && $height && in_array($type, ["gif", "png", "jpeg", "bmp", "svg","webp"], true)) === false) {
127
-                throw new ImageException("Image type unknown", E_WARNING);
128
-            }
129
-
130
-            if ($type === "svg") {
131
-                $parser = xml_parser_create("utf-8");
132
-                xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
133
-                xml_set_element_handler(
134
-                    $parser,
135
-                    function ($parser, $name, $attributes) use ($options, $parsed_url, $full_url) {
136
-                        if ($name === "image") {
137
-                            $attributes = array_change_key_case($attributes, CASE_LOWER);
138
-                            $url = $attributes["xlink:href"] ?? $attributes["href"];
139
-                            if (!empty($url)) {
140
-                                $inner_full_url = Helpers::build_url($parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $url);
141
-                                if ($inner_full_url === $full_url) {
142
-                                    throw new ImageException("SVG self-reference is not allowed", E_WARNING);
143
-                                }
144
-                                [$resolved_url, $type, $message] = self::resolve_url($url, $parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $options);
145
-                                if (!empty($message)) {
146
-                                    throw new ImageException("This SVG document references a restricted resource. $message", E_WARNING);
147
-                                }
148
-                            }
149
-                        }
150
-                    },
151
-                    false
152
-                );
73
+			if (!$is_data_uri) {
74
+				$allowed_protocols = $options->getAllowedProtocols();
75
+				if (!array_key_exists($protocol, $allowed_protocols)) {
76
+					throw new ImageException("Permission denied on $url. The communication protocol is not supported.", E_WARNING);
77
+				}
78
+				foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
79
+					[$result, $message] = $rule($full_url);
80
+					if (!$result) {
81
+						throw new ImageException("Error loading $url: $message", E_WARNING);
82
+					}
83
+				}
84
+			}
85
+
86
+			if ($protocol === "file://") {
87
+				$resolved_url = $full_url;
88
+			} elseif (isset(self::$_cache[$full_url])) {
89
+				$resolved_url = self::$_cache[$full_url];
90
+			} else {
91
+				$tmp_dir = $options->getTempDir();
92
+				if (($resolved_url = @tempnam($tmp_dir, "ca_dompdf_img_")) === false) {
93
+					throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
94
+				}
95
+				$tempfile = $resolved_url;
96
+
97
+				$image = null;
98
+				if ($is_data_uri) {
99
+					if (($parsed_data_uri = Helpers::parse_data_uri($url)) !== false) {
100
+						$image = $parsed_data_uri["data"];
101
+					}
102
+				} else {
103
+					list($image, $http_response_header) = Helpers::getFileContent($full_url, $options->getHttpContext());
104
+				}
105
+
106
+				// Image not found or invalid
107
+				if ($image === null) {
108
+					$msg = ($is_data_uri ? "Data-URI could not be parsed" : "Image not found");
109
+					throw new ImageException($msg, E_WARNING);
110
+				}
111
+
112
+				if (@file_put_contents($resolved_url, $image) === false) {
113
+					throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
114
+				}
115
+
116
+				self::$_cache[$full_url] = $resolved_url;
117
+			}
118
+
119
+			// Check if the local file is readable
120
+			if (!is_readable($resolved_url) || !filesize($resolved_url)) {
121
+				throw new ImageException("Image not readable or empty", E_WARNING);
122
+			}
123
+
124
+			list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext());
125
+
126
+			if (($width && $height && in_array($type, ["gif", "png", "jpeg", "bmp", "svg","webp"], true)) === false) {
127
+				throw new ImageException("Image type unknown", E_WARNING);
128
+			}
129
+
130
+			if ($type === "svg") {
131
+				$parser = xml_parser_create("utf-8");
132
+				xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
133
+				xml_set_element_handler(
134
+					$parser,
135
+					function ($parser, $name, $attributes) use ($options, $parsed_url, $full_url) {
136
+						if ($name === "image") {
137
+							$attributes = array_change_key_case($attributes, CASE_LOWER);
138
+							$url = $attributes["xlink:href"] ?? $attributes["href"];
139
+							if (!empty($url)) {
140
+								$inner_full_url = Helpers::build_url($parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $url);
141
+								if ($inner_full_url === $full_url) {
142
+									throw new ImageException("SVG self-reference is not allowed", E_WARNING);
143
+								}
144
+								[$resolved_url, $type, $message] = self::resolve_url($url, $parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $options);
145
+								if (!empty($message)) {
146
+									throw new ImageException("This SVG document references a restricted resource. $message", E_WARNING);
147
+								}
148
+							}
149
+						}
150
+					},
151
+					false
152
+				);
153 153
         
154
-                if (($fp = fopen($resolved_url, "r")) !== false) {
155
-                    while ($line = fread($fp, 8192)) {
156
-                        xml_parse($parser, $line, false);
157
-                    }
158
-                    fclose($fp);
159
-                }
160
-                xml_parser_free($parser);
161
-            }
162
-        } catch (ImageException $e) {
163
-            if ($tempfile) {
164
-                unlink($tempfile);
165
-            }
166
-            $resolved_url = self::$broken_image;
167
-            list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext());
168
-            $message = self::$error_message;
169
-            Helpers::record_warnings($e->getCode(), $e->getMessage() . " \n $url", $e->getFile(), $e->getLine());
170
-            self::$_cache[$full_url] = $resolved_url;
171
-        }
172
-
173
-        return [$resolved_url, $type, $message];
174
-    }
175
-
176
-    /**
177
-     * Register a temp file for the given original image file.
178
-     *
179
-     * @param string $filePath The path of the original image.
180
-     * @param string $tempPath The path of the temp file to register.
181
-     * @param string $key      An optional key to register the temp file at.
182
-     */
183
-    static function addTempImage(string $filePath, string $tempPath, string $key = "default"): void
184
-    {
185
-        if (!isset(self::$tempImages[$filePath])) {
186
-            self::$tempImages[$filePath] = [];
187
-        }
188
-
189
-        self::$tempImages[$filePath][$key] = $tempPath;
190
-    }
191
-
192
-    /**
193
-     * Get the path of a temp file registered for the given original image file.
194
-     *
195
-     * @param string $filePath The path of the original image.
196
-     * @param string $key      The key the temp file is registered at.
197
-     */
198
-    static function getTempImage(string $filePath, string $key = "default"): ?string
199
-    {
200
-        return self::$tempImages[$filePath][$key] ?? null;
201
-    }
202
-
203
-    /**
204
-     * Unlink all cached images (i.e. temporary images either downloaded
205
-     * or converted) except for the bundled "broken image"
206
-     */
207
-    static function clear(bool $debugPng = false)
208
-    {
209
-        foreach (self::$_cache as $file) {
210
-            if ($file === self::$broken_image) {
211
-                continue;
212
-            }
213
-            if ($debugPng) {
214
-                print "[clear unlink $file]";
215
-            }
216
-            if (file_exists($file)) {
217
-                unlink($file);
218
-            }
219
-        }
220
-
221
-        foreach (self::$tempImages as $versions) {
222
-            foreach ($versions as $file) {
223
-                if ($file === self::$broken_image) {
224
-                    continue;
225
-                }
226
-                if ($debugPng) {
227
-                    print "[unlink temp image $file]";
228
-                }
229
-                if (file_exists($file)) {
230
-                    unlink($file);
231
-                }
232
-            }
233
-        }
234
-
235
-        self::$_cache = [];
236
-        self::$tempImages = [];
237
-    }
238
-
239
-    static function detect_type($file, $context = null)
240
-    {
241
-        list(, , $type) = Helpers::dompdf_getimagesize($file, $context);
242
-
243
-        return $type;
244
-    }
245
-
246
-    static function is_broken($url)
247
-    {
248
-        return $url === self::$broken_image;
249
-    }
154
+				if (($fp = fopen($resolved_url, "r")) !== false) {
155
+					while ($line = fread($fp, 8192)) {
156
+						xml_parse($parser, $line, false);
157
+					}
158
+					fclose($fp);
159
+				}
160
+				xml_parser_free($parser);
161
+			}
162
+		} catch (ImageException $e) {
163
+			if ($tempfile) {
164
+				unlink($tempfile);
165
+			}
166
+			$resolved_url = self::$broken_image;
167
+			list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext());
168
+			$message = self::$error_message;
169
+			Helpers::record_warnings($e->getCode(), $e->getMessage() . " \n $url", $e->getFile(), $e->getLine());
170
+			self::$_cache[$full_url] = $resolved_url;
171
+		}
172
+
173
+		return [$resolved_url, $type, $message];
174
+	}
175
+
176
+	/**
177
+	 * Register a temp file for the given original image file.
178
+	 *
179
+	 * @param string $filePath The path of the original image.
180
+	 * @param string $tempPath The path of the temp file to register.
181
+	 * @param string $key      An optional key to register the temp file at.
182
+	 */
183
+	static function addTempImage(string $filePath, string $tempPath, string $key = "default"): void
184
+	{
185
+		if (!isset(self::$tempImages[$filePath])) {
186
+			self::$tempImages[$filePath] = [];
187
+		}
188
+
189
+		self::$tempImages[$filePath][$key] = $tempPath;
190
+	}
191
+
192
+	/**
193
+	 * Get the path of a temp file registered for the given original image file.
194
+	 *
195
+	 * @param string $filePath The path of the original image.
196
+	 * @param string $key      The key the temp file is registered at.
197
+	 */
198
+	static function getTempImage(string $filePath, string $key = "default"): ?string
199
+	{
200
+		return self::$tempImages[$filePath][$key] ?? null;
201
+	}
202
+
203
+	/**
204
+	 * Unlink all cached images (i.e. temporary images either downloaded
205
+	 * or converted) except for the bundled "broken image"
206
+	 */
207
+	static function clear(bool $debugPng = false)
208
+	{
209
+		foreach (self::$_cache as $file) {
210
+			if ($file === self::$broken_image) {
211
+				continue;
212
+			}
213
+			if ($debugPng) {
214
+				print "[clear unlink $file]";
215
+			}
216
+			if (file_exists($file)) {
217
+				unlink($file);
218
+			}
219
+		}
220
+
221
+		foreach (self::$tempImages as $versions) {
222
+			foreach ($versions as $file) {
223
+				if ($file === self::$broken_image) {
224
+					continue;
225
+				}
226
+				if ($debugPng) {
227
+					print "[unlink temp image $file]";
228
+				}
229
+				if (file_exists($file)) {
230
+					unlink($file);
231
+				}
232
+			}
233
+		}
234
+
235
+		self::$_cache = [];
236
+		self::$tempImages = [];
237
+	}
238
+
239
+	static function detect_type($file, $context = null)
240
+	{
241
+		list(, , $type) = Helpers::dompdf_getimagesize($file, $context);
242
+
243
+		return $type;
244
+	}
245
+
246
+	static function is_broken($url)
247
+	{
248
+		return $url === self::$broken_image;
249
+	}
250 250
 }
251 251
 
252 252
 if (file_exists(realpath(__DIR__ . "/../../lib/res/broken_image.svg"))) {
253
-    Cache::$broken_image = realpath(__DIR__ . "/../../lib/res/broken_image.svg");
253
+	Cache::$broken_image = realpath(__DIR__ . "/../../lib/res/broken_image.svg");
254 254
 }
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Renderer/ListBullet.php 2 patches
Indentation   +213 added lines, -213 removed lines patch added patch discarded remove patch
@@ -19,217 +19,217 @@
 block discarded – undo
19 19
  */
20 20
 class ListBullet extends AbstractRenderer
21 21
 {
22
-    /**
23
-     * @param $type
24
-     * @return mixed|string
25
-     */
26
-    static function get_counter_chars($type)
27
-    {
28
-        static $cache = [];
29
-
30
-        if (isset($cache[$type])) {
31
-            return $cache[$type];
32
-        }
33
-
34
-        $uppercase = false;
35
-        $text = "";
36
-
37
-        switch ($type) {
38
-            case "decimal-leading-zero":
39
-            case "decimal":
40
-            case "1":
41
-                return "0123456789";
42
-
43
-            case "upper-alpha":
44
-            case "upper-latin":
45
-            case "A":
46
-                $uppercase = true;
47
-            case "lower-alpha":
48
-            case "lower-latin":
49
-            case "a":
50
-                $text = "abcdefghijklmnopqrstuvwxyz";
51
-                break;
52
-
53
-            case "upper-roman":
54
-            case "I":
55
-                $uppercase = true;
56
-            case "lower-roman":
57
-            case "i":
58
-                $text = "ivxlcdm";
59
-                break;
60
-
61
-            case "lower-greek":
62
-                for ($i = 0; $i < 24; $i++) {
63
-                    $text .= Helpers::unichr($i + 944);
64
-                }
65
-                break;
66
-        }
67
-
68
-        if ($uppercase) {
69
-            $text = strtoupper($text);
70
-        }
71
-
72
-        return $cache[$type] = "$text.";
73
-    }
74
-
75
-    /**
76
-     * @param int $n
77
-     * @param string $type
78
-     * @param int|null $pad
79
-     *
80
-     * @return string
81
-     */
82
-    private function make_counter($n, $type, $pad = null)
83
-    {
84
-        $n = intval($n);
85
-        $text = "";
86
-        $uppercase = false;
87
-
88
-        switch ($type) {
89
-            case "decimal-leading-zero":
90
-            case "decimal":
91
-            case "1":
92
-                if ($pad) {
93
-                    $text = str_pad($n, $pad, "0", STR_PAD_LEFT);
94
-                } else {
95
-                    $text = $n;
96
-                }
97
-                break;
98
-
99
-            case "upper-alpha":
100
-            case "upper-latin":
101
-            case "A":
102
-                $uppercase = true;
103
-            case "lower-alpha":
104
-            case "lower-latin":
105
-            case "a":
106
-                $text = chr((($n - 1) % 26) + ord('a'));
107
-                break;
108
-
109
-            case "upper-roman":
110
-            case "I":
111
-                $uppercase = true;
112
-            case "lower-roman":
113
-            case "i":
114
-                $text = Helpers::dec2roman($n);
115
-                break;
116
-
117
-            case "lower-greek":
118
-                $text = Helpers::unichr($n + 944);
119
-                break;
120
-        }
121
-
122
-        if ($uppercase) {
123
-            $text = strtoupper($text);
124
-        }
125
-
126
-        return "$text.";
127
-    }
128
-
129
-    /**
130
-     * @param ListBulletFrameDecorator $frame
131
-     */
132
-    function render(Frame $frame)
133
-    {
134
-        $li = $frame->get_parent();
135
-        $style = $frame->get_style();
136
-
137
-        $this->_set_opacity($frame->get_opacity($style->opacity));
138
-
139
-        // Don't render bullets twice if the list item was split
140
-        if ($li->is_split_off) {
141
-            return;
142
-        }
143
-
144
-        $font_family = $style->font_family;
145
-        $font_size = $style->font_size;
146
-        $baseline = $this->_canvas->get_font_baseline($font_family, $font_size);
147
-
148
-        // Handle list-style-image
149
-        // If list style image is requested but missing, fall back to predefined types
150
-        if ($frame instanceof ListBulletImage && !Cache::is_broken($img = $frame->get_image_url())) {
151
-            [$x, $y] = $frame->get_position();
152
-            $w = $frame->get_width();
153
-            $h = $frame->get_height();
154
-            $y += $baseline - $h;
155
-
156
-            $this->_canvas->image($img, $x, $y, $w, $h);
157
-        } else {
158
-            $bullet_style = $style->list_style_type;
159
-
160
-            switch ($bullet_style) {
161
-                default:
162
-                case "disc":
163
-                case "circle":
164
-                    [$x, $y] = $frame->get_position();
165
-                    $offset = $font_size * ListBulletFrameDecorator::BULLET_OFFSET;
166
-                    $r = ($font_size * ListBulletFrameDecorator::BULLET_SIZE) / 2;
167
-                    $x += $r;
168
-                    $y += $baseline - $r - $offset;
169
-                    $o = $font_size * ListBulletFrameDecorator::BULLET_THICKNESS;
170
-                    $this->_canvas->circle($x, $y, $r, $style->color, $o, null, $bullet_style !== "circle");
171
-                    break;
172
-
173
-                case "square":
174
-                    [$x, $y] = $frame->get_position();
175
-                    $offset = $font_size * ListBulletFrameDecorator::BULLET_OFFSET;
176
-                    $w = $font_size * ListBulletFrameDecorator::BULLET_SIZE;
177
-                    $y += $baseline - $w - $offset;
178
-                    $this->_canvas->filled_rectangle($x, $y, $w, $w, $style->color);
179
-                    break;
180
-
181
-                case "decimal-leading-zero":
182
-                case "decimal":
183
-                case "lower-alpha":
184
-                case "lower-latin":
185
-                case "lower-roman":
186
-                case "lower-greek":
187
-                case "upper-alpha":
188
-                case "upper-latin":
189
-                case "upper-roman":
190
-                case "1": // HTML 4.0 compatibility
191
-                case "a":
192
-                case "i":
193
-                case "A":
194
-                case "I":
195
-                    $pad = null;
196
-                    if ($bullet_style === "decimal-leading-zero") {
197
-                        $pad = strlen($li->get_parent()->get_node()->getAttribute("dompdf-children-count"));
198
-                    }
199
-
200
-                    $node = $frame->get_node();
201
-
202
-                    if (!$node->hasAttribute("dompdf-counter")) {
203
-                        return;
204
-                    }
205
-
206
-                    $index = $node->getAttribute("dompdf-counter");
207
-                    $text = $this->make_counter($index, $bullet_style, $pad);
208
-
209
-                    if (trim($text) === "") {
210
-                        return;
211
-                    }
212
-
213
-                    $word_spacing = $style->word_spacing;
214
-                    $letter_spacing = $style->letter_spacing;
215
-                    $text_width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font_family, $font_size, $word_spacing, $letter_spacing);
216
-
217
-                    [$x, $y] = $frame->get_position();
218
-                    // Correct for static frame width applied by positioner
219
-                    $x += $frame->get_width() - $text_width;
220
-
221
-                    $this->_canvas->text($x, $y, $text,
222
-                        $font_family, $font_size,
223
-                        $style->color, $word_spacing, $letter_spacing);
224
-
225
-                case "none":
226
-                    break;
227
-            }
228
-        }
229
-
230
-        $id = $frame->get_node()->getAttribute("id");
231
-        if (strlen($id) > 0) {
232
-            $this->_canvas->add_named_dest($id);
233
-        }
234
-    }
22
+	/**
23
+	 * @param $type
24
+	 * @return mixed|string
25
+	 */
26
+	static function get_counter_chars($type)
27
+	{
28
+		static $cache = [];
29
+
30
+		if (isset($cache[$type])) {
31
+			return $cache[$type];
32
+		}
33
+
34
+		$uppercase = false;
35
+		$text = "";
36
+
37
+		switch ($type) {
38
+			case "decimal-leading-zero":
39
+			case "decimal":
40
+			case "1":
41
+				return "0123456789";
42
+
43
+			case "upper-alpha":
44
+			case "upper-latin":
45
+			case "A":
46
+				$uppercase = true;
47
+			case "lower-alpha":
48
+			case "lower-latin":
49
+			case "a":
50
+				$text = "abcdefghijklmnopqrstuvwxyz";
51
+				break;
52
+
53
+			case "upper-roman":
54
+			case "I":
55
+				$uppercase = true;
56
+			case "lower-roman":
57
+			case "i":
58
+				$text = "ivxlcdm";
59
+				break;
60
+
61
+			case "lower-greek":
62
+				for ($i = 0; $i < 24; $i++) {
63
+					$text .= Helpers::unichr($i + 944);
64
+				}
65
+				break;
66
+		}
67
+
68
+		if ($uppercase) {
69
+			$text = strtoupper($text);
70
+		}
71
+
72
+		return $cache[$type] = "$text.";
73
+	}
74
+
75
+	/**
76
+	 * @param int $n
77
+	 * @param string $type
78
+	 * @param int|null $pad
79
+	 *
80
+	 * @return string
81
+	 */
82
+	private function make_counter($n, $type, $pad = null)
83
+	{
84
+		$n = intval($n);
85
+		$text = "";
86
+		$uppercase = false;
87
+
88
+		switch ($type) {
89
+			case "decimal-leading-zero":
90
+			case "decimal":
91
+			case "1":
92
+				if ($pad) {
93
+					$text = str_pad($n, $pad, "0", STR_PAD_LEFT);
94
+				} else {
95
+					$text = $n;
96
+				}
97
+				break;
98
+
99
+			case "upper-alpha":
100
+			case "upper-latin":
101
+			case "A":
102
+				$uppercase = true;
103
+			case "lower-alpha":
104
+			case "lower-latin":
105
+			case "a":
106
+				$text = chr((($n - 1) % 26) + ord('a'));
107
+				break;
108
+
109
+			case "upper-roman":
110
+			case "I":
111
+				$uppercase = true;
112
+			case "lower-roman":
113
+			case "i":
114
+				$text = Helpers::dec2roman($n);
115
+				break;
116
+
117
+			case "lower-greek":
118
+				$text = Helpers::unichr($n + 944);
119
+				break;
120
+		}
121
+
122
+		if ($uppercase) {
123
+			$text = strtoupper($text);
124
+		}
125
+
126
+		return "$text.";
127
+	}
128
+
129
+	/**
130
+	 * @param ListBulletFrameDecorator $frame
131
+	 */
132
+	function render(Frame $frame)
133
+	{
134
+		$li = $frame->get_parent();
135
+		$style = $frame->get_style();
136
+
137
+		$this->_set_opacity($frame->get_opacity($style->opacity));
138
+
139
+		// Don't render bullets twice if the list item was split
140
+		if ($li->is_split_off) {
141
+			return;
142
+		}
143
+
144
+		$font_family = $style->font_family;
145
+		$font_size = $style->font_size;
146
+		$baseline = $this->_canvas->get_font_baseline($font_family, $font_size);
147
+
148
+		// Handle list-style-image
149
+		// If list style image is requested but missing, fall back to predefined types
150
+		if ($frame instanceof ListBulletImage && !Cache::is_broken($img = $frame->get_image_url())) {
151
+			[$x, $y] = $frame->get_position();
152
+			$w = $frame->get_width();
153
+			$h = $frame->get_height();
154
+			$y += $baseline - $h;
155
+
156
+			$this->_canvas->image($img, $x, $y, $w, $h);
157
+		} else {
158
+			$bullet_style = $style->list_style_type;
159
+
160
+			switch ($bullet_style) {
161
+				default:
162
+				case "disc":
163
+				case "circle":
164
+					[$x, $y] = $frame->get_position();
165
+					$offset = $font_size * ListBulletFrameDecorator::BULLET_OFFSET;
166
+					$r = ($font_size * ListBulletFrameDecorator::BULLET_SIZE) / 2;
167
+					$x += $r;
168
+					$y += $baseline - $r - $offset;
169
+					$o = $font_size * ListBulletFrameDecorator::BULLET_THICKNESS;
170
+					$this->_canvas->circle($x, $y, $r, $style->color, $o, null, $bullet_style !== "circle");
171
+					break;
172
+
173
+				case "square":
174
+					[$x, $y] = $frame->get_position();
175
+					$offset = $font_size * ListBulletFrameDecorator::BULLET_OFFSET;
176
+					$w = $font_size * ListBulletFrameDecorator::BULLET_SIZE;
177
+					$y += $baseline - $w - $offset;
178
+					$this->_canvas->filled_rectangle($x, $y, $w, $w, $style->color);
179
+					break;
180
+
181
+				case "decimal-leading-zero":
182
+				case "decimal":
183
+				case "lower-alpha":
184
+				case "lower-latin":
185
+				case "lower-roman":
186
+				case "lower-greek":
187
+				case "upper-alpha":
188
+				case "upper-latin":
189
+				case "upper-roman":
190
+				case "1": // HTML 4.0 compatibility
191
+				case "a":
192
+				case "i":
193
+				case "A":
194
+				case "I":
195
+					$pad = null;
196
+					if ($bullet_style === "decimal-leading-zero") {
197
+						$pad = strlen($li->get_parent()->get_node()->getAttribute("dompdf-children-count"));
198
+					}
199
+
200
+					$node = $frame->get_node();
201
+
202
+					if (!$node->hasAttribute("dompdf-counter")) {
203
+						return;
204
+					}
205
+
206
+					$index = $node->getAttribute("dompdf-counter");
207
+					$text = $this->make_counter($index, $bullet_style, $pad);
208
+
209
+					if (trim($text) === "") {
210
+						return;
211
+					}
212
+
213
+					$word_spacing = $style->word_spacing;
214
+					$letter_spacing = $style->letter_spacing;
215
+					$text_width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font_family, $font_size, $word_spacing, $letter_spacing);
216
+
217
+					[$x, $y] = $frame->get_position();
218
+					// Correct for static frame width applied by positioner
219
+					$x += $frame->get_width() - $text_width;
220
+
221
+					$this->_canvas->text($x, $y, $text,
222
+						$font_family, $font_size,
223
+						$style->color, $word_spacing, $letter_spacing);
224
+
225
+				case "none":
226
+					break;
227
+			}
228
+		}
229
+
230
+		$id = $frame->get_node()->getAttribute("id");
231
+		if (strlen($id) > 0) {
232
+			$this->_canvas->add_named_dest($id);
233
+		}
234
+	}
235 235
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -147,7 +147,7 @@  discard block
 block discarded – undo
147 147
 
148 148
         // Handle list-style-image
149 149
         // If list style image is requested but missing, fall back to predefined types
150
-        if ($frame instanceof ListBulletImage && !Cache::is_broken($img = $frame->get_image_url())) {
150
+        if ($frame instanceof ListBulletImage && ! Cache::is_broken($img = $frame->get_image_url())) {
151 151
             [$x, $y] = $frame->get_position();
152 152
             $w = $frame->get_width();
153 153
             $h = $frame->get_height();
@@ -199,7 +199,7 @@  discard block
 block discarded – undo
199 199
 
200 200
                     $node = $frame->get_node();
201 201
 
202
-                    if (!$node->hasAttribute("dompdf-counter")) {
202
+                    if ( ! $node->hasAttribute("dompdf-counter")) {
203 203
                         return;
204 204
                     }
205 205
 
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Renderer/Block.php 2 patches
Indentation   +67 added lines, -67 removed lines patch added patch discarded remove patch
@@ -18,71 +18,71 @@
 block discarded – undo
18 18
 class Block extends AbstractRenderer
19 19
 {
20 20
 
21
-    /**
22
-     * @param Frame $frame
23
-     */
24
-    function render(Frame $frame)
25
-    {
26
-        $style = $frame->get_style();
27
-        $node = $frame->get_node();
28
-        $dompdf = $this->_dompdf;
29
-
30
-        $this->_set_opacity($frame->get_opacity($style->opacity));
31
-
32
-        [$x, $y, $w, $h] = $frame->get_border_box();
33
-
34
-        if ($node->nodeName === "body") {
35
-            // Margins should be fully resolved at this point
36
-            $mt = $style->margin_top;
37
-            $mb = $style->margin_bottom;
38
-            $h = $frame->get_containing_block("h") - $mt - $mb;
39
-        }
40
-
41
-        $border_box = [$x, $y, $w, $h];
42
-
43
-        // Draw our background, border and content
44
-        $this->_render_background($frame, $border_box);
45
-        $this->_render_border($frame, $border_box);
46
-        $this->_render_outline($frame, $border_box);
47
-
48
-        // Handle anchors & links
49
-        if ($node->nodeName === "a" && $href = $node->getAttribute("href")) {
50
-            $href = Helpers::build_url($dompdf->getProtocol(), $dompdf->getBaseHost(), $dompdf->getBasePath(), $href) ?? $href;
51
-            $this->_canvas->add_link($href, $x, $y, $w, $h);
52
-        }
53
-
54
-        $id = $frame->get_node()->getAttribute("id");
55
-        if (strlen($id) > 0) {
56
-            $this->_canvas->add_named_dest($id);
57
-        }
58
-
59
-        $this->debugBlockLayout($frame, "red", false);
60
-    }
61
-
62
-    protected function debugBlockLayout(Frame $frame, ?string $color, bool $lines = false): void
63
-    {
64
-        $options = $this->_dompdf->getOptions();
65
-        $debugLayout = $options->getDebugLayout();
66
-
67
-        if (!$debugLayout) {
68
-            return;
69
-        }
70
-
71
-        if ($color && $options->getDebugLayoutBlocks()) {
72
-            $this->_debug_layout($frame->get_border_box(), $color);
73
-
74
-            if ($options->getDebugLayoutPaddingBox()) {
75
-                $this->_debug_layout($frame->get_padding_box(), $color, [0.5, 0.5]);
76
-            }
77
-        }
78
-
79
-        if ($lines && $options->getDebugLayoutLines() && $frame instanceof BlockFrameDecorator) {
80
-            [$cx, , $cw] = $frame->get_content_box();
81
-
82
-            foreach ($frame->get_line_boxes() as $line) {
83
-                $lw = $cw - $line->left - $line->right;
84
-                $this->_debug_layout([$cx + $line->left, $line->y, $lw, $line->h], "orange");
85
-            }
86
-        }
87
-    }
21
+	/**
22
+	 * @param Frame $frame
23
+	 */
24
+	function render(Frame $frame)
25
+	{
26
+		$style = $frame->get_style();
27
+		$node = $frame->get_node();
28
+		$dompdf = $this->_dompdf;
29
+
30
+		$this->_set_opacity($frame->get_opacity($style->opacity));
31
+
32
+		[$x, $y, $w, $h] = $frame->get_border_box();
33
+
34
+		if ($node->nodeName === "body") {
35
+			// Margins should be fully resolved at this point
36
+			$mt = $style->margin_top;
37
+			$mb = $style->margin_bottom;
38
+			$h = $frame->get_containing_block("h") - $mt - $mb;
39
+		}
40
+
41
+		$border_box = [$x, $y, $w, $h];
42
+
43
+		// Draw our background, border and content
44
+		$this->_render_background($frame, $border_box);
45
+		$this->_render_border($frame, $border_box);
46
+		$this->_render_outline($frame, $border_box);
47
+
48
+		// Handle anchors & links
49
+		if ($node->nodeName === "a" && $href = $node->getAttribute("href")) {
50
+			$href = Helpers::build_url($dompdf->getProtocol(), $dompdf->getBaseHost(), $dompdf->getBasePath(), $href) ?? $href;
51
+			$this->_canvas->add_link($href, $x, $y, $w, $h);
52
+		}
53
+
54
+		$id = $frame->get_node()->getAttribute("id");
55
+		if (strlen($id) > 0) {
56
+			$this->_canvas->add_named_dest($id);
57
+		}
58
+
59
+		$this->debugBlockLayout($frame, "red", false);
60
+	}
61
+
62
+	protected function debugBlockLayout(Frame $frame, ?string $color, bool $lines = false): void
63
+	{
64
+		$options = $this->_dompdf->getOptions();
65
+		$debugLayout = $options->getDebugLayout();
66
+
67
+		if (!$debugLayout) {
68
+			return;
69
+		}
70
+
71
+		if ($color && $options->getDebugLayoutBlocks()) {
72
+			$this->_debug_layout($frame->get_border_box(), $color);
73
+
74
+			if ($options->getDebugLayoutPaddingBox()) {
75
+				$this->_debug_layout($frame->get_padding_box(), $color, [0.5, 0.5]);
76
+			}
77
+		}
78
+
79
+		if ($lines && $options->getDebugLayoutLines() && $frame instanceof BlockFrameDecorator) {
80
+			[$cx, , $cw] = $frame->get_content_box();
81
+
82
+			foreach ($frame->get_line_boxes() as $line) {
83
+				$lw = $cw - $line->left - $line->right;
84
+				$this->_debug_layout([$cx + $line->left, $line->y, $lw, $line->h], "orange");
85
+			}
86
+		}
87
+	}
88 88
 }
Please login to merge, or discard this patch.
Spacing   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -64,7 +64,7 @@  discard block
 block discarded – undo
64 64
         $options = $this->_dompdf->getOptions();
65 65
         $debugLayout = $options->getDebugLayout();
66 66
 
67
-        if (!$debugLayout) {
67
+        if ( ! $debugLayout) {
68 68
             return;
69 69
         }
70 70
 
@@ -77,7 +77,7 @@  discard block
 block discarded – undo
77 77
         }
78 78
 
79 79
         if ($lines && $options->getDebugLayoutLines() && $frame instanceof BlockFrameDecorator) {
80
-            [$cx, , $cw] = $frame->get_content_box();
80
+            [$cx,, $cw] = $frame->get_content_box();
81 81
 
82 82
             foreach ($frame->get_line_boxes() as $line) {
83 83
                 $lw = $cw - $line->left - $line->right;
Please login to merge, or discard this patch.