Completed
Branch dependabot/composer/tijsverkoy... (491ea6)
by
unknown
32:00 queued 25:42
created
vendor/dompdf/dompdf/src/Dompdf.php 2 patches
Spacing   +22 added lines, -22 removed lines patch added patch discarded remove patch
@@ -260,7 +260,7 @@  discard block
 block discarded – undo
260 260
             $this->setOptions(new Options());
261 261
         }
262 262
 
263
-        $versionFile = realpath(__DIR__ . '/../VERSION');
263
+        $versionFile = realpath(__DIR__.'/../VERSION');
264 264
         if (($version = file_get_contents($versionFile)) !== false) {
265 265
             $version = trim($version);
266 266
             if ($version !== '$Format:<%h>$') {
@@ -341,27 +341,27 @@  discard block
 block discarded – undo
341 341
     {
342 342
         $this->setPhpConfig();
343 343
 
344
-        if (!$this->protocol && !$this->baseHost && !$this->basePath) {
344
+        if ( ! $this->protocol && ! $this->baseHost && ! $this->basePath) {
345 345
             [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($file);
346 346
         }
347 347
         $protocol = strtolower($this->protocol);
348 348
         $uri = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $file);
349 349
 
350 350
         $allowed_protocols = $this->options->getAllowedProtocols();
351
-        if (!array_key_exists($protocol, $allowed_protocols)) {
351
+        if ( ! array_key_exists($protocol, $allowed_protocols)) {
352 352
             throw new Exception("Permission denied on $file. The communication protocol is not supported.");
353 353
         }
354 354
 
355 355
         if ($protocol === "file://") {
356 356
             $ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION));
357
-            if (!in_array($ext, $this->allowedLocalFileExtensions)) {
357
+            if ( ! in_array($ext, $this->allowedLocalFileExtensions)) {
358 358
                 throw new Exception("Permission denied on $file: The file extension is forbidden.");
359 359
             }
360 360
         }
361 361
 
362 362
         foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
363 363
             [$result, $message] = $rule($uri);
364
-            if (!$result) {
364
+            if ( ! $result) {
365 365
                 throw new Exception("Error loading $file: $message");
366 366
             }
367 367
         }
@@ -433,7 +433,7 @@  discard block
 block discarded – undo
433 433
             }
434 434
         }
435 435
 
436
-        if (in_array(strtoupper($encoding), array('UTF-8','UTF8')) === false) {
436
+        if (in_array(strtoupper($encoding), array('UTF-8', 'UTF8')) === false) {
437 437
             $str = mb_convert_encoding($str, 'UTF-8', $encoding);
438 438
 
439 439
             //Update encoding after converting
@@ -453,17 +453,17 @@  discard block
 block discarded – undo
453 453
                 }
454 454
             }
455 455
         }
456
-        if (isset($document_encoding) && in_array(strtoupper($document_encoding), ['UTF-8','UTF8']) === false) {
456
+        if (isset($document_encoding) && in_array(strtoupper($document_encoding), ['UTF-8', 'UTF8']) === false) {
457 457
             $str = preg_replace('/charset=([^\s"]+)/i', 'charset=UTF-8', $str);
458 458
         } elseif (isset($document_encoding) === false && strpos($str, '<head>') !== false) {
459 459
             $str = str_replace('<head>', '<head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">', $str);
460 460
         } elseif (isset($document_encoding) === false) {
461
-            $str = '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">' . $str;
461
+            $str = '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">'.$str;
462 462
         }
463 463
 
464 464
         // remove BOM mark from UTF-8, it's treated as document text by DOMDocument
465 465
         // 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)?
466
-        if (substr($str, 0, 3) == chr(0xEF) . chr(0xBB) . chr(0xBF)) {
466
+        if (substr($str, 0, 3) == chr(0xEF).chr(0xBB).chr(0xBF)) {
467 467
             $str = substr($str, 3);
468 468
         }
469 469
 
@@ -569,7 +569,7 @@  discard block
 block discarded – undo
569 569
                                 }
570 570
                             }
571 571
 
572
-                            if (!$accept) {
572
+                            if ( ! $accept) {
573 573
                                 //found at least one mediatype, but none of the accepted ones
574 574
                                 //Skip this css file.
575 575
                                 break;
@@ -593,7 +593,7 @@  discard block
 block discarded – undo
593 593
                     // which states that the default media type is 'screen'
594 594
                     if ($tag->hasAttributes() &&
595 595
                         ($media = $tag->getAttribute("media")) &&
596
-                        !in_array($media, $acceptedmedia)
596
+                        ! in_array($media, $acceptedmedia)
597 597
                     ) {
598 598
                         break;
599 599
                     }
@@ -665,7 +665,7 @@  discard block
 block discarded – undo
665 665
         $options = preg_split("/\s*,\s*/", trim($value));
666 666
         $defaultView = array_shift($options);
667 667
 
668
-        if (!in_array($defaultView, $valid)) {
668
+        if ( ! in_array($defaultView, $valid)) {
669 669
             return false;
670 670
         }
671 671
 
@@ -682,7 +682,7 @@  discard block
 block discarded – undo
682 682
 
683 683
         $logOutputFile = $this->options->getLogOutputFile();
684 684
         if ($logOutputFile) {
685
-            if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
685
+            if ( ! file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
686 686
                 touch($logOutputFile);
687 687
             }
688 688
 
@@ -778,7 +778,7 @@  discard block
 block discarded – undo
778 778
         }
779 779
 
780 780
         // Clean up cached images
781
-        if (!$this->options->getDebugKeepTemp()) {
781
+        if ( ! $this->options->getDebugKeepTemp()) {
782 782
             Cache::clear($this->options->getDebugPng());
783 783
         }
784 784
 
@@ -786,7 +786,7 @@  discard block
 block discarded – undo
786 786
         if ($_dompdf_show_warnings && isset($_dompdf_warnings)) {
787 787
             echo '<b>Dompdf Warnings</b><br><pre>';
788 788
             foreach ($_dompdf_warnings as $msg) {
789
-                echo $msg . "\n";
789
+                echo $msg."\n";
790 790
             }
791 791
 
792 792
             if ($canvas instanceof CPDF) {
@@ -817,11 +817,11 @@  discard block
 block discarded – undo
817 817
         $time = (microtime(true) - $startTime) * 1000;
818 818
 
819 819
         $out = sprintf(
820
-            "<span style='color: #000' title='Frames'>%6d</span>" .
821
-            "<span style='color: #009' title='Memory'>%10.2f KB</span>" .
822
-            "<span style='color: #900' title='Time'>%10.2f ms</span>" .
823
-            "<span  title='Quirksmode'>  " .
824
-            ($this->quirksmode ? "<span style='color: #d00'> ON</span>" : "<span style='color: #0d0'>OFF</span>") .
820
+            "<span style='color: #000' title='Frames'>%6d</span>".
821
+            "<span style='color: #009' title='Memory'>%10.2f KB</span>".
822
+            "<span style='color: #900' title='Time'>%10.2f ms</span>".
823
+            "<span  title='Quirksmode'>  ".
824
+            ($this->quirksmode ? "<span style='color: #d00'> ON</span>" : "<span style='color: #0d0'>OFF</span>").
825 825
             "</span><br />", $frames, $memory, $time);
826 826
 
827 827
         $out .= ob_get_contents();
@@ -1327,7 +1327,7 @@  discard block
 block discarded – undo
1327 1327
     public function setOptions(Options $options)
1328 1328
     {
1329 1329
         // For backwards compatibility
1330
-        if ($this->options && $this->options->getHttpContext() && !$options->getHttpContext()) {
1330
+        if ($this->options && $this->options->getHttpContext() && ! $options->getHttpContext()) {
1331 1331
             $options->setHttpContext($this->options->getHttpContext());
1332 1332
         }
1333 1333
 
@@ -1469,7 +1469,7 @@  discard block
 block discarded – undo
1469 1469
             case 'version':
1470 1470
                 return $this->version;
1471 1471
             default:
1472
-                throw new Exception('Invalid property: ' . $prop);
1472
+                throw new Exception('Invalid property: '.$prop);
1473 1473
         }
1474 1474
     }
1475 1475
 }
Please login to merge, or discard this 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|float[]
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, string $orientation = "portrait"): self
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(): array
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(): string
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|float[]
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, string $orientation = "portrait"): self
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(): array
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(): string
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/AttributeTranslator.php 2 patches
Indentation   +625 added lines, -625 removed lines patch added patch discarded remove patch
@@ -18,85 +18,85 @@  discard block
 block discarded – undo
18 18
  */
19 19
 class AttributeTranslator
20 20
 {
21
-    static $_style_attr = "_html_style_attribute";
22
-
23
-    // Munged data originally from
24
-    // http://www.w3.org/TR/REC-html40/index/attributes.html
25
-    // http://www.cs.tut.fi/~jkorpela/html2css.html
26
-    private static $__ATTRIBUTE_LOOKUP = [
27
-        //'caption' => array ( 'align' => '', ),
28
-        'img' => [
29
-            'align' => [
30
-                'bottom' => 'vertical-align: baseline;',
31
-                'middle' => 'vertical-align: middle;',
32
-                'top' => 'vertical-align: top;',
33
-                'left' => 'float: left;',
34
-                'right' => 'float: right;'
35
-            ],
36
-            'border' => 'border: %0.2Fpx solid;',
37
-            'height' => '_set_px_height',
38
-            'hspace' => 'padding-left: %1$0.2Fpx; padding-right: %1$0.2Fpx;',
39
-            'vspace' => 'padding-top: %1$0.2Fpx; padding-bottom: %1$0.2Fpx;',
40
-            'width' => '_set_px_width',
41
-        ],
42
-        'table' => [
43
-            'align' => [
44
-                'left' => 'margin-left: 0; margin-right: auto;',
45
-                'center' => 'margin-left: auto; margin-right: auto;',
46
-                'right' => 'margin-left: auto; margin-right: 0;'
47
-            ],
48
-            'bgcolor' => 'background-color: %s;',
49
-            'border' => '_set_table_border',
50
-            'cellpadding' => '_set_table_cellpadding', //'border-spacing: %0.2F; border-collapse: separate;',
51
-            'cellspacing' => '_set_table_cellspacing',
52
-            'frame' => [
53
-                'void' => 'border-style: none;',
54
-                'above' => 'border-top-style: solid;',
55
-                'below' => 'border-bottom-style: solid;',
56
-                'hsides' => 'border-left-style: solid; border-right-style: solid;',
57
-                'vsides' => 'border-top-style: solid; border-bottom-style: solid;',
58
-                'lhs' => 'border-left-style: solid;',
59
-                'rhs' => 'border-right-style: solid;',
60
-                'box' => 'border-style: solid;',
61
-                'border' => 'border-style: solid;'
62
-            ],
63
-            'rules' => '_set_table_rules',
64
-            'width' => 'width: %s;',
65
-        ],
66
-        'hr' => [
67
-            'align' => '_set_hr_align', // Need to grab width to set 'left' & 'right' correctly
68
-            'noshade' => 'border-style: solid;',
69
-            'size' => '_set_hr_size', //'border-width: %0.2F px;',
70
-            'width' => 'width: %s;',
71
-        ],
72
-        'div' => [
73
-            'align' => 'text-align: %s;',
74
-        ],
75
-        'h1' => [
76
-            'align' => 'text-align: %s;',
77
-        ],
78
-        'h2' => [
79
-            'align' => 'text-align: %s;',
80
-        ],
81
-        'h3' => [
82
-            'align' => 'text-align: %s;',
83
-        ],
84
-        'h4' => [
85
-            'align' => 'text-align: %s;',
86
-        ],
87
-        'h5' => [
88
-            'align' => 'text-align: %s;',
89
-        ],
90
-        'h6' => [
91
-            'align' => 'text-align: %s;',
92
-        ],
93
-        //TODO: translate more form element attributes
94
-        'input' => [
95
-            'size' => '_set_input_width'
96
-        ],
97
-        'p' => [
98
-            'align' => 'text-align: %s;',
99
-        ],
21
+	static $_style_attr = "_html_style_attribute";
22
+
23
+	// Munged data originally from
24
+	// http://www.w3.org/TR/REC-html40/index/attributes.html
25
+	// http://www.cs.tut.fi/~jkorpela/html2css.html
26
+	private static $__ATTRIBUTE_LOOKUP = [
27
+		//'caption' => array ( 'align' => '', ),
28
+		'img' => [
29
+			'align' => [
30
+				'bottom' => 'vertical-align: baseline;',
31
+				'middle' => 'vertical-align: middle;',
32
+				'top' => 'vertical-align: top;',
33
+				'left' => 'float: left;',
34
+				'right' => 'float: right;'
35
+			],
36
+			'border' => 'border: %0.2Fpx solid;',
37
+			'height' => '_set_px_height',
38
+			'hspace' => 'padding-left: %1$0.2Fpx; padding-right: %1$0.2Fpx;',
39
+			'vspace' => 'padding-top: %1$0.2Fpx; padding-bottom: %1$0.2Fpx;',
40
+			'width' => '_set_px_width',
41
+		],
42
+		'table' => [
43
+			'align' => [
44
+				'left' => 'margin-left: 0; margin-right: auto;',
45
+				'center' => 'margin-left: auto; margin-right: auto;',
46
+				'right' => 'margin-left: auto; margin-right: 0;'
47
+			],
48
+			'bgcolor' => 'background-color: %s;',
49
+			'border' => '_set_table_border',
50
+			'cellpadding' => '_set_table_cellpadding', //'border-spacing: %0.2F; border-collapse: separate;',
51
+			'cellspacing' => '_set_table_cellspacing',
52
+			'frame' => [
53
+				'void' => 'border-style: none;',
54
+				'above' => 'border-top-style: solid;',
55
+				'below' => 'border-bottom-style: solid;',
56
+				'hsides' => 'border-left-style: solid; border-right-style: solid;',
57
+				'vsides' => 'border-top-style: solid; border-bottom-style: solid;',
58
+				'lhs' => 'border-left-style: solid;',
59
+				'rhs' => 'border-right-style: solid;',
60
+				'box' => 'border-style: solid;',
61
+				'border' => 'border-style: solid;'
62
+			],
63
+			'rules' => '_set_table_rules',
64
+			'width' => 'width: %s;',
65
+		],
66
+		'hr' => [
67
+			'align' => '_set_hr_align', // Need to grab width to set 'left' & 'right' correctly
68
+			'noshade' => 'border-style: solid;',
69
+			'size' => '_set_hr_size', //'border-width: %0.2F px;',
70
+			'width' => 'width: %s;',
71
+		],
72
+		'div' => [
73
+			'align' => 'text-align: %s;',
74
+		],
75
+		'h1' => [
76
+			'align' => 'text-align: %s;',
77
+		],
78
+		'h2' => [
79
+			'align' => 'text-align: %s;',
80
+		],
81
+		'h3' => [
82
+			'align' => 'text-align: %s;',
83
+		],
84
+		'h4' => [
85
+			'align' => 'text-align: %s;',
86
+		],
87
+		'h5' => [
88
+			'align' => 'text-align: %s;',
89
+		],
90
+		'h6' => [
91
+			'align' => 'text-align: %s;',
92
+		],
93
+		//TODO: translate more form element attributes
94
+		'input' => [
95
+			'size' => '_set_input_width'
96
+		],
97
+		'p' => [
98
+			'align' => 'text-align: %s;',
99
+		],
100 100
 //    'col' => array(
101 101
 //      'align'  => '',
102 102
 //      'valign' => '',
@@ -105,550 +105,550 @@  discard block
 block discarded – undo
105 105
 //      'align'  => '',
106 106
 //      'valign' => '',
107 107
 //    ),
108
-        'tbody' => [
109
-            'align' => '_set_table_row_align',
110
-            'valign' => '_set_table_row_valign',
111
-        ],
112
-        'td' => [
113
-            'align' => 'text-align: %s;',
114
-            'bgcolor' => '_set_background_color',
115
-            'height' => 'height: %s;',
116
-            'nowrap' => 'white-space: nowrap;',
117
-            'valign' => 'vertical-align: %s;',
118
-            'width' => 'width: %s;',
119
-        ],
120
-        'tfoot' => [
121
-            'align' => '_set_table_row_align',
122
-            'valign' => '_set_table_row_valign',
123
-        ],
124
-        'th' => [
125
-            'align' => 'text-align: %s;',
126
-            'bgcolor' => '_set_background_color',
127
-            'height' => 'height: %s;',
128
-            'nowrap' => 'white-space: nowrap;',
129
-            'valign' => 'vertical-align: %s;',
130
-            'width' => 'width: %s;',
131
-        ],
132
-        'thead' => [
133
-            'align' => '_set_table_row_align',
134
-            'valign' => '_set_table_row_valign',
135
-        ],
136
-        'tr' => [
137
-            'align' => '_set_table_row_align',
138
-            'bgcolor' => '_set_table_row_bgcolor',
139
-            'valign' => '_set_table_row_valign',
140
-        ],
141
-        'body' => [
142
-            'background' => 'background-image: url(%s);',
143
-            'bgcolor' => '_set_background_color',
144
-            'link' => '_set_body_link',
145
-            'text' => '_set_color',
146
-        ],
147
-        'br' => [
148
-            'clear' => 'clear: %s;',
149
-        ],
150
-        'basefont' => [
151
-            'color' => '_set_color',
152
-            'face' => 'font-family: %s;',
153
-            'size' => '_set_basefont_size',
154
-        ],
155
-        'font' => [
156
-            'color' => '_set_color',
157
-            'face' => 'font-family: %s;',
158
-            'size' => '_set_font_size',
159
-        ],
160
-        'dir' => [
161
-            'compact' => 'margin: 0.5em 0;',
162
-        ],
163
-        'dl' => [
164
-            'compact' => 'margin: 0.5em 0;',
165
-        ],
166
-        'menu' => [
167
-            'compact' => 'margin: 0.5em 0;',
168
-        ],
169
-        'ol' => [
170
-            'compact' => 'margin: 0.5em 0;',
171
-            'start' => 'counter-reset: -dompdf-default-counter %d;',
172
-            'type' => 'list-style-type: %s;',
173
-        ],
174
-        'ul' => [
175
-            'compact' => 'margin: 0.5em 0;',
176
-            'type' => 'list-style-type: %s;',
177
-        ],
178
-        'li' => [
179
-            'type' => 'list-style-type: %s;',
180
-            'value' => 'counter-reset: -dompdf-default-counter %d;',
181
-        ],
182
-        'pre' => [
183
-            'width' => 'width: %s;',
184
-        ],
185
-    ];
186
-
187
-    protected static $_last_basefont_size = 3;
188
-    protected static $_font_size_lookup = [
189
-        // For basefont support
190
-        -3 => "4pt",
191
-        -2 => "5pt",
192
-        -1 => "6pt",
193
-        0 => "7pt",
194
-
195
-        1 => "8pt",
196
-        2 => "10pt",
197
-        3 => "12pt",
198
-        4 => "14pt",
199
-        5 => "18pt",
200
-        6 => "24pt",
201
-        7 => "34pt",
202
-
203
-        // For basefont support
204
-        8 => "48pt",
205
-        9 => "44pt",
206
-        10 => "52pt",
207
-        11 => "60pt",
208
-    ];
209
-
210
-    /**
211
-     * @param Frame $frame
212
-     */
213
-    static function translate_attributes(Frame $frame)
214
-    {
215
-        $node = $frame->get_node();
216
-        $tag = $node->nodeName;
217
-
218
-        if (!isset(self::$__ATTRIBUTE_LOOKUP[$tag])) {
219
-            return;
220
-        }
221
-
222
-        $valid_attrs = self::$__ATTRIBUTE_LOOKUP[$tag];
223
-        $attrs = $node->attributes;
224
-        $style = rtrim($node->getAttribute(self::$_style_attr), "; ");
225
-        if ($style != "") {
226
-            $style .= ";";
227
-        }
228
-
229
-        foreach ($attrs as $attr => $attr_node) {
230
-            if (!isset($valid_attrs[$attr])) {
231
-                continue;
232
-            }
233
-
234
-            $value = $attr_node->value;
235
-
236
-            $target = $valid_attrs[$attr];
237
-
238
-            // Look up $value in $target, if $target is an array:
239
-            if (is_array($target)) {
240
-                if (isset($target[$value])) {
241
-                    $style .= " " . self::_resolve_target($node, $target[$value], $value);
242
-                }
243
-            } else {
244
-                // otherwise use target directly
245
-                $style .= " " . self::_resolve_target($node, $target, $value);
246
-            }
247
-        }
248
-
249
-        if (!is_null($style)) {
250
-            $style = ltrim($style);
251
-            $node->setAttribute(self::$_style_attr, $style);
252
-        }
253
-    }
254
-
255
-    /**
256
-     * @param \DOMNode $node
257
-     * @param string $target
258
-     * @param string $value
259
-     *
260
-     * @return string
261
-     */
262
-    protected static function _resolve_target(\DOMNode $node, $target, $value)
263
-    {
264
-        if ($target[0] === "_") {
265
-            return self::$target($node, $value);
266
-        }
267
-
268
-        return $value ? sprintf($target, $value) : "";
269
-    }
270
-
271
-    /**
272
-     * @param \DOMElement $node
273
-     * @param string $new_style
274
-     */
275
-    static function append_style(\DOMElement $node, $new_style)
276
-    {
277
-        $style = rtrim($node->getAttribute(self::$_style_attr), ";");
278
-        $style .= $new_style;
279
-        $style = ltrim($style, ";");
280
-        $node->setAttribute(self::$_style_attr, $style);
281
-    }
282
-
283
-    /**
284
-     * @param \DOMNode $node
285
-     *
286
-     * @return \DOMNodeList|\DOMElement[]
287
-     */
288
-    protected static function get_cell_list(\DOMNode $node)
289
-    {
290
-        $xpath = new \DOMXpath($node->ownerDocument);
291
-
292
-        switch ($node->nodeName) {
293
-            default:
294
-            case "table":
295
-                $query = "tr/td | thead/tr/td | tbody/tr/td | tfoot/tr/td | tr/th | thead/tr/th | tbody/tr/th | tfoot/tr/th";
296
-                break;
297
-
298
-            case "tbody":
299
-            case "tfoot":
300
-            case "thead":
301
-                $query = "tr/td | tr/th";
302
-                break;
303
-
304
-            case "tr":
305
-                $query = "td | th";
306
-                break;
307
-        }
308
-
309
-        return $xpath->query($query, $node);
310
-    }
311
-
312
-    /**
313
-     * @param string $value
314
-     *
315
-     * @return string
316
-     */
317
-    protected static function _get_valid_color($value)
318
-    {
319
-        if (preg_match('/^#?([0-9A-F]{6})$/i', $value, $matches)) {
320
-            $value = "#$matches[1]";
321
-        }
322
-
323
-        return $value;
324
-    }
325
-
326
-    /**
327
-     * @param \DOMElement $node
328
-     * @param string $value
329
-     *
330
-     * @return string
331
-     */
332
-    protected static function _set_color(\DOMElement $node, $value)
333
-    {
334
-        $value = self::_get_valid_color($value);
335
-
336
-        return "color: $value;";
337
-    }
338
-
339
-    /**
340
-     * @param \DOMElement $node
341
-     * @param string $value
342
-     *
343
-     * @return string
344
-     */
345
-    protected static function _set_background_color(\DOMElement $node, $value)
346
-    {
347
-        $value = self::_get_valid_color($value);
348
-
349
-        return "background-color: $value;";
350
-    }
351
-
352
-    protected static function _set_px_width(\DOMElement $node, string $value): string
353
-    {
354
-        $v = trim($value);
355
-
356
-        if (Helpers::is_percent($v)) {
357
-            return sprintf("width: %s;", $v);
358
-        }
359
-
360
-        if (is_numeric(mb_substr($v, 0, 1))) {
361
-            return sprintf("width: %spx;", (float) $v);
362
-        }
363
-
364
-        return "";
365
-    }
366
-
367
-    protected static function _set_px_height(\DOMElement $node, string $value): string
368
-    {
369
-        $v = trim($value);
370
-
371
-        if (Helpers::is_percent($v)) {
372
-            return sprintf("height: %s;", $v);
373
-        }
374
-
375
-        if (is_numeric(mb_substr($v, 0, 1))) {
376
-            return sprintf("height: %spx;", (float) $v);
377
-        }
378
-
379
-        return "";
380
-    }
381
-
382
-    /**
383
-     * @param \DOMElement $node
384
-     * @param string $value
385
-     *
386
-     * @return null
387
-     */
388
-    protected static function _set_table_cellpadding(\DOMElement $node, $value)
389
-    {
390
-        $cell_list = self::get_cell_list($node);
391
-
392
-        foreach ($cell_list as $cell) {
393
-            self::append_style($cell, "; padding: {$value}px;");
394
-        }
395
-
396
-        return null;
397
-    }
398
-
399
-    /**
400
-     * @param \DOMElement $node
401
-     * @param string $value
402
-     *
403
-     * @return string
404
-     */
405
-    protected static function _set_table_border(\DOMElement $node, $value)
406
-    {
407
-        return "border-width: $value" . "px;";
408
-    }
409
-
410
-    /**
411
-     * @param \DOMElement $node
412
-     * @param string $value
413
-     *
414
-     * @return string
415
-     */
416
-    protected static function _set_table_cellspacing(\DOMElement $node, $value)
417
-    {
418
-        $style = rtrim($node->getAttribute(self::$_style_attr), ";");
419
-
420
-        if ($value == 0) {
421
-            $style .= "; border-collapse: collapse;";
422
-        } else {
423
-            $style .= "; border-spacing: {$value}px; border-collapse: separate;";
424
-        }
425
-
426
-        return ltrim($style, ";");
427
-    }
428
-
429
-    /**
430
-     * @param \DOMElement $node
431
-     * @param string $value
432
-     *
433
-     * @return null|string
434
-     */
435
-    protected static function _set_table_rules(\DOMElement $node, $value)
436
-    {
437
-        $new_style = "; border-collapse: collapse;";
438
-
439
-        switch ($value) {
440
-            case "none":
441
-                $new_style .= "border-style: none;";
442
-                break;
443
-
444
-            case "groups":
445
-                // FIXME: unsupported
446
-                return null;
447
-
448
-            case "rows":
449
-                $new_style .= "border-style: solid none solid none; border-width: 1px; ";
450
-                break;
451
-
452
-            case "cols":
453
-                $new_style .= "border-style: none solid none solid; border-width: 1px; ";
454
-                break;
455
-
456
-            case "all":
457
-                $new_style .= "border-style: solid; border-width: 1px; ";
458
-                break;
459
-
460
-            default:
461
-                // Invalid value
462
-                return null;
463
-        }
464
-
465
-        $cell_list = self::get_cell_list($node);
466
-
467
-        foreach ($cell_list as $cell) {
468
-            $style = $cell->getAttribute(self::$_style_attr);
469
-            $style .= $new_style;
470
-            $cell->setAttribute(self::$_style_attr, $style);
471
-        }
472
-
473
-        $style = rtrim($node->getAttribute(self::$_style_attr), ";");
474
-        $style .= "; border-collapse: collapse; ";
475
-
476
-        return ltrim($style, "; ");
477
-    }
478
-
479
-    /**
480
-     * @param \DOMElement $node
481
-     * @param string $value
482
-     *
483
-     * @return string
484
-     */
485
-    protected static function _set_hr_size(\DOMElement $node, $value)
486
-    {
487
-        $style = rtrim($node->getAttribute(self::$_style_attr), ";");
488
-        $style .= "; border-width: " . max(0, $value - 2) . "; ";
489
-
490
-        return ltrim($style, "; ");
491
-    }
492
-
493
-    /**
494
-     * @param \DOMElement $node
495
-     * @param string $value
496
-     *
497
-     * @return null|string
498
-     */
499
-    protected static function _set_hr_align(\DOMElement $node, $value)
500
-    {
501
-        $style = rtrim($node->getAttribute(self::$_style_attr), ";");
502
-        $width = $node->getAttribute("width");
503
-
504
-        if ($width == "") {
505
-            $width = "100%";
506
-        }
507
-
508
-        $remainder = 100 - (double)rtrim($width, "% ");
509
-
510
-        switch ($value) {
511
-            case "left":
512
-                $style .= "; margin-right: $remainder %;";
513
-                break;
514
-
515
-            case "right":
516
-                $style .= "; margin-left: $remainder %;";
517
-                break;
518
-
519
-            case "center":
520
-                $style .= "; margin-left: auto; margin-right: auto;";
521
-                break;
522
-
523
-            default:
524
-                return null;
525
-        }
526
-
527
-        return ltrim($style, "; ");
528
-    }
529
-
530
-    /**
531
-     * @param \DOMElement $node
532
-     * @param string $value
533
-     *
534
-     * @return null|string
535
-     */
536
-    protected static function _set_input_width(\DOMElement $node, $value)
537
-    {
538
-        if (empty($value)) { return null; }
539
-
540
-        if ($node->hasAttribute("type") && in_array(strtolower($node->getAttribute("type")), ["text","password"])) {
541
-            return sprintf("width: %Fem", (((int)$value * .65)+2));
542
-        } else {
543
-            return sprintf("width: %upx;", (int)$value);
544
-        }
545
-    }
546
-
547
-    /**
548
-     * @param \DOMElement $node
549
-     * @param string $value
550
-     *
551
-     * @return null
552
-     */
553
-    protected static function _set_table_row_align(\DOMElement $node, $value)
554
-    {
555
-        $cell_list = self::get_cell_list($node);
556
-
557
-        foreach ($cell_list as $cell) {
558
-            self::append_style($cell, "; text-align: $value;");
559
-        }
560
-
561
-        return null;
562
-    }
563
-
564
-    /**
565
-     * @param \DOMElement $node
566
-     * @param string $value
567
-     *
568
-     * @return null
569
-     */
570
-    protected static function _set_table_row_valign(\DOMElement $node, $value)
571
-    {
572
-        $cell_list = self::get_cell_list($node);
573
-
574
-        foreach ($cell_list as $cell) {
575
-            self::append_style($cell, "; vertical-align: $value;");
576
-        }
577
-
578
-        return null;
579
-    }
580
-
581
-    /**
582
-     * @param \DOMElement $node
583
-     * @param string $value
584
-     *
585
-     * @return null
586
-     */
587
-    protected static function _set_table_row_bgcolor(\DOMElement $node, $value)
588
-    {
589
-        $cell_list = self::get_cell_list($node);
590
-        $value = self::_get_valid_color($value);
591
-
592
-        foreach ($cell_list as $cell) {
593
-            self::append_style($cell, "; background-color: $value;");
594
-        }
595
-
596
-        return null;
597
-    }
598
-
599
-    /**
600
-     * @param \DOMElement $node
601
-     * @param string $value
602
-     *
603
-     * @return null
604
-     */
605
-    protected static function _set_body_link(\DOMElement $node, $value)
606
-    {
607
-        $a_list = $node->getElementsByTagName("a");
608
-        $value = self::_get_valid_color($value);
609
-
610
-        foreach ($a_list as $a) {
611
-            self::append_style($a, "; color: $value;");
612
-        }
613
-
614
-        return null;
615
-    }
616
-
617
-    /**
618
-     * @param \DOMElement $node
619
-     * @param string $value
620
-     *
621
-     * @return null
622
-     */
623
-    protected static function _set_basefont_size(\DOMElement $node, $value)
624
-    {
625
-        // FIXME: ? we don't actually set the font size of anything here, just
626
-        // the base size for later modification by <font> tags.
627
-        self::$_last_basefont_size = $value;
628
-
629
-        return null;
630
-    }
631
-
632
-    /**
633
-     * @param \DOMElement $node
634
-     * @param string $value
635
-     *
636
-     * @return string
637
-     */
638
-    protected static function _set_font_size(\DOMElement $node, $value)
639
-    {
640
-        $style = $node->getAttribute(self::$_style_attr);
641
-
642
-        if ($value[0] === "-" || $value[0] === "+") {
643
-            $value = self::$_last_basefont_size + (int)$value;
644
-        }
645
-
646
-        if (isset(self::$_font_size_lookup[$value])) {
647
-            $style .= "; font-size: " . self::$_font_size_lookup[$value] . ";";
648
-        } else {
649
-            $style .= "; font-size: $value;";
650
-        }
651
-
652
-        return ltrim($style, "; ");
653
-    }
108
+		'tbody' => [
109
+			'align' => '_set_table_row_align',
110
+			'valign' => '_set_table_row_valign',
111
+		],
112
+		'td' => [
113
+			'align' => 'text-align: %s;',
114
+			'bgcolor' => '_set_background_color',
115
+			'height' => 'height: %s;',
116
+			'nowrap' => 'white-space: nowrap;',
117
+			'valign' => 'vertical-align: %s;',
118
+			'width' => 'width: %s;',
119
+		],
120
+		'tfoot' => [
121
+			'align' => '_set_table_row_align',
122
+			'valign' => '_set_table_row_valign',
123
+		],
124
+		'th' => [
125
+			'align' => 'text-align: %s;',
126
+			'bgcolor' => '_set_background_color',
127
+			'height' => 'height: %s;',
128
+			'nowrap' => 'white-space: nowrap;',
129
+			'valign' => 'vertical-align: %s;',
130
+			'width' => 'width: %s;',
131
+		],
132
+		'thead' => [
133
+			'align' => '_set_table_row_align',
134
+			'valign' => '_set_table_row_valign',
135
+		],
136
+		'tr' => [
137
+			'align' => '_set_table_row_align',
138
+			'bgcolor' => '_set_table_row_bgcolor',
139
+			'valign' => '_set_table_row_valign',
140
+		],
141
+		'body' => [
142
+			'background' => 'background-image: url(%s);',
143
+			'bgcolor' => '_set_background_color',
144
+			'link' => '_set_body_link',
145
+			'text' => '_set_color',
146
+		],
147
+		'br' => [
148
+			'clear' => 'clear: %s;',
149
+		],
150
+		'basefont' => [
151
+			'color' => '_set_color',
152
+			'face' => 'font-family: %s;',
153
+			'size' => '_set_basefont_size',
154
+		],
155
+		'font' => [
156
+			'color' => '_set_color',
157
+			'face' => 'font-family: %s;',
158
+			'size' => '_set_font_size',
159
+		],
160
+		'dir' => [
161
+			'compact' => 'margin: 0.5em 0;',
162
+		],
163
+		'dl' => [
164
+			'compact' => 'margin: 0.5em 0;',
165
+		],
166
+		'menu' => [
167
+			'compact' => 'margin: 0.5em 0;',
168
+		],
169
+		'ol' => [
170
+			'compact' => 'margin: 0.5em 0;',
171
+			'start' => 'counter-reset: -dompdf-default-counter %d;',
172
+			'type' => 'list-style-type: %s;',
173
+		],
174
+		'ul' => [
175
+			'compact' => 'margin: 0.5em 0;',
176
+			'type' => 'list-style-type: %s;',
177
+		],
178
+		'li' => [
179
+			'type' => 'list-style-type: %s;',
180
+			'value' => 'counter-reset: -dompdf-default-counter %d;',
181
+		],
182
+		'pre' => [
183
+			'width' => 'width: %s;',
184
+		],
185
+	];
186
+
187
+	protected static $_last_basefont_size = 3;
188
+	protected static $_font_size_lookup = [
189
+		// For basefont support
190
+		-3 => "4pt",
191
+		-2 => "5pt",
192
+		-1 => "6pt",
193
+		0 => "7pt",
194
+
195
+		1 => "8pt",
196
+		2 => "10pt",
197
+		3 => "12pt",
198
+		4 => "14pt",
199
+		5 => "18pt",
200
+		6 => "24pt",
201
+		7 => "34pt",
202
+
203
+		// For basefont support
204
+		8 => "48pt",
205
+		9 => "44pt",
206
+		10 => "52pt",
207
+		11 => "60pt",
208
+	];
209
+
210
+	/**
211
+	 * @param Frame $frame
212
+	 */
213
+	static function translate_attributes(Frame $frame)
214
+	{
215
+		$node = $frame->get_node();
216
+		$tag = $node->nodeName;
217
+
218
+		if (!isset(self::$__ATTRIBUTE_LOOKUP[$tag])) {
219
+			return;
220
+		}
221
+
222
+		$valid_attrs = self::$__ATTRIBUTE_LOOKUP[$tag];
223
+		$attrs = $node->attributes;
224
+		$style = rtrim($node->getAttribute(self::$_style_attr), "; ");
225
+		if ($style != "") {
226
+			$style .= ";";
227
+		}
228
+
229
+		foreach ($attrs as $attr => $attr_node) {
230
+			if (!isset($valid_attrs[$attr])) {
231
+				continue;
232
+			}
233
+
234
+			$value = $attr_node->value;
235
+
236
+			$target = $valid_attrs[$attr];
237
+
238
+			// Look up $value in $target, if $target is an array:
239
+			if (is_array($target)) {
240
+				if (isset($target[$value])) {
241
+					$style .= " " . self::_resolve_target($node, $target[$value], $value);
242
+				}
243
+			} else {
244
+				// otherwise use target directly
245
+				$style .= " " . self::_resolve_target($node, $target, $value);
246
+			}
247
+		}
248
+
249
+		if (!is_null($style)) {
250
+			$style = ltrim($style);
251
+			$node->setAttribute(self::$_style_attr, $style);
252
+		}
253
+	}
254
+
255
+	/**
256
+	 * @param \DOMNode $node
257
+	 * @param string $target
258
+	 * @param string $value
259
+	 *
260
+	 * @return string
261
+	 */
262
+	protected static function _resolve_target(\DOMNode $node, $target, $value)
263
+	{
264
+		if ($target[0] === "_") {
265
+			return self::$target($node, $value);
266
+		}
267
+
268
+		return $value ? sprintf($target, $value) : "";
269
+	}
270
+
271
+	/**
272
+	 * @param \DOMElement $node
273
+	 * @param string $new_style
274
+	 */
275
+	static function append_style(\DOMElement $node, $new_style)
276
+	{
277
+		$style = rtrim($node->getAttribute(self::$_style_attr), ";");
278
+		$style .= $new_style;
279
+		$style = ltrim($style, ";");
280
+		$node->setAttribute(self::$_style_attr, $style);
281
+	}
282
+
283
+	/**
284
+	 * @param \DOMNode $node
285
+	 *
286
+	 * @return \DOMNodeList|\DOMElement[]
287
+	 */
288
+	protected static function get_cell_list(\DOMNode $node)
289
+	{
290
+		$xpath = new \DOMXpath($node->ownerDocument);
291
+
292
+		switch ($node->nodeName) {
293
+			default:
294
+			case "table":
295
+				$query = "tr/td | thead/tr/td | tbody/tr/td | tfoot/tr/td | tr/th | thead/tr/th | tbody/tr/th | tfoot/tr/th";
296
+				break;
297
+
298
+			case "tbody":
299
+			case "tfoot":
300
+			case "thead":
301
+				$query = "tr/td | tr/th";
302
+				break;
303
+
304
+			case "tr":
305
+				$query = "td | th";
306
+				break;
307
+		}
308
+
309
+		return $xpath->query($query, $node);
310
+	}
311
+
312
+	/**
313
+	 * @param string $value
314
+	 *
315
+	 * @return string
316
+	 */
317
+	protected static function _get_valid_color($value)
318
+	{
319
+		if (preg_match('/^#?([0-9A-F]{6})$/i', $value, $matches)) {
320
+			$value = "#$matches[1]";
321
+		}
322
+
323
+		return $value;
324
+	}
325
+
326
+	/**
327
+	 * @param \DOMElement $node
328
+	 * @param string $value
329
+	 *
330
+	 * @return string
331
+	 */
332
+	protected static function _set_color(\DOMElement $node, $value)
333
+	{
334
+		$value = self::_get_valid_color($value);
335
+
336
+		return "color: $value;";
337
+	}
338
+
339
+	/**
340
+	 * @param \DOMElement $node
341
+	 * @param string $value
342
+	 *
343
+	 * @return string
344
+	 */
345
+	protected static function _set_background_color(\DOMElement $node, $value)
346
+	{
347
+		$value = self::_get_valid_color($value);
348
+
349
+		return "background-color: $value;";
350
+	}
351
+
352
+	protected static function _set_px_width(\DOMElement $node, string $value): string
353
+	{
354
+		$v = trim($value);
355
+
356
+		if (Helpers::is_percent($v)) {
357
+			return sprintf("width: %s;", $v);
358
+		}
359
+
360
+		if (is_numeric(mb_substr($v, 0, 1))) {
361
+			return sprintf("width: %spx;", (float) $v);
362
+		}
363
+
364
+		return "";
365
+	}
366
+
367
+	protected static function _set_px_height(\DOMElement $node, string $value): string
368
+	{
369
+		$v = trim($value);
370
+
371
+		if (Helpers::is_percent($v)) {
372
+			return sprintf("height: %s;", $v);
373
+		}
374
+
375
+		if (is_numeric(mb_substr($v, 0, 1))) {
376
+			return sprintf("height: %spx;", (float) $v);
377
+		}
378
+
379
+		return "";
380
+	}
381
+
382
+	/**
383
+	 * @param \DOMElement $node
384
+	 * @param string $value
385
+	 *
386
+	 * @return null
387
+	 */
388
+	protected static function _set_table_cellpadding(\DOMElement $node, $value)
389
+	{
390
+		$cell_list = self::get_cell_list($node);
391
+
392
+		foreach ($cell_list as $cell) {
393
+			self::append_style($cell, "; padding: {$value}px;");
394
+		}
395
+
396
+		return null;
397
+	}
398
+
399
+	/**
400
+	 * @param \DOMElement $node
401
+	 * @param string $value
402
+	 *
403
+	 * @return string
404
+	 */
405
+	protected static function _set_table_border(\DOMElement $node, $value)
406
+	{
407
+		return "border-width: $value" . "px;";
408
+	}
409
+
410
+	/**
411
+	 * @param \DOMElement $node
412
+	 * @param string $value
413
+	 *
414
+	 * @return string
415
+	 */
416
+	protected static function _set_table_cellspacing(\DOMElement $node, $value)
417
+	{
418
+		$style = rtrim($node->getAttribute(self::$_style_attr), ";");
419
+
420
+		if ($value == 0) {
421
+			$style .= "; border-collapse: collapse;";
422
+		} else {
423
+			$style .= "; border-spacing: {$value}px; border-collapse: separate;";
424
+		}
425
+
426
+		return ltrim($style, ";");
427
+	}
428
+
429
+	/**
430
+	 * @param \DOMElement $node
431
+	 * @param string $value
432
+	 *
433
+	 * @return null|string
434
+	 */
435
+	protected static function _set_table_rules(\DOMElement $node, $value)
436
+	{
437
+		$new_style = "; border-collapse: collapse;";
438
+
439
+		switch ($value) {
440
+			case "none":
441
+				$new_style .= "border-style: none;";
442
+				break;
443
+
444
+			case "groups":
445
+				// FIXME: unsupported
446
+				return null;
447
+
448
+			case "rows":
449
+				$new_style .= "border-style: solid none solid none; border-width: 1px; ";
450
+				break;
451
+
452
+			case "cols":
453
+				$new_style .= "border-style: none solid none solid; border-width: 1px; ";
454
+				break;
455
+
456
+			case "all":
457
+				$new_style .= "border-style: solid; border-width: 1px; ";
458
+				break;
459
+
460
+			default:
461
+				// Invalid value
462
+				return null;
463
+		}
464
+
465
+		$cell_list = self::get_cell_list($node);
466
+
467
+		foreach ($cell_list as $cell) {
468
+			$style = $cell->getAttribute(self::$_style_attr);
469
+			$style .= $new_style;
470
+			$cell->setAttribute(self::$_style_attr, $style);
471
+		}
472
+
473
+		$style = rtrim($node->getAttribute(self::$_style_attr), ";");
474
+		$style .= "; border-collapse: collapse; ";
475
+
476
+		return ltrim($style, "; ");
477
+	}
478
+
479
+	/**
480
+	 * @param \DOMElement $node
481
+	 * @param string $value
482
+	 *
483
+	 * @return string
484
+	 */
485
+	protected static function _set_hr_size(\DOMElement $node, $value)
486
+	{
487
+		$style = rtrim($node->getAttribute(self::$_style_attr), ";");
488
+		$style .= "; border-width: " . max(0, $value - 2) . "; ";
489
+
490
+		return ltrim($style, "; ");
491
+	}
492
+
493
+	/**
494
+	 * @param \DOMElement $node
495
+	 * @param string $value
496
+	 *
497
+	 * @return null|string
498
+	 */
499
+	protected static function _set_hr_align(\DOMElement $node, $value)
500
+	{
501
+		$style = rtrim($node->getAttribute(self::$_style_attr), ";");
502
+		$width = $node->getAttribute("width");
503
+
504
+		if ($width == "") {
505
+			$width = "100%";
506
+		}
507
+
508
+		$remainder = 100 - (double)rtrim($width, "% ");
509
+
510
+		switch ($value) {
511
+			case "left":
512
+				$style .= "; margin-right: $remainder %;";
513
+				break;
514
+
515
+			case "right":
516
+				$style .= "; margin-left: $remainder %;";
517
+				break;
518
+
519
+			case "center":
520
+				$style .= "; margin-left: auto; margin-right: auto;";
521
+				break;
522
+
523
+			default:
524
+				return null;
525
+		}
526
+
527
+		return ltrim($style, "; ");
528
+	}
529
+
530
+	/**
531
+	 * @param \DOMElement $node
532
+	 * @param string $value
533
+	 *
534
+	 * @return null|string
535
+	 */
536
+	protected static function _set_input_width(\DOMElement $node, $value)
537
+	{
538
+		if (empty($value)) { return null; }
539
+
540
+		if ($node->hasAttribute("type") && in_array(strtolower($node->getAttribute("type")), ["text","password"])) {
541
+			return sprintf("width: %Fem", (((int)$value * .65)+2));
542
+		} else {
543
+			return sprintf("width: %upx;", (int)$value);
544
+		}
545
+	}
546
+
547
+	/**
548
+	 * @param \DOMElement $node
549
+	 * @param string $value
550
+	 *
551
+	 * @return null
552
+	 */
553
+	protected static function _set_table_row_align(\DOMElement $node, $value)
554
+	{
555
+		$cell_list = self::get_cell_list($node);
556
+
557
+		foreach ($cell_list as $cell) {
558
+			self::append_style($cell, "; text-align: $value;");
559
+		}
560
+
561
+		return null;
562
+	}
563
+
564
+	/**
565
+	 * @param \DOMElement $node
566
+	 * @param string $value
567
+	 *
568
+	 * @return null
569
+	 */
570
+	protected static function _set_table_row_valign(\DOMElement $node, $value)
571
+	{
572
+		$cell_list = self::get_cell_list($node);
573
+
574
+		foreach ($cell_list as $cell) {
575
+			self::append_style($cell, "; vertical-align: $value;");
576
+		}
577
+
578
+		return null;
579
+	}
580
+
581
+	/**
582
+	 * @param \DOMElement $node
583
+	 * @param string $value
584
+	 *
585
+	 * @return null
586
+	 */
587
+	protected static function _set_table_row_bgcolor(\DOMElement $node, $value)
588
+	{
589
+		$cell_list = self::get_cell_list($node);
590
+		$value = self::_get_valid_color($value);
591
+
592
+		foreach ($cell_list as $cell) {
593
+			self::append_style($cell, "; background-color: $value;");
594
+		}
595
+
596
+		return null;
597
+	}
598
+
599
+	/**
600
+	 * @param \DOMElement $node
601
+	 * @param string $value
602
+	 *
603
+	 * @return null
604
+	 */
605
+	protected static function _set_body_link(\DOMElement $node, $value)
606
+	{
607
+		$a_list = $node->getElementsByTagName("a");
608
+		$value = self::_get_valid_color($value);
609
+
610
+		foreach ($a_list as $a) {
611
+			self::append_style($a, "; color: $value;");
612
+		}
613
+
614
+		return null;
615
+	}
616
+
617
+	/**
618
+	 * @param \DOMElement $node
619
+	 * @param string $value
620
+	 *
621
+	 * @return null
622
+	 */
623
+	protected static function _set_basefont_size(\DOMElement $node, $value)
624
+	{
625
+		// FIXME: ? we don't actually set the font size of anything here, just
626
+		// the base size for later modification by <font> tags.
627
+		self::$_last_basefont_size = $value;
628
+
629
+		return null;
630
+	}
631
+
632
+	/**
633
+	 * @param \DOMElement $node
634
+	 * @param string $value
635
+	 *
636
+	 * @return string
637
+	 */
638
+	protected static function _set_font_size(\DOMElement $node, $value)
639
+	{
640
+		$style = $node->getAttribute(self::$_style_attr);
641
+
642
+		if ($value[0] === "-" || $value[0] === "+") {
643
+			$value = self::$_last_basefont_size + (int)$value;
644
+		}
645
+
646
+		if (isset(self::$_font_size_lookup[$value])) {
647
+			$style .= "; font-size: " . self::$_font_size_lookup[$value] . ";";
648
+		} else {
649
+			$style .= "; font-size: $value;";
650
+		}
651
+
652
+		return ltrim($style, "; ");
653
+	}
654 654
 }
Please login to merge, or discard this patch.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -215,7 +215,7 @@  discard block
 block discarded – undo
215 215
         $node = $frame->get_node();
216 216
         $tag = $node->nodeName;
217 217
 
218
-        if (!isset(self::$__ATTRIBUTE_LOOKUP[$tag])) {
218
+        if ( ! isset(self::$__ATTRIBUTE_LOOKUP[$tag])) {
219 219
             return;
220 220
         }
221 221
 
@@ -227,7 +227,7 @@  discard block
 block discarded – undo
227 227
         }
228 228
 
229 229
         foreach ($attrs as $attr => $attr_node) {
230
-            if (!isset($valid_attrs[$attr])) {
230
+            if ( ! isset($valid_attrs[$attr])) {
231 231
                 continue;
232 232
             }
233 233
 
@@ -238,15 +238,15 @@  discard block
 block discarded – undo
238 238
             // Look up $value in $target, if $target is an array:
239 239
             if (is_array($target)) {
240 240
                 if (isset($target[$value])) {
241
-                    $style .= " " . self::_resolve_target($node, $target[$value], $value);
241
+                    $style .= " ".self::_resolve_target($node, $target[$value], $value);
242 242
                 }
243 243
             } else {
244 244
                 // otherwise use target directly
245
-                $style .= " " . self::_resolve_target($node, $target, $value);
245
+                $style .= " ".self::_resolve_target($node, $target, $value);
246 246
             }
247 247
         }
248 248
 
249
-        if (!is_null($style)) {
249
+        if ( ! is_null($style)) {
250 250
             $style = ltrim($style);
251 251
             $node->setAttribute(self::$_style_attr, $style);
252 252
         }
@@ -404,7 +404,7 @@  discard block
 block discarded – undo
404 404
      */
405 405
     protected static function _set_table_border(\DOMElement $node, $value)
406 406
     {
407
-        return "border-width: $value" . "px;";
407
+        return "border-width: $value"."px;";
408 408
     }
409 409
 
410 410
     /**
@@ -485,7 +485,7 @@  discard block
 block discarded – undo
485 485
     protected static function _set_hr_size(\DOMElement $node, $value)
486 486
     {
487 487
         $style = rtrim($node->getAttribute(self::$_style_attr), ";");
488
-        $style .= "; border-width: " . max(0, $value - 2) . "; ";
488
+        $style .= "; border-width: ".max(0, $value - 2)."; ";
489 489
 
490 490
         return ltrim($style, "; ");
491 491
     }
@@ -505,7 +505,7 @@  discard block
 block discarded – undo
505 505
             $width = "100%";
506 506
         }
507 507
 
508
-        $remainder = 100 - (double)rtrim($width, "% ");
508
+        $remainder = 100 - (double) rtrim($width, "% ");
509 509
 
510 510
         switch ($value) {
511 511
             case "left":
@@ -537,10 +537,10 @@  discard block
 block discarded – undo
537 537
     {
538 538
         if (empty($value)) { return null; }
539 539
 
540
-        if ($node->hasAttribute("type") && in_array(strtolower($node->getAttribute("type")), ["text","password"])) {
541
-            return sprintf("width: %Fem", (((int)$value * .65)+2));
540
+        if ($node->hasAttribute("type") && in_array(strtolower($node->getAttribute("type")), ["text", "password"])) {
541
+            return sprintf("width: %Fem", (((int) $value * .65) + 2));
542 542
         } else {
543
-            return sprintf("width: %upx;", (int)$value);
543
+            return sprintf("width: %upx;", (int) $value);
544 544
         }
545 545
     }
546 546
 
@@ -640,11 +640,11 @@  discard block
 block discarded – undo
640 640
         $style = $node->getAttribute(self::$_style_attr);
641 641
 
642 642
         if ($value[0] === "-" || $value[0] === "+") {
643
-            $value = self::$_last_basefont_size + (int)$value;
643
+            $value = self::$_last_basefont_size + (int) $value;
644 644
         }
645 645
 
646 646
         if (isset(self::$_font_size_lookup[$value])) {
647
-            $style .= "; font-size: " . self::$_font_size_lookup[$value] . ";";
647
+            $style .= "; font-size: ".self::$_font_size_lookup[$value].";";
648 648
         } else {
649 649
             $style .= "; font-size: $value;";
650 650
         }
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Css/Style.php 3 patches
Indentation   +3569 added lines, -3569 removed lines patch added patch discarded remove patch
@@ -173,3575 +173,3575 @@
 block discarded – undo
173 173
  */
174 174
 class Style
175 175
 {
176
-    protected const CSS_IDENTIFIER = "-?[_a-zA-Z]+[_a-zA-Z0-9-]*";
177
-    protected const CSS_INTEGER = "[+-]?\d+";
178
-    protected const CSS_NUMBER = "[+-]?\d*\.?\d+(?:[eE][+-]?\d+)?";
179
-
180
-    /**
181
-     * Default font size, in points.
182
-     *
183
-     * @var float
184
-     */
185
-    public static $default_font_size = 12;
186
-
187
-    /**
188
-     * Default line height, as a fraction of the font size.
189
-     *
190
-     * @var float
191
-     */
192
-    public static $default_line_height = 1.2;
193
-
194
-    /**
195
-     * Default "absolute" font sizes relative to the default font-size
196
-     * https://www.w3.org/TR/css-fonts-3/#absolute-size-value
197
-     *
198
-     * @var array<float>
199
-     */
200
-    public static $font_size_keywords = [
201
-        "xx-small" => 0.6, // 3/5
202
-        "x-small" => 0.75, // 3/4
203
-        "small" => 0.889, // 8/9
204
-        "medium" => 1, // 1
205
-        "large" => 1.2, // 6/5
206
-        "x-large" => 1.5, // 3/2
207
-        "xx-large" => 2.0, // 2/1
208
-    ];
209
-
210
-    /**
211
-     * List of valid text-align keywords.
212
-     */
213
-    public const TEXT_ALIGN_KEYWORDS = ["left", "right", "center", "justify"];
214
-
215
-    /**
216
-     * List of valid vertical-align keywords.
217
-     */
218
-    public const VERTICAL_ALIGN_KEYWORDS = ["baseline", "bottom", "middle",
219
-        "sub", "super", "text-bottom", "text-top", "top"];
220
-
221
-    /**
222
-     * List of all block-level (outer) display types.
223
-     * * https://www.w3.org/TR/css-display-3/#display-type
224
-     * * https://www.w3.org/TR/css-display-3/#block-level
225
-     */
226
-    public const BLOCK_LEVEL_TYPES = [
227
-        "block",
228
-        // "flow-root",
229
-        "list-item",
230
-        // "flex",
231
-        // "grid",
232
-        "table"
233
-    ];
234
-
235
-    /**
236
-     * List of all inline-level (outer) display types.
237
-     * * https://www.w3.org/TR/css-display-3/#display-type
238
-     * * https://www.w3.org/TR/css-display-3/#inline-level
239
-     */
240
-    public const INLINE_LEVEL_TYPES = [
241
-        "inline",
242
-        "inline-block",
243
-        // "inline-flex",
244
-        // "inline-grid",
245
-        "inline-table"
246
-    ];
247
-
248
-    /**
249
-     * List of all table-internal (outer) display types.
250
-     * * https://www.w3.org/TR/css-display-3/#layout-specific-display
251
-     */
252
-    public const TABLE_INTERNAL_TYPES = [
253
-        "table-row-group",
254
-        "table-header-group",
255
-        "table-footer-group",
256
-        "table-row",
257
-        "table-cell",
258
-        "table-column-group",
259
-        "table-column",
260
-        "table-caption"
261
-    ];
262
-
263
-    /**
264
-     * List of all inline (inner) display types.
265
-     */
266
-    public const INLINE_TYPES = ["inline"];
267
-
268
-    /**
269
-     * List of all block (inner) display types.
270
-     */
271
-    public const BLOCK_TYPES = ["block", "inline-block", "table-cell", "list-item"];
272
-
273
-    /**
274
-     * List of all table (inner) display types.
275
-     */
276
-    public const TABLE_TYPES = ["table", "inline-table"];
277
-
278
-    /**
279
-     * Lookup table for valid display types. Initially computed from the
280
-     * different constants.
281
-     *
282
-     * @var array
283
-     */
284
-    protected static $valid_display_types = [];
285
-
286
-    /**
287
-     * List of all positioned types.
288
-     */
289
-    public const POSITIONED_TYPES = ["relative", "absolute", "fixed"];
290
-
291
-    /**
292
-     * List of valid border styles.
293
-     */
294
-    public const BORDER_STYLES = [
295
-        "none", "hidden",
296
-        "dotted", "dashed", "solid",
297
-        "double", "groove", "ridge", "inset", "outset"
298
-    ];
299
-
300
-    /**
301
-     * List of valid outline-style values.
302
-     * Same as the border styles, except `auto` is allowed, `hidden` is not.
303
-     *
304
-     * @link https://www.w3.org/TR/css-ui-4/#typedef-outline-line-style
305
-     */
306
-    protected const OUTLINE_STYLES = [
307
-        "auto", "none",
308
-        "dotted", "dashed", "solid",
309
-        "double", "groove", "ridge", "inset", "outset"
310
-    ];
311
-
312
-    /**
313
-     * Map of CSS shorthand properties and their corresponding sub-properties.
314
-     * The order of the sub-properties is relevant for the fallback getter,
315
-     * which is used in case no specific getter method is defined.
316
-     *
317
-     * @var array
318
-     */
319
-    protected static $_props_shorthand = [
320
-        "background" => [
321
-            "background_image",
322
-            "background_position",
323
-            "background_size",
324
-            "background_repeat",
325
-            // "background_origin",
326
-            // "background_clip",
327
-            "background_attachment",
328
-            "background_color"
329
-        ],
330
-        "border" => [
331
-            "border_top_width",
332
-            "border_right_width",
333
-            "border_bottom_width",
334
-            "border_left_width",
335
-            "border_top_style",
336
-            "border_right_style",
337
-            "border_bottom_style",
338
-            "border_left_style",
339
-            "border_top_color",
340
-            "border_right_color",
341
-            "border_bottom_color",
342
-            "border_left_color"
343
-        ],
344
-        "border_top" => [
345
-            "border_top_width",
346
-            "border_top_style",
347
-            "border_top_color"
348
-        ],
349
-        "border_right" => [
350
-            "border_right_width",
351
-            "border_right_style",
352
-            "border_right_color"
353
-        ],
354
-        "border_bottom" => [
355
-            "border_bottom_width",
356
-            "border_bottom_style",
357
-            "border_bottom_color"
358
-        ],
359
-        "border_left" => [
360
-            "border_left_width",
361
-            "border_left_style",
362
-            "border_left_color"
363
-        ],
364
-        "border_width" => [
365
-            "border_top_width",
366
-            "border_right_width",
367
-            "border_bottom_width",
368
-            "border_left_width"
369
-        ],
370
-        "border_style" => [
371
-            "border_top_style",
372
-            "border_right_style",
373
-            "border_bottom_style",
374
-            "border_left_style"
375
-        ],
376
-        "border_color" => [
377
-            "border_top_color",
378
-            "border_right_color",
379
-            "border_bottom_color",
380
-            "border_left_color"
381
-        ],
382
-        "border_radius" => [
383
-            "border_top_left_radius",
384
-            "border_top_right_radius",
385
-            "border_bottom_right_radius",
386
-            "border_bottom_left_radius"
387
-        ],
388
-        "font" => [
389
-            "font_family",
390
-            "font_size",
391
-            // "font_stretch",
392
-            "font_style",
393
-            "font_variant",
394
-            "font_weight",
395
-            "line_height"
396
-        ],
397
-        "inset" => [
398
-            "top",
399
-            "right",
400
-            "bottom",
401
-            "left"
402
-        ],
403
-        "list_style" => [
404
-            "list_style_image",
405
-            "list_style_position",
406
-            "list_style_type"
407
-        ],
408
-        "margin" => [
409
-            "margin_top",
410
-            "margin_right",
411
-            "margin_bottom",
412
-            "margin_left"
413
-        ],
414
-        "padding" => [
415
-            "padding_top",
416
-            "padding_right",
417
-            "padding_bottom",
418
-            "padding_left"
419
-        ],
420
-        "outline" => [
421
-            "outline_width",
422
-            "outline_style",
423
-            "outline_color"
424
-        ]
425
-    ];
426
-
427
-    /**
428
-     * Maps legacy property names to actual property names.
429
-     *
430
-     * @var array
431
-     */
432
-    protected static $_props_alias = [
433
-        "word_wrap"                           => "overflow_wrap",
434
-        "_dompdf_background_image_resolution" => "background_image_resolution",
435
-        "_dompdf_image_resolution"            => "image_resolution",
436
-        "_webkit_transform"                   => "transform",
437
-        "_webkit_transform_origin"            => "transform_origin"
438
-    ];
439
-
440
-    /**
441
-     * Default style values.
442
-     *
443
-     * @link https://www.w3.org/TR/CSS21/propidx.html
444
-     *
445
-     * @var array
446
-     */
447
-    protected static $_defaults = null;
448
-
449
-    /**
450
-     * List of inherited properties
451
-     *
452
-     * @link https://www.w3.org/TR/CSS21/propidx.html
453
-     *
454
-     * @var array
455
-     */
456
-    protected static $_inherited = null;
457
-
458
-    /**
459
-     * Caches method_exists result
460
-     *
461
-     * @var array<bool>
462
-     */
463
-    protected static $_methods_cache = [];
464
-
465
-    /**
466
-     * The stylesheet this style belongs to
467
-     *
468
-     * @var Stylesheet
469
-     */
470
-    protected $_stylesheet;
471
-
472
-    /**
473
-     * Media queries attached to the style
474
-     *
475
-     * @var array
476
-     */
477
-    protected $_media_queries;
478
-
479
-    /**
480
-     * Properties set by an `!important` declaration.
481
-     *
482
-     * @var array
483
-     */
484
-    protected $_important_props = [];
485
-
486
-    /**
487
-     * Specified (or declared) values of the CSS properties.
488
-     *
489
-     * https://www.w3.org/TR/css-cascade-3/#value-stages
490
-     *
491
-     * @var array
492
-     */
493
-    protected $_props = [];
494
-
495
-    /**
496
-     * Computed values of the CSS properties.
497
-     *
498
-     * @var array
499
-     */
500
-    protected $_props_computed = [];
501
-
502
-    /**
503
-     * Used values of the CSS properties.
504
-     *
505
-     * @var array
506
-     */
507
-    protected $_props_used = [];
508
-
509
-    /**
510
-     * Marks properties with non-final used values that should be cleared on
511
-     * style reset.
512
-     *
513
-     * @var array
514
-     */
515
-    protected $non_final_used = [];
516
-
517
-    protected static $_dependency_map = [
518
-        "border_top_style" => [
519
-            "border_top_width"
520
-        ],
521
-        "border_bottom_style" => [
522
-            "border_bottom_width"
523
-        ],
524
-        "border_left_style" => [
525
-            "border_left_width"
526
-        ],
527
-        "border_right_style" => [
528
-            "border_right_width"
529
-        ],
530
-        "direction" => [
531
-            "text_align"
532
-        ],
533
-        "font_size" => [
534
-            "background_position",
535
-            "background_size",
536
-            "border_top_width",
537
-            "border_right_width",
538
-            "border_bottom_width",
539
-            "border_left_width",
540
-            "border_top_left_radius",
541
-            "border_top_right_radius",
542
-            "border_bottom_right_radius",
543
-            "border_bottom_left_radius",
544
-            "letter_spacing",
545
-            "line_height",
546
-            "margin_top",
547
-            "margin_right",
548
-            "margin_bottom",
549
-            "margin_left",
550
-            "outline_width",
551
-            "outline_offset",
552
-            "padding_top",
553
-            "padding_right",
554
-            "padding_bottom",
555
-            "padding_left",
556
-            "word_spacing",
557
-            "width",
558
-            "height",
559
-            "min-width",
560
-            "min-height",
561
-            "max-width",
562
-            "max-height"
563
-        ],
564
-        "float" => [
565
-            "display"
566
-        ],
567
-        "position" => [
568
-            "display"
569
-        ],
570
-        "outline_style" => [
571
-            "outline_width"
572
-        ]
573
-    ];
574
-
575
-    /**
576
-     * Lookup table for dependent properties. Initially computed from the
577
-     * dependency map.
578
-     *
579
-     * @var array
580
-     */
581
-    protected static $_dependent_props = [];
582
-
583
-    /**
584
-     * Style of the parent element in document tree.
585
-     *
586
-     * @var Style
587
-     */
588
-    protected $parent_style;
589
-
590
-    /**
591
-     * @var Frame|null
592
-     */
593
-    protected $_frame;
594
-
595
-    /**
596
-     * The origin of the style
597
-     *
598
-     * @var int
599
-     */
600
-    protected $_origin = Stylesheet::ORIG_AUTHOR;
601
-
602
-    /**
603
-     * The computed bottom spacing
604
-     *
605
-     * @var float|string|null
606
-     */
607
-    private $_computed_bottom_spacing = null;
608
-
609
-    /**
610
-     * @var bool|null
611
-     */
612
-    private $has_border_radius_cache = null;
613
-
614
-    /**
615
-     * @var array|null
616
-     */
617
-    private $resolved_border_radius = null;
618
-
619
-    /**
620
-     * @var FontMetrics
621
-     */
622
-    private $fontMetrics;
623
-
624
-    /**
625
-     * @param Stylesheet $stylesheet The stylesheet the style is associated with.
626
-     * @param int        $origin
627
-     */
628
-    public function __construct(Stylesheet $stylesheet, int $origin = Stylesheet::ORIG_AUTHOR)
629
-    {
630
-        $this->fontMetrics = $stylesheet->getFontMetrics();
631
-
632
-        $this->_stylesheet = $stylesheet;
633
-        $this->_media_queries = [];
634
-        $this->_origin = $origin;
635
-        $this->parent_style = null;
636
-
637
-        if (!isset(self::$_defaults)) {
638
-
639
-            // Shorthand
640
-            $d =& self::$_defaults;
641
-
642
-            // All CSS 2.1 properties, and their default values
643
-            // Some properties are specified with their computed value for
644
-            // efficiency; this only works if the computed value is not
645
-            // dependent on another property
646
-            $d["azimuth"] = "center";
647
-            $d["background_attachment"] = "scroll";
648
-            $d["background_color"] = "transparent";
649
-            $d["background_image"] = "none";
650
-            $d["background_image_resolution"] = "normal";
651
-            $d["background_position"] = ["0%", "0%"];
652
-            $d["background_repeat"] = "repeat";
653
-            $d["background"] = "";
654
-            $d["border_collapse"] = "separate";
655
-            $d["border_color"] = "";
656
-            $d["border_spacing"] = [0.0, 0.0];
657
-            $d["border_style"] = "";
658
-            $d["border_top"] = "";
659
-            $d["border_right"] = "";
660
-            $d["border_bottom"] = "";
661
-            $d["border_left"] = "";
662
-            $d["border_top_color"] = "currentcolor";
663
-            $d["border_right_color"] = "currentcolor";
664
-            $d["border_bottom_color"] = "currentcolor";
665
-            $d["border_left_color"] = "currentcolor";
666
-            $d["border_top_style"] = "none";
667
-            $d["border_right_style"] = "none";
668
-            $d["border_bottom_style"] = "none";
669
-            $d["border_left_style"] = "none";
670
-            $d["border_top_width"] = "medium";
671
-            $d["border_right_width"] = "medium";
672
-            $d["border_bottom_width"] = "medium";
673
-            $d["border_left_width"] = "medium";
674
-            $d["border_width"] = "";
675
-            $d["border_bottom_left_radius"] = 0.0;
676
-            $d["border_bottom_right_radius"] = 0.0;
677
-            $d["border_top_left_radius"] = 0.0;
678
-            $d["border_top_right_radius"] = 0.0;
679
-            $d["border_radius"] = "";
680
-            $d["border"] = "";
681
-            $d["bottom"] = "auto";
682
-            $d["caption_side"] = "top";
683
-            $d["clear"] = "none";
684
-            $d["clip"] = "auto";
685
-            $d["color"] = "#000000";
686
-            $d["content"] = "normal";
687
-            $d["counter_increment"] = "none";
688
-            $d["counter_reset"] = "none";
689
-            $d["cue_after"] = "none";
690
-            $d["cue_before"] = "none";
691
-            $d["cue"] = "";
692
-            $d["cursor"] = "auto";
693
-            $d["direction"] = "ltr";
694
-            $d["display"] = "inline";
695
-            $d["elevation"] = "level";
696
-            $d["empty_cells"] = "show";
697
-            $d["float"] = "none";
698
-            $d["font_family"] = $stylesheet->get_dompdf()->getOptions()->getDefaultFont();
699
-            $d["font_size"] = "medium";
700
-            $d["font_style"] = "normal";
701
-            $d["font_variant"] = "normal";
702
-            $d["font_weight"] = "normal";
703
-            $d["font"] = "";
704
-            $d["height"] = "auto";
705
-            $d["image_resolution"] = "normal";
706
-            $d["inset"] = "";
707
-            $d["left"] = "auto";
708
-            $d["letter_spacing"] = "normal";
709
-            $d["line_height"] = "normal";
710
-            $d["list_style_image"] = "none";
711
-            $d["list_style_position"] = "outside";
712
-            $d["list_style_type"] = "disc";
713
-            $d["list_style"] = "";
714
-            $d["margin_right"] = 0.0;
715
-            $d["margin_left"] = 0.0;
716
-            $d["margin_top"] = 0.0;
717
-            $d["margin_bottom"] = 0.0;
718
-            $d["margin"] = "";
719
-            $d["max_height"] = "none";
720
-            $d["max_width"] = "none";
721
-            $d["min_height"] = "auto";
722
-            $d["min_width"] = "auto";
723
-            $d["orphans"] = 2;
724
-            $d["outline_color"] = "currentcolor"; // "invert" special color is not supported
725
-            $d["outline_style"] = "none";
726
-            $d["outline_width"] = "medium";
727
-            $d["outline_offset"] = 0.0;
728
-            $d["outline"] = "";
729
-            $d["overflow"] = "visible";
730
-            $d["overflow_wrap"] = "normal";
731
-            $d["padding_top"] = 0.0;
732
-            $d["padding_right"] = 0.0;
733
-            $d["padding_bottom"] = 0.0;
734
-            $d["padding_left"] = 0.0;
735
-            $d["padding"] = "";
736
-            $d["page_break_after"] = "auto";
737
-            $d["page_break_before"] = "auto";
738
-            $d["page_break_inside"] = "auto";
739
-            $d["pause_after"] = "0";
740
-            $d["pause_before"] = "0";
741
-            $d["pause"] = "";
742
-            $d["pitch_range"] = "50";
743
-            $d["pitch"] = "medium";
744
-            $d["play_during"] = "auto";
745
-            $d["position"] = "static";
746
-            $d["quotes"] = "auto";
747
-            $d["richness"] = "50";
748
-            $d["right"] = "auto";
749
-            $d["size"] = "auto"; // @page
750
-            $d["speak_header"] = "once";
751
-            $d["speak_numeral"] = "continuous";
752
-            $d["speak_punctuation"] = "none";
753
-            $d["speak"] = "normal";
754
-            $d["speech_rate"] = "medium";
755
-            $d["stress"] = "50";
756
-            $d["table_layout"] = "auto";
757
-            $d["text_align"] = "";
758
-            $d["text_decoration"] = "none";
759
-            $d["text_indent"] = 0.0;
760
-            $d["text_transform"] = "none";
761
-            $d["top"] = "auto";
762
-            $d["unicode_bidi"] = "normal";
763
-            $d["vertical_align"] = "baseline";
764
-            $d["visibility"] = "visible";
765
-            $d["voice_family"] = "";
766
-            $d["volume"] = "medium";
767
-            $d["white_space"] = "normal";
768
-            $d["widows"] = 2;
769
-            $d["width"] = "auto";
770
-            $d["word_break"] = "normal";
771
-            $d["word_spacing"] = "normal";
772
-            $d["z_index"] = "auto";
773
-
774
-            // CSS3
775
-            $d["opacity"] = 1.0;
776
-            $d["background_size"] = ["auto", "auto"];
777
-            $d["transform"] = "none";
778
-            $d["transform_origin"] = "50% 50%";
779
-
780
-            // for @font-face
781
-            $d["src"] = "";
782
-            $d["unicode_range"] = "";
783
-
784
-            // vendor-prefixed properties
785
-            $d["_dompdf_keep"] = "";
786
-
787
-            // Properties that inherit by default
788
-            self::$_inherited = [
789
-                "azimuth",
790
-                "background_image_resolution",
791
-                "border_collapse",
792
-                "border_spacing",
793
-                "caption_side",
794
-                "color",
795
-                "cursor",
796
-                "direction",
797
-                "elevation",
798
-                "empty_cells",
799
-                "font_family",
800
-                "font_size",
801
-                "font_style",
802
-                "font_variant",
803
-                "font_weight",
804
-                "font",
805
-                "image_resolution",
806
-                "letter_spacing",
807
-                "line_height",
808
-                "list_style_image",
809
-                "list_style_position",
810
-                "list_style_type",
811
-                "list_style",
812
-                "orphans",
813
-                "overflow_wrap",
814
-                "pitch_range",
815
-                "pitch",
816
-                "quotes",
817
-                "richness",
818
-                "speak_header",
819
-                "speak_numeral",
820
-                "speak_punctuation",
821
-                "speak",
822
-                "speech_rate",
823
-                "stress",
824
-                "text_align",
825
-                "text_indent",
826
-                "text_transform",
827
-                "visibility",
828
-                "voice_family",
829
-                "volume",
830
-                "white_space",
831
-                "widows",
832
-                "word_break",
833
-                "word_spacing",
834
-            ];
835
-
836
-            // Compute dependent props from dependency map
837
-            foreach (self::$_dependency_map as $props) {
838
-                foreach ($props as $prop) {
839
-                    self::$_dependent_props[$prop] = true;
840
-                }
841
-            }
842
-
843
-            // Compute valid display-type lookup table
844
-            self::$valid_display_types = [
845
-                "none"                => true,
846
-                "-dompdf-br"          => true,
847
-                "-dompdf-image"       => true,
848
-                "-dompdf-list-bullet" => true,
849
-                "-dompdf-page"        => true
850
-            ];
851
-            foreach (self::BLOCK_LEVEL_TYPES as $val) {
852
-                self::$valid_display_types[$val] = true;
853
-            }
854
-            foreach (self::INLINE_LEVEL_TYPES as $val) {
855
-                self::$valid_display_types[$val] = true;
856
-            }
857
-            foreach (self::TABLE_INTERNAL_TYPES as $val) {
858
-                self::$valid_display_types[$val] = true;
859
-            }
860
-        }
861
-    }
862
-
863
-    /**
864
-     * Clear all non-final used values.
865
-     *
866
-     * @return void
867
-     */
868
-    public function reset(): void
869
-    {
870
-        foreach (array_keys($this->non_final_used) as $prop) {
871
-            unset($this->_props_used[$prop]);
872
-        }
873
-
874
-        $this->non_final_used = [];
875
-    }
876
-
877
-    /**
878
-     * @param array $media_queries
879
-     */
880
-    public function set_media_queries(array $media_queries): void
881
-    {
882
-        $this->_media_queries = $media_queries;
883
-    }
884
-
885
-    /**
886
-     * @return array
887
-     */
888
-    public function get_media_queries(): array
889
-    {
890
-        return $this->_media_queries;
891
-    }
892
-
893
-    /**
894
-     * @param Frame $frame
895
-     */
896
-    public function set_frame(Frame $frame): void
897
-    {
898
-        $this->_frame = $frame;
899
-    }
900
-
901
-    /**
902
-     * @return Frame|null
903
-     */
904
-    public function get_frame(): ?Frame
905
-    {
906
-        return $this->_frame;
907
-    }
908
-
909
-    /**
910
-     * @param int $origin
911
-     */
912
-    public function set_origin(int $origin): void
913
-    {
914
-        $this->_origin = $origin;
915
-    }
916
-
917
-    /**
918
-     * @return int
919
-     */
920
-    public function get_origin(): int
921
-    {
922
-        return $this->_origin;
923
-    }
924
-
925
-    /**
926
-     * Returns the {@link Stylesheet} the style is associated with.
927
-     *
928
-     * @return Stylesheet
929
-     */
930
-    public function get_stylesheet(): Stylesheet
931
-    {
932
-        return $this->_stylesheet;
933
-    }
934
-
935
-    public function is_absolute(): bool
936
-    {
937
-        $position = $this->__get("position");
938
-        return $position === "absolute" || $position === "fixed";
939
-    }
940
-
941
-    public function is_in_flow(): bool
942
-    {
943
-        $float = $this->__get("float");
944
-        return $float === "none" && !$this->is_absolute();
945
-    }
946
-
947
-    /**
948
-     * Converts any CSS length value into an absolute length in points.
949
-     *
950
-     * length_in_pt() takes a single length (e.g. '1em') or an array of
951
-     * lengths and returns an absolute length.  If an array is passed, then
952
-     * the return value is the sum of all elements. If any of the lengths
953
-     * provided are "auto" or "none" then that value is returned.
954
-     *
955
-     * If a reference size is not provided, the current font size is used.
956
-     *
957
-     * @param float|string|array $length   The numeric length (or string measurement) or array of lengths to resolve.
958
-     * @param float|null         $ref_size An absolute reference size to resolve percentage lengths.
959
-     *
960
-     * @return float|string
961
-     */
962
-    public function length_in_pt($length, ?float $ref_size = null)
963
-    {
964
-        $font_size = $this->__get("font_size");
965
-        $ref_size = $ref_size ?? $font_size;
966
-
967
-        if (!\is_array($length)) {
968
-            $length = [$length];
969
-        }
970
-
971
-        $ret = 0.0;
972
-
973
-        foreach ($length as $l) {
974
-            if ($l === "auto" || $l === "none") {
975
-                return $l;
976
-            }
977
-
978
-            // Assume numeric values are already in points
979
-            if (is_numeric($l)) {
980
-                $ret += (float) $l;
981
-                continue;
982
-            }
983
-
984
-            $val = $this->single_length_in_pt((string) $l, $ref_size, $font_size);
985
-            $ret += $val ?? 0;
986
-        }
987
-
988
-        return $ret;
989
-    }
990
-
991
-    /**
992
-     * Convert a length declaration to pt.
993
-     *
994
-     * @param string     $l         The length declaration.
995
-     * @param float      $ref_size  Reference size for percentage declarations.
996
-     * @param float|null $font_size Font size for resolving font-size relative units.
997
-     *
998
-     * @return float|null The length in pt, or `null` for invalid declarations.
999
-     */
1000
-    protected function single_length_in_pt(string $l, float $ref_size = 0, ?float $font_size = null): ?float
1001
-    {
1002
-        static $cache = [];
1003
-
1004
-        $font_size = $font_size ?? $this->__get("font_size");
1005
-
1006
-        $key = "$l/$ref_size/$font_size";
1007
-
1008
-        if (\array_key_exists($key, $cache)) {
1009
-            return $cache[$key];
1010
-        }
1011
-
1012
-        $number = self::CSS_NUMBER;
1013
-        $pattern = "/^($number)(.*)?$/";
1014
-
1015
-        if (!preg_match($pattern, $l, $matches)) {
1016
-            return null;
1017
-        }
1018
-
1019
-        $v = (float) $matches[1];
1020
-        $unit = mb_strtolower($matches[2]);
1021
-
1022
-        if ($unit === "") {
1023
-            // Legacy support for unitless values, not covered by spec. Might
1024
-            // want to restrict this to unitless `0` in the future
1025
-            $value = $v;
1026
-        }
1027
-
1028
-        elseif ($unit === "%") {
1029
-            $value = $v / 100 * $ref_size;
1030
-        }
1031
-
1032
-        elseif ($unit === "px") {
1033
-            $dpi = $this->_stylesheet->get_dompdf()->getOptions()->getDpi();
1034
-            $value = ($v * 72) / $dpi;
1035
-        }
1036
-
1037
-        elseif ($unit === "pt") {
1038
-            $value = $v;
1039
-        }
1040
-
1041
-        elseif ($unit === "rem") {
1042
-            $tree = $this->_stylesheet->get_dompdf()->getTree();
1043
-            $root_style = $tree !== null ? $tree->get_root()->get_style() : null;
1044
-            $root_font_size = $root_style === null || $root_style === $this
1045
-                ? $font_size
1046
-                : $root_style->__get("font_size");
1047
-            $value = $v * $root_font_size;
1048
-
1049
-            // Skip caching if the root style is not available yet, as to avoid
1050
-            // incorrectly cached values if the root font size is different from
1051
-            // the default
1052
-            if ($root_style === null) {
1053
-                return $value;
1054
-            }
1055
-        }
1056
-
1057
-        elseif ($unit === "em") {
1058
-            $value = $v * $font_size;
1059
-        }
1060
-
1061
-        elseif ($unit === "cm") {
1062
-            $value = $v * 72 / 2.54;
1063
-        }
1064
-
1065
-        elseif ($unit === "mm") {
1066
-            $value = $v * 72 / 25.4;
1067
-        }
1068
-
1069
-        elseif ($unit === "ex") {
1070
-            // FIXME: em:ex ratio?
1071
-            $value = $v * $font_size / 2;
1072
-        }
1073
-
1074
-        elseif ($unit === "in") {
1075
-            $value = $v * 72;
1076
-        }
1077
-
1078
-        elseif ($unit === "pc") {
1079
-            $value = $v * 12;
1080
-        }
1081
-
1082
-        else {
1083
-            // Invalid or unsupported declaration
1084
-            $value = null;
1085
-        }
1086
-
1087
-        return $cache[$key] = $value;
1088
-    }
1089
-
1090
-    /**
1091
-     * Resolve inherited property values using the provided parent style or the
1092
-     * default values, in case no parent style exists.
1093
-     *
1094
-     * https://www.w3.org/TR/css-cascade-3/#inheriting
1095
-     *
1096
-     * @param Style|null $parent
1097
-     */
1098
-    public function inherit(?Style $parent = null): void
1099
-    {
1100
-        $this->parent_style = $parent;
1101
-
1102
-        // Clear the computed font size, as it might depend on the parent
1103
-        // font size
1104
-        unset($this->_props_computed["font_size"]);
1105
-        unset($this->_props_used["font_size"]);
1106
-
1107
-        if ($parent) {
1108
-            foreach (self::$_inherited as $prop) {
1109
-                // For properties that inherit by default: When the cascade did
1110
-                // not result in a value, inherit the parent value. Inheritance
1111
-                // is handled via the specific sub-properties for shorthands
1112
-                if (isset($this->_props[$prop]) || isset(self::$_props_shorthand[$prop])) {
1113
-                    continue;
1114
-                }
1115
-
1116
-                if (isset($parent->_props[$prop])) {
1117
-                    $parent_val = $parent->computed($prop);
1118
-
1119
-                    $this->_props[$prop] = $parent_val;
1120
-                    $this->_props_computed[$prop] = $parent_val;
1121
-                    $this->_props_used[$prop] = null;
1122
-                }
1123
-            }
1124
-        }
1125
-
1126
-        foreach ($this->_props as $prop => $val) {
1127
-            if ($val === "inherit") {
1128
-                if ($parent && isset($parent->_props[$prop])) {
1129
-                    $parent_val = $parent->computed($prop);
1130
-
1131
-                    $this->_props[$prop] = $parent_val;
1132
-                    $this->_props_computed[$prop] = $parent_val;
1133
-                    $this->_props_used[$prop] = null;
1134
-                } else {
1135
-                    // Parent prop not set, use default
1136
-                    $this->_props[$prop] = self::$_defaults[$prop];
1137
-                    unset($this->_props_computed[$prop]);
1138
-                    unset($this->_props_used[$prop]);
1139
-                }
1140
-            }
1141
-        }
1142
-    }
1143
-
1144
-    /**
1145
-     * Override properties in this style with those in $style
1146
-     *
1147
-     * @param Style $style
1148
-     */
1149
-    public function merge(Style $style): void
1150
-    {
1151
-        foreach ($style->_props as $prop => $val) {
1152
-            $important = isset($style->_important_props[$prop]);
1153
-
1154
-            // `!important` declarations take precedence over normal ones
1155
-            if (!$important && isset($this->_important_props[$prop])) {
1156
-                continue;
1157
-            }
1158
-
1159
-            if ($important) {
1160
-                $this->_important_props[$prop] = true;
1161
-            }
1162
-
1163
-            $this->_props[$prop] = $val;
1164
-
1165
-            // Copy an existing computed value only for non-dependent
1166
-            // properties; otherwise it may be invalid for the current style
1167
-            if (!isset(self::$_dependent_props[$prop])
1168
-                && \array_key_exists($prop, $style->_props_computed)
1169
-            ) {
1170
-                $this->_props_computed[$prop] = $style->_props_computed[$prop];
1171
-                $this->_props_used[$prop] = null;
1172
-            } else {
1173
-                unset($this->_props_computed[$prop]);
1174
-                unset($this->_props_used[$prop]);
1175
-            }
1176
-        }
1177
-    }
1178
-
1179
-    /**
1180
-     * Clear information about important declarations after the style has been
1181
-     * finalized during stylesheet loading.
1182
-     */
1183
-    public function clear_important(): void
1184
-    {
1185
-        $this->_important_props = [];
1186
-    }
1187
-
1188
-    /**
1189
-     * Clear border-radius and bottom-spacing cache as necessary when a given
1190
-     * property is set.
1191
-     *
1192
-     * @param string $prop The property that is set.
1193
-     */
1194
-    protected function clear_cache(string $prop): void
1195
-    {
1196
-        // Clear border-radius cache on setting any border-radius
1197
-        // property
1198
-        if ($prop === "border_top_left_radius"
1199
-            || $prop === "border_top_right_radius"
1200
-            || $prop === "border_bottom_left_radius"
1201
-            || $prop === "border_bottom_right_radius"
1202
-        ) {
1203
-            $this->has_border_radius_cache = null;
1204
-            $this->resolved_border_radius = null;
1205
-        }
1206
-
1207
-        // Clear bottom-spacing cache if necessary. Border style can
1208
-        // disable/enable border calculations
1209
-        if ($prop === "margin_bottom"
1210
-            || $prop === "padding_bottom"
1211
-            || $prop === "border_bottom_width"
1212
-            || $prop === "border_bottom_style"
1213
-        ) {
1214
-            $this->_computed_bottom_spacing = null;
1215
-        }
1216
-    }
1217
-
1218
-    /**
1219
-     * Set a style property from a value declaration.
1220
-     *
1221
-     * Setting `$clear_dependencies` to `false` is useful for saving a bit of
1222
-     * unnecessary work while loading stylesheets.
1223
-     *
1224
-     * @param string $prop               The property to set.
1225
-     * @param mixed  $val                The value declaration or computed value.
1226
-     * @param bool   $important          Whether the declaration is important.
1227
-     * @param bool   $clear_dependencies Whether to clear computed values of dependent properties.
1228
-     */
1229
-    public function set_prop(string $prop, $val, bool $important = false, bool $clear_dependencies = true): void
1230
-    {
1231
-        $prop = str_replace("-", "_", $prop);
1232
-
1233
-        // Legacy property aliases
1234
-        if (isset(self::$_props_alias[$prop])) {
1235
-            $prop = self::$_props_alias[$prop];
1236
-        }
1237
-
1238
-        if (!isset(self::$_defaults[$prop])) {
1239
-            global $_dompdf_warnings;
1240
-            $_dompdf_warnings[] = "'$prop' is not a recognized CSS property.";
1241
-            return;
1242
-        }
1243
-
1244
-        if ($prop !== "content" && \is_string($val) && mb_strpos($val, "url") === false && mb_strlen($val) > 1) {
1245
-            $val = mb_strtolower(trim(str_replace(["\n", "\t"], [" "], $val)));
1246
-        }
1247
-
1248
-        if (isset(self::$_props_shorthand[$prop])) {
1249
-            // Shorthand properties directly set their respective sub-properties
1250
-            // https://www.w3.org/TR/css-cascade-3/#shorthand
1251
-            if ($val === "initial" || $val === "inherit" || $val === "unset") {
1252
-                foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1253
-                    $this->set_prop($sub_prop, $val, $important, $clear_dependencies);
1254
-                }
1255
-            } else {
1256
-                $method = "_set_$prop";
1257
-
1258
-                if (!isset(self::$_methods_cache[$method])) {
1259
-                    self::$_methods_cache[$method] = method_exists($this, $method);
1260
-                }
1261
-
1262
-                if (self::$_methods_cache[$method]) {
1263
-                    $values = $this->$method($val);
1264
-
1265
-                    if ($values === []) {
1266
-                        return;
1267
-                    }
1268
-
1269
-                    // Each missing sub-property is assigned its initial value
1270
-                    // https://www.w3.org/TR/css-cascade-3/#shorthand
1271
-                    foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1272
-                        $sub_val = $values[$sub_prop] ?? self::$_defaults[$sub_prop];
1273
-                        $this->set_prop($sub_prop, $sub_val, $important, $clear_dependencies);
1274
-                    }
1275
-                }
1276
-            }
1277
-        } else {
1278
-            // Legacy support for `word-break: break-word`
1279
-            // https://www.w3.org/TR/css-text-3/#valdef-word-break-break-word
1280
-            if ($prop === "word_break" && $val === "break-word") {
1281
-                $val = "normal";
1282
-                $this->set_prop("overflow_wrap", "anywhere", $important, $clear_dependencies);
1283
-            }
1284
-
1285
-            // `!important` declarations take precedence over normal ones
1286
-            if (!$important && isset($this->_important_props[$prop])) {
1287
-                return;
1288
-            }
1289
-
1290
-            if ($important) {
1291
-                $this->_important_props[$prop] = true;
1292
-            }
1293
-
1294
-            // https://www.w3.org/TR/css-cascade-3/#inherit-initial
1295
-            if ($val === "unset") {
1296
-                $val = \in_array($prop, self::$_inherited, true)
1297
-                    ? "inherit"
1298
-                    : "initial";
1299
-            }
1300
-
1301
-            // https://www.w3.org/TR/css-cascade-3/#valdef-all-initial
1302
-            if ($val === "initial") {
1303
-                $val = self::$_defaults[$prop];
1304
-            }
1305
-
1306
-            $computed = $this->compute_prop($prop, $val);
1307
-
1308
-            // Skip invalid declarations
1309
-            if ($computed === null) {
1310
-                return;
1311
-            }
1312
-
1313
-            $this->_props[$prop] = $val;
1314
-            $this->_props_computed[$prop] = $computed;
1315
-            $this->_props_used[$prop] = null;
1316
-
1317
-            if ($clear_dependencies) {
1318
-                // Clear the computed values of any dependent properties, so
1319
-                // they can be re-computed
1320
-                if (isset(self::$_dependency_map[$prop])) {
1321
-                    foreach (self::$_dependency_map[$prop] as $dependent) {
1322
-                        unset($this->_props_computed[$dependent]);
1323
-                        unset($this->_props_used[$dependent]);
1324
-                    }
1325
-                }
1326
-
1327
-                $this->clear_cache($prop);
1328
-            }
1329
-        }
1330
-    }
1331
-
1332
-    /**
1333
-     * Get the specified value of a style property.
1334
-     *
1335
-     * @param string $prop
1336
-     *
1337
-     * @return mixed
1338
-     * @throws Exception
1339
-     */
1340
-    public function get_specified(string $prop)
1341
-    {
1342
-        // Legacy property aliases
1343
-        if (isset(self::$_props_alias[$prop])) {
1344
-            $prop = self::$_props_alias[$prop];
1345
-        }
1346
-
1347
-        if (!isset(self::$_defaults[$prop])) {
1348
-            throw new Exception("'$prop' is not a recognized CSS property.");
1349
-        }
1350
-
1351
-        return $this->_props[$prop] ?? self::$_defaults[$prop];
1352
-    }
1353
-
1354
-    /**
1355
-     * Set a style property to its final value.
1356
-     *
1357
-     * This sets the specified and used value of the style property to the given
1358
-     * value, meaning the value is not parsed and thus should have a type
1359
-     * compatible with the property.
1360
-     *
1361
-     * If a shorthand property is specified, all of its sub-properties are set
1362
-     * to the given value.
1363
-     *
1364
-     * @param string $prop The property to set.
1365
-     * @param mixed  $val  The final value of the property.
1366
-     *
1367
-     * @throws Exception
1368
-     */
1369
-    public function __set(string $prop, $val)
1370
-    {
1371
-        // Legacy property aliases
1372
-        if (isset(self::$_props_alias[$prop])) {
1373
-            $prop = self::$_props_alias[$prop];
1374
-        }
1375
-
1376
-        if (!isset(self::$_defaults[$prop])) {
1377
-            throw new Exception("'$prop' is not a recognized CSS property.");
1378
-        }
1379
-
1380
-        if (isset(self::$_props_shorthand[$prop])) {
1381
-            foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1382
-                $this->__set($sub_prop, $val);
1383
-            }
1384
-        } else {
1385
-            $this->_props[$prop] = $val;
1386
-            $this->_props_computed[$prop] = $val;
1387
-            $this->_props_used[$prop] = $val;
1388
-
1389
-            $this->clear_cache($prop);
1390
-        }
1391
-    }
1392
-
1393
-    /**
1394
-     * Set the used value of a style property.
1395
-     *
1396
-     * Used values are cleared on style reset.
1397
-     *
1398
-     * If a shorthand property is specified, all of its sub-properties are set
1399
-     * to the given value.
1400
-     *
1401
-     * @param string $prop The property to set.
1402
-     * @param mixed  $val  The used value of the property.
1403
-     *
1404
-     * @throws Exception
1405
-     */
1406
-    public function set_used(string $prop, $val): void
1407
-    {
1408
-        // Legacy property aliases
1409
-        if (isset(self::$_props_alias[$prop])) {
1410
-            $prop = self::$_props_alias[$prop];
1411
-        }
1412
-
1413
-        if (!isset(self::$_defaults[$prop])) {
1414
-            throw new Exception("'$prop' is not a recognized CSS property.");
1415
-        }
1416
-
1417
-        if (isset(self::$_props_shorthand[$prop])) {
1418
-            foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1419
-                $this->set_used($sub_prop, $val);
1420
-            }
1421
-        } else {
1422
-            $this->_props_used[$prop] = $val;
1423
-            $this->non_final_used[$prop] = true;
1424
-        }
1425
-    }
1426
-
1427
-    /**
1428
-     * Get the used or computed value of a style property, depending on whether
1429
-     * the used value has been determined yet.
1430
-     *
1431
-     * @param string $prop
1432
-     *
1433
-     * @return mixed
1434
-     * @throws Exception
1435
-     */
1436
-    public function __get(string $prop)
1437
-    {
1438
-        // Legacy property aliases
1439
-        if (isset(self::$_props_alias[$prop])) {
1440
-            $prop = self::$_props_alias[$prop];
1441
-        }
1442
-
1443
-        if (!isset(self::$_defaults[$prop])) {
1444
-            throw new Exception("'$prop' is not a recognized CSS property.");
1445
-        }
1446
-
1447
-        if (isset($this->_props_used[$prop])) {
1448
-            return $this->_props_used[$prop];
1449
-        }
1450
-
1451
-        $method = "_get_$prop";
1452
-
1453
-        if (!isset(self::$_methods_cache[$method])) {
1454
-            self::$_methods_cache[$method] = method_exists($this, $method);
1455
-        }
1456
-
1457
-        if (isset(self::$_props_shorthand[$prop])) {
1458
-            // Don't cache shorthand values, always use getter. If no dedicated
1459
-            // getter exists, use a simple fallback getter concatenating all
1460
-            // sub-property values
1461
-            if (self::$_methods_cache[$method]) {
1462
-                return $this->$method();
1463
-            } else {
1464
-                return implode(" ", array_map(function ($sub_prop) {
1465
-                    $val = $this->__get($sub_prop);
1466
-                    return \is_array($val) ? implode(" ", $val) : $val;
1467
-                }, self::$_props_shorthand[$prop]));
1468
-            }
1469
-        } else {
1470
-            $computed = $this->computed($prop);
1471
-            $used = self::$_methods_cache[$method]
1472
-                ? $this->$method($computed)
1473
-                : $computed;
1474
-
1475
-            $this->_props_used[$prop] = $used;
1476
-            return $used;
1477
-        }
1478
-    }
1479
-
1480
-    /**
1481
-     * @param string $prop The property to compute.
1482
-     * @param mixed  $val  The value to compute. Non-string values are treated as already computed.
1483
-     *
1484
-     * @return mixed The computed value.
1485
-     */
1486
-    protected function compute_prop(string $prop, $val)
1487
-    {
1488
-        // During style merge, the parent style is not available yet, so
1489
-        // temporarily use the initial value for `inherit` properties. The
1490
-        // keyword is properly resolved during inheritance
1491
-        if ($val === "inherit") {
1492
-            $val = self::$_defaults[$prop];
1493
-        }
1494
-
1495
-        // Check for values which are already computed
1496
-        if (!\is_string($val)) {
1497
-            return $val;
1498
-        }
1499
-
1500
-        $method = "_compute_$prop";
1501
-
1502
-        if (!isset(self::$_methods_cache[$method])) {
1503
-            self::$_methods_cache[$method] = method_exists($this, $method);
1504
-        }
1505
-
1506
-        if (self::$_methods_cache[$method]) {
1507
-            return $this->$method($val);
1508
-        } elseif ($val !== "") {
1509
-            return $val;
1510
-        } else {
1511
-            return null;
1512
-        }
1513
-    }
1514
-
1515
-    /**
1516
-     * Get the computed value for the given property.
1517
-     *
1518
-     * @param string $prop The property to get the computed value of.
1519
-     *
1520
-     * @return mixed The computed value.
1521
-     */
1522
-    protected function computed(string $prop)
1523
-    {
1524
-        if (!\array_key_exists($prop, $this->_props_computed)) {
1525
-            $val = $this->_props[$prop] ?? self::$_defaults[$prop];
1526
-            $computed = $this->compute_prop($prop, $val);
1527
-
1528
-            $this->_props_computed[$prop] = $computed;
1529
-        }
1530
-
1531
-        return $this->_props_computed[$prop];
1532
-    }
1533
-
1534
-    /**
1535
-     * @param float $cbw The width of the containing block.
1536
-     * @return float|string|null
1537
-     */
1538
-    public function computed_bottom_spacing(float $cbw)
1539
-    {
1540
-        // Caching the bottom spacing independently of the given width is a bit
1541
-        // iffy, but should be okay, as the containing block should only
1542
-        // potentially change after a page break, and the style is reset in that
1543
-        // case
1544
-        if ($this->_computed_bottom_spacing !== null) {
1545
-            return $this->_computed_bottom_spacing;
1546
-        }
1547
-        return $this->_computed_bottom_spacing = $this->length_in_pt(
1548
-            [
1549
-                $this->margin_bottom,
1550
-                $this->padding_bottom,
1551
-                $this->border_bottom_width
1552
-            ],
1553
-            $cbw
1554
-        );
1555
-    }
1556
-
1557
-    /**
1558
-     * Returns an `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")`
1559
-     * based on the provided CSS color value.
1560
-     *
1561
-     * @param string|null $color
1562
-     * @return array|string|null
1563
-     */
1564
-    public function munge_color($color)
1565
-    {
1566
-        return Color::parse($color);
1567
-    }
1568
-
1569
-    /**
1570
-     * @return string
1571
-     */
1572
-    public function get_font_family_raw(): string
1573
-    {
1574
-        return trim($this->_props["font_family"], " \t\n\r\x0B\"'");
1575
-    }
1576
-
1577
-    /**
1578
-     * Getter for the `font-family` CSS property.
1579
-     *
1580
-     * Uses the {@link FontMetrics} class to resolve the font family into an
1581
-     * actual font file.
1582
-     *
1583
-     * @param string $computed
1584
-     * @return string
1585
-     * @throws Exception
1586
-     *
1587
-     * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-family
1588
-     */
1589
-    protected function _get_font_family($computed): string
1590
-    {
1591
-        //TODO: we should be using the calculated prop rather than perform the entire family parsing operation again
1592
-
1593
-        $fontMetrics = $this->getFontMetrics();
1594
-        $DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss();
1595
-
1596
-        // Select the appropriate font.  First determine the subtype, then check
1597
-        // the specified font-families for a candidate.
1598
-
1599
-        // Resolve font-weight
1600
-        $weight = $this->__get("font_weight");
1601
-        if ($weight === 'bold') {
1602
-            $weight = 700;
1603
-        } elseif (preg_match('/^[0-9]+$/', $weight, $match)) {
1604
-            $weight = (int)$match[0];
1605
-        } else {
1606
-            $weight = 400;
1607
-        }
1608
-
1609
-        // Resolve font-style
1610
-        $font_style = $this->__get("font_style");
1611
-        $subtype = $fontMetrics->getType($weight . ' ' . $font_style);
1612
-
1613
-        $families = preg_split("/\s*,\s*/", $computed);
1614
-
1615
-        $font = null;
1616
-        foreach ($families as $family) {
1617
-            //remove leading and trailing string delimiters, e.g. on font names with spaces;
1618
-            //remove leading and trailing whitespace
1619
-            $family = trim($family, " \t\n\r\x0B\"'");
1620
-            if ($DEBUGCSS) {
1621
-                print '(' . $family . ')';
1622
-            }
1623
-            $font = $fontMetrics->getFont($family, $subtype);
1624
-
1625
-            if ($font) {
1626
-                if ($DEBUGCSS) {
1627
-                    print "<pre>[get_font_family:";
1628
-                    print '(' . $computed . '.' . $font_style . '.' . $weight . '.' . $subtype . ')';
1629
-                    print '(' . $font . ")get_font_family]\n</pre>";
1630
-                }
1631
-                return $font;
1632
-            }
1633
-        }
1634
-
1635
-        $family = null;
1636
-        if ($DEBUGCSS) {
1637
-            print '(default)';
1638
-        }
1639
-        $font = $fontMetrics->getFont($family, $subtype);
1640
-
1641
-        if ($font) {
1642
-            if ($DEBUGCSS) {
1643
-                print '(' . $font . ")get_font_family]\n</pre>";
1644
-            }
1645
-            return $font;
1646
-        }
1647
-
1648
-        throw new Exception("Unable to find a suitable font replacement for: '" . $computed . "'");
1649
-    }
1650
-
1651
-    /**
1652
-     * @param float|string $computed
1653
-     * @return float
1654
-     *
1655
-     * @link https://www.w3.org/TR/css-text-4/#word-spacing-property
1656
-     */
1657
-    protected function _get_word_spacing($computed)
1658
-    {
1659
-        if (\is_float($computed)) {
1660
-            return $computed;
1661
-        }
1662
-
1663
-        // Resolve percentage values
1664
-        $font_size = $this->__get("font_size");
1665
-        return $this->single_length_in_pt($computed, $font_size);
1666
-    }
1667
-
1668
-    /**
1669
-     * @param float|string $computed
1670
-     * @return float
1671
-     *
1672
-     * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property
1673
-     */
1674
-    protected function _get_letter_spacing($computed)
1675
-    {
1676
-        if (\is_float($computed)) {
1677
-            return $computed;
1678
-        }
1679
-
1680
-        // Resolve percentage values
1681
-        $font_size = $this->__get("font_size");
1682
-        return $this->single_length_in_pt($computed, $font_size);
1683
-    }
1684
-
1685
-    /**
1686
-     * @param float|string $computed
1687
-     * @return float
1688
-     *
1689
-     * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
1690
-     */
1691
-    protected function _get_line_height($computed)
1692
-    {
1693
-        // Lengths have been computed to float, number values to string
1694
-        if (\is_float($computed)) {
1695
-            return $computed;
1696
-        }
1697
-
1698
-        $font_size = $this->__get("font_size");
1699
-        $factor = $computed === "normal"
1700
-            ? self::$default_line_height
1701
-            : (float) $computed;
1702
-
1703
-        return $factor * $font_size;
1704
-    }
1705
-
1706
-    /**
1707
-     * @param string $computed
1708
-     * @param bool   $current_is_parent
1709
-     *
1710
-     * @return array|string
1711
-     */
1712
-    protected function get_color_value($computed, bool $current_is_parent = false)
1713
-    {
1714
-        if ($computed === "currentcolor") {
1715
-            // https://www.w3.org/TR/css-color-4/#resolving-other-colors
1716
-            if ($current_is_parent) {
1717
-                // Use the `color` value from the parent for the `color`
1718
-                // property itself
1719
-                return isset($this->parent_style)
1720
-                    ? $this->parent_style->__get("color")
1721
-                    : $this->munge_color(self::$_defaults["color"]);
1722
-            }
1723
-
1724
-            return $this->__get("color");
1725
-        }
1726
-
1727
-        return $this->munge_color($computed) ?? "transparent";
1728
-    }
1729
-
1730
-    /**
1731
-     * Returns the color as an array
1732
-     *
1733
-     * The array has the following format:
1734
-     * `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")`
1735
-     *
1736
-     * @param string $computed
1737
-     * @return array|string
1738
-     *
1739
-     * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color
1740
-     */
1741
-    protected function _get_color($computed)
1742
-    {
1743
-        return $this->get_color_value($computed, true);
1744
-    }
1745
-
1746
-    /**
1747
-     * Returns the background color as an array
1748
-     *
1749
-     * See {@link Style::_get_color()} for format of the color array.
1750
-     *
1751
-     * @param string $computed
1752
-     * @return array|string
1753
-     *
1754
-     * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color
1755
-     */
1756
-    protected function _get_background_color($computed)
1757
-    {
1758
-        return $this->get_color_value($computed);
1759
-    }
1760
-
1761
-    /**
1762
-     * Returns the background image URI, or "none"
1763
-     *
1764
-     * @param string $computed
1765
-     * @return string
1766
-     *
1767
-     * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
1768
-     */
1769
-    protected function _get_background_image($computed): string
1770
-    {
1771
-        return $this->_stylesheet->resolve_url($computed);
1772
-    }
1773
-
1774
-    /**
1775
-     * Returns the border color as an array
1776
-     *
1777
-     * See {@link Style::_get_color()} for format of the color array.
1778
-     *
1779
-     * @param string $computed
1780
-     * @return array|string
1781
-     *
1782
-     * @link https://www.w3.org/TR/CSS21/box.html#border-color-properties
1783
-     */
1784
-    protected function _get_border_top_color($computed)
1785
-    {
1786
-        return $this->get_color_value($computed);
1787
-    }
1788
-
1789
-    /**
1790
-     * @param string $computed
1791
-     * @return array|string
1792
-     */
1793
-    protected function _get_border_right_color($computed)
1794
-    {
1795
-        return $this->get_color_value($computed);
1796
-    }
1797
-
1798
-    /**
1799
-     * @param string $computed
1800
-     * @return array|string
1801
-     */
1802
-    protected function _get_border_bottom_color($computed)
1803
-    {
1804
-        return $this->get_color_value($computed);
1805
-    }
1806
-
1807
-    /**
1808
-     * @param string $computed
1809
-     * @return array|string
1810
-     */
1811
-    protected function _get_border_left_color($computed)
1812
-    {
1813
-        return $this->get_color_value($computed);
1814
-    }
1815
-
1816
-    /**
1817
-     * Return an array of all border properties.
1818
-     *
1819
-     * The returned array has the following structure:
1820
-     *
1821
-     * ```
1822
-     * array("top" => array("width" => [border-width],
1823
-     *                      "style" => [border-style],
1824
-     *                      "color" => [border-color (array)]),
1825
-     *       "bottom" ... )
1826
-     * ```
1827
-     *
1828
-     * @return array
1829
-     */
1830
-    public function get_border_properties(): array
1831
-    {
1832
-        return [
1833
-            "top" => [
1834
-                "width" => $this->__get("border_top_width"),
1835
-                "style" => $this->__get("border_top_style"),
1836
-                "color" => $this->__get("border_top_color"),
1837
-            ],
1838
-            "bottom" => [
1839
-                "width" => $this->__get("border_bottom_width"),
1840
-                "style" => $this->__get("border_bottom_style"),
1841
-                "color" => $this->__get("border_bottom_color"),
1842
-            ],
1843
-            "right" => [
1844
-                "width" => $this->__get("border_right_width"),
1845
-                "style" => $this->__get("border_right_style"),
1846
-                "color" => $this->__get("border_right_color"),
1847
-            ],
1848
-            "left" => [
1849
-                "width" => $this->__get("border_left_width"),
1850
-                "style" => $this->__get("border_left_style"),
1851
-                "color" => $this->__get("border_left_color"),
1852
-            ],
1853
-        ];
1854
-    }
1855
-
1856
-    /**
1857
-     * Return a single border-side property
1858
-     *
1859
-     * @param string $side
1860
-     * @return string
1861
-     */
1862
-    protected function get_border_side(string $side): string
1863
-    {
1864
-        $color = $this->__get("border_{$side}_color");
1865
-
1866
-        return $this->__get("border_{$side}_width") . " " .
1867
-            $this->__get("border_{$side}_style") . " " .
1868
-            (\is_array($color) ? $color["hex"] : $color);
1869
-    }
1870
-
1871
-    /**
1872
-     * Return full border properties as a string
1873
-     *
1874
-     * Border properties are returned just as specified in CSS:
1875
-     * `[width] [style] [color]`
1876
-     * e.g. "1px solid blue"
1877
-     *
1878
-     * @return string
1879
-     *
1880
-     * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
1881
-     */
1882
-    protected function _get_border_top(): string
1883
-    {
1884
-        return $this->get_border_side("top");
1885
-    }
1886
-
1887
-    /**
1888
-     * @return string
1889
-     */
1890
-    protected function _get_border_right(): string
1891
-    {
1892
-        return $this->get_border_side("right");
1893
-    }
1894
-
1895
-    /**
1896
-     * @return string
1897
-     */
1898
-    protected function _get_border_bottom(): string
1899
-    {
1900
-        return $this->get_border_side("bottom");
1901
-    }
1902
-
1903
-    /**
1904
-     * @return string
1905
-     */
1906
-    protected function _get_border_left(): string
1907
-    {
1908
-        return $this->get_border_side("left");
1909
-    }
1910
-
1911
-    public function has_border_radius(): bool
1912
-    {
1913
-        if (isset($this->has_border_radius_cache)) {
1914
-            return $this->has_border_radius_cache;
1915
-        }
1916
-
1917
-        // Use a fixed ref size here. We don't know the border-box width here
1918
-        // and font size might be 0. Since we are only interested in whether
1919
-        // there is any border radius at all, this should do
1920
-        $tl = (float) $this->length_in_pt($this->border_top_left_radius, 12);
1921
-        $tr = (float) $this->length_in_pt($this->border_top_right_radius, 12);
1922
-        $br = (float) $this->length_in_pt($this->border_bottom_right_radius, 12);
1923
-        $bl = (float) $this->length_in_pt($this->border_bottom_left_radius, 12);
1924
-
1925
-        $this->has_border_radius_cache = $tl + $tr + $br + $bl > 0;
1926
-        return $this->has_border_radius_cache;
1927
-    }
1928
-
1929
-    /**
1930
-     * Get the final border-radius values to use.
1931
-     *
1932
-     * Percentage values are resolved relative to the width of the border box.
1933
-     * The border radius is additionally scaled for the given render box, and
1934
-     * constrained by its width and height.
1935
-     *
1936
-     * @param float[]      $border_box The border box of the frame.
1937
-     * @param float[]|null $render_box The box to resolve the border radius for.
1938
-     *
1939
-     * @return float[] A 4-tuple of top-left, top-right, bottom-right, and bottom-left radius.
1940
-     */
1941
-    public function resolve_border_radius(
1942
-        array $border_box,
1943
-        ?array $render_box = null
1944
-    ): array {
1945
-        $render_box = $render_box ?? $border_box;
1946
-        $use_cache = $render_box === $border_box;
1947
-
1948
-        if ($use_cache && isset($this->resolved_border_radius)) {
1949
-            return $this->resolved_border_radius;
1950
-        }
1951
-
1952
-        [$x, $y, $w, $h] = $border_box;
1953
-
1954
-        // Resolve percentages relative to width, as long as we have no support
1955
-        // for per-axis radii
1956
-        $tl = (float) $this->length_in_pt($this->border_top_left_radius, $w);
1957
-        $tr = (float) $this->length_in_pt($this->border_top_right_radius, $w);
1958
-        $br = (float) $this->length_in_pt($this->border_bottom_right_radius, $w);
1959
-        $bl = (float) $this->length_in_pt($this->border_bottom_left_radius, $w);
1960
-
1961
-        if ($tl + $tr + $br + $bl > 0) {
1962
-            [$rx, $ry, $rw, $rh] = $render_box;
1963
-
1964
-            $t_offset = $y - $ry;
1965
-            $r_offset = $rx + $rw - $x - $w;
1966
-            $b_offset = $ry + $rh - $y - $h;
1967
-            $l_offset = $x - $rx;
1968
-
1969
-            if ($tl > 0) {
1970
-                $tl = max($tl + ($t_offset + $l_offset) / 2, 0);
1971
-            }
1972
-            if ($tr > 0) {
1973
-                $tr = max($tr + ($t_offset + $r_offset) / 2, 0);
1974
-            }
1975
-            if ($br > 0) {
1976
-                $br = max($br + ($b_offset + $r_offset) / 2, 0);
1977
-            }
1978
-            if ($bl > 0) {
1979
-                $bl = max($bl + ($b_offset + $l_offset) / 2, 0);
1980
-            }
1981
-
1982
-            if ($tl + $bl > $rh) {
1983
-                $f = $rh / ($tl + $bl);
1984
-                $tl = $f * $tl;
1985
-                $bl = $f * $bl;
1986
-            }
1987
-            if ($tr + $br > $rh) {
1988
-                $f = $rh / ($tr + $br);
1989
-                $tr = $f * $tr;
1990
-                $br = $f * $br;
1991
-            }
1992
-            if ($tl + $tr > $rw) {
1993
-                $f = $rw / ($tl + $tr);
1994
-                $tl = $f * $tl;
1995
-                $tr = $f * $tr;
1996
-            }
1997
-            if ($bl + $br > $rw) {
1998
-                $f = $rw / ($bl + $br);
1999
-                $bl = $f * $bl;
2000
-                $br = $f * $br;
2001
-            }
2002
-        }
2003
-
2004
-        $values = [$tl, $tr, $br, $bl];
2005
-
2006
-        if ($use_cache) {
2007
-            $this->resolved_border_radius = $values;
2008
-        }
2009
-
2010
-        return $values;
2011
-    }
2012
-
2013
-    /**
2014
-     * Returns the outline color as an array
2015
-     *
2016
-     * See {@link Style::_get_color()} for format of the color array.
2017
-     *
2018
-     * @param string $computed
2019
-     * @return array|string
2020
-     *
2021
-     * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-color
2022
-     */
2023
-    protected function _get_outline_color($computed)
2024
-    {
2025
-        return $this->get_color_value($computed);
2026
-    }
2027
-
2028
-    /**
2029
-     * @param string $computed
2030
-     * @return string
2031
-     *
2032
-     * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-style
2033
-     */
2034
-    protected function _get_outline_style($computed): string
2035
-    {
2036
-        return $computed === "auto" ? "solid" : $computed;
2037
-    }
2038
-
2039
-    /**
2040
-     * Return full outline properties as a string
2041
-     *
2042
-     * Outline properties are returned just as specified in CSS:
2043
-     * `[width] [style] [color]`
2044
-     * e.g. "1px solid blue"
2045
-     *
2046
-     * @return string
2047
-     *
2048
-     * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
2049
-     */
2050
-    protected function _get_outline(): string
2051
-    {
2052
-        $color = $this->__get("outline_color");
2053
-
2054
-        return $this->__get("outline_width") . " " .
2055
-            $this->__get("outline_style") . " " .
2056
-            (\is_array($color) ? $color["hex"] : $color);
2057
-    }
2058
-
2059
-    /**
2060
-     * Returns the list style image URI, or "none"
2061
-     *
2062
-     * @param string $computed
2063
-     * @return string
2064
-     *
2065
-     * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
2066
-     */
2067
-    protected function _get_list_style_image($computed): string
2068
-    {
2069
-        return $this->_stylesheet->resolve_url($computed);
2070
-    }
2071
-
2072
-    /**
2073
-     * @param string $value
2074
-     * @param int    $default
2075
-     *
2076
-     * @return array|string
2077
-     */
2078
-    protected function parse_counter_prop(string $value, int $default)
2079
-    {
2080
-        $ident = self::CSS_IDENTIFIER;
2081
-        $integer = self::CSS_INTEGER;
2082
-        $pattern = "/($ident)(?:\s+($integer))?/";
2083
-
2084
-        if (!preg_match_all($pattern, $value, $matches, PREG_SET_ORDER)) {
2085
-            return "none";
2086
-        }
2087
-
2088
-        $counters = [];
2089
-
2090
-        foreach ($matches as $match) {
2091
-            $counter = $match[1];
2092
-            $value = isset($match[2]) ? (int) $match[2] : $default;
2093
-            $counters[$counter] = $value;
2094
-        }
2095
-
2096
-        return $counters;
2097
-    }
2098
-
2099
-    /**
2100
-     * @param string $computed
2101
-     * @return array|string
2102
-     *
2103
-     * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment
2104
-     */
2105
-    protected function _get_counter_increment($computed)
2106
-    {
2107
-        if ($computed === "none") {
2108
-            return $computed;
2109
-        }
2110
-
2111
-        return $this->parse_counter_prop($computed, 1);
2112
-    }
2113
-
2114
-    /**
2115
-     * @param string $computed
2116
-     * @return array|string
2117
-     *
2118
-     * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-reset
2119
-     */
2120
-    protected function _get_counter_reset($computed)
2121
-    {
2122
-        if ($computed === "none") {
2123
-            return $computed;
2124
-        }
2125
-
2126
-        return $this->parse_counter_prop($computed, 0);
2127
-    }
2128
-
2129
-    /**
2130
-     * @param string $computed
2131
-     * @return string[]|string
2132
-     *
2133
-     * @link https://www.w3.org/TR/CSS21/generate.html#propdef-content
2134
-     */
2135
-    protected function _get_content($computed)
2136
-    {
2137
-        if ($computed === "normal" || $computed === "none") {
2138
-            return $computed;
2139
-        }
2140
-
2141
-        return $this->parse_property_value($computed);
2142
-    }
2143
-
2144
-    /*==============================*/
2145
-
2146
-    /**
2147
-     * Parse a property value into its components.
2148
-     *
2149
-     * @param string $value
2150
-     *
2151
-     * @return string[]
2152
-     */
2153
-    protected function parse_property_value(string $value): array
2154
-    {
2155
-        $ident = self::CSS_IDENTIFIER;
2156
-        $number = self::CSS_NUMBER;
2157
-
2158
-        $pattern = "/\n" .
2159
-            "\s* \" ( (?:[^\"]|\\\\[\"])* ) (?<!\\\\)\" |\n" . // String ""
2160
-            "\s* '  ( (?:[^']|\\\\['])* )   (?<!\\\\)'  |\n" . // String ''
2161
-            "\s* ($ident \\([^)]*\\) )                  |\n" . // Functional
2162
-            "\s* ($ident)                               |\n" . // Keyword
2163
-            "\s* (\#[0-9a-fA-F]*)                       |\n" . // Hex value
2164
-            "\s* ($number [a-zA-Z%]*)                   |\n" . // Number (+ unit/percentage)
2165
-            "\s* ([\/,;])                                \n" . // Delimiter
2166
-            "/Sx";
2167
-
2168
-        if (!preg_match_all($pattern, $value, $matches)) {
2169
-            return [];
2170
-        }
2171
-
2172
-        return array_map("trim", $matches[0]);
2173
-    }
2174
-
2175
-    protected function is_color_value(string $val): bool
2176
-    {
2177
-        return $val === "currentcolor"
2178
-            || $val === "transparent"
2179
-            || isset(Color::$cssColorNames[$val])
2180
-            || preg_match("/^#|rgb\(|rgba\(|cmyk\(/", $val);
2181
-    }
2182
-
2183
-    /**
2184
-     * @param string $val
2185
-     * @return string|null
2186
-     */
2187
-    protected function compute_color_value(string $val): ?string
2188
-    {
2189
-        // https://www.w3.org/TR/css-color-4/#resolving-other-colors
2190
-        $munged_color = $val !== "currentcolor"
2191
-            ? $this->munge_color($val)
2192
-            : $val;
2193
-
2194
-        if ($munged_color === null) {
2195
-            return null;
2196
-        }
2197
-
2198
-        return \is_array($munged_color) ? $munged_color["hex"] : $munged_color;
2199
-    }
2200
-
2201
-    /**
2202
-     * @param string $val
2203
-     * @return int|null
2204
-     */
2205
-    protected function compute_integer(string $val): ?int
2206
-    {
2207
-        $integer = self::CSS_INTEGER;
2208
-        return preg_match("/^$integer$/", $val)
2209
-            ? (int) $val
2210
-            : null;
2211
-    }
2212
-
2213
-    /**
2214
-     * @param string $val
2215
-     * @return float|null
2216
-     */
2217
-    protected function compute_length(string $val): ?float
2218
-    {
2219
-        return mb_strpos($val, "%") === false
2220
-            ? $this->single_length_in_pt($val)
2221
-            : null;
2222
-    }
2223
-
2224
-    /**
2225
-     * @param string $val
2226
-     * @return float|null
2227
-     */
2228
-    protected function compute_length_positive(string $val): ?float
2229
-    {
2230
-        $computed = $this->compute_length($val);
2231
-        return $computed !== null && $computed >= 0 ? $computed : null;
2232
-    }
2233
-
2234
-    /**
2235
-     * @param string $val
2236
-     * @return float|string|null
2237
-     */
2238
-    protected function compute_length_percentage(string $val)
2239
-    {
2240
-        // Compute with a fixed ref size to decide whether percentage values
2241
-        // are valid
2242
-        $computed = $this->single_length_in_pt($val, 12);
2243
-
2244
-        if ($computed === null) {
2245
-            return null;
2246
-        }
2247
-
2248
-        // Retain valid percentage declarations
2249
-        return mb_strpos($val, "%") === false ? $computed : $val;
2250
-    }
2251
-
2252
-    /**
2253
-     * @param string $val
2254
-     * @return float|string|null
2255
-     */
2256
-    protected function compute_length_percentage_positive(string $val)
2257
-    {
2258
-        // Compute with a fixed ref size to decide whether percentage values
2259
-        // are valid
2260
-        $computed = $this->single_length_in_pt($val, 12);
2261
-
2262
-        if ($computed === null || $computed < 0) {
2263
-            return null;
2264
-        }
2265
-
2266
-        // Retain valid percentage declarations
2267
-        return mb_strpos($val, "%") === false ? $computed : $val;
2268
-    }
2269
-
2270
-    /**
2271
-     * @param string $val
2272
-     * @param string $style_prop The corresponding border-/outline-style property.
2273
-     *
2274
-     * @return float|null
2275
-     *
2276
-     * @link https://www.w3.org/TR/css-backgrounds-3/#typedef-line-width
2277
-     */
2278
-    protected function compute_line_width(string $val, string $style_prop): ?float
2279
-    {
2280
-        // Border-width keywords
2281
-        if ($val === "thin") {
2282
-            $computed = 0.5;
2283
-        } elseif ($val === "medium") {
2284
-            $computed = 1.5;
2285
-        } elseif ($val === "thick") {
2286
-            $computed = 2.5;
2287
-        } else {
2288
-            $computed = $this->compute_length_positive($val);
2289
-        }
2290
-
2291
-        if ($computed === null) {
2292
-            return null;
2293
-        }
2294
-
2295
-        // Computed width is 0 if the line style is `none` or `hidden`
2296
-        // https://www.w3.org/TR/css-backgrounds-3/#border-width
2297
-        // https://www.w3.org/TR/css-ui-4/#outline-width
2298
-        $lineStyle = $this->__get($style_prop);
2299
-        $hasLineStyle = $lineStyle !== "none" && $lineStyle !== "hidden";
2300
-
2301
-        return $hasLineStyle ? $computed : 0.0;
2302
-    }
2303
-
2304
-    /**
2305
-     * @param string $val
2306
-     * @return string|null
2307
-     */
2308
-    protected function compute_border_style(string $val): ?string
2309
-    {
2310
-        return \in_array($val, self::BORDER_STYLES, true) ? $val : null;
2311
-    }
2312
-
2313
-    /**
2314
-     * Parse a property value with 1 to 4 components into 4 values, as required
2315
-     * by shorthand properties such as `margin`, `padding`, and `border-radius`.
2316
-     *
2317
-     * @param string $prop  The shorthand property with exactly 4 sub-properties to handle.
2318
-     * @param string $value The property value to parse.
2319
-     *
2320
-     * @return string[]
2321
-     */
2322
-    protected function set_quad_shorthand(string $prop, string $value): array
2323
-    {
2324
-        $v = $this->parse_property_value($value);
2325
-
2326
-        switch (\count($v)) {
2327
-            case 1:
2328
-                $values = [$v[0], $v[0], $v[0], $v[0]];
2329
-                break;
2330
-            case 2:
2331
-                $values = [$v[0], $v[1], $v[0], $v[1]];
2332
-                break;
2333
-            case 3:
2334
-                $values = [$v[0], $v[1], $v[2], $v[1]];
2335
-                break;
2336
-            case 4:
2337
-                $values = [$v[0], $v[1], $v[2], $v[3]];
2338
-                break;
2339
-            default:
2340
-                return [];
2341
-        }
2342
-
2343
-        return array_combine(self::$_props_shorthand[$prop], $values);
2344
-    }
2345
-
2346
-    /*======================*/
2347
-
2348
-    /**
2349
-     * @link https://www.w3.org/TR/CSS21/visuren.html#display-prop
2350
-     */
2351
-    protected function _compute_display(string $val)
2352
-    {
2353
-        // Make sure that common valid, but unsupported display types have an
2354
-        // appropriate fallback display type
2355
-        switch ($val) {
2356
-            case "flow-root":
2357
-            case "flex":
2358
-            case "grid":
2359
-            case "table-caption":
2360
-                $val = "block";
2361
-                break;
2362
-            case "inline-flex":
2363
-            case "inline-grid":
2364
-                $val = "inline-block";
2365
-                break;
2366
-        }
2367
-
2368
-        if (!isset(self::$valid_display_types[$val])) {
2369
-            return null;
2370
-        }
2371
-
2372
-        // https://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
2373
-        if ($this->is_in_flow()) {
2374
-            return $val;
2375
-        } else {
2376
-            switch ($val) {
2377
-                case "inline":
2378
-                case "inline-block":
2379
-                // case "table-row-group":
2380
-                // case "table-header-group":
2381
-                // case "table-footer-group":
2382
-                // case "table-row":
2383
-                // case "table-cell":
2384
-                // case "table-column-group":
2385
-                // case "table-column":
2386
-                // case "table-caption":
2387
-                    return "block";
2388
-                case "inline-table":
2389
-                    return "table";
2390
-                default:
2391
-                    return $val;
2392
-            }
2393
-        }
2394
-    }
2395
-
2396
-    /**
2397
-     * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color
2398
-     */
2399
-    protected function _compute_color(string $color)
2400
-    {
2401
-        return $this->compute_color_value($color);
2402
-    }
2403
-
2404
-    /**
2405
-     * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color
2406
-     */
2407
-    protected function _compute_background_color(string $color)
2408
-    {
2409
-        return $this->compute_color_value($color);
2410
-    }
2411
-
2412
-    /**
2413
-     * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
2414
-     */
2415
-    protected function _compute_background_image(string $val)
2416
-    {
2417
-        $parsed_val = $this->_stylesheet->resolve_url($val);
2418
-
2419
-        if ($parsed_val === "none") {
2420
-            return "none";
2421
-        } else {
2422
-            return "url($parsed_val)";
2423
-        }
2424
-    }
2425
-
2426
-    /**
2427
-     * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
2428
-     */
2429
-    protected function _compute_background_repeat(string $val)
2430
-    {
2431
-        $keywords = ["repeat", "repeat-x", "repeat-y", "no-repeat"];
2432
-        return \in_array($val, $keywords, true) ? $val : null;
2433
-    }
2434
-
2435
-    /**
2436
-     * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
2437
-     */
2438
-    protected function _compute_background_attachment(string $val)
2439
-    {
2440
-        $keywords = ["scroll", "fixed"];
2441
-        return \in_array($val, $keywords, true) ? $val : null;
2442
-    }
2443
-
2444
-    /**
2445
-     * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-position
2446
-     */
2447
-    protected function _compute_background_position(string $val)
2448
-    {
2449
-        $parts = preg_split("/\s+/", $val);
2450
-
2451
-        if (\count($parts) > 2) {
2452
-            return null;
2453
-        }
2454
-
2455
-        switch ($parts[0]) {
2456
-            case "left":
2457
-                $x = "0%";
2458
-                break;
2459
-
2460
-            case "right":
2461
-                $x = "100%";
2462
-                break;
2463
-
2464
-            case "top":
2465
-                $y = "0%";
2466
-                break;
2467
-
2468
-            case "bottom":
2469
-                $y = "100%";
2470
-                break;
2471
-
2472
-            case "center":
2473
-                $x = "50%";
2474
-                $y = "50%";
2475
-                break;
2476
-
2477
-            default:
2478
-                $x = $parts[0];
2479
-                break;
2480
-        }
2481
-
2482
-        if (isset($parts[1])) {
2483
-            switch ($parts[1]) {
2484
-                case "left":
2485
-                    $x = "0%";
2486
-                    break;
2487
-
2488
-                case "right":
2489
-                    $x = "100%";
2490
-                    break;
2491
-
2492
-                case "top":
2493
-                    $y = "0%";
2494
-                    break;
2495
-
2496
-                case "bottom":
2497
-                    $y = "100%";
2498
-                    break;
2499
-
2500
-                case "center":
2501
-                    if ($parts[0] === "left" || $parts[0] === "right" || $parts[0] === "center") {
2502
-                        $y = "50%";
2503
-                    } else {
2504
-                        $x = "50%";
2505
-                    }
2506
-                    break;
2507
-
2508
-                default:
2509
-                    $y = $parts[1];
2510
-                    break;
2511
-            }
2512
-        } else {
2513
-            $y = "50%";
2514
-        }
2515
-
2516
-        if (!isset($x)) {
2517
-            $x = "0%";
2518
-        }
2519
-
2520
-        if (!isset($y)) {
2521
-            $y = "0%";
2522
-        }
2523
-
2524
-        return [$x, $y];
2525
-    }
2526
-
2527
-    /**
2528
-     * Compute `background-size`.
2529
-     *
2530
-     * Computes to one of the following values:
2531
-     * * `cover`
2532
-     * * `contain`
2533
-     * * `[width, height]`, each being a length, percentage, or `auto`
2534
-     *
2535
-     * @link https://www.w3.org/TR/css-backgrounds-3/#background-size
2536
-     */
2537
-    protected function _compute_background_size(string $val)
2538
-    {
2539
-        if ($val === "cover" || $val === "contain") {
2540
-            return $val;
2541
-        }
2542
-
2543
-        $parts = preg_split("/\s+/", $val);
2544
-
2545
-        if (\count($parts) > 2) {
2546
-            return null;
2547
-        }
2548
-
2549
-        $width = $parts[0];
2550
-        if ($width !== "auto") {
2551
-            $width = $this->compute_length_percentage_positive($width);
2552
-        }
2553
-
2554
-        $height = $parts[1] ?? "auto";
2555
-        if ($height !== "auto") {
2556
-            $height = $this->compute_length_percentage_positive($height);
2557
-        }
2558
-
2559
-        if ($width === null || $height === null) {
2560
-            return null;
2561
-        }
2562
-
2563
-        return [$width, $height];
2564
-    }
2565
-
2566
-    /**
2567
-     * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-background
2568
-     */
2569
-    protected function _set_background(string $value): array
2570
-    {
2571
-        $components = $this->parse_property_value($value);
2572
-        $props = [];
2573
-        $pos_size = [];
2574
-
2575
-        foreach ($components as $val) {
2576
-            if ($val === "none" || mb_substr($val, 0, 4) === "url(") {
2577
-                $props["background_image"] = $val;
2578
-            } elseif ($val === "scroll" || $val === "fixed") {
2579
-                $props["background_attachment"] = $val;
2580
-            } elseif ($val === "repeat" || $val === "repeat-x" || $val === "repeat-y" || $val === "no-repeat") {
2581
-                $props["background_repeat"] = $val;
2582
-            } elseif ($this->is_color_value($val)) {
2583
-                $props["background_color"] = $val;
2584
-            } else {
2585
-                $pos_size[] = $val;
2586
-            }
2587
-        }
2588
-
2589
-        if (\count($pos_size)) {
2590
-            // Split value list at "/"
2591
-            $index = array_search("/", $pos_size, true);
2592
-
2593
-            if ($index !== false) {
2594
-                $pos = \array_slice($pos_size, 0, $index);
2595
-                $size = \array_slice($pos_size, $index + 1);
2596
-            } else {
2597
-                $pos = $pos_size;
2598
-                $size = [];
2599
-            }
2600
-
2601
-            $props["background_position"] = implode(" ", $pos);
2602
-
2603
-            if (\count($size)) {
2604
-                $props["background_size"] = implode(" ", $size);
2605
-            }
2606
-        }
2607
-
2608
-        return $props;
2609
-    }
2610
-
2611
-    /**
2612
-     * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
2613
-     */
2614
-    protected function _compute_font_size(string $size)
2615
-    {
2616
-        $parent_font_size = isset($this->parent_style)
2617
-            ? $this->parent_style->__get("font_size")
2618
-            : self::$default_font_size;
2619
-
2620
-        switch ($size) {
2621
-            case "xx-small":
2622
-            case "x-small":
2623
-            case "small":
2624
-            case "medium":
2625
-            case "large":
2626
-            case "x-large":
2627
-            case "xx-large":
2628
-                $fs = self::$default_font_size * self::$font_size_keywords[$size];
2629
-                break;
2630
-
2631
-            case "smaller":
2632
-                $fs = 8 / 9 * $parent_font_size;
2633
-                break;
2634
-
2635
-            case "larger":
2636
-                $fs = 6 / 5 * $parent_font_size;
2637
-                break;
2638
-
2639
-            default:
2640
-                $fs = $this->single_length_in_pt($size, $parent_font_size, $parent_font_size);
2641
-                break;
2642
-        }
2643
-
2644
-        return $fs;
2645
-    }
2646
-
2647
-    /**
2648
-     * @link https://www.w3.org/TR/CSS21/fonts.html#font-boldness
2649
-     */
2650
-    protected function _compute_font_weight(string $weight)
2651
-    {
2652
-        $computed_weight = $weight;
2653
-
2654
-        if ($weight === "bolder") {
2655
-            //TODO: One font weight heavier than the parent element (among the available weights of the font).
2656
-            $computed_weight = "bold";
2657
-        } elseif ($weight === "lighter") {
2658
-            //TODO: One font weight lighter than the parent element (among the available weights of the font).
2659
-            $computed_weight = "normal";
2660
-        }
2661
-
2662
-        return $computed_weight;
2663
-    }
2664
-
2665
-    /**
2666
-     * Handle the `font` shorthand property.
2667
-     *
2668
-     * `[ font-style || font-variant || font-weight ] font-size [ / line-height ] font-family`
2669
-     *
2670
-     * @link https://www.w3.org/TR/CSS21/fonts.html#font-shorthand
2671
-     */
2672
-    protected function _set_font(string $value): array
2673
-    {
2674
-        $components = $this->parse_property_value($value);
2675
-        $props = [];
2676
-
2677
-        $number = self::CSS_NUMBER;
2678
-        $unit = "pt|px|pc|rem|em|ex|in|cm|mm|%";
2679
-        $sizePattern = "/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|$number(?:$unit))$/";
2680
-        $sizeIndex = null;
2681
-
2682
-        // Find index of font-size to split the component list
2683
-        foreach ($components as $i => $val) {
2684
-            if (preg_match($sizePattern, $val)) {
2685
-                $sizeIndex = $i;
2686
-                $props["font_size"] = $val;
2687
-                break;
2688
-            }
2689
-        }
2690
-
2691
-        // `font-size` is mandatory
2692
-        if ($sizeIndex === null) {
2693
-            return [];
2694
-        }
2695
-
2696
-        // `font-style`, `font-variant`, `font-weight` in any order
2697
-        $styleVariantWeight = \array_slice($components, 0, $sizeIndex);
2698
-        $stylePattern = "/^(italic|oblique)$/";
2699
-        $variantPattern = "/^(small-caps)$/";
2700
-        $weightPattern = "/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900)$/";
2701
-
2702
-        if (\count($styleVariantWeight) > 3) {
2703
-            return [];
2704
-        }
2705
-
2706
-        foreach ($styleVariantWeight as $val) {
2707
-            if ($val === "normal") {
2708
-                // Ignore any `normal` value, as it is valid and the initial
2709
-                // value for all three properties
2710
-            } elseif (!isset($props["font_style"]) && preg_match($stylePattern, $val)) {
2711
-                $props["font_style"] = $val;
2712
-            } elseif (!isset($props["font_variant"]) && preg_match($variantPattern, $val)) {
2713
-                $props["font_variant"] = $val;
2714
-            } elseif (!isset($props["font_weight"]) && preg_match($weightPattern, $val)) {
2715
-                $props["font_weight"] = $val;
2716
-            } else {
2717
-                // Duplicates and other values disallowed here
2718
-                return [];
2719
-            }
2720
-        }
2721
-
2722
-        // Optional slash + `line-height` followed by mandatory `font-family`
2723
-        $lineFamily = \array_slice($components, $sizeIndex + 1);
2724
-        $hasLineHeight = $lineFamily !== [] && $lineFamily[0] === "/";
2725
-        $lineHeight = $hasLineHeight ? \array_slice($lineFamily, 1, 1) : [];
2726
-        $fontFamily = $hasLineHeight ? \array_slice($lineFamily, 2) : $lineFamily;
2727
-        $lineHeightPattern = "/^(normal|$number(?:$unit)?)$/";
2728
-
2729
-        // Missing `font-family` or `line-height` after slash
2730
-        if ($fontFamily === []
2731
-            || ($hasLineHeight && !preg_match($lineHeightPattern, $lineHeight[0]))
2732
-        ) {
2733
-            return [];
2734
-        }
2735
-
2736
-        if ($hasLineHeight) {
2737
-            $props["line_height"] = $lineHeight[0];
2738
-        }
2739
-
2740
-        $props["font_family"] = implode("", $fontFamily);
2741
-
2742
-        return $props;
2743
-    }
2744
-
2745
-    /**
2746
-     * Compute `text-align`.
2747
-     *
2748
-     * If no alignment is set on the element and the direction is rtl then
2749
-     * the property is set to "right", otherwise it is set to "left".
2750
-     *
2751
-     * @link https://www.w3.org/TR/CSS21/text.html#propdef-text-align
2752
-     */
2753
-    protected function _compute_text_align(string $val)
2754
-    {
2755
-        $alignment = $val;
2756
-        if ($alignment === "") {
2757
-            $alignment = "left";
2758
-            if ($this->__get("direction") === "rtl") {
2759
-                $alignment = "right";
2760
-            }
2761
-        }
2762
-
2763
-        if (!\in_array($alignment, self::TEXT_ALIGN_KEYWORDS, true)) {
2764
-            return null;
2765
-        }
2766
-
2767
-        return $alignment;
2768
-    }
2769
-
2770
-    /**
2771
-     * @link https://www.w3.org/TR/css-text-4/#word-spacing-property
2772
-     */
2773
-    protected function _compute_word_spacing(string $val)
2774
-    {
2775
-        if ($val === "normal") {
2776
-            return 0.0;
2777
-        }
2778
-
2779
-        return $this->compute_length_percentage($val);
2780
-    }
2781
-
2782
-    /**
2783
-     * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property
2784
-     */
2785
-    protected function _compute_letter_spacing(string $val)
2786
-    {
2787
-        if ($val === "normal") {
2788
-            return 0.0;
2789
-        }
2790
-
2791
-        return $this->compute_length_percentage($val);
2792
-    }
2793
-
2794
-    /**
2795
-     * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
2796
-     */
2797
-    protected function _compute_line_height(string $val)
2798
-    {
2799
-        if ($val === "normal") {
2800
-            return $val;
2801
-        }
2802
-
2803
-        // Compute number values to string and lengths to float (in pt)
2804
-        if (is_numeric($val)) {
2805
-            return (string) $val;
2806
-        }
2807
-
2808
-        $font_size = $this->__get("font_size");
2809
-        $computed = $this->single_length_in_pt($val, $font_size);
2810
-        return $computed !== null && $computed >= 0 ? $computed : null;
2811
-    }
2812
-
2813
-    /**
2814
-     * @link https://www.w3.org/TR/css-text-3/#text-indent-property
2815
-     */
2816
-    protected function _compute_text_indent(string $val)
2817
-    {
2818
-        return $this->compute_length_percentage($val);
2819
-    }
2820
-
2821
-    /**
2822
-     * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-before
2823
-     */
2824
-    protected function _compute_page_break_before(string $break)
2825
-    {
2826
-        if ($break === "left" || $break === "right") {
2827
-            $break = "always";
2828
-        }
2829
-
2830
-        return $break;
2831
-    }
2832
-
2833
-    /**
2834
-     * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-after
2835
-     */
2836
-    protected function _compute_page_break_after(string $break)
2837
-    {
2838
-        if ($break === "left" || $break === "right") {
2839
-            $break = "always";
2840
-        }
2841
-
2842
-        return $break;
2843
-    }
2844
-
2845
-    /**
2846
-     * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-width
2847
-     */
2848
-    protected function _compute_width(string $val)
2849
-    {
2850
-        if ($val === "auto") {
2851
-            return $val;
2852
-        }
2853
-
2854
-        return $this->compute_length_percentage_positive($val);
2855
-    }
2856
-
2857
-    /**
2858
-     * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-height
2859
-     */
2860
-    protected function _compute_height(string $val)
2861
-    {
2862
-        if ($val === "auto") {
2863
-            return $val;
2864
-        }
2865
-
2866
-        return $this->compute_length_percentage_positive($val);
2867
-    }
2868
-
2869
-    /**
2870
-     * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-width
2871
-     */
2872
-    protected function _compute_min_width(string $val)
2873
-    {
2874
-        // Legacy support for `none`, not covered by spec
2875
-        if ($val === "auto" || $val === "none") {
2876
-            return "auto";
2877
-        }
2878
-
2879
-        return $this->compute_length_percentage_positive($val);
2880
-    }
2881
-
2882
-    /**
2883
-     * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-height
2884
-     */
2885
-    protected function _compute_min_height(string $val)
2886
-    {
2887
-        // Legacy support for `none`, not covered by spec
2888
-        if ($val === "auto" || $val === "none") {
2889
-            return "auto";
2890
-        }
2891
-
2892
-        return $this->compute_length_percentage_positive($val);
2893
-    }
2894
-
2895
-    /**
2896
-     * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-width
2897
-     */
2898
-    protected function _compute_max_width(string $val)
2899
-    {
2900
-        // Legacy support for `auto`, not covered by spec
2901
-        if ($val === "none" || $val === "auto") {
2902
-            return "none";
2903
-        }
2904
-
2905
-        return $this->compute_length_percentage_positive($val);
2906
-    }
2907
-
2908
-    /**
2909
-     * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-height
2910
-     */
2911
-    protected function _compute_max_height(string $val)
2912
-    {
2913
-        // Legacy support for `auto`, not covered by spec
2914
-        if ($val === "none" || $val === "auto") {
2915
-            return "none";
2916
-        }
2917
-
2918
-        return $this->compute_length_percentage_positive($val);
2919
-    }
2920
-
2921
-    /**
2922
-     * @link https://www.w3.org/TR/css-position-3/#inset-properties
2923
-     * @link https://www.w3.org/TR/css-position-3/#propdef-inset
2924
-     */
2925
-    protected function _set_inset(string $val): array
2926
-    {
2927
-        return $this->set_quad_shorthand("inset", $val);
2928
-    }
2929
-
2930
-    /**
2931
-     * @param string $val
2932
-     * @return float|string|null
2933
-     */
2934
-    protected function compute_box_inset(string $val)
2935
-    {
2936
-        if ($val === "auto") {
2937
-            return $val;
2938
-        }
2939
-
2940
-        return $this->compute_length_percentage($val);
2941
-    }
2942
-
2943
-    protected function _compute_top(string $val)
2944
-    {
2945
-        return $this->compute_box_inset($val);
2946
-    }
2947
-
2948
-    protected function _compute_right(string $val)
2949
-    {
2950
-        return $this->compute_box_inset($val);
2951
-    }
2952
-
2953
-    protected function _compute_bottom(string $val)
2954
-    {
2955
-        return $this->compute_box_inset($val);
2956
-    }
2957
-
2958
-    protected function _compute_left(string $val)
2959
-    {
2960
-        return $this->compute_box_inset($val);
2961
-    }
2962
-
2963
-    /**
2964
-     * @link https://www.w3.org/TR/CSS21/box.html#margin-properties
2965
-     * @link https://www.w3.org/TR/CSS21/box.html#propdef-margin
2966
-     */
2967
-    protected function _set_margin(string $val): array
2968
-    {
2969
-        return $this->set_quad_shorthand("margin", $val);
2970
-    }
2971
-
2972
-    /**
2973
-     * @param string $val
2974
-     * @return float|string|null
2975
-     */
2976
-    protected function compute_margin(string $val)
2977
-    {
2978
-        // Legacy support for `none` keyword, not covered by spec
2979
-        if ($val === "none") {
2980
-            return 0.0;
2981
-        }
2982
-
2983
-        if ($val === "auto") {
2984
-            return $val;
2985
-        }
2986
-
2987
-        return $this->compute_length_percentage($val);
2988
-    }
2989
-
2990
-    protected function _compute_margin_top(string $val)
2991
-    {
2992
-        return $this->compute_margin($val);
2993
-    }
2994
-
2995
-    protected function _compute_margin_right(string $val)
2996
-    {
2997
-        return $this->compute_margin($val);
2998
-    }
2999
-
3000
-    protected function _compute_margin_bottom(string $val)
3001
-    {
3002
-        return $this->compute_margin($val);
3003
-    }
3004
-
3005
-    protected function _compute_margin_left(string $val)
3006
-    {
3007
-        return $this->compute_margin($val);
3008
-    }
3009
-
3010
-    /**
3011
-     * @link https://www.w3.org/TR/CSS21/box.html#padding-properties
3012
-     * @link https://www.w3.org/TR/CSS21/box.html#propdef-padding
3013
-     */
3014
-    protected function _set_padding(string $val): array
3015
-    {
3016
-        return $this->set_quad_shorthand("padding", $val);
3017
-    }
3018
-
3019
-    /**
3020
-     * @param string $val
3021
-     * @return float|string|null
3022
-     */
3023
-    protected function compute_padding(string $val)
3024
-    {
3025
-        // Legacy support for `none` keyword, not covered by spec
3026
-        if ($val === "none") {
3027
-            return 0.0;
3028
-        }
3029
-
3030
-        return $this->compute_length_percentage_positive($val);
3031
-    }
3032
-
3033
-    protected function _compute_padding_top(string $val)
3034
-    {
3035
-        return $this->compute_padding($val);
3036
-    }
3037
-
3038
-    protected function _compute_padding_right(string $val)
3039
-    {
3040
-        return $this->compute_padding($val);
3041
-    }
3042
-
3043
-    protected function _compute_padding_bottom(string $val)
3044
-    {
3045
-        return $this->compute_padding($val);
3046
-    }
3047
-
3048
-    protected function _compute_padding_left(string $val)
3049
-    {
3050
-        return $this->compute_padding($val);
3051
-    }
3052
-
3053
-    /**
3054
-     * @param string   $value  `width || style || color`
3055
-     * @param string[] $styles The list of border styles to accept.
3056
-     *
3057
-     * @return array Array of `[width, style, color]`, or `null` if the declaration is invalid.
3058
-     */
3059
-    protected function parse_border_side(string $value, array $styles = self::BORDER_STYLES): ?array
3060
-    {
3061
-        $components = $this->parse_property_value($value);
3062
-        $width = null;
3063
-        $style = null;
3064
-        $color = null;
3065
-
3066
-        foreach ($components as $val) {
3067
-            if ($style === null && \in_array($val, $styles, true)) {
3068
-                $style = $val;
3069
-            } elseif ($color === null && $this->is_color_value($val)) {
3070
-                $color = $val;
3071
-            } elseif ($width === null) {
3072
-                // Assume width
3073
-                $width = $val;
3074
-            } else {
3075
-                // Duplicates are not allowed
3076
-                return null;
3077
-            }
3078
-        }
3079
-
3080
-        return [$width, $style, $color];
3081
-    }
3082
-
3083
-    /**
3084
-     * @link https://www.w3.org/TR/CSS21/box.html#border-properties
3085
-     * @link https://www.w3.org/TR/CSS21/box.html#propdef-border
3086
-     */
3087
-    protected function _set_border(string $value): array
3088
-    {
3089
-        $values = $this->parse_border_side($value);
3090
-
3091
-        if ($values === null) {
3092
-            return [];
3093
-        }
3094
-
3095
-        return array_merge(
3096
-            array_combine(self::$_props_shorthand["border_top"], $values),
3097
-            array_combine(self::$_props_shorthand["border_right"], $values),
3098
-            array_combine(self::$_props_shorthand["border_bottom"], $values),
3099
-            array_combine(self::$_props_shorthand["border_left"], $values)
3100
-        );
3101
-    }
3102
-
3103
-    /**
3104
-     * @param string $prop
3105
-     * @param string $value
3106
-     * @return array
3107
-     */
3108
-    protected function set_border_side(string $prop, string $value): array
3109
-    {
3110
-        $values = $this->parse_border_side($value);
3111
-
3112
-        if ($values === null) {
3113
-            return [];
3114
-        }
3115
-
3116
-        return array_combine(self::$_props_shorthand[$prop], $values);
3117
-    }
3118
-
3119
-    protected function _set_border_top(string $val): array
3120
-    {
3121
-        return $this->set_border_side("border_top", $val);
3122
-    }
3123
-
3124
-    protected function _set_border_right(string $val): array
3125
-    {
3126
-        return $this->set_border_side("border_right", $val);
3127
-    }
3128
-
3129
-    protected function _set_border_bottom(string $val): array
3130
-    {
3131
-        return $this->set_border_side("border_bottom", $val);
3132
-    }
3133
-
3134
-    protected function _set_border_left(string $val): array
3135
-    {
3136
-        return $this->set_border_side("border_left", $val);
3137
-    }
3138
-
3139
-    /**
3140
-     * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-color
3141
-     */
3142
-    protected function _set_border_color(string $val): array
3143
-    {
3144
-        return $this->set_quad_shorthand("border_color", $val);
3145
-    }
3146
-
3147
-    protected function _compute_border_top_color(string $val)
3148
-    {
3149
-        return $this->compute_color_value($val);
3150
-    }
3151
-
3152
-    protected function _compute_border_right_color(string $val)
3153
-    {
3154
-        return $this->compute_color_value($val);
3155
-    }
3156
-
3157
-    protected function _compute_border_bottom_color(string $val)
3158
-    {
3159
-        return $this->compute_color_value($val);
3160
-    }
3161
-
3162
-    protected function _compute_border_left_color(string $val)
3163
-    {
3164
-        return $this->compute_color_value($val);
3165
-    }
3166
-
3167
-    /**
3168
-     * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-style
3169
-     */
3170
-    protected function _set_border_style(string $val): array
3171
-    {
3172
-        return $this->set_quad_shorthand("border_style", $val);
3173
-    }
3174
-
3175
-    protected function _compute_border_top_style(string $val)
3176
-    {
3177
-        return $this->compute_border_style($val);
3178
-    }
3179
-
3180
-    protected function _compute_border_right_style(string $val)
3181
-    {
3182
-        return $this->compute_border_style($val);
3183
-    }
3184
-
3185
-    protected function _compute_border_bottom_style(string $val)
3186
-    {
3187
-        return $this->compute_border_style($val);
3188
-    }
3189
-
3190
-    protected function _compute_border_left_style(string $val)
3191
-    {
3192
-        return $this->compute_border_style($val);
3193
-    }
3194
-
3195
-    /**
3196
-     * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-width
3197
-     */
3198
-    protected function _set_border_width(string $val): array
3199
-    {
3200
-        return $this->set_quad_shorthand("border_width", $val);
3201
-    }
3202
-
3203
-    protected function _compute_border_top_width(string $val)
3204
-    {
3205
-        return $this->compute_line_width($val, "border_top_style");
3206
-    }
3207
-
3208
-    protected function _compute_border_right_width(string $val)
3209
-    {
3210
-        return $this->compute_line_width($val, "border_right_style");
3211
-    }
3212
-
3213
-    protected function _compute_border_bottom_width(string $val)
3214
-    {
3215
-        return $this->compute_line_width($val, "border_bottom_style");
3216
-    }
3217
-
3218
-    protected function _compute_border_left_width(string $val)
3219
-    {
3220
-        return $this->compute_line_width($val, "border_left_style");
3221
-    }
3222
-
3223
-    /**
3224
-     * @link https://www.w3.org/TR/css-backgrounds-3/#corners
3225
-     * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-border-radius
3226
-     */
3227
-    protected function _set_border_radius(string $val): array
3228
-    {
3229
-        return $this->set_quad_shorthand("border_radius", $val);
3230
-    }
3231
-
3232
-    protected function _compute_border_top_left_radius(string $val)
3233
-    {
3234
-        return $this->compute_length_percentage_positive($val);
3235
-    }
3236
-
3237
-    protected function _compute_border_top_right_radius(string $val)
3238
-    {
3239
-        return $this->compute_length_percentage_positive($val);
3240
-    }
3241
-
3242
-    protected function _compute_border_bottom_right_radius(string $val)
3243
-    {
3244
-        return $this->compute_length_percentage_positive($val);
3245
-    }
3246
-
3247
-    protected function _compute_border_bottom_left_radius(string $val)
3248
-    {
3249
-        return $this->compute_length_percentage_positive($val);
3250
-    }
3251
-
3252
-    /**
3253
-     * @link https://www.w3.org/TR/css-ui-4/#outline-props
3254
-     * @link https://www.w3.org/TR/css-ui-4/#propdef-outline
3255
-     */
3256
-    protected function _set_outline(string $value): array
3257
-    {
3258
-        $values = $this->parse_border_side($value, self::OUTLINE_STYLES);
3259
-
3260
-        if ($values === null) {
3261
-            return [];
3262
-        }
3263
-
3264
-        return array_combine(self::$_props_shorthand["outline"], $values);
3265
-    }
3266
-
3267
-    protected function _compute_outline_color(string $val)
3268
-    {
3269
-        return $this->compute_color_value($val);
3270
-    }
3271
-
3272
-    protected function _compute_outline_style(string $val)
3273
-    {
3274
-        return \in_array($val, self::OUTLINE_STYLES, true) ? $val : null;
3275
-    }
3276
-
3277
-    protected function _compute_outline_width(string $val)
3278
-    {
3279
-        return $this->compute_line_width($val, "outline_style");
3280
-    }
3281
-
3282
-    /**
3283
-     * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-offset
3284
-     */
3285
-    protected function _compute_outline_offset(string $val)
3286
-    {
3287
-        return $this->compute_length($val);
3288
-    }
3289
-
3290
-    /**
3291
-     * Compute `border-spacing` to two lengths of the form
3292
-     * `[horizontal, vertical]`.
3293
-     *
3294
-     * @link https://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing
3295
-     */
3296
-    protected function _compute_border_spacing(string $val)
3297
-    {
3298
-        $parts = preg_split("/\s+/", $val);
3299
-
3300
-        if (\count($parts) > 2) {
3301
-            return null;
3302
-        }
3303
-
3304
-        $h = $this->compute_length_positive($parts[0]);
3305
-        $v = isset($parts[1])
3306
-            ? $this->compute_length_positive($parts[1])
3307
-            : $h;
3308
-
3309
-        if ($h === null || $v === null) {
3310
-            return null;
3311
-        }
3312
-
3313
-        return [$h, $v];
3314
-    }
3315
-
3316
-    /**
3317
-     * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
3318
-     */
3319
-    protected function _compute_list_style_image(string $val)
3320
-    {
3321
-        $parsed_val = $this->_stylesheet->resolve_url($val);
3322
-
3323
-        if ($parsed_val === "none") {
3324
-            return "none";
3325
-        } else {
3326
-            return "url($parsed_val)";
3327
-        }
3328
-    }
3329
-
3330
-    /**
3331
-     * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style
3332
-     */
3333
-    protected function _set_list_style(string $value): array
3334
-    {
3335
-        static $positions = ["inside", "outside"];
3336
-        static $types = [
3337
-            "disc", "circle", "square",
3338
-            "decimal-leading-zero", "decimal", "1",
3339
-            "lower-roman", "upper-roman", "a", "A",
3340
-            "lower-greek",
3341
-            "lower-latin", "upper-latin",
3342
-            "lower-alpha", "upper-alpha",
3343
-            "armenian", "georgian", "hebrew",
3344
-            "cjk-ideographic", "hiragana", "katakana",
3345
-            "hiragana-iroha", "katakana-iroha", "none"
3346
-        ];
3347
-
3348
-        $components = $this->parse_property_value($value);
3349
-        $props = [];
3350
-
3351
-        foreach ($components as $val) {
3352
-            /* https://www.w3.org/TR/CSS21/generate.html#list-style
176
+	protected const CSS_IDENTIFIER = "-?[_a-zA-Z]+[_a-zA-Z0-9-]*";
177
+	protected const CSS_INTEGER = "[+-]?\d+";
178
+	protected const CSS_NUMBER = "[+-]?\d*\.?\d+(?:[eE][+-]?\d+)?";
179
+
180
+	/**
181
+	 * Default font size, in points.
182
+	 *
183
+	 * @var float
184
+	 */
185
+	public static $default_font_size = 12;
186
+
187
+	/**
188
+	 * Default line height, as a fraction of the font size.
189
+	 *
190
+	 * @var float
191
+	 */
192
+	public static $default_line_height = 1.2;
193
+
194
+	/**
195
+	 * Default "absolute" font sizes relative to the default font-size
196
+	 * https://www.w3.org/TR/css-fonts-3/#absolute-size-value
197
+	 *
198
+	 * @var array<float>
199
+	 */
200
+	public static $font_size_keywords = [
201
+		"xx-small" => 0.6, // 3/5
202
+		"x-small" => 0.75, // 3/4
203
+		"small" => 0.889, // 8/9
204
+		"medium" => 1, // 1
205
+		"large" => 1.2, // 6/5
206
+		"x-large" => 1.5, // 3/2
207
+		"xx-large" => 2.0, // 2/1
208
+	];
209
+
210
+	/**
211
+	 * List of valid text-align keywords.
212
+	 */
213
+	public const TEXT_ALIGN_KEYWORDS = ["left", "right", "center", "justify"];
214
+
215
+	/**
216
+	 * List of valid vertical-align keywords.
217
+	 */
218
+	public const VERTICAL_ALIGN_KEYWORDS = ["baseline", "bottom", "middle",
219
+		"sub", "super", "text-bottom", "text-top", "top"];
220
+
221
+	/**
222
+	 * List of all block-level (outer) display types.
223
+	 * * https://www.w3.org/TR/css-display-3/#display-type
224
+	 * * https://www.w3.org/TR/css-display-3/#block-level
225
+	 */
226
+	public const BLOCK_LEVEL_TYPES = [
227
+		"block",
228
+		// "flow-root",
229
+		"list-item",
230
+		// "flex",
231
+		// "grid",
232
+		"table"
233
+	];
234
+
235
+	/**
236
+	 * List of all inline-level (outer) display types.
237
+	 * * https://www.w3.org/TR/css-display-3/#display-type
238
+	 * * https://www.w3.org/TR/css-display-3/#inline-level
239
+	 */
240
+	public const INLINE_LEVEL_TYPES = [
241
+		"inline",
242
+		"inline-block",
243
+		// "inline-flex",
244
+		// "inline-grid",
245
+		"inline-table"
246
+	];
247
+
248
+	/**
249
+	 * List of all table-internal (outer) display types.
250
+	 * * https://www.w3.org/TR/css-display-3/#layout-specific-display
251
+	 */
252
+	public const TABLE_INTERNAL_TYPES = [
253
+		"table-row-group",
254
+		"table-header-group",
255
+		"table-footer-group",
256
+		"table-row",
257
+		"table-cell",
258
+		"table-column-group",
259
+		"table-column",
260
+		"table-caption"
261
+	];
262
+
263
+	/**
264
+	 * List of all inline (inner) display types.
265
+	 */
266
+	public const INLINE_TYPES = ["inline"];
267
+
268
+	/**
269
+	 * List of all block (inner) display types.
270
+	 */
271
+	public const BLOCK_TYPES = ["block", "inline-block", "table-cell", "list-item"];
272
+
273
+	/**
274
+	 * List of all table (inner) display types.
275
+	 */
276
+	public const TABLE_TYPES = ["table", "inline-table"];
277
+
278
+	/**
279
+	 * Lookup table for valid display types. Initially computed from the
280
+	 * different constants.
281
+	 *
282
+	 * @var array
283
+	 */
284
+	protected static $valid_display_types = [];
285
+
286
+	/**
287
+	 * List of all positioned types.
288
+	 */
289
+	public const POSITIONED_TYPES = ["relative", "absolute", "fixed"];
290
+
291
+	/**
292
+	 * List of valid border styles.
293
+	 */
294
+	public const BORDER_STYLES = [
295
+		"none", "hidden",
296
+		"dotted", "dashed", "solid",
297
+		"double", "groove", "ridge", "inset", "outset"
298
+	];
299
+
300
+	/**
301
+	 * List of valid outline-style values.
302
+	 * Same as the border styles, except `auto` is allowed, `hidden` is not.
303
+	 *
304
+	 * @link https://www.w3.org/TR/css-ui-4/#typedef-outline-line-style
305
+	 */
306
+	protected const OUTLINE_STYLES = [
307
+		"auto", "none",
308
+		"dotted", "dashed", "solid",
309
+		"double", "groove", "ridge", "inset", "outset"
310
+	];
311
+
312
+	/**
313
+	 * Map of CSS shorthand properties and their corresponding sub-properties.
314
+	 * The order of the sub-properties is relevant for the fallback getter,
315
+	 * which is used in case no specific getter method is defined.
316
+	 *
317
+	 * @var array
318
+	 */
319
+	protected static $_props_shorthand = [
320
+		"background" => [
321
+			"background_image",
322
+			"background_position",
323
+			"background_size",
324
+			"background_repeat",
325
+			// "background_origin",
326
+			// "background_clip",
327
+			"background_attachment",
328
+			"background_color"
329
+		],
330
+		"border" => [
331
+			"border_top_width",
332
+			"border_right_width",
333
+			"border_bottom_width",
334
+			"border_left_width",
335
+			"border_top_style",
336
+			"border_right_style",
337
+			"border_bottom_style",
338
+			"border_left_style",
339
+			"border_top_color",
340
+			"border_right_color",
341
+			"border_bottom_color",
342
+			"border_left_color"
343
+		],
344
+		"border_top" => [
345
+			"border_top_width",
346
+			"border_top_style",
347
+			"border_top_color"
348
+		],
349
+		"border_right" => [
350
+			"border_right_width",
351
+			"border_right_style",
352
+			"border_right_color"
353
+		],
354
+		"border_bottom" => [
355
+			"border_bottom_width",
356
+			"border_bottom_style",
357
+			"border_bottom_color"
358
+		],
359
+		"border_left" => [
360
+			"border_left_width",
361
+			"border_left_style",
362
+			"border_left_color"
363
+		],
364
+		"border_width" => [
365
+			"border_top_width",
366
+			"border_right_width",
367
+			"border_bottom_width",
368
+			"border_left_width"
369
+		],
370
+		"border_style" => [
371
+			"border_top_style",
372
+			"border_right_style",
373
+			"border_bottom_style",
374
+			"border_left_style"
375
+		],
376
+		"border_color" => [
377
+			"border_top_color",
378
+			"border_right_color",
379
+			"border_bottom_color",
380
+			"border_left_color"
381
+		],
382
+		"border_radius" => [
383
+			"border_top_left_radius",
384
+			"border_top_right_radius",
385
+			"border_bottom_right_radius",
386
+			"border_bottom_left_radius"
387
+		],
388
+		"font" => [
389
+			"font_family",
390
+			"font_size",
391
+			// "font_stretch",
392
+			"font_style",
393
+			"font_variant",
394
+			"font_weight",
395
+			"line_height"
396
+		],
397
+		"inset" => [
398
+			"top",
399
+			"right",
400
+			"bottom",
401
+			"left"
402
+		],
403
+		"list_style" => [
404
+			"list_style_image",
405
+			"list_style_position",
406
+			"list_style_type"
407
+		],
408
+		"margin" => [
409
+			"margin_top",
410
+			"margin_right",
411
+			"margin_bottom",
412
+			"margin_left"
413
+		],
414
+		"padding" => [
415
+			"padding_top",
416
+			"padding_right",
417
+			"padding_bottom",
418
+			"padding_left"
419
+		],
420
+		"outline" => [
421
+			"outline_width",
422
+			"outline_style",
423
+			"outline_color"
424
+		]
425
+	];
426
+
427
+	/**
428
+	 * Maps legacy property names to actual property names.
429
+	 *
430
+	 * @var array
431
+	 */
432
+	protected static $_props_alias = [
433
+		"word_wrap"                           => "overflow_wrap",
434
+		"_dompdf_background_image_resolution" => "background_image_resolution",
435
+		"_dompdf_image_resolution"            => "image_resolution",
436
+		"_webkit_transform"                   => "transform",
437
+		"_webkit_transform_origin"            => "transform_origin"
438
+	];
439
+
440
+	/**
441
+	 * Default style values.
442
+	 *
443
+	 * @link https://www.w3.org/TR/CSS21/propidx.html
444
+	 *
445
+	 * @var array
446
+	 */
447
+	protected static $_defaults = null;
448
+
449
+	/**
450
+	 * List of inherited properties
451
+	 *
452
+	 * @link https://www.w3.org/TR/CSS21/propidx.html
453
+	 *
454
+	 * @var array
455
+	 */
456
+	protected static $_inherited = null;
457
+
458
+	/**
459
+	 * Caches method_exists result
460
+	 *
461
+	 * @var array<bool>
462
+	 */
463
+	protected static $_methods_cache = [];
464
+
465
+	/**
466
+	 * The stylesheet this style belongs to
467
+	 *
468
+	 * @var Stylesheet
469
+	 */
470
+	protected $_stylesheet;
471
+
472
+	/**
473
+	 * Media queries attached to the style
474
+	 *
475
+	 * @var array
476
+	 */
477
+	protected $_media_queries;
478
+
479
+	/**
480
+	 * Properties set by an `!important` declaration.
481
+	 *
482
+	 * @var array
483
+	 */
484
+	protected $_important_props = [];
485
+
486
+	/**
487
+	 * Specified (or declared) values of the CSS properties.
488
+	 *
489
+	 * https://www.w3.org/TR/css-cascade-3/#value-stages
490
+	 *
491
+	 * @var array
492
+	 */
493
+	protected $_props = [];
494
+
495
+	/**
496
+	 * Computed values of the CSS properties.
497
+	 *
498
+	 * @var array
499
+	 */
500
+	protected $_props_computed = [];
501
+
502
+	/**
503
+	 * Used values of the CSS properties.
504
+	 *
505
+	 * @var array
506
+	 */
507
+	protected $_props_used = [];
508
+
509
+	/**
510
+	 * Marks properties with non-final used values that should be cleared on
511
+	 * style reset.
512
+	 *
513
+	 * @var array
514
+	 */
515
+	protected $non_final_used = [];
516
+
517
+	protected static $_dependency_map = [
518
+		"border_top_style" => [
519
+			"border_top_width"
520
+		],
521
+		"border_bottom_style" => [
522
+			"border_bottom_width"
523
+		],
524
+		"border_left_style" => [
525
+			"border_left_width"
526
+		],
527
+		"border_right_style" => [
528
+			"border_right_width"
529
+		],
530
+		"direction" => [
531
+			"text_align"
532
+		],
533
+		"font_size" => [
534
+			"background_position",
535
+			"background_size",
536
+			"border_top_width",
537
+			"border_right_width",
538
+			"border_bottom_width",
539
+			"border_left_width",
540
+			"border_top_left_radius",
541
+			"border_top_right_radius",
542
+			"border_bottom_right_radius",
543
+			"border_bottom_left_radius",
544
+			"letter_spacing",
545
+			"line_height",
546
+			"margin_top",
547
+			"margin_right",
548
+			"margin_bottom",
549
+			"margin_left",
550
+			"outline_width",
551
+			"outline_offset",
552
+			"padding_top",
553
+			"padding_right",
554
+			"padding_bottom",
555
+			"padding_left",
556
+			"word_spacing",
557
+			"width",
558
+			"height",
559
+			"min-width",
560
+			"min-height",
561
+			"max-width",
562
+			"max-height"
563
+		],
564
+		"float" => [
565
+			"display"
566
+		],
567
+		"position" => [
568
+			"display"
569
+		],
570
+		"outline_style" => [
571
+			"outline_width"
572
+		]
573
+	];
574
+
575
+	/**
576
+	 * Lookup table for dependent properties. Initially computed from the
577
+	 * dependency map.
578
+	 *
579
+	 * @var array
580
+	 */
581
+	protected static $_dependent_props = [];
582
+
583
+	/**
584
+	 * Style of the parent element in document tree.
585
+	 *
586
+	 * @var Style
587
+	 */
588
+	protected $parent_style;
589
+
590
+	/**
591
+	 * @var Frame|null
592
+	 */
593
+	protected $_frame;
594
+
595
+	/**
596
+	 * The origin of the style
597
+	 *
598
+	 * @var int
599
+	 */
600
+	protected $_origin = Stylesheet::ORIG_AUTHOR;
601
+
602
+	/**
603
+	 * The computed bottom spacing
604
+	 *
605
+	 * @var float|string|null
606
+	 */
607
+	private $_computed_bottom_spacing = null;
608
+
609
+	/**
610
+	 * @var bool|null
611
+	 */
612
+	private $has_border_radius_cache = null;
613
+
614
+	/**
615
+	 * @var array|null
616
+	 */
617
+	private $resolved_border_radius = null;
618
+
619
+	/**
620
+	 * @var FontMetrics
621
+	 */
622
+	private $fontMetrics;
623
+
624
+	/**
625
+	 * @param Stylesheet $stylesheet The stylesheet the style is associated with.
626
+	 * @param int        $origin
627
+	 */
628
+	public function __construct(Stylesheet $stylesheet, int $origin = Stylesheet::ORIG_AUTHOR)
629
+	{
630
+		$this->fontMetrics = $stylesheet->getFontMetrics();
631
+
632
+		$this->_stylesheet = $stylesheet;
633
+		$this->_media_queries = [];
634
+		$this->_origin = $origin;
635
+		$this->parent_style = null;
636
+
637
+		if (!isset(self::$_defaults)) {
638
+
639
+			// Shorthand
640
+			$d =& self::$_defaults;
641
+
642
+			// All CSS 2.1 properties, and their default values
643
+			// Some properties are specified with their computed value for
644
+			// efficiency; this only works if the computed value is not
645
+			// dependent on another property
646
+			$d["azimuth"] = "center";
647
+			$d["background_attachment"] = "scroll";
648
+			$d["background_color"] = "transparent";
649
+			$d["background_image"] = "none";
650
+			$d["background_image_resolution"] = "normal";
651
+			$d["background_position"] = ["0%", "0%"];
652
+			$d["background_repeat"] = "repeat";
653
+			$d["background"] = "";
654
+			$d["border_collapse"] = "separate";
655
+			$d["border_color"] = "";
656
+			$d["border_spacing"] = [0.0, 0.0];
657
+			$d["border_style"] = "";
658
+			$d["border_top"] = "";
659
+			$d["border_right"] = "";
660
+			$d["border_bottom"] = "";
661
+			$d["border_left"] = "";
662
+			$d["border_top_color"] = "currentcolor";
663
+			$d["border_right_color"] = "currentcolor";
664
+			$d["border_bottom_color"] = "currentcolor";
665
+			$d["border_left_color"] = "currentcolor";
666
+			$d["border_top_style"] = "none";
667
+			$d["border_right_style"] = "none";
668
+			$d["border_bottom_style"] = "none";
669
+			$d["border_left_style"] = "none";
670
+			$d["border_top_width"] = "medium";
671
+			$d["border_right_width"] = "medium";
672
+			$d["border_bottom_width"] = "medium";
673
+			$d["border_left_width"] = "medium";
674
+			$d["border_width"] = "";
675
+			$d["border_bottom_left_radius"] = 0.0;
676
+			$d["border_bottom_right_radius"] = 0.0;
677
+			$d["border_top_left_radius"] = 0.0;
678
+			$d["border_top_right_radius"] = 0.0;
679
+			$d["border_radius"] = "";
680
+			$d["border"] = "";
681
+			$d["bottom"] = "auto";
682
+			$d["caption_side"] = "top";
683
+			$d["clear"] = "none";
684
+			$d["clip"] = "auto";
685
+			$d["color"] = "#000000";
686
+			$d["content"] = "normal";
687
+			$d["counter_increment"] = "none";
688
+			$d["counter_reset"] = "none";
689
+			$d["cue_after"] = "none";
690
+			$d["cue_before"] = "none";
691
+			$d["cue"] = "";
692
+			$d["cursor"] = "auto";
693
+			$d["direction"] = "ltr";
694
+			$d["display"] = "inline";
695
+			$d["elevation"] = "level";
696
+			$d["empty_cells"] = "show";
697
+			$d["float"] = "none";
698
+			$d["font_family"] = $stylesheet->get_dompdf()->getOptions()->getDefaultFont();
699
+			$d["font_size"] = "medium";
700
+			$d["font_style"] = "normal";
701
+			$d["font_variant"] = "normal";
702
+			$d["font_weight"] = "normal";
703
+			$d["font"] = "";
704
+			$d["height"] = "auto";
705
+			$d["image_resolution"] = "normal";
706
+			$d["inset"] = "";
707
+			$d["left"] = "auto";
708
+			$d["letter_spacing"] = "normal";
709
+			$d["line_height"] = "normal";
710
+			$d["list_style_image"] = "none";
711
+			$d["list_style_position"] = "outside";
712
+			$d["list_style_type"] = "disc";
713
+			$d["list_style"] = "";
714
+			$d["margin_right"] = 0.0;
715
+			$d["margin_left"] = 0.0;
716
+			$d["margin_top"] = 0.0;
717
+			$d["margin_bottom"] = 0.0;
718
+			$d["margin"] = "";
719
+			$d["max_height"] = "none";
720
+			$d["max_width"] = "none";
721
+			$d["min_height"] = "auto";
722
+			$d["min_width"] = "auto";
723
+			$d["orphans"] = 2;
724
+			$d["outline_color"] = "currentcolor"; // "invert" special color is not supported
725
+			$d["outline_style"] = "none";
726
+			$d["outline_width"] = "medium";
727
+			$d["outline_offset"] = 0.0;
728
+			$d["outline"] = "";
729
+			$d["overflow"] = "visible";
730
+			$d["overflow_wrap"] = "normal";
731
+			$d["padding_top"] = 0.0;
732
+			$d["padding_right"] = 0.0;
733
+			$d["padding_bottom"] = 0.0;
734
+			$d["padding_left"] = 0.0;
735
+			$d["padding"] = "";
736
+			$d["page_break_after"] = "auto";
737
+			$d["page_break_before"] = "auto";
738
+			$d["page_break_inside"] = "auto";
739
+			$d["pause_after"] = "0";
740
+			$d["pause_before"] = "0";
741
+			$d["pause"] = "";
742
+			$d["pitch_range"] = "50";
743
+			$d["pitch"] = "medium";
744
+			$d["play_during"] = "auto";
745
+			$d["position"] = "static";
746
+			$d["quotes"] = "auto";
747
+			$d["richness"] = "50";
748
+			$d["right"] = "auto";
749
+			$d["size"] = "auto"; // @page
750
+			$d["speak_header"] = "once";
751
+			$d["speak_numeral"] = "continuous";
752
+			$d["speak_punctuation"] = "none";
753
+			$d["speak"] = "normal";
754
+			$d["speech_rate"] = "medium";
755
+			$d["stress"] = "50";
756
+			$d["table_layout"] = "auto";
757
+			$d["text_align"] = "";
758
+			$d["text_decoration"] = "none";
759
+			$d["text_indent"] = 0.0;
760
+			$d["text_transform"] = "none";
761
+			$d["top"] = "auto";
762
+			$d["unicode_bidi"] = "normal";
763
+			$d["vertical_align"] = "baseline";
764
+			$d["visibility"] = "visible";
765
+			$d["voice_family"] = "";
766
+			$d["volume"] = "medium";
767
+			$d["white_space"] = "normal";
768
+			$d["widows"] = 2;
769
+			$d["width"] = "auto";
770
+			$d["word_break"] = "normal";
771
+			$d["word_spacing"] = "normal";
772
+			$d["z_index"] = "auto";
773
+
774
+			// CSS3
775
+			$d["opacity"] = 1.0;
776
+			$d["background_size"] = ["auto", "auto"];
777
+			$d["transform"] = "none";
778
+			$d["transform_origin"] = "50% 50%";
779
+
780
+			// for @font-face
781
+			$d["src"] = "";
782
+			$d["unicode_range"] = "";
783
+
784
+			// vendor-prefixed properties
785
+			$d["_dompdf_keep"] = "";
786
+
787
+			// Properties that inherit by default
788
+			self::$_inherited = [
789
+				"azimuth",
790
+				"background_image_resolution",
791
+				"border_collapse",
792
+				"border_spacing",
793
+				"caption_side",
794
+				"color",
795
+				"cursor",
796
+				"direction",
797
+				"elevation",
798
+				"empty_cells",
799
+				"font_family",
800
+				"font_size",
801
+				"font_style",
802
+				"font_variant",
803
+				"font_weight",
804
+				"font",
805
+				"image_resolution",
806
+				"letter_spacing",
807
+				"line_height",
808
+				"list_style_image",
809
+				"list_style_position",
810
+				"list_style_type",
811
+				"list_style",
812
+				"orphans",
813
+				"overflow_wrap",
814
+				"pitch_range",
815
+				"pitch",
816
+				"quotes",
817
+				"richness",
818
+				"speak_header",
819
+				"speak_numeral",
820
+				"speak_punctuation",
821
+				"speak",
822
+				"speech_rate",
823
+				"stress",
824
+				"text_align",
825
+				"text_indent",
826
+				"text_transform",
827
+				"visibility",
828
+				"voice_family",
829
+				"volume",
830
+				"white_space",
831
+				"widows",
832
+				"word_break",
833
+				"word_spacing",
834
+			];
835
+
836
+			// Compute dependent props from dependency map
837
+			foreach (self::$_dependency_map as $props) {
838
+				foreach ($props as $prop) {
839
+					self::$_dependent_props[$prop] = true;
840
+				}
841
+			}
842
+
843
+			// Compute valid display-type lookup table
844
+			self::$valid_display_types = [
845
+				"none"                => true,
846
+				"-dompdf-br"          => true,
847
+				"-dompdf-image"       => true,
848
+				"-dompdf-list-bullet" => true,
849
+				"-dompdf-page"        => true
850
+			];
851
+			foreach (self::BLOCK_LEVEL_TYPES as $val) {
852
+				self::$valid_display_types[$val] = true;
853
+			}
854
+			foreach (self::INLINE_LEVEL_TYPES as $val) {
855
+				self::$valid_display_types[$val] = true;
856
+			}
857
+			foreach (self::TABLE_INTERNAL_TYPES as $val) {
858
+				self::$valid_display_types[$val] = true;
859
+			}
860
+		}
861
+	}
862
+
863
+	/**
864
+	 * Clear all non-final used values.
865
+	 *
866
+	 * @return void
867
+	 */
868
+	public function reset(): void
869
+	{
870
+		foreach (array_keys($this->non_final_used) as $prop) {
871
+			unset($this->_props_used[$prop]);
872
+		}
873
+
874
+		$this->non_final_used = [];
875
+	}
876
+
877
+	/**
878
+	 * @param array $media_queries
879
+	 */
880
+	public function set_media_queries(array $media_queries): void
881
+	{
882
+		$this->_media_queries = $media_queries;
883
+	}
884
+
885
+	/**
886
+	 * @return array
887
+	 */
888
+	public function get_media_queries(): array
889
+	{
890
+		return $this->_media_queries;
891
+	}
892
+
893
+	/**
894
+	 * @param Frame $frame
895
+	 */
896
+	public function set_frame(Frame $frame): void
897
+	{
898
+		$this->_frame = $frame;
899
+	}
900
+
901
+	/**
902
+	 * @return Frame|null
903
+	 */
904
+	public function get_frame(): ?Frame
905
+	{
906
+		return $this->_frame;
907
+	}
908
+
909
+	/**
910
+	 * @param int $origin
911
+	 */
912
+	public function set_origin(int $origin): void
913
+	{
914
+		$this->_origin = $origin;
915
+	}
916
+
917
+	/**
918
+	 * @return int
919
+	 */
920
+	public function get_origin(): int
921
+	{
922
+		return $this->_origin;
923
+	}
924
+
925
+	/**
926
+	 * Returns the {@link Stylesheet} the style is associated with.
927
+	 *
928
+	 * @return Stylesheet
929
+	 */
930
+	public function get_stylesheet(): Stylesheet
931
+	{
932
+		return $this->_stylesheet;
933
+	}
934
+
935
+	public function is_absolute(): bool
936
+	{
937
+		$position = $this->__get("position");
938
+		return $position === "absolute" || $position === "fixed";
939
+	}
940
+
941
+	public function is_in_flow(): bool
942
+	{
943
+		$float = $this->__get("float");
944
+		return $float === "none" && !$this->is_absolute();
945
+	}
946
+
947
+	/**
948
+	 * Converts any CSS length value into an absolute length in points.
949
+	 *
950
+	 * length_in_pt() takes a single length (e.g. '1em') or an array of
951
+	 * lengths and returns an absolute length.  If an array is passed, then
952
+	 * the return value is the sum of all elements. If any of the lengths
953
+	 * provided are "auto" or "none" then that value is returned.
954
+	 *
955
+	 * If a reference size is not provided, the current font size is used.
956
+	 *
957
+	 * @param float|string|array $length   The numeric length (or string measurement) or array of lengths to resolve.
958
+	 * @param float|null         $ref_size An absolute reference size to resolve percentage lengths.
959
+	 *
960
+	 * @return float|string
961
+	 */
962
+	public function length_in_pt($length, ?float $ref_size = null)
963
+	{
964
+		$font_size = $this->__get("font_size");
965
+		$ref_size = $ref_size ?? $font_size;
966
+
967
+		if (!\is_array($length)) {
968
+			$length = [$length];
969
+		}
970
+
971
+		$ret = 0.0;
972
+
973
+		foreach ($length as $l) {
974
+			if ($l === "auto" || $l === "none") {
975
+				return $l;
976
+			}
977
+
978
+			// Assume numeric values are already in points
979
+			if (is_numeric($l)) {
980
+				$ret += (float) $l;
981
+				continue;
982
+			}
983
+
984
+			$val = $this->single_length_in_pt((string) $l, $ref_size, $font_size);
985
+			$ret += $val ?? 0;
986
+		}
987
+
988
+		return $ret;
989
+	}
990
+
991
+	/**
992
+	 * Convert a length declaration to pt.
993
+	 *
994
+	 * @param string     $l         The length declaration.
995
+	 * @param float      $ref_size  Reference size for percentage declarations.
996
+	 * @param float|null $font_size Font size for resolving font-size relative units.
997
+	 *
998
+	 * @return float|null The length in pt, or `null` for invalid declarations.
999
+	 */
1000
+	protected function single_length_in_pt(string $l, float $ref_size = 0, ?float $font_size = null): ?float
1001
+	{
1002
+		static $cache = [];
1003
+
1004
+		$font_size = $font_size ?? $this->__get("font_size");
1005
+
1006
+		$key = "$l/$ref_size/$font_size";
1007
+
1008
+		if (\array_key_exists($key, $cache)) {
1009
+			return $cache[$key];
1010
+		}
1011
+
1012
+		$number = self::CSS_NUMBER;
1013
+		$pattern = "/^($number)(.*)?$/";
1014
+
1015
+		if (!preg_match($pattern, $l, $matches)) {
1016
+			return null;
1017
+		}
1018
+
1019
+		$v = (float) $matches[1];
1020
+		$unit = mb_strtolower($matches[2]);
1021
+
1022
+		if ($unit === "") {
1023
+			// Legacy support for unitless values, not covered by spec. Might
1024
+			// want to restrict this to unitless `0` in the future
1025
+			$value = $v;
1026
+		}
1027
+
1028
+		elseif ($unit === "%") {
1029
+			$value = $v / 100 * $ref_size;
1030
+		}
1031
+
1032
+		elseif ($unit === "px") {
1033
+			$dpi = $this->_stylesheet->get_dompdf()->getOptions()->getDpi();
1034
+			$value = ($v * 72) / $dpi;
1035
+		}
1036
+
1037
+		elseif ($unit === "pt") {
1038
+			$value = $v;
1039
+		}
1040
+
1041
+		elseif ($unit === "rem") {
1042
+			$tree = $this->_stylesheet->get_dompdf()->getTree();
1043
+			$root_style = $tree !== null ? $tree->get_root()->get_style() : null;
1044
+			$root_font_size = $root_style === null || $root_style === $this
1045
+				? $font_size
1046
+				: $root_style->__get("font_size");
1047
+			$value = $v * $root_font_size;
1048
+
1049
+			// Skip caching if the root style is not available yet, as to avoid
1050
+			// incorrectly cached values if the root font size is different from
1051
+			// the default
1052
+			if ($root_style === null) {
1053
+				return $value;
1054
+			}
1055
+		}
1056
+
1057
+		elseif ($unit === "em") {
1058
+			$value = $v * $font_size;
1059
+		}
1060
+
1061
+		elseif ($unit === "cm") {
1062
+			$value = $v * 72 / 2.54;
1063
+		}
1064
+
1065
+		elseif ($unit === "mm") {
1066
+			$value = $v * 72 / 25.4;
1067
+		}
1068
+
1069
+		elseif ($unit === "ex") {
1070
+			// FIXME: em:ex ratio?
1071
+			$value = $v * $font_size / 2;
1072
+		}
1073
+
1074
+		elseif ($unit === "in") {
1075
+			$value = $v * 72;
1076
+		}
1077
+
1078
+		elseif ($unit === "pc") {
1079
+			$value = $v * 12;
1080
+		}
1081
+
1082
+		else {
1083
+			// Invalid or unsupported declaration
1084
+			$value = null;
1085
+		}
1086
+
1087
+		return $cache[$key] = $value;
1088
+	}
1089
+
1090
+	/**
1091
+	 * Resolve inherited property values using the provided parent style or the
1092
+	 * default values, in case no parent style exists.
1093
+	 *
1094
+	 * https://www.w3.org/TR/css-cascade-3/#inheriting
1095
+	 *
1096
+	 * @param Style|null $parent
1097
+	 */
1098
+	public function inherit(?Style $parent = null): void
1099
+	{
1100
+		$this->parent_style = $parent;
1101
+
1102
+		// Clear the computed font size, as it might depend on the parent
1103
+		// font size
1104
+		unset($this->_props_computed["font_size"]);
1105
+		unset($this->_props_used["font_size"]);
1106
+
1107
+		if ($parent) {
1108
+			foreach (self::$_inherited as $prop) {
1109
+				// For properties that inherit by default: When the cascade did
1110
+				// not result in a value, inherit the parent value. Inheritance
1111
+				// is handled via the specific sub-properties for shorthands
1112
+				if (isset($this->_props[$prop]) || isset(self::$_props_shorthand[$prop])) {
1113
+					continue;
1114
+				}
1115
+
1116
+				if (isset($parent->_props[$prop])) {
1117
+					$parent_val = $parent->computed($prop);
1118
+
1119
+					$this->_props[$prop] = $parent_val;
1120
+					$this->_props_computed[$prop] = $parent_val;
1121
+					$this->_props_used[$prop] = null;
1122
+				}
1123
+			}
1124
+		}
1125
+
1126
+		foreach ($this->_props as $prop => $val) {
1127
+			if ($val === "inherit") {
1128
+				if ($parent && isset($parent->_props[$prop])) {
1129
+					$parent_val = $parent->computed($prop);
1130
+
1131
+					$this->_props[$prop] = $parent_val;
1132
+					$this->_props_computed[$prop] = $parent_val;
1133
+					$this->_props_used[$prop] = null;
1134
+				} else {
1135
+					// Parent prop not set, use default
1136
+					$this->_props[$prop] = self::$_defaults[$prop];
1137
+					unset($this->_props_computed[$prop]);
1138
+					unset($this->_props_used[$prop]);
1139
+				}
1140
+			}
1141
+		}
1142
+	}
1143
+
1144
+	/**
1145
+	 * Override properties in this style with those in $style
1146
+	 *
1147
+	 * @param Style $style
1148
+	 */
1149
+	public function merge(Style $style): void
1150
+	{
1151
+		foreach ($style->_props as $prop => $val) {
1152
+			$important = isset($style->_important_props[$prop]);
1153
+
1154
+			// `!important` declarations take precedence over normal ones
1155
+			if (!$important && isset($this->_important_props[$prop])) {
1156
+				continue;
1157
+			}
1158
+
1159
+			if ($important) {
1160
+				$this->_important_props[$prop] = true;
1161
+			}
1162
+
1163
+			$this->_props[$prop] = $val;
1164
+
1165
+			// Copy an existing computed value only for non-dependent
1166
+			// properties; otherwise it may be invalid for the current style
1167
+			if (!isset(self::$_dependent_props[$prop])
1168
+				&& \array_key_exists($prop, $style->_props_computed)
1169
+			) {
1170
+				$this->_props_computed[$prop] = $style->_props_computed[$prop];
1171
+				$this->_props_used[$prop] = null;
1172
+			} else {
1173
+				unset($this->_props_computed[$prop]);
1174
+				unset($this->_props_used[$prop]);
1175
+			}
1176
+		}
1177
+	}
1178
+
1179
+	/**
1180
+	 * Clear information about important declarations after the style has been
1181
+	 * finalized during stylesheet loading.
1182
+	 */
1183
+	public function clear_important(): void
1184
+	{
1185
+		$this->_important_props = [];
1186
+	}
1187
+
1188
+	/**
1189
+	 * Clear border-radius and bottom-spacing cache as necessary when a given
1190
+	 * property is set.
1191
+	 *
1192
+	 * @param string $prop The property that is set.
1193
+	 */
1194
+	protected function clear_cache(string $prop): void
1195
+	{
1196
+		// Clear border-radius cache on setting any border-radius
1197
+		// property
1198
+		if ($prop === "border_top_left_radius"
1199
+			|| $prop === "border_top_right_radius"
1200
+			|| $prop === "border_bottom_left_radius"
1201
+			|| $prop === "border_bottom_right_radius"
1202
+		) {
1203
+			$this->has_border_radius_cache = null;
1204
+			$this->resolved_border_radius = null;
1205
+		}
1206
+
1207
+		// Clear bottom-spacing cache if necessary. Border style can
1208
+		// disable/enable border calculations
1209
+		if ($prop === "margin_bottom"
1210
+			|| $prop === "padding_bottom"
1211
+			|| $prop === "border_bottom_width"
1212
+			|| $prop === "border_bottom_style"
1213
+		) {
1214
+			$this->_computed_bottom_spacing = null;
1215
+		}
1216
+	}
1217
+
1218
+	/**
1219
+	 * Set a style property from a value declaration.
1220
+	 *
1221
+	 * Setting `$clear_dependencies` to `false` is useful for saving a bit of
1222
+	 * unnecessary work while loading stylesheets.
1223
+	 *
1224
+	 * @param string $prop               The property to set.
1225
+	 * @param mixed  $val                The value declaration or computed value.
1226
+	 * @param bool   $important          Whether the declaration is important.
1227
+	 * @param bool   $clear_dependencies Whether to clear computed values of dependent properties.
1228
+	 */
1229
+	public function set_prop(string $prop, $val, bool $important = false, bool $clear_dependencies = true): void
1230
+	{
1231
+		$prop = str_replace("-", "_", $prop);
1232
+
1233
+		// Legacy property aliases
1234
+		if (isset(self::$_props_alias[$prop])) {
1235
+			$prop = self::$_props_alias[$prop];
1236
+		}
1237
+
1238
+		if (!isset(self::$_defaults[$prop])) {
1239
+			global $_dompdf_warnings;
1240
+			$_dompdf_warnings[] = "'$prop' is not a recognized CSS property.";
1241
+			return;
1242
+		}
1243
+
1244
+		if ($prop !== "content" && \is_string($val) && mb_strpos($val, "url") === false && mb_strlen($val) > 1) {
1245
+			$val = mb_strtolower(trim(str_replace(["\n", "\t"], [" "], $val)));
1246
+		}
1247
+
1248
+		if (isset(self::$_props_shorthand[$prop])) {
1249
+			// Shorthand properties directly set their respective sub-properties
1250
+			// https://www.w3.org/TR/css-cascade-3/#shorthand
1251
+			if ($val === "initial" || $val === "inherit" || $val === "unset") {
1252
+				foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1253
+					$this->set_prop($sub_prop, $val, $important, $clear_dependencies);
1254
+				}
1255
+			} else {
1256
+				$method = "_set_$prop";
1257
+
1258
+				if (!isset(self::$_methods_cache[$method])) {
1259
+					self::$_methods_cache[$method] = method_exists($this, $method);
1260
+				}
1261
+
1262
+				if (self::$_methods_cache[$method]) {
1263
+					$values = $this->$method($val);
1264
+
1265
+					if ($values === []) {
1266
+						return;
1267
+					}
1268
+
1269
+					// Each missing sub-property is assigned its initial value
1270
+					// https://www.w3.org/TR/css-cascade-3/#shorthand
1271
+					foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1272
+						$sub_val = $values[$sub_prop] ?? self::$_defaults[$sub_prop];
1273
+						$this->set_prop($sub_prop, $sub_val, $important, $clear_dependencies);
1274
+					}
1275
+				}
1276
+			}
1277
+		} else {
1278
+			// Legacy support for `word-break: break-word`
1279
+			// https://www.w3.org/TR/css-text-3/#valdef-word-break-break-word
1280
+			if ($prop === "word_break" && $val === "break-word") {
1281
+				$val = "normal";
1282
+				$this->set_prop("overflow_wrap", "anywhere", $important, $clear_dependencies);
1283
+			}
1284
+
1285
+			// `!important` declarations take precedence over normal ones
1286
+			if (!$important && isset($this->_important_props[$prop])) {
1287
+				return;
1288
+			}
1289
+
1290
+			if ($important) {
1291
+				$this->_important_props[$prop] = true;
1292
+			}
1293
+
1294
+			// https://www.w3.org/TR/css-cascade-3/#inherit-initial
1295
+			if ($val === "unset") {
1296
+				$val = \in_array($prop, self::$_inherited, true)
1297
+					? "inherit"
1298
+					: "initial";
1299
+			}
1300
+
1301
+			// https://www.w3.org/TR/css-cascade-3/#valdef-all-initial
1302
+			if ($val === "initial") {
1303
+				$val = self::$_defaults[$prop];
1304
+			}
1305
+
1306
+			$computed = $this->compute_prop($prop, $val);
1307
+
1308
+			// Skip invalid declarations
1309
+			if ($computed === null) {
1310
+				return;
1311
+			}
1312
+
1313
+			$this->_props[$prop] = $val;
1314
+			$this->_props_computed[$prop] = $computed;
1315
+			$this->_props_used[$prop] = null;
1316
+
1317
+			if ($clear_dependencies) {
1318
+				// Clear the computed values of any dependent properties, so
1319
+				// they can be re-computed
1320
+				if (isset(self::$_dependency_map[$prop])) {
1321
+					foreach (self::$_dependency_map[$prop] as $dependent) {
1322
+						unset($this->_props_computed[$dependent]);
1323
+						unset($this->_props_used[$dependent]);
1324
+					}
1325
+				}
1326
+
1327
+				$this->clear_cache($prop);
1328
+			}
1329
+		}
1330
+	}
1331
+
1332
+	/**
1333
+	 * Get the specified value of a style property.
1334
+	 *
1335
+	 * @param string $prop
1336
+	 *
1337
+	 * @return mixed
1338
+	 * @throws Exception
1339
+	 */
1340
+	public function get_specified(string $prop)
1341
+	{
1342
+		// Legacy property aliases
1343
+		if (isset(self::$_props_alias[$prop])) {
1344
+			$prop = self::$_props_alias[$prop];
1345
+		}
1346
+
1347
+		if (!isset(self::$_defaults[$prop])) {
1348
+			throw new Exception("'$prop' is not a recognized CSS property.");
1349
+		}
1350
+
1351
+		return $this->_props[$prop] ?? self::$_defaults[$prop];
1352
+	}
1353
+
1354
+	/**
1355
+	 * Set a style property to its final value.
1356
+	 *
1357
+	 * This sets the specified and used value of the style property to the given
1358
+	 * value, meaning the value is not parsed and thus should have a type
1359
+	 * compatible with the property.
1360
+	 *
1361
+	 * If a shorthand property is specified, all of its sub-properties are set
1362
+	 * to the given value.
1363
+	 *
1364
+	 * @param string $prop The property to set.
1365
+	 * @param mixed  $val  The final value of the property.
1366
+	 *
1367
+	 * @throws Exception
1368
+	 */
1369
+	public function __set(string $prop, $val)
1370
+	{
1371
+		// Legacy property aliases
1372
+		if (isset(self::$_props_alias[$prop])) {
1373
+			$prop = self::$_props_alias[$prop];
1374
+		}
1375
+
1376
+		if (!isset(self::$_defaults[$prop])) {
1377
+			throw new Exception("'$prop' is not a recognized CSS property.");
1378
+		}
1379
+
1380
+		if (isset(self::$_props_shorthand[$prop])) {
1381
+			foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1382
+				$this->__set($sub_prop, $val);
1383
+			}
1384
+		} else {
1385
+			$this->_props[$prop] = $val;
1386
+			$this->_props_computed[$prop] = $val;
1387
+			$this->_props_used[$prop] = $val;
1388
+
1389
+			$this->clear_cache($prop);
1390
+		}
1391
+	}
1392
+
1393
+	/**
1394
+	 * Set the used value of a style property.
1395
+	 *
1396
+	 * Used values are cleared on style reset.
1397
+	 *
1398
+	 * If a shorthand property is specified, all of its sub-properties are set
1399
+	 * to the given value.
1400
+	 *
1401
+	 * @param string $prop The property to set.
1402
+	 * @param mixed  $val  The used value of the property.
1403
+	 *
1404
+	 * @throws Exception
1405
+	 */
1406
+	public function set_used(string $prop, $val): void
1407
+	{
1408
+		// Legacy property aliases
1409
+		if (isset(self::$_props_alias[$prop])) {
1410
+			$prop = self::$_props_alias[$prop];
1411
+		}
1412
+
1413
+		if (!isset(self::$_defaults[$prop])) {
1414
+			throw new Exception("'$prop' is not a recognized CSS property.");
1415
+		}
1416
+
1417
+		if (isset(self::$_props_shorthand[$prop])) {
1418
+			foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1419
+				$this->set_used($sub_prop, $val);
1420
+			}
1421
+		} else {
1422
+			$this->_props_used[$prop] = $val;
1423
+			$this->non_final_used[$prop] = true;
1424
+		}
1425
+	}
1426
+
1427
+	/**
1428
+	 * Get the used or computed value of a style property, depending on whether
1429
+	 * the used value has been determined yet.
1430
+	 *
1431
+	 * @param string $prop
1432
+	 *
1433
+	 * @return mixed
1434
+	 * @throws Exception
1435
+	 */
1436
+	public function __get(string $prop)
1437
+	{
1438
+		// Legacy property aliases
1439
+		if (isset(self::$_props_alias[$prop])) {
1440
+			$prop = self::$_props_alias[$prop];
1441
+		}
1442
+
1443
+		if (!isset(self::$_defaults[$prop])) {
1444
+			throw new Exception("'$prop' is not a recognized CSS property.");
1445
+		}
1446
+
1447
+		if (isset($this->_props_used[$prop])) {
1448
+			return $this->_props_used[$prop];
1449
+		}
1450
+
1451
+		$method = "_get_$prop";
1452
+
1453
+		if (!isset(self::$_methods_cache[$method])) {
1454
+			self::$_methods_cache[$method] = method_exists($this, $method);
1455
+		}
1456
+
1457
+		if (isset(self::$_props_shorthand[$prop])) {
1458
+			// Don't cache shorthand values, always use getter. If no dedicated
1459
+			// getter exists, use a simple fallback getter concatenating all
1460
+			// sub-property values
1461
+			if (self::$_methods_cache[$method]) {
1462
+				return $this->$method();
1463
+			} else {
1464
+				return implode(" ", array_map(function ($sub_prop) {
1465
+					$val = $this->__get($sub_prop);
1466
+					return \is_array($val) ? implode(" ", $val) : $val;
1467
+				}, self::$_props_shorthand[$prop]));
1468
+			}
1469
+		} else {
1470
+			$computed = $this->computed($prop);
1471
+			$used = self::$_methods_cache[$method]
1472
+				? $this->$method($computed)
1473
+				: $computed;
1474
+
1475
+			$this->_props_used[$prop] = $used;
1476
+			return $used;
1477
+		}
1478
+	}
1479
+
1480
+	/**
1481
+	 * @param string $prop The property to compute.
1482
+	 * @param mixed  $val  The value to compute. Non-string values are treated as already computed.
1483
+	 *
1484
+	 * @return mixed The computed value.
1485
+	 */
1486
+	protected function compute_prop(string $prop, $val)
1487
+	{
1488
+		// During style merge, the parent style is not available yet, so
1489
+		// temporarily use the initial value for `inherit` properties. The
1490
+		// keyword is properly resolved during inheritance
1491
+		if ($val === "inherit") {
1492
+			$val = self::$_defaults[$prop];
1493
+		}
1494
+
1495
+		// Check for values which are already computed
1496
+		if (!\is_string($val)) {
1497
+			return $val;
1498
+		}
1499
+
1500
+		$method = "_compute_$prop";
1501
+
1502
+		if (!isset(self::$_methods_cache[$method])) {
1503
+			self::$_methods_cache[$method] = method_exists($this, $method);
1504
+		}
1505
+
1506
+		if (self::$_methods_cache[$method]) {
1507
+			return $this->$method($val);
1508
+		} elseif ($val !== "") {
1509
+			return $val;
1510
+		} else {
1511
+			return null;
1512
+		}
1513
+	}
1514
+
1515
+	/**
1516
+	 * Get the computed value for the given property.
1517
+	 *
1518
+	 * @param string $prop The property to get the computed value of.
1519
+	 *
1520
+	 * @return mixed The computed value.
1521
+	 */
1522
+	protected function computed(string $prop)
1523
+	{
1524
+		if (!\array_key_exists($prop, $this->_props_computed)) {
1525
+			$val = $this->_props[$prop] ?? self::$_defaults[$prop];
1526
+			$computed = $this->compute_prop($prop, $val);
1527
+
1528
+			$this->_props_computed[$prop] = $computed;
1529
+		}
1530
+
1531
+		return $this->_props_computed[$prop];
1532
+	}
1533
+
1534
+	/**
1535
+	 * @param float $cbw The width of the containing block.
1536
+	 * @return float|string|null
1537
+	 */
1538
+	public function computed_bottom_spacing(float $cbw)
1539
+	{
1540
+		// Caching the bottom spacing independently of the given width is a bit
1541
+		// iffy, but should be okay, as the containing block should only
1542
+		// potentially change after a page break, and the style is reset in that
1543
+		// case
1544
+		if ($this->_computed_bottom_spacing !== null) {
1545
+			return $this->_computed_bottom_spacing;
1546
+		}
1547
+		return $this->_computed_bottom_spacing = $this->length_in_pt(
1548
+			[
1549
+				$this->margin_bottom,
1550
+				$this->padding_bottom,
1551
+				$this->border_bottom_width
1552
+			],
1553
+			$cbw
1554
+		);
1555
+	}
1556
+
1557
+	/**
1558
+	 * Returns an `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")`
1559
+	 * based on the provided CSS color value.
1560
+	 *
1561
+	 * @param string|null $color
1562
+	 * @return array|string|null
1563
+	 */
1564
+	public function munge_color($color)
1565
+	{
1566
+		return Color::parse($color);
1567
+	}
1568
+
1569
+	/**
1570
+	 * @return string
1571
+	 */
1572
+	public function get_font_family_raw(): string
1573
+	{
1574
+		return trim($this->_props["font_family"], " \t\n\r\x0B\"'");
1575
+	}
1576
+
1577
+	/**
1578
+	 * Getter for the `font-family` CSS property.
1579
+	 *
1580
+	 * Uses the {@link FontMetrics} class to resolve the font family into an
1581
+	 * actual font file.
1582
+	 *
1583
+	 * @param string $computed
1584
+	 * @return string
1585
+	 * @throws Exception
1586
+	 *
1587
+	 * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-family
1588
+	 */
1589
+	protected function _get_font_family($computed): string
1590
+	{
1591
+		//TODO: we should be using the calculated prop rather than perform the entire family parsing operation again
1592
+
1593
+		$fontMetrics = $this->getFontMetrics();
1594
+		$DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss();
1595
+
1596
+		// Select the appropriate font.  First determine the subtype, then check
1597
+		// the specified font-families for a candidate.
1598
+
1599
+		// Resolve font-weight
1600
+		$weight = $this->__get("font_weight");
1601
+		if ($weight === 'bold') {
1602
+			$weight = 700;
1603
+		} elseif (preg_match('/^[0-9]+$/', $weight, $match)) {
1604
+			$weight = (int)$match[0];
1605
+		} else {
1606
+			$weight = 400;
1607
+		}
1608
+
1609
+		// Resolve font-style
1610
+		$font_style = $this->__get("font_style");
1611
+		$subtype = $fontMetrics->getType($weight . ' ' . $font_style);
1612
+
1613
+		$families = preg_split("/\s*,\s*/", $computed);
1614
+
1615
+		$font = null;
1616
+		foreach ($families as $family) {
1617
+			//remove leading and trailing string delimiters, e.g. on font names with spaces;
1618
+			//remove leading and trailing whitespace
1619
+			$family = trim($family, " \t\n\r\x0B\"'");
1620
+			if ($DEBUGCSS) {
1621
+				print '(' . $family . ')';
1622
+			}
1623
+			$font = $fontMetrics->getFont($family, $subtype);
1624
+
1625
+			if ($font) {
1626
+				if ($DEBUGCSS) {
1627
+					print "<pre>[get_font_family:";
1628
+					print '(' . $computed . '.' . $font_style . '.' . $weight . '.' . $subtype . ')';
1629
+					print '(' . $font . ")get_font_family]\n</pre>";
1630
+				}
1631
+				return $font;
1632
+			}
1633
+		}
1634
+
1635
+		$family = null;
1636
+		if ($DEBUGCSS) {
1637
+			print '(default)';
1638
+		}
1639
+		$font = $fontMetrics->getFont($family, $subtype);
1640
+
1641
+		if ($font) {
1642
+			if ($DEBUGCSS) {
1643
+				print '(' . $font . ")get_font_family]\n</pre>";
1644
+			}
1645
+			return $font;
1646
+		}
1647
+
1648
+		throw new Exception("Unable to find a suitable font replacement for: '" . $computed . "'");
1649
+	}
1650
+
1651
+	/**
1652
+	 * @param float|string $computed
1653
+	 * @return float
1654
+	 *
1655
+	 * @link https://www.w3.org/TR/css-text-4/#word-spacing-property
1656
+	 */
1657
+	protected function _get_word_spacing($computed)
1658
+	{
1659
+		if (\is_float($computed)) {
1660
+			return $computed;
1661
+		}
1662
+
1663
+		// Resolve percentage values
1664
+		$font_size = $this->__get("font_size");
1665
+		return $this->single_length_in_pt($computed, $font_size);
1666
+	}
1667
+
1668
+	/**
1669
+	 * @param float|string $computed
1670
+	 * @return float
1671
+	 *
1672
+	 * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property
1673
+	 */
1674
+	protected function _get_letter_spacing($computed)
1675
+	{
1676
+		if (\is_float($computed)) {
1677
+			return $computed;
1678
+		}
1679
+
1680
+		// Resolve percentage values
1681
+		$font_size = $this->__get("font_size");
1682
+		return $this->single_length_in_pt($computed, $font_size);
1683
+	}
1684
+
1685
+	/**
1686
+	 * @param float|string $computed
1687
+	 * @return float
1688
+	 *
1689
+	 * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
1690
+	 */
1691
+	protected function _get_line_height($computed)
1692
+	{
1693
+		// Lengths have been computed to float, number values to string
1694
+		if (\is_float($computed)) {
1695
+			return $computed;
1696
+		}
1697
+
1698
+		$font_size = $this->__get("font_size");
1699
+		$factor = $computed === "normal"
1700
+			? self::$default_line_height
1701
+			: (float) $computed;
1702
+
1703
+		return $factor * $font_size;
1704
+	}
1705
+
1706
+	/**
1707
+	 * @param string $computed
1708
+	 * @param bool   $current_is_parent
1709
+	 *
1710
+	 * @return array|string
1711
+	 */
1712
+	protected function get_color_value($computed, bool $current_is_parent = false)
1713
+	{
1714
+		if ($computed === "currentcolor") {
1715
+			// https://www.w3.org/TR/css-color-4/#resolving-other-colors
1716
+			if ($current_is_parent) {
1717
+				// Use the `color` value from the parent for the `color`
1718
+				// property itself
1719
+				return isset($this->parent_style)
1720
+					? $this->parent_style->__get("color")
1721
+					: $this->munge_color(self::$_defaults["color"]);
1722
+			}
1723
+
1724
+			return $this->__get("color");
1725
+		}
1726
+
1727
+		return $this->munge_color($computed) ?? "transparent";
1728
+	}
1729
+
1730
+	/**
1731
+	 * Returns the color as an array
1732
+	 *
1733
+	 * The array has the following format:
1734
+	 * `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")`
1735
+	 *
1736
+	 * @param string $computed
1737
+	 * @return array|string
1738
+	 *
1739
+	 * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color
1740
+	 */
1741
+	protected function _get_color($computed)
1742
+	{
1743
+		return $this->get_color_value($computed, true);
1744
+	}
1745
+
1746
+	/**
1747
+	 * Returns the background color as an array
1748
+	 *
1749
+	 * See {@link Style::_get_color()} for format of the color array.
1750
+	 *
1751
+	 * @param string $computed
1752
+	 * @return array|string
1753
+	 *
1754
+	 * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color
1755
+	 */
1756
+	protected function _get_background_color($computed)
1757
+	{
1758
+		return $this->get_color_value($computed);
1759
+	}
1760
+
1761
+	/**
1762
+	 * Returns the background image URI, or "none"
1763
+	 *
1764
+	 * @param string $computed
1765
+	 * @return string
1766
+	 *
1767
+	 * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
1768
+	 */
1769
+	protected function _get_background_image($computed): string
1770
+	{
1771
+		return $this->_stylesheet->resolve_url($computed);
1772
+	}
1773
+
1774
+	/**
1775
+	 * Returns the border color as an array
1776
+	 *
1777
+	 * See {@link Style::_get_color()} for format of the color array.
1778
+	 *
1779
+	 * @param string $computed
1780
+	 * @return array|string
1781
+	 *
1782
+	 * @link https://www.w3.org/TR/CSS21/box.html#border-color-properties
1783
+	 */
1784
+	protected function _get_border_top_color($computed)
1785
+	{
1786
+		return $this->get_color_value($computed);
1787
+	}
1788
+
1789
+	/**
1790
+	 * @param string $computed
1791
+	 * @return array|string
1792
+	 */
1793
+	protected function _get_border_right_color($computed)
1794
+	{
1795
+		return $this->get_color_value($computed);
1796
+	}
1797
+
1798
+	/**
1799
+	 * @param string $computed
1800
+	 * @return array|string
1801
+	 */
1802
+	protected function _get_border_bottom_color($computed)
1803
+	{
1804
+		return $this->get_color_value($computed);
1805
+	}
1806
+
1807
+	/**
1808
+	 * @param string $computed
1809
+	 * @return array|string
1810
+	 */
1811
+	protected function _get_border_left_color($computed)
1812
+	{
1813
+		return $this->get_color_value($computed);
1814
+	}
1815
+
1816
+	/**
1817
+	 * Return an array of all border properties.
1818
+	 *
1819
+	 * The returned array has the following structure:
1820
+	 *
1821
+	 * ```
1822
+	 * array("top" => array("width" => [border-width],
1823
+	 *                      "style" => [border-style],
1824
+	 *                      "color" => [border-color (array)]),
1825
+	 *       "bottom" ... )
1826
+	 * ```
1827
+	 *
1828
+	 * @return array
1829
+	 */
1830
+	public function get_border_properties(): array
1831
+	{
1832
+		return [
1833
+			"top" => [
1834
+				"width" => $this->__get("border_top_width"),
1835
+				"style" => $this->__get("border_top_style"),
1836
+				"color" => $this->__get("border_top_color"),
1837
+			],
1838
+			"bottom" => [
1839
+				"width" => $this->__get("border_bottom_width"),
1840
+				"style" => $this->__get("border_bottom_style"),
1841
+				"color" => $this->__get("border_bottom_color"),
1842
+			],
1843
+			"right" => [
1844
+				"width" => $this->__get("border_right_width"),
1845
+				"style" => $this->__get("border_right_style"),
1846
+				"color" => $this->__get("border_right_color"),
1847
+			],
1848
+			"left" => [
1849
+				"width" => $this->__get("border_left_width"),
1850
+				"style" => $this->__get("border_left_style"),
1851
+				"color" => $this->__get("border_left_color"),
1852
+			],
1853
+		];
1854
+	}
1855
+
1856
+	/**
1857
+	 * Return a single border-side property
1858
+	 *
1859
+	 * @param string $side
1860
+	 * @return string
1861
+	 */
1862
+	protected function get_border_side(string $side): string
1863
+	{
1864
+		$color = $this->__get("border_{$side}_color");
1865
+
1866
+		return $this->__get("border_{$side}_width") . " " .
1867
+			$this->__get("border_{$side}_style") . " " .
1868
+			(\is_array($color) ? $color["hex"] : $color);
1869
+	}
1870
+
1871
+	/**
1872
+	 * Return full border properties as a string
1873
+	 *
1874
+	 * Border properties are returned just as specified in CSS:
1875
+	 * `[width] [style] [color]`
1876
+	 * e.g. "1px solid blue"
1877
+	 *
1878
+	 * @return string
1879
+	 *
1880
+	 * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
1881
+	 */
1882
+	protected function _get_border_top(): string
1883
+	{
1884
+		return $this->get_border_side("top");
1885
+	}
1886
+
1887
+	/**
1888
+	 * @return string
1889
+	 */
1890
+	protected function _get_border_right(): string
1891
+	{
1892
+		return $this->get_border_side("right");
1893
+	}
1894
+
1895
+	/**
1896
+	 * @return string
1897
+	 */
1898
+	protected function _get_border_bottom(): string
1899
+	{
1900
+		return $this->get_border_side("bottom");
1901
+	}
1902
+
1903
+	/**
1904
+	 * @return string
1905
+	 */
1906
+	protected function _get_border_left(): string
1907
+	{
1908
+		return $this->get_border_side("left");
1909
+	}
1910
+
1911
+	public function has_border_radius(): bool
1912
+	{
1913
+		if (isset($this->has_border_radius_cache)) {
1914
+			return $this->has_border_radius_cache;
1915
+		}
1916
+
1917
+		// Use a fixed ref size here. We don't know the border-box width here
1918
+		// and font size might be 0. Since we are only interested in whether
1919
+		// there is any border radius at all, this should do
1920
+		$tl = (float) $this->length_in_pt($this->border_top_left_radius, 12);
1921
+		$tr = (float) $this->length_in_pt($this->border_top_right_radius, 12);
1922
+		$br = (float) $this->length_in_pt($this->border_bottom_right_radius, 12);
1923
+		$bl = (float) $this->length_in_pt($this->border_bottom_left_radius, 12);
1924
+
1925
+		$this->has_border_radius_cache = $tl + $tr + $br + $bl > 0;
1926
+		return $this->has_border_radius_cache;
1927
+	}
1928
+
1929
+	/**
1930
+	 * Get the final border-radius values to use.
1931
+	 *
1932
+	 * Percentage values are resolved relative to the width of the border box.
1933
+	 * The border radius is additionally scaled for the given render box, and
1934
+	 * constrained by its width and height.
1935
+	 *
1936
+	 * @param float[]      $border_box The border box of the frame.
1937
+	 * @param float[]|null $render_box The box to resolve the border radius for.
1938
+	 *
1939
+	 * @return float[] A 4-tuple of top-left, top-right, bottom-right, and bottom-left radius.
1940
+	 */
1941
+	public function resolve_border_radius(
1942
+		array $border_box,
1943
+		?array $render_box = null
1944
+	): array {
1945
+		$render_box = $render_box ?? $border_box;
1946
+		$use_cache = $render_box === $border_box;
1947
+
1948
+		if ($use_cache && isset($this->resolved_border_radius)) {
1949
+			return $this->resolved_border_radius;
1950
+		}
1951
+
1952
+		[$x, $y, $w, $h] = $border_box;
1953
+
1954
+		// Resolve percentages relative to width, as long as we have no support
1955
+		// for per-axis radii
1956
+		$tl = (float) $this->length_in_pt($this->border_top_left_radius, $w);
1957
+		$tr = (float) $this->length_in_pt($this->border_top_right_radius, $w);
1958
+		$br = (float) $this->length_in_pt($this->border_bottom_right_radius, $w);
1959
+		$bl = (float) $this->length_in_pt($this->border_bottom_left_radius, $w);
1960
+
1961
+		if ($tl + $tr + $br + $bl > 0) {
1962
+			[$rx, $ry, $rw, $rh] = $render_box;
1963
+
1964
+			$t_offset = $y - $ry;
1965
+			$r_offset = $rx + $rw - $x - $w;
1966
+			$b_offset = $ry + $rh - $y - $h;
1967
+			$l_offset = $x - $rx;
1968
+
1969
+			if ($tl > 0) {
1970
+				$tl = max($tl + ($t_offset + $l_offset) / 2, 0);
1971
+			}
1972
+			if ($tr > 0) {
1973
+				$tr = max($tr + ($t_offset + $r_offset) / 2, 0);
1974
+			}
1975
+			if ($br > 0) {
1976
+				$br = max($br + ($b_offset + $r_offset) / 2, 0);
1977
+			}
1978
+			if ($bl > 0) {
1979
+				$bl = max($bl + ($b_offset + $l_offset) / 2, 0);
1980
+			}
1981
+
1982
+			if ($tl + $bl > $rh) {
1983
+				$f = $rh / ($tl + $bl);
1984
+				$tl = $f * $tl;
1985
+				$bl = $f * $bl;
1986
+			}
1987
+			if ($tr + $br > $rh) {
1988
+				$f = $rh / ($tr + $br);
1989
+				$tr = $f * $tr;
1990
+				$br = $f * $br;
1991
+			}
1992
+			if ($tl + $tr > $rw) {
1993
+				$f = $rw / ($tl + $tr);
1994
+				$tl = $f * $tl;
1995
+				$tr = $f * $tr;
1996
+			}
1997
+			if ($bl + $br > $rw) {
1998
+				$f = $rw / ($bl + $br);
1999
+				$bl = $f * $bl;
2000
+				$br = $f * $br;
2001
+			}
2002
+		}
2003
+
2004
+		$values = [$tl, $tr, $br, $bl];
2005
+
2006
+		if ($use_cache) {
2007
+			$this->resolved_border_radius = $values;
2008
+		}
2009
+
2010
+		return $values;
2011
+	}
2012
+
2013
+	/**
2014
+	 * Returns the outline color as an array
2015
+	 *
2016
+	 * See {@link Style::_get_color()} for format of the color array.
2017
+	 *
2018
+	 * @param string $computed
2019
+	 * @return array|string
2020
+	 *
2021
+	 * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-color
2022
+	 */
2023
+	protected function _get_outline_color($computed)
2024
+	{
2025
+		return $this->get_color_value($computed);
2026
+	}
2027
+
2028
+	/**
2029
+	 * @param string $computed
2030
+	 * @return string
2031
+	 *
2032
+	 * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-style
2033
+	 */
2034
+	protected function _get_outline_style($computed): string
2035
+	{
2036
+		return $computed === "auto" ? "solid" : $computed;
2037
+	}
2038
+
2039
+	/**
2040
+	 * Return full outline properties as a string
2041
+	 *
2042
+	 * Outline properties are returned just as specified in CSS:
2043
+	 * `[width] [style] [color]`
2044
+	 * e.g. "1px solid blue"
2045
+	 *
2046
+	 * @return string
2047
+	 *
2048
+	 * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
2049
+	 */
2050
+	protected function _get_outline(): string
2051
+	{
2052
+		$color = $this->__get("outline_color");
2053
+
2054
+		return $this->__get("outline_width") . " " .
2055
+			$this->__get("outline_style") . " " .
2056
+			(\is_array($color) ? $color["hex"] : $color);
2057
+	}
2058
+
2059
+	/**
2060
+	 * Returns the list style image URI, or "none"
2061
+	 *
2062
+	 * @param string $computed
2063
+	 * @return string
2064
+	 *
2065
+	 * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
2066
+	 */
2067
+	protected function _get_list_style_image($computed): string
2068
+	{
2069
+		return $this->_stylesheet->resolve_url($computed);
2070
+	}
2071
+
2072
+	/**
2073
+	 * @param string $value
2074
+	 * @param int    $default
2075
+	 *
2076
+	 * @return array|string
2077
+	 */
2078
+	protected function parse_counter_prop(string $value, int $default)
2079
+	{
2080
+		$ident = self::CSS_IDENTIFIER;
2081
+		$integer = self::CSS_INTEGER;
2082
+		$pattern = "/($ident)(?:\s+($integer))?/";
2083
+
2084
+		if (!preg_match_all($pattern, $value, $matches, PREG_SET_ORDER)) {
2085
+			return "none";
2086
+		}
2087
+
2088
+		$counters = [];
2089
+
2090
+		foreach ($matches as $match) {
2091
+			$counter = $match[1];
2092
+			$value = isset($match[2]) ? (int) $match[2] : $default;
2093
+			$counters[$counter] = $value;
2094
+		}
2095
+
2096
+		return $counters;
2097
+	}
2098
+
2099
+	/**
2100
+	 * @param string $computed
2101
+	 * @return array|string
2102
+	 *
2103
+	 * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment
2104
+	 */
2105
+	protected function _get_counter_increment($computed)
2106
+	{
2107
+		if ($computed === "none") {
2108
+			return $computed;
2109
+		}
2110
+
2111
+		return $this->parse_counter_prop($computed, 1);
2112
+	}
2113
+
2114
+	/**
2115
+	 * @param string $computed
2116
+	 * @return array|string
2117
+	 *
2118
+	 * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-reset
2119
+	 */
2120
+	protected function _get_counter_reset($computed)
2121
+	{
2122
+		if ($computed === "none") {
2123
+			return $computed;
2124
+		}
2125
+
2126
+		return $this->parse_counter_prop($computed, 0);
2127
+	}
2128
+
2129
+	/**
2130
+	 * @param string $computed
2131
+	 * @return string[]|string
2132
+	 *
2133
+	 * @link https://www.w3.org/TR/CSS21/generate.html#propdef-content
2134
+	 */
2135
+	protected function _get_content($computed)
2136
+	{
2137
+		if ($computed === "normal" || $computed === "none") {
2138
+			return $computed;
2139
+		}
2140
+
2141
+		return $this->parse_property_value($computed);
2142
+	}
2143
+
2144
+	/*==============================*/
2145
+
2146
+	/**
2147
+	 * Parse a property value into its components.
2148
+	 *
2149
+	 * @param string $value
2150
+	 *
2151
+	 * @return string[]
2152
+	 */
2153
+	protected function parse_property_value(string $value): array
2154
+	{
2155
+		$ident = self::CSS_IDENTIFIER;
2156
+		$number = self::CSS_NUMBER;
2157
+
2158
+		$pattern = "/\n" .
2159
+			"\s* \" ( (?:[^\"]|\\\\[\"])* ) (?<!\\\\)\" |\n" . // String ""
2160
+			"\s* '  ( (?:[^']|\\\\['])* )   (?<!\\\\)'  |\n" . // String ''
2161
+			"\s* ($ident \\([^)]*\\) )                  |\n" . // Functional
2162
+			"\s* ($ident)                               |\n" . // Keyword
2163
+			"\s* (\#[0-9a-fA-F]*)                       |\n" . // Hex value
2164
+			"\s* ($number [a-zA-Z%]*)                   |\n" . // Number (+ unit/percentage)
2165
+			"\s* ([\/,;])                                \n" . // Delimiter
2166
+			"/Sx";
2167
+
2168
+		if (!preg_match_all($pattern, $value, $matches)) {
2169
+			return [];
2170
+		}
2171
+
2172
+		return array_map("trim", $matches[0]);
2173
+	}
2174
+
2175
+	protected function is_color_value(string $val): bool
2176
+	{
2177
+		return $val === "currentcolor"
2178
+			|| $val === "transparent"
2179
+			|| isset(Color::$cssColorNames[$val])
2180
+			|| preg_match("/^#|rgb\(|rgba\(|cmyk\(/", $val);
2181
+	}
2182
+
2183
+	/**
2184
+	 * @param string $val
2185
+	 * @return string|null
2186
+	 */
2187
+	protected function compute_color_value(string $val): ?string
2188
+	{
2189
+		// https://www.w3.org/TR/css-color-4/#resolving-other-colors
2190
+		$munged_color = $val !== "currentcolor"
2191
+			? $this->munge_color($val)
2192
+			: $val;
2193
+
2194
+		if ($munged_color === null) {
2195
+			return null;
2196
+		}
2197
+
2198
+		return \is_array($munged_color) ? $munged_color["hex"] : $munged_color;
2199
+	}
2200
+
2201
+	/**
2202
+	 * @param string $val
2203
+	 * @return int|null
2204
+	 */
2205
+	protected function compute_integer(string $val): ?int
2206
+	{
2207
+		$integer = self::CSS_INTEGER;
2208
+		return preg_match("/^$integer$/", $val)
2209
+			? (int) $val
2210
+			: null;
2211
+	}
2212
+
2213
+	/**
2214
+	 * @param string $val
2215
+	 * @return float|null
2216
+	 */
2217
+	protected function compute_length(string $val): ?float
2218
+	{
2219
+		return mb_strpos($val, "%") === false
2220
+			? $this->single_length_in_pt($val)
2221
+			: null;
2222
+	}
2223
+
2224
+	/**
2225
+	 * @param string $val
2226
+	 * @return float|null
2227
+	 */
2228
+	protected function compute_length_positive(string $val): ?float
2229
+	{
2230
+		$computed = $this->compute_length($val);
2231
+		return $computed !== null && $computed >= 0 ? $computed : null;
2232
+	}
2233
+
2234
+	/**
2235
+	 * @param string $val
2236
+	 * @return float|string|null
2237
+	 */
2238
+	protected function compute_length_percentage(string $val)
2239
+	{
2240
+		// Compute with a fixed ref size to decide whether percentage values
2241
+		// are valid
2242
+		$computed = $this->single_length_in_pt($val, 12);
2243
+
2244
+		if ($computed === null) {
2245
+			return null;
2246
+		}
2247
+
2248
+		// Retain valid percentage declarations
2249
+		return mb_strpos($val, "%") === false ? $computed : $val;
2250
+	}
2251
+
2252
+	/**
2253
+	 * @param string $val
2254
+	 * @return float|string|null
2255
+	 */
2256
+	protected function compute_length_percentage_positive(string $val)
2257
+	{
2258
+		// Compute with a fixed ref size to decide whether percentage values
2259
+		// are valid
2260
+		$computed = $this->single_length_in_pt($val, 12);
2261
+
2262
+		if ($computed === null || $computed < 0) {
2263
+			return null;
2264
+		}
2265
+
2266
+		// Retain valid percentage declarations
2267
+		return mb_strpos($val, "%") === false ? $computed : $val;
2268
+	}
2269
+
2270
+	/**
2271
+	 * @param string $val
2272
+	 * @param string $style_prop The corresponding border-/outline-style property.
2273
+	 *
2274
+	 * @return float|null
2275
+	 *
2276
+	 * @link https://www.w3.org/TR/css-backgrounds-3/#typedef-line-width
2277
+	 */
2278
+	protected function compute_line_width(string $val, string $style_prop): ?float
2279
+	{
2280
+		// Border-width keywords
2281
+		if ($val === "thin") {
2282
+			$computed = 0.5;
2283
+		} elseif ($val === "medium") {
2284
+			$computed = 1.5;
2285
+		} elseif ($val === "thick") {
2286
+			$computed = 2.5;
2287
+		} else {
2288
+			$computed = $this->compute_length_positive($val);
2289
+		}
2290
+
2291
+		if ($computed === null) {
2292
+			return null;
2293
+		}
2294
+
2295
+		// Computed width is 0 if the line style is `none` or `hidden`
2296
+		// https://www.w3.org/TR/css-backgrounds-3/#border-width
2297
+		// https://www.w3.org/TR/css-ui-4/#outline-width
2298
+		$lineStyle = $this->__get($style_prop);
2299
+		$hasLineStyle = $lineStyle !== "none" && $lineStyle !== "hidden";
2300
+
2301
+		return $hasLineStyle ? $computed : 0.0;
2302
+	}
2303
+
2304
+	/**
2305
+	 * @param string $val
2306
+	 * @return string|null
2307
+	 */
2308
+	protected function compute_border_style(string $val): ?string
2309
+	{
2310
+		return \in_array($val, self::BORDER_STYLES, true) ? $val : null;
2311
+	}
2312
+
2313
+	/**
2314
+	 * Parse a property value with 1 to 4 components into 4 values, as required
2315
+	 * by shorthand properties such as `margin`, `padding`, and `border-radius`.
2316
+	 *
2317
+	 * @param string $prop  The shorthand property with exactly 4 sub-properties to handle.
2318
+	 * @param string $value The property value to parse.
2319
+	 *
2320
+	 * @return string[]
2321
+	 */
2322
+	protected function set_quad_shorthand(string $prop, string $value): array
2323
+	{
2324
+		$v = $this->parse_property_value($value);
2325
+
2326
+		switch (\count($v)) {
2327
+			case 1:
2328
+				$values = [$v[0], $v[0], $v[0], $v[0]];
2329
+				break;
2330
+			case 2:
2331
+				$values = [$v[0], $v[1], $v[0], $v[1]];
2332
+				break;
2333
+			case 3:
2334
+				$values = [$v[0], $v[1], $v[2], $v[1]];
2335
+				break;
2336
+			case 4:
2337
+				$values = [$v[0], $v[1], $v[2], $v[3]];
2338
+				break;
2339
+			default:
2340
+				return [];
2341
+		}
2342
+
2343
+		return array_combine(self::$_props_shorthand[$prop], $values);
2344
+	}
2345
+
2346
+	/*======================*/
2347
+
2348
+	/**
2349
+	 * @link https://www.w3.org/TR/CSS21/visuren.html#display-prop
2350
+	 */
2351
+	protected function _compute_display(string $val)
2352
+	{
2353
+		// Make sure that common valid, but unsupported display types have an
2354
+		// appropriate fallback display type
2355
+		switch ($val) {
2356
+			case "flow-root":
2357
+			case "flex":
2358
+			case "grid":
2359
+			case "table-caption":
2360
+				$val = "block";
2361
+				break;
2362
+			case "inline-flex":
2363
+			case "inline-grid":
2364
+				$val = "inline-block";
2365
+				break;
2366
+		}
2367
+
2368
+		if (!isset(self::$valid_display_types[$val])) {
2369
+			return null;
2370
+		}
2371
+
2372
+		// https://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
2373
+		if ($this->is_in_flow()) {
2374
+			return $val;
2375
+		} else {
2376
+			switch ($val) {
2377
+				case "inline":
2378
+				case "inline-block":
2379
+				// case "table-row-group":
2380
+				// case "table-header-group":
2381
+				// case "table-footer-group":
2382
+				// case "table-row":
2383
+				// case "table-cell":
2384
+				// case "table-column-group":
2385
+				// case "table-column":
2386
+				// case "table-caption":
2387
+					return "block";
2388
+				case "inline-table":
2389
+					return "table";
2390
+				default:
2391
+					return $val;
2392
+			}
2393
+		}
2394
+	}
2395
+
2396
+	/**
2397
+	 * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color
2398
+	 */
2399
+	protected function _compute_color(string $color)
2400
+	{
2401
+		return $this->compute_color_value($color);
2402
+	}
2403
+
2404
+	/**
2405
+	 * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color
2406
+	 */
2407
+	protected function _compute_background_color(string $color)
2408
+	{
2409
+		return $this->compute_color_value($color);
2410
+	}
2411
+
2412
+	/**
2413
+	 * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
2414
+	 */
2415
+	protected function _compute_background_image(string $val)
2416
+	{
2417
+		$parsed_val = $this->_stylesheet->resolve_url($val);
2418
+
2419
+		if ($parsed_val === "none") {
2420
+			return "none";
2421
+		} else {
2422
+			return "url($parsed_val)";
2423
+		}
2424
+	}
2425
+
2426
+	/**
2427
+	 * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
2428
+	 */
2429
+	protected function _compute_background_repeat(string $val)
2430
+	{
2431
+		$keywords = ["repeat", "repeat-x", "repeat-y", "no-repeat"];
2432
+		return \in_array($val, $keywords, true) ? $val : null;
2433
+	}
2434
+
2435
+	/**
2436
+	 * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
2437
+	 */
2438
+	protected function _compute_background_attachment(string $val)
2439
+	{
2440
+		$keywords = ["scroll", "fixed"];
2441
+		return \in_array($val, $keywords, true) ? $val : null;
2442
+	}
2443
+
2444
+	/**
2445
+	 * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-position
2446
+	 */
2447
+	protected function _compute_background_position(string $val)
2448
+	{
2449
+		$parts = preg_split("/\s+/", $val);
2450
+
2451
+		if (\count($parts) > 2) {
2452
+			return null;
2453
+		}
2454
+
2455
+		switch ($parts[0]) {
2456
+			case "left":
2457
+				$x = "0%";
2458
+				break;
2459
+
2460
+			case "right":
2461
+				$x = "100%";
2462
+				break;
2463
+
2464
+			case "top":
2465
+				$y = "0%";
2466
+				break;
2467
+
2468
+			case "bottom":
2469
+				$y = "100%";
2470
+				break;
2471
+
2472
+			case "center":
2473
+				$x = "50%";
2474
+				$y = "50%";
2475
+				break;
2476
+
2477
+			default:
2478
+				$x = $parts[0];
2479
+				break;
2480
+		}
2481
+
2482
+		if (isset($parts[1])) {
2483
+			switch ($parts[1]) {
2484
+				case "left":
2485
+					$x = "0%";
2486
+					break;
2487
+
2488
+				case "right":
2489
+					$x = "100%";
2490
+					break;
2491
+
2492
+				case "top":
2493
+					$y = "0%";
2494
+					break;
2495
+
2496
+				case "bottom":
2497
+					$y = "100%";
2498
+					break;
2499
+
2500
+				case "center":
2501
+					if ($parts[0] === "left" || $parts[0] === "right" || $parts[0] === "center") {
2502
+						$y = "50%";
2503
+					} else {
2504
+						$x = "50%";
2505
+					}
2506
+					break;
2507
+
2508
+				default:
2509
+					$y = $parts[1];
2510
+					break;
2511
+			}
2512
+		} else {
2513
+			$y = "50%";
2514
+		}
2515
+
2516
+		if (!isset($x)) {
2517
+			$x = "0%";
2518
+		}
2519
+
2520
+		if (!isset($y)) {
2521
+			$y = "0%";
2522
+		}
2523
+
2524
+		return [$x, $y];
2525
+	}
2526
+
2527
+	/**
2528
+	 * Compute `background-size`.
2529
+	 *
2530
+	 * Computes to one of the following values:
2531
+	 * * `cover`
2532
+	 * * `contain`
2533
+	 * * `[width, height]`, each being a length, percentage, or `auto`
2534
+	 *
2535
+	 * @link https://www.w3.org/TR/css-backgrounds-3/#background-size
2536
+	 */
2537
+	protected function _compute_background_size(string $val)
2538
+	{
2539
+		if ($val === "cover" || $val === "contain") {
2540
+			return $val;
2541
+		}
2542
+
2543
+		$parts = preg_split("/\s+/", $val);
2544
+
2545
+		if (\count($parts) > 2) {
2546
+			return null;
2547
+		}
2548
+
2549
+		$width = $parts[0];
2550
+		if ($width !== "auto") {
2551
+			$width = $this->compute_length_percentage_positive($width);
2552
+		}
2553
+
2554
+		$height = $parts[1] ?? "auto";
2555
+		if ($height !== "auto") {
2556
+			$height = $this->compute_length_percentage_positive($height);
2557
+		}
2558
+
2559
+		if ($width === null || $height === null) {
2560
+			return null;
2561
+		}
2562
+
2563
+		return [$width, $height];
2564
+	}
2565
+
2566
+	/**
2567
+	 * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-background
2568
+	 */
2569
+	protected function _set_background(string $value): array
2570
+	{
2571
+		$components = $this->parse_property_value($value);
2572
+		$props = [];
2573
+		$pos_size = [];
2574
+
2575
+		foreach ($components as $val) {
2576
+			if ($val === "none" || mb_substr($val, 0, 4) === "url(") {
2577
+				$props["background_image"] = $val;
2578
+			} elseif ($val === "scroll" || $val === "fixed") {
2579
+				$props["background_attachment"] = $val;
2580
+			} elseif ($val === "repeat" || $val === "repeat-x" || $val === "repeat-y" || $val === "no-repeat") {
2581
+				$props["background_repeat"] = $val;
2582
+			} elseif ($this->is_color_value($val)) {
2583
+				$props["background_color"] = $val;
2584
+			} else {
2585
+				$pos_size[] = $val;
2586
+			}
2587
+		}
2588
+
2589
+		if (\count($pos_size)) {
2590
+			// Split value list at "/"
2591
+			$index = array_search("/", $pos_size, true);
2592
+
2593
+			if ($index !== false) {
2594
+				$pos = \array_slice($pos_size, 0, $index);
2595
+				$size = \array_slice($pos_size, $index + 1);
2596
+			} else {
2597
+				$pos = $pos_size;
2598
+				$size = [];
2599
+			}
2600
+
2601
+			$props["background_position"] = implode(" ", $pos);
2602
+
2603
+			if (\count($size)) {
2604
+				$props["background_size"] = implode(" ", $size);
2605
+			}
2606
+		}
2607
+
2608
+		return $props;
2609
+	}
2610
+
2611
+	/**
2612
+	 * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
2613
+	 */
2614
+	protected function _compute_font_size(string $size)
2615
+	{
2616
+		$parent_font_size = isset($this->parent_style)
2617
+			? $this->parent_style->__get("font_size")
2618
+			: self::$default_font_size;
2619
+
2620
+		switch ($size) {
2621
+			case "xx-small":
2622
+			case "x-small":
2623
+			case "small":
2624
+			case "medium":
2625
+			case "large":
2626
+			case "x-large":
2627
+			case "xx-large":
2628
+				$fs = self::$default_font_size * self::$font_size_keywords[$size];
2629
+				break;
2630
+
2631
+			case "smaller":
2632
+				$fs = 8 / 9 * $parent_font_size;
2633
+				break;
2634
+
2635
+			case "larger":
2636
+				$fs = 6 / 5 * $parent_font_size;
2637
+				break;
2638
+
2639
+			default:
2640
+				$fs = $this->single_length_in_pt($size, $parent_font_size, $parent_font_size);
2641
+				break;
2642
+		}
2643
+
2644
+		return $fs;
2645
+	}
2646
+
2647
+	/**
2648
+	 * @link https://www.w3.org/TR/CSS21/fonts.html#font-boldness
2649
+	 */
2650
+	protected function _compute_font_weight(string $weight)
2651
+	{
2652
+		$computed_weight = $weight;
2653
+
2654
+		if ($weight === "bolder") {
2655
+			//TODO: One font weight heavier than the parent element (among the available weights of the font).
2656
+			$computed_weight = "bold";
2657
+		} elseif ($weight === "lighter") {
2658
+			//TODO: One font weight lighter than the parent element (among the available weights of the font).
2659
+			$computed_weight = "normal";
2660
+		}
2661
+
2662
+		return $computed_weight;
2663
+	}
2664
+
2665
+	/**
2666
+	 * Handle the `font` shorthand property.
2667
+	 *
2668
+	 * `[ font-style || font-variant || font-weight ] font-size [ / line-height ] font-family`
2669
+	 *
2670
+	 * @link https://www.w3.org/TR/CSS21/fonts.html#font-shorthand
2671
+	 */
2672
+	protected function _set_font(string $value): array
2673
+	{
2674
+		$components = $this->parse_property_value($value);
2675
+		$props = [];
2676
+
2677
+		$number = self::CSS_NUMBER;
2678
+		$unit = "pt|px|pc|rem|em|ex|in|cm|mm|%";
2679
+		$sizePattern = "/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|$number(?:$unit))$/";
2680
+		$sizeIndex = null;
2681
+
2682
+		// Find index of font-size to split the component list
2683
+		foreach ($components as $i => $val) {
2684
+			if (preg_match($sizePattern, $val)) {
2685
+				$sizeIndex = $i;
2686
+				$props["font_size"] = $val;
2687
+				break;
2688
+			}
2689
+		}
2690
+
2691
+		// `font-size` is mandatory
2692
+		if ($sizeIndex === null) {
2693
+			return [];
2694
+		}
2695
+
2696
+		// `font-style`, `font-variant`, `font-weight` in any order
2697
+		$styleVariantWeight = \array_slice($components, 0, $sizeIndex);
2698
+		$stylePattern = "/^(italic|oblique)$/";
2699
+		$variantPattern = "/^(small-caps)$/";
2700
+		$weightPattern = "/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900)$/";
2701
+
2702
+		if (\count($styleVariantWeight) > 3) {
2703
+			return [];
2704
+		}
2705
+
2706
+		foreach ($styleVariantWeight as $val) {
2707
+			if ($val === "normal") {
2708
+				// Ignore any `normal` value, as it is valid and the initial
2709
+				// value for all three properties
2710
+			} elseif (!isset($props["font_style"]) && preg_match($stylePattern, $val)) {
2711
+				$props["font_style"] = $val;
2712
+			} elseif (!isset($props["font_variant"]) && preg_match($variantPattern, $val)) {
2713
+				$props["font_variant"] = $val;
2714
+			} elseif (!isset($props["font_weight"]) && preg_match($weightPattern, $val)) {
2715
+				$props["font_weight"] = $val;
2716
+			} else {
2717
+				// Duplicates and other values disallowed here
2718
+				return [];
2719
+			}
2720
+		}
2721
+
2722
+		// Optional slash + `line-height` followed by mandatory `font-family`
2723
+		$lineFamily = \array_slice($components, $sizeIndex + 1);
2724
+		$hasLineHeight = $lineFamily !== [] && $lineFamily[0] === "/";
2725
+		$lineHeight = $hasLineHeight ? \array_slice($lineFamily, 1, 1) : [];
2726
+		$fontFamily = $hasLineHeight ? \array_slice($lineFamily, 2) : $lineFamily;
2727
+		$lineHeightPattern = "/^(normal|$number(?:$unit)?)$/";
2728
+
2729
+		// Missing `font-family` or `line-height` after slash
2730
+		if ($fontFamily === []
2731
+			|| ($hasLineHeight && !preg_match($lineHeightPattern, $lineHeight[0]))
2732
+		) {
2733
+			return [];
2734
+		}
2735
+
2736
+		if ($hasLineHeight) {
2737
+			$props["line_height"] = $lineHeight[0];
2738
+		}
2739
+
2740
+		$props["font_family"] = implode("", $fontFamily);
2741
+
2742
+		return $props;
2743
+	}
2744
+
2745
+	/**
2746
+	 * Compute `text-align`.
2747
+	 *
2748
+	 * If no alignment is set on the element and the direction is rtl then
2749
+	 * the property is set to "right", otherwise it is set to "left".
2750
+	 *
2751
+	 * @link https://www.w3.org/TR/CSS21/text.html#propdef-text-align
2752
+	 */
2753
+	protected function _compute_text_align(string $val)
2754
+	{
2755
+		$alignment = $val;
2756
+		if ($alignment === "") {
2757
+			$alignment = "left";
2758
+			if ($this->__get("direction") === "rtl") {
2759
+				$alignment = "right";
2760
+			}
2761
+		}
2762
+
2763
+		if (!\in_array($alignment, self::TEXT_ALIGN_KEYWORDS, true)) {
2764
+			return null;
2765
+		}
2766
+
2767
+		return $alignment;
2768
+	}
2769
+
2770
+	/**
2771
+	 * @link https://www.w3.org/TR/css-text-4/#word-spacing-property
2772
+	 */
2773
+	protected function _compute_word_spacing(string $val)
2774
+	{
2775
+		if ($val === "normal") {
2776
+			return 0.0;
2777
+		}
2778
+
2779
+		return $this->compute_length_percentage($val);
2780
+	}
2781
+
2782
+	/**
2783
+	 * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property
2784
+	 */
2785
+	protected function _compute_letter_spacing(string $val)
2786
+	{
2787
+		if ($val === "normal") {
2788
+			return 0.0;
2789
+		}
2790
+
2791
+		return $this->compute_length_percentage($val);
2792
+	}
2793
+
2794
+	/**
2795
+	 * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
2796
+	 */
2797
+	protected function _compute_line_height(string $val)
2798
+	{
2799
+		if ($val === "normal") {
2800
+			return $val;
2801
+		}
2802
+
2803
+		// Compute number values to string and lengths to float (in pt)
2804
+		if (is_numeric($val)) {
2805
+			return (string) $val;
2806
+		}
2807
+
2808
+		$font_size = $this->__get("font_size");
2809
+		$computed = $this->single_length_in_pt($val, $font_size);
2810
+		return $computed !== null && $computed >= 0 ? $computed : null;
2811
+	}
2812
+
2813
+	/**
2814
+	 * @link https://www.w3.org/TR/css-text-3/#text-indent-property
2815
+	 */
2816
+	protected function _compute_text_indent(string $val)
2817
+	{
2818
+		return $this->compute_length_percentage($val);
2819
+	}
2820
+
2821
+	/**
2822
+	 * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-before
2823
+	 */
2824
+	protected function _compute_page_break_before(string $break)
2825
+	{
2826
+		if ($break === "left" || $break === "right") {
2827
+			$break = "always";
2828
+		}
2829
+
2830
+		return $break;
2831
+	}
2832
+
2833
+	/**
2834
+	 * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-after
2835
+	 */
2836
+	protected function _compute_page_break_after(string $break)
2837
+	{
2838
+		if ($break === "left" || $break === "right") {
2839
+			$break = "always";
2840
+		}
2841
+
2842
+		return $break;
2843
+	}
2844
+
2845
+	/**
2846
+	 * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-width
2847
+	 */
2848
+	protected function _compute_width(string $val)
2849
+	{
2850
+		if ($val === "auto") {
2851
+			return $val;
2852
+		}
2853
+
2854
+		return $this->compute_length_percentage_positive($val);
2855
+	}
2856
+
2857
+	/**
2858
+	 * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-height
2859
+	 */
2860
+	protected function _compute_height(string $val)
2861
+	{
2862
+		if ($val === "auto") {
2863
+			return $val;
2864
+		}
2865
+
2866
+		return $this->compute_length_percentage_positive($val);
2867
+	}
2868
+
2869
+	/**
2870
+	 * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-width
2871
+	 */
2872
+	protected function _compute_min_width(string $val)
2873
+	{
2874
+		// Legacy support for `none`, not covered by spec
2875
+		if ($val === "auto" || $val === "none") {
2876
+			return "auto";
2877
+		}
2878
+
2879
+		return $this->compute_length_percentage_positive($val);
2880
+	}
2881
+
2882
+	/**
2883
+	 * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-height
2884
+	 */
2885
+	protected function _compute_min_height(string $val)
2886
+	{
2887
+		// Legacy support for `none`, not covered by spec
2888
+		if ($val === "auto" || $val === "none") {
2889
+			return "auto";
2890
+		}
2891
+
2892
+		return $this->compute_length_percentage_positive($val);
2893
+	}
2894
+
2895
+	/**
2896
+	 * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-width
2897
+	 */
2898
+	protected function _compute_max_width(string $val)
2899
+	{
2900
+		// Legacy support for `auto`, not covered by spec
2901
+		if ($val === "none" || $val === "auto") {
2902
+			return "none";
2903
+		}
2904
+
2905
+		return $this->compute_length_percentage_positive($val);
2906
+	}
2907
+
2908
+	/**
2909
+	 * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-height
2910
+	 */
2911
+	protected function _compute_max_height(string $val)
2912
+	{
2913
+		// Legacy support for `auto`, not covered by spec
2914
+		if ($val === "none" || $val === "auto") {
2915
+			return "none";
2916
+		}
2917
+
2918
+		return $this->compute_length_percentage_positive($val);
2919
+	}
2920
+
2921
+	/**
2922
+	 * @link https://www.w3.org/TR/css-position-3/#inset-properties
2923
+	 * @link https://www.w3.org/TR/css-position-3/#propdef-inset
2924
+	 */
2925
+	protected function _set_inset(string $val): array
2926
+	{
2927
+		return $this->set_quad_shorthand("inset", $val);
2928
+	}
2929
+
2930
+	/**
2931
+	 * @param string $val
2932
+	 * @return float|string|null
2933
+	 */
2934
+	protected function compute_box_inset(string $val)
2935
+	{
2936
+		if ($val === "auto") {
2937
+			return $val;
2938
+		}
2939
+
2940
+		return $this->compute_length_percentage($val);
2941
+	}
2942
+
2943
+	protected function _compute_top(string $val)
2944
+	{
2945
+		return $this->compute_box_inset($val);
2946
+	}
2947
+
2948
+	protected function _compute_right(string $val)
2949
+	{
2950
+		return $this->compute_box_inset($val);
2951
+	}
2952
+
2953
+	protected function _compute_bottom(string $val)
2954
+	{
2955
+		return $this->compute_box_inset($val);
2956
+	}
2957
+
2958
+	protected function _compute_left(string $val)
2959
+	{
2960
+		return $this->compute_box_inset($val);
2961
+	}
2962
+
2963
+	/**
2964
+	 * @link https://www.w3.org/TR/CSS21/box.html#margin-properties
2965
+	 * @link https://www.w3.org/TR/CSS21/box.html#propdef-margin
2966
+	 */
2967
+	protected function _set_margin(string $val): array
2968
+	{
2969
+		return $this->set_quad_shorthand("margin", $val);
2970
+	}
2971
+
2972
+	/**
2973
+	 * @param string $val
2974
+	 * @return float|string|null
2975
+	 */
2976
+	protected function compute_margin(string $val)
2977
+	{
2978
+		// Legacy support for `none` keyword, not covered by spec
2979
+		if ($val === "none") {
2980
+			return 0.0;
2981
+		}
2982
+
2983
+		if ($val === "auto") {
2984
+			return $val;
2985
+		}
2986
+
2987
+		return $this->compute_length_percentage($val);
2988
+	}
2989
+
2990
+	protected function _compute_margin_top(string $val)
2991
+	{
2992
+		return $this->compute_margin($val);
2993
+	}
2994
+
2995
+	protected function _compute_margin_right(string $val)
2996
+	{
2997
+		return $this->compute_margin($val);
2998
+	}
2999
+
3000
+	protected function _compute_margin_bottom(string $val)
3001
+	{
3002
+		return $this->compute_margin($val);
3003
+	}
3004
+
3005
+	protected function _compute_margin_left(string $val)
3006
+	{
3007
+		return $this->compute_margin($val);
3008
+	}
3009
+
3010
+	/**
3011
+	 * @link https://www.w3.org/TR/CSS21/box.html#padding-properties
3012
+	 * @link https://www.w3.org/TR/CSS21/box.html#propdef-padding
3013
+	 */
3014
+	protected function _set_padding(string $val): array
3015
+	{
3016
+		return $this->set_quad_shorthand("padding", $val);
3017
+	}
3018
+
3019
+	/**
3020
+	 * @param string $val
3021
+	 * @return float|string|null
3022
+	 */
3023
+	protected function compute_padding(string $val)
3024
+	{
3025
+		// Legacy support for `none` keyword, not covered by spec
3026
+		if ($val === "none") {
3027
+			return 0.0;
3028
+		}
3029
+
3030
+		return $this->compute_length_percentage_positive($val);
3031
+	}
3032
+
3033
+	protected function _compute_padding_top(string $val)
3034
+	{
3035
+		return $this->compute_padding($val);
3036
+	}
3037
+
3038
+	protected function _compute_padding_right(string $val)
3039
+	{
3040
+		return $this->compute_padding($val);
3041
+	}
3042
+
3043
+	protected function _compute_padding_bottom(string $val)
3044
+	{
3045
+		return $this->compute_padding($val);
3046
+	}
3047
+
3048
+	protected function _compute_padding_left(string $val)
3049
+	{
3050
+		return $this->compute_padding($val);
3051
+	}
3052
+
3053
+	/**
3054
+	 * @param string   $value  `width || style || color`
3055
+	 * @param string[] $styles The list of border styles to accept.
3056
+	 *
3057
+	 * @return array Array of `[width, style, color]`, or `null` if the declaration is invalid.
3058
+	 */
3059
+	protected function parse_border_side(string $value, array $styles = self::BORDER_STYLES): ?array
3060
+	{
3061
+		$components = $this->parse_property_value($value);
3062
+		$width = null;
3063
+		$style = null;
3064
+		$color = null;
3065
+
3066
+		foreach ($components as $val) {
3067
+			if ($style === null && \in_array($val, $styles, true)) {
3068
+				$style = $val;
3069
+			} elseif ($color === null && $this->is_color_value($val)) {
3070
+				$color = $val;
3071
+			} elseif ($width === null) {
3072
+				// Assume width
3073
+				$width = $val;
3074
+			} else {
3075
+				// Duplicates are not allowed
3076
+				return null;
3077
+			}
3078
+		}
3079
+
3080
+		return [$width, $style, $color];
3081
+	}
3082
+
3083
+	/**
3084
+	 * @link https://www.w3.org/TR/CSS21/box.html#border-properties
3085
+	 * @link https://www.w3.org/TR/CSS21/box.html#propdef-border
3086
+	 */
3087
+	protected function _set_border(string $value): array
3088
+	{
3089
+		$values = $this->parse_border_side($value);
3090
+
3091
+		if ($values === null) {
3092
+			return [];
3093
+		}
3094
+
3095
+		return array_merge(
3096
+			array_combine(self::$_props_shorthand["border_top"], $values),
3097
+			array_combine(self::$_props_shorthand["border_right"], $values),
3098
+			array_combine(self::$_props_shorthand["border_bottom"], $values),
3099
+			array_combine(self::$_props_shorthand["border_left"], $values)
3100
+		);
3101
+	}
3102
+
3103
+	/**
3104
+	 * @param string $prop
3105
+	 * @param string $value
3106
+	 * @return array
3107
+	 */
3108
+	protected function set_border_side(string $prop, string $value): array
3109
+	{
3110
+		$values = $this->parse_border_side($value);
3111
+
3112
+		if ($values === null) {
3113
+			return [];
3114
+		}
3115
+
3116
+		return array_combine(self::$_props_shorthand[$prop], $values);
3117
+	}
3118
+
3119
+	protected function _set_border_top(string $val): array
3120
+	{
3121
+		return $this->set_border_side("border_top", $val);
3122
+	}
3123
+
3124
+	protected function _set_border_right(string $val): array
3125
+	{
3126
+		return $this->set_border_side("border_right", $val);
3127
+	}
3128
+
3129
+	protected function _set_border_bottom(string $val): array
3130
+	{
3131
+		return $this->set_border_side("border_bottom", $val);
3132
+	}
3133
+
3134
+	protected function _set_border_left(string $val): array
3135
+	{
3136
+		return $this->set_border_side("border_left", $val);
3137
+	}
3138
+
3139
+	/**
3140
+	 * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-color
3141
+	 */
3142
+	protected function _set_border_color(string $val): array
3143
+	{
3144
+		return $this->set_quad_shorthand("border_color", $val);
3145
+	}
3146
+
3147
+	protected function _compute_border_top_color(string $val)
3148
+	{
3149
+		return $this->compute_color_value($val);
3150
+	}
3151
+
3152
+	protected function _compute_border_right_color(string $val)
3153
+	{
3154
+		return $this->compute_color_value($val);
3155
+	}
3156
+
3157
+	protected function _compute_border_bottom_color(string $val)
3158
+	{
3159
+		return $this->compute_color_value($val);
3160
+	}
3161
+
3162
+	protected function _compute_border_left_color(string $val)
3163
+	{
3164
+		return $this->compute_color_value($val);
3165
+	}
3166
+
3167
+	/**
3168
+	 * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-style
3169
+	 */
3170
+	protected function _set_border_style(string $val): array
3171
+	{
3172
+		return $this->set_quad_shorthand("border_style", $val);
3173
+	}
3174
+
3175
+	protected function _compute_border_top_style(string $val)
3176
+	{
3177
+		return $this->compute_border_style($val);
3178
+	}
3179
+
3180
+	protected function _compute_border_right_style(string $val)
3181
+	{
3182
+		return $this->compute_border_style($val);
3183
+	}
3184
+
3185
+	protected function _compute_border_bottom_style(string $val)
3186
+	{
3187
+		return $this->compute_border_style($val);
3188
+	}
3189
+
3190
+	protected function _compute_border_left_style(string $val)
3191
+	{
3192
+		return $this->compute_border_style($val);
3193
+	}
3194
+
3195
+	/**
3196
+	 * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-width
3197
+	 */
3198
+	protected function _set_border_width(string $val): array
3199
+	{
3200
+		return $this->set_quad_shorthand("border_width", $val);
3201
+	}
3202
+
3203
+	protected function _compute_border_top_width(string $val)
3204
+	{
3205
+		return $this->compute_line_width($val, "border_top_style");
3206
+	}
3207
+
3208
+	protected function _compute_border_right_width(string $val)
3209
+	{
3210
+		return $this->compute_line_width($val, "border_right_style");
3211
+	}
3212
+
3213
+	protected function _compute_border_bottom_width(string $val)
3214
+	{
3215
+		return $this->compute_line_width($val, "border_bottom_style");
3216
+	}
3217
+
3218
+	protected function _compute_border_left_width(string $val)
3219
+	{
3220
+		return $this->compute_line_width($val, "border_left_style");
3221
+	}
3222
+
3223
+	/**
3224
+	 * @link https://www.w3.org/TR/css-backgrounds-3/#corners
3225
+	 * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-border-radius
3226
+	 */
3227
+	protected function _set_border_radius(string $val): array
3228
+	{
3229
+		return $this->set_quad_shorthand("border_radius", $val);
3230
+	}
3231
+
3232
+	protected function _compute_border_top_left_radius(string $val)
3233
+	{
3234
+		return $this->compute_length_percentage_positive($val);
3235
+	}
3236
+
3237
+	protected function _compute_border_top_right_radius(string $val)
3238
+	{
3239
+		return $this->compute_length_percentage_positive($val);
3240
+	}
3241
+
3242
+	protected function _compute_border_bottom_right_radius(string $val)
3243
+	{
3244
+		return $this->compute_length_percentage_positive($val);
3245
+	}
3246
+
3247
+	protected function _compute_border_bottom_left_radius(string $val)
3248
+	{
3249
+		return $this->compute_length_percentage_positive($val);
3250
+	}
3251
+
3252
+	/**
3253
+	 * @link https://www.w3.org/TR/css-ui-4/#outline-props
3254
+	 * @link https://www.w3.org/TR/css-ui-4/#propdef-outline
3255
+	 */
3256
+	protected function _set_outline(string $value): array
3257
+	{
3258
+		$values = $this->parse_border_side($value, self::OUTLINE_STYLES);
3259
+
3260
+		if ($values === null) {
3261
+			return [];
3262
+		}
3263
+
3264
+		return array_combine(self::$_props_shorthand["outline"], $values);
3265
+	}
3266
+
3267
+	protected function _compute_outline_color(string $val)
3268
+	{
3269
+		return $this->compute_color_value($val);
3270
+	}
3271
+
3272
+	protected function _compute_outline_style(string $val)
3273
+	{
3274
+		return \in_array($val, self::OUTLINE_STYLES, true) ? $val : null;
3275
+	}
3276
+
3277
+	protected function _compute_outline_width(string $val)
3278
+	{
3279
+		return $this->compute_line_width($val, "outline_style");
3280
+	}
3281
+
3282
+	/**
3283
+	 * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-offset
3284
+	 */
3285
+	protected function _compute_outline_offset(string $val)
3286
+	{
3287
+		return $this->compute_length($val);
3288
+	}
3289
+
3290
+	/**
3291
+	 * Compute `border-spacing` to two lengths of the form
3292
+	 * `[horizontal, vertical]`.
3293
+	 *
3294
+	 * @link https://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing
3295
+	 */
3296
+	protected function _compute_border_spacing(string $val)
3297
+	{
3298
+		$parts = preg_split("/\s+/", $val);
3299
+
3300
+		if (\count($parts) > 2) {
3301
+			return null;
3302
+		}
3303
+
3304
+		$h = $this->compute_length_positive($parts[0]);
3305
+		$v = isset($parts[1])
3306
+			? $this->compute_length_positive($parts[1])
3307
+			: $h;
3308
+
3309
+		if ($h === null || $v === null) {
3310
+			return null;
3311
+		}
3312
+
3313
+		return [$h, $v];
3314
+	}
3315
+
3316
+	/**
3317
+	 * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
3318
+	 */
3319
+	protected function _compute_list_style_image(string $val)
3320
+	{
3321
+		$parsed_val = $this->_stylesheet->resolve_url($val);
3322
+
3323
+		if ($parsed_val === "none") {
3324
+			return "none";
3325
+		} else {
3326
+			return "url($parsed_val)";
3327
+		}
3328
+	}
3329
+
3330
+	/**
3331
+	 * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style
3332
+	 */
3333
+	protected function _set_list_style(string $value): array
3334
+	{
3335
+		static $positions = ["inside", "outside"];
3336
+		static $types = [
3337
+			"disc", "circle", "square",
3338
+			"decimal-leading-zero", "decimal", "1",
3339
+			"lower-roman", "upper-roman", "a", "A",
3340
+			"lower-greek",
3341
+			"lower-latin", "upper-latin",
3342
+			"lower-alpha", "upper-alpha",
3343
+			"armenian", "georgian", "hebrew",
3344
+			"cjk-ideographic", "hiragana", "katakana",
3345
+			"hiragana-iroha", "katakana-iroha", "none"
3346
+		];
3347
+
3348
+		$components = $this->parse_property_value($value);
3349
+		$props = [];
3350
+
3351
+		foreach ($components as $val) {
3352
+			/* https://www.w3.org/TR/CSS21/generate.html#list-style
3353 3353
              * A value of 'none' for the 'list-style' property sets both 'list-style-type' and 'list-style-image' to 'none'
3354 3354
              */
3355
-            if ($val === "none") {
3356
-                $props["list_style_type"] = $val;
3357
-                $props["list_style_image"] = $val;
3358
-                continue;
3359
-            }
3360
-
3361
-            //On setting or merging or inheriting list_style_image as well as list_style_type,
3362
-            //and url exists, then url has precedence, otherwise fall back to list_style_type
3363
-            //Firefox is wrong here (list_style_image gets overwritten on explicit list_style_type)
3364
-            //Internet Explorer 7/8 and dompdf is right.
3365
-
3366
-            if (mb_substr($val, 0, 4) === "url(") {
3367
-                $props["list_style_image"] = $val;
3368
-                continue;
3369
-            }
3370
-
3371
-            if (\in_array($val, $types, true)) {
3372
-                $props["list_style_type"] = $val;
3373
-            } elseif (\in_array($val, $positions, true)) {
3374
-                $props["list_style_position"] = $val;
3375
-            }
3376
-        }
3377
-
3378
-        return $props;
3379
-    }
3380
-
3381
-    /**
3382
-     * @link https://www.w3.org/TR/css-page-3/#page-size-prop
3383
-     */
3384
-    protected function _compute_size(string $val)
3385
-    {
3386
-        if ($val === "auto") {
3387
-            return $val;
3388
-        }
3389
-
3390
-        $parts = $this->parse_property_value($val);
3391
-        $count = \count($parts);
3392
-
3393
-        if ($count === 0 || $count > 3) {
3394
-            return null;
3395
-        }
3396
-
3397
-        $size = null;
3398
-        $orientation = null;
3399
-        $lengths = [];
3400
-
3401
-        foreach ($parts as $part) {
3402
-            if ($size === null && isset(CPDF::$PAPER_SIZES[$part])) {
3403
-                $size = $part;
3404
-            } elseif ($orientation === null && ($part === "portrait" || $part === "landscape")) {
3405
-                $orientation = $part;
3406
-            } else {
3407
-                $lengths[] = $part;
3408
-            }
3409
-        }
3410
-
3411
-        if ($size !== null && $lengths !== []) {
3412
-            return null;
3413
-        }
3414
-
3415
-        if ($size !== null) {
3416
-            // Standard paper size
3417
-            [$l1, $l2] = \array_slice(CPDF::$PAPER_SIZES[$size], 2, 2);
3418
-        } elseif ($lengths === []) {
3419
-            // Orientation only, use default paper size
3420
-            $dims = $this->_stylesheet->get_dompdf()->getPaperSize();
3421
-            [$l1, $l2] = \array_slice($dims, 2, 2);
3422
-        } else {
3423
-            // Custom paper size
3424
-            $l1 = $this->compute_length_positive($lengths[0]);
3425
-            $l2 = isset($lengths[1]) ? $this->compute_length_positive($lengths[1]) : $l1;
3426
-
3427
-            if ($l1 === null || $l2 === null) {
3428
-                return null;
3429
-            }
3430
-        }
3431
-
3432
-        if (($orientation === "portrait" && $l1 > $l2)
3433
-            || ($orientation === "landscape" && $l2 > $l1)
3434
-        ) {
3435
-            return [$l2, $l1];
3436
-        }
3437
-
3438
-        return [$l1, $l2];
3439
-    }
3440
-
3441
-    /**
3442
-     * @param string $computed
3443
-     * @return array
3444
-     *
3445
-     * @link https://www.w3.org/TR/css-transforms-1/#transform-property
3446
-     */
3447
-    protected function _get_transform($computed)
3448
-    {
3449
-        //TODO: should be handled in setter (lengths set to absolute)
3450
-
3451
-        $number = "\s*([^,\s]+)\s*";
3452
-        $tr_value = "\s*([^,\s]+)\s*";
3453
-        $angle = "\s*([^,\s]+(?:deg|rad)?)\s*";
3454
-
3455
-        if (!preg_match_all("/[a-z]+\([^\)]+\)/i", $computed, $parts, PREG_SET_ORDER)) {
3456
-            return [];
3457
-        }
3458
-
3459
-        $functions = [
3460
-            //"matrix"     => "\($number,$number,$number,$number,$number,$number\)",
3461
-
3462
-            "translate" => "\($tr_value(?:,$tr_value)?\)",
3463
-            "translateX" => "\($tr_value\)",
3464
-            "translateY" => "\($tr_value\)",
3465
-
3466
-            "scale" => "\($number(?:,$number)?\)",
3467
-            "scaleX" => "\($number\)",
3468
-            "scaleY" => "\($number\)",
3469
-
3470
-            "rotate" => "\($angle\)",
3471
-
3472
-            "skew" => "\($angle(?:,$angle)?\)",
3473
-            "skewX" => "\($angle\)",
3474
-            "skewY" => "\($angle\)",
3475
-        ];
3476
-
3477
-        $transforms = [];
3478
-
3479
-        foreach ($parts as $part) {
3480
-            $t = $part[0];
3481
-
3482
-            foreach ($functions as $name => $pattern) {
3483
-                if (preg_match("/$name\s*$pattern/i", $t, $matches)) {
3484
-                    $values = \array_slice($matches, 1);
3485
-
3486
-                    switch ($name) {
3487
-                        // <angle> units
3488
-                        case "rotate":
3489
-                        case "skew":
3490
-                        case "skewX":
3491
-                        case "skewY":
3492
-
3493
-                            foreach ($values as $i => $value) {
3494
-                                if (strpos($value, "rad")) {
3495
-                                    $values[$i] = rad2deg((float) $value);
3496
-                                } else {
3497
-                                    $values[$i] = (float) $value;
3498
-                                }
3499
-                            }
3500
-
3501
-                            switch ($name) {
3502
-                                case "skew":
3503
-                                    if (!isset($values[1])) {
3504
-                                        $values[1] = 0;
3505
-                                    }
3506
-                                    break;
3507
-                                case "skewX":
3508
-                                    $name = "skew";
3509
-                                    $values = [$values[0], 0];
3510
-                                    break;
3511
-                                case "skewY":
3512
-                                    $name = "skew";
3513
-                                    $values = [0, $values[0]];
3514
-                                    break;
3515
-                            }
3516
-                            break;
3517
-
3518
-                        // <translation-value> units
3519
-                        case "translate":
3520
-                            $values[0] = $this->length_in_pt($values[0], (float)$this->length_in_pt($this->width));
3521
-
3522
-                            if (isset($values[1])) {
3523
-                                $values[1] = $this->length_in_pt($values[1], (float)$this->length_in_pt($this->height));
3524
-                            } else {
3525
-                                $values[1] = 0;
3526
-                            }
3527
-                            break;
3528
-
3529
-                        case "translateX":
3530
-                            $name = "translate";
3531
-                            $values = [$this->length_in_pt($values[0], (float)$this->length_in_pt($this->width)), 0];
3532
-                            break;
3533
-
3534
-                        case "translateY":
3535
-                            $name = "translate";
3536
-                            $values = [0, $this->length_in_pt($values[0], (float)$this->length_in_pt($this->height))];
3537
-                            break;
3538
-
3539
-                        // <number> units
3540
-                        case "scale":
3541
-                            if (!isset($values[1])) {
3542
-                                $values[1] = $values[0];
3543
-                            }
3544
-                            break;
3545
-
3546
-                        case "scaleX":
3547
-                            $name = "scale";
3548
-                            $values = [$values[0], 1.0];
3549
-                            break;
3550
-
3551
-                        case "scaleY":
3552
-                            $name = "scale";
3553
-                            $values = [1.0, $values[0]];
3554
-                            break;
3555
-                    }
3556
-
3557
-                    $transforms[] = [
3558
-                        $name,
3559
-                        $values,
3560
-                    ];
3561
-                }
3562
-            }
3563
-        }
3564
-
3565
-        return $transforms;
3566
-    }
3567
-
3568
-    /**
3569
-     * @param string $computed
3570
-     * @return array
3571
-     *
3572
-     * @link https://www.w3.org/TR/css-transforms-1/#transform-origin-property
3573
-     */
3574
-    protected function _get_transform_origin($computed)
3575
-    {
3576
-        //TODO: should be handled in setter
3577
-
3578
-        $values = preg_split("/\s+/", $computed);
3579
-
3580
-        $values = array_map(function ($value) {
3581
-            if (\in_array($value, ["top", "left"], true)) {
3582
-                return 0;
3583
-            } elseif (\in_array($value, ["bottom", "right"], true)) {
3584
-                return "100%";
3585
-            } else {
3586
-                return $value;
3587
-            }
3588
-        }, $values);
3589
-
3590
-        if (!isset($values[1])) {
3591
-            $values[1] = $values[0];
3592
-        }
3593
-
3594
-        return $values;
3595
-    }
3596
-
3597
-    /**
3598
-     * @param string $val
3599
-     * @return string|null
3600
-     */
3601
-    protected function parse_image_resolution(string $val): ?string
3602
-    {
3603
-        // If exif data could be get:
3604
-        // $re = '/^\s*(\d+|normal|auto)(?:\s*,\s*(\d+|normal))?\s*$/';
3605
-
3606
-        $re = '/^\s*(\d+|normal|auto)\s*$/';
3607
-
3608
-        if (!preg_match($re, $val, $matches)) {
3609
-            return null;
3610
-        }
3611
-
3612
-        return $matches[1];
3613
-    }
3614
-
3615
-    /**
3616
-     * auto | normal | dpi
3617
-     */
3618
-    protected function _compute_background_image_resolution(string $val)
3619
-    {
3620
-        return $this->parse_image_resolution($val);
3621
-    }
3622
-
3623
-    /**
3624
-     * auto | normal | dpi
3625
-     */
3626
-    protected function _compute_image_resolution(string $val)
3627
-    {
3628
-        return $this->parse_image_resolution($val);
3629
-    }
3630
-
3631
-    /**
3632
-     * @link https://www.w3.org/TR/css-break-3/#propdef-orphans
3633
-     */
3634
-    protected function _compute_orphans(string $val)
3635
-    {
3636
-        return $this->compute_integer($val);
3637
-    }
3638
-
3639
-    /**
3640
-     * @link https://www.w3.org/TR/css-break-3/#propdef-widows
3641
-     */
3642
-    protected function _compute_widows(string $val)
3643
-    {
3644
-        return $this->compute_integer($val);
3645
-    }
3646
-
3647
-    /**
3648
-     * @link https://www.w3.org/TR/css-color-4/#propdef-opacity
3649
-     */
3650
-    protected function _compute_opacity(string $val)
3651
-    {
3652
-        $number = self::CSS_NUMBER;
3653
-        $pattern = "/^($number)(%?)$/";
3654
-
3655
-        if (!preg_match($pattern, $val, $matches)) {
3656
-            return null;
3657
-        }
3658
-
3659
-        $v = (float) $matches[1];
3660
-        $percent = $matches[2] === "%";
3661
-        $opacity = $percent ? ($v / 100) : $v;
3662
-
3663
-        return max(0.0, min($opacity, 1.0));
3664
-    }
3665
-
3666
-    /**
3667
-     * @link https://www.w3.org/TR/CSS21//visuren.html#propdef-z-index
3668
-     */
3669
-    protected function _compute_z_index(string $val)
3670
-    {
3671
-        if ($val === "auto") {
3672
-            return $val;
3673
-        }
3674
-
3675
-        return $this->compute_integer($val);
3676
-    }
3677
-
3678
-    /**
3679
-     * @param FontMetrics $fontMetrics
3680
-     * @return $this
3681
-     */
3682
-    public function setFontMetrics(FontMetrics $fontMetrics)
3683
-    {
3684
-        $this->fontMetrics = $fontMetrics;
3685
-        return $this;
3686
-    }
3687
-
3688
-    /**
3689
-     * @return FontMetrics
3690
-     */
3691
-    public function getFontMetrics()
3692
-    {
3693
-        return $this->fontMetrics;
3694
-    }
3695
-
3696
-    /**
3697
-     * Generate a string representation of the Style
3698
-     *
3699
-     * This dumps the entire property array into a string via print_r.  Useful
3700
-     * for debugging.
3701
-     *
3702
-     * @return string
3703
-     */
3704
-    /*DEBUGCSS print: see below additional debugging util*/
3705
-    public function __toString(): string
3706
-    {
3707
-        $parent_font_size = $this->parent_style
3708
-            ? $this->parent_style->font_size
3709
-            : self::$default_font_size;
3710
-
3711
-        return print_r(array_merge(["parent_font_size" => $parent_font_size],
3712
-            $this->_props), true);
3713
-    }
3714
-
3715
-    /*DEBUGCSS*/
3716
-    public function debug_print(): void
3717
-    {
3718
-        $parent_font_size = $this->parent_style
3719
-            ? $this->parent_style->font_size
3720
-            : self::$default_font_size;
3721
-
3722
-        print "    parent_font_size:" . $parent_font_size . ";\n";
3723
-        print "    Props [\n";
3724
-        print "      specified [\n";
3725
-        foreach ($this->_props as $prop => $val) {
3726
-            print '        ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3727
-            if (isset($this->_important_props[$prop])) {
3728
-                print ' !important';
3729
-            }
3730
-            print ";\n";
3731
-        }
3732
-        print "      ]\n";
3733
-        print "      computed [\n";
3734
-        foreach ($this->_props_computed as $prop => $val) {
3735
-            print '        ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3736
-            print ";\n";
3737
-        }
3738
-        print "      ]\n";
3739
-        print "      cached [\n";
3740
-        foreach ($this->_props_used as $prop => $val) {
3741
-            print '        ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3742
-            print ";\n";
3743
-        }
3744
-        print "      ]\n";
3745
-        print "    ]\n";
3746
-    }
3355
+			if ($val === "none") {
3356
+				$props["list_style_type"] = $val;
3357
+				$props["list_style_image"] = $val;
3358
+				continue;
3359
+			}
3360
+
3361
+			//On setting or merging or inheriting list_style_image as well as list_style_type,
3362
+			//and url exists, then url has precedence, otherwise fall back to list_style_type
3363
+			//Firefox is wrong here (list_style_image gets overwritten on explicit list_style_type)
3364
+			//Internet Explorer 7/8 and dompdf is right.
3365
+
3366
+			if (mb_substr($val, 0, 4) === "url(") {
3367
+				$props["list_style_image"] = $val;
3368
+				continue;
3369
+			}
3370
+
3371
+			if (\in_array($val, $types, true)) {
3372
+				$props["list_style_type"] = $val;
3373
+			} elseif (\in_array($val, $positions, true)) {
3374
+				$props["list_style_position"] = $val;
3375
+			}
3376
+		}
3377
+
3378
+		return $props;
3379
+	}
3380
+
3381
+	/**
3382
+	 * @link https://www.w3.org/TR/css-page-3/#page-size-prop
3383
+	 */
3384
+	protected function _compute_size(string $val)
3385
+	{
3386
+		if ($val === "auto") {
3387
+			return $val;
3388
+		}
3389
+
3390
+		$parts = $this->parse_property_value($val);
3391
+		$count = \count($parts);
3392
+
3393
+		if ($count === 0 || $count > 3) {
3394
+			return null;
3395
+		}
3396
+
3397
+		$size = null;
3398
+		$orientation = null;
3399
+		$lengths = [];
3400
+
3401
+		foreach ($parts as $part) {
3402
+			if ($size === null && isset(CPDF::$PAPER_SIZES[$part])) {
3403
+				$size = $part;
3404
+			} elseif ($orientation === null && ($part === "portrait" || $part === "landscape")) {
3405
+				$orientation = $part;
3406
+			} else {
3407
+				$lengths[] = $part;
3408
+			}
3409
+		}
3410
+
3411
+		if ($size !== null && $lengths !== []) {
3412
+			return null;
3413
+		}
3414
+
3415
+		if ($size !== null) {
3416
+			// Standard paper size
3417
+			[$l1, $l2] = \array_slice(CPDF::$PAPER_SIZES[$size], 2, 2);
3418
+		} elseif ($lengths === []) {
3419
+			// Orientation only, use default paper size
3420
+			$dims = $this->_stylesheet->get_dompdf()->getPaperSize();
3421
+			[$l1, $l2] = \array_slice($dims, 2, 2);
3422
+		} else {
3423
+			// Custom paper size
3424
+			$l1 = $this->compute_length_positive($lengths[0]);
3425
+			$l2 = isset($lengths[1]) ? $this->compute_length_positive($lengths[1]) : $l1;
3426
+
3427
+			if ($l1 === null || $l2 === null) {
3428
+				return null;
3429
+			}
3430
+		}
3431
+
3432
+		if (($orientation === "portrait" && $l1 > $l2)
3433
+			|| ($orientation === "landscape" && $l2 > $l1)
3434
+		) {
3435
+			return [$l2, $l1];
3436
+		}
3437
+
3438
+		return [$l1, $l2];
3439
+	}
3440
+
3441
+	/**
3442
+	 * @param string $computed
3443
+	 * @return array
3444
+	 *
3445
+	 * @link https://www.w3.org/TR/css-transforms-1/#transform-property
3446
+	 */
3447
+	protected function _get_transform($computed)
3448
+	{
3449
+		//TODO: should be handled in setter (lengths set to absolute)
3450
+
3451
+		$number = "\s*([^,\s]+)\s*";
3452
+		$tr_value = "\s*([^,\s]+)\s*";
3453
+		$angle = "\s*([^,\s]+(?:deg|rad)?)\s*";
3454
+
3455
+		if (!preg_match_all("/[a-z]+\([^\)]+\)/i", $computed, $parts, PREG_SET_ORDER)) {
3456
+			return [];
3457
+		}
3458
+
3459
+		$functions = [
3460
+			//"matrix"     => "\($number,$number,$number,$number,$number,$number\)",
3461
+
3462
+			"translate" => "\($tr_value(?:,$tr_value)?\)",
3463
+			"translateX" => "\($tr_value\)",
3464
+			"translateY" => "\($tr_value\)",
3465
+
3466
+			"scale" => "\($number(?:,$number)?\)",
3467
+			"scaleX" => "\($number\)",
3468
+			"scaleY" => "\($number\)",
3469
+
3470
+			"rotate" => "\($angle\)",
3471
+
3472
+			"skew" => "\($angle(?:,$angle)?\)",
3473
+			"skewX" => "\($angle\)",
3474
+			"skewY" => "\($angle\)",
3475
+		];
3476
+
3477
+		$transforms = [];
3478
+
3479
+		foreach ($parts as $part) {
3480
+			$t = $part[0];
3481
+
3482
+			foreach ($functions as $name => $pattern) {
3483
+				if (preg_match("/$name\s*$pattern/i", $t, $matches)) {
3484
+					$values = \array_slice($matches, 1);
3485
+
3486
+					switch ($name) {
3487
+						// <angle> units
3488
+						case "rotate":
3489
+						case "skew":
3490
+						case "skewX":
3491
+						case "skewY":
3492
+
3493
+							foreach ($values as $i => $value) {
3494
+								if (strpos($value, "rad")) {
3495
+									$values[$i] = rad2deg((float) $value);
3496
+								} else {
3497
+									$values[$i] = (float) $value;
3498
+								}
3499
+							}
3500
+
3501
+							switch ($name) {
3502
+								case "skew":
3503
+									if (!isset($values[1])) {
3504
+										$values[1] = 0;
3505
+									}
3506
+									break;
3507
+								case "skewX":
3508
+									$name = "skew";
3509
+									$values = [$values[0], 0];
3510
+									break;
3511
+								case "skewY":
3512
+									$name = "skew";
3513
+									$values = [0, $values[0]];
3514
+									break;
3515
+							}
3516
+							break;
3517
+
3518
+						// <translation-value> units
3519
+						case "translate":
3520
+							$values[0] = $this->length_in_pt($values[0], (float)$this->length_in_pt($this->width));
3521
+
3522
+							if (isset($values[1])) {
3523
+								$values[1] = $this->length_in_pt($values[1], (float)$this->length_in_pt($this->height));
3524
+							} else {
3525
+								$values[1] = 0;
3526
+							}
3527
+							break;
3528
+
3529
+						case "translateX":
3530
+							$name = "translate";
3531
+							$values = [$this->length_in_pt($values[0], (float)$this->length_in_pt($this->width)), 0];
3532
+							break;
3533
+
3534
+						case "translateY":
3535
+							$name = "translate";
3536
+							$values = [0, $this->length_in_pt($values[0], (float)$this->length_in_pt($this->height))];
3537
+							break;
3538
+
3539
+						// <number> units
3540
+						case "scale":
3541
+							if (!isset($values[1])) {
3542
+								$values[1] = $values[0];
3543
+							}
3544
+							break;
3545
+
3546
+						case "scaleX":
3547
+							$name = "scale";
3548
+							$values = [$values[0], 1.0];
3549
+							break;
3550
+
3551
+						case "scaleY":
3552
+							$name = "scale";
3553
+							$values = [1.0, $values[0]];
3554
+							break;
3555
+					}
3556
+
3557
+					$transforms[] = [
3558
+						$name,
3559
+						$values,
3560
+					];
3561
+				}
3562
+			}
3563
+		}
3564
+
3565
+		return $transforms;
3566
+	}
3567
+
3568
+	/**
3569
+	 * @param string $computed
3570
+	 * @return array
3571
+	 *
3572
+	 * @link https://www.w3.org/TR/css-transforms-1/#transform-origin-property
3573
+	 */
3574
+	protected function _get_transform_origin($computed)
3575
+	{
3576
+		//TODO: should be handled in setter
3577
+
3578
+		$values = preg_split("/\s+/", $computed);
3579
+
3580
+		$values = array_map(function ($value) {
3581
+			if (\in_array($value, ["top", "left"], true)) {
3582
+				return 0;
3583
+			} elseif (\in_array($value, ["bottom", "right"], true)) {
3584
+				return "100%";
3585
+			} else {
3586
+				return $value;
3587
+			}
3588
+		}, $values);
3589
+
3590
+		if (!isset($values[1])) {
3591
+			$values[1] = $values[0];
3592
+		}
3593
+
3594
+		return $values;
3595
+	}
3596
+
3597
+	/**
3598
+	 * @param string $val
3599
+	 * @return string|null
3600
+	 */
3601
+	protected function parse_image_resolution(string $val): ?string
3602
+	{
3603
+		// If exif data could be get:
3604
+		// $re = '/^\s*(\d+|normal|auto)(?:\s*,\s*(\d+|normal))?\s*$/';
3605
+
3606
+		$re = '/^\s*(\d+|normal|auto)\s*$/';
3607
+
3608
+		if (!preg_match($re, $val, $matches)) {
3609
+			return null;
3610
+		}
3611
+
3612
+		return $matches[1];
3613
+	}
3614
+
3615
+	/**
3616
+	 * auto | normal | dpi
3617
+	 */
3618
+	protected function _compute_background_image_resolution(string $val)
3619
+	{
3620
+		return $this->parse_image_resolution($val);
3621
+	}
3622
+
3623
+	/**
3624
+	 * auto | normal | dpi
3625
+	 */
3626
+	protected function _compute_image_resolution(string $val)
3627
+	{
3628
+		return $this->parse_image_resolution($val);
3629
+	}
3630
+
3631
+	/**
3632
+	 * @link https://www.w3.org/TR/css-break-3/#propdef-orphans
3633
+	 */
3634
+	protected function _compute_orphans(string $val)
3635
+	{
3636
+		return $this->compute_integer($val);
3637
+	}
3638
+
3639
+	/**
3640
+	 * @link https://www.w3.org/TR/css-break-3/#propdef-widows
3641
+	 */
3642
+	protected function _compute_widows(string $val)
3643
+	{
3644
+		return $this->compute_integer($val);
3645
+	}
3646
+
3647
+	/**
3648
+	 * @link https://www.w3.org/TR/css-color-4/#propdef-opacity
3649
+	 */
3650
+	protected function _compute_opacity(string $val)
3651
+	{
3652
+		$number = self::CSS_NUMBER;
3653
+		$pattern = "/^($number)(%?)$/";
3654
+
3655
+		if (!preg_match($pattern, $val, $matches)) {
3656
+			return null;
3657
+		}
3658
+
3659
+		$v = (float) $matches[1];
3660
+		$percent = $matches[2] === "%";
3661
+		$opacity = $percent ? ($v / 100) : $v;
3662
+
3663
+		return max(0.0, min($opacity, 1.0));
3664
+	}
3665
+
3666
+	/**
3667
+	 * @link https://www.w3.org/TR/CSS21//visuren.html#propdef-z-index
3668
+	 */
3669
+	protected function _compute_z_index(string $val)
3670
+	{
3671
+		if ($val === "auto") {
3672
+			return $val;
3673
+		}
3674
+
3675
+		return $this->compute_integer($val);
3676
+	}
3677
+
3678
+	/**
3679
+	 * @param FontMetrics $fontMetrics
3680
+	 * @return $this
3681
+	 */
3682
+	public function setFontMetrics(FontMetrics $fontMetrics)
3683
+	{
3684
+		$this->fontMetrics = $fontMetrics;
3685
+		return $this;
3686
+	}
3687
+
3688
+	/**
3689
+	 * @return FontMetrics
3690
+	 */
3691
+	public function getFontMetrics()
3692
+	{
3693
+		return $this->fontMetrics;
3694
+	}
3695
+
3696
+	/**
3697
+	 * Generate a string representation of the Style
3698
+	 *
3699
+	 * This dumps the entire property array into a string via print_r.  Useful
3700
+	 * for debugging.
3701
+	 *
3702
+	 * @return string
3703
+	 */
3704
+	/*DEBUGCSS print: see below additional debugging util*/
3705
+	public function __toString(): string
3706
+	{
3707
+		$parent_font_size = $this->parent_style
3708
+			? $this->parent_style->font_size
3709
+			: self::$default_font_size;
3710
+
3711
+		return print_r(array_merge(["parent_font_size" => $parent_font_size],
3712
+			$this->_props), true);
3713
+	}
3714
+
3715
+	/*DEBUGCSS*/
3716
+	public function debug_print(): void
3717
+	{
3718
+		$parent_font_size = $this->parent_style
3719
+			? $this->parent_style->font_size
3720
+			: self::$default_font_size;
3721
+
3722
+		print "    parent_font_size:" . $parent_font_size . ";\n";
3723
+		print "    Props [\n";
3724
+		print "      specified [\n";
3725
+		foreach ($this->_props as $prop => $val) {
3726
+			print '        ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3727
+			if (isset($this->_important_props[$prop])) {
3728
+				print ' !important';
3729
+			}
3730
+			print ";\n";
3731
+		}
3732
+		print "      ]\n";
3733
+		print "      computed [\n";
3734
+		foreach ($this->_props_computed as $prop => $val) {
3735
+			print '        ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3736
+			print ";\n";
3737
+		}
3738
+		print "      ]\n";
3739
+		print "      cached [\n";
3740
+		foreach ($this->_props_used as $prop => $val) {
3741
+			print '        ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3742
+			print ";\n";
3743
+		}
3744
+		print "      ]\n";
3745
+		print "    ]\n";
3746
+	}
3747 3747
 }
Please login to merge, or discard this patch.
Spacing   +63 added lines, -63 removed lines patch added patch discarded remove patch
@@ -634,10 +634,10 @@  discard block
 block discarded – undo
634 634
         $this->_origin = $origin;
635 635
         $this->parent_style = null;
636 636
 
637
-        if (!isset(self::$_defaults)) {
637
+        if ( ! isset(self::$_defaults)) {
638 638
 
639 639
             // Shorthand
640
-            $d =& self::$_defaults;
640
+            $d = & self::$_defaults;
641 641
 
642 642
             // All CSS 2.1 properties, and their default values
643 643
             // Some properties are specified with their computed value for
@@ -941,7 +941,7 @@  discard block
 block discarded – undo
941 941
     public function is_in_flow(): bool
942 942
     {
943 943
         $float = $this->__get("float");
944
-        return $float === "none" && !$this->is_absolute();
944
+        return $float === "none" && ! $this->is_absolute();
945 945
     }
946 946
 
947 947
     /**
@@ -964,7 +964,7 @@  discard block
 block discarded – undo
964 964
         $font_size = $this->__get("font_size");
965 965
         $ref_size = $ref_size ?? $font_size;
966 966
 
967
-        if (!\is_array($length)) {
967
+        if ( ! \is_array($length)) {
968 968
             $length = [$length];
969 969
         }
970 970
 
@@ -1012,7 +1012,7 @@  discard block
 block discarded – undo
1012 1012
         $number = self::CSS_NUMBER;
1013 1013
         $pattern = "/^($number)(.*)?$/";
1014 1014
 
1015
-        if (!preg_match($pattern, $l, $matches)) {
1015
+        if ( ! preg_match($pattern, $l, $matches)) {
1016 1016
             return null;
1017 1017
         }
1018 1018
 
@@ -1152,7 +1152,7 @@  discard block
 block discarded – undo
1152 1152
             $important = isset($style->_important_props[$prop]);
1153 1153
 
1154 1154
             // `!important` declarations take precedence over normal ones
1155
-            if (!$important && isset($this->_important_props[$prop])) {
1155
+            if ( ! $important && isset($this->_important_props[$prop])) {
1156 1156
                 continue;
1157 1157
             }
1158 1158
 
@@ -1164,7 +1164,7 @@  discard block
 block discarded – undo
1164 1164
 
1165 1165
             // Copy an existing computed value only for non-dependent
1166 1166
             // properties; otherwise it may be invalid for the current style
1167
-            if (!isset(self::$_dependent_props[$prop])
1167
+            if ( ! isset(self::$_dependent_props[$prop])
1168 1168
                 && \array_key_exists($prop, $style->_props_computed)
1169 1169
             ) {
1170 1170
                 $this->_props_computed[$prop] = $style->_props_computed[$prop];
@@ -1235,7 +1235,7 @@  discard block
 block discarded – undo
1235 1235
             $prop = self::$_props_alias[$prop];
1236 1236
         }
1237 1237
 
1238
-        if (!isset(self::$_defaults[$prop])) {
1238
+        if ( ! isset(self::$_defaults[$prop])) {
1239 1239
             global $_dompdf_warnings;
1240 1240
             $_dompdf_warnings[] = "'$prop' is not a recognized CSS property.";
1241 1241
             return;
@@ -1255,7 +1255,7 @@  discard block
 block discarded – undo
1255 1255
             } else {
1256 1256
                 $method = "_set_$prop";
1257 1257
 
1258
-                if (!isset(self::$_methods_cache[$method])) {
1258
+                if ( ! isset(self::$_methods_cache[$method])) {
1259 1259
                     self::$_methods_cache[$method] = method_exists($this, $method);
1260 1260
                 }
1261 1261
 
@@ -1283,7 +1283,7 @@  discard block
 block discarded – undo
1283 1283
             }
1284 1284
 
1285 1285
             // `!important` declarations take precedence over normal ones
1286
-            if (!$important && isset($this->_important_props[$prop])) {
1286
+            if ( ! $important && isset($this->_important_props[$prop])) {
1287 1287
                 return;
1288 1288
             }
1289 1289
 
@@ -1344,7 +1344,7 @@  discard block
 block discarded – undo
1344 1344
             $prop = self::$_props_alias[$prop];
1345 1345
         }
1346 1346
 
1347
-        if (!isset(self::$_defaults[$prop])) {
1347
+        if ( ! isset(self::$_defaults[$prop])) {
1348 1348
             throw new Exception("'$prop' is not a recognized CSS property.");
1349 1349
         }
1350 1350
 
@@ -1373,7 +1373,7 @@  discard block
 block discarded – undo
1373 1373
             $prop = self::$_props_alias[$prop];
1374 1374
         }
1375 1375
 
1376
-        if (!isset(self::$_defaults[$prop])) {
1376
+        if ( ! isset(self::$_defaults[$prop])) {
1377 1377
             throw new Exception("'$prop' is not a recognized CSS property.");
1378 1378
         }
1379 1379
 
@@ -1410,7 +1410,7 @@  discard block
 block discarded – undo
1410 1410
             $prop = self::$_props_alias[$prop];
1411 1411
         }
1412 1412
 
1413
-        if (!isset(self::$_defaults[$prop])) {
1413
+        if ( ! isset(self::$_defaults[$prop])) {
1414 1414
             throw new Exception("'$prop' is not a recognized CSS property.");
1415 1415
         }
1416 1416
 
@@ -1440,7 +1440,7 @@  discard block
 block discarded – undo
1440 1440
             $prop = self::$_props_alias[$prop];
1441 1441
         }
1442 1442
 
1443
-        if (!isset(self::$_defaults[$prop])) {
1443
+        if ( ! isset(self::$_defaults[$prop])) {
1444 1444
             throw new Exception("'$prop' is not a recognized CSS property.");
1445 1445
         }
1446 1446
 
@@ -1450,7 +1450,7 @@  discard block
 block discarded – undo
1450 1450
 
1451 1451
         $method = "_get_$prop";
1452 1452
 
1453
-        if (!isset(self::$_methods_cache[$method])) {
1453
+        if ( ! isset(self::$_methods_cache[$method])) {
1454 1454
             self::$_methods_cache[$method] = method_exists($this, $method);
1455 1455
         }
1456 1456
 
@@ -1461,7 +1461,7 @@  discard block
 block discarded – undo
1461 1461
             if (self::$_methods_cache[$method]) {
1462 1462
                 return $this->$method();
1463 1463
             } else {
1464
-                return implode(" ", array_map(function ($sub_prop) {
1464
+                return implode(" ", array_map(function($sub_prop) {
1465 1465
                     $val = $this->__get($sub_prop);
1466 1466
                     return \is_array($val) ? implode(" ", $val) : $val;
1467 1467
                 }, self::$_props_shorthand[$prop]));
@@ -1493,13 +1493,13 @@  discard block
 block discarded – undo
1493 1493
         }
1494 1494
 
1495 1495
         // Check for values which are already computed
1496
-        if (!\is_string($val)) {
1496
+        if ( ! \is_string($val)) {
1497 1497
             return $val;
1498 1498
         }
1499 1499
 
1500 1500
         $method = "_compute_$prop";
1501 1501
 
1502
-        if (!isset(self::$_methods_cache[$method])) {
1502
+        if ( ! isset(self::$_methods_cache[$method])) {
1503 1503
             self::$_methods_cache[$method] = method_exists($this, $method);
1504 1504
         }
1505 1505
 
@@ -1521,7 +1521,7 @@  discard block
 block discarded – undo
1521 1521
      */
1522 1522
     protected function computed(string $prop)
1523 1523
     {
1524
-        if (!\array_key_exists($prop, $this->_props_computed)) {
1524
+        if ( ! \array_key_exists($prop, $this->_props_computed)) {
1525 1525
             $val = $this->_props[$prop] ?? self::$_defaults[$prop];
1526 1526
             $computed = $this->compute_prop($prop, $val);
1527 1527
 
@@ -1601,14 +1601,14 @@  discard block
 block discarded – undo
1601 1601
         if ($weight === 'bold') {
1602 1602
             $weight = 700;
1603 1603
         } elseif (preg_match('/^[0-9]+$/', $weight, $match)) {
1604
-            $weight = (int)$match[0];
1604
+            $weight = (int) $match[0];
1605 1605
         } else {
1606 1606
             $weight = 400;
1607 1607
         }
1608 1608
 
1609 1609
         // Resolve font-style
1610 1610
         $font_style = $this->__get("font_style");
1611
-        $subtype = $fontMetrics->getType($weight . ' ' . $font_style);
1611
+        $subtype = $fontMetrics->getType($weight.' '.$font_style);
1612 1612
 
1613 1613
         $families = preg_split("/\s*,\s*/", $computed);
1614 1614
 
@@ -1618,15 +1618,15 @@  discard block
 block discarded – undo
1618 1618
             //remove leading and trailing whitespace
1619 1619
             $family = trim($family, " \t\n\r\x0B\"'");
1620 1620
             if ($DEBUGCSS) {
1621
-                print '(' . $family . ')';
1621
+                print '('.$family.')';
1622 1622
             }
1623 1623
             $font = $fontMetrics->getFont($family, $subtype);
1624 1624
 
1625 1625
             if ($font) {
1626 1626
                 if ($DEBUGCSS) {
1627 1627
                     print "<pre>[get_font_family:";
1628
-                    print '(' . $computed . '.' . $font_style . '.' . $weight . '.' . $subtype . ')';
1629
-                    print '(' . $font . ")get_font_family]\n</pre>";
1628
+                    print '('.$computed.'.'.$font_style.'.'.$weight.'.'.$subtype.')';
1629
+                    print '('.$font.")get_font_family]\n</pre>";
1630 1630
                 }
1631 1631
                 return $font;
1632 1632
             }
@@ -1640,12 +1640,12 @@  discard block
 block discarded – undo
1640 1640
 
1641 1641
         if ($font) {
1642 1642
             if ($DEBUGCSS) {
1643
-                print '(' . $font . ")get_font_family]\n</pre>";
1643
+                print '('.$font.")get_font_family]\n</pre>";
1644 1644
             }
1645 1645
             return $font;
1646 1646
         }
1647 1647
 
1648
-        throw new Exception("Unable to find a suitable font replacement for: '" . $computed . "'");
1648
+        throw new Exception("Unable to find a suitable font replacement for: '".$computed."'");
1649 1649
     }
1650 1650
 
1651 1651
     /**
@@ -1863,8 +1863,8 @@  discard block
 block discarded – undo
1863 1863
     {
1864 1864
         $color = $this->__get("border_{$side}_color");
1865 1865
 
1866
-        return $this->__get("border_{$side}_width") . " " .
1867
-            $this->__get("border_{$side}_style") . " " .
1866
+        return $this->__get("border_{$side}_width")." ".
1867
+            $this->__get("border_{$side}_style")." ".
1868 1868
             (\is_array($color) ? $color["hex"] : $color);
1869 1869
     }
1870 1870
 
@@ -2051,8 +2051,8 @@  discard block
 block discarded – undo
2051 2051
     {
2052 2052
         $color = $this->__get("outline_color");
2053 2053
 
2054
-        return $this->__get("outline_width") . " " .
2055
-            $this->__get("outline_style") . " " .
2054
+        return $this->__get("outline_width")." ".
2055
+            $this->__get("outline_style")." ".
2056 2056
             (\is_array($color) ? $color["hex"] : $color);
2057 2057
     }
2058 2058
 
@@ -2081,7 +2081,7 @@  discard block
 block discarded – undo
2081 2081
         $integer = self::CSS_INTEGER;
2082 2082
         $pattern = "/($ident)(?:\s+($integer))?/";
2083 2083
 
2084
-        if (!preg_match_all($pattern, $value, $matches, PREG_SET_ORDER)) {
2084
+        if ( ! preg_match_all($pattern, $value, $matches, PREG_SET_ORDER)) {
2085 2085
             return "none";
2086 2086
         }
2087 2087
 
@@ -2155,17 +2155,17 @@  discard block
 block discarded – undo
2155 2155
         $ident = self::CSS_IDENTIFIER;
2156 2156
         $number = self::CSS_NUMBER;
2157 2157
 
2158
-        $pattern = "/\n" .
2159
-            "\s* \" ( (?:[^\"]|\\\\[\"])* ) (?<!\\\\)\" |\n" . // String ""
2160
-            "\s* '  ( (?:[^']|\\\\['])* )   (?<!\\\\)'  |\n" . // String ''
2161
-            "\s* ($ident \\([^)]*\\) )                  |\n" . // Functional
2162
-            "\s* ($ident)                               |\n" . // Keyword
2163
-            "\s* (\#[0-9a-fA-F]*)                       |\n" . // Hex value
2164
-            "\s* ($number [a-zA-Z%]*)                   |\n" . // Number (+ unit/percentage)
2165
-            "\s* ([\/,;])                                \n" . // Delimiter
2158
+        $pattern = "/\n".
2159
+            "\s* \" ( (?:[^\"]|\\\\[\"])* ) (?<!\\\\)\" |\n".// String ""
2160
+            "\s* '  ( (?:[^']|\\\\['])* )   (?<!\\\\)'  |\n".// String ''
2161
+            "\s* ($ident \\([^)]*\\) )                  |\n".// Functional
2162
+            "\s* ($ident)                               |\n".// Keyword
2163
+            "\s* (\#[0-9a-fA-F]*)                       |\n".// Hex value
2164
+            "\s* ($number [a-zA-Z%]*)                   |\n".// Number (+ unit/percentage)
2165
+            "\s* ([\/,;])                                \n".// Delimiter
2166 2166
             "/Sx";
2167 2167
 
2168
-        if (!preg_match_all($pattern, $value, $matches)) {
2168
+        if ( ! preg_match_all($pattern, $value, $matches)) {
2169 2169
             return [];
2170 2170
         }
2171 2171
 
@@ -2365,7 +2365,7 @@  discard block
 block discarded – undo
2365 2365
                 break;
2366 2366
         }
2367 2367
 
2368
-        if (!isset(self::$valid_display_types[$val])) {
2368
+        if ( ! isset(self::$valid_display_types[$val])) {
2369 2369
             return null;
2370 2370
         }
2371 2371
 
@@ -2513,11 +2513,11 @@  discard block
 block discarded – undo
2513 2513
             $y = "50%";
2514 2514
         }
2515 2515
 
2516
-        if (!isset($x)) {
2516
+        if ( ! isset($x)) {
2517 2517
             $x = "0%";
2518 2518
         }
2519 2519
 
2520
-        if (!isset($y)) {
2520
+        if ( ! isset($y)) {
2521 2521
             $y = "0%";
2522 2522
         }
2523 2523
 
@@ -2707,11 +2707,11 @@  discard block
 block discarded – undo
2707 2707
             if ($val === "normal") {
2708 2708
                 // Ignore any `normal` value, as it is valid and the initial
2709 2709
                 // value for all three properties
2710
-            } elseif (!isset($props["font_style"]) && preg_match($stylePattern, $val)) {
2710
+            } elseif ( ! isset($props["font_style"]) && preg_match($stylePattern, $val)) {
2711 2711
                 $props["font_style"] = $val;
2712
-            } elseif (!isset($props["font_variant"]) && preg_match($variantPattern, $val)) {
2712
+            } elseif ( ! isset($props["font_variant"]) && preg_match($variantPattern, $val)) {
2713 2713
                 $props["font_variant"] = $val;
2714
-            } elseif (!isset($props["font_weight"]) && preg_match($weightPattern, $val)) {
2714
+            } elseif ( ! isset($props["font_weight"]) && preg_match($weightPattern, $val)) {
2715 2715
                 $props["font_weight"] = $val;
2716 2716
             } else {
2717 2717
                 // Duplicates and other values disallowed here
@@ -2728,7 +2728,7 @@  discard block
 block discarded – undo
2728 2728
 
2729 2729
         // Missing `font-family` or `line-height` after slash
2730 2730
         if ($fontFamily === []
2731
-            || ($hasLineHeight && !preg_match($lineHeightPattern, $lineHeight[0]))
2731
+            || ($hasLineHeight && ! preg_match($lineHeightPattern, $lineHeight[0]))
2732 2732
         ) {
2733 2733
             return [];
2734 2734
         }
@@ -2760,7 +2760,7 @@  discard block
 block discarded – undo
2760 2760
             }
2761 2761
         }
2762 2762
 
2763
-        if (!\in_array($alignment, self::TEXT_ALIGN_KEYWORDS, true)) {
2763
+        if ( ! \in_array($alignment, self::TEXT_ALIGN_KEYWORDS, true)) {
2764 2764
             return null;
2765 2765
         }
2766 2766
 
@@ -3452,7 +3452,7 @@  discard block
 block discarded – undo
3452 3452
         $tr_value = "\s*([^,\s]+)\s*";
3453 3453
         $angle = "\s*([^,\s]+(?:deg|rad)?)\s*";
3454 3454
 
3455
-        if (!preg_match_all("/[a-z]+\([^\)]+\)/i", $computed, $parts, PREG_SET_ORDER)) {
3455
+        if ( ! preg_match_all("/[a-z]+\([^\)]+\)/i", $computed, $parts, PREG_SET_ORDER)) {
3456 3456
             return [];
3457 3457
         }
3458 3458
 
@@ -3500,7 +3500,7 @@  discard block
 block discarded – undo
3500 3500
 
3501 3501
                             switch ($name) {
3502 3502
                                 case "skew":
3503
-                                    if (!isset($values[1])) {
3503
+                                    if ( ! isset($values[1])) {
3504 3504
                                         $values[1] = 0;
3505 3505
                                     }
3506 3506
                                     break;
@@ -3517,10 +3517,10 @@  discard block
 block discarded – undo
3517 3517
 
3518 3518
                         // <translation-value> units
3519 3519
                         case "translate":
3520
-                            $values[0] = $this->length_in_pt($values[0], (float)$this->length_in_pt($this->width));
3520
+                            $values[0] = $this->length_in_pt($values[0], (float) $this->length_in_pt($this->width));
3521 3521
 
3522 3522
                             if (isset($values[1])) {
3523
-                                $values[1] = $this->length_in_pt($values[1], (float)$this->length_in_pt($this->height));
3523
+                                $values[1] = $this->length_in_pt($values[1], (float) $this->length_in_pt($this->height));
3524 3524
                             } else {
3525 3525
                                 $values[1] = 0;
3526 3526
                             }
@@ -3528,17 +3528,17 @@  discard block
 block discarded – undo
3528 3528
 
3529 3529
                         case "translateX":
3530 3530
                             $name = "translate";
3531
-                            $values = [$this->length_in_pt($values[0], (float)$this->length_in_pt($this->width)), 0];
3531
+                            $values = [$this->length_in_pt($values[0], (float) $this->length_in_pt($this->width)), 0];
3532 3532
                             break;
3533 3533
 
3534 3534
                         case "translateY":
3535 3535
                             $name = "translate";
3536
-                            $values = [0, $this->length_in_pt($values[0], (float)$this->length_in_pt($this->height))];
3536
+                            $values = [0, $this->length_in_pt($values[0], (float) $this->length_in_pt($this->height))];
3537 3537
                             break;
3538 3538
 
3539 3539
                         // <number> units
3540 3540
                         case "scale":
3541
-                            if (!isset($values[1])) {
3541
+                            if ( ! isset($values[1])) {
3542 3542
                                 $values[1] = $values[0];
3543 3543
                             }
3544 3544
                             break;
@@ -3577,7 +3577,7 @@  discard block
 block discarded – undo
3577 3577
 
3578 3578
         $values = preg_split("/\s+/", $computed);
3579 3579
 
3580
-        $values = array_map(function ($value) {
3580
+        $values = array_map(function($value) {
3581 3581
             if (\in_array($value, ["top", "left"], true)) {
3582 3582
                 return 0;
3583 3583
             } elseif (\in_array($value, ["bottom", "right"], true)) {
@@ -3587,7 +3587,7 @@  discard block
 block discarded – undo
3587 3587
             }
3588 3588
         }, $values);
3589 3589
 
3590
-        if (!isset($values[1])) {
3590
+        if ( ! isset($values[1])) {
3591 3591
             $values[1] = $values[0];
3592 3592
         }
3593 3593
 
@@ -3605,7 +3605,7 @@  discard block
 block discarded – undo
3605 3605
 
3606 3606
         $re = '/^\s*(\d+|normal|auto)\s*$/';
3607 3607
 
3608
-        if (!preg_match($re, $val, $matches)) {
3608
+        if ( ! preg_match($re, $val, $matches)) {
3609 3609
             return null;
3610 3610
         }
3611 3611
 
@@ -3652,7 +3652,7 @@  discard block
 block discarded – undo
3652 3652
         $number = self::CSS_NUMBER;
3653 3653
         $pattern = "/^($number)(%?)$/";
3654 3654
 
3655
-        if (!preg_match($pattern, $val, $matches)) {
3655
+        if ( ! preg_match($pattern, $val, $matches)) {
3656 3656
             return null;
3657 3657
         }
3658 3658
 
@@ -3719,11 +3719,11 @@  discard block
 block discarded – undo
3719 3719
             ? $this->parent_style->font_size
3720 3720
             : self::$default_font_size;
3721 3721
 
3722
-        print "    parent_font_size:" . $parent_font_size . ";\n";
3722
+        print "    parent_font_size:".$parent_font_size.";\n";
3723 3723
         print "    Props [\n";
3724 3724
         print "      specified [\n";
3725 3725
         foreach ($this->_props as $prop => $val) {
3726
-            print '        ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3726
+            print '        '.$prop.': '.preg_replace("/\r\n/", ' ', print_r($val, true));
3727 3727
             if (isset($this->_important_props[$prop])) {
3728 3728
                 print ' !important';
3729 3729
             }
@@ -3732,13 +3732,13 @@  discard block
 block discarded – undo
3732 3732
         print "      ]\n";
3733 3733
         print "      computed [\n";
3734 3734
         foreach ($this->_props_computed as $prop => $val) {
3735
-            print '        ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3735
+            print '        '.$prop.': '.preg_replace("/\r\n/", ' ', print_r($val, true));
3736 3736
             print ";\n";
3737 3737
         }
3738 3738
         print "      ]\n";
3739 3739
         print "      cached [\n";
3740 3740
         foreach ($this->_props_used as $prop => $val) {
3741
-            print '        ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3741
+            print '        '.$prop.': '.preg_replace("/\r\n/", ' ', print_r($val, true));
3742 3742
             print ";\n";
3743 3743
         }
3744 3744
         print "      ]\n";
Please login to merge, or discard this patch.
Braces   +11 added lines, -33 removed lines patch added patch discarded remove patch
@@ -1023,22 +1023,14 @@  discard block
 block discarded – undo
1023 1023
             // Legacy support for unitless values, not covered by spec. Might
1024 1024
             // want to restrict this to unitless `0` in the future
1025 1025
             $value = $v;
1026
-        }
1027
-
1028
-        elseif ($unit === "%") {
1026
+        } elseif ($unit === "%") {
1029 1027
             $value = $v / 100 * $ref_size;
1030
-        }
1031
-
1032
-        elseif ($unit === "px") {
1028
+        } elseif ($unit === "px") {
1033 1029
             $dpi = $this->_stylesheet->get_dompdf()->getOptions()->getDpi();
1034 1030
             $value = ($v * 72) / $dpi;
1035
-        }
1036
-
1037
-        elseif ($unit === "pt") {
1031
+        } elseif ($unit === "pt") {
1038 1032
             $value = $v;
1039
-        }
1040
-
1041
-        elseif ($unit === "rem") {
1033
+        } elseif ($unit === "rem") {
1042 1034
             $tree = $this->_stylesheet->get_dompdf()->getTree();
1043 1035
             $root_style = $tree !== null ? $tree->get_root()->get_style() : null;
1044 1036
             $root_font_size = $root_style === null || $root_style === $this
@@ -1052,34 +1044,20 @@  discard block
 block discarded – undo
1052 1044
             if ($root_style === null) {
1053 1045
                 return $value;
1054 1046
             }
1055
-        }
1056
-
1057
-        elseif ($unit === "em") {
1047
+        } elseif ($unit === "em") {
1058 1048
             $value = $v * $font_size;
1059
-        }
1060
-
1061
-        elseif ($unit === "cm") {
1049
+        } elseif ($unit === "cm") {
1062 1050
             $value = $v * 72 / 2.54;
1063
-        }
1064
-
1065
-        elseif ($unit === "mm") {
1051
+        } elseif ($unit === "mm") {
1066 1052
             $value = $v * 72 / 25.4;
1067
-        }
1068
-
1069
-        elseif ($unit === "ex") {
1053
+        } elseif ($unit === "ex") {
1070 1054
             // FIXME: em:ex ratio?
1071 1055
             $value = $v * $font_size / 2;
1072
-        }
1073
-
1074
-        elseif ($unit === "in") {
1056
+        } elseif ($unit === "in") {
1075 1057
             $value = $v * 72;
1076
-        }
1077
-
1078
-        elseif ($unit === "pc") {
1058
+        } elseif ($unit === "pc") {
1079 1059
             $value = $v * 12;
1080
-        }
1081
-
1082
-        else {
1060
+        } else {
1083 1061
             // Invalid or unsupported declaration
1084 1062
             $value = null;
1085 1063
         }
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Css/Color.php 2 patches
Indentation   +324 added lines, -324 removed lines patch added patch discarded remove patch
@@ -14,329 +14,329 @@
 block discarded – undo
14 14
 
15 15
 class Color
16 16
 {
17
-    static $cssColorNames = [
18
-        "aliceblue" => "F0F8FF",
19
-        "antiquewhite" => "FAEBD7",
20
-        "aqua" => "00FFFF",
21
-        "aquamarine" => "7FFFD4",
22
-        "azure" => "F0FFFF",
23
-        "beige" => "F5F5DC",
24
-        "bisque" => "FFE4C4",
25
-        "black" => "000000",
26
-        "blanchedalmond" => "FFEBCD",
27
-        "blue" => "0000FF",
28
-        "blueviolet" => "8A2BE2",
29
-        "brown" => "A52A2A",
30
-        "burlywood" => "DEB887",
31
-        "cadetblue" => "5F9EA0",
32
-        "chartreuse" => "7FFF00",
33
-        "chocolate" => "D2691E",
34
-        "coral" => "FF7F50",
35
-        "cornflowerblue" => "6495ED",
36
-        "cornsilk" => "FFF8DC",
37
-        "crimson" => "DC143C",
38
-        "cyan" => "00FFFF",
39
-        "darkblue" => "00008B",
40
-        "darkcyan" => "008B8B",
41
-        "darkgoldenrod" => "B8860B",
42
-        "darkgray" => "A9A9A9",
43
-        "darkgreen" => "006400",
44
-        "darkgrey" => "A9A9A9",
45
-        "darkkhaki" => "BDB76B",
46
-        "darkmagenta" => "8B008B",
47
-        "darkolivegreen" => "556B2F",
48
-        "darkorange" => "FF8C00",
49
-        "darkorchid" => "9932CC",
50
-        "darkred" => "8B0000",
51
-        "darksalmon" => "E9967A",
52
-        "darkseagreen" => "8FBC8F",
53
-        "darkslateblue" => "483D8B",
54
-        "darkslategray" => "2F4F4F",
55
-        "darkslategrey" => "2F4F4F",
56
-        "darkturquoise" => "00CED1",
57
-        "darkviolet" => "9400D3",
58
-        "deeppink" => "FF1493",
59
-        "deepskyblue" => "00BFFF",
60
-        "dimgray" => "696969",
61
-        "dimgrey" => "696969",
62
-        "dodgerblue" => "1E90FF",
63
-        "firebrick" => "B22222",
64
-        "floralwhite" => "FFFAF0",
65
-        "forestgreen" => "228B22",
66
-        "fuchsia" => "FF00FF",
67
-        "gainsboro" => "DCDCDC",
68
-        "ghostwhite" => "F8F8FF",
69
-        "gold" => "FFD700",
70
-        "goldenrod" => "DAA520",
71
-        "gray" => "808080",
72
-        "green" => "008000",
73
-        "greenyellow" => "ADFF2F",
74
-        "grey" => "808080",
75
-        "honeydew" => "F0FFF0",
76
-        "hotpink" => "FF69B4",
77
-        "indianred" => "CD5C5C",
78
-        "indigo" => "4B0082",
79
-        "ivory" => "FFFFF0",
80
-        "khaki" => "F0E68C",
81
-        "lavender" => "E6E6FA",
82
-        "lavenderblush" => "FFF0F5",
83
-        "lawngreen" => "7CFC00",
84
-        "lemonchiffon" => "FFFACD",
85
-        "lightblue" => "ADD8E6",
86
-        "lightcoral" => "F08080",
87
-        "lightcyan" => "E0FFFF",
88
-        "lightgoldenrodyellow" => "FAFAD2",
89
-        "lightgray" => "D3D3D3",
90
-        "lightgreen" => "90EE90",
91
-        "lightgrey" => "D3D3D3",
92
-        "lightpink" => "FFB6C1",
93
-        "lightsalmon" => "FFA07A",
94
-        "lightseagreen" => "20B2AA",
95
-        "lightskyblue" => "87CEFA",
96
-        "lightslategray" => "778899",
97
-        "lightslategrey" => "778899",
98
-        "lightsteelblue" => "B0C4DE",
99
-        "lightyellow" => "FFFFE0",
100
-        "lime" => "00FF00",
101
-        "limegreen" => "32CD32",
102
-        "linen" => "FAF0E6",
103
-        "magenta" => "FF00FF",
104
-        "maroon" => "800000",
105
-        "mediumaquamarine" => "66CDAA",
106
-        "mediumblue" => "0000CD",
107
-        "mediumorchid" => "BA55D3",
108
-        "mediumpurple" => "9370DB",
109
-        "mediumseagreen" => "3CB371",
110
-        "mediumslateblue" => "7B68EE",
111
-        "mediumspringgreen" => "00FA9A",
112
-        "mediumturquoise" => "48D1CC",
113
-        "mediumvioletred" => "C71585",
114
-        "midnightblue" => "191970",
115
-        "mintcream" => "F5FFFA",
116
-        "mistyrose" => "FFE4E1",
117
-        "moccasin" => "FFE4B5",
118
-        "navajowhite" => "FFDEAD",
119
-        "navy" => "000080",
120
-        "oldlace" => "FDF5E6",
121
-        "olive" => "808000",
122
-        "olivedrab" => "6B8E23",
123
-        "orange" => "FFA500",
124
-        "orangered" => "FF4500",
125
-        "orchid" => "DA70D6",
126
-        "palegoldenrod" => "EEE8AA",
127
-        "palegreen" => "98FB98",
128
-        "paleturquoise" => "AFEEEE",
129
-        "palevioletred" => "DB7093",
130
-        "papayawhip" => "FFEFD5",
131
-        "peachpuff" => "FFDAB9",
132
-        "peru" => "CD853F",
133
-        "pink" => "FFC0CB",
134
-        "plum" => "DDA0DD",
135
-        "powderblue" => "B0E0E6",
136
-        "purple" => "800080",
137
-        "red" => "FF0000",
138
-        "rosybrown" => "BC8F8F",
139
-        "royalblue" => "4169E1",
140
-        "saddlebrown" => "8B4513",
141
-        "salmon" => "FA8072",
142
-        "sandybrown" => "F4A460",
143
-        "seagreen" => "2E8B57",
144
-        "seashell" => "FFF5EE",
145
-        "sienna" => "A0522D",
146
-        "silver" => "C0C0C0",
147
-        "skyblue" => "87CEEB",
148
-        "slateblue" => "6A5ACD",
149
-        "slategray" => "708090",
150
-        "slategrey" => "708090",
151
-        "snow" => "FFFAFA",
152
-        "springgreen" => "00FF7F",
153
-        "steelblue" => "4682B4",
154
-        "tan" => "D2B48C",
155
-        "teal" => "008080",
156
-        "thistle" => "D8BFD8",
157
-        "tomato" => "FF6347",
158
-        "turquoise" => "40E0D0",
159
-        "violet" => "EE82EE",
160
-        "wheat" => "F5DEB3",
161
-        "white" => "FFFFFF",
162
-        "whitesmoke" => "F5F5F5",
163
-        "yellow" => "FFFF00",
164
-        "yellowgreen" => "9ACD32",
165
-    ];
166
-
167
-    /**
168
-     * @param array|string|null $color
169
-     * @return array|string|null
170
-     */
171
-    static function parse($color)
172
-    {
173
-        if ($color === null) {
174
-            return null;
175
-        }
176
-
177
-        if (is_array($color)) {
178
-            // Assume the array has the right format...
179
-            // FIXME: should/could verify this.
180
-            return $color;
181
-        }
182
-
183
-        static $cache = [];
184
-
185
-        $color = strtolower($color);
186
-
187
-        if (isset($cache[$color])) {
188
-            return $cache[$color];
189
-        }
190
-
191
-        if ($color === "transparent") {
192
-            return $cache[$color] = $color;
193
-        }
194
-
195
-        if (isset(self::$cssColorNames[$color])) {
196
-            return $cache[$color] = self::getArray(self::$cssColorNames[$color]);
197
-        }
198
-
199
-        // https://www.w3.org/TR/css-color-4/#hex-notation
200
-        if (mb_substr($color, 0, 1) === "#") {
201
-            $length = mb_strlen($color);
202
-            $alpha = 1.0;
203
-
204
-            // #rgb format
205
-            if ($length === 4) {
206
-                return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]);
207
-            }
208
-
209
-            // #rgba format
210
-            if ($length === 5) {
211
-                if (ctype_xdigit($color[4])) {
212
-                    $alpha = round(hexdec($color[4] . $color[4])/255, 2);
213
-                }
214
-                return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3], $alpha);
215
-            }
216
-
217
-            // #rrggbb format
218
-            if ($length === 7) {
219
-                return $cache[$color] = self::getArray(mb_substr($color, 1, 6));
220
-            }
17
+	static $cssColorNames = [
18
+		"aliceblue" => "F0F8FF",
19
+		"antiquewhite" => "FAEBD7",
20
+		"aqua" => "00FFFF",
21
+		"aquamarine" => "7FFFD4",
22
+		"azure" => "F0FFFF",
23
+		"beige" => "F5F5DC",
24
+		"bisque" => "FFE4C4",
25
+		"black" => "000000",
26
+		"blanchedalmond" => "FFEBCD",
27
+		"blue" => "0000FF",
28
+		"blueviolet" => "8A2BE2",
29
+		"brown" => "A52A2A",
30
+		"burlywood" => "DEB887",
31
+		"cadetblue" => "5F9EA0",
32
+		"chartreuse" => "7FFF00",
33
+		"chocolate" => "D2691E",
34
+		"coral" => "FF7F50",
35
+		"cornflowerblue" => "6495ED",
36
+		"cornsilk" => "FFF8DC",
37
+		"crimson" => "DC143C",
38
+		"cyan" => "00FFFF",
39
+		"darkblue" => "00008B",
40
+		"darkcyan" => "008B8B",
41
+		"darkgoldenrod" => "B8860B",
42
+		"darkgray" => "A9A9A9",
43
+		"darkgreen" => "006400",
44
+		"darkgrey" => "A9A9A9",
45
+		"darkkhaki" => "BDB76B",
46
+		"darkmagenta" => "8B008B",
47
+		"darkolivegreen" => "556B2F",
48
+		"darkorange" => "FF8C00",
49
+		"darkorchid" => "9932CC",
50
+		"darkred" => "8B0000",
51
+		"darksalmon" => "E9967A",
52
+		"darkseagreen" => "8FBC8F",
53
+		"darkslateblue" => "483D8B",
54
+		"darkslategray" => "2F4F4F",
55
+		"darkslategrey" => "2F4F4F",
56
+		"darkturquoise" => "00CED1",
57
+		"darkviolet" => "9400D3",
58
+		"deeppink" => "FF1493",
59
+		"deepskyblue" => "00BFFF",
60
+		"dimgray" => "696969",
61
+		"dimgrey" => "696969",
62
+		"dodgerblue" => "1E90FF",
63
+		"firebrick" => "B22222",
64
+		"floralwhite" => "FFFAF0",
65
+		"forestgreen" => "228B22",
66
+		"fuchsia" => "FF00FF",
67
+		"gainsboro" => "DCDCDC",
68
+		"ghostwhite" => "F8F8FF",
69
+		"gold" => "FFD700",
70
+		"goldenrod" => "DAA520",
71
+		"gray" => "808080",
72
+		"green" => "008000",
73
+		"greenyellow" => "ADFF2F",
74
+		"grey" => "808080",
75
+		"honeydew" => "F0FFF0",
76
+		"hotpink" => "FF69B4",
77
+		"indianred" => "CD5C5C",
78
+		"indigo" => "4B0082",
79
+		"ivory" => "FFFFF0",
80
+		"khaki" => "F0E68C",
81
+		"lavender" => "E6E6FA",
82
+		"lavenderblush" => "FFF0F5",
83
+		"lawngreen" => "7CFC00",
84
+		"lemonchiffon" => "FFFACD",
85
+		"lightblue" => "ADD8E6",
86
+		"lightcoral" => "F08080",
87
+		"lightcyan" => "E0FFFF",
88
+		"lightgoldenrodyellow" => "FAFAD2",
89
+		"lightgray" => "D3D3D3",
90
+		"lightgreen" => "90EE90",
91
+		"lightgrey" => "D3D3D3",
92
+		"lightpink" => "FFB6C1",
93
+		"lightsalmon" => "FFA07A",
94
+		"lightseagreen" => "20B2AA",
95
+		"lightskyblue" => "87CEFA",
96
+		"lightslategray" => "778899",
97
+		"lightslategrey" => "778899",
98
+		"lightsteelblue" => "B0C4DE",
99
+		"lightyellow" => "FFFFE0",
100
+		"lime" => "00FF00",
101
+		"limegreen" => "32CD32",
102
+		"linen" => "FAF0E6",
103
+		"magenta" => "FF00FF",
104
+		"maroon" => "800000",
105
+		"mediumaquamarine" => "66CDAA",
106
+		"mediumblue" => "0000CD",
107
+		"mediumorchid" => "BA55D3",
108
+		"mediumpurple" => "9370DB",
109
+		"mediumseagreen" => "3CB371",
110
+		"mediumslateblue" => "7B68EE",
111
+		"mediumspringgreen" => "00FA9A",
112
+		"mediumturquoise" => "48D1CC",
113
+		"mediumvioletred" => "C71585",
114
+		"midnightblue" => "191970",
115
+		"mintcream" => "F5FFFA",
116
+		"mistyrose" => "FFE4E1",
117
+		"moccasin" => "FFE4B5",
118
+		"navajowhite" => "FFDEAD",
119
+		"navy" => "000080",
120
+		"oldlace" => "FDF5E6",
121
+		"olive" => "808000",
122
+		"olivedrab" => "6B8E23",
123
+		"orange" => "FFA500",
124
+		"orangered" => "FF4500",
125
+		"orchid" => "DA70D6",
126
+		"palegoldenrod" => "EEE8AA",
127
+		"palegreen" => "98FB98",
128
+		"paleturquoise" => "AFEEEE",
129
+		"palevioletred" => "DB7093",
130
+		"papayawhip" => "FFEFD5",
131
+		"peachpuff" => "FFDAB9",
132
+		"peru" => "CD853F",
133
+		"pink" => "FFC0CB",
134
+		"plum" => "DDA0DD",
135
+		"powderblue" => "B0E0E6",
136
+		"purple" => "800080",
137
+		"red" => "FF0000",
138
+		"rosybrown" => "BC8F8F",
139
+		"royalblue" => "4169E1",
140
+		"saddlebrown" => "8B4513",
141
+		"salmon" => "FA8072",
142
+		"sandybrown" => "F4A460",
143
+		"seagreen" => "2E8B57",
144
+		"seashell" => "FFF5EE",
145
+		"sienna" => "A0522D",
146
+		"silver" => "C0C0C0",
147
+		"skyblue" => "87CEEB",
148
+		"slateblue" => "6A5ACD",
149
+		"slategray" => "708090",
150
+		"slategrey" => "708090",
151
+		"snow" => "FFFAFA",
152
+		"springgreen" => "00FF7F",
153
+		"steelblue" => "4682B4",
154
+		"tan" => "D2B48C",
155
+		"teal" => "008080",
156
+		"thistle" => "D8BFD8",
157
+		"tomato" => "FF6347",
158
+		"turquoise" => "40E0D0",
159
+		"violet" => "EE82EE",
160
+		"wheat" => "F5DEB3",
161
+		"white" => "FFFFFF",
162
+		"whitesmoke" => "F5F5F5",
163
+		"yellow" => "FFFF00",
164
+		"yellowgreen" => "9ACD32",
165
+	];
166
+
167
+	/**
168
+	 * @param array|string|null $color
169
+	 * @return array|string|null
170
+	 */
171
+	static function parse($color)
172
+	{
173
+		if ($color === null) {
174
+			return null;
175
+		}
176
+
177
+		if (is_array($color)) {
178
+			// Assume the array has the right format...
179
+			// FIXME: should/could verify this.
180
+			return $color;
181
+		}
182
+
183
+		static $cache = [];
184
+
185
+		$color = strtolower($color);
186
+
187
+		if (isset($cache[$color])) {
188
+			return $cache[$color];
189
+		}
190
+
191
+		if ($color === "transparent") {
192
+			return $cache[$color] = $color;
193
+		}
194
+
195
+		if (isset(self::$cssColorNames[$color])) {
196
+			return $cache[$color] = self::getArray(self::$cssColorNames[$color]);
197
+		}
198
+
199
+		// https://www.w3.org/TR/css-color-4/#hex-notation
200
+		if (mb_substr($color, 0, 1) === "#") {
201
+			$length = mb_strlen($color);
202
+			$alpha = 1.0;
203
+
204
+			// #rgb format
205
+			if ($length === 4) {
206
+				return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]);
207
+			}
208
+
209
+			// #rgba format
210
+			if ($length === 5) {
211
+				if (ctype_xdigit($color[4])) {
212
+					$alpha = round(hexdec($color[4] . $color[4])/255, 2);
213
+				}
214
+				return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3], $alpha);
215
+			}
216
+
217
+			// #rrggbb format
218
+			if ($length === 7) {
219
+				return $cache[$color] = self::getArray(mb_substr($color, 1, 6));
220
+			}
221 221
             
222
-            // #rrggbbaa format
223
-            if ($length === 9) {
224
-                if (ctype_xdigit(mb_substr($color, 7, 2))) {
225
-                    $alpha = round(hexdec(mb_substr($color, 7, 2))/255, 2);
226
-                }
227
-                return $cache[$color] = self::getArray(mb_substr($color, 1, 6), $alpha);
228
-            }
229
-
230
-            return null;
231
-        }
232
-
233
-        // rgb( r g b [/α] ) / rgb( r,g,b[,α] ) format and alias rgba()
234
-        // https://www.w3.org/TR/css-color-4/#rgb-functions
235
-        if (mb_substr($color, 0, 4) === "rgb(" || mb_substr($color, 0, 5) === "rgba(") {
236
-            $i = mb_strpos($color, "(");
237
-            $j = mb_strpos($color, ")");
238
-
239
-            // Bad color value
240
-            if ($i === false || $j === false) {
241
-                return null;
242
-            }
243
-
244
-            $value_decl = trim(mb_substr($color, $i + 1, $j - $i - 1));
245
-
246
-            if (mb_strpos($value_decl, ",") === false) {
247
-                // Space-separated values syntax `r g b` or `r g b / α`
248
-                $parts = preg_split("/\s*\/\s*/", $value_decl);
249
-                $triplet = preg_split("/\s+/", $parts[0]);
250
-                $alpha = $parts[1] ?? 1.0;
251
-            } else {
252
-                // Comma-separated values syntax `r, g, b` or `r, g, b, α`
253
-                $parts = preg_split("/\s*,\s*/", $value_decl);
254
-                $triplet = array_slice($parts, 0, 3);
255
-                $alpha = $parts[3] ?? 1.0;
256
-            }
257
-
258
-            if (count($triplet) !== 3) {
259
-                return null;
260
-            }
261
-
262
-            // Parse alpha value
263
-            if (Helpers::is_percent($alpha)) {
264
-                $alpha = (float) $alpha / 100;
265
-            } else {
266
-                $alpha = (float) $alpha;
267
-            }
268
-
269
-            $alpha = max(0.0, min($alpha, 1.0));
270
-
271
-            foreach ($triplet as &$c) {
272
-                if (Helpers::is_percent($c)) {
273
-                    $c = round((float) $c * 2.55);
274
-                }
275
-            }
276
-
277
-            return $cache[$color] = self::getArray(vsprintf("%02X%02X%02X", $triplet), $alpha);
278
-        }
279
-
280
-        // cmyk( c,m,y,k ) format
281
-        // http://www.w3.org/TR/css3-gcpm/#cmyk-colors
282
-        if (mb_substr($color, 0, 5) === "cmyk(") {
283
-            $i = mb_strpos($color, "(");
284
-            $j = mb_strpos($color, ")");
285
-
286
-            // Bad color value
287
-            if ($i === false || $j === false) {
288
-                return null;
289
-            }
290
-
291
-            $values = explode(",", mb_substr($color, $i + 1, $j - $i - 1));
292
-
293
-            if (count($values) != 4) {
294
-                return null;
295
-            }
296
-
297
-            $values = array_map(function ($c) {
298
-                return min(1.0, max(0.0, floatval(trim($c))));
299
-            }, $values);
300
-
301
-            return $cache[$color] = self::getArray($values);
302
-        }
303
-
304
-        // Invalid or unsupported color format
305
-        return null;
306
-    }
307
-
308
-    /**
309
-     * @param array|string $color
310
-     * @param float $alpha
311
-     * @return array
312
-     */
313
-    static function getArray($color, $alpha = 1.0)
314
-    {
315
-        $c = [null, null, null, null, "alpha" => $alpha, "hex" => null];
316
-
317
-        if (is_array($color)) {
318
-            $c = $color;
319
-            $c["c"] = $c[0];
320
-            $c["m"] = $c[1];
321
-            $c["y"] = $c[2];
322
-            $c["k"] = $c[3];
323
-            $c["alpha"] = $alpha;
324
-            $c["hex"] = "cmyk($c[0],$c[1],$c[2],$c[3])";
325
-        } else {
326
-            if (ctype_xdigit($color) === false || mb_strlen($color) !== 6) {
327
-                // invalid color value ... expected 6-character hex
328
-                return $c;
329
-            }
330
-            $c[0] = hexdec(mb_substr($color, 0, 2)) / 0xff;
331
-            $c[1] = hexdec(mb_substr($color, 2, 2)) / 0xff;
332
-            $c[2] = hexdec(mb_substr($color, 4, 2)) / 0xff;
333
-            $c["r"] = $c[0];
334
-            $c["g"] = $c[1];
335
-            $c["b"] = $c[2];
336
-            $c["alpha"] = $alpha;
337
-            $c["hex"] = sprintf("#%s%02X", $color, round($alpha * 255));
338
-        }
339
-
340
-        return $c;
341
-    }
222
+			// #rrggbbaa format
223
+			if ($length === 9) {
224
+				if (ctype_xdigit(mb_substr($color, 7, 2))) {
225
+					$alpha = round(hexdec(mb_substr($color, 7, 2))/255, 2);
226
+				}
227
+				return $cache[$color] = self::getArray(mb_substr($color, 1, 6), $alpha);
228
+			}
229
+
230
+			return null;
231
+		}
232
+
233
+		// rgb( r g b [/α] ) / rgb( r,g,b[,α] ) format and alias rgba()
234
+		// https://www.w3.org/TR/css-color-4/#rgb-functions
235
+		if (mb_substr($color, 0, 4) === "rgb(" || mb_substr($color, 0, 5) === "rgba(") {
236
+			$i = mb_strpos($color, "(");
237
+			$j = mb_strpos($color, ")");
238
+
239
+			// Bad color value
240
+			if ($i === false || $j === false) {
241
+				return null;
242
+			}
243
+
244
+			$value_decl = trim(mb_substr($color, $i + 1, $j - $i - 1));
245
+
246
+			if (mb_strpos($value_decl, ",") === false) {
247
+				// Space-separated values syntax `r g b` or `r g b / α`
248
+				$parts = preg_split("/\s*\/\s*/", $value_decl);
249
+				$triplet = preg_split("/\s+/", $parts[0]);
250
+				$alpha = $parts[1] ?? 1.0;
251
+			} else {
252
+				// Comma-separated values syntax `r, g, b` or `r, g, b, α`
253
+				$parts = preg_split("/\s*,\s*/", $value_decl);
254
+				$triplet = array_slice($parts, 0, 3);
255
+				$alpha = $parts[3] ?? 1.0;
256
+			}
257
+
258
+			if (count($triplet) !== 3) {
259
+				return null;
260
+			}
261
+
262
+			// Parse alpha value
263
+			if (Helpers::is_percent($alpha)) {
264
+				$alpha = (float) $alpha / 100;
265
+			} else {
266
+				$alpha = (float) $alpha;
267
+			}
268
+
269
+			$alpha = max(0.0, min($alpha, 1.0));
270
+
271
+			foreach ($triplet as &$c) {
272
+				if (Helpers::is_percent($c)) {
273
+					$c = round((float) $c * 2.55);
274
+				}
275
+			}
276
+
277
+			return $cache[$color] = self::getArray(vsprintf("%02X%02X%02X", $triplet), $alpha);
278
+		}
279
+
280
+		// cmyk( c,m,y,k ) format
281
+		// http://www.w3.org/TR/css3-gcpm/#cmyk-colors
282
+		if (mb_substr($color, 0, 5) === "cmyk(") {
283
+			$i = mb_strpos($color, "(");
284
+			$j = mb_strpos($color, ")");
285
+
286
+			// Bad color value
287
+			if ($i === false || $j === false) {
288
+				return null;
289
+			}
290
+
291
+			$values = explode(",", mb_substr($color, $i + 1, $j - $i - 1));
292
+
293
+			if (count($values) != 4) {
294
+				return null;
295
+			}
296
+
297
+			$values = array_map(function ($c) {
298
+				return min(1.0, max(0.0, floatval(trim($c))));
299
+			}, $values);
300
+
301
+			return $cache[$color] = self::getArray($values);
302
+		}
303
+
304
+		// Invalid or unsupported color format
305
+		return null;
306
+	}
307
+
308
+	/**
309
+	 * @param array|string $color
310
+	 * @param float $alpha
311
+	 * @return array
312
+	 */
313
+	static function getArray($color, $alpha = 1.0)
314
+	{
315
+		$c = [null, null, null, null, "alpha" => $alpha, "hex" => null];
316
+
317
+		if (is_array($color)) {
318
+			$c = $color;
319
+			$c["c"] = $c[0];
320
+			$c["m"] = $c[1];
321
+			$c["y"] = $c[2];
322
+			$c["k"] = $c[3];
323
+			$c["alpha"] = $alpha;
324
+			$c["hex"] = "cmyk($c[0],$c[1],$c[2],$c[3])";
325
+		} else {
326
+			if (ctype_xdigit($color) === false || mb_strlen($color) !== 6) {
327
+				// invalid color value ... expected 6-character hex
328
+				return $c;
329
+			}
330
+			$c[0] = hexdec(mb_substr($color, 0, 2)) / 0xff;
331
+			$c[1] = hexdec(mb_substr($color, 2, 2)) / 0xff;
332
+			$c[2] = hexdec(mb_substr($color, 4, 2)) / 0xff;
333
+			$c["r"] = $c[0];
334
+			$c["g"] = $c[1];
335
+			$c["b"] = $c[2];
336
+			$c["alpha"] = $alpha;
337
+			$c["hex"] = sprintf("#%s%02X", $color, round($alpha * 255));
338
+		}
339
+
340
+		return $c;
341
+	}
342 342
 }
Please login to merge, or discard this patch.
Spacing   +5 added lines, -5 removed lines patch added patch discarded remove patch
@@ -203,15 +203,15 @@  discard block
 block discarded – undo
203 203
 
204 204
             // #rgb format
205 205
             if ($length === 4) {
206
-                return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]);
206
+                return $cache[$color] = self::getArray($color[1].$color[1].$color[2].$color[2].$color[3].$color[3]);
207 207
             }
208 208
 
209 209
             // #rgba format
210 210
             if ($length === 5) {
211 211
                 if (ctype_xdigit($color[4])) {
212
-                    $alpha = round(hexdec($color[4] . $color[4])/255, 2);
212
+                    $alpha = round(hexdec($color[4].$color[4]) / 255, 2);
213 213
                 }
214
-                return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3], $alpha);
214
+                return $cache[$color] = self::getArray($color[1].$color[1].$color[2].$color[2].$color[3].$color[3], $alpha);
215 215
             }
216 216
 
217 217
             // #rrggbb format
@@ -222,7 +222,7 @@  discard block
 block discarded – undo
222 222
             // #rrggbbaa format
223 223
             if ($length === 9) {
224 224
                 if (ctype_xdigit(mb_substr($color, 7, 2))) {
225
-                    $alpha = round(hexdec(mb_substr($color, 7, 2))/255, 2);
225
+                    $alpha = round(hexdec(mb_substr($color, 7, 2)) / 255, 2);
226 226
                 }
227 227
                 return $cache[$color] = self::getArray(mb_substr($color, 1, 6), $alpha);
228 228
             }
@@ -294,7 +294,7 @@  discard block
 block discarded – undo
294 294
                 return null;
295 295
             }
296 296
 
297
-            $values = array_map(function ($c) {
297
+            $values = array_map(function($c) {
298 298
                 return min(1.0, max(0.0, floatval(trim($c))));
299 299
             }, $values);
300 300
 
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Frame.php 2 patches
Indentation   +1187 added lines, -1187 removed lines patch added patch discarded remove patch
@@ -25,1196 +25,1196 @@
 block discarded – undo
25 25
  */
26 26
 class Frame
27 27
 {
28
-    const WS_TEXT = 1;
29
-    const WS_SPACE = 2;
30
-
31
-    /**
32
-     * The DOMElement or DOMText object this frame represents
33
-     *
34
-     * @var \DOMElement|\DOMText
35
-     */
36
-    protected $_node;
37
-
38
-    /**
39
-     * Unique identifier for this frame.  Used to reference this frame
40
-     * via the node.
41
-     *
42
-     * @var int
43
-     */
44
-    protected $_id;
45
-
46
-    /**
47
-     * Unique id counter
48
-     *
49
-     * @var int
50
-     */
51
-    public static $ID_COUNTER = 0; /*protected*/
52
-
53
-    /**
54
-     * This frame's calculated style
55
-     *
56
-     * @var Style
57
-     */
58
-    protected $_style;
59
-
60
-    /**
61
-     * This frame's parent in the document tree.
62
-     *
63
-     * @var Frame
64
-     */
65
-    protected $_parent;
66
-
67
-    /**
68
-     * This frame's first child.  All children are handled as a
69
-     * doubly-linked list.
70
-     *
71
-     * @var Frame
72
-     */
73
-    protected $_first_child;
74
-
75
-    /**
76
-     * This frame's last child.
77
-     *
78
-     * @var Frame
79
-     */
80
-    protected $_last_child;
81
-
82
-    /**
83
-     * This frame's previous sibling in the document tree.
84
-     *
85
-     * @var Frame
86
-     */
87
-    protected $_prev_sibling;
88
-
89
-    /**
90
-     * This frame's next sibling in the document tree.
91
-     *
92
-     * @var Frame
93
-     */
94
-    protected $_next_sibling;
95
-
96
-    /**
97
-     * This frame's containing block (used in layout): array(x, y, w, h)
98
-     *
99
-     * @var float[]
100
-     */
101
-    protected $_containing_block;
102
-
103
-    /**
104
-     * Position on the page of the top-left corner of the margin box of
105
-     * this frame: array(x,y)
106
-     *
107
-     * @var float[]
108
-     */
109
-    protected $_position;
110
-
111
-    /**
112
-     * Absolute opacity of this frame
113
-     *
114
-     * @var float
115
-     */
116
-    protected $_opacity;
117
-
118
-    /**
119
-     * This frame's decorator
120
-     *
121
-     * @var FrameDecorator\AbstractFrameDecorator
122
-     */
123
-    protected $_decorator;
124
-
125
-    /**
126
-     * This frame's containing line box
127
-     *
128
-     * @var LineBox|null
129
-     */
130
-    protected $_containing_line;
131
-
132
-    /**
133
-     * @var array
134
-     */
135
-    protected $_is_cache = [];
136
-
137
-    /**
138
-     * Tells whether the frame was already pushed to the next page
139
-     *
140
-     * @var bool
141
-     */
142
-    public $_already_pushed = false;
143
-
144
-    /**
145
-     * @var bool
146
-     */
147
-    public $_float_next_line = false;
148
-
149
-    /**
150
-     * @var int
151
-     */
152
-    public static $_ws_state = self::WS_SPACE;
153
-
154
-    /**
155
-     * Class constructor
156
-     *
157
-     * @param \DOMNode $node the DOMNode this frame represents
158
-     */
159
-    public function __construct(\DOMNode $node)
160
-    {
161
-        $this->_node = $node;
162
-
163
-        $this->_parent = null;
164
-        $this->_first_child = null;
165
-        $this->_last_child = null;
166
-        $this->_prev_sibling = $this->_next_sibling = null;
167
-
168
-        $this->_style = null;
169
-
170
-        $this->_containing_block = [
171
-            "x" => null,
172
-            "y" => null,
173
-            "w" => null,
174
-            "h" => null,
175
-        ];
176
-
177
-        $this->_containing_block[0] =& $this->_containing_block["x"];
178
-        $this->_containing_block[1] =& $this->_containing_block["y"];
179
-        $this->_containing_block[2] =& $this->_containing_block["w"];
180
-        $this->_containing_block[3] =& $this->_containing_block["h"];
181
-
182
-        $this->_position = [
183
-            "x" => null,
184
-            "y" => null,
185
-        ];
186
-
187
-        $this->_position[0] =& $this->_position["x"];
188
-        $this->_position[1] =& $this->_position["y"];
189
-
190
-        $this->_opacity = 1.0;
191
-        $this->_decorator = null;
192
-
193
-        $this->set_id(self::$ID_COUNTER++);
194
-    }
195
-
196
-    /**
197
-     * WIP : preprocessing to remove all the unused whitespace
198
-     */
199
-    protected function ws_trim()
200
-    {
201
-        if ($this->ws_keep()) {
202
-            return;
203
-        }
204
-
205
-        if (self::$_ws_state === self::WS_SPACE) {
206
-            $node = $this->_node;
207
-
208
-            if ($node->nodeName === "#text" && !empty($node->nodeValue)) {
209
-                $node->nodeValue = preg_replace("/[ \t\r\n\f]+/u", " ", trim($node->nodeValue));
210
-                self::$_ws_state = self::WS_TEXT;
211
-            }
212
-        }
213
-    }
214
-
215
-    /**
216
-     * @return bool
217
-     */
218
-    protected function ws_keep()
219
-    {
220
-        $whitespace = $this->get_style()->white_space;
221
-
222
-        return in_array($whitespace, ["pre", "pre-wrap", "pre-line"]);
223
-    }
224
-
225
-    /**
226
-     * @return bool
227
-     */
228
-    protected function ws_is_text()
229
-    {
230
-        $node = $this->get_node();
231
-
232
-        if ($node->nodeName === "img") {
233
-            return true;
234
-        }
235
-
236
-        if (!$this->is_in_flow()) {
237
-            return false;
238
-        }
239
-
240
-        if ($this->is_text_node()) {
241
-            return trim($node->nodeValue) !== "";
242
-        }
243
-
244
-        return true;
245
-    }
246
-
247
-    /**
248
-     * "Destructor": forcibly free all references held by this frame
249
-     *
250
-     * @param bool $recursive if true, call dispose on all children
251
-     */
252
-    public function dispose($recursive = false)
253
-    {
254
-        if ($recursive) {
255
-            while ($child = $this->_first_child) {
256
-                $child->dispose(true);
257
-            }
258
-        }
259
-
260
-        // Remove this frame from the tree
261
-        if ($this->_prev_sibling) {
262
-            $this->_prev_sibling->_next_sibling = $this->_next_sibling;
263
-        }
264
-
265
-        if ($this->_next_sibling) {
266
-            $this->_next_sibling->_prev_sibling = $this->_prev_sibling;
267
-        }
268
-
269
-        if ($this->_parent && $this->_parent->_first_child === $this) {
270
-            $this->_parent->_first_child = $this->_next_sibling;
271
-        }
272
-
273
-        if ($this->_parent && $this->_parent->_last_child === $this) {
274
-            $this->_parent->_last_child = $this->_prev_sibling;
275
-        }
276
-
277
-        if ($this->_parent) {
278
-            $this->_parent->get_node()->removeChild($this->_node);
279
-        }
280
-
281
-        $this->_style = null;
282
-        unset($this->_style);
283
-    }
284
-
285
-    /**
286
-     * Re-initialize the frame
287
-     */
288
-    public function reset()
289
-    {
290
-        $this->_position["x"] = null;
291
-        $this->_position["y"] = null;
292
-
293
-        $this->_containing_block["x"] = null;
294
-        $this->_containing_block["y"] = null;
295
-        $this->_containing_block["w"] = null;
296
-        $this->_containing_block["h"] = null;
297
-
298
-        $this->_style->reset();
299
-    }
300
-
301
-    /**
302
-     * @return \DOMElement|\DOMText
303
-     */
304
-    public function get_node()
305
-    {
306
-        return $this->_node;
307
-    }
308
-
309
-    /**
310
-     * @return int
311
-     */
312
-    public function get_id()
313
-    {
314
-        return $this->_id;
315
-    }
316
-
317
-    /**
318
-     * @return Style
319
-     */
320
-    public function get_style()
321
-    {
322
-        return $this->_style;
323
-    }
324
-
325
-    /**
326
-     * @deprecated
327
-     * @return Style
328
-     */
329
-    public function get_original_style()
330
-    {
331
-        return $this->_style;
332
-    }
333
-
334
-    /**
335
-     * @return Frame
336
-     */
337
-    public function get_parent()
338
-    {
339
-        return $this->_parent;
340
-    }
341
-
342
-    /**
343
-     * @return FrameDecorator\AbstractFrameDecorator
344
-     */
345
-    public function get_decorator()
346
-    {
347
-        return $this->_decorator;
348
-    }
349
-
350
-    /**
351
-     * @return Frame
352
-     */
353
-    public function get_first_child()
354
-    {
355
-        return $this->_first_child;
356
-    }
357
-
358
-    /**
359
-     * @return Frame
360
-     */
361
-    public function get_last_child()
362
-    {
363
-        return $this->_last_child;
364
-    }
365
-
366
-    /**
367
-     * @return Frame
368
-     */
369
-    public function get_prev_sibling()
370
-    {
371
-        return $this->_prev_sibling;
372
-    }
373
-
374
-    /**
375
-     * @return Frame
376
-     */
377
-    public function get_next_sibling()
378
-    {
379
-        return $this->_next_sibling;
380
-    }
381
-
382
-    /**
383
-     * @return FrameListIterator
384
-     */
385
-    public function get_children(): FrameListIterator
386
-    {
387
-        return new FrameListIterator($this);
388
-    }
389
-
390
-    // Layout property accessors
391
-
392
-    /**
393
-     * Containing block dimensions
394
-     *
395
-     * @param string|null $i The key of the wanted containing block's dimension (x, y, w, h)
396
-     *
397
-     * @return float[]|float
398
-     */
399
-    public function get_containing_block($i = null)
400
-    {
401
-        if (isset($i)) {
402
-            return $this->_containing_block[$i];
403
-        }
404
-
405
-        return $this->_containing_block;
406
-    }
407
-
408
-    /**
409
-     * Block position
410
-     *
411
-     * @param string|null $i The key of the wanted position value (x, y)
412
-     *
413
-     * @return float[]|float
414
-     */
415
-    public function get_position($i = null)
416
-    {
417
-        if (isset($i)) {
418
-            return $this->_position[$i];
419
-        }
420
-
421
-        return $this->_position;
422
-    }
423
-
424
-    //........................................................................
425
-
426
-    /**
427
-     * Return the width of the margin box of the frame, in pt.  Meaningless
428
-     * unless the width has been calculated properly.
429
-     *
430
-     * @return float
431
-     */
432
-    public function get_margin_width(): float
433
-    {
434
-        $style = $this->_style;
435
-
436
-        return (float)$style->length_in_pt([
437
-            $style->width,
438
-            $style->margin_left,
439
-            $style->margin_right,
440
-            $style->border_left_width,
441
-            $style->border_right_width,
442
-            $style->padding_left,
443
-            $style->padding_right
444
-        ], $this->_containing_block["w"]);
445
-    }
446
-
447
-    /**
448
-     * Return the height of the margin box of the frame, in pt.  Meaningless
449
-     * unless the height has been calculated properly.
450
-     *
451
-     * @return float
452
-     */
453
-    public function get_margin_height(): float
454
-    {
455
-        $style = $this->_style;
456
-
457
-        return (float)$style->length_in_pt(
458
-            [
459
-                $style->height,
460
-                (float)$style->length_in_pt(
461
-                    [
462
-                        $style->border_top_width,
463
-                        $style->border_bottom_width,
464
-                        $style->margin_top,
465
-                        $style->margin_bottom,
466
-                        $style->padding_top,
467
-                        $style->padding_bottom
468
-                    ], $this->_containing_block["w"]
469
-                )
470
-            ],
471
-            $this->_containing_block["h"]
472
-        );
473
-    }
474
-
475
-    /**
476
-     * Return the content box (x,y,w,h) of the frame.
477
-     *
478
-     * Width and height might be reported as 0 if they have not been resolved
479
-     * yet.
480
-     *
481
-     * @return float[]
482
-     */
483
-    public function get_content_box(): array
484
-    {
485
-        $style = $this->_style;
486
-        $cb = $this->_containing_block;
487
-
488
-        $x = $this->_position["x"] +
489
-            (float)$style->length_in_pt(
490
-                [
491
-                    $style->margin_left,
492
-                    $style->border_left_width,
493
-                    $style->padding_left
494
-                ],
495
-                $cb["w"]
496
-            );
497
-
498
-        $y = $this->_position["y"] +
499
-            (float)$style->length_in_pt(
500
-                [
501
-                    $style->margin_top,
502
-                    $style->border_top_width,
503
-                    $style->padding_top
504
-                ], $cb["w"]
505
-            );
506
-
507
-        $w = (float)$style->length_in_pt($style->width, $cb["w"]);
508
-
509
-        $h = (float)$style->length_in_pt($style->height, $cb["h"]);
510
-
511
-        return [0 => $x, "x" => $x,
512
-            1 => $y, "y" => $y,
513
-            2 => $w, "w" => $w,
514
-            3 => $h, "h" => $h];
515
-    }
516
-
517
-    /**
518
-     * Return the padding box (x,y,w,h) of the frame.
519
-     *
520
-     * Width and height might be reported as 0 if they have not been resolved
521
-     * yet.
522
-     *
523
-     * @return float[]
524
-     */
525
-    public function get_padding_box(): array
526
-    {
527
-        $style = $this->_style;
528
-        $cb = $this->_containing_block;
529
-
530
-        $x = $this->_position["x"] +
531
-            (float)$style->length_in_pt(
532
-                [
533
-                    $style->margin_left,
534
-                    $style->border_left_width
535
-                ],
536
-                $cb["w"]
537
-            );
538
-
539
-        $y = $this->_position["y"] +
540
-            (float)$style->length_in_pt(
541
-                [
542
-                    $style->margin_top,
543
-                    $style->border_top_width
544
-                ],
545
-                $cb["h"]
546
-            );
547
-
548
-        $w = (float)$style->length_in_pt(
549
-                [
550
-                    $style->padding_left,
551
-                    $style->width,
552
-                    $style->padding_right
553
-                ],
554
-                $cb["w"]
555
-            );
556
-
557
-        $h = (float)$style->length_in_pt(
558
-                [
559
-                    $style->padding_top,
560
-                    $style->padding_bottom,
561
-                    $style->length_in_pt($style->height, $cb["h"])
562
-                ],
563
-                $cb["w"]
564
-            );
565
-
566
-        return [0 => $x, "x" => $x,
567
-            1 => $y, "y" => $y,
568
-            2 => $w, "w" => $w,
569
-            3 => $h, "h" => $h];
570
-    }
571
-
572
-    /**
573
-     * Return the border box of the frame.
574
-     *
575
-     * Width and height might be reported as 0 if they have not been resolved
576
-     * yet.
577
-     *
578
-     * @return float[]
579
-     */
580
-    public function get_border_box(): array
581
-    {
582
-        $style = $this->_style;
583
-        $cb = $this->_containing_block;
584
-
585
-        $x = $this->_position["x"] + (float)$style->length_in_pt($style->margin_left, $cb["w"]);
586
-
587
-        $y = $this->_position["y"] + (float)$style->length_in_pt($style->margin_top, $cb["w"]);
588
-
589
-        $w = (float)$style->length_in_pt(
590
-            [
591
-                $style->border_left_width,
592
-                $style->padding_left,
593
-                $style->width,
594
-                $style->padding_right,
595
-                $style->border_right_width
596
-            ],
597
-            $cb["w"]
598
-        );
599
-
600
-        $h = (float)$style->length_in_pt(
601
-            [
602
-                $style->border_top_width,
603
-                $style->padding_top,
604
-                $style->padding_bottom,
605
-                $style->border_bottom_width,
606
-                $style->length_in_pt($style->height, $cb["h"])
607
-            ],
608
-            $cb["w"]
609
-        );
610
-
611
-        return [0 => $x, "x" => $x,
612
-            1 => $y, "y" => $y,
613
-            2 => $w, "w" => $w,
614
-            3 => $h, "h" => $h];
615
-    }
616
-
617
-    /**
618
-     * @param float|null $opacity
619
-     *
620
-     * @return float
621
-     */
622
-    public function get_opacity(?float $opacity = null): float
623
-    {
624
-        if ($opacity !== null) {
625
-            $this->set_opacity($opacity);
626
-        }
627
-
628
-        return $this->_opacity;
629
-    }
630
-
631
-    /**
632
-     * @return LineBox|null
633
-     */
634
-    public function &get_containing_line()
635
-    {
636
-        return $this->_containing_line;
637
-    }
638
-
639
-    //........................................................................
640
-    // Set methods
641
-
642
-    /**
643
-     * @param int $id
644
-     */
645
-    public function set_id($id)
646
-    {
647
-        $this->_id = $id;
648
-
649
-        // We can only set attributes of DOMElement objects (nodeType == 1).
650
-        // Since these are the only objects that we can assign CSS rules to,
651
-        // this shortcoming is okay.
652
-        if ($this->_node->nodeType == XML_ELEMENT_NODE) {
653
-            $this->_node->setAttribute("frame_id", $id);
654
-        }
655
-    }
656
-
657
-    /**
658
-     * @param Style $style
659
-     */
660
-    public function set_style(Style $style): void
661
-    {
662
-        // $style->set_frame($this);
663
-        $this->_style = $style;
664
-    }
665
-
666
-    /**
667
-     * @param FrameDecorator\AbstractFrameDecorator $decorator
668
-     */
669
-    public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator)
670
-    {
671
-        $this->_decorator = $decorator;
672
-    }
673
-
674
-    /**
675
-     * @param float|float[]|null $x
676
-     * @param float|null $y
677
-     * @param float|null $w
678
-     * @param float|null $h
679
-     */
680
-    public function set_containing_block($x = null, $y = null, $w = null, $h = null)
681
-    {
682
-        if (is_array($x)) {
683
-            foreach ($x as $key => $val) {
684
-                $$key = $val;
685
-            }
686
-        }
687
-
688
-        if (is_numeric($x)) {
689
-            $this->_containing_block["x"] = $x;
690
-        }
691
-
692
-        if (is_numeric($y)) {
693
-            $this->_containing_block["y"] = $y;
694
-        }
695
-
696
-        if (is_numeric($w)) {
697
-            $this->_containing_block["w"] = $w;
698
-        }
699
-
700
-        if (is_numeric($h)) {
701
-            $this->_containing_block["h"] = $h;
702
-        }
703
-    }
704
-
705
-    /**
706
-     * @param float|float[]|null $x
707
-     * @param float|null $y
708
-     */
709
-    public function set_position($x = null, $y = null)
710
-    {
711
-        if (is_array($x)) {
712
-            list($x, $y) = [$x["x"], $x["y"]];
713
-        }
714
-
715
-        if (is_numeric($x)) {
716
-            $this->_position["x"] = $x;
717
-        }
718
-
719
-        if (is_numeric($y)) {
720
-            $this->_position["y"] = $y;
721
-        }
722
-    }
723
-
724
-    /**
725
-     * @param float $opacity
726
-     */
727
-    public function set_opacity(float $opacity): void
728
-    {
729
-        $parent = $this->get_parent();
730
-        $base_opacity = $parent && $parent->_opacity !== null ? $parent->_opacity : 1.0;
731
-        $this->_opacity = $base_opacity * $opacity;
732
-    }
733
-
734
-    /**
735
-     * @param LineBox $line
736
-     */
737
-    public function set_containing_line(LineBox $line)
738
-    {
739
-        $this->_containing_line = $line;
740
-    }
741
-
742
-    /**
743
-     * Indicates if the margin height is auto sized
744
-     *
745
-     * @return bool
746
-     */
747
-    public function is_auto_height()
748
-    {
749
-        $style = $this->_style;
750
-
751
-        return in_array(
752
-            "auto",
753
-            [
754
-                $style->height,
755
-                $style->margin_top,
756
-                $style->margin_bottom,
757
-                $style->border_top_width,
758
-                $style->border_bottom_width,
759
-                $style->padding_top,
760
-                $style->padding_bottom,
761
-                $this->_containing_block["h"]
762
-            ],
763
-            true
764
-        );
765
-    }
766
-
767
-    /**
768
-     * Indicates if the margin width is auto sized
769
-     *
770
-     * @return bool
771
-     */
772
-    public function is_auto_width()
773
-    {
774
-        $style = $this->_style;
775
-
776
-        return in_array(
777
-            "auto",
778
-            [
779
-                $style->width,
780
-                $style->margin_left,
781
-                $style->margin_right,
782
-                $style->border_left_width,
783
-                $style->border_right_width,
784
-                $style->padding_left,
785
-                $style->padding_right,
786
-                $this->_containing_block["w"]
787
-            ],
788
-            true
789
-        );
790
-    }
791
-
792
-    /**
793
-     * Tells if the frame is a text node
794
-     *
795
-     * @return bool
796
-     */
797
-    public function is_text_node(): bool
798
-    {
799
-        if (isset($this->_is_cache["text_node"])) {
800
-            return $this->_is_cache["text_node"];
801
-        }
802
-
803
-        return $this->_is_cache["text_node"] = ($this->get_node()->nodeName === "#text");
804
-    }
805
-
806
-    /**
807
-     * @return bool
808
-     */
809
-    public function is_positioned(): bool
810
-    {
811
-        if (isset($this->_is_cache["positioned"])) {
812
-            return $this->_is_cache["positioned"];
813
-        }
814
-
815
-        $position = $this->get_style()->position;
816
-
817
-        return $this->_is_cache["positioned"] = in_array($position, Style::POSITIONED_TYPES, true);
818
-    }
819
-
820
-    /**
821
-     * @return bool
822
-     */
823
-    public function is_absolute(): bool
824
-    {
825
-        if (isset($this->_is_cache["absolute"])) {
826
-            return $this->_is_cache["absolute"];
827
-        }
828
-
829
-        return $this->_is_cache["absolute"] = $this->get_style()->is_absolute();
830
-    }
831
-
832
-    /**
833
-     * Whether the frame is a block container.
834
-     *
835
-     * @return bool
836
-     */
837
-    public function is_block(): bool
838
-    {
839
-        if (isset($this->_is_cache["block"])) {
840
-            return $this->_is_cache["block"];
841
-        }
842
-
843
-        return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::BLOCK_TYPES, true);
844
-    }
845
-
846
-    /**
847
-     * Whether the frame has a block-level display type.
848
-     *
849
-     * @return bool
850
-     */
851
-    public function is_block_level(): bool
852
-    {
853
-        if (isset($this->_is_cache["block_level"])) {
854
-            return $this->_is_cache["block_level"];
855
-        }
856
-
857
-        $display = $this->get_style()->display;
858
-
859
-        return $this->_is_cache["block_level"] = in_array($display, Style::BLOCK_LEVEL_TYPES, true);
860
-    }
861
-
862
-    /**
863
-     * Whether the frame has an inline-level display type.
864
-     *
865
-     * @return bool
866
-     */
867
-    public function is_inline_level(): bool
868
-    {
869
-        if (isset($this->_is_cache["inline_level"])) {
870
-            return $this->_is_cache["inline_level"];
871
-        }
872
-
873
-        $display = $this->get_style()->display;
874
-
875
-        return $this->_is_cache["inline_level"] = in_array($display, Style::INLINE_LEVEL_TYPES, true);
876
-    }
877
-
878
-    /**
879
-     * @return bool
880
-     */
881
-    public function is_in_flow(): bool
882
-    {
883
-        if (isset($this->_is_cache["in_flow"])) {
884
-            return $this->_is_cache["in_flow"];
885
-        }
886
-
887
-        return $this->_is_cache["in_flow"] = $this->get_style()->is_in_flow();
888
-    }
889
-
890
-    /**
891
-     * @return bool
892
-     */
893
-    public function is_pre(): bool
894
-    {
895
-        if (isset($this->_is_cache["pre"])) {
896
-            return $this->_is_cache["pre"];
897
-        }
898
-
899
-        $white_space = $this->get_style()->white_space;
900
-
901
-        return $this->_is_cache["pre"] = in_array($white_space, ["pre", "pre-wrap"], true);
902
-    }
903
-
904
-    /**
905
-     * @return bool
906
-     */
907
-    public function is_table(): bool
908
-    {
909
-        if (isset($this->_is_cache["table"])) {
910
-            return $this->_is_cache["table"];
911
-        }
912
-
913
-        $display = $this->get_style()->display;
914
-
915
-        return $this->_is_cache["table"] = in_array($display, Style::TABLE_TYPES, true);
916
-    }
917
-
918
-
919
-    /**
920
-     * Inserts a new child at the beginning of the Frame
921
-     *
922
-     * @param Frame $child       The new Frame to insert
923
-     * @param bool  $update_node Whether or not to update the DOM
924
-     */
925
-    public function prepend_child(Frame $child, $update_node = true)
926
-    {
927
-        if ($update_node) {
928
-            $this->_node->insertBefore($child->_node, $this->_first_child ? $this->_first_child->_node : null);
929
-        }
930
-
931
-        // Remove the child from its parent
932
-        if ($child->_parent) {
933
-            $child->_parent->remove_child($child, false);
934
-        }
935
-
936
-        $child->_parent = $this;
937
-        $child->_prev_sibling = null;
938
-
939
-        // Handle the first child
940
-        if (!$this->_first_child) {
941
-            $this->_first_child = $child;
942
-            $this->_last_child = $child;
943
-            $child->_next_sibling = null;
944
-        } else {
945
-            $this->_first_child->_prev_sibling = $child;
946
-            $child->_next_sibling = $this->_first_child;
947
-            $this->_first_child = $child;
948
-        }
949
-    }
950
-
951
-    /**
952
-     * Inserts a new child at the end of the Frame
953
-     *
954
-     * @param Frame $child       The new Frame to insert
955
-     * @param bool  $update_node Whether or not to update the DOM
956
-     */
957
-    public function append_child(Frame $child, $update_node = true)
958
-    {
959
-        if ($update_node) {
960
-            $this->_node->appendChild($child->_node);
961
-        }
962
-
963
-        // Remove the child from its parent
964
-        if ($child->_parent) {
965
-            $child->_parent->remove_child($child, false);
966
-        }
967
-
968
-        $child->_parent = $this;
969
-        $decorator = $child->get_decorator();
970
-        // force an update to the cached parent
971
-        if ($decorator !== null) {
972
-            $decorator->get_parent(false);
973
-        }
974
-        $child->_next_sibling = null;
975
-
976
-        // Handle the first child
977
-        if (!$this->_last_child) {
978
-            $this->_first_child = $child;
979
-            $this->_last_child = $child;
980
-            $child->_prev_sibling = null;
981
-        } else {
982
-            $this->_last_child->_next_sibling = $child;
983
-            $child->_prev_sibling = $this->_last_child;
984
-            $this->_last_child = $child;
985
-        }
986
-    }
987
-
988
-    /**
989
-     * Inserts a new child immediately before the specified frame
990
-     *
991
-     * @param Frame $new_child   The new Frame to insert
992
-     * @param Frame $ref         The Frame after the new Frame
993
-     * @param bool  $update_node Whether or not to update the DOM
994
-     *
995
-     * @throws Exception
996
-     */
997
-    public function insert_child_before(Frame $new_child, Frame $ref, $update_node = true)
998
-    {
999
-        if ($ref === $this->_first_child) {
1000
-            $this->prepend_child($new_child, $update_node);
1001
-
1002
-            return;
1003
-        }
1004
-
1005
-        if (is_null($ref)) {
1006
-            $this->append_child($new_child, $update_node);
1007
-
1008
-            return;
1009
-        }
1010
-
1011
-        if ($ref->_parent !== $this) {
1012
-            throw new Exception("Reference child is not a child of this node.");
1013
-        }
1014
-
1015
-        // Update the node
1016
-        if ($update_node) {
1017
-            $this->_node->insertBefore($new_child->_node, $ref->_node);
1018
-        }
1019
-
1020
-        // Remove the child from its parent
1021
-        if ($new_child->_parent) {
1022
-            $new_child->_parent->remove_child($new_child, false);
1023
-        }
1024
-
1025
-        $new_child->_parent = $this;
1026
-        $new_child->_next_sibling = $ref;
1027
-        $new_child->_prev_sibling = $ref->_prev_sibling;
1028
-
1029
-        if ($ref->_prev_sibling) {
1030
-            $ref->_prev_sibling->_next_sibling = $new_child;
1031
-        }
1032
-
1033
-        $ref->_prev_sibling = $new_child;
1034
-    }
1035
-
1036
-    /**
1037
-     * Inserts a new child immediately after the specified frame
1038
-     *
1039
-     * @param Frame $new_child   The new Frame to insert
1040
-     * @param Frame $ref         The Frame before the new Frame
1041
-     * @param bool  $update_node Whether or not to update the DOM
1042
-     *
1043
-     * @throws Exception
1044
-     */
1045
-    public function insert_child_after(Frame $new_child, Frame $ref, $update_node = true)
1046
-    {
1047
-        if ($ref === $this->_last_child) {
1048
-            $this->append_child($new_child, $update_node);
1049
-
1050
-            return;
1051
-        }
1052
-
1053
-        if (is_null($ref)) {
1054
-            $this->prepend_child($new_child, $update_node);
1055
-
1056
-            return;
1057
-        }
1058
-
1059
-        if ($ref->_parent !== $this) {
1060
-            throw new Exception("Reference child is not a child of this node.");
1061
-        }
1062
-
1063
-        // Update the node
1064
-        if ($update_node) {
1065
-            if ($ref->_next_sibling) {
1066
-                $next_node = $ref->_next_sibling->_node;
1067
-                $this->_node->insertBefore($new_child->_node, $next_node);
1068
-            } else {
1069
-                $new_child->_node = $this->_node->appendChild($new_child->_node);
1070
-            }
1071
-        }
1072
-
1073
-        // Remove the child from its parent
1074
-        if ($new_child->_parent) {
1075
-            $new_child->_parent->remove_child($new_child, false);
1076
-        }
1077
-
1078
-        $new_child->_parent = $this;
1079
-        $new_child->_prev_sibling = $ref;
1080
-        $new_child->_next_sibling = $ref->_next_sibling;
1081
-
1082
-        if ($ref->_next_sibling) {
1083
-            $ref->_next_sibling->_prev_sibling = $new_child;
1084
-        }
1085
-
1086
-        $ref->_next_sibling = $new_child;
1087
-    }
1088
-
1089
-    /**
1090
-     * Remove a child frame
1091
-     *
1092
-     * @param Frame $child
1093
-     * @param bool  $update_node Whether or not to remove the DOM node
1094
-     *
1095
-     * @throws Exception
1096
-     * @return Frame The removed child frame
1097
-     */
1098
-    public function remove_child(Frame $child, $update_node = true)
1099
-    {
1100
-        if ($child->_parent !== $this) {
1101
-            throw new Exception("Child not found in this frame");
1102
-        }
1103
-
1104
-        if ($update_node) {
1105
-            $this->_node->removeChild($child->_node);
1106
-        }
1107
-
1108
-        if ($child === $this->_first_child) {
1109
-            $this->_first_child = $child->_next_sibling;
1110
-        }
1111
-
1112
-        if ($child === $this->_last_child) {
1113
-            $this->_last_child = $child->_prev_sibling;
1114
-        }
1115
-
1116
-        if ($child->_prev_sibling) {
1117
-            $child->_prev_sibling->_next_sibling = $child->_next_sibling;
1118
-        }
1119
-
1120
-        if ($child->_next_sibling) {
1121
-            $child->_next_sibling->_prev_sibling = $child->_prev_sibling;
1122
-        }
1123
-
1124
-        $child->_next_sibling = null;
1125
-        $child->_prev_sibling = null;
1126
-        $child->_parent = null;
1127
-
1128
-        return $child;
1129
-    }
1130
-
1131
-    //........................................................................
1132
-
1133
-    // Debugging function:
1134
-    /**
1135
-     * @return string
1136
-     */
1137
-    public function __toString()
1138
-    {
1139
-        // Skip empty text frames
28
+	const WS_TEXT = 1;
29
+	const WS_SPACE = 2;
30
+
31
+	/**
32
+	 * The DOMElement or DOMText object this frame represents
33
+	 *
34
+	 * @var \DOMElement|\DOMText
35
+	 */
36
+	protected $_node;
37
+
38
+	/**
39
+	 * Unique identifier for this frame.  Used to reference this frame
40
+	 * via the node.
41
+	 *
42
+	 * @var int
43
+	 */
44
+	protected $_id;
45
+
46
+	/**
47
+	 * Unique id counter
48
+	 *
49
+	 * @var int
50
+	 */
51
+	public static $ID_COUNTER = 0; /*protected*/
52
+
53
+	/**
54
+	 * This frame's calculated style
55
+	 *
56
+	 * @var Style
57
+	 */
58
+	protected $_style;
59
+
60
+	/**
61
+	 * This frame's parent in the document tree.
62
+	 *
63
+	 * @var Frame
64
+	 */
65
+	protected $_parent;
66
+
67
+	/**
68
+	 * This frame's first child.  All children are handled as a
69
+	 * doubly-linked list.
70
+	 *
71
+	 * @var Frame
72
+	 */
73
+	protected $_first_child;
74
+
75
+	/**
76
+	 * This frame's last child.
77
+	 *
78
+	 * @var Frame
79
+	 */
80
+	protected $_last_child;
81
+
82
+	/**
83
+	 * This frame's previous sibling in the document tree.
84
+	 *
85
+	 * @var Frame
86
+	 */
87
+	protected $_prev_sibling;
88
+
89
+	/**
90
+	 * This frame's next sibling in the document tree.
91
+	 *
92
+	 * @var Frame
93
+	 */
94
+	protected $_next_sibling;
95
+
96
+	/**
97
+	 * This frame's containing block (used in layout): array(x, y, w, h)
98
+	 *
99
+	 * @var float[]
100
+	 */
101
+	protected $_containing_block;
102
+
103
+	/**
104
+	 * Position on the page of the top-left corner of the margin box of
105
+	 * this frame: array(x,y)
106
+	 *
107
+	 * @var float[]
108
+	 */
109
+	protected $_position;
110
+
111
+	/**
112
+	 * Absolute opacity of this frame
113
+	 *
114
+	 * @var float
115
+	 */
116
+	protected $_opacity;
117
+
118
+	/**
119
+	 * This frame's decorator
120
+	 *
121
+	 * @var FrameDecorator\AbstractFrameDecorator
122
+	 */
123
+	protected $_decorator;
124
+
125
+	/**
126
+	 * This frame's containing line box
127
+	 *
128
+	 * @var LineBox|null
129
+	 */
130
+	protected $_containing_line;
131
+
132
+	/**
133
+	 * @var array
134
+	 */
135
+	protected $_is_cache = [];
136
+
137
+	/**
138
+	 * Tells whether the frame was already pushed to the next page
139
+	 *
140
+	 * @var bool
141
+	 */
142
+	public $_already_pushed = false;
143
+
144
+	/**
145
+	 * @var bool
146
+	 */
147
+	public $_float_next_line = false;
148
+
149
+	/**
150
+	 * @var int
151
+	 */
152
+	public static $_ws_state = self::WS_SPACE;
153
+
154
+	/**
155
+	 * Class constructor
156
+	 *
157
+	 * @param \DOMNode $node the DOMNode this frame represents
158
+	 */
159
+	public function __construct(\DOMNode $node)
160
+	{
161
+		$this->_node = $node;
162
+
163
+		$this->_parent = null;
164
+		$this->_first_child = null;
165
+		$this->_last_child = null;
166
+		$this->_prev_sibling = $this->_next_sibling = null;
167
+
168
+		$this->_style = null;
169
+
170
+		$this->_containing_block = [
171
+			"x" => null,
172
+			"y" => null,
173
+			"w" => null,
174
+			"h" => null,
175
+		];
176
+
177
+		$this->_containing_block[0] =& $this->_containing_block["x"];
178
+		$this->_containing_block[1] =& $this->_containing_block["y"];
179
+		$this->_containing_block[2] =& $this->_containing_block["w"];
180
+		$this->_containing_block[3] =& $this->_containing_block["h"];
181
+
182
+		$this->_position = [
183
+			"x" => null,
184
+			"y" => null,
185
+		];
186
+
187
+		$this->_position[0] =& $this->_position["x"];
188
+		$this->_position[1] =& $this->_position["y"];
189
+
190
+		$this->_opacity = 1.0;
191
+		$this->_decorator = null;
192
+
193
+		$this->set_id(self::$ID_COUNTER++);
194
+	}
195
+
196
+	/**
197
+	 * WIP : preprocessing to remove all the unused whitespace
198
+	 */
199
+	protected function ws_trim()
200
+	{
201
+		if ($this->ws_keep()) {
202
+			return;
203
+		}
204
+
205
+		if (self::$_ws_state === self::WS_SPACE) {
206
+			$node = $this->_node;
207
+
208
+			if ($node->nodeName === "#text" && !empty($node->nodeValue)) {
209
+				$node->nodeValue = preg_replace("/[ \t\r\n\f]+/u", " ", trim($node->nodeValue));
210
+				self::$_ws_state = self::WS_TEXT;
211
+			}
212
+		}
213
+	}
214
+
215
+	/**
216
+	 * @return bool
217
+	 */
218
+	protected function ws_keep()
219
+	{
220
+		$whitespace = $this->get_style()->white_space;
221
+
222
+		return in_array($whitespace, ["pre", "pre-wrap", "pre-line"]);
223
+	}
224
+
225
+	/**
226
+	 * @return bool
227
+	 */
228
+	protected function ws_is_text()
229
+	{
230
+		$node = $this->get_node();
231
+
232
+		if ($node->nodeName === "img") {
233
+			return true;
234
+		}
235
+
236
+		if (!$this->is_in_flow()) {
237
+			return false;
238
+		}
239
+
240
+		if ($this->is_text_node()) {
241
+			return trim($node->nodeValue) !== "";
242
+		}
243
+
244
+		return true;
245
+	}
246
+
247
+	/**
248
+	 * "Destructor": forcibly free all references held by this frame
249
+	 *
250
+	 * @param bool $recursive if true, call dispose on all children
251
+	 */
252
+	public function dispose($recursive = false)
253
+	{
254
+		if ($recursive) {
255
+			while ($child = $this->_first_child) {
256
+				$child->dispose(true);
257
+			}
258
+		}
259
+
260
+		// Remove this frame from the tree
261
+		if ($this->_prev_sibling) {
262
+			$this->_prev_sibling->_next_sibling = $this->_next_sibling;
263
+		}
264
+
265
+		if ($this->_next_sibling) {
266
+			$this->_next_sibling->_prev_sibling = $this->_prev_sibling;
267
+		}
268
+
269
+		if ($this->_parent && $this->_parent->_first_child === $this) {
270
+			$this->_parent->_first_child = $this->_next_sibling;
271
+		}
272
+
273
+		if ($this->_parent && $this->_parent->_last_child === $this) {
274
+			$this->_parent->_last_child = $this->_prev_sibling;
275
+		}
276
+
277
+		if ($this->_parent) {
278
+			$this->_parent->get_node()->removeChild($this->_node);
279
+		}
280
+
281
+		$this->_style = null;
282
+		unset($this->_style);
283
+	}
284
+
285
+	/**
286
+	 * Re-initialize the frame
287
+	 */
288
+	public function reset()
289
+	{
290
+		$this->_position["x"] = null;
291
+		$this->_position["y"] = null;
292
+
293
+		$this->_containing_block["x"] = null;
294
+		$this->_containing_block["y"] = null;
295
+		$this->_containing_block["w"] = null;
296
+		$this->_containing_block["h"] = null;
297
+
298
+		$this->_style->reset();
299
+	}
300
+
301
+	/**
302
+	 * @return \DOMElement|\DOMText
303
+	 */
304
+	public function get_node()
305
+	{
306
+		return $this->_node;
307
+	}
308
+
309
+	/**
310
+	 * @return int
311
+	 */
312
+	public function get_id()
313
+	{
314
+		return $this->_id;
315
+	}
316
+
317
+	/**
318
+	 * @return Style
319
+	 */
320
+	public function get_style()
321
+	{
322
+		return $this->_style;
323
+	}
324
+
325
+	/**
326
+	 * @deprecated
327
+	 * @return Style
328
+	 */
329
+	public function get_original_style()
330
+	{
331
+		return $this->_style;
332
+	}
333
+
334
+	/**
335
+	 * @return Frame
336
+	 */
337
+	public function get_parent()
338
+	{
339
+		return $this->_parent;
340
+	}
341
+
342
+	/**
343
+	 * @return FrameDecorator\AbstractFrameDecorator
344
+	 */
345
+	public function get_decorator()
346
+	{
347
+		return $this->_decorator;
348
+	}
349
+
350
+	/**
351
+	 * @return Frame
352
+	 */
353
+	public function get_first_child()
354
+	{
355
+		return $this->_first_child;
356
+	}
357
+
358
+	/**
359
+	 * @return Frame
360
+	 */
361
+	public function get_last_child()
362
+	{
363
+		return $this->_last_child;
364
+	}
365
+
366
+	/**
367
+	 * @return Frame
368
+	 */
369
+	public function get_prev_sibling()
370
+	{
371
+		return $this->_prev_sibling;
372
+	}
373
+
374
+	/**
375
+	 * @return Frame
376
+	 */
377
+	public function get_next_sibling()
378
+	{
379
+		return $this->_next_sibling;
380
+	}
381
+
382
+	/**
383
+	 * @return FrameListIterator
384
+	 */
385
+	public function get_children(): FrameListIterator
386
+	{
387
+		return new FrameListIterator($this);
388
+	}
389
+
390
+	// Layout property accessors
391
+
392
+	/**
393
+	 * Containing block dimensions
394
+	 *
395
+	 * @param string|null $i The key of the wanted containing block's dimension (x, y, w, h)
396
+	 *
397
+	 * @return float[]|float
398
+	 */
399
+	public function get_containing_block($i = null)
400
+	{
401
+		if (isset($i)) {
402
+			return $this->_containing_block[$i];
403
+		}
404
+
405
+		return $this->_containing_block;
406
+	}
407
+
408
+	/**
409
+	 * Block position
410
+	 *
411
+	 * @param string|null $i The key of the wanted position value (x, y)
412
+	 *
413
+	 * @return float[]|float
414
+	 */
415
+	public function get_position($i = null)
416
+	{
417
+		if (isset($i)) {
418
+			return $this->_position[$i];
419
+		}
420
+
421
+		return $this->_position;
422
+	}
423
+
424
+	//........................................................................
425
+
426
+	/**
427
+	 * Return the width of the margin box of the frame, in pt.  Meaningless
428
+	 * unless the width has been calculated properly.
429
+	 *
430
+	 * @return float
431
+	 */
432
+	public function get_margin_width(): float
433
+	{
434
+		$style = $this->_style;
435
+
436
+		return (float)$style->length_in_pt([
437
+			$style->width,
438
+			$style->margin_left,
439
+			$style->margin_right,
440
+			$style->border_left_width,
441
+			$style->border_right_width,
442
+			$style->padding_left,
443
+			$style->padding_right
444
+		], $this->_containing_block["w"]);
445
+	}
446
+
447
+	/**
448
+	 * Return the height of the margin box of the frame, in pt.  Meaningless
449
+	 * unless the height has been calculated properly.
450
+	 *
451
+	 * @return float
452
+	 */
453
+	public function get_margin_height(): float
454
+	{
455
+		$style = $this->_style;
456
+
457
+		return (float)$style->length_in_pt(
458
+			[
459
+				$style->height,
460
+				(float)$style->length_in_pt(
461
+					[
462
+						$style->border_top_width,
463
+						$style->border_bottom_width,
464
+						$style->margin_top,
465
+						$style->margin_bottom,
466
+						$style->padding_top,
467
+						$style->padding_bottom
468
+					], $this->_containing_block["w"]
469
+				)
470
+			],
471
+			$this->_containing_block["h"]
472
+		);
473
+	}
474
+
475
+	/**
476
+	 * Return the content box (x,y,w,h) of the frame.
477
+	 *
478
+	 * Width and height might be reported as 0 if they have not been resolved
479
+	 * yet.
480
+	 *
481
+	 * @return float[]
482
+	 */
483
+	public function get_content_box(): array
484
+	{
485
+		$style = $this->_style;
486
+		$cb = $this->_containing_block;
487
+
488
+		$x = $this->_position["x"] +
489
+			(float)$style->length_in_pt(
490
+				[
491
+					$style->margin_left,
492
+					$style->border_left_width,
493
+					$style->padding_left
494
+				],
495
+				$cb["w"]
496
+			);
497
+
498
+		$y = $this->_position["y"] +
499
+			(float)$style->length_in_pt(
500
+				[
501
+					$style->margin_top,
502
+					$style->border_top_width,
503
+					$style->padding_top
504
+				], $cb["w"]
505
+			);
506
+
507
+		$w = (float)$style->length_in_pt($style->width, $cb["w"]);
508
+
509
+		$h = (float)$style->length_in_pt($style->height, $cb["h"]);
510
+
511
+		return [0 => $x, "x" => $x,
512
+			1 => $y, "y" => $y,
513
+			2 => $w, "w" => $w,
514
+			3 => $h, "h" => $h];
515
+	}
516
+
517
+	/**
518
+	 * Return the padding box (x,y,w,h) of the frame.
519
+	 *
520
+	 * Width and height might be reported as 0 if they have not been resolved
521
+	 * yet.
522
+	 *
523
+	 * @return float[]
524
+	 */
525
+	public function get_padding_box(): array
526
+	{
527
+		$style = $this->_style;
528
+		$cb = $this->_containing_block;
529
+
530
+		$x = $this->_position["x"] +
531
+			(float)$style->length_in_pt(
532
+				[
533
+					$style->margin_left,
534
+					$style->border_left_width
535
+				],
536
+				$cb["w"]
537
+			);
538
+
539
+		$y = $this->_position["y"] +
540
+			(float)$style->length_in_pt(
541
+				[
542
+					$style->margin_top,
543
+					$style->border_top_width
544
+				],
545
+				$cb["h"]
546
+			);
547
+
548
+		$w = (float)$style->length_in_pt(
549
+				[
550
+					$style->padding_left,
551
+					$style->width,
552
+					$style->padding_right
553
+				],
554
+				$cb["w"]
555
+			);
556
+
557
+		$h = (float)$style->length_in_pt(
558
+				[
559
+					$style->padding_top,
560
+					$style->padding_bottom,
561
+					$style->length_in_pt($style->height, $cb["h"])
562
+				],
563
+				$cb["w"]
564
+			);
565
+
566
+		return [0 => $x, "x" => $x,
567
+			1 => $y, "y" => $y,
568
+			2 => $w, "w" => $w,
569
+			3 => $h, "h" => $h];
570
+	}
571
+
572
+	/**
573
+	 * Return the border box of the frame.
574
+	 *
575
+	 * Width and height might be reported as 0 if they have not been resolved
576
+	 * yet.
577
+	 *
578
+	 * @return float[]
579
+	 */
580
+	public function get_border_box(): array
581
+	{
582
+		$style = $this->_style;
583
+		$cb = $this->_containing_block;
584
+
585
+		$x = $this->_position["x"] + (float)$style->length_in_pt($style->margin_left, $cb["w"]);
586
+
587
+		$y = $this->_position["y"] + (float)$style->length_in_pt($style->margin_top, $cb["w"]);
588
+
589
+		$w = (float)$style->length_in_pt(
590
+			[
591
+				$style->border_left_width,
592
+				$style->padding_left,
593
+				$style->width,
594
+				$style->padding_right,
595
+				$style->border_right_width
596
+			],
597
+			$cb["w"]
598
+		);
599
+
600
+		$h = (float)$style->length_in_pt(
601
+			[
602
+				$style->border_top_width,
603
+				$style->padding_top,
604
+				$style->padding_bottom,
605
+				$style->border_bottom_width,
606
+				$style->length_in_pt($style->height, $cb["h"])
607
+			],
608
+			$cb["w"]
609
+		);
610
+
611
+		return [0 => $x, "x" => $x,
612
+			1 => $y, "y" => $y,
613
+			2 => $w, "w" => $w,
614
+			3 => $h, "h" => $h];
615
+	}
616
+
617
+	/**
618
+	 * @param float|null $opacity
619
+	 *
620
+	 * @return float
621
+	 */
622
+	public function get_opacity(?float $opacity = null): float
623
+	{
624
+		if ($opacity !== null) {
625
+			$this->set_opacity($opacity);
626
+		}
627
+
628
+		return $this->_opacity;
629
+	}
630
+
631
+	/**
632
+	 * @return LineBox|null
633
+	 */
634
+	public function &get_containing_line()
635
+	{
636
+		return $this->_containing_line;
637
+	}
638
+
639
+	//........................................................................
640
+	// Set methods
641
+
642
+	/**
643
+	 * @param int $id
644
+	 */
645
+	public function set_id($id)
646
+	{
647
+		$this->_id = $id;
648
+
649
+		// We can only set attributes of DOMElement objects (nodeType == 1).
650
+		// Since these are the only objects that we can assign CSS rules to,
651
+		// this shortcoming is okay.
652
+		if ($this->_node->nodeType == XML_ELEMENT_NODE) {
653
+			$this->_node->setAttribute("frame_id", $id);
654
+		}
655
+	}
656
+
657
+	/**
658
+	 * @param Style $style
659
+	 */
660
+	public function set_style(Style $style): void
661
+	{
662
+		// $style->set_frame($this);
663
+		$this->_style = $style;
664
+	}
665
+
666
+	/**
667
+	 * @param FrameDecorator\AbstractFrameDecorator $decorator
668
+	 */
669
+	public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator)
670
+	{
671
+		$this->_decorator = $decorator;
672
+	}
673
+
674
+	/**
675
+	 * @param float|float[]|null $x
676
+	 * @param float|null $y
677
+	 * @param float|null $w
678
+	 * @param float|null $h
679
+	 */
680
+	public function set_containing_block($x = null, $y = null, $w = null, $h = null)
681
+	{
682
+		if (is_array($x)) {
683
+			foreach ($x as $key => $val) {
684
+				$$key = $val;
685
+			}
686
+		}
687
+
688
+		if (is_numeric($x)) {
689
+			$this->_containing_block["x"] = $x;
690
+		}
691
+
692
+		if (is_numeric($y)) {
693
+			$this->_containing_block["y"] = $y;
694
+		}
695
+
696
+		if (is_numeric($w)) {
697
+			$this->_containing_block["w"] = $w;
698
+		}
699
+
700
+		if (is_numeric($h)) {
701
+			$this->_containing_block["h"] = $h;
702
+		}
703
+	}
704
+
705
+	/**
706
+	 * @param float|float[]|null $x
707
+	 * @param float|null $y
708
+	 */
709
+	public function set_position($x = null, $y = null)
710
+	{
711
+		if (is_array($x)) {
712
+			list($x, $y) = [$x["x"], $x["y"]];
713
+		}
714
+
715
+		if (is_numeric($x)) {
716
+			$this->_position["x"] = $x;
717
+		}
718
+
719
+		if (is_numeric($y)) {
720
+			$this->_position["y"] = $y;
721
+		}
722
+	}
723
+
724
+	/**
725
+	 * @param float $opacity
726
+	 */
727
+	public function set_opacity(float $opacity): void
728
+	{
729
+		$parent = $this->get_parent();
730
+		$base_opacity = $parent && $parent->_opacity !== null ? $parent->_opacity : 1.0;
731
+		$this->_opacity = $base_opacity * $opacity;
732
+	}
733
+
734
+	/**
735
+	 * @param LineBox $line
736
+	 */
737
+	public function set_containing_line(LineBox $line)
738
+	{
739
+		$this->_containing_line = $line;
740
+	}
741
+
742
+	/**
743
+	 * Indicates if the margin height is auto sized
744
+	 *
745
+	 * @return bool
746
+	 */
747
+	public function is_auto_height()
748
+	{
749
+		$style = $this->_style;
750
+
751
+		return in_array(
752
+			"auto",
753
+			[
754
+				$style->height,
755
+				$style->margin_top,
756
+				$style->margin_bottom,
757
+				$style->border_top_width,
758
+				$style->border_bottom_width,
759
+				$style->padding_top,
760
+				$style->padding_bottom,
761
+				$this->_containing_block["h"]
762
+			],
763
+			true
764
+		);
765
+	}
766
+
767
+	/**
768
+	 * Indicates if the margin width is auto sized
769
+	 *
770
+	 * @return bool
771
+	 */
772
+	public function is_auto_width()
773
+	{
774
+		$style = $this->_style;
775
+
776
+		return in_array(
777
+			"auto",
778
+			[
779
+				$style->width,
780
+				$style->margin_left,
781
+				$style->margin_right,
782
+				$style->border_left_width,
783
+				$style->border_right_width,
784
+				$style->padding_left,
785
+				$style->padding_right,
786
+				$this->_containing_block["w"]
787
+			],
788
+			true
789
+		);
790
+	}
791
+
792
+	/**
793
+	 * Tells if the frame is a text node
794
+	 *
795
+	 * @return bool
796
+	 */
797
+	public function is_text_node(): bool
798
+	{
799
+		if (isset($this->_is_cache["text_node"])) {
800
+			return $this->_is_cache["text_node"];
801
+		}
802
+
803
+		return $this->_is_cache["text_node"] = ($this->get_node()->nodeName === "#text");
804
+	}
805
+
806
+	/**
807
+	 * @return bool
808
+	 */
809
+	public function is_positioned(): bool
810
+	{
811
+		if (isset($this->_is_cache["positioned"])) {
812
+			return $this->_is_cache["positioned"];
813
+		}
814
+
815
+		$position = $this->get_style()->position;
816
+
817
+		return $this->_is_cache["positioned"] = in_array($position, Style::POSITIONED_TYPES, true);
818
+	}
819
+
820
+	/**
821
+	 * @return bool
822
+	 */
823
+	public function is_absolute(): bool
824
+	{
825
+		if (isset($this->_is_cache["absolute"])) {
826
+			return $this->_is_cache["absolute"];
827
+		}
828
+
829
+		return $this->_is_cache["absolute"] = $this->get_style()->is_absolute();
830
+	}
831
+
832
+	/**
833
+	 * Whether the frame is a block container.
834
+	 *
835
+	 * @return bool
836
+	 */
837
+	public function is_block(): bool
838
+	{
839
+		if (isset($this->_is_cache["block"])) {
840
+			return $this->_is_cache["block"];
841
+		}
842
+
843
+		return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::BLOCK_TYPES, true);
844
+	}
845
+
846
+	/**
847
+	 * Whether the frame has a block-level display type.
848
+	 *
849
+	 * @return bool
850
+	 */
851
+	public function is_block_level(): bool
852
+	{
853
+		if (isset($this->_is_cache["block_level"])) {
854
+			return $this->_is_cache["block_level"];
855
+		}
856
+
857
+		$display = $this->get_style()->display;
858
+
859
+		return $this->_is_cache["block_level"] = in_array($display, Style::BLOCK_LEVEL_TYPES, true);
860
+	}
861
+
862
+	/**
863
+	 * Whether the frame has an inline-level display type.
864
+	 *
865
+	 * @return bool
866
+	 */
867
+	public function is_inline_level(): bool
868
+	{
869
+		if (isset($this->_is_cache["inline_level"])) {
870
+			return $this->_is_cache["inline_level"];
871
+		}
872
+
873
+		$display = $this->get_style()->display;
874
+
875
+		return $this->_is_cache["inline_level"] = in_array($display, Style::INLINE_LEVEL_TYPES, true);
876
+	}
877
+
878
+	/**
879
+	 * @return bool
880
+	 */
881
+	public function is_in_flow(): bool
882
+	{
883
+		if (isset($this->_is_cache["in_flow"])) {
884
+			return $this->_is_cache["in_flow"];
885
+		}
886
+
887
+		return $this->_is_cache["in_flow"] = $this->get_style()->is_in_flow();
888
+	}
889
+
890
+	/**
891
+	 * @return bool
892
+	 */
893
+	public function is_pre(): bool
894
+	{
895
+		if (isset($this->_is_cache["pre"])) {
896
+			return $this->_is_cache["pre"];
897
+		}
898
+
899
+		$white_space = $this->get_style()->white_space;
900
+
901
+		return $this->_is_cache["pre"] = in_array($white_space, ["pre", "pre-wrap"], true);
902
+	}
903
+
904
+	/**
905
+	 * @return bool
906
+	 */
907
+	public function is_table(): bool
908
+	{
909
+		if (isset($this->_is_cache["table"])) {
910
+			return $this->_is_cache["table"];
911
+		}
912
+
913
+		$display = $this->get_style()->display;
914
+
915
+		return $this->_is_cache["table"] = in_array($display, Style::TABLE_TYPES, true);
916
+	}
917
+
918
+
919
+	/**
920
+	 * Inserts a new child at the beginning of the Frame
921
+	 *
922
+	 * @param Frame $child       The new Frame to insert
923
+	 * @param bool  $update_node Whether or not to update the DOM
924
+	 */
925
+	public function prepend_child(Frame $child, $update_node = true)
926
+	{
927
+		if ($update_node) {
928
+			$this->_node->insertBefore($child->_node, $this->_first_child ? $this->_first_child->_node : null);
929
+		}
930
+
931
+		// Remove the child from its parent
932
+		if ($child->_parent) {
933
+			$child->_parent->remove_child($child, false);
934
+		}
935
+
936
+		$child->_parent = $this;
937
+		$child->_prev_sibling = null;
938
+
939
+		// Handle the first child
940
+		if (!$this->_first_child) {
941
+			$this->_first_child = $child;
942
+			$this->_last_child = $child;
943
+			$child->_next_sibling = null;
944
+		} else {
945
+			$this->_first_child->_prev_sibling = $child;
946
+			$child->_next_sibling = $this->_first_child;
947
+			$this->_first_child = $child;
948
+		}
949
+	}
950
+
951
+	/**
952
+	 * Inserts a new child at the end of the Frame
953
+	 *
954
+	 * @param Frame $child       The new Frame to insert
955
+	 * @param bool  $update_node Whether or not to update the DOM
956
+	 */
957
+	public function append_child(Frame $child, $update_node = true)
958
+	{
959
+		if ($update_node) {
960
+			$this->_node->appendChild($child->_node);
961
+		}
962
+
963
+		// Remove the child from its parent
964
+		if ($child->_parent) {
965
+			$child->_parent->remove_child($child, false);
966
+		}
967
+
968
+		$child->_parent = $this;
969
+		$decorator = $child->get_decorator();
970
+		// force an update to the cached parent
971
+		if ($decorator !== null) {
972
+			$decorator->get_parent(false);
973
+		}
974
+		$child->_next_sibling = null;
975
+
976
+		// Handle the first child
977
+		if (!$this->_last_child) {
978
+			$this->_first_child = $child;
979
+			$this->_last_child = $child;
980
+			$child->_prev_sibling = null;
981
+		} else {
982
+			$this->_last_child->_next_sibling = $child;
983
+			$child->_prev_sibling = $this->_last_child;
984
+			$this->_last_child = $child;
985
+		}
986
+	}
987
+
988
+	/**
989
+	 * Inserts a new child immediately before the specified frame
990
+	 *
991
+	 * @param Frame $new_child   The new Frame to insert
992
+	 * @param Frame $ref         The Frame after the new Frame
993
+	 * @param bool  $update_node Whether or not to update the DOM
994
+	 *
995
+	 * @throws Exception
996
+	 */
997
+	public function insert_child_before(Frame $new_child, Frame $ref, $update_node = true)
998
+	{
999
+		if ($ref === $this->_first_child) {
1000
+			$this->prepend_child($new_child, $update_node);
1001
+
1002
+			return;
1003
+		}
1004
+
1005
+		if (is_null($ref)) {
1006
+			$this->append_child($new_child, $update_node);
1007
+
1008
+			return;
1009
+		}
1010
+
1011
+		if ($ref->_parent !== $this) {
1012
+			throw new Exception("Reference child is not a child of this node.");
1013
+		}
1014
+
1015
+		// Update the node
1016
+		if ($update_node) {
1017
+			$this->_node->insertBefore($new_child->_node, $ref->_node);
1018
+		}
1019
+
1020
+		// Remove the child from its parent
1021
+		if ($new_child->_parent) {
1022
+			$new_child->_parent->remove_child($new_child, false);
1023
+		}
1024
+
1025
+		$new_child->_parent = $this;
1026
+		$new_child->_next_sibling = $ref;
1027
+		$new_child->_prev_sibling = $ref->_prev_sibling;
1028
+
1029
+		if ($ref->_prev_sibling) {
1030
+			$ref->_prev_sibling->_next_sibling = $new_child;
1031
+		}
1032
+
1033
+		$ref->_prev_sibling = $new_child;
1034
+	}
1035
+
1036
+	/**
1037
+	 * Inserts a new child immediately after the specified frame
1038
+	 *
1039
+	 * @param Frame $new_child   The new Frame to insert
1040
+	 * @param Frame $ref         The Frame before the new Frame
1041
+	 * @param bool  $update_node Whether or not to update the DOM
1042
+	 *
1043
+	 * @throws Exception
1044
+	 */
1045
+	public function insert_child_after(Frame $new_child, Frame $ref, $update_node = true)
1046
+	{
1047
+		if ($ref === $this->_last_child) {
1048
+			$this->append_child($new_child, $update_node);
1049
+
1050
+			return;
1051
+		}
1052
+
1053
+		if (is_null($ref)) {
1054
+			$this->prepend_child($new_child, $update_node);
1055
+
1056
+			return;
1057
+		}
1058
+
1059
+		if ($ref->_parent !== $this) {
1060
+			throw new Exception("Reference child is not a child of this node.");
1061
+		}
1062
+
1063
+		// Update the node
1064
+		if ($update_node) {
1065
+			if ($ref->_next_sibling) {
1066
+				$next_node = $ref->_next_sibling->_node;
1067
+				$this->_node->insertBefore($new_child->_node, $next_node);
1068
+			} else {
1069
+				$new_child->_node = $this->_node->appendChild($new_child->_node);
1070
+			}
1071
+		}
1072
+
1073
+		// Remove the child from its parent
1074
+		if ($new_child->_parent) {
1075
+			$new_child->_parent->remove_child($new_child, false);
1076
+		}
1077
+
1078
+		$new_child->_parent = $this;
1079
+		$new_child->_prev_sibling = $ref;
1080
+		$new_child->_next_sibling = $ref->_next_sibling;
1081
+
1082
+		if ($ref->_next_sibling) {
1083
+			$ref->_next_sibling->_prev_sibling = $new_child;
1084
+		}
1085
+
1086
+		$ref->_next_sibling = $new_child;
1087
+	}
1088
+
1089
+	/**
1090
+	 * Remove a child frame
1091
+	 *
1092
+	 * @param Frame $child
1093
+	 * @param bool  $update_node Whether or not to remove the DOM node
1094
+	 *
1095
+	 * @throws Exception
1096
+	 * @return Frame The removed child frame
1097
+	 */
1098
+	public function remove_child(Frame $child, $update_node = true)
1099
+	{
1100
+		if ($child->_parent !== $this) {
1101
+			throw new Exception("Child not found in this frame");
1102
+		}
1103
+
1104
+		if ($update_node) {
1105
+			$this->_node->removeChild($child->_node);
1106
+		}
1107
+
1108
+		if ($child === $this->_first_child) {
1109
+			$this->_first_child = $child->_next_sibling;
1110
+		}
1111
+
1112
+		if ($child === $this->_last_child) {
1113
+			$this->_last_child = $child->_prev_sibling;
1114
+		}
1115
+
1116
+		if ($child->_prev_sibling) {
1117
+			$child->_prev_sibling->_next_sibling = $child->_next_sibling;
1118
+		}
1119
+
1120
+		if ($child->_next_sibling) {
1121
+			$child->_next_sibling->_prev_sibling = $child->_prev_sibling;
1122
+		}
1123
+
1124
+		$child->_next_sibling = null;
1125
+		$child->_prev_sibling = null;
1126
+		$child->_parent = null;
1127
+
1128
+		return $child;
1129
+	}
1130
+
1131
+	//........................................................................
1132
+
1133
+	// Debugging function:
1134
+	/**
1135
+	 * @return string
1136
+	 */
1137
+	public function __toString()
1138
+	{
1139
+		// Skip empty text frames
1140 1140
 //     if ( $this->is_text_node() &&
1141 1141
 //          preg_replace("/\s/", "", $this->_node->data) === "" )
1142 1142
 //       return "";
1143 1143
 
1144 1144
 
1145
-        $str = "<b>" . $this->_node->nodeName . ":</b><br/>";
1146
-        //$str .= spl_object_hash($this->_node) . "<br/>";
1147
-        $str .= "Id: " . $this->get_id() . "<br/>";
1148
-        $str .= "Class: " . get_class($this) . "<br/>";
1149
-
1150
-        if ($this->is_text_node()) {
1151
-            $tmp = htmlspecialchars($this->_node->nodeValue);
1152
-            $str .= "<pre>'" . mb_substr($tmp, 0, 70) .
1153
-                (mb_strlen($tmp) > 70 ? "..." : "") . "'</pre>";
1154
-        } elseif ($css_class = $this->_node->getAttribute("class")) {
1155
-            $str .= "CSS class: '$css_class'<br/>";
1156
-        }
1157
-
1158
-        if ($this->_parent) {
1159
-            $str .= "\nParent:" . $this->_parent->_node->nodeName .
1160
-                " (" . spl_object_hash($this->_parent->_node) . ") " .
1161
-                "<br/>";
1162
-        }
1163
-
1164
-        if ($this->_prev_sibling) {
1165
-            $str .= "Prev: " . $this->_prev_sibling->_node->nodeName .
1166
-                " (" . spl_object_hash($this->_prev_sibling->_node) . ") " .
1167
-                "<br/>";
1168
-        }
1169
-
1170
-        if ($this->_next_sibling) {
1171
-            $str .= "Next: " . $this->_next_sibling->_node->nodeName .
1172
-                " (" . spl_object_hash($this->_next_sibling->_node) . ") " .
1173
-                "<br/>";
1174
-        }
1175
-
1176
-        $d = $this->get_decorator();
1177
-        while ($d && $d != $d->get_decorator()) {
1178
-            $str .= "Decorator: " . get_class($d) . "<br/>";
1179
-            $d = $d->get_decorator();
1180
-        }
1181
-
1182
-        $str .= "Position: " . Helpers::pre_r($this->_position, true);
1183
-        $str .= "\nContaining block: " . Helpers::pre_r($this->_containing_block, true);
1184
-        $str .= "\nMargin width: " . Helpers::pre_r($this->get_margin_width(), true);
1185
-        $str .= "\nMargin height: " . Helpers::pre_r($this->get_margin_height(), true);
1186
-
1187
-        $str .= "\nStyle: <pre>" . $this->_style->__toString() . "</pre>";
1188
-
1189
-        if ($this->_decorator instanceof FrameDecorator\Block) {
1190
-            $str .= "Lines:<pre>";
1191
-            foreach ($this->_decorator->get_line_boxes() as $line) {
1192
-                foreach ($line->get_frames() as $frame) {
1193
-                    if ($frame instanceof FrameDecorator\Text) {
1194
-                        $str .= "\ntext: ";
1195
-                        $str .= "'" . htmlspecialchars($frame->get_text()) . "'";
1196
-                    } else {
1197
-                        $str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")";
1198
-                    }
1199
-                }
1200
-
1201
-                $str .=
1202
-                    "\ny => " . $line->y . "\n" .
1203
-                    "w => " . $line->w . "\n" .
1204
-                    "h => " . $line->h . "\n" .
1205
-                    "left => " . $line->left . "\n" .
1206
-                    "right => " . $line->right . "\n";
1207
-            }
1208
-            $str .= "</pre>";
1209
-        }
1210
-
1211
-        $str .= "\n";
1212
-        if (php_sapi_name() === "cli") {
1213
-            $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
1214
-                ["\n", "", ""],
1215
-                $str));
1216
-        }
1217
-
1218
-        return $str;
1219
-    }
1145
+		$str = "<b>" . $this->_node->nodeName . ":</b><br/>";
1146
+		//$str .= spl_object_hash($this->_node) . "<br/>";
1147
+		$str .= "Id: " . $this->get_id() . "<br/>";
1148
+		$str .= "Class: " . get_class($this) . "<br/>";
1149
+
1150
+		if ($this->is_text_node()) {
1151
+			$tmp = htmlspecialchars($this->_node->nodeValue);
1152
+			$str .= "<pre>'" . mb_substr($tmp, 0, 70) .
1153
+				(mb_strlen($tmp) > 70 ? "..." : "") . "'</pre>";
1154
+		} elseif ($css_class = $this->_node->getAttribute("class")) {
1155
+			$str .= "CSS class: '$css_class'<br/>";
1156
+		}
1157
+
1158
+		if ($this->_parent) {
1159
+			$str .= "\nParent:" . $this->_parent->_node->nodeName .
1160
+				" (" . spl_object_hash($this->_parent->_node) . ") " .
1161
+				"<br/>";
1162
+		}
1163
+
1164
+		if ($this->_prev_sibling) {
1165
+			$str .= "Prev: " . $this->_prev_sibling->_node->nodeName .
1166
+				" (" . spl_object_hash($this->_prev_sibling->_node) . ") " .
1167
+				"<br/>";
1168
+		}
1169
+
1170
+		if ($this->_next_sibling) {
1171
+			$str .= "Next: " . $this->_next_sibling->_node->nodeName .
1172
+				" (" . spl_object_hash($this->_next_sibling->_node) . ") " .
1173
+				"<br/>";
1174
+		}
1175
+
1176
+		$d = $this->get_decorator();
1177
+		while ($d && $d != $d->get_decorator()) {
1178
+			$str .= "Decorator: " . get_class($d) . "<br/>";
1179
+			$d = $d->get_decorator();
1180
+		}
1181
+
1182
+		$str .= "Position: " . Helpers::pre_r($this->_position, true);
1183
+		$str .= "\nContaining block: " . Helpers::pre_r($this->_containing_block, true);
1184
+		$str .= "\nMargin width: " . Helpers::pre_r($this->get_margin_width(), true);
1185
+		$str .= "\nMargin height: " . Helpers::pre_r($this->get_margin_height(), true);
1186
+
1187
+		$str .= "\nStyle: <pre>" . $this->_style->__toString() . "</pre>";
1188
+
1189
+		if ($this->_decorator instanceof FrameDecorator\Block) {
1190
+			$str .= "Lines:<pre>";
1191
+			foreach ($this->_decorator->get_line_boxes() as $line) {
1192
+				foreach ($line->get_frames() as $frame) {
1193
+					if ($frame instanceof FrameDecorator\Text) {
1194
+						$str .= "\ntext: ";
1195
+						$str .= "'" . htmlspecialchars($frame->get_text()) . "'";
1196
+					} else {
1197
+						$str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")";
1198
+					}
1199
+				}
1200
+
1201
+				$str .=
1202
+					"\ny => " . $line->y . "\n" .
1203
+					"w => " . $line->w . "\n" .
1204
+					"h => " . $line->h . "\n" .
1205
+					"left => " . $line->left . "\n" .
1206
+					"right => " . $line->right . "\n";
1207
+			}
1208
+			$str .= "</pre>";
1209
+		}
1210
+
1211
+		$str .= "\n";
1212
+		if (php_sapi_name() === "cli") {
1213
+			$str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
1214
+				["\n", "", ""],
1215
+				$str));
1216
+		}
1217
+
1218
+		return $str;
1219
+	}
1220 1220
 }
Please login to merge, or discard this patch.
Spacing   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -174,18 +174,18 @@  discard block
 block discarded – undo
174 174
             "h" => null,
175 175
         ];
176 176
 
177
-        $this->_containing_block[0] =& $this->_containing_block["x"];
178
-        $this->_containing_block[1] =& $this->_containing_block["y"];
179
-        $this->_containing_block[2] =& $this->_containing_block["w"];
180
-        $this->_containing_block[3] =& $this->_containing_block["h"];
177
+        $this->_containing_block[0] = & $this->_containing_block["x"];
178
+        $this->_containing_block[1] = & $this->_containing_block["y"];
179
+        $this->_containing_block[2] = & $this->_containing_block["w"];
180
+        $this->_containing_block[3] = & $this->_containing_block["h"];
181 181
 
182 182
         $this->_position = [
183 183
             "x" => null,
184 184
             "y" => null,
185 185
         ];
186 186
 
187
-        $this->_position[0] =& $this->_position["x"];
188
-        $this->_position[1] =& $this->_position["y"];
187
+        $this->_position[0] = & $this->_position["x"];
188
+        $this->_position[1] = & $this->_position["y"];
189 189
 
190 190
         $this->_opacity = 1.0;
191 191
         $this->_decorator = null;
@@ -205,7 +205,7 @@  discard block
 block discarded – undo
205 205
         if (self::$_ws_state === self::WS_SPACE) {
206 206
             $node = $this->_node;
207 207
 
208
-            if ($node->nodeName === "#text" && !empty($node->nodeValue)) {
208
+            if ($node->nodeName === "#text" && ! empty($node->nodeValue)) {
209 209
                 $node->nodeValue = preg_replace("/[ \t\r\n\f]+/u", " ", trim($node->nodeValue));
210 210
                 self::$_ws_state = self::WS_TEXT;
211 211
             }
@@ -233,7 +233,7 @@  discard block
 block discarded – undo
233 233
             return true;
234 234
         }
235 235
 
236
-        if (!$this->is_in_flow()) {
236
+        if ( ! $this->is_in_flow()) {
237 237
             return false;
238 238
         }
239 239
 
@@ -433,7 +433,7 @@  discard block
 block discarded – undo
433 433
     {
434 434
         $style = $this->_style;
435 435
 
436
-        return (float)$style->length_in_pt([
436
+        return (float) $style->length_in_pt([
437 437
             $style->width,
438 438
             $style->margin_left,
439 439
             $style->margin_right,
@@ -454,10 +454,10 @@  discard block
 block discarded – undo
454 454
     {
455 455
         $style = $this->_style;
456 456
 
457
-        return (float)$style->length_in_pt(
457
+        return (float) $style->length_in_pt(
458 458
             [
459 459
                 $style->height,
460
-                (float)$style->length_in_pt(
460
+                (float) $style->length_in_pt(
461 461
                     [
462 462
                         $style->border_top_width,
463 463
                         $style->border_bottom_width,
@@ -486,7 +486,7 @@  discard block
 block discarded – undo
486 486
         $cb = $this->_containing_block;
487 487
 
488 488
         $x = $this->_position["x"] +
489
-            (float)$style->length_in_pt(
489
+            (float) $style->length_in_pt(
490 490
                 [
491 491
                     $style->margin_left,
492 492
                     $style->border_left_width,
@@ -496,7 +496,7 @@  discard block
 block discarded – undo
496 496
             );
497 497
 
498 498
         $y = $this->_position["y"] +
499
-            (float)$style->length_in_pt(
499
+            (float) $style->length_in_pt(
500 500
                 [
501 501
                     $style->margin_top,
502 502
                     $style->border_top_width,
@@ -504,9 +504,9 @@  discard block
 block discarded – undo
504 504
                 ], $cb["w"]
505 505
             );
506 506
 
507
-        $w = (float)$style->length_in_pt($style->width, $cb["w"]);
507
+        $w = (float) $style->length_in_pt($style->width, $cb["w"]);
508 508
 
509
-        $h = (float)$style->length_in_pt($style->height, $cb["h"]);
509
+        $h = (float) $style->length_in_pt($style->height, $cb["h"]);
510 510
 
511 511
         return [0 => $x, "x" => $x,
512 512
             1 => $y, "y" => $y,
@@ -528,7 +528,7 @@  discard block
 block discarded – undo
528 528
         $cb = $this->_containing_block;
529 529
 
530 530
         $x = $this->_position["x"] +
531
-            (float)$style->length_in_pt(
531
+            (float) $style->length_in_pt(
532 532
                 [
533 533
                     $style->margin_left,
534 534
                     $style->border_left_width
@@ -537,7 +537,7 @@  discard block
 block discarded – undo
537 537
             );
538 538
 
539 539
         $y = $this->_position["y"] +
540
-            (float)$style->length_in_pt(
540
+            (float) $style->length_in_pt(
541 541
                 [
542 542
                     $style->margin_top,
543 543
                     $style->border_top_width
@@ -545,7 +545,7 @@  discard block
 block discarded – undo
545 545
                 $cb["h"]
546 546
             );
547 547
 
548
-        $w = (float)$style->length_in_pt(
548
+        $w = (float) $style->length_in_pt(
549 549
                 [
550 550
                     $style->padding_left,
551 551
                     $style->width,
@@ -554,7 +554,7 @@  discard block
 block discarded – undo
554 554
                 $cb["w"]
555 555
             );
556 556
 
557
-        $h = (float)$style->length_in_pt(
557
+        $h = (float) $style->length_in_pt(
558 558
                 [
559 559
                     $style->padding_top,
560 560
                     $style->padding_bottom,
@@ -582,11 +582,11 @@  discard block
 block discarded – undo
582 582
         $style = $this->_style;
583 583
         $cb = $this->_containing_block;
584 584
 
585
-        $x = $this->_position["x"] + (float)$style->length_in_pt($style->margin_left, $cb["w"]);
585
+        $x = $this->_position["x"] + (float) $style->length_in_pt($style->margin_left, $cb["w"]);
586 586
 
587
-        $y = $this->_position["y"] + (float)$style->length_in_pt($style->margin_top, $cb["w"]);
587
+        $y = $this->_position["y"] + (float) $style->length_in_pt($style->margin_top, $cb["w"]);
588 588
 
589
-        $w = (float)$style->length_in_pt(
589
+        $w = (float) $style->length_in_pt(
590 590
             [
591 591
                 $style->border_left_width,
592 592
                 $style->padding_left,
@@ -597,7 +597,7 @@  discard block
 block discarded – undo
597 597
             $cb["w"]
598 598
         );
599 599
 
600
-        $h = (float)$style->length_in_pt(
600
+        $h = (float) $style->length_in_pt(
601 601
             [
602 602
                 $style->border_top_width,
603 603
                 $style->padding_top,
@@ -937,7 +937,7 @@  discard block
 block discarded – undo
937 937
         $child->_prev_sibling = null;
938 938
 
939 939
         // Handle the first child
940
-        if (!$this->_first_child) {
940
+        if ( ! $this->_first_child) {
941 941
             $this->_first_child = $child;
942 942
             $this->_last_child = $child;
943 943
             $child->_next_sibling = null;
@@ -974,7 +974,7 @@  discard block
 block discarded – undo
974 974
         $child->_next_sibling = null;
975 975
 
976 976
         // Handle the first child
977
-        if (!$this->_last_child) {
977
+        if ( ! $this->_last_child) {
978 978
             $this->_first_child = $child;
979 979
             $this->_last_child = $child;
980 980
             $child->_prev_sibling = null;
@@ -1142,49 +1142,49 @@  discard block
 block discarded – undo
1142 1142
 //       return "";
1143 1143
 
1144 1144
 
1145
-        $str = "<b>" . $this->_node->nodeName . ":</b><br/>";
1145
+        $str = "<b>".$this->_node->nodeName.":</b><br/>";
1146 1146
         //$str .= spl_object_hash($this->_node) . "<br/>";
1147
-        $str .= "Id: " . $this->get_id() . "<br/>";
1148
-        $str .= "Class: " . get_class($this) . "<br/>";
1147
+        $str .= "Id: ".$this->get_id()."<br/>";
1148
+        $str .= "Class: ".get_class($this)."<br/>";
1149 1149
 
1150 1150
         if ($this->is_text_node()) {
1151 1151
             $tmp = htmlspecialchars($this->_node->nodeValue);
1152
-            $str .= "<pre>'" . mb_substr($tmp, 0, 70) .
1153
-                (mb_strlen($tmp) > 70 ? "..." : "") . "'</pre>";
1152
+            $str .= "<pre>'".mb_substr($tmp, 0, 70).
1153
+                (mb_strlen($tmp) > 70 ? "..." : "")."'</pre>";
1154 1154
         } elseif ($css_class = $this->_node->getAttribute("class")) {
1155 1155
             $str .= "CSS class: '$css_class'<br/>";
1156 1156
         }
1157 1157
 
1158 1158
         if ($this->_parent) {
1159
-            $str .= "\nParent:" . $this->_parent->_node->nodeName .
1160
-                " (" . spl_object_hash($this->_parent->_node) . ") " .
1159
+            $str .= "\nParent:".$this->_parent->_node->nodeName.
1160
+                " (".spl_object_hash($this->_parent->_node).") ".
1161 1161
                 "<br/>";
1162 1162
         }
1163 1163
 
1164 1164
         if ($this->_prev_sibling) {
1165
-            $str .= "Prev: " . $this->_prev_sibling->_node->nodeName .
1166
-                " (" . spl_object_hash($this->_prev_sibling->_node) . ") " .
1165
+            $str .= "Prev: ".$this->_prev_sibling->_node->nodeName.
1166
+                " (".spl_object_hash($this->_prev_sibling->_node).") ".
1167 1167
                 "<br/>";
1168 1168
         }
1169 1169
 
1170 1170
         if ($this->_next_sibling) {
1171
-            $str .= "Next: " . $this->_next_sibling->_node->nodeName .
1172
-                " (" . spl_object_hash($this->_next_sibling->_node) . ") " .
1171
+            $str .= "Next: ".$this->_next_sibling->_node->nodeName.
1172
+                " (".spl_object_hash($this->_next_sibling->_node).") ".
1173 1173
                 "<br/>";
1174 1174
         }
1175 1175
 
1176 1176
         $d = $this->get_decorator();
1177 1177
         while ($d && $d != $d->get_decorator()) {
1178
-            $str .= "Decorator: " . get_class($d) . "<br/>";
1178
+            $str .= "Decorator: ".get_class($d)."<br/>";
1179 1179
             $d = $d->get_decorator();
1180 1180
         }
1181 1181
 
1182
-        $str .= "Position: " . Helpers::pre_r($this->_position, true);
1183
-        $str .= "\nContaining block: " . Helpers::pre_r($this->_containing_block, true);
1184
-        $str .= "\nMargin width: " . Helpers::pre_r($this->get_margin_width(), true);
1185
-        $str .= "\nMargin height: " . Helpers::pre_r($this->get_margin_height(), true);
1182
+        $str .= "Position: ".Helpers::pre_r($this->_position, true);
1183
+        $str .= "\nContaining block: ".Helpers::pre_r($this->_containing_block, true);
1184
+        $str .= "\nMargin width: ".Helpers::pre_r($this->get_margin_width(), true);
1185
+        $str .= "\nMargin height: ".Helpers::pre_r($this->get_margin_height(), true);
1186 1186
 
1187
-        $str .= "\nStyle: <pre>" . $this->_style->__toString() . "</pre>";
1187
+        $str .= "\nStyle: <pre>".$this->_style->__toString()."</pre>";
1188 1188
 
1189 1189
         if ($this->_decorator instanceof FrameDecorator\Block) {
1190 1190
             $str .= "Lines:<pre>";
@@ -1192,18 +1192,18 @@  discard block
 block discarded – undo
1192 1192
                 foreach ($line->get_frames() as $frame) {
1193 1193
                     if ($frame instanceof FrameDecorator\Text) {
1194 1194
                         $str .= "\ntext: ";
1195
-                        $str .= "'" . htmlspecialchars($frame->get_text()) . "'";
1195
+                        $str .= "'".htmlspecialchars($frame->get_text())."'";
1196 1196
                     } else {
1197
-                        $str .= "\nBlock: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")";
1197
+                        $str .= "\nBlock: ".$frame->get_node()->nodeName." (".spl_object_hash($frame->get_node()).")";
1198 1198
                     }
1199 1199
                 }
1200 1200
 
1201 1201
                 $str .=
1202
-                    "\ny => " . $line->y . "\n" .
1203
-                    "w => " . $line->w . "\n" .
1204
-                    "h => " . $line->h . "\n" .
1205
-                    "left => " . $line->left . "\n" .
1206
-                    "right => " . $line->right . "\n";
1202
+                    "\ny => ".$line->y."\n".
1203
+                    "w => ".$line->w."\n".
1204
+                    "h => ".$line->h."\n".
1205
+                    "left => ".$line->left."\n".
1206
+                    "right => ".$line->right."\n";
1207 1207
             }
1208 1208
             $str .= "</pre>";
1209 1209
         }
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Renderer.php 2 patches
Indentation   +264 added lines, -264 removed lines patch added patch discarded remove patch
@@ -25,268 +25,268 @@
 block discarded – undo
25 25
 class Renderer extends AbstractRenderer
26 26
 {
27 27
 
28
-    /**
29
-     * Array of renderers for specific frame types
30
-     *
31
-     * @var AbstractRenderer[]
32
-     */
33
-    protected $_renderers;
34
-
35
-    /**
36
-     * Cache of the callbacks array
37
-     *
38
-     * @var array
39
-     */
40
-    private $_callbacks;
41
-
42
-    /**
43
-     * Advance the canvas to the next page
44
-     */
45
-    function new_page()
46
-    {
47
-        $this->_canvas->new_page();
48
-    }
49
-
50
-    /**
51
-     * Render frames recursively
52
-     *
53
-     * @param Frame $frame the frame to render
54
-     */
55
-    public function render(Frame $frame)
56
-    {
57
-        global $_dompdf_debug;
58
-
59
-        $this->_check_callbacks("begin_frame", $frame);
60
-
61
-        if ($_dompdf_debug) {
62
-            echo $frame;
63
-            flush();
64
-        }
65
-
66
-        $style = $frame->get_style();
67
-
68
-        if (in_array($style->visibility, ["hidden", "collapse"], true)) {
69
-            return;
70
-        }
71
-
72
-        $display = $style->display;
73
-        $transformList = $style->transform;
74
-        $hasTransform = $transformList !== [];
75
-
76
-        // Starts the CSS transformation
77
-        if ($hasTransform) {
78
-            $this->_canvas->save();
79
-            list($x, $y) = $frame->get_padding_box();
80
-            $origin = $style->transform_origin;
81
-
82
-            foreach ($transformList as $transform) {
83
-                list($function, $values) = $transform;
84
-                if ($function === "matrix") {
85
-                    $function = "transform";
86
-                }
87
-
88
-                $values = array_map("floatval", $values);
89
-                $values[] = $x + (float)$style->length_in_pt($origin[0], (float)$style->length_in_pt($style->width));
90
-                $values[] = $y + (float)$style->length_in_pt($origin[1], (float)$style->length_in_pt($style->height));
91
-
92
-                call_user_func_array([$this->_canvas, $function], $values);
93
-            }
94
-        }
95
-
96
-        switch ($display) {
97
-
98
-            case "block":
99
-            case "list-item":
100
-            case "inline-block":
101
-            case "table":
102
-            case "inline-table":
103
-                $this->_render_frame("block", $frame);
104
-                break;
105
-
106
-            case "inline":
107
-                if ($frame->is_text_node()) {
108
-                    $this->_render_frame("text", $frame);
109
-                } else {
110
-                    $this->_render_frame("inline", $frame);
111
-                }
112
-                break;
113
-
114
-            case "table-cell":
115
-                $this->_render_frame("table-cell", $frame);
116
-                break;
117
-
118
-            case "table-row-group":
119
-            case "table-header-group":
120
-            case "table-footer-group":
121
-                $this->_render_frame("table-row-group", $frame);
122
-                break;
123
-
124
-            case "-dompdf-list-bullet":
125
-                $this->_render_frame("list-bullet", $frame);
126
-                break;
127
-
128
-            case "-dompdf-image":
129
-                $this->_render_frame("image", $frame);
130
-                break;
131
-
132
-            case "none":
133
-                $node = $frame->get_node();
134
-
135
-                if ($node->nodeName === "script") {
136
-                    if ($node->getAttribute("type") === "text/php" ||
137
-                        $node->getAttribute("language") === "php"
138
-                    ) {
139
-                        // Evaluate embedded php scripts
140
-                        $this->_render_frame("php", $frame);
141
-                    } elseif ($node->getAttribute("type") === "text/javascript" ||
142
-                        $node->getAttribute("language") === "javascript"
143
-                    ) {
144
-                        // Insert JavaScript
145
-                        $this->_render_frame("javascript", $frame);
146
-                    }
147
-                }
148
-
149
-                // Don't render children, so skip to next iter
150
-                return;
151
-
152
-            default:
153
-                break;
154
-
155
-        }
156
-
157
-        // Starts the overflow: hidden box
158
-        if ($style->overflow === "hidden") {
159
-            $padding_box = $frame->get_padding_box();
160
-            [$x, $y, $w, $h] = $padding_box;
161
-            $style = $frame->get_style();
162
-
163
-            if ($style->has_border_radius()) {
164
-                $border_box = $frame->get_border_box();
165
-                [$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box, $padding_box);
166
-                $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl);
167
-            } else {
168
-                $this->_canvas->clipping_rectangle($x, $y, $w, $h);
169
-            }
170
-        }
171
-
172
-        $stack = [];
173
-
174
-        foreach ($frame->get_children() as $child) {
175
-            // < 0 : negative z-index
176
-            // = 0 : no z-index, no stacking context
177
-            // = 1 : stacking context without z-index
178
-            // > 1 : z-index
179
-            $child_style = $child->get_style();
180
-            $child_z_index = $child_style->z_index;
181
-            $z_index = 0;
182
-
183
-            if ($child_z_index !== "auto") {
184
-                $z_index = $child_z_index + 1;
185
-            } elseif ($child_style->float !== "none" || $child->is_positioned()) {
186
-                $z_index = 1;
187
-            }
188
-
189
-            $stack[$z_index][] = $child;
190
-        }
191
-
192
-        ksort($stack);
193
-
194
-        foreach ($stack as $by_index) {
195
-            foreach ($by_index as $child) {
196
-                $this->render($child);
197
-            }
198
-        }
199
-
200
-        // Ends the overflow: hidden box
201
-        if ($style->overflow === "hidden") {
202
-            $this->_canvas->clipping_end();
203
-        }
204
-
205
-        if ($hasTransform) {
206
-            $this->_canvas->restore();
207
-        }
208
-
209
-        // Check for end frame callback
210
-        $this->_check_callbacks("end_frame", $frame);
211
-    }
212
-
213
-    /**
214
-     * Check for callbacks that need to be performed when a given event
215
-     * gets triggered on a frame
216
-     *
217
-     * @param string $event The type of event
218
-     * @param Frame  $frame The frame that event is triggered on
219
-     */
220
-    protected function _check_callbacks(string $event, Frame $frame): void
221
-    {
222
-        if (!isset($this->_callbacks)) {
223
-            $this->_callbacks = $this->_dompdf->getCallbacks();
224
-        }
225
-
226
-        if (isset($this->_callbacks[$event])) {
227
-            $fs = $this->_callbacks[$event];
228
-            $canvas = $this->_canvas;
229
-            $fontMetrics = $this->_dompdf->getFontMetrics();
230
-
231
-            foreach ($fs as $f) {
232
-                $f($frame, $canvas, $fontMetrics);
233
-            }
234
-        }
235
-    }
236
-
237
-    /**
238
-     * Render a single frame
239
-     *
240
-     * Creates Renderer objects on demand
241
-     *
242
-     * @param string $type type of renderer to use
243
-     * @param Frame $frame the frame to render
244
-     */
245
-    protected function _render_frame($type, $frame)
246
-    {
247
-
248
-        if (!isset($this->_renderers[$type])) {
249
-
250
-            switch ($type) {
251
-                case "block":
252
-                    $this->_renderers[$type] = new Block($this->_dompdf);
253
-                    break;
254
-
255
-                case "inline":
256
-                    $this->_renderers[$type] = new Renderer\Inline($this->_dompdf);
257
-                    break;
258
-
259
-                case "text":
260
-                    $this->_renderers[$type] = new Text($this->_dompdf);
261
-                    break;
262
-
263
-                case "image":
264
-                    $this->_renderers[$type] = new Image($this->_dompdf);
265
-                    break;
266
-
267
-                case "table-cell":
268
-                    $this->_renderers[$type] = new TableCell($this->_dompdf);
269
-                    break;
270
-
271
-                case "table-row-group":
272
-                    $this->_renderers[$type] = new TableRowGroup($this->_dompdf);
273
-                    break;
274
-
275
-                case "list-bullet":
276
-                    $this->_renderers[$type] = new ListBullet($this->_dompdf);
277
-                    break;
278
-
279
-                case "php":
280
-                    $this->_renderers[$type] = new PhpEvaluator($this->_canvas);
281
-                    break;
282
-
283
-                case "javascript":
284
-                    $this->_renderers[$type] = new JavascriptEmbedder($this->_dompdf);
285
-                    break;
286
-
287
-            }
288
-        }
289
-
290
-        $this->_renderers[$type]->render($frame);
291
-    }
28
+	/**
29
+	 * Array of renderers for specific frame types
30
+	 *
31
+	 * @var AbstractRenderer[]
32
+	 */
33
+	protected $_renderers;
34
+
35
+	/**
36
+	 * Cache of the callbacks array
37
+	 *
38
+	 * @var array
39
+	 */
40
+	private $_callbacks;
41
+
42
+	/**
43
+	 * Advance the canvas to the next page
44
+	 */
45
+	function new_page()
46
+	{
47
+		$this->_canvas->new_page();
48
+	}
49
+
50
+	/**
51
+	 * Render frames recursively
52
+	 *
53
+	 * @param Frame $frame the frame to render
54
+	 */
55
+	public function render(Frame $frame)
56
+	{
57
+		global $_dompdf_debug;
58
+
59
+		$this->_check_callbacks("begin_frame", $frame);
60
+
61
+		if ($_dompdf_debug) {
62
+			echo $frame;
63
+			flush();
64
+		}
65
+
66
+		$style = $frame->get_style();
67
+
68
+		if (in_array($style->visibility, ["hidden", "collapse"], true)) {
69
+			return;
70
+		}
71
+
72
+		$display = $style->display;
73
+		$transformList = $style->transform;
74
+		$hasTransform = $transformList !== [];
75
+
76
+		// Starts the CSS transformation
77
+		if ($hasTransform) {
78
+			$this->_canvas->save();
79
+			list($x, $y) = $frame->get_padding_box();
80
+			$origin = $style->transform_origin;
81
+
82
+			foreach ($transformList as $transform) {
83
+				list($function, $values) = $transform;
84
+				if ($function === "matrix") {
85
+					$function = "transform";
86
+				}
87
+
88
+				$values = array_map("floatval", $values);
89
+				$values[] = $x + (float)$style->length_in_pt($origin[0], (float)$style->length_in_pt($style->width));
90
+				$values[] = $y + (float)$style->length_in_pt($origin[1], (float)$style->length_in_pt($style->height));
91
+
92
+				call_user_func_array([$this->_canvas, $function], $values);
93
+			}
94
+		}
95
+
96
+		switch ($display) {
97
+
98
+			case "block":
99
+			case "list-item":
100
+			case "inline-block":
101
+			case "table":
102
+			case "inline-table":
103
+				$this->_render_frame("block", $frame);
104
+				break;
105
+
106
+			case "inline":
107
+				if ($frame->is_text_node()) {
108
+					$this->_render_frame("text", $frame);
109
+				} else {
110
+					$this->_render_frame("inline", $frame);
111
+				}
112
+				break;
113
+
114
+			case "table-cell":
115
+				$this->_render_frame("table-cell", $frame);
116
+				break;
117
+
118
+			case "table-row-group":
119
+			case "table-header-group":
120
+			case "table-footer-group":
121
+				$this->_render_frame("table-row-group", $frame);
122
+				break;
123
+
124
+			case "-dompdf-list-bullet":
125
+				$this->_render_frame("list-bullet", $frame);
126
+				break;
127
+
128
+			case "-dompdf-image":
129
+				$this->_render_frame("image", $frame);
130
+				break;
131
+
132
+			case "none":
133
+				$node = $frame->get_node();
134
+
135
+				if ($node->nodeName === "script") {
136
+					if ($node->getAttribute("type") === "text/php" ||
137
+						$node->getAttribute("language") === "php"
138
+					) {
139
+						// Evaluate embedded php scripts
140
+						$this->_render_frame("php", $frame);
141
+					} elseif ($node->getAttribute("type") === "text/javascript" ||
142
+						$node->getAttribute("language") === "javascript"
143
+					) {
144
+						// Insert JavaScript
145
+						$this->_render_frame("javascript", $frame);
146
+					}
147
+				}
148
+
149
+				// Don't render children, so skip to next iter
150
+				return;
151
+
152
+			default:
153
+				break;
154
+
155
+		}
156
+
157
+		// Starts the overflow: hidden box
158
+		if ($style->overflow === "hidden") {
159
+			$padding_box = $frame->get_padding_box();
160
+			[$x, $y, $w, $h] = $padding_box;
161
+			$style = $frame->get_style();
162
+
163
+			if ($style->has_border_radius()) {
164
+				$border_box = $frame->get_border_box();
165
+				[$tl, $tr, $br, $bl] = $style->resolve_border_radius($border_box, $padding_box);
166
+				$this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl);
167
+			} else {
168
+				$this->_canvas->clipping_rectangle($x, $y, $w, $h);
169
+			}
170
+		}
171
+
172
+		$stack = [];
173
+
174
+		foreach ($frame->get_children() as $child) {
175
+			// < 0 : negative z-index
176
+			// = 0 : no z-index, no stacking context
177
+			// = 1 : stacking context without z-index
178
+			// > 1 : z-index
179
+			$child_style = $child->get_style();
180
+			$child_z_index = $child_style->z_index;
181
+			$z_index = 0;
182
+
183
+			if ($child_z_index !== "auto") {
184
+				$z_index = $child_z_index + 1;
185
+			} elseif ($child_style->float !== "none" || $child->is_positioned()) {
186
+				$z_index = 1;
187
+			}
188
+
189
+			$stack[$z_index][] = $child;
190
+		}
191
+
192
+		ksort($stack);
193
+
194
+		foreach ($stack as $by_index) {
195
+			foreach ($by_index as $child) {
196
+				$this->render($child);
197
+			}
198
+		}
199
+
200
+		// Ends the overflow: hidden box
201
+		if ($style->overflow === "hidden") {
202
+			$this->_canvas->clipping_end();
203
+		}
204
+
205
+		if ($hasTransform) {
206
+			$this->_canvas->restore();
207
+		}
208
+
209
+		// Check for end frame callback
210
+		$this->_check_callbacks("end_frame", $frame);
211
+	}
212
+
213
+	/**
214
+	 * Check for callbacks that need to be performed when a given event
215
+	 * gets triggered on a frame
216
+	 *
217
+	 * @param string $event The type of event
218
+	 * @param Frame  $frame The frame that event is triggered on
219
+	 */
220
+	protected function _check_callbacks(string $event, Frame $frame): void
221
+	{
222
+		if (!isset($this->_callbacks)) {
223
+			$this->_callbacks = $this->_dompdf->getCallbacks();
224
+		}
225
+
226
+		if (isset($this->_callbacks[$event])) {
227
+			$fs = $this->_callbacks[$event];
228
+			$canvas = $this->_canvas;
229
+			$fontMetrics = $this->_dompdf->getFontMetrics();
230
+
231
+			foreach ($fs as $f) {
232
+				$f($frame, $canvas, $fontMetrics);
233
+			}
234
+		}
235
+	}
236
+
237
+	/**
238
+	 * Render a single frame
239
+	 *
240
+	 * Creates Renderer objects on demand
241
+	 *
242
+	 * @param string $type type of renderer to use
243
+	 * @param Frame $frame the frame to render
244
+	 */
245
+	protected function _render_frame($type, $frame)
246
+	{
247
+
248
+		if (!isset($this->_renderers[$type])) {
249
+
250
+			switch ($type) {
251
+				case "block":
252
+					$this->_renderers[$type] = new Block($this->_dompdf);
253
+					break;
254
+
255
+				case "inline":
256
+					$this->_renderers[$type] = new Renderer\Inline($this->_dompdf);
257
+					break;
258
+
259
+				case "text":
260
+					$this->_renderers[$type] = new Text($this->_dompdf);
261
+					break;
262
+
263
+				case "image":
264
+					$this->_renderers[$type] = new Image($this->_dompdf);
265
+					break;
266
+
267
+				case "table-cell":
268
+					$this->_renderers[$type] = new TableCell($this->_dompdf);
269
+					break;
270
+
271
+				case "table-row-group":
272
+					$this->_renderers[$type] = new TableRowGroup($this->_dompdf);
273
+					break;
274
+
275
+				case "list-bullet":
276
+					$this->_renderers[$type] = new ListBullet($this->_dompdf);
277
+					break;
278
+
279
+				case "php":
280
+					$this->_renderers[$type] = new PhpEvaluator($this->_canvas);
281
+					break;
282
+
283
+				case "javascript":
284
+					$this->_renderers[$type] = new JavascriptEmbedder($this->_dompdf);
285
+					break;
286
+
287
+			}
288
+		}
289
+
290
+		$this->_renderers[$type]->render($frame);
291
+	}
292 292
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -86,8 +86,8 @@  discard block
 block discarded – undo
86 86
                 }
87 87
 
88 88
                 $values = array_map("floatval", $values);
89
-                $values[] = $x + (float)$style->length_in_pt($origin[0], (float)$style->length_in_pt($style->width));
90
-                $values[] = $y + (float)$style->length_in_pt($origin[1], (float)$style->length_in_pt($style->height));
89
+                $values[] = $x + (float) $style->length_in_pt($origin[0], (float) $style->length_in_pt($style->width));
90
+                $values[] = $y + (float) $style->length_in_pt($origin[1], (float) $style->length_in_pt($style->height));
91 91
 
92 92
                 call_user_func_array([$this->_canvas, $function], $values);
93 93
             }
@@ -219,7 +219,7 @@  discard block
 block discarded – undo
219 219
      */
220 220
     protected function _check_callbacks(string $event, Frame $frame): void
221 221
     {
222
-        if (!isset($this->_callbacks)) {
222
+        if ( ! isset($this->_callbacks)) {
223 223
             $this->_callbacks = $this->_dompdf->getCallbacks();
224 224
         }
225 225
 
@@ -245,7 +245,7 @@  discard block
 block discarded – undo
245 245
     protected function _render_frame($type, $frame)
246 246
     {
247 247
 
248
-        if (!isset($this->_renderers[$type])) {
248
+        if ( ! isset($this->_renderers[$type])) {
249 249
 
250 250
             switch ($type) {
251 251
                 case "block":
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Cellmap.php 2 patches
Indentation   +976 added lines, -976 removed lines patch added patch discarded remove patch
@@ -21,980 +21,980 @@
 block discarded – undo
21 21
  */
22 22
 class Cellmap
23 23
 {
24
-    /**
25
-     * Border style weight lookup for collapsed border resolution.
26
-     */
27
-    protected const BORDER_STYLE_SCORE = [
28
-        "double" => 8,
29
-        "solid"  => 7,
30
-        "dashed" => 6,
31
-        "dotted" => 5,
32
-        "ridge"  => 4,
33
-        "outset" => 3,
34
-        "groove" => 2,
35
-        "inset"  => 1,
36
-        "none"   => 0
37
-    ];
38
-
39
-    /**
40
-     * The table object this cellmap is attached to.
41
-     *
42
-     * @var TableFrameDecorator
43
-     */
44
-    protected $_table;
45
-
46
-    /**
47
-     * The total number of rows in the table
48
-     *
49
-     * @var int
50
-     */
51
-    protected $_num_rows;
52
-
53
-    /**
54
-     * The total number of columns in the table
55
-     *
56
-     * @var int
57
-     */
58
-    protected $_num_cols;
59
-
60
-    /**
61
-     * 2D array mapping <row,column> to frames
62
-     *
63
-     * @var Frame[][]
64
-     */
65
-    protected $_cells;
66
-
67
-    /**
68
-     * 1D array of column dimensions
69
-     *
70
-     * @var array
71
-     */
72
-    protected $_columns;
73
-
74
-    /**
75
-     * 1D array of row dimensions
76
-     *
77
-     * @var array
78
-     */
79
-    protected $_rows;
80
-
81
-    /**
82
-     * 2D array of border specs
83
-     *
84
-     * @var array
85
-     */
86
-    protected $_borders;
87
-
88
-    /**
89
-     * 1D Array mapping frames to (multiple) <row, col> pairs, keyed on frame_id.
90
-     *
91
-     * @var array[]
92
-     */
93
-    protected $_frames;
94
-
95
-    /**
96
-     * Current column when adding cells, 0-based
97
-     *
98
-     * @var int
99
-     */
100
-    private $__col;
101
-
102
-    /**
103
-     * Current row when adding cells, 0-based
104
-     *
105
-     * @var int
106
-     */
107
-    private $__row;
108
-
109
-    /**
110
-     * Tells whether the columns' width can be modified
111
-     *
112
-     * @var bool
113
-     */
114
-    private $_columns_locked = false;
115
-
116
-    /**
117
-     * Tells whether the table has table-layout:fixed
118
-     *
119
-     * @var bool
120
-     */
121
-    private $_fixed_layout = false;
122
-
123
-    /**
124
-     * @param TableFrameDecorator $table
125
-     */
126
-    public function __construct(TableFrameDecorator $table)
127
-    {
128
-        $this->_table = $table;
129
-        $this->reset();
130
-    }
131
-
132
-    public function reset(): void
133
-    {
134
-        $this->_num_rows = 0;
135
-        $this->_num_cols = 0;
136
-
137
-        $this->_cells = [];
138
-        $this->_frames = [];
139
-
140
-        if (!$this->_columns_locked) {
141
-            $this->_columns = [];
142
-        }
143
-
144
-        $this->_rows = [];
145
-
146
-        $this->_borders = [];
147
-
148
-        $this->__col = $this->__row = 0;
149
-    }
150
-
151
-    public function lock_columns(): void
152
-    {
153
-        $this->_columns_locked = true;
154
-    }
155
-
156
-    /**
157
-     * @return bool
158
-     */
159
-    public function is_columns_locked()
160
-    {
161
-        return $this->_columns_locked;
162
-    }
163
-
164
-    /**
165
-     * @param bool $fixed
166
-     */
167
-    public function set_layout_fixed(bool $fixed)
168
-    {
169
-        $this->_fixed_layout = $fixed;
170
-    }
171
-
172
-    /**
173
-     * @return bool
174
-     */
175
-    public function is_layout_fixed()
176
-    {
177
-        return $this->_fixed_layout;
178
-    }
179
-
180
-    /**
181
-     * @return int
182
-     */
183
-    public function get_num_rows()
184
-    {
185
-        return $this->_num_rows;
186
-    }
187
-
188
-    /**
189
-     * @return int
190
-     */
191
-    public function get_num_cols()
192
-    {
193
-        return $this->_num_cols;
194
-    }
195
-
196
-    /**
197
-     * @return array
198
-     */
199
-    public function &get_columns()
200
-    {
201
-        return $this->_columns;
202
-    }
203
-
204
-    /**
205
-     * @param $columns
206
-     */
207
-    public function set_columns($columns)
208
-    {
209
-        $this->_columns = $columns;
210
-    }
211
-
212
-    /**
213
-     * @param int $i
214
-     *
215
-     * @return mixed
216
-     */
217
-    public function &get_column($i)
218
-    {
219
-        if (!isset($this->_columns[$i])) {
220
-            $this->_columns[$i] = [
221
-                "x"          => 0,
222
-                "min-width"  => 0,
223
-                "max-width"  => 0,
224
-                "used-width" => null,
225
-                "absolute"   => 0,
226
-                "percent"    => 0,
227
-                "auto"       => true,
228
-            ];
229
-        }
230
-
231
-        return $this->_columns[$i];
232
-    }
233
-
234
-    /**
235
-     * @return array
236
-     */
237
-    public function &get_rows()
238
-    {
239
-        return $this->_rows;
240
-    }
241
-
242
-    /**
243
-     * @param int $j
244
-     *
245
-     * @return mixed
246
-     */
247
-    public function &get_row($j)
248
-    {
249
-        if (!isset($this->_rows[$j])) {
250
-            $this->_rows[$j] = [
251
-                "y"            => 0,
252
-                "first-column" => 0,
253
-                "height"       => null,
254
-            ];
255
-        }
256
-
257
-        return $this->_rows[$j];
258
-    }
259
-
260
-    /**
261
-     * @param int $i
262
-     * @param int $j
263
-     * @param mixed $h_v
264
-     * @param null|mixed $prop
265
-     *
266
-     * @return mixed
267
-     */
268
-    public function get_border($i, $j, $h_v, $prop = null)
269
-    {
270
-        if (!isset($this->_borders[$i][$j][$h_v])) {
271
-            $this->_borders[$i][$j][$h_v] = [
272
-                "width" => 0,
273
-                "style" => "solid",
274
-                "color" => "black",
275
-            ];
276
-        }
277
-
278
-        if (isset($prop)) {
279
-            return $this->_borders[$i][$j][$h_v][$prop];
280
-        }
281
-
282
-        return $this->_borders[$i][$j][$h_v];
283
-    }
284
-
285
-    /**
286
-     * @param int $i
287
-     * @param int $j
288
-     *
289
-     * @return array
290
-     */
291
-    public function get_border_properties($i, $j)
292
-    {
293
-        return [
294
-            "top"    => $this->get_border($i, $j, "horizontal"),
295
-            "right"  => $this->get_border($i, $j + 1, "vertical"),
296
-            "bottom" => $this->get_border($i + 1, $j, "horizontal"),
297
-            "left"   => $this->get_border($i, $j, "vertical"),
298
-        ];
299
-    }
300
-
301
-    /**
302
-     * @param Frame $frame
303
-     *
304
-     * @return array|null
305
-     */
306
-    public function get_spanned_cells(Frame $frame)
307
-    {
308
-        $key = $frame->get_id();
309
-
310
-        if (isset($this->_frames[$key])) {
311
-            return $this->_frames[$key];
312
-        }
313
-
314
-        return null;
315
-    }
316
-
317
-    /**
318
-     * @param Frame $frame
319
-     *
320
-     * @return bool
321
-     */
322
-    public function frame_exists_in_cellmap(Frame $frame)
323
-    {
324
-        $key = $frame->get_id();
325
-
326
-        return isset($this->_frames[$key]);
327
-    }
328
-
329
-    /**
330
-     * @param Frame $frame
331
-     *
332
-     * @return array
333
-     * @throws Exception
334
-     */
335
-    public function get_frame_position(Frame $frame)
336
-    {
337
-        global $_dompdf_warnings;
338
-
339
-        $key = $frame->get_id();
340
-
341
-        if (!isset($this->_frames[$key])) {
342
-            throw new Exception("Frame not found in cellmap");
343
-        }
344
-
345
-        // Positions are stored relative to the table position
346
-        [$table_x, $table_y] = $this->_table->get_position();
347
-        $col = $this->_frames[$key]["columns"][0];
348
-        $row = $this->_frames[$key]["rows"][0];
349
-
350
-        if (!isset($this->_columns[$col])) {
351
-            $_dompdf_warnings[] = "Frame not found in columns array.  Check your table layout for missing or extra TDs.";
352
-            $x = $table_x;
353
-        } else {
354
-            $x = $table_x + $this->_columns[$col]["x"];
355
-        }
356
-
357
-        if (!isset($this->_rows[$row])) {
358
-            $_dompdf_warnings[] = "Frame not found in row array.  Check your table layout for missing or extra TDs.";
359
-            $y = $table_y;
360
-        } else {
361
-            $y = $table_y + $this->_rows[$row]["y"];
362
-        }
363
-
364
-        return [$x, $y, "x" => $x, "y" => $y];
365
-    }
366
-
367
-    /**
368
-     * @param Frame $frame
369
-     *
370
-     * @return int
371
-     * @throws Exception
372
-     */
373
-    public function get_frame_width(Frame $frame)
374
-    {
375
-        $key = $frame->get_id();
376
-
377
-        if (!isset($this->_frames[$key])) {
378
-            throw new Exception("Frame not found in cellmap");
379
-        }
380
-
381
-        $cols = $this->_frames[$key]["columns"];
382
-        $w = 0;
383
-        foreach ($cols as $i) {
384
-            $w += $this->_columns[$i]["used-width"];
385
-        }
386
-
387
-        return $w;
388
-    }
389
-
390
-    /**
391
-     * @param Frame $frame
392
-     *
393
-     * @return int
394
-     * @throws Exception
395
-     * @throws Exception
396
-     */
397
-    public function get_frame_height(Frame $frame)
398
-    {
399
-        $key = $frame->get_id();
400
-
401
-        if (!isset($this->_frames[$key])) {
402
-            throw new Exception("Frame not found in cellmap");
403
-        }
404
-
405
-        $rows = $this->_frames[$key]["rows"];
406
-        $h = 0;
407
-        foreach ($rows as $i) {
408
-            if (!isset($this->_rows[$i])) {
409
-                throw new Exception("The row #$i could not be found, please file an issue in the tracker with the HTML code");
410
-            }
411
-
412
-            $h += $this->_rows[$i]["height"];
413
-        }
414
-
415
-        return $h;
416
-    }
417
-
418
-    /**
419
-     * @param int $j
420
-     * @param mixed $width
421
-     */
422
-    public function set_column_width($j, $width)
423
-    {
424
-        if ($this->_columns_locked) {
425
-            return;
426
-        }
427
-
428
-        $col =& $this->get_column($j);
429
-        $col["used-width"] = $width;
430
-        $next_col =& $this->get_column($j + 1);
431
-        $next_col["x"] = $col["x"] + $width;
432
-    }
433
-
434
-    /**
435
-     * @param int $i
436
-     * @param long $height
437
-     */
438
-    public function set_row_height($i, $height)
439
-    {
440
-        $row =& $this->get_row($i);
441
-        if ($height > $row["height"]) {
442
-            $row["height"] = $height;
443
-        }
444
-        $next_row =& $this->get_row($i + 1);
445
-        $next_row["y"] = $row["y"] + $row["height"];
446
-    }
447
-
448
-    /**
449
-     * https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution
450
-     *
451
-     * @param int    $i
452
-     * @param int    $j
453
-     * @param string $h_v         `horizontal` or `vertical`
454
-     * @param array  $border_spec
455
-     */
456
-    protected function resolve_border(int $i, int $j, string $h_v, array $border_spec): void
457
-    {
458
-        if (!isset($this->_borders[$i][$j][$h_v])) {
459
-            $this->_borders[$i][$j][$h_v] = $border_spec;
460
-            return;
461
-        }
462
-
463
-        $border = $this->_borders[$i][$j][$h_v];
464
-
465
-        $n_width = $border_spec["width"];
466
-        $n_style = $border_spec["style"];
467
-        $o_width = $border["width"];
468
-        $o_style = $border["style"];
469
-
470
-        if ($o_style === "hidden") {
471
-            return;
472
-        }
473
-
474
-        // A style of `none` has lowest priority independent of its specified
475
-        // width here, as its resolved width is always 0
476
-        if ($n_style === "hidden" || $n_width > $o_width
477
-            || ($o_width == $n_width
478
-                && isset(self::BORDER_STYLE_SCORE[$n_style])
479
-                && isset(self::BORDER_STYLE_SCORE[$o_style])
480
-                && self::BORDER_STYLE_SCORE[$n_style] > self::BORDER_STYLE_SCORE[$o_style])
481
-        ) {
482
-            $this->_borders[$i][$j][$h_v] = $border_spec;
483
-        }
484
-    }
485
-
486
-    /**
487
-     * Get the resolved border properties for the given frame.
488
-     *
489
-     * @param AbstractFrameDecorator $frame
490
-     *
491
-     * @return array[]
492
-     */
493
-    protected function get_resolved_border(AbstractFrameDecorator $frame): array
494
-    {
495
-        $key = $frame->get_id();
496
-        $columns = $this->_frames[$key]["columns"];
497
-        $rows = $this->_frames[$key]["rows"];
498
-
499
-        $first_col = $columns[0];
500
-        $last_col = $columns[count($columns) - 1];
501
-        $first_row = $rows[0];
502
-        $last_row = $rows[count($rows) - 1];
503
-
504
-        $max_top = null;
505
-        $max_bottom = null;
506
-        $max_left = null;
507
-        $max_right = null;
508
-
509
-        foreach ($columns as $col) {
510
-            $top = $this->_borders[$first_row][$col]["horizontal"];
511
-            $bottom = $this->_borders[$last_row + 1][$col]["horizontal"];
512
-
513
-            if ($max_top === null || $top["width"] > $max_top["width"]) {
514
-                $max_top = $top;
515
-            }
516
-            if ($max_bottom === null || $bottom["width"] > $max_bottom["width"]) {
517
-                $max_bottom = $bottom;
518
-            }
519
-        }
520
-
521
-        foreach ($rows as $row) {
522
-            $left = $this->_borders[$row][$first_col]["vertical"];
523
-            $right = $this->_borders[$row][$last_col + 1]["vertical"];
524
-
525
-            if ($max_left === null || $left["width"] > $max_left["width"]) {
526
-                $max_left = $left;
527
-            }
528
-            if ($max_right === null || $right["width"] > $max_right["width"]) {
529
-                $max_right = $right;
530
-            }
531
-        }
532
-
533
-        return [$max_top, $max_right, $max_bottom, $max_left];
534
-    }
535
-
536
-    /**
537
-     * @param AbstractFrameDecorator $frame
538
-     */
539
-    public function add_frame(Frame $frame): void
540
-    {
541
-        $style = $frame->get_style();
542
-        $display = $style->display;
543
-
544
-        $collapse = $this->_table->get_style()->border_collapse === "collapse";
545
-
546
-        // Recursively add the frames within the table, its row groups and rows
547
-        if ($frame === $this->_table
548
-            || $display === "table-row"
549
-            || in_array($display, TableFrameDecorator::ROW_GROUPS, true)
550
-        ) {
551
-            $start_row = $this->__row;
552
-
553
-            foreach ($frame->get_children() as $child) {
554
-                $this->add_frame($child);
555
-            }
556
-
557
-            if ($display === "table-row") {
558
-                $this->add_row();
559
-            }
560
-
561
-            $num_rows = $this->__row - $start_row - 1;
562
-            $key = $frame->get_id();
563
-
564
-            // Row groups always span across the entire table
565
-            $this->_frames[$key]["columns"] = range(0, max(0, $this->_num_cols - 1));
566
-            $this->_frames[$key]["rows"] = range($start_row, max(0, $this->__row - 1));
567
-            $this->_frames[$key]["frame"] = $frame;
568
-
569
-            if ($collapse) {
570
-                $bp = $style->get_border_properties();
571
-
572
-                // Resolve vertical borders
573
-                for ($i = 0; $i < $num_rows + 1; $i++) {
574
-                    $this->resolve_border($start_row + $i, 0, "vertical", $bp["left"]);
575
-                    $this->resolve_border($start_row + $i, $this->_num_cols, "vertical", $bp["right"]);
576
-                }
577
-
578
-                // Resolve horizontal borders
579
-                for ($j = 0; $j < $this->_num_cols; $j++) {
580
-                    $this->resolve_border($start_row, $j, "horizontal", $bp["top"]);
581
-                    $this->resolve_border($this->__row, $j, "horizontal", $bp["bottom"]);
582
-                }
583
-
584
-                if ($frame === $this->_table) {
585
-                    // Clear borders because the cells are now using them. The
586
-                    // border width still needs to be set to half the resolved
587
-                    // width so that the table is positioned properly
588
-                    [$top, $right, $bottom, $left] = $this->get_resolved_border($frame);
589
-
590
-                    $style->set_used("border_top_width", $top["width"] / 2);
591
-                    $style->set_used("border_right_width", $right["width"] / 2);
592
-                    $style->set_used("border_bottom_width", $bottom["width"] / 2);
593
-                    $style->set_used("border_left_width", $left["width"] / 2);
594
-                    $style->set_used("border_style", "none");
595
-                } else {
596
-                    // Clear borders for rows and row groups
597
-                    $style->set_used("border_width", 0);
598
-                    $style->set_used("border_style", "none");
599
-                }
600
-            }
601
-
602
-            if ($frame === $this->_table) {
603
-                // Apply resolved borders to table cells and calculate column
604
-                // widths after all frames have been added
605
-                $this->calculate_column_widths();
606
-            }
607
-            return;
608
-        }
609
-
610
-        // Add the frame to the cellmap
611
-        $key = $frame->get_id();
612
-        $node = $frame->get_node();
613
-        $bp = $style->get_border_properties();
614
-
615
-        // Determine where this cell is going
616
-        $colspan = max((int) $node->getAttribute("colspan"), 1);
617
-        $rowspan = max((int) $node->getAttribute("rowspan"), 1);
618
-
619
-        // Find the next available column (fix by Ciro Mondueri)
620
-        $ac = $this->__col;
621
-        while (isset($this->_cells[$this->__row][$ac])) {
622
-            $ac++;
623
-        }
624
-
625
-        $this->__col = $ac;
626
-
627
-        // Rows:
628
-        for ($i = 0; $i < $rowspan; $i++) {
629
-            $row = $this->__row + $i;
630
-
631
-            $this->_frames[$key]["rows"][] = $row;
632
-
633
-            for ($j = 0; $j < $colspan; $j++) {
634
-                $this->_cells[$row][$this->__col + $j] = $frame;
635
-            }
636
-
637
-            if ($collapse) {
638
-                // Resolve vertical borders
639
-                $this->resolve_border($row, $this->__col, "vertical", $bp["left"]);
640
-                $this->resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"]);
641
-            }
642
-        }
643
-
644
-        // Columns:
645
-        for ($j = 0; $j < $colspan; $j++) {
646
-            $col = $this->__col + $j;
647
-            $this->_frames[$key]["columns"][] = $col;
648
-
649
-            if ($collapse) {
650
-                // Resolve horizontal borders
651
-                $this->resolve_border($this->__row, $col, "horizontal", $bp["top"]);
652
-                $this->resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"]);
653
-            }
654
-        }
655
-
656
-        $this->_frames[$key]["frame"] = $frame;
657
-
658
-        $this->__col += $colspan;
659
-        if ($this->__col > $this->_num_cols) {
660
-            $this->_num_cols = $this->__col;
661
-        }
662
-    }
663
-
664
-    /**
665
-     * Apply resolved borders to table cells and calculate column widths.
666
-     */
667
-    protected function calculate_column_widths(): void
668
-    {
669
-        $table = $this->_table;
670
-        $table_style = $table->get_style();
671
-        $collapse = $table_style->border_collapse === "collapse";
672
-
673
-        if ($collapse) {
674
-            $v_spacing = 0;
675
-            $h_spacing = 0;
676
-        } else {
677
-            // The additional 1/2 width gets added to the table proper
678
-            [$h, $v] = $table_style->border_spacing;
679
-            $v_spacing = $v / 2;
680
-            $h_spacing = $h / 2;
681
-        }
682
-
683
-        foreach ($this->_frames as $frame_info) {
684
-            /** @var TableCellFrameDecorator */
685
-            $frame = $frame_info["frame"];
686
-            $style = $frame->get_style();
687
-            $display = $style->display;
688
-
689
-            if ($display !== "table-cell") {
690
-                continue;
691
-            }
692
-
693
-            if ($collapse) {
694
-                // Set the resolved border at half width
695
-                [$top, $right, $bottom, $left] = $this->get_resolved_border($frame);
696
-
697
-                $style->set_used("border_top_width", $top["width"] / 2);
698
-                $style->set_used("border_top_style", $top["style"]);
699
-                $style->set_used("border_top_color", $top["color"]);
700
-                $style->set_used("border_right_width", $right["width"] / 2);
701
-                $style->set_used("border_right_style", $right["style"]);
702
-                $style->set_used("border_right_color", $right["color"]);
703
-                $style->set_used("border_bottom_width", $bottom["width"] / 2);
704
-                $style->set_used("border_bottom_style", $bottom["style"]);
705
-                $style->set_used("border_bottom_color", $bottom["color"]);
706
-                $style->set_used("border_left_width", $left["width"] / 2);
707
-                $style->set_used("border_left_style", $left["style"]);
708
-                $style->set_used("border_left_color", $left["color"]);
709
-                $style->set_used("margin", 0);
710
-            } else {
711
-                // Border spacing is effectively a margin between cells
712
-                $style->set_used("margin_top", $v_spacing);
713
-                $style->set_used("margin_bottom", $v_spacing);
714
-                $style->set_used("margin_left", $h_spacing);
715
-                $style->set_used("margin_right", $h_spacing);
716
-            }
717
-
718
-            if ($this->_columns_locked) {
719
-                continue;
720
-            }
721
-
722
-            $node = $frame->get_node();
723
-            $colspan = max((int) $node->getAttribute("colspan"), 1);
724
-            $first_col = $frame_info["columns"][0];
725
-
726
-            // Resolve the frame's width
727
-            if ($this->_fixed_layout) {
728
-                list($frame_min, $frame_max) = [0, 10e-10];
729
-            } else {
730
-                list($frame_min, $frame_max) = $frame->get_min_max_width();
731
-            }
732
-
733
-            $width = $style->width;
734
-
735
-            $val = null;
736
-            if (Helpers::is_percent($width) && $colspan === 1) {
737
-                $var = "percent";
738
-                $val = (float)rtrim($width, "% ");
739
-            } elseif ($width !== "auto" && $colspan === 1) {
740
-                $var = "absolute";
741
-                $val = $frame_min;
742
-            }
743
-
744
-            $min = 0;
745
-            $max = 0;
746
-            for ($cs = 0; $cs < $colspan; $cs++) {
747
-
748
-                // Resolve the frame's width(s) with other cells
749
-                $col =& $this->get_column($first_col + $cs);
750
-
751
-                // Note: $var is either 'percent' or 'absolute'.  We compare the
752
-                // requested percentage or absolute values with the existing widths
753
-                // and adjust accordingly.
754
-                if (isset($var) && $val > $col[$var]) {
755
-                    $col[$var] = $val;
756
-                    $col["auto"] = false;
757
-                }
758
-
759
-                $min += $col["min-width"];
760
-                $max += $col["max-width"];
761
-            }
762
-
763
-            if ($frame_min > $min && $colspan === 1) {
764
-                // The frame needs more space.  Expand each sub-column
765
-                // FIXME try to avoid putting this dummy value when table-layout:fixed
766
-                $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_min - $min));
767
-                for ($c = 0; $c < $colspan; $c++) {
768
-                    $col =& $this->get_column($first_col + $c);
769
-                    $col["min-width"] += $inc;
770
-                }
771
-            }
772
-
773
-            if ($frame_max > $max) {
774
-                // FIXME try to avoid putting this dummy value when table-layout:fixed
775
-                $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_max - $max) / $colspan);
776
-                for ($c = 0; $c < $colspan; $c++) {
777
-                    $col =& $this->get_column($first_col + $c);
778
-                    $col["max-width"] += $inc;
779
-                }
780
-            }
781
-        }
782
-
783
-        // Adjust absolute columns so that the absolute (and max) width is the
784
-        // largest minimum width of all cells. This accounts for cells without
785
-        // absolute width within an absolute column
786
-        foreach ($this->_columns as &$col) {
787
-            if ($col["absolute"] > 0) {
788
-                $col["absolute"] = $col["min-width"];
789
-                $col["max-width"] = $col["min-width"];
790
-            }
791
-        }
792
-    }
793
-
794
-    protected function add_row(): void
795
-    {
796
-        $this->__row++;
797
-        $this->_num_rows++;
798
-
799
-        // Find the next available column
800
-        $i = 0;
801
-        while (isset($this->_cells[$this->__row][$i])) {
802
-            $i++;
803
-        }
804
-
805
-        $this->__col = $i;
806
-    }
807
-
808
-    /**
809
-     * Remove a row from the cellmap.
810
-     *
811
-     * @param Frame
812
-     */
813
-    public function remove_row(Frame $row)
814
-    {
815
-        $key = $row->get_id();
816
-        if (!isset($this->_frames[$key])) {
817
-            return; // Presumably this row has already been removed
818
-        }
819
-
820
-        $this->__row = $this->_num_rows--;
821
-
822
-        $rows = $this->_frames[$key]["rows"];
823
-        $columns = $this->_frames[$key]["columns"];
824
-
825
-        // Remove all frames from this row
826
-        foreach ($rows as $r) {
827
-            foreach ($columns as $c) {
828
-                if (isset($this->_cells[$r][$c])) {
829
-                    $id = $this->_cells[$r][$c]->get_id();
830
-
831
-                    $this->_cells[$r][$c] = null;
832
-                    unset($this->_cells[$r][$c]);
833
-
834
-                    // has multiple rows?
835
-                    if (isset($this->_frames[$id]) && count($this->_frames[$id]["rows"]) > 1) {
836
-                        // remove just the desired row, but leave the frame
837
-                        if (($row_key = array_search($r, $this->_frames[$id]["rows"])) !== false) {
838
-                            unset($this->_frames[$id]["rows"][$row_key]);
839
-                        }
840
-                        continue;
841
-                    }
842
-
843
-                    $this->_frames[$id] = null;
844
-                    unset($this->_frames[$id]);
845
-                }
846
-            }
847
-
848
-            $this->_rows[$r] = null;
849
-            unset($this->_rows[$r]);
850
-        }
851
-
852
-        $this->_frames[$key] = null;
853
-        unset($this->_frames[$key]);
854
-    }
855
-
856
-    /**
857
-     * Remove a row group from the cellmap.
858
-     *
859
-     * @param Frame $group The group to remove
860
-     */
861
-    public function remove_row_group(Frame $group)
862
-    {
863
-        $key = $group->get_id();
864
-        if (!isset($this->_frames[$key])) {
865
-            return; // Presumably this row has already been removed
866
-        }
867
-
868
-        $iter = $group->get_first_child();
869
-        while ($iter) {
870
-            $this->remove_row($iter);
871
-            $iter = $iter->get_next_sibling();
872
-        }
873
-
874
-        $this->_frames[$key] = null;
875
-        unset($this->_frames[$key]);
876
-    }
877
-
878
-    /**
879
-     * Update a row group after rows have been removed
880
-     *
881
-     * @param Frame $group    The group to update
882
-     * @param Frame $last_row The last row in the row group
883
-     */
884
-    public function update_row_group(Frame $group, Frame $last_row)
885
-    {
886
-        $g_key = $group->get_id();
887
-
888
-        $first_index = $this->_frames[$g_key]["rows"][0];
889
-        $last_index = $first_index;
890
-        $row = $last_row;
891
-        while ($row = $row->get_prev_sibling()) {
892
-            $last_index++;
893
-        }
894
-
895
-        $this->_frames[$g_key]["rows"] = range($first_index, $last_index);
896
-    }
897
-
898
-    public function assign_x_positions(): void
899
-    {
900
-        // Pre-condition: widths must be resolved and assigned to columns and
901
-        // column[0]["x"] must be set.
902
-
903
-        if ($this->_columns_locked) {
904
-            return;
905
-        }
906
-
907
-        $x = $this->_columns[0]["x"];
908
-        foreach (array_keys($this->_columns) as $j) {
909
-            $this->_columns[$j]["x"] = $x;
910
-            $x += $this->_columns[$j]["used-width"];
911
-        }
912
-    }
913
-
914
-    public function assign_frame_heights(): void
915
-    {
916
-        // Pre-condition: widths and heights of each column & row must be
917
-        // calcluated
918
-        foreach ($this->_frames as $arr) {
919
-            $frame = $arr["frame"];
920
-
921
-            $h = 0.0;
922
-            foreach ($arr["rows"] as $row) {
923
-                if (!isset($this->_rows[$row])) {
924
-                    // The row has been removed because of a page split, so skip it.
925
-                    continue;
926
-                }
927
-
928
-                $h += $this->_rows[$row]["height"];
929
-            }
930
-
931
-            if ($frame instanceof TableCellFrameDecorator) {
932
-                $frame->set_cell_height($h);
933
-            } else {
934
-                $frame->get_style()->set_used("height", $h);
935
-            }
936
-        }
937
-    }
938
-
939
-    /**
940
-     * Re-adjust frame height if the table height is larger than its content
941
-     */
942
-    public function set_frame_heights(float $table_height, float $content_height): void
943
-    {
944
-        // Distribute the increased height proportionally amongst each row
945
-        foreach ($this->_frames as $arr) {
946
-            $frame = $arr["frame"];
947
-
948
-            $h = 0.0;
949
-            foreach ($arr["rows"] as $row) {
950
-                if (!isset($this->_rows[$row])) {
951
-                    continue;
952
-                }
953
-
954
-                $h += $this->_rows[$row]["height"];
955
-            }
956
-
957
-            if ($content_height > 0) {
958
-                $new_height = ($h / $content_height) * $table_height;
959
-            } else {
960
-                $new_height = 0.0;
961
-            }
962
-
963
-            if ($frame instanceof TableCellFrameDecorator) {
964
-                $frame->set_cell_height($new_height);
965
-            } else {
966
-                $frame->get_style()->set_used("height", $new_height);
967
-            }
968
-        }
969
-    }
970
-
971
-    /**
972
-     * Used for debugging:
973
-     *
974
-     * @return string
975
-     */
976
-    public function __toString(): string
977
-    {
978
-        $str = "";
979
-        $str .= "Columns:<br/>";
980
-        $str .= Helpers::pre_r($this->_columns, true);
981
-        $str .= "Rows:<br/>";
982
-        $str .= Helpers::pre_r($this->_rows, true);
983
-
984
-        $str .= "Frames:<br/>";
985
-        $arr = [];
986
-        foreach ($this->_frames as $key => $val) {
987
-            $arr[$key] = ["columns" => $val["columns"], "rows" => $val["rows"]];
988
-        }
989
-
990
-        $str .= Helpers::pre_r($arr, true);
991
-
992
-        if (php_sapi_name() == "cli") {
993
-            $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
994
-                ["\n", chr(27) . "[01;33m", chr(27) . "[0m"],
995
-                $str));
996
-        }
997
-
998
-        return $str;
999
-    }
24
+	/**
25
+	 * Border style weight lookup for collapsed border resolution.
26
+	 */
27
+	protected const BORDER_STYLE_SCORE = [
28
+		"double" => 8,
29
+		"solid"  => 7,
30
+		"dashed" => 6,
31
+		"dotted" => 5,
32
+		"ridge"  => 4,
33
+		"outset" => 3,
34
+		"groove" => 2,
35
+		"inset"  => 1,
36
+		"none"   => 0
37
+	];
38
+
39
+	/**
40
+	 * The table object this cellmap is attached to.
41
+	 *
42
+	 * @var TableFrameDecorator
43
+	 */
44
+	protected $_table;
45
+
46
+	/**
47
+	 * The total number of rows in the table
48
+	 *
49
+	 * @var int
50
+	 */
51
+	protected $_num_rows;
52
+
53
+	/**
54
+	 * The total number of columns in the table
55
+	 *
56
+	 * @var int
57
+	 */
58
+	protected $_num_cols;
59
+
60
+	/**
61
+	 * 2D array mapping <row,column> to frames
62
+	 *
63
+	 * @var Frame[][]
64
+	 */
65
+	protected $_cells;
66
+
67
+	/**
68
+	 * 1D array of column dimensions
69
+	 *
70
+	 * @var array
71
+	 */
72
+	protected $_columns;
73
+
74
+	/**
75
+	 * 1D array of row dimensions
76
+	 *
77
+	 * @var array
78
+	 */
79
+	protected $_rows;
80
+
81
+	/**
82
+	 * 2D array of border specs
83
+	 *
84
+	 * @var array
85
+	 */
86
+	protected $_borders;
87
+
88
+	/**
89
+	 * 1D Array mapping frames to (multiple) <row, col> pairs, keyed on frame_id.
90
+	 *
91
+	 * @var array[]
92
+	 */
93
+	protected $_frames;
94
+
95
+	/**
96
+	 * Current column when adding cells, 0-based
97
+	 *
98
+	 * @var int
99
+	 */
100
+	private $__col;
101
+
102
+	/**
103
+	 * Current row when adding cells, 0-based
104
+	 *
105
+	 * @var int
106
+	 */
107
+	private $__row;
108
+
109
+	/**
110
+	 * Tells whether the columns' width can be modified
111
+	 *
112
+	 * @var bool
113
+	 */
114
+	private $_columns_locked = false;
115
+
116
+	/**
117
+	 * Tells whether the table has table-layout:fixed
118
+	 *
119
+	 * @var bool
120
+	 */
121
+	private $_fixed_layout = false;
122
+
123
+	/**
124
+	 * @param TableFrameDecorator $table
125
+	 */
126
+	public function __construct(TableFrameDecorator $table)
127
+	{
128
+		$this->_table = $table;
129
+		$this->reset();
130
+	}
131
+
132
+	public function reset(): void
133
+	{
134
+		$this->_num_rows = 0;
135
+		$this->_num_cols = 0;
136
+
137
+		$this->_cells = [];
138
+		$this->_frames = [];
139
+
140
+		if (!$this->_columns_locked) {
141
+			$this->_columns = [];
142
+		}
143
+
144
+		$this->_rows = [];
145
+
146
+		$this->_borders = [];
147
+
148
+		$this->__col = $this->__row = 0;
149
+	}
150
+
151
+	public function lock_columns(): void
152
+	{
153
+		$this->_columns_locked = true;
154
+	}
155
+
156
+	/**
157
+	 * @return bool
158
+	 */
159
+	public function is_columns_locked()
160
+	{
161
+		return $this->_columns_locked;
162
+	}
163
+
164
+	/**
165
+	 * @param bool $fixed
166
+	 */
167
+	public function set_layout_fixed(bool $fixed)
168
+	{
169
+		$this->_fixed_layout = $fixed;
170
+	}
171
+
172
+	/**
173
+	 * @return bool
174
+	 */
175
+	public function is_layout_fixed()
176
+	{
177
+		return $this->_fixed_layout;
178
+	}
179
+
180
+	/**
181
+	 * @return int
182
+	 */
183
+	public function get_num_rows()
184
+	{
185
+		return $this->_num_rows;
186
+	}
187
+
188
+	/**
189
+	 * @return int
190
+	 */
191
+	public function get_num_cols()
192
+	{
193
+		return $this->_num_cols;
194
+	}
195
+
196
+	/**
197
+	 * @return array
198
+	 */
199
+	public function &get_columns()
200
+	{
201
+		return $this->_columns;
202
+	}
203
+
204
+	/**
205
+	 * @param $columns
206
+	 */
207
+	public function set_columns($columns)
208
+	{
209
+		$this->_columns = $columns;
210
+	}
211
+
212
+	/**
213
+	 * @param int $i
214
+	 *
215
+	 * @return mixed
216
+	 */
217
+	public function &get_column($i)
218
+	{
219
+		if (!isset($this->_columns[$i])) {
220
+			$this->_columns[$i] = [
221
+				"x"          => 0,
222
+				"min-width"  => 0,
223
+				"max-width"  => 0,
224
+				"used-width" => null,
225
+				"absolute"   => 0,
226
+				"percent"    => 0,
227
+				"auto"       => true,
228
+			];
229
+		}
230
+
231
+		return $this->_columns[$i];
232
+	}
233
+
234
+	/**
235
+	 * @return array
236
+	 */
237
+	public function &get_rows()
238
+	{
239
+		return $this->_rows;
240
+	}
241
+
242
+	/**
243
+	 * @param int $j
244
+	 *
245
+	 * @return mixed
246
+	 */
247
+	public function &get_row($j)
248
+	{
249
+		if (!isset($this->_rows[$j])) {
250
+			$this->_rows[$j] = [
251
+				"y"            => 0,
252
+				"first-column" => 0,
253
+				"height"       => null,
254
+			];
255
+		}
256
+
257
+		return $this->_rows[$j];
258
+	}
259
+
260
+	/**
261
+	 * @param int $i
262
+	 * @param int $j
263
+	 * @param mixed $h_v
264
+	 * @param null|mixed $prop
265
+	 *
266
+	 * @return mixed
267
+	 */
268
+	public function get_border($i, $j, $h_v, $prop = null)
269
+	{
270
+		if (!isset($this->_borders[$i][$j][$h_v])) {
271
+			$this->_borders[$i][$j][$h_v] = [
272
+				"width" => 0,
273
+				"style" => "solid",
274
+				"color" => "black",
275
+			];
276
+		}
277
+
278
+		if (isset($prop)) {
279
+			return $this->_borders[$i][$j][$h_v][$prop];
280
+		}
281
+
282
+		return $this->_borders[$i][$j][$h_v];
283
+	}
284
+
285
+	/**
286
+	 * @param int $i
287
+	 * @param int $j
288
+	 *
289
+	 * @return array
290
+	 */
291
+	public function get_border_properties($i, $j)
292
+	{
293
+		return [
294
+			"top"    => $this->get_border($i, $j, "horizontal"),
295
+			"right"  => $this->get_border($i, $j + 1, "vertical"),
296
+			"bottom" => $this->get_border($i + 1, $j, "horizontal"),
297
+			"left"   => $this->get_border($i, $j, "vertical"),
298
+		];
299
+	}
300
+
301
+	/**
302
+	 * @param Frame $frame
303
+	 *
304
+	 * @return array|null
305
+	 */
306
+	public function get_spanned_cells(Frame $frame)
307
+	{
308
+		$key = $frame->get_id();
309
+
310
+		if (isset($this->_frames[$key])) {
311
+			return $this->_frames[$key];
312
+		}
313
+
314
+		return null;
315
+	}
316
+
317
+	/**
318
+	 * @param Frame $frame
319
+	 *
320
+	 * @return bool
321
+	 */
322
+	public function frame_exists_in_cellmap(Frame $frame)
323
+	{
324
+		$key = $frame->get_id();
325
+
326
+		return isset($this->_frames[$key]);
327
+	}
328
+
329
+	/**
330
+	 * @param Frame $frame
331
+	 *
332
+	 * @return array
333
+	 * @throws Exception
334
+	 */
335
+	public function get_frame_position(Frame $frame)
336
+	{
337
+		global $_dompdf_warnings;
338
+
339
+		$key = $frame->get_id();
340
+
341
+		if (!isset($this->_frames[$key])) {
342
+			throw new Exception("Frame not found in cellmap");
343
+		}
344
+
345
+		// Positions are stored relative to the table position
346
+		[$table_x, $table_y] = $this->_table->get_position();
347
+		$col = $this->_frames[$key]["columns"][0];
348
+		$row = $this->_frames[$key]["rows"][0];
349
+
350
+		if (!isset($this->_columns[$col])) {
351
+			$_dompdf_warnings[] = "Frame not found in columns array.  Check your table layout for missing or extra TDs.";
352
+			$x = $table_x;
353
+		} else {
354
+			$x = $table_x + $this->_columns[$col]["x"];
355
+		}
356
+
357
+		if (!isset($this->_rows[$row])) {
358
+			$_dompdf_warnings[] = "Frame not found in row array.  Check your table layout for missing or extra TDs.";
359
+			$y = $table_y;
360
+		} else {
361
+			$y = $table_y + $this->_rows[$row]["y"];
362
+		}
363
+
364
+		return [$x, $y, "x" => $x, "y" => $y];
365
+	}
366
+
367
+	/**
368
+	 * @param Frame $frame
369
+	 *
370
+	 * @return int
371
+	 * @throws Exception
372
+	 */
373
+	public function get_frame_width(Frame $frame)
374
+	{
375
+		$key = $frame->get_id();
376
+
377
+		if (!isset($this->_frames[$key])) {
378
+			throw new Exception("Frame not found in cellmap");
379
+		}
380
+
381
+		$cols = $this->_frames[$key]["columns"];
382
+		$w = 0;
383
+		foreach ($cols as $i) {
384
+			$w += $this->_columns[$i]["used-width"];
385
+		}
386
+
387
+		return $w;
388
+	}
389
+
390
+	/**
391
+	 * @param Frame $frame
392
+	 *
393
+	 * @return int
394
+	 * @throws Exception
395
+	 * @throws Exception
396
+	 */
397
+	public function get_frame_height(Frame $frame)
398
+	{
399
+		$key = $frame->get_id();
400
+
401
+		if (!isset($this->_frames[$key])) {
402
+			throw new Exception("Frame not found in cellmap");
403
+		}
404
+
405
+		$rows = $this->_frames[$key]["rows"];
406
+		$h = 0;
407
+		foreach ($rows as $i) {
408
+			if (!isset($this->_rows[$i])) {
409
+				throw new Exception("The row #$i could not be found, please file an issue in the tracker with the HTML code");
410
+			}
411
+
412
+			$h += $this->_rows[$i]["height"];
413
+		}
414
+
415
+		return $h;
416
+	}
417
+
418
+	/**
419
+	 * @param int $j
420
+	 * @param mixed $width
421
+	 */
422
+	public function set_column_width($j, $width)
423
+	{
424
+		if ($this->_columns_locked) {
425
+			return;
426
+		}
427
+
428
+		$col =& $this->get_column($j);
429
+		$col["used-width"] = $width;
430
+		$next_col =& $this->get_column($j + 1);
431
+		$next_col["x"] = $col["x"] + $width;
432
+	}
433
+
434
+	/**
435
+	 * @param int $i
436
+	 * @param long $height
437
+	 */
438
+	public function set_row_height($i, $height)
439
+	{
440
+		$row =& $this->get_row($i);
441
+		if ($height > $row["height"]) {
442
+			$row["height"] = $height;
443
+		}
444
+		$next_row =& $this->get_row($i + 1);
445
+		$next_row["y"] = $row["y"] + $row["height"];
446
+	}
447
+
448
+	/**
449
+	 * https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution
450
+	 *
451
+	 * @param int    $i
452
+	 * @param int    $j
453
+	 * @param string $h_v         `horizontal` or `vertical`
454
+	 * @param array  $border_spec
455
+	 */
456
+	protected function resolve_border(int $i, int $j, string $h_v, array $border_spec): void
457
+	{
458
+		if (!isset($this->_borders[$i][$j][$h_v])) {
459
+			$this->_borders[$i][$j][$h_v] = $border_spec;
460
+			return;
461
+		}
462
+
463
+		$border = $this->_borders[$i][$j][$h_v];
464
+
465
+		$n_width = $border_spec["width"];
466
+		$n_style = $border_spec["style"];
467
+		$o_width = $border["width"];
468
+		$o_style = $border["style"];
469
+
470
+		if ($o_style === "hidden") {
471
+			return;
472
+		}
473
+
474
+		// A style of `none` has lowest priority independent of its specified
475
+		// width here, as its resolved width is always 0
476
+		if ($n_style === "hidden" || $n_width > $o_width
477
+			|| ($o_width == $n_width
478
+				&& isset(self::BORDER_STYLE_SCORE[$n_style])
479
+				&& isset(self::BORDER_STYLE_SCORE[$o_style])
480
+				&& self::BORDER_STYLE_SCORE[$n_style] > self::BORDER_STYLE_SCORE[$o_style])
481
+		) {
482
+			$this->_borders[$i][$j][$h_v] = $border_spec;
483
+		}
484
+	}
485
+
486
+	/**
487
+	 * Get the resolved border properties for the given frame.
488
+	 *
489
+	 * @param AbstractFrameDecorator $frame
490
+	 *
491
+	 * @return array[]
492
+	 */
493
+	protected function get_resolved_border(AbstractFrameDecorator $frame): array
494
+	{
495
+		$key = $frame->get_id();
496
+		$columns = $this->_frames[$key]["columns"];
497
+		$rows = $this->_frames[$key]["rows"];
498
+
499
+		$first_col = $columns[0];
500
+		$last_col = $columns[count($columns) - 1];
501
+		$first_row = $rows[0];
502
+		$last_row = $rows[count($rows) - 1];
503
+
504
+		$max_top = null;
505
+		$max_bottom = null;
506
+		$max_left = null;
507
+		$max_right = null;
508
+
509
+		foreach ($columns as $col) {
510
+			$top = $this->_borders[$first_row][$col]["horizontal"];
511
+			$bottom = $this->_borders[$last_row + 1][$col]["horizontal"];
512
+
513
+			if ($max_top === null || $top["width"] > $max_top["width"]) {
514
+				$max_top = $top;
515
+			}
516
+			if ($max_bottom === null || $bottom["width"] > $max_bottom["width"]) {
517
+				$max_bottom = $bottom;
518
+			}
519
+		}
520
+
521
+		foreach ($rows as $row) {
522
+			$left = $this->_borders[$row][$first_col]["vertical"];
523
+			$right = $this->_borders[$row][$last_col + 1]["vertical"];
524
+
525
+			if ($max_left === null || $left["width"] > $max_left["width"]) {
526
+				$max_left = $left;
527
+			}
528
+			if ($max_right === null || $right["width"] > $max_right["width"]) {
529
+				$max_right = $right;
530
+			}
531
+		}
532
+
533
+		return [$max_top, $max_right, $max_bottom, $max_left];
534
+	}
535
+
536
+	/**
537
+	 * @param AbstractFrameDecorator $frame
538
+	 */
539
+	public function add_frame(Frame $frame): void
540
+	{
541
+		$style = $frame->get_style();
542
+		$display = $style->display;
543
+
544
+		$collapse = $this->_table->get_style()->border_collapse === "collapse";
545
+
546
+		// Recursively add the frames within the table, its row groups and rows
547
+		if ($frame === $this->_table
548
+			|| $display === "table-row"
549
+			|| in_array($display, TableFrameDecorator::ROW_GROUPS, true)
550
+		) {
551
+			$start_row = $this->__row;
552
+
553
+			foreach ($frame->get_children() as $child) {
554
+				$this->add_frame($child);
555
+			}
556
+
557
+			if ($display === "table-row") {
558
+				$this->add_row();
559
+			}
560
+
561
+			$num_rows = $this->__row - $start_row - 1;
562
+			$key = $frame->get_id();
563
+
564
+			// Row groups always span across the entire table
565
+			$this->_frames[$key]["columns"] = range(0, max(0, $this->_num_cols - 1));
566
+			$this->_frames[$key]["rows"] = range($start_row, max(0, $this->__row - 1));
567
+			$this->_frames[$key]["frame"] = $frame;
568
+
569
+			if ($collapse) {
570
+				$bp = $style->get_border_properties();
571
+
572
+				// Resolve vertical borders
573
+				for ($i = 0; $i < $num_rows + 1; $i++) {
574
+					$this->resolve_border($start_row + $i, 0, "vertical", $bp["left"]);
575
+					$this->resolve_border($start_row + $i, $this->_num_cols, "vertical", $bp["right"]);
576
+				}
577
+
578
+				// Resolve horizontal borders
579
+				for ($j = 0; $j < $this->_num_cols; $j++) {
580
+					$this->resolve_border($start_row, $j, "horizontal", $bp["top"]);
581
+					$this->resolve_border($this->__row, $j, "horizontal", $bp["bottom"]);
582
+				}
583
+
584
+				if ($frame === $this->_table) {
585
+					// Clear borders because the cells are now using them. The
586
+					// border width still needs to be set to half the resolved
587
+					// width so that the table is positioned properly
588
+					[$top, $right, $bottom, $left] = $this->get_resolved_border($frame);
589
+
590
+					$style->set_used("border_top_width", $top["width"] / 2);
591
+					$style->set_used("border_right_width", $right["width"] / 2);
592
+					$style->set_used("border_bottom_width", $bottom["width"] / 2);
593
+					$style->set_used("border_left_width", $left["width"] / 2);
594
+					$style->set_used("border_style", "none");
595
+				} else {
596
+					// Clear borders for rows and row groups
597
+					$style->set_used("border_width", 0);
598
+					$style->set_used("border_style", "none");
599
+				}
600
+			}
601
+
602
+			if ($frame === $this->_table) {
603
+				// Apply resolved borders to table cells and calculate column
604
+				// widths after all frames have been added
605
+				$this->calculate_column_widths();
606
+			}
607
+			return;
608
+		}
609
+
610
+		// Add the frame to the cellmap
611
+		$key = $frame->get_id();
612
+		$node = $frame->get_node();
613
+		$bp = $style->get_border_properties();
614
+
615
+		// Determine where this cell is going
616
+		$colspan = max((int) $node->getAttribute("colspan"), 1);
617
+		$rowspan = max((int) $node->getAttribute("rowspan"), 1);
618
+
619
+		// Find the next available column (fix by Ciro Mondueri)
620
+		$ac = $this->__col;
621
+		while (isset($this->_cells[$this->__row][$ac])) {
622
+			$ac++;
623
+		}
624
+
625
+		$this->__col = $ac;
626
+
627
+		// Rows:
628
+		for ($i = 0; $i < $rowspan; $i++) {
629
+			$row = $this->__row + $i;
630
+
631
+			$this->_frames[$key]["rows"][] = $row;
632
+
633
+			for ($j = 0; $j < $colspan; $j++) {
634
+				$this->_cells[$row][$this->__col + $j] = $frame;
635
+			}
636
+
637
+			if ($collapse) {
638
+				// Resolve vertical borders
639
+				$this->resolve_border($row, $this->__col, "vertical", $bp["left"]);
640
+				$this->resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"]);
641
+			}
642
+		}
643
+
644
+		// Columns:
645
+		for ($j = 0; $j < $colspan; $j++) {
646
+			$col = $this->__col + $j;
647
+			$this->_frames[$key]["columns"][] = $col;
648
+
649
+			if ($collapse) {
650
+				// Resolve horizontal borders
651
+				$this->resolve_border($this->__row, $col, "horizontal", $bp["top"]);
652
+				$this->resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"]);
653
+			}
654
+		}
655
+
656
+		$this->_frames[$key]["frame"] = $frame;
657
+
658
+		$this->__col += $colspan;
659
+		if ($this->__col > $this->_num_cols) {
660
+			$this->_num_cols = $this->__col;
661
+		}
662
+	}
663
+
664
+	/**
665
+	 * Apply resolved borders to table cells and calculate column widths.
666
+	 */
667
+	protected function calculate_column_widths(): void
668
+	{
669
+		$table = $this->_table;
670
+		$table_style = $table->get_style();
671
+		$collapse = $table_style->border_collapse === "collapse";
672
+
673
+		if ($collapse) {
674
+			$v_spacing = 0;
675
+			$h_spacing = 0;
676
+		} else {
677
+			// The additional 1/2 width gets added to the table proper
678
+			[$h, $v] = $table_style->border_spacing;
679
+			$v_spacing = $v / 2;
680
+			$h_spacing = $h / 2;
681
+		}
682
+
683
+		foreach ($this->_frames as $frame_info) {
684
+			/** @var TableCellFrameDecorator */
685
+			$frame = $frame_info["frame"];
686
+			$style = $frame->get_style();
687
+			$display = $style->display;
688
+
689
+			if ($display !== "table-cell") {
690
+				continue;
691
+			}
692
+
693
+			if ($collapse) {
694
+				// Set the resolved border at half width
695
+				[$top, $right, $bottom, $left] = $this->get_resolved_border($frame);
696
+
697
+				$style->set_used("border_top_width", $top["width"] / 2);
698
+				$style->set_used("border_top_style", $top["style"]);
699
+				$style->set_used("border_top_color", $top["color"]);
700
+				$style->set_used("border_right_width", $right["width"] / 2);
701
+				$style->set_used("border_right_style", $right["style"]);
702
+				$style->set_used("border_right_color", $right["color"]);
703
+				$style->set_used("border_bottom_width", $bottom["width"] / 2);
704
+				$style->set_used("border_bottom_style", $bottom["style"]);
705
+				$style->set_used("border_bottom_color", $bottom["color"]);
706
+				$style->set_used("border_left_width", $left["width"] / 2);
707
+				$style->set_used("border_left_style", $left["style"]);
708
+				$style->set_used("border_left_color", $left["color"]);
709
+				$style->set_used("margin", 0);
710
+			} else {
711
+				// Border spacing is effectively a margin between cells
712
+				$style->set_used("margin_top", $v_spacing);
713
+				$style->set_used("margin_bottom", $v_spacing);
714
+				$style->set_used("margin_left", $h_spacing);
715
+				$style->set_used("margin_right", $h_spacing);
716
+			}
717
+
718
+			if ($this->_columns_locked) {
719
+				continue;
720
+			}
721
+
722
+			$node = $frame->get_node();
723
+			$colspan = max((int) $node->getAttribute("colspan"), 1);
724
+			$first_col = $frame_info["columns"][0];
725
+
726
+			// Resolve the frame's width
727
+			if ($this->_fixed_layout) {
728
+				list($frame_min, $frame_max) = [0, 10e-10];
729
+			} else {
730
+				list($frame_min, $frame_max) = $frame->get_min_max_width();
731
+			}
732
+
733
+			$width = $style->width;
734
+
735
+			$val = null;
736
+			if (Helpers::is_percent($width) && $colspan === 1) {
737
+				$var = "percent";
738
+				$val = (float)rtrim($width, "% ");
739
+			} elseif ($width !== "auto" && $colspan === 1) {
740
+				$var = "absolute";
741
+				$val = $frame_min;
742
+			}
743
+
744
+			$min = 0;
745
+			$max = 0;
746
+			for ($cs = 0; $cs < $colspan; $cs++) {
747
+
748
+				// Resolve the frame's width(s) with other cells
749
+				$col =& $this->get_column($first_col + $cs);
750
+
751
+				// Note: $var is either 'percent' or 'absolute'.  We compare the
752
+				// requested percentage or absolute values with the existing widths
753
+				// and adjust accordingly.
754
+				if (isset($var) && $val > $col[$var]) {
755
+					$col[$var] = $val;
756
+					$col["auto"] = false;
757
+				}
758
+
759
+				$min += $col["min-width"];
760
+				$max += $col["max-width"];
761
+			}
762
+
763
+			if ($frame_min > $min && $colspan === 1) {
764
+				// The frame needs more space.  Expand each sub-column
765
+				// FIXME try to avoid putting this dummy value when table-layout:fixed
766
+				$inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_min - $min));
767
+				for ($c = 0; $c < $colspan; $c++) {
768
+					$col =& $this->get_column($first_col + $c);
769
+					$col["min-width"] += $inc;
770
+				}
771
+			}
772
+
773
+			if ($frame_max > $max) {
774
+				// FIXME try to avoid putting this dummy value when table-layout:fixed
775
+				$inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_max - $max) / $colspan);
776
+				for ($c = 0; $c < $colspan; $c++) {
777
+					$col =& $this->get_column($first_col + $c);
778
+					$col["max-width"] += $inc;
779
+				}
780
+			}
781
+		}
782
+
783
+		// Adjust absolute columns so that the absolute (and max) width is the
784
+		// largest minimum width of all cells. This accounts for cells without
785
+		// absolute width within an absolute column
786
+		foreach ($this->_columns as &$col) {
787
+			if ($col["absolute"] > 0) {
788
+				$col["absolute"] = $col["min-width"];
789
+				$col["max-width"] = $col["min-width"];
790
+			}
791
+		}
792
+	}
793
+
794
+	protected function add_row(): void
795
+	{
796
+		$this->__row++;
797
+		$this->_num_rows++;
798
+
799
+		// Find the next available column
800
+		$i = 0;
801
+		while (isset($this->_cells[$this->__row][$i])) {
802
+			$i++;
803
+		}
804
+
805
+		$this->__col = $i;
806
+	}
807
+
808
+	/**
809
+	 * Remove a row from the cellmap.
810
+	 *
811
+	 * @param Frame
812
+	 */
813
+	public function remove_row(Frame $row)
814
+	{
815
+		$key = $row->get_id();
816
+		if (!isset($this->_frames[$key])) {
817
+			return; // Presumably this row has already been removed
818
+		}
819
+
820
+		$this->__row = $this->_num_rows--;
821
+
822
+		$rows = $this->_frames[$key]["rows"];
823
+		$columns = $this->_frames[$key]["columns"];
824
+
825
+		// Remove all frames from this row
826
+		foreach ($rows as $r) {
827
+			foreach ($columns as $c) {
828
+				if (isset($this->_cells[$r][$c])) {
829
+					$id = $this->_cells[$r][$c]->get_id();
830
+
831
+					$this->_cells[$r][$c] = null;
832
+					unset($this->_cells[$r][$c]);
833
+
834
+					// has multiple rows?
835
+					if (isset($this->_frames[$id]) && count($this->_frames[$id]["rows"]) > 1) {
836
+						// remove just the desired row, but leave the frame
837
+						if (($row_key = array_search($r, $this->_frames[$id]["rows"])) !== false) {
838
+							unset($this->_frames[$id]["rows"][$row_key]);
839
+						}
840
+						continue;
841
+					}
842
+
843
+					$this->_frames[$id] = null;
844
+					unset($this->_frames[$id]);
845
+				}
846
+			}
847
+
848
+			$this->_rows[$r] = null;
849
+			unset($this->_rows[$r]);
850
+		}
851
+
852
+		$this->_frames[$key] = null;
853
+		unset($this->_frames[$key]);
854
+	}
855
+
856
+	/**
857
+	 * Remove a row group from the cellmap.
858
+	 *
859
+	 * @param Frame $group The group to remove
860
+	 */
861
+	public function remove_row_group(Frame $group)
862
+	{
863
+		$key = $group->get_id();
864
+		if (!isset($this->_frames[$key])) {
865
+			return; // Presumably this row has already been removed
866
+		}
867
+
868
+		$iter = $group->get_first_child();
869
+		while ($iter) {
870
+			$this->remove_row($iter);
871
+			$iter = $iter->get_next_sibling();
872
+		}
873
+
874
+		$this->_frames[$key] = null;
875
+		unset($this->_frames[$key]);
876
+	}
877
+
878
+	/**
879
+	 * Update a row group after rows have been removed
880
+	 *
881
+	 * @param Frame $group    The group to update
882
+	 * @param Frame $last_row The last row in the row group
883
+	 */
884
+	public function update_row_group(Frame $group, Frame $last_row)
885
+	{
886
+		$g_key = $group->get_id();
887
+
888
+		$first_index = $this->_frames[$g_key]["rows"][0];
889
+		$last_index = $first_index;
890
+		$row = $last_row;
891
+		while ($row = $row->get_prev_sibling()) {
892
+			$last_index++;
893
+		}
894
+
895
+		$this->_frames[$g_key]["rows"] = range($first_index, $last_index);
896
+	}
897
+
898
+	public function assign_x_positions(): void
899
+	{
900
+		// Pre-condition: widths must be resolved and assigned to columns and
901
+		// column[0]["x"] must be set.
902
+
903
+		if ($this->_columns_locked) {
904
+			return;
905
+		}
906
+
907
+		$x = $this->_columns[0]["x"];
908
+		foreach (array_keys($this->_columns) as $j) {
909
+			$this->_columns[$j]["x"] = $x;
910
+			$x += $this->_columns[$j]["used-width"];
911
+		}
912
+	}
913
+
914
+	public function assign_frame_heights(): void
915
+	{
916
+		// Pre-condition: widths and heights of each column & row must be
917
+		// calcluated
918
+		foreach ($this->_frames as $arr) {
919
+			$frame = $arr["frame"];
920
+
921
+			$h = 0.0;
922
+			foreach ($arr["rows"] as $row) {
923
+				if (!isset($this->_rows[$row])) {
924
+					// The row has been removed because of a page split, so skip it.
925
+					continue;
926
+				}
927
+
928
+				$h += $this->_rows[$row]["height"];
929
+			}
930
+
931
+			if ($frame instanceof TableCellFrameDecorator) {
932
+				$frame->set_cell_height($h);
933
+			} else {
934
+				$frame->get_style()->set_used("height", $h);
935
+			}
936
+		}
937
+	}
938
+
939
+	/**
940
+	 * Re-adjust frame height if the table height is larger than its content
941
+	 */
942
+	public function set_frame_heights(float $table_height, float $content_height): void
943
+	{
944
+		// Distribute the increased height proportionally amongst each row
945
+		foreach ($this->_frames as $arr) {
946
+			$frame = $arr["frame"];
947
+
948
+			$h = 0.0;
949
+			foreach ($arr["rows"] as $row) {
950
+				if (!isset($this->_rows[$row])) {
951
+					continue;
952
+				}
953
+
954
+				$h += $this->_rows[$row]["height"];
955
+			}
956
+
957
+			if ($content_height > 0) {
958
+				$new_height = ($h / $content_height) * $table_height;
959
+			} else {
960
+				$new_height = 0.0;
961
+			}
962
+
963
+			if ($frame instanceof TableCellFrameDecorator) {
964
+				$frame->set_cell_height($new_height);
965
+			} else {
966
+				$frame->get_style()->set_used("height", $new_height);
967
+			}
968
+		}
969
+	}
970
+
971
+	/**
972
+	 * Used for debugging:
973
+	 *
974
+	 * @return string
975
+	 */
976
+	public function __toString(): string
977
+	{
978
+		$str = "";
979
+		$str .= "Columns:<br/>";
980
+		$str .= Helpers::pre_r($this->_columns, true);
981
+		$str .= "Rows:<br/>";
982
+		$str .= Helpers::pre_r($this->_rows, true);
983
+
984
+		$str .= "Frames:<br/>";
985
+		$arr = [];
986
+		foreach ($this->_frames as $key => $val) {
987
+			$arr[$key] = ["columns" => $val["columns"], "rows" => $val["rows"]];
988
+		}
989
+
990
+		$str .= Helpers::pre_r($arr, true);
991
+
992
+		if (php_sapi_name() == "cli") {
993
+			$str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
994
+				["\n", chr(27) . "[01;33m", chr(27) . "[0m"],
995
+				$str));
996
+		}
997
+
998
+		return $str;
999
+	}
1000 1000
 }
Please login to merge, or discard this patch.
Spacing   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -137,7 +137,7 @@  discard block
 block discarded – undo
137 137
         $this->_cells = [];
138 138
         $this->_frames = [];
139 139
 
140
-        if (!$this->_columns_locked) {
140
+        if ( ! $this->_columns_locked) {
141 141
             $this->_columns = [];
142 142
         }
143 143
 
@@ -216,7 +216,7 @@  discard block
 block discarded – undo
216 216
      */
217 217
     public function &get_column($i)
218 218
     {
219
-        if (!isset($this->_columns[$i])) {
219
+        if ( ! isset($this->_columns[$i])) {
220 220
             $this->_columns[$i] = [
221 221
                 "x"          => 0,
222 222
                 "min-width"  => 0,
@@ -246,7 +246,7 @@  discard block
 block discarded – undo
246 246
      */
247 247
     public function &get_row($j)
248 248
     {
249
-        if (!isset($this->_rows[$j])) {
249
+        if ( ! isset($this->_rows[$j])) {
250 250
             $this->_rows[$j] = [
251 251
                 "y"            => 0,
252 252
                 "first-column" => 0,
@@ -267,7 +267,7 @@  discard block
 block discarded – undo
267 267
      */
268 268
     public function get_border($i, $j, $h_v, $prop = null)
269 269
     {
270
-        if (!isset($this->_borders[$i][$j][$h_v])) {
270
+        if ( ! isset($this->_borders[$i][$j][$h_v])) {
271 271
             $this->_borders[$i][$j][$h_v] = [
272 272
                 "width" => 0,
273 273
                 "style" => "solid",
@@ -338,7 +338,7 @@  discard block
 block discarded – undo
338 338
 
339 339
         $key = $frame->get_id();
340 340
 
341
-        if (!isset($this->_frames[$key])) {
341
+        if ( ! isset($this->_frames[$key])) {
342 342
             throw new Exception("Frame not found in cellmap");
343 343
         }
344 344
 
@@ -347,14 +347,14 @@  discard block
 block discarded – undo
347 347
         $col = $this->_frames[$key]["columns"][0];
348 348
         $row = $this->_frames[$key]["rows"][0];
349 349
 
350
-        if (!isset($this->_columns[$col])) {
350
+        if ( ! isset($this->_columns[$col])) {
351 351
             $_dompdf_warnings[] = "Frame not found in columns array.  Check your table layout for missing or extra TDs.";
352 352
             $x = $table_x;
353 353
         } else {
354 354
             $x = $table_x + $this->_columns[$col]["x"];
355 355
         }
356 356
 
357
-        if (!isset($this->_rows[$row])) {
357
+        if ( ! isset($this->_rows[$row])) {
358 358
             $_dompdf_warnings[] = "Frame not found in row array.  Check your table layout for missing or extra TDs.";
359 359
             $y = $table_y;
360 360
         } else {
@@ -374,7 +374,7 @@  discard block
 block discarded – undo
374 374
     {
375 375
         $key = $frame->get_id();
376 376
 
377
-        if (!isset($this->_frames[$key])) {
377
+        if ( ! isset($this->_frames[$key])) {
378 378
             throw new Exception("Frame not found in cellmap");
379 379
         }
380 380
 
@@ -398,14 +398,14 @@  discard block
 block discarded – undo
398 398
     {
399 399
         $key = $frame->get_id();
400 400
 
401
-        if (!isset($this->_frames[$key])) {
401
+        if ( ! isset($this->_frames[$key])) {
402 402
             throw new Exception("Frame not found in cellmap");
403 403
         }
404 404
 
405 405
         $rows = $this->_frames[$key]["rows"];
406 406
         $h = 0;
407 407
         foreach ($rows as $i) {
408
-            if (!isset($this->_rows[$i])) {
408
+            if ( ! isset($this->_rows[$i])) {
409 409
                 throw new Exception("The row #$i could not be found, please file an issue in the tracker with the HTML code");
410 410
             }
411 411
 
@@ -425,9 +425,9 @@  discard block
 block discarded – undo
425 425
             return;
426 426
         }
427 427
 
428
-        $col =& $this->get_column($j);
428
+        $col = & $this->get_column($j);
429 429
         $col["used-width"] = $width;
430
-        $next_col =& $this->get_column($j + 1);
430
+        $next_col = & $this->get_column($j + 1);
431 431
         $next_col["x"] = $col["x"] + $width;
432 432
     }
433 433
 
@@ -437,11 +437,11 @@  discard block
 block discarded – undo
437 437
      */
438 438
     public function set_row_height($i, $height)
439 439
     {
440
-        $row =& $this->get_row($i);
440
+        $row = & $this->get_row($i);
441 441
         if ($height > $row["height"]) {
442 442
             $row["height"] = $height;
443 443
         }
444
-        $next_row =& $this->get_row($i + 1);
444
+        $next_row = & $this->get_row($i + 1);
445 445
         $next_row["y"] = $row["y"] + $row["height"];
446 446
     }
447 447
 
@@ -455,7 +455,7 @@  discard block
 block discarded – undo
455 455
      */
456 456
     protected function resolve_border(int $i, int $j, string $h_v, array $border_spec): void
457 457
     {
458
-        if (!isset($this->_borders[$i][$j][$h_v])) {
458
+        if ( ! isset($this->_borders[$i][$j][$h_v])) {
459 459
             $this->_borders[$i][$j][$h_v] = $border_spec;
460 460
             return;
461 461
         }
@@ -735,7 +735,7 @@  discard block
 block discarded – undo
735 735
             $val = null;
736 736
             if (Helpers::is_percent($width) && $colspan === 1) {
737 737
                 $var = "percent";
738
-                $val = (float)rtrim($width, "% ");
738
+                $val = (float) rtrim($width, "% ");
739 739
             } elseif ($width !== "auto" && $colspan === 1) {
740 740
                 $var = "absolute";
741 741
                 $val = $frame_min;
@@ -746,7 +746,7 @@  discard block
 block discarded – undo
746 746
             for ($cs = 0; $cs < $colspan; $cs++) {
747 747
 
748 748
                 // Resolve the frame's width(s) with other cells
749
-                $col =& $this->get_column($first_col + $cs);
749
+                $col = & $this->get_column($first_col + $cs);
750 750
 
751 751
                 // Note: $var is either 'percent' or 'absolute'.  We compare the
752 752
                 // requested percentage or absolute values with the existing widths
@@ -765,7 +765,7 @@  discard block
 block discarded – undo
765 765
                 // FIXME try to avoid putting this dummy value when table-layout:fixed
766 766
                 $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_min - $min));
767 767
                 for ($c = 0; $c < $colspan; $c++) {
768
-                    $col =& $this->get_column($first_col + $c);
768
+                    $col = & $this->get_column($first_col + $c);
769 769
                     $col["min-width"] += $inc;
770 770
                 }
771 771
             }
@@ -774,7 +774,7 @@  discard block
 block discarded – undo
774 774
                 // FIXME try to avoid putting this dummy value when table-layout:fixed
775 775
                 $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_max - $max) / $colspan);
776 776
                 for ($c = 0; $c < $colspan; $c++) {
777
-                    $col =& $this->get_column($first_col + $c);
777
+                    $col = & $this->get_column($first_col + $c);
778 778
                     $col["max-width"] += $inc;
779 779
                 }
780 780
             }
@@ -813,7 +813,7 @@  discard block
 block discarded – undo
813 813
     public function remove_row(Frame $row)
814 814
     {
815 815
         $key = $row->get_id();
816
-        if (!isset($this->_frames[$key])) {
816
+        if ( ! isset($this->_frames[$key])) {
817 817
             return; // Presumably this row has already been removed
818 818
         }
819 819
 
@@ -861,7 +861,7 @@  discard block
 block discarded – undo
861 861
     public function remove_row_group(Frame $group)
862 862
     {
863 863
         $key = $group->get_id();
864
-        if (!isset($this->_frames[$key])) {
864
+        if ( ! isset($this->_frames[$key])) {
865 865
             return; // Presumably this row has already been removed
866 866
         }
867 867
 
@@ -920,7 +920,7 @@  discard block
 block discarded – undo
920 920
 
921 921
             $h = 0.0;
922 922
             foreach ($arr["rows"] as $row) {
923
-                if (!isset($this->_rows[$row])) {
923
+                if ( ! isset($this->_rows[$row])) {
924 924
                     // The row has been removed because of a page split, so skip it.
925 925
                     continue;
926 926
                 }
@@ -947,7 +947,7 @@  discard block
 block discarded – undo
947 947
 
948 948
             $h = 0.0;
949 949
             foreach ($arr["rows"] as $row) {
950
-                if (!isset($this->_rows[$row])) {
950
+                if ( ! isset($this->_rows[$row])) {
951 951
                     continue;
952 952
                 }
953 953
 
@@ -991,7 +991,7 @@  discard block
 block discarded – undo
991 991
 
992 992
         if (php_sapi_name() == "cli") {
993 993
             $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
994
-                ["\n", chr(27) . "[01;33m", chr(27) . "[0m"],
994
+                ["\n", chr(27)."[01;33m", chr(27)."[0m"],
995 995
                 $str));
996 996
         }
997 997
 
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/PhpEvaluator.php 2 patches
Indentation   +45 added lines, -45 removed lines patch added patch discarded remove patch
@@ -15,49 +15,49 @@
 block discarded – undo
15 15
 class PhpEvaluator
16 16
 {
17 17
 
18
-    /**
19
-     * @var Canvas
20
-     */
21
-    protected $_canvas;
22
-
23
-    /**
24
-     * PhpEvaluator constructor.
25
-     * @param Canvas $canvas
26
-     */
27
-    public function __construct(Canvas $canvas)
28
-    {
29
-        $this->_canvas = $canvas;
30
-    }
31
-
32
-    /**
33
-     * @param $code
34
-     * @param array $vars
35
-     */
36
-    public function evaluate($code, $vars = [])
37
-    {
38
-        if (!$this->_canvas->get_dompdf()->getOptions()->getIsPhpEnabled()) {
39
-            return;
40
-        }
41
-
42
-        // Set up some variables for the inline code
43
-        $pdf = $this->_canvas;
44
-        $fontMetrics = $pdf->get_dompdf()->getFontMetrics();
45
-        $PAGE_NUM = $pdf->get_page_number();
46
-        $PAGE_COUNT = $pdf->get_page_count();
47
-
48
-        // Override those variables if passed in
49
-        foreach ($vars as $k => $v) {
50
-            $$k = $v;
51
-        }
52
-
53
-        eval($code);
54
-    }
55
-
56
-    /**
57
-     * @param Frame $frame
58
-     */
59
-    public function render(Frame $frame)
60
-    {
61
-        $this->evaluate($frame->get_node()->nodeValue);
62
-    }
18
+	/**
19
+	 * @var Canvas
20
+	 */
21
+	protected $_canvas;
22
+
23
+	/**
24
+	 * PhpEvaluator constructor.
25
+	 * @param Canvas $canvas
26
+	 */
27
+	public function __construct(Canvas $canvas)
28
+	{
29
+		$this->_canvas = $canvas;
30
+	}
31
+
32
+	/**
33
+	 * @param $code
34
+	 * @param array $vars
35
+	 */
36
+	public function evaluate($code, $vars = [])
37
+	{
38
+		if (!$this->_canvas->get_dompdf()->getOptions()->getIsPhpEnabled()) {
39
+			return;
40
+		}
41
+
42
+		// Set up some variables for the inline code
43
+		$pdf = $this->_canvas;
44
+		$fontMetrics = $pdf->get_dompdf()->getFontMetrics();
45
+		$PAGE_NUM = $pdf->get_page_number();
46
+		$PAGE_COUNT = $pdf->get_page_count();
47
+
48
+		// Override those variables if passed in
49
+		foreach ($vars as $k => $v) {
50
+			$$k = $v;
51
+		}
52
+
53
+		eval($code);
54
+	}
55
+
56
+	/**
57
+	 * @param Frame $frame
58
+	 */
59
+	public function render(Frame $frame)
60
+	{
61
+		$this->evaluate($frame->get_node()->nodeValue);
62
+	}
63 63
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -35,7 +35,7 @@
 block discarded – undo
35 35
      */
36 36
     public function evaluate($code, $vars = [])
37 37
     {
38
-        if (!$this->_canvas->get_dompdf()->getOptions()->getIsPhpEnabled()) {
38
+        if ( ! $this->_canvas->get_dompdf()->getOptions()->getIsPhpEnabled()) {
39 39
             return;
40 40
         }
41 41
 
Please login to merge, or discard this patch.
vendor/dompdf/dompdf/src/Exception/ImageException.php 1 patch
Indentation   +10 added lines, -10 removed lines patch added patch discarded remove patch
@@ -17,15 +17,15 @@
 block discarded – undo
17 17
 class ImageException extends Exception
18 18
 {
19 19
 
20
-    /**
21
-     * Class constructor
22
-     *
23
-     * @param string $message Error message
24
-     * @param int $code       Error code
25
-     */
26
-    function __construct($message = null, $code = 0)
27
-    {
28
-        parent::__construct($message, $code);
29
-    }
20
+	/**
21
+	 * Class constructor
22
+	 *
23
+	 * @param string $message Error message
24
+	 * @param int $code       Error code
25
+	 */
26
+	function __construct($message = null, $code = 0)
27
+	{
28
+		parent::__construct($message, $code);
29
+	}
30 30
 
31 31
 }
Please login to merge, or discard this patch.