Completed
Branch development (b1b115)
by Johannes
10:28
created

Style   F

Complexity

Total Complexity 373

Size/Duplication

Total Lines 2919
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 373
c 0
b 0
f 0
dl 0
loc 2919
rs 0.8

How to fix   Complexity   

Complex Class

Complex classes like Style often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Style, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package dompdf
4
 * @link    http://dompdf.github.com/
5
 * @author  Benj Carson <[email protected]>
6
 * @author  Helmut Tischer <[email protected]>
7
 * @author  Fabien Ménager <[email protected]>
8
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
9
 */
10
namespace Dompdf\Css;
11
12
use Dompdf\Adapter\CPDF;
13
use Dompdf\Exception;
14
use Dompdf\Helpers;
15
use Dompdf\FontMetrics;
16
use Dompdf\Frame;
17
18
/**
19
 * Represents CSS properties.
20
 *
21
 * The Style class is responsible for handling and storing CSS properties.
22
 * It includes methods to resolve colors and lengths, as well as getters &
23
 * setters for many CSS properites.
24
 *
25
 * Actual CSS parsing is performed in the {@link Stylesheet} class.
26
 *
27
 * @package dompdf
28
 */
29
class Style
30
{
31
32
    const CSS_IDENTIFIER = "-?[_a-zA-Z]+[_a-zA-Z0-9-]*";
33
    const CSS_INTEGER = "-?\d+";
34
35
    /**
36
     * Default font size, in points.
37
     *
38
     * @var float
39
     */
40
    static $default_font_size = 12;
41
42
    /**
43
     * Default line height, as a fraction of the font size.
44
     *
45
     * @var float
46
     */
47
    static $default_line_height = 1.2;
48
49
    /**
50
     * Default "absolute" font sizes relative to the default font-size
51
     * http://www.w3.org/TR/css3-fonts/#font-size-the-font-size-property
52
     * @var array<float>
53
     */
54
    static $font_size_keywords = array(
55
        "xx-small" => 0.6, // 3/5
56
        "x-small" => 0.75, // 3/4
57
        "small" => 0.889, // 8/9
58
        "medium" => 1, // 1
59
        "large" => 1.2, // 6/5
60
        "x-large" => 1.5, // 3/2
61
        "xx-large" => 2.0, // 2/1
62
    );
63
64
    /**
65
     * List of valid vertical-align keywords.  Should also really be a constant.
66
     *
67
     * @var array
68
     */
69
    static $vertical_align_keywords = array("baseline", "bottom", "middle", "sub",
70
        "super", "text-bottom", "text-top", "top");
71
72
    /**
73
     * List of all inline types.  Should really be a constant.
74
     *
75
     * @var array
76
     */
77
    static $INLINE_TYPES = array("inline");
78
79
    /**
80
     * List of all block types.  Should really be a constant.
81
     *
82
     * @var array
83
     */
84
    static $BLOCK_TYPES = array("block", "inline-block", "table-cell", "list-item");
85
86
    /**
87
     * List of all positionned types.  Should really be a constant.
88
     *
89
     * @var array
90
     */
91
    static $POSITIONNED_TYPES = array("relative", "absolute", "fixed");
92
93
    /**
94
     * List of all table types.  Should really be a constant.
95
     *
96
     * @var array;
97
     */
98
    static $TABLE_TYPES = array("table", "inline-table");
99
100
    /**
101
     * List of valid border styles.  Should also really be a constant.
102
     *
103
     * @var array
104
     */
105
    static $BORDER_STYLES = array("none", "hidden", "dotted", "dashed", "solid",
106
        "double", "groove", "ridge", "inset", "outset");
107
108
    /**
109
     * Default style values.
110
     *
111
     * @link http://www.w3.org/TR/CSS21/propidx.html
112
     *
113
     * @var array
114
     */
115
    static protected $_defaults = null;
116
117
    /**
118
     * List of inherited properties
119
     *
120
     * @link http://www.w3.org/TR/CSS21/propidx.html
121
     *
122
     * @var array
123
     */
124
    static protected $_inherited = null;
125
126
    /**
127
     * Caches method_exists result
128
     *
129
     * @var array<bool>
130
     */
131
    static protected $_methods_cache = array();
132
133
    /**
134
     * The stylesheet this style belongs to
135
     *
136
     * @see Stylesheet
137
     * @var Stylesheet
138
     */
139
    protected $_stylesheet; // stylesheet this style is attached to
140
141
    /**
142
     * Media queries attached to the style
143
     *
144
     * @var int
145
     */
146
    protected $_media_queries;
147
148
    /**
149
     * Main array of all CSS properties & values
150
     *
151
     * @var array
152
     */
153
    protected $_props;
154
155
    /* var instead of protected would allow access outside of class */
156
    protected $_important_props;
157
158
    /**
159
     * Cached property values
160
     *
161
     * @var array
162
     */
163
    protected $_prop_cache;
164
165
    /**
166
     * Font size of parent element in document tree.  Used for relative font
167
     * size resolution.
168
     *
169
     * @var float
170
     */
171
    protected $_parent_font_size; // Font size of parent element
172
173
    protected $_font_family;
174
175
    /**
176
     * @var Frame
177
     */
178
    protected $_frame;
179
180
    /**
181
     * The origin of the style
182
     *
183
     * @var int
184
     */
185
    protected $_origin = Stylesheet::ORIG_AUTHOR;
186
187
    // private members
188
    /**
189
     * True once the font size is resolved absolutely
190
     *
191
     * @var bool
192
     */
193
    private $__font_size_calculated; // Cache flag
194
195
    /**
196
     * The computed bottom spacing
197
     */
198
    private $_computed_bottom_spacing = null;
199
200
    /**
201
     * The computed border radius
202
     */
203
    private $_computed_border_radius = null;
204
205
    /**
206
     * @var bool
207
     */
208
    public $_has_border_radius = false;
209
210
    /**
211
     * @var FontMetrics
212
     */
213
    private $fontMetrics;
214
215
    /**
216
     * Class constructor
217
     *
218
     * @param Stylesheet $stylesheet the stylesheet this Style is associated with.
219
     * @param int $origin
220
     */
221
    public function __construct(Stylesheet $stylesheet, $origin = Stylesheet::ORIG_AUTHOR)
222
    {
223
        $this->setFontMetrics($stylesheet->getFontMetrics());
224
225
        $this->_props = array();
226
        $this->_important_props = array();
227
        $this->_stylesheet = $stylesheet;
228
        $this->_media_queries = array();
229
        $this->_origin = $origin;
230
        $this->_parent_font_size = null;
231
        $this->__font_size_calculated = false;
232
233
        if (!isset(self::$_defaults)) {
234
235
            // Shorthand
236
            $d =& self::$_defaults;
237
238
            // All CSS 2.1 properties, and their default values
239
            $d["azimuth"] = "center";
240
            $d["background_attachment"] = "scroll";
241
            $d["background_color"] = "transparent";
242
            $d["background_image"] = "none";
243
            $d["background_image_resolution"] = "normal";
244
            $d["_dompdf_background_image_resolution"] = $d["background_image_resolution"];
245
            $d["background_position"] = "0% 0%";
246
            $d["background_repeat"] = "repeat";
247
            $d["background"] = "";
248
            $d["border_collapse"] = "separate";
249
            $d["border_color"] = "";
250
            $d["border_spacing"] = "0";
251
            $d["border_style"] = "";
252
            $d["border_top"] = "";
253
            $d["border_right"] = "";
254
            $d["border_bottom"] = "";
255
            $d["border_left"] = "";
256
            $d["border_top_color"] = "";
257
            $d["border_right_color"] = "";
258
            $d["border_bottom_color"] = "";
259
            $d["border_left_color"] = "";
260
            $d["border_top_style"] = "none";
261
            $d["border_right_style"] = "none";
262
            $d["border_bottom_style"] = "none";
263
            $d["border_left_style"] = "none";
264
            $d["border_top_width"] = "medium";
265
            $d["border_right_width"] = "medium";
266
            $d["border_bottom_width"] = "medium";
267
            $d["border_left_width"] = "medium";
268
            $d["border_width"] = "medium";
269
            $d["border_bottom_left_radius"] = "";
270
            $d["border_bottom_right_radius"] = "";
271
            $d["border_top_left_radius"] = "";
272
            $d["border_top_right_radius"] = "";
273
            $d["border_radius"] = "";
274
            $d["border"] = "";
275
            $d["bottom"] = "auto";
276
            $d["caption_side"] = "top";
277
            $d["clear"] = "none";
278
            $d["clip"] = "auto";
279
            $d["color"] = "#000000";
280
            $d["content"] = "normal";
281
            $d["counter_increment"] = "none";
282
            $d["counter_reset"] = "none";
283
            $d["cue_after"] = "none";
284
            $d["cue_before"] = "none";
285
            $d["cue"] = "";
286
            $d["cursor"] = "auto";
287
            $d["direction"] = "ltr";
288
            $d["display"] = "inline";
289
            $d["elevation"] = "level";
290
            $d["empty_cells"] = "show";
291
            $d["float"] = "none";
292
            $d["font_family"] = $stylesheet->get_dompdf()->getOptions()->getDefaultFont();
293
            $d["font_size"] = "medium";
294
            $d["font_style"] = "normal";
295
            $d["font_variant"] = "normal";
296
            $d["font_weight"] = "normal";
297
            $d["font"] = "";
298
            $d["height"] = "auto";
299
            $d["image_resolution"] = "normal";
300
            $d["_dompdf_image_resolution"] = $d["image_resolution"];
301
            $d["_dompdf_keep"] = "";
302
            $d["left"] = "auto";
303
            $d["letter_spacing"] = "normal";
304
            $d["line_height"] = "normal";
305
            $d["list_style_image"] = "none";
306
            $d["list_style_position"] = "outside";
307
            $d["list_style_type"] = "disc";
308
            $d["list_style"] = "";
309
            $d["margin_right"] = "0";
310
            $d["margin_left"] = "0";
311
            $d["margin_top"] = "0";
312
            $d["margin_bottom"] = "0";
313
            $d["margin"] = "";
314
            $d["max_height"] = "none";
315
            $d["max_width"] = "none";
316
            $d["min_height"] = "0";
317
            $d["min_width"] = "0";
318
            $d["opacity"] = "1.0"; // CSS3
319
            $d["orphans"] = "2";
320
            $d["outline_color"] = ""; // "invert" special color is not supported
321
            $d["outline_style"] = "none";
322
            $d["outline_width"] = "medium";
323
            $d["outline"] = "";
324
            $d["overflow"] = "visible";
325
            $d["padding_top"] = "0";
326
            $d["padding_right"] = "0";
327
            $d["padding_bottom"] = "0";
328
            $d["padding_left"] = "0";
329
            $d["padding"] = "";
330
            $d["page_break_after"] = "auto";
331
            $d["page_break_before"] = "auto";
332
            $d["page_break_inside"] = "auto";
333
            $d["pause_after"] = "0";
334
            $d["pause_before"] = "0";
335
            $d["pause"] = "";
336
            $d["pitch_range"] = "50";
337
            $d["pitch"] = "medium";
338
            $d["play_during"] = "auto";
339
            $d["position"] = "static";
340
            $d["quotes"] = "";
341
            $d["richness"] = "50";
342
            $d["right"] = "auto";
343
            $d["size"] = "auto"; // @page
344
            $d["speak_header"] = "once";
345
            $d["speak_numeral"] = "continuous";
346
            $d["speak_punctuation"] = "none";
347
            $d["speak"] = "normal";
348
            $d["speech_rate"] = "medium";
349
            $d["stress"] = "50";
350
            $d["table_layout"] = "auto";
351
            $d["text_align"] = "left";
352
            $d["text_decoration"] = "none";
353
            $d["text_indent"] = "0";
354
            $d["text_transform"] = "none";
355
            $d["top"] = "auto";
356
            $d["transform"] = "none"; // CSS3
357
            $d["transform_origin"] = "50% 50%"; // CSS3
358
            $d["_webkit_transform"] = $d["transform"]; // CSS3
359
            $d["_webkit_transform_origin"] = $d["transform_origin"]; // CSS3
360
            $d["unicode_bidi"] = "normal";
361
            $d["vertical_align"] = "baseline";
362
            $d["visibility"] = "visible";
363
            $d["voice_family"] = "";
364
            $d["volume"] = "medium";
365
            $d["white_space"] = "normal";
366
            $d["word_wrap"] = "normal";
367
            $d["widows"] = "2";
368
            $d["width"] = "auto";
369
            $d["word_spacing"] = "normal";
370
            $d["z_index"] = "auto";
371
372
            // for @font-face
373
            $d["src"] = "";
374
            $d["unicode_range"] = "";
375
376
            // Properties that inherit by default
377
            self::$_inherited = array(
378
                "azimuth",
379
                "background_image_resolution",
380
                "border_collapse",
381
                "border_spacing",
382
                "caption_side",
383
                "color",
384
                "cursor",
385
                "direction",
386
                "elevation",
387
                "empty_cells",
388
                "font_family",
389
                "font_size",
390
                "font_style",
391
                "font_variant",
392
                "font_weight",
393
                "font",
394
                "image_resolution",
395
                "letter_spacing",
396
                "line_height",
397
                "list_style_image",
398
                "list_style_position",
399
                "list_style_type",
400
                "list_style",
401
                "orphans",
402
                "page_break_inside",
403
                "pitch_range",
404
                "pitch",
405
                "quotes",
406
                "richness",
407
                "speak_header",
408
                "speak_numeral",
409
                "speak_punctuation",
410
                "speak",
411
                "speech_rate",
412
                "stress",
413
                "text_align",
414
                "text_indent",
415
                "text_transform",
416
                "visibility",
417
                "voice_family",
418
                "volume",
419
                "white_space",
420
                "word_wrap",
421
                "widows",
422
                "word_spacing",
423
            );
424
        }
425
    }
426
427
    /**
428
     * "Destructor": forcibly free all references held by this object
429
     */
430
    function dispose()
431
    {
432
    }
433
434
    /**
435
     * @param $media_queries
436
     */
437
    function set_media_queries($media_queries)
438
    {
439
        $this->_media_queries = $media_queries;
440
    }
441
442
    /**
443
     * @return array|int
444
     */
445
    function get_media_queries()
446
    {
447
        return $this->_media_queries;
448
    }
449
450
    /**
451
     * @param Frame $frame
452
     */
453
    function set_frame(Frame $frame)
454
    {
455
        $this->_frame = $frame;
456
    }
457
458
    /**
459
     * @return Frame
460
     */
461
    function get_frame()
462
    {
463
        return $this->_frame;
464
    }
465
466
    /**
467
     * @param $origin
468
     */
469
    function set_origin($origin)
470
    {
471
        $this->_origin = $origin;
472
    }
473
474
    /**
475
     * @return int
476
     */
477
    function get_origin()
478
    {
479
        return $this->_origin;
480
    }
481
482
    /**
483
     * returns the {@link Stylesheet} this Style is associated with.
484
     *
485
     * @return Stylesheet
486
     */
487
    function get_stylesheet()
488
    {
489
        return $this->_stylesheet;
490
    }
491
492
    /**
493
     * Converts any CSS length value into an absolute length in points.
494
     *
495
     * length_in_pt() takes a single length (e.g. '1em') or an array of
496
     * lengths and returns an absolute length.  If an array is passed, then
497
     * the return value is the sum of all elements. If any of the lengths
498
     * provided are "auto" or "none" then that value is returned.
499
     *
500
     * If a reference size is not provided, the default font size is used
501
     * ({@link Style::$default_font_size}).
502
     *
503
     * @param float|string|array $length the numeric length (or string measurement) or array of lengths to resolve
504
     * @param float $ref_size an absolute reference size to resolve percentage lengths
505
     * @return float|string
506
     */
507
    function length_in_pt($length, $ref_size = null)
508
    {
509
        static $cache = array();
510
511
        if (!isset($ref_size)) {
512
            $ref_size = self::$default_font_size;
513
        }
514
515
        if (!is_array($length)) {
516
            $key = $length . "/$ref_size";
517
            //Early check on cache, before converting $length to array
518
            if (isset($cache[$key])) {
519
                return $cache[$key];
520
            }
521
            $length = array($length);
522
        } else {
523
            $key = implode("@", $length) . "/$ref_size";
524
            if (isset($cache[$key])) {
525
                return $cache[$key];
526
            }
527
        }
528
529
        $ret = 0;
530
        foreach ($length as $l) {
531
532
            if ($l === "auto") {
533
                return "auto";
534
            }
535
536
            if ($l === "none") {
537
                return "none";
538
            }
539
540
            // Assume numeric values are already in points
541
            if (is_numeric($l)) {
542
                $ret += $l;
543
                continue;
544
            }
545
546
            if ($l === "normal") {
547
                $ret += (float)$ref_size;
548
                continue;
549
            }
550
551
            // Border lengths
552
            if ($l === "thin") {
553
                $ret += 0.5;
554
                continue;
555
            }
556
557
            if ($l === "medium") {
558
                $ret += 1.5;
559
                continue;
560
            }
561
562
            if ($l === "thick") {
563
                $ret += 2.5;
564
                continue;
565
            }
566
567
            if (($i = mb_strpos($l, "px")) !== false) {
568
                $dpi = $this->_stylesheet->get_dompdf()->getOptions()->getDpi();
569
                $ret += ((float)mb_substr($l, 0, $i) * 72) / $dpi;
570
                continue;
571
            }
572
573
            if (($i = mb_strpos($l, "pt")) !== false) {
574
                $ret += (float)mb_substr($l, 0, $i);
575
                continue;
576
            }
577
578
            if (($i = mb_strpos($l, "%")) !== false) {
579
                $ret += (float)mb_substr($l, 0, $i) / 100 * (float)$ref_size;
580
                continue;
581
            }
582
583
            if (($i = mb_strpos($l, "rem")) !== false) {
584
                if ($this->_stylesheet->get_dompdf()->getTree()->get_root()->get_style() === null) {
585
                    // Interpreting it as "em", see https://github.com/dompdf/dompdf/issues/1406
586
                    $ret += (float)mb_substr($l, 0, $i) * $this->__get("font_size");
587
                } else {
588
                    $ret += (float)mb_substr($l, 0, $i) * $this->_stylesheet->get_dompdf()->getTree()->get_root()->get_style()->font_size;
589
                }
590
                continue;
591
            }
592
593
            if (($i = mb_strpos($l, "em")) !== false) {
594
                $ret += (float)mb_substr($l, 0, $i) * $this->__get("font_size");
595
                continue;
596
            }
597
598
            if (($i = mb_strpos($l, "cm")) !== false) {
599
                $ret += (float)mb_substr($l, 0, $i) * 72 / 2.54;
600
                continue;
601
            }
602
603
            if (($i = mb_strpos($l, "mm")) !== false) {
604
                $ret += (float)mb_substr($l, 0, $i) * 72 / 25.4;
605
                continue;
606
            }
607
608
            // FIXME: em:ex ratio?
609
            if (($i = mb_strpos($l, "ex")) !== false) {
610
                $ret += (float)mb_substr($l, 0, $i) * $this->__get("font_size") / 2;
611
                continue;
612
            }
613
614
            if (($i = mb_strpos($l, "in")) !== false) {
615
                $ret += (float)mb_substr($l, 0, $i) * 72;
616
                continue;
617
            }
618
619
            if (($i = mb_strpos($l, "pc")) !== false) {
620
                $ret += (float)mb_substr($l, 0, $i) * 12;
621
                continue;
622
            }
623
624
            // Bogus value
625
            $ret += (float)$ref_size;
626
        }
627
628
        return $cache[$key] = $ret;
629
    }
630
631
632
    /**
633
     * Set inherited properties in this style using values in $parent
634
     *
635
     * @param Style $parent
636
     *
637
     * @return Style
638
     */
639
    function inherit(Style $parent)
640
    {
641
642
        // Set parent font size
643
        $this->_parent_font_size = $parent->get_font_size();
644
645
        foreach (self::$_inherited as $prop) {
646
            //inherit the !important property also.
647
            //if local property is also !important, don't inherit.
648
            if (isset($parent->_props[$prop]) &&
649
                (!isset($this->_props[$prop]) ||
650
                    (isset($parent->_important_props[$prop]) && !isset($this->_important_props[$prop]))
651
                )
652
            ) {
653
                if (isset($parent->_important_props[$prop])) {
654
                    $this->_important_props[$prop] = true;
655
                }
656
                //see __set and __get, on all assignments clear cache!
657
                $this->_prop_cache[$prop] = null;
658
                $this->_props[$prop] = $parent->_props[$prop];
659
            }
660
        }
661
662
        foreach ($this->_props as $prop => $value) {
663
            if ($value === "inherit") {
664
                if (isset($parent->_important_props[$prop])) {
665
                    $this->_important_props[$prop] = true;
666
                }
667
                //do not assign direct, but
668
                //implicite assignment through __set, redirect to specialized, get value with __get
669
                //This is for computing defaults if the parent setting is also missing.
670
                //Therefore do not directly assign the value without __set
671
                //set _important_props before that to be able to propagate.
672
                //see __set and __get, on all assignments clear cache!
673
                //$this->_prop_cache[$prop] = null;
674
                //$this->_props[$prop] = $parent->_props[$prop];
675
                //props_set for more obvious explicite assignment not implemented, because
676
                //too many implicite uses.
677
                // $this->props_set($prop, $parent->$prop);
678
                $this->__set($prop, $parent->__get($prop));
679
            }
680
        }
681
682
        return $this;
683
    }
684
685
    /**
686
     * Override properties in this style with those in $style
687
     *
688
     * @param Style $style
689
     */
690
    function merge(Style $style)
691
    {
692
        $shorthand_properties = array("background", "border", "border_bottom", "border_color", "border_left", "border_radius", "border_right", "border_style", "border_top", "border_width", "flex", "font", "list_style", "margin", "padding", "transform");
693
        //treat the !important attribute
694
        //if old rule has !important attribute, override with new rule only if
695
        //the new rule is also !important
696
        foreach ($style->_props as $prop => $val) {
697
            $can_merge = false;
698
            if (isset($style->_important_props[$prop])) {
699
                $this->_important_props[$prop] = true;
700
                $can_merge = true;
701
            } else if (!isset($this->_important_props[$prop])) {
702
                $can_merge = true;
703
            }
704
705
            if ($can_merge) {
706
                //see __set and __get, on all assignments clear cache!
707
                $this->_prop_cache[$prop] = null;
708
                $this->_props[$prop] = $val;
709
710
                // Clear out "inherit" shorthand properties if specific properties have been set
711
                $shorthands = array_filter($shorthand_properties, function($el) use ($prop) {
712
                    return ( strpos($prop, $el."_") !== false );
713
                });
714
                foreach ($shorthands as $shorthand) {
715
                    if (array_key_exists($shorthand, $this->_props) && $this->_props[$shorthand] === "inherit") {
716
                        unset($this->_props[$shorthand]);
717
                    }
718
                } 
719
            }
720
        }
721
722
        if (isset($style->_props["font_size"])) {
723
            $this->__font_size_calculated = false;
724
        }
725
    }
726
727
    /**
728
     * Returns an array(r, g, b, "r"=> r, "g"=>g, "b"=>b, "hex"=>"#rrggbb")
729
     * based on the provided CSS color value.
730
     *
731
     * @param string $color
732
     * @return array
733
     */
734
    function munge_color($color)
735
    {
736
        return Color::parse($color);
737
    }
738
739
    /* direct access to _important_props array from outside would work only when declared as
740
     * 'var $_important_props;' instead of 'protected $_important_props;'
741
     * Don't call _set/__get on missing attribute. Therefore need a special access.
742
     * Assume that __set will be also called when this is called, so do not check validity again.
743
     * Only created, if !important exists -> always set true.
744
     */
745
    function important_set($prop)
746
    {
747
        $prop = str_replace("-", "_", $prop);
748
        $this->_important_props[$prop] = true;
749
    }
750
751
    /**
752
     * @param $prop
753
     * @return bool
754
     */
755
    function important_get($prop)
756
    {
757
        return isset($this->_important_props[$prop]);
758
    }
759
760
    /**
761
     * PHP5 overloaded setter
762
     *
763
     * This function along with {@link Style::__get()} permit a user of the
764
     * Style class to access any (CSS) property using the following syntax:
765
     * <code>
766
     *  Style->margin_top = "1em";
767
     *  echo (Style->margin_top);
768
     * </code>
769
     *
770
     * __set() automatically calls the provided set function, if one exists,
771
     * otherwise it sets the property directly.  Typically, __set() is not
772
     * called directly from outside of this class.
773
     *
774
     * On each modification clear cache to return accurate setting.
775
     * Also affects direct settings not using __set
776
     * For easier finding all assignments, attempted to allowing only explicite assignment:
777
     * Very many uses, e.g. AbstractFrameReflower.php -> for now leave as it is
778
     * function __set($prop, $val) {
779
     *   throw new Exception("Implicite replacement of assignment by __set.  Not good.");
780
     * }
781
     * function props_set($prop, $val) { ... }
782
     *
783
     * @param string $prop the property to set
784
     * @param mixed $val the value of the property
785
     *
786
     */
787
    function __set($prop, $val)
788
    {
789
        $prop = str_replace("-", "_", $prop);
790
        $this->_prop_cache[$prop] = null;
791
792
        if (!isset(self::$_defaults[$prop])) {
793
            global $_dompdf_warnings;
794
            $_dompdf_warnings[] = "'$prop' is not a valid CSS2 property.";
795
            return;
796
        }
797
798
        if ($prop !== "content" && is_string($val) && strlen($val) > 5 && mb_strpos($val, "url") === false) {
799
            $val = mb_strtolower(trim(str_replace(array("\n", "\t"), array(" "), $val)));
800
            $val = preg_replace("/([0-9]+) (pt|px|pc|em|ex|in|cm|mm|%)/S", "\\1\\2", $val);
801
        }
802
803
        $method = "set_$prop";
804
805
        if (!isset(self::$_methods_cache[$method])) {
806
            self::$_methods_cache[$method] = method_exists($this, $method);
807
        }
808
809
        if (self::$_methods_cache[$method]) {
810
            $this->$method($val);
811
        } else {
812
            $this->_props[$prop] = $val;
813
        }
814
    }
815
816
    /**
817
     * PHP5 overloaded getter
818
     * Along with {@link Style::__set()} __get() provides access to all CSS
819
     * properties directly.  Typically __get() is not called directly outside
820
     * of this class.
821
     * On each modification clear cache to return accurate setting.
822
     * Also affects direct settings not using __set
823
     *
824
     * @param string $prop
825
     *
826
     * @throws Exception
827
     * @return mixed
828
     */
829
    function __get($prop)
830
    {
831
        if (!isset(self::$_defaults[$prop])) {
832
            throw new Exception("'$prop' is not a valid CSS2 property.");
833
        }
834
835
        if (isset($this->_prop_cache[$prop]) && $this->_prop_cache[$prop] != null) {
836
            return $this->_prop_cache[$prop];
837
        }
838
839
        $method = "get_$prop";
840
841
        // Fall back on defaults if property is not set
842
        if (!isset($this->_props[$prop])) {
843
            $this->_props[$prop] = self::$_defaults[$prop];
844
        }
845
846
        if (!isset(self::$_methods_cache[$method])) {
847
            self::$_methods_cache[$method] = method_exists($this, $method);
848
        }
849
850
        if (self::$_methods_cache[$method]) {
851
            return $this->_prop_cache[$prop] = $this->$method();
852
        }
853
854
        return $this->_prop_cache[$prop] = $this->_props[$prop];
855
    }
856
857
    /**
858
     * Similar to __get() without storing the result. Useful for accessing
859
     * properties while loading stylesheets.
860
     *
861
     * @param $prop
862
     * @return string
863
     * @throws Exception
864
     */
865
    function get_prop($prop)
866
    {
867
        if (!isset(self::$_defaults[$prop])) {
868
            throw new Exception("'$prop' is not a valid CSS2 property.");
869
        }
870
871
        $method = "get_$prop";
872
873
        // Fall back on defaults if property is not set
874
        if (!isset($this->_props[$prop])) {
875
            return self::$_defaults[$prop];
876
        }
877
878
        if (method_exists($this, $method)) {
879
            return $this->$method();
880
        }
881
882
        return $this->_props[$prop];
883
    }
884
885
    /**
886
     * @return float|null|string
887
     */
888
    function computed_bottom_spacing() {
889
        if ($this->_computed_bottom_spacing !== null) {
890
            return $this->_computed_bottom_spacing;
891
        }
892
        return $this->_computed_bottom_spacing = $this->length_in_pt(
893
            array(
894
                $this->margin_bottom,
895
                $this->padding_bottom,
896
                $this->border_bottom_width
897
            )
898
        );
899
    }
900
901
    /**
902
     * @return string
903
     */
904
    function get_font_family_raw()
905
    {
906
        return trim($this->_props["font_family"], " \t\n\r\x0B\"'");
907
    }
908
909
    /**
910
     * Getter for the 'font-family' CSS property.
911
     * Uses the {@link FontMetrics} class to resolve the font family into an
912
     * actual font file.
913
     *
914
     * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-family
915
     * @throws Exception
916
     *
917
     * @return string
918
     */
919
    function get_font_family()
920
    {
921
        if (isset($this->_font_family)) {
922
            return $this->_font_family;
923
        }
924
925
        $DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss();
926
927
        // Select the appropriate font.  First determine the subtype, then check
928
        // the specified font-families for a candidate.
929
930
        // Resolve font-weight
931
        $weight = $this->__get("font_weight");
932
933
        if (is_numeric($weight)) {
934
            if ($weight < 600) {
935
                $weight = "normal";
936
            } else {
937
                $weight = "bold";
938
            }
939
        } else if ($weight === "bold" || $weight === "bolder") {
940
            $weight = "bold";
941
        } else {
942
            $weight = "normal";
943
        }
944
945
        // Resolve font-style
946
        $font_style = $this->__get("font_style");
947
948
        if ($weight === "bold" && ($font_style === "italic" || $font_style === "oblique")) {
949
            $subtype = "bold_italic";
950
        } else if ($weight === "bold" && $font_style !== "italic" && $font_style !== "oblique") {
951
            $subtype = "bold";
952
        } else if ($weight !== "bold" && ($font_style === "italic" || $font_style === "oblique")) {
953
            $subtype = "italic";
954
        } else {
955
            $subtype = "normal";
956
        }
957
958
        // Resolve the font family
959
        if ($DEBUGCSS) {
960
            print "<pre>[get_font_family:";
961
            print '(' . $this->_props["font_family"] . '.' . $font_style . '.' . $this->__get("font_weight") . '.' . $weight . '.' . $subtype . ')';
962
        }
963
964
        $families = preg_split("/\s*,\s*/", $this->_props["font_family"]);
965
966
        $font = null;
967
        foreach ($families as $family) {
968
            //remove leading and trailing string delimiters, e.g. on font names with spaces;
969
            //remove leading and trailing whitespace
970
            $family = trim($family, " \t\n\r\x0B\"'");
971
            if ($DEBUGCSS) {
972
                print '(' . $family . ')';
973
            }
974
            $font = $this->getFontMetrics()->getFont($family, $subtype);
975
976
            if ($font) {
977
                if ($DEBUGCSS) {
978
                    print '(' . $font . ")get_font_family]\n</pre>";
979
                }
980
                return $this->_font_family = $font;
981
            }
982
        }
983
984
        $family = null;
985
        if ($DEBUGCSS) {
986
            print '(default)';
987
        }
988
        $font = $this->getFontMetrics()->getFont($family, $subtype);
989
990
        if ($font) {
991
            if ($DEBUGCSS) {
992
                print '(' . $font . ")get_font_family]\n</pre>";
993
            }
994
            return $this->_font_family = $font;
995
        }
996
997
        throw new Exception("Unable to find a suitable font replacement for: '" . $this->_props["font_family"] . "'");
998
999
    }
1000
1001
    /**
1002
     * Returns the resolved font size, in points
1003
     *
1004
     * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
1005
     * @return float
1006
     */
1007
    function get_font_size()
1008
    {
1009
1010
        if ($this->__font_size_calculated) {
1011
            return $this->_props["font_size"];
1012
        }
1013
1014
        if (!isset($this->_props["font_size"])) {
1015
            $fs = self::$_defaults["font_size"];
1016
        } else {
1017
            $fs = $this->_props["font_size"];
1018
        }
1019
1020
        if (!isset($this->_parent_font_size)) {
1021
            $this->_parent_font_size = self::$default_font_size;
1022
        }
1023
1024
        switch ((string)$fs) {
1025
            case "xx-small":
1026
            case "x-small":
1027
            case "small":
1028
            case "medium":
1029
            case "large":
1030
            case "x-large":
1031
            case "xx-large":
1032
                $fs = self::$default_font_size * self::$font_size_keywords[$fs];
1033
                break;
1034
1035
            case "smaller":
1036
                $fs = 8 / 9 * $this->_parent_font_size;
1037
                break;
1038
1039
            case "larger":
1040
                $fs = 6 / 5 * $this->_parent_font_size;
1041
                break;
1042
1043
            default:
1044
                break;
1045
        }
1046
1047
        // Ensure relative sizes resolve to something
1048
        if (($i = mb_strpos($fs, "em")) !== false) {
1049
            $fs = (float)mb_substr($fs, 0, $i) * $this->_parent_font_size;
1050
        } else if (($i = mb_strpos($fs, "ex")) !== false) {
1051
            $fs = (float)mb_substr($fs, 0, $i) * $this->_parent_font_size;
1052
        } else {
1053
            $fs = (float)$this->length_in_pt($fs);
1054
        }
1055
1056
        //see __set and __get, on all assignments clear cache!
1057
        $this->_prop_cache["font_size"] = null;
1058
        $this->_props["font_size"] = $fs;
1059
        $this->__font_size_calculated = true;
1060
        return $this->_props["font_size"];
1061
1062
    }
1063
1064
    /**
1065
     * @link http://www.w3.org/TR/CSS21/text.html#propdef-word-spacing
1066
     * @return float
1067
     */
1068
    function get_word_spacing()
1069
    {
1070
        if ($this->_props["word_spacing"] === "normal") {
1071
            return 0;
1072
        }
1073
1074
        return $this->_props["word_spacing"];
1075
    }
1076
1077
    /**
1078
     * @link http://www.w3.org/TR/CSS21/text.html#propdef-letter-spacing
1079
     * @return float
1080
     */
1081
    function get_letter_spacing()
1082
    {
1083
        if ($this->_props["letter_spacing"] === "normal") {
1084
            return 0;
1085
        }
1086
1087
        return $this->_props["letter_spacing"];
1088
    }
1089
1090
    /**
1091
     * @link http://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
1092
     * @return float
1093
     */
1094
    function get_line_height()
1095
    {
1096
        if (array_key_exists("line_height", $this->_props) === false) {
1097
            $this->_props["line_height"] = self::$_defaults["line_height"];
1098
        }
1099
        $line_height = $this->_props["line_height"];
1100
1101
        if ($line_height === "normal") {
1102
            return self::$default_line_height * $this->get_font_size();
1103
        }
1104
1105
        if (is_numeric($line_height)) {
1106
            return $this->length_in_pt($line_height . "em", $this->get_font_size());
1107
        }
1108
1109
        return $this->length_in_pt($line_height, $this->_parent_font_size);
1110
    }
1111
1112
    /**
1113
     * Returns the color as an array
1114
     *
1115
     * The array has the following format:
1116
     * <code>array(r,g,b, "r" => r, "g" => g, "b" => b, "hex" => "#rrggbb")</code>
1117
     *
1118
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
1119
     * @return array
1120
     */
1121
    function get_color()
1122
    {
1123
        return $this->munge_color($this->_props["color"]);
1124
    }
1125
1126
    /**
1127
     * Returns the background color as an array
1128
     *
1129
     * The returned array has the same format as {@link Style::get_color()}
1130
     *
1131
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
1132
     * @return array
1133
     */
1134
    function get_background_color()
1135
    {
1136
        return $this->munge_color($this->_props["background_color"]);
1137
    }
1138
1139
    /**
1140
     * Returns the background position as an array
1141
     *
1142
     * The returned array has the following format:
1143
     * <code>array(x,y, "x" => x, "y" => y)</code>
1144
     *
1145
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
1146
     * @return array
1147
     */
1148
    function get_background_position()
1149
    {
1150
        $tmp = explode(" ", $this->_props["background_position"]);
1151
1152
        switch ($tmp[0]) {
1153
            case "left":
1154
                $x = "0%";
1155
                break;
1156
1157
            case "right":
1158
                $x = "100%";
1159
                break;
1160
1161
            case "top":
1162
                $y = "0%";
1163
                break;
1164
1165
            case "bottom":
1166
                $y = "100%";
1167
                break;
1168
1169
            case "center":
1170
                $x = "50%";
1171
                $y = "50%";
1172
                break;
1173
1174
            default:
1175
                $x = $tmp[0];
1176
                break;
1177
        }
1178
1179
        if (isset($tmp[1])) {
1180
            switch ($tmp[1]) {
1181
                case "left":
1182
                    $x = "0%";
1183
                    break;
1184
1185
                case "right":
1186
                    $x = "100%";
1187
                    break;
1188
1189
                case "top":
1190
                    $y = "0%";
1191
                    break;
1192
1193
                case "bottom":
1194
                    $y = "100%";
1195
                    break;
1196
1197
                case "center":
1198
                    if ($tmp[0] === "left" || $tmp[0] === "right" || $tmp[0] === "center") {
1199
                        $y = "50%";
1200
                    } else {
1201
                        $x = "50%";
1202
                    }
1203
                    break;
1204
1205
                default:
1206
                    $y = $tmp[1];
1207
                    break;
1208
            }
1209
        } else {
1210
            $y = "50%";
1211
        }
1212
1213
        if (!isset($x)) {
1214
            $x = "0%";
1215
        }
1216
1217
        if (!isset($y)) {
1218
            $y = "0%";
1219
        }
1220
1221
        return array(
1222
            0 => $x, "x" => $x,
1223
            1 => $y, "y" => $y,
1224
        );
1225
    }
1226
1227
1228
    /**
1229
     * Returns the background as it is currently stored
1230
     *
1231
     * (currently anyway only for completeness.
1232
     * not used for further processing)
1233
     *
1234
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
1235
     * @return string
1236
     */
1237
    function get_background_attachment()
1238
    {
1239
        return $this->_props["background_attachment"];
1240
    }
1241
1242
1243
    /**
1244
     * Returns the background_repeat as it is currently stored
1245
     *
1246
     * (currently anyway only for completeness.
1247
     * not used for further processing)
1248
     *
1249
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
1250
     * @return string
1251
     */
1252
    function get_background_repeat()
1253
    {
1254
        return $this->_props["background_repeat"];
1255
    }
1256
1257
1258
    /**
1259
     * Returns the background as it is currently stored
1260
     *
1261
     * (currently anyway only for completeness.
1262
     * not used for further processing, but the individual get_background_xxx)
1263
     *
1264
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background
1265
     * @return string
1266
     */
1267
    function get_background()
1268
    {
1269
        return $this->_props["background"];
1270
    }
1271
1272
1273
    /**#@+
1274
     * Returns the border color as an array
1275
     *
1276
     * See {@link Style::get_color()}
1277
     *
1278
     * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties
1279
     * @return array
1280
     */
1281
    function get_border_top_color()
1282
    {
1283
        if ($this->_props["border_top_color"] === "") {
1284
            //see __set and __get, on all assignments clear cache!
1285
            $this->_prop_cache["border_top_color"] = null;
1286
            $this->_props["border_top_color"] = $this->__get("color");
1287
        }
1288
1289
        return $this->munge_color($this->_props["border_top_color"]);
1290
    }
1291
1292
    /**
1293
     * @return array
1294
     */
1295
    function get_border_right_color()
1296
    {
1297
        if ($this->_props["border_right_color"] === "") {
1298
            //see __set and __get, on all assignments clear cache!
1299
            $this->_prop_cache["border_right_color"] = null;
1300
            $this->_props["border_right_color"] = $this->__get("color");
1301
        }
1302
1303
        return $this->munge_color($this->_props["border_right_color"]);
1304
    }
1305
1306
    /**
1307
     * @return array
1308
     */
1309
    function get_border_bottom_color()
1310
    {
1311
        if ($this->_props["border_bottom_color"] === "") {
1312
            //see __set and __get, on all assignments clear cache!
1313
            $this->_prop_cache["border_bottom_color"] = null;
1314
            $this->_props["border_bottom_color"] = $this->__get("color");
1315
        }
1316
1317
        return $this->munge_color($this->_props["border_bottom_color"]);
1318
    }
1319
1320
    /**
1321
     * @return array
1322
     */
1323
    function get_border_left_color()
1324
    {
1325
        if ($this->_props["border_left_color"] === "") {
1326
            //see __set and __get, on all assignments clear cache!
1327
            $this->_prop_cache["border_left_color"] = null;
1328
            $this->_props["border_left_color"] = $this->__get("color");
1329
        }
1330
1331
        return $this->munge_color($this->_props["border_left_color"]);
1332
    }
1333
1334
    /**#@-*/
1335
1336
    /**#@+
1337
     * Returns the border width, as it is currently stored
1338
     *
1339
     * @link http://www.w3.org/TR/CSS21/box.html#border-width-properties
1340
     * @return float|string
1341
     */
1342
    function get_border_top_width()
1343
    {
1344
        $style = $this->__get("border_top_style");
1345
        return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_top_width"]) : 0;
1346
    }
1347
1348
    /**
1349
     * @return float|int|string
1350
     */
1351
    function get_border_right_width()
1352
    {
1353
        $style = $this->__get("border_right_style");
1354
        return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_right_width"]) : 0;
1355
    }
1356
1357
    /**
1358
     * @return float|int|string
1359
     */
1360
    function get_border_bottom_width()
1361
    {
1362
        $style = $this->__get("border_bottom_style");
1363
        return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_bottom_width"]) : 0;
1364
    }
1365
1366
    /**
1367
     * @return float|int|string
1368
     */
1369
    function get_border_left_width()
1370
    {
1371
        $style = $this->__get("border_left_style");
1372
        return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_left_width"]) : 0;
1373
    }
1374
    /**#@-*/
1375
1376
    /**
1377
     * Return an array of all border properties.
1378
     *
1379
     * The returned array has the following structure:
1380
     * <code>
1381
     * array("top" => array("width" => [border-width],
1382
     *                      "style" => [border-style],
1383
     *                      "color" => [border-color (array)]),
1384
     *       "bottom" ... )
1385
     * </code>
1386
     *
1387
     * @return array
1388
     */
1389
    function get_border_properties()
1390
    {
1391
        return array(
1392
            "top" => array(
1393
                "width" => $this->__get("border_top_width"),
1394
                "style" => $this->__get("border_top_style"),
1395
                "color" => $this->__get("border_top_color"),
1396
            ),
1397
            "bottom" => array(
1398
                "width" => $this->__get("border_bottom_width"),
1399
                "style" => $this->__get("border_bottom_style"),
1400
                "color" => $this->__get("border_bottom_color"),
1401
            ),
1402
            "right" => array(
1403
                "width" => $this->__get("border_right_width"),
1404
                "style" => $this->__get("border_right_style"),
1405
                "color" => $this->__get("border_right_color"),
1406
            ),
1407
            "left" => array(
1408
                "width" => $this->__get("border_left_width"),
1409
                "style" => $this->__get("border_left_style"),
1410
                "color" => $this->__get("border_left_color"),
1411
            ),
1412
        );
1413
    }
1414
1415
    /**
1416
     * Return a single border property
1417
     *
1418
     * @param string $side
1419
     *
1420
     * @return mixed
1421
     */
1422
    protected function _get_border($side)
1423
    {
1424
        $color = $this->__get("border_" . $side . "_color");
1425
1426
        return $this->__get("border_" . $side . "_width") . " " .
1427
        $this->__get("border_" . $side . "_style") . " " . $color["hex"];
1428
    }
1429
1430
    /**#@+
1431
     * Return full border properties as a string
1432
     *
1433
     * Border properties are returned just as specified in CSS:
1434
     * <pre>[width] [style] [color]</pre>
1435
     * e.g. "1px solid blue"
1436
     *
1437
     * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
1438
     * @return string
1439
     */
1440
    function get_border_top()
1441
    {
1442
        return $this->_get_border("top");
1443
    }
1444
1445
    /**
1446
     * @return mixed
1447
     */
1448
    function get_border_right()
1449
    {
1450
        return $this->_get_border("right");
1451
    }
1452
1453
    /**
1454
     * @return mixed
1455
     */
1456
    function get_border_bottom()
1457
    {
1458
        return $this->_get_border("bottom");
1459
    }
1460
1461
    /**
1462
     * @return mixed
1463
     */
1464
    function get_border_left()
1465
    {
1466
        return $this->_get_border("left");
1467
    }
1468
1469
    /**
1470
     * @param $w
1471
     * @param $h
1472
     * @return array|null
1473
     */
1474
    function get_computed_border_radius($w, $h)
1475
    {
1476
        if (!empty($this->_computed_border_radius)) {
1477
            return $this->_computed_border_radius;
1478
        }
1479
1480
        $w = (float)$w;
1481
        $h = (float)$h;
1482
        $rTL = (float)$this->__get("border_top_left_radius");
1483
        $rTR = (float)$this->__get("border_top_right_radius");
1484
        $rBL = (float)$this->__get("border_bottom_left_radius");
1485
        $rBR = (float)$this->__get("border_bottom_right_radius");
1486
1487
        if ($rTL + $rTR + $rBL + $rBR == 0) {
1488
            return $this->_computed_border_radius = array(
1489
                0, 0, 0, 0,
1490
                "top-left" => 0,
1491
                "top-right" => 0,
1492
                "bottom-right" => 0,
1493
                "bottom-left" => 0,
1494
            );
1495
        }
1496
1497
        $t = (float)$this->__get("border_top_width");
1498
        $r = (float)$this->__get("border_right_width");
1499
        $b = (float)$this->__get("border_bottom_width");
1500
        $l = (float)$this->__get("border_left_width");
1501
1502
        $rTL = min($rTL, $h - $rBL - $t / 2 - $b / 2, $w - $rTR - $l / 2 - $r / 2);
1503
        $rTR = min($rTR, $h - $rBR - $t / 2 - $b / 2, $w - $rTL - $l / 2 - $r / 2);
1504
        $rBL = min($rBL, $h - $rTL - $t / 2 - $b / 2, $w - $rBR - $l / 2 - $r / 2);
1505
        $rBR = min($rBR, $h - $rTR - $t / 2 - $b / 2, $w - $rBL - $l / 2 - $r / 2);
1506
1507
        return $this->_computed_border_radius = array(
1508
            $rTL, $rTR, $rBR, $rBL,
1509
            "top-left" => $rTL,
1510
            "top-right" => $rTR,
1511
            "bottom-right" => $rBR,
1512
            "bottom-left" => $rBL,
1513
        );
1514
    }
1515
1516
    /**
1517
     * Returns the outline color as an array
1518
     *
1519
     * See {@link Style::get_color()}
1520
     *
1521
     * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties
1522
     * @return array
1523
     */
1524
    function get_outline_color()
1525
    {
1526
        if ($this->_props["outline_color"] === "") {
1527
            //see __set and __get, on all assignments clear cache!
1528
            $this->_prop_cache["outline_color"] = null;
1529
            $this->_props["outline_color"] = $this->__get("color");
1530
        }
1531
1532
        return $this->munge_color($this->_props["outline_color"]);
1533
    }
1534
1535
    /**#@+
1536
     * Returns the outline width, as it is currently stored
1537
     * @return float|string
1538
     */
1539
    function get_outline_width()
1540
    {
1541
        $style = $this->__get("outline_style");
1542
        return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["outline_width"]) : 0;
1543
    }
1544
1545
    /**#@+
1546
     * Return full outline properties as a string
1547
     *
1548
     * Outline properties are returned just as specified in CSS:
1549
     * <pre>[width] [style] [color]</pre>
1550
     * e.g. "1px solid blue"
1551
     *
1552
     * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
1553
     * @return string
1554
     */
1555
    function get_outline()
1556
    {
1557
        $color = $this->__get("outline_color");
1558
        return
1559
            $this->__get("outline_width") . " " .
1560
            $this->__get("outline_style") . " " .
1561
            $color["hex"];
1562
    }
1563
    /**#@-*/
1564
1565
    /**
1566
     * Returns border spacing as an array
1567
     *
1568
     * The array has the format (h_space,v_space)
1569
     *
1570
     * @link http://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing
1571
     * @return array
1572
     */
1573
    function get_border_spacing()
1574
    {
1575
        $arr = explode(" ", $this->_props["border_spacing"]);
1576
        if (count($arr) == 1) {
1577
            $arr[1] = $arr[0];
1578
        }
1579
        return $arr;
1580
    }
1581
1582
    /*==============================*/
1583
1584
    /*
1585
     !important attribute
1586
     For basic functionality of the !important attribute with overloading
1587
     of several styles of an element, changes in inherit(), merge() and _parse_properties()
1588
     are sufficient [helpers var $_important_props, __construct(), important_set(), important_get()]
1589
1590
     Only for combined attributes extra treatment needed. See below.
1591
1592
     div { border: 1px red; }
1593
     div { border: solid; } // Not combined! Only one occurence of same style per context
1594
     //
1595
     div { border: 1px red; }
1596
     div a { border: solid; } // Adding to border style ok by inheritance
1597
     //
1598
     div { border-style: solid; } // Adding to border style ok because of different styles
1599
     div { border: 1px red; }
1600
     //
1601
     div { border-style: solid; !important} // border: overrides, even though not !important
1602
     div { border: 1px dashed red; }
1603
     //
1604
     div { border: 1px red; !important }
1605
     div a { border-style: solid; } // Need to override because not set
1606
1607
     Special treatment:
1608
     At individual property like border-top-width need to check whether overriding value is also !important.
1609
     Also store the !important condition for later overrides.
1610
     Since not known who is initiating the override, need to get passed !important as parameter.
1611
     !important Paramter taken as in the original style in the css file.
1612
     When property border !important given, do not mark subsets like border_style as important. Only
1613
     individual properties.
1614
1615
     Note:
1616
     Setting individual property directly from css with e.g. set_border_top_style() is not needed, because
1617
     missing set funcions handled by a generic handler __set(), including the !important.
1618
     Setting individual property of as sub-property is handled below.
1619
1620
     Implementation see at _set_style_side_type()
1621
     Callers _set_style_sides_type(), _set_style_type, _set_style_type_important()
1622
1623
     Related functionality for background, padding, margin, font, list_style
1624
    */
1625
1626
    /**
1627
     * Generalized set function for individual attribute of combined style.
1628
     * With check for !important
1629
     * Applicable for background, border, padding, margin, font, list_style
1630
     *
1631
     * Note: $type has a leading underscore (or is empty), the others not.
1632
     *
1633
     * @param $style
1634
     * @param $side
1635
     * @param $type
1636
     * @param $val
1637
     * @param $important
1638
     */
1639
    protected function _set_style_side_type($style, $side, $type, $val, $important)
1640
    {
1641
        $prop = $style . '_' . $side . $type;
1642
1643
        if (!isset($this->_important_props[$prop]) || $important) {
1644
            if ($side === "bottom") {
1645
                $this->_computed_bottom_spacing = null; //reset computed cache, border style can disable/enable border calculations
1646
            }
1647
            //see __set and __get, on all assignments clear cache!
1648
            $this->_prop_cache[$prop] = null;
1649
            if ($important) {
1650
                $this->_important_props[$prop] = true;
1651
            }
1652
            $this->_props[$prop] = $val;
1653
        }
1654
    }
1655
1656
    /**
1657
     * @param $style
1658
     * @param $top
1659
     * @param $right
1660
     * @param $bottom
1661
     * @param $left
1662
     * @param $type
1663
     * @param $important
1664
     */
1665
    protected function _set_style_sides_type($style, $top, $right, $bottom, $left, $type, $important)
1666
    {
1667
        $this->_set_style_side_type($style, 'top', $type, $top, $important);
1668
        $this->_set_style_side_type($style, 'right', $type, $right, $important);
1669
        $this->_set_style_side_type($style, 'bottom', $type, $bottom, $important);
1670
        $this->_set_style_side_type($style, 'left', $type, $left, $important);
1671
    }
1672
1673
    /**
1674
     * @param $style
1675
     * @param $type
1676
     * @param $val
1677
     * @param $important
1678
     */
1679
    protected function _set_style_type($style, $type, $val, $important)
1680
    {
1681
        $val = preg_replace("/\s*\,\s*/", ",", $val); // when rgb() has spaces
1682
        $arr = explode(" ", $val);
1683
1684
        switch (count($arr)) {
1685
            case 1:
1686
                $this->_set_style_sides_type($style, $arr[0], $arr[0], $arr[0], $arr[0], $type, $important);
1687
                break;
1688
            case 2:
1689
                $this->_set_style_sides_type($style, $arr[0], $arr[1], $arr[0], $arr[1], $type, $important);
1690
                break;
1691
            case 3:
1692
                $this->_set_style_sides_type($style, $arr[0], $arr[1], $arr[2], $arr[1], $type, $important);
1693
                break;
1694
            case 4:
1695
                $this->_set_style_sides_type($style, $arr[0], $arr[1], $arr[2], $arr[3], $type, $important);
1696
                break;
1697
        }
1698
1699
        //see __set and __get, on all assignments clear cache!
1700
        $this->_prop_cache[$style . $type] = null;
1701
        $this->_props[$style . $type] = $val;
1702
    }
1703
1704
    /**
1705
     * @param $style
1706
     * @param $type
1707
     * @param $val
1708
     */
1709
    protected function _set_style_type_important($style, $type, $val)
1710
    {
1711
        $this->_set_style_type($style, $type, $val, isset($this->_important_props[$style . $type]));
1712
    }
1713
1714
    /**
1715
     * Anyway only called if _important matches and is assigned
1716
     * E.g. _set_style_side_type($style,$side,'',str_replace("none", "0px", $val),isset($this->_important_props[$style.'_'.$side]));
1717
     *
1718
     * @param $style
1719
     * @param $side
1720
     * @param $val
1721
     */
1722
    protected function _set_style_side_width_important($style, $side, $val)
1723
    {
1724
        if ($side === "bottom") {
1725
            $this->_computed_bottom_spacing = null; //reset cache for any bottom width changes
1726
        }
1727
        //see __set and __get, on all assignments clear cache!
1728
        $this->_prop_cache[$style . '_' . $side] = null;
1729
        $this->_props[$style . '_' . $side] = str_replace("none", "0px", $val);
1730
    }
1731
1732
    /**
1733
     * @param $style
1734
     * @param $val
1735
     * @param $important
1736
     */
1737
    protected function _set_style($style, $val, $important)
1738
    {
1739
        if (!isset($this->_important_props[$style]) || $important) {
1740
            if ($important) {
1741
                $this->_important_props[$style] = true;
1742
            }
1743
            //see __set and __get, on all assignments clear cache!
1744
            $this->_prop_cache[$style] = null;
1745
            $this->_props[$style] = $val;
1746
        }
1747
    }
1748
1749
    /**
1750
     * @param $val
1751
     * @return string
1752
     */
1753
    protected function _image($val)
1754
    {
1755
        $DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss();
1756
        $parsed_url = "none";
1757
1758
        if (mb_strpos($val, "url") === false) {
1759
            $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none
1760
        } else {
1761
            $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val));
1762
1763
            // Resolve the url now in the context of the current stylesheet
1764
            $parsed_url = Helpers::explode_url($val);
1765
            if ($parsed_url["protocol"] == "" && $this->_stylesheet->get_protocol() == "") {
1766
                if ($parsed_url["path"][0] === '/' || $parsed_url["path"][0] === '\\') {
1767
                    $path = $_SERVER["DOCUMENT_ROOT"] . '/';
1768
                } else {
1769
                    $path = $this->_stylesheet->get_base_path();
1770
                }
1771
1772
                $path .= $parsed_url["path"] . $parsed_url["file"];
1773
                $path = realpath($path);
1774
                // If realpath returns FALSE then specifically state that there is no background image
1775
                if (!$path) {
1776
                    $path = 'none';
1777
                }
1778
            } else {
1779
                $path = Helpers::build_url($this->_stylesheet->get_protocol(),
1780
                    $this->_stylesheet->get_host(),
1781
                    $this->_stylesheet->get_base_path(),
1782
                    $val);
1783
            }
1784
        }
1785
        if ($DEBUGCSS) {
1786
            print "<pre>[_image\n";
1787
            print_r($parsed_url);
1788
            print $this->_stylesheet->get_protocol() . "\n" . $this->_stylesheet->get_base_path() . "\n" . $path . "\n";
1789
            print "_image]</pre>";;
1790
        }
1791
        return $path;
1792
    }
1793
1794
    /*======================*/
1795
1796
    /**
1797
     * Sets color
1798
     *
1799
     * The color parameter can be any valid CSS color value
1800
     *
1801
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
1802
     * @param string $color
1803
     */
1804
    function set_color($color)
1805
    {
1806
        $col = $this->munge_color($color);
1807
1808
        if (is_null($col) || !isset($col["hex"])) {
1809
            $color = "inherit";
1810
        } else {
1811
            $color = $col["hex"];
1812
        }
1813
1814
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1815
        $this->_prop_cache["color"] = null;
1816
        $this->_props["color"] = $color;
1817
    }
1818
1819
    /**
1820
     * Sets the background color
1821
     *
1822
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
1823
     * @param string $color
1824
     */
1825
    function set_background_color($color)
1826
    {
1827
        $col = $this->munge_color($color);
1828
1829
        if (is_null($col)) {
1830
            return;
1831
            //$col = self::$_defaults["background_color"];
1832
        }
1833
1834
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1835
        $this->_prop_cache["background_color"] = null;
1836
        $this->_props["background_color"] = is_array($col) ? $col["hex"] : $col;
1837
    }
1838
1839
    /**
1840
     * Set the background image url
1841
     * @link     http://www.w3.org/TR/CSS21/colors.html#background-properties
1842
     *
1843
     * @param string $val
1844
     */
1845
    function set_background_image($val)
1846
    {
1847
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1848
        $this->_prop_cache["background_image"] = null;
1849
        $this->_props["background_image"] = $this->_image($val);
1850
    }
1851
1852
    /**
1853
     * Sets the background repeat
1854
     *
1855
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
1856
     * @param string $val
1857
     */
1858
    function set_background_repeat($val)
1859
    {
1860
        if (is_null($val)) {
1861
            $val = self::$_defaults["background_repeat"];
1862
        }
1863
1864
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1865
        $this->_prop_cache["background_repeat"] = null;
1866
        $this->_props["background_repeat"] = $val;
1867
    }
1868
1869
    /**
1870
     * Sets the background attachment
1871
     *
1872
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
1873
     * @param string $val
1874
     */
1875
    function set_background_attachment($val)
1876
    {
1877
        if (is_null($val)) {
1878
            $val = self::$_defaults["background_attachment"];
1879
        }
1880
1881
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1882
        $this->_prop_cache["background_attachment"] = null;
1883
        $this->_props["background_attachment"] = $val;
1884
    }
1885
1886
    /**
1887
     * Sets the background position
1888
     *
1889
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
1890
     * @param string $val
1891
     */
1892
    function set_background_position($val)
1893
    {
1894
        if (is_null($val)) {
1895
            $val = self::$_defaults["background_position"];
1896
        }
1897
1898
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1899
        $this->_prop_cache["background_position"] = null;
1900
        $this->_props["background_position"] = $val;
1901
    }
1902
1903
    /**
1904
     * Sets the background - combined options
1905
     *
1906
     * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background
1907
     * @param string $val
1908
     */
1909
    function set_background($val)
1910
    {
1911
        $val = trim($val);
1912
        $important = isset($this->_important_props["background"]);
1913
1914
        if ($val === "none") {
1915
            $this->_set_style("background_image", "none", $important);
1916
            $this->_set_style("background_color", "transparent", $important);
1917
        } else {
1918
            $pos = array();
1919
            $tmp = preg_replace("/\s*\,\s*/", ",", $val); // when rgb() has spaces
1920
            $tmp = preg_split("/\s+/", $tmp);
1921
1922
            foreach ($tmp as $attr) {
1923
                if (mb_substr($attr, 0, 3) === "url" || $attr === "none") {
1924
                    $this->_set_style("background_image", $this->_image($attr), $important);
1925
                } elseif ($attr === "fixed" || $attr === "scroll") {
1926
                    $this->_set_style("background_attachment", $attr, $important);
1927
                } elseif ($attr === "repeat" || $attr === "repeat-x" || $attr === "repeat-y" || $attr === "no-repeat") {
1928
                    $this->_set_style("background_repeat", $attr, $important);
1929
                } elseif (($col = $this->munge_color($attr)) != null) {
1930
                    $this->_set_style("background_color", is_array($col) ? $col["hex"] : $col, $important);
1931
                } else {
1932
                    $pos[] = $attr;
1933
                }
1934
            }
1935
1936
            if (count($pos)) {
1937
                $this->_set_style("background_position", implode(" ", $pos), $important);
1938
            }
1939
        }
1940
1941
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1942
        $this->_prop_cache["background"] = null;
1943
        $this->_props["background"] = $val;
1944
    }
1945
1946
    /**
1947
     * Sets the font size
1948
     *
1949
     * $size can be any acceptable CSS size
1950
     *
1951
     * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
1952
     * @param string|float $size
1953
     */
1954
    function set_font_size($size)
1955
    {
1956
        $this->__font_size_calculated = false;
1957
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1958
        $this->_prop_cache["font_size"] = null;
1959
        $this->_props["font_size"] = $size;
1960
    }
1961
1962
    /**
1963
     * Sets the font style
1964
     *
1965
     * combined attributes
1966
     * set individual attributes also, respecting !important mark
1967
     * exactly this order, separate by space. Multiple fonts separated by comma:
1968
     * font-style, font-variant, font-weight, font-size, line-height, font-family
1969
     *
1970
     * Other than with border and list, existing partial attributes should
1971
     * reset when starting here, even when not mentioned.
1972
     * If individual attribute is !important and explicite or implicite replacement is not,
1973
     * keep individual attribute
1974
     *
1975
     * require whitespace as delimiters for single value attributes
1976
     * On delimiter "/" treat first as font height, second as line height
1977
     * treat all remaining at the end of line as font
1978
     * font-style, font-variant, font-weight, font-size, line-height, font-family
1979
     *
1980
     * missing font-size and font-family might be not allowed, but accept it here and
1981
     * use default (medium size, enpty font name)
1982
     *
1983
     * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style
1984
     * @param $val
1985
     */
1986
    function set_font($val)
1987
    {
1988
        $this->__font_size_calculated = false;
1989
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1990
        $this->_prop_cache["font"] = null;
1991
        $this->_props["font"] = $val;
1992
1993
        $important = isset($this->_important_props["font"]);
1994
1995
        if (preg_match("/^(italic|oblique|normal)\s*(.*)$/i", $val, $match)) {
1996
            $this->_set_style("font_style", $match[1], $important);
1997
            $val = $match[2];
1998
        } else {
1999
            $this->_set_style("font_style", self::$_defaults["font_style"], $important);
2000
        }
2001
2002
        if (preg_match("/^(small-caps|normal)\s*(.*)$/i", $val, $match)) {
2003
            $this->_set_style("font_variant", $match[1], $important);
2004
            $val = $match[2];
2005
        } else {
2006
            $this->_set_style("font_variant", self::$_defaults["font_variant"], $important);
2007
        }
2008
2009
        //matching numeric value followed by unit -> this is indeed a subsequent font size. Skip!
2010
        if (preg_match("/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900|normal)\s*(.*)$/i", $val, $match) &&
2011
            !preg_match("/^(?:pt|px|pc|em|ex|in|cm|mm|%)/", $match[2])
2012
        ) {
2013
            $this->_set_style("font_weight", $match[1], $important);
2014
            $val = $match[2];
2015
        } else {
2016
            $this->_set_style("font_weight", self::$_defaults["font_weight"], $important);
2017
        }
2018
2019
        if (preg_match("/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))(?:\/|\s*)(.*)$/i", $val, $match)) {
2020
            $this->_set_style("font_size", $match[1], $important);
2021
            $val = $match[2];
2022
            if (preg_match("/^(?:\/|\s*)(\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%)?)\s*(.*)$/i", $val, $match)) {
2023
                $this->_set_style("line_height", $match[1], $important);
2024
                $val = $match[2];
2025
            } else {
2026
                $this->_set_style("line_height", self::$_defaults["line_height"], $important);
2027
            }
2028
        } else {
2029
            $this->_set_style("font_size", self::$_defaults["font_size"], $important);
2030
            $this->_set_style("line_height", self::$_defaults["line_height"], $important);
2031
        }
2032
2033
        if (strlen($val) != 0) {
2034
            $this->_set_style("font_family", $val, $important);
2035
        } else {
2036
            $this->_set_style("font_family", self::$_defaults["font_family"], $important);
2037
        }
2038
    }
2039
2040
    /**
2041
     * Sets page break properties
2042
     *
2043
     * @link http://www.w3.org/TR/CSS21/page.html#page-breaks
2044
     * @param string $break
2045
     */
2046
    function set_page_break_before($break)
2047
    {
2048
        if ($break === "left" || $break === "right") {
2049
            $break = "always";
2050
        }
2051
2052
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
2053
        $this->_prop_cache["page_break_before"] = null;
2054
        $this->_props["page_break_before"] = $break;
2055
    }
2056
2057
    /**
2058
     * @param $break
2059
     */
2060
    function set_page_break_after($break)
2061
    {
2062
        if ($break === "left" || $break === "right") {
2063
            $break = "always";
2064
        }
2065
2066
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
2067
        $this->_prop_cache["page_break_after"] = null;
2068
        $this->_props["page_break_after"] = $break;
2069
    }
2070
2071
    /**
2072
     * Sets the margin size
2073
     *
2074
     * @link http://www.w3.org/TR/CSS21/box.html#margin-properties
2075
     * @param $val
2076
     */
2077
    function set_margin_top($val)
2078
    {
2079
        $this->_set_style_side_width_important('margin', 'top', $val);
2080
    }
2081
2082
    /**
2083
     * @param $val
2084
     */
2085
    function set_margin_right($val)
2086
    {
2087
        $this->_set_style_side_width_important('margin', 'right', $val);
2088
    }
2089
2090
    /**
2091
     * @param $val
2092
     */
2093
    function set_margin_bottom($val)
2094
    {
2095
        $this->_set_style_side_width_important('margin', 'bottom', $val);
2096
    }
2097
2098
    /**
2099
     * @param $val
2100
     */
2101
    function set_margin_left($val)
2102
    {
2103
        $this->_set_style_side_width_important('margin', 'left', $val);
2104
    }
2105
2106
    /**
2107
     * @param $val
2108
     */
2109
    function set_margin($val)
2110
    {
2111
        $val = str_replace("none", "0px", $val);
2112
        $this->_set_style_type_important('margin', '', $val);
2113
    }
2114
2115
    /**
2116
     * Sets the padding size
2117
     *
2118
     * @link http://www.w3.org/TR/CSS21/box.html#padding-properties
2119
     * @param $val
2120
     */
2121
    function set_padding_top($val)
2122
    {
2123
        $this->_set_style_side_width_important('padding', 'top', $val);
2124
    }
2125
2126
    /**
2127
     * @param $val
2128
     */
2129
    function set_padding_right($val)
2130
    {
2131
        $this->_set_style_side_width_important('padding', 'right', $val);
2132
    }
2133
2134
    /**
2135
     * @param $val
2136
     */
2137
    function set_padding_bottom($val)
2138
    {
2139
        $this->_set_style_side_width_important('padding', 'bottom', $val);
2140
    }
2141
2142
    /**
2143
     * @param $val
2144
     */
2145
    function set_padding_left($val)
2146
    {
2147
        $this->_set_style_side_width_important('padding', 'left', $val);
2148
    }
2149
2150
    /**
2151
     * @param $val
2152
     */
2153
    function set_padding($val)
2154
    {
2155
        $val = str_replace("none", "0px", $val);
2156
        $this->_set_style_type_important('padding', '', $val);
2157
    }
2158
    /**#@-*/
2159
2160
    /**
2161
     * Sets a single border
2162
     *
2163
     * @param string $side
2164
     * @param string $border_spec ([width] [style] [color])
2165
     * @param boolean $important
2166
     */
2167
    protected function _set_border($side, $border_spec, $important)
2168
    {
2169
        $border_spec = preg_replace("/\s*\,\s*/", ",", $border_spec);
2170
        //$border_spec = str_replace(",", " ", $border_spec); // Why did we have this ?? rbg(10, 102, 10) > rgb(10  102  10)
2171
        $arr = explode(" ", $border_spec);
2172
2173
        // FIXME: handle partial values
2174
2175
        //For consistency of individal and combined properties, and with ie8 and firefox3
2176
        //reset all attributes, even if only partially given
2177
        $this->_set_style_side_type('border', $side, '_style', self::$_defaults['border_' . $side . '_style'], $important);
2178
        $this->_set_style_side_type('border', $side, '_width', self::$_defaults['border_' . $side . '_width'], $important);
2179
        $this->_set_style_side_type('border', $side, '_color', self::$_defaults['border_' . $side . '_color'], $important);
2180
2181
        foreach ($arr as $value) {
2182
            $value = trim($value);
2183
            if (in_array($value, self::$BORDER_STYLES)) {
2184
                $this->_set_style_side_type('border', $side, '_style', $value, $important);
2185
            } else if (preg_match("/[.0-9]+(?:px|pt|pc|em|ex|%|in|mm|cm)|(?:thin|medium|thick)/", $value)) {
2186
                $this->_set_style_side_type('border', $side, '_width', $value, $important);
2187
            } else {
2188
                // must be color
2189
                $this->_set_style_side_type('border', $side, '_color', $value, $important);
2190
            }
2191
        }
2192
2193
        //see __set and __get, on all assignments clear cache!
2194
        $this->_prop_cache['border_' . $side] = null;
2195
        $this->_props['border_' . $side] = $border_spec;
2196
    }
2197
2198
    /**
2199
     * Sets the border styles
2200
     *
2201
     * @link http://www.w3.org/TR/CSS21/box.html#border-properties
2202
     * @param string $val
2203
     */
2204
    function set_border_top($val)
2205
    {
2206
        $this->_set_border("top", $val, isset($this->_important_props['border_top']));
2207
    }
2208
2209
    /**
2210
     * @param $val
2211
     */
2212
    function set_border_right($val)
2213
    {
2214
        $this->_set_border("right", $val, isset($this->_important_props['border_right']));
2215
    }
2216
2217
    /**
2218
     * @param $val
2219
     */
2220
    function set_border_bottom($val)
2221
    {
2222
        $this->_set_border("bottom", $val, isset($this->_important_props['border_bottom']));
2223
    }
2224
2225
    /**
2226
     * @param $val
2227
     */
2228
    function set_border_left($val)
2229
    {
2230
        $this->_set_border("left", $val, isset($this->_important_props['border_left']));
2231
    }
2232
2233
    /**
2234
     * @param $val
2235
     */
2236
    function set_border($val)
2237
    {
2238
        $important = isset($this->_important_props["border"]);
2239
        $this->_set_border("top", $val, $important);
2240
        $this->_set_border("right", $val, $important);
2241
        $this->_set_border("bottom", $val, $important);
2242
        $this->_set_border("left", $val, $important);
2243
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
2244
        $this->_prop_cache["border"] = null;
2245
        $this->_props["border"] = $val;
2246
    }
2247
2248
    /**
2249
     * @param $val
2250
     */
2251
    function set_border_width($val)
2252
    {
2253
        $this->_set_style_type_important('border', '_width', $val);
2254
    }
2255
2256
    /**
2257
     * @param $val
2258
     */
2259
    function set_border_color($val)
2260
    {
2261
        $this->_set_style_type_important('border', '_color', $val);
2262
    }
2263
2264
    /**
2265
     * @param $val
2266
     */
2267
    function set_border_style($val)
2268
    {
2269
        $this->_set_style_type_important('border', '_style', $val);
2270
    }
2271
2272
    /**
2273
     * Sets the border radius size
2274
     *
2275
     * http://www.w3.org/TR/css3-background/#corners
2276
     *
2277
     * @param $val
2278
     */
2279
    function set_border_top_left_radius($val)
2280
    {
2281
        $this->_set_border_radius_corner($val, "top_left");
2282
    }
2283
2284
    /**
2285
     * @param $val
2286
     */
2287
    function set_border_top_right_radius($val)
2288
    {
2289
        $this->_set_border_radius_corner($val, "top_right");
2290
    }
2291
2292
    /**
2293
     * @param $val
2294
     */
2295
    function set_border_bottom_left_radius($val)
2296
    {
2297
        $this->_set_border_radius_corner($val, "bottom_left");
2298
    }
2299
2300
    /**
2301
     * @param $val
2302
     */
2303
    function set_border_bottom_right_radius($val)
2304
    {
2305
        $this->_set_border_radius_corner($val, "bottom_right");
2306
    }
2307
2308
    /**
2309
     * @param $val
2310
     */
2311
    function set_border_radius($val)
2312
    {
2313
        $val = preg_replace("/\s*\,\s*/", ",", $val); // when border-radius has spaces
2314
        $arr = explode(" ", $val);
2315
2316
        switch (count($arr)) {
2317
            case 1:
2318
                $this->_set_border_radii($arr[0], $arr[0], $arr[0], $arr[0]);
2319
                break;
2320
            case 2:
2321
                $this->_set_border_radii($arr[0], $arr[1], $arr[0], $arr[1]);
2322
                break;
2323
            case 3:
2324
                $this->_set_border_radii($arr[0], $arr[1], $arr[2], $arr[1]);
2325
                break;
2326
            case 4:
2327
                $this->_set_border_radii($arr[0], $arr[1], $arr[2], $arr[3]);
2328
                break;
2329
        }
2330
    }
2331
2332
    /**
2333
     * @param $val1
2334
     * @param $val2
2335
     * @param $val3
2336
     * @param $val4
2337
     */
2338
    protected function _set_border_radii($val1, $val2, $val3, $val4)
2339
    {
2340
        $this->_set_border_radius_corner($val1, "top_left");
2341
        $this->_set_border_radius_corner($val2, "top_right");
2342
        $this->_set_border_radius_corner($val3, "bottom_right");
2343
        $this->_set_border_radius_corner($val4, "bottom_left");
2344
    }
2345
2346
    /**
2347
     * @param $val
2348
     * @param $corner
2349
     */
2350
    protected function _set_border_radius_corner($val, $corner)
2351
    {
2352
        $this->_has_border_radius = true;
2353
2354
        //see __set and __get, on all assignments clear cache!
2355
        $this->_prop_cache["border_" . $corner . "_radius"] = null;
2356
2357
        $this->_props["border_" . $corner . "_radius"] = $val;
2358
    }
2359
2360
    /**
2361
     * @return float|int|string
2362
     */
2363
    function get_border_top_left_radius()
2364
    {
2365
        return $this->_get_border_radius_corner("top_left");
2366
    }
2367
2368
    /**
2369
     * @return float|int|string
2370
     */
2371
    function get_border_top_right_radius()
2372
    {
2373
        return $this->_get_border_radius_corner("top_right");
2374
    }
2375
2376
    /**
2377
     * @return float|int|string
2378
     */
2379
    function get_border_bottom_left_radius()
2380
    {
2381
        return $this->_get_border_radius_corner("bottom_left");
2382
    }
2383
2384
    /**
2385
     * @return float|int|string
2386
     */
2387
    function get_border_bottom_right_radius()
2388
    {
2389
        return $this->_get_border_radius_corner("bottom_right");
2390
    }
2391
2392
    /**
2393
     * @param $corner
2394
     * @return float|int|string
2395
     */
2396
    protected function _get_border_radius_corner($corner)
2397
    {
2398
        if (!isset($this->_props["border_" . $corner . "_radius"]) || empty($this->_props["border_" . $corner . "_radius"])) {
2399
            return 0;
2400
        }
2401
2402
        return $this->length_in_pt($this->_props["border_" . $corner . "_radius"]);
2403
    }
2404
2405
    /**
2406
     * Sets the outline styles
2407
     *
2408
     * @link http://www.w3.org/TR/CSS21/ui.html#dynamic-outlines
2409
     * @param string $val
2410
     */
2411
    function set_outline($val)
2412
    {
2413
        $important = isset($this->_important_props["outline"]);
2414
2415
        $props = array(
2416
            "outline_style",
2417
            "outline_width",
2418
            "outline_color",
2419
        );
2420
2421
        foreach ($props as $prop) {
2422
            $_val = self::$_defaults[$prop];
2423
2424
            if (!isset($this->_important_props[$prop]) || $important) {
2425
                //see __set and __get, on all assignments clear cache!
2426
                $this->_prop_cache[$prop] = null;
2427
                if ($important) {
2428
                    $this->_important_props[$prop] = true;
2429
                }
2430
                $this->_props[$prop] = $_val;
2431
            }
2432
        }
2433
2434
        $val = preg_replace("/\s*\,\s*/", ",", $val); // when rgb() has spaces
2435
        $arr = explode(" ", $val);
2436
        foreach ($arr as $value) {
2437
            $value = trim($value);
2438
2439
            if (in_array($value, self::$BORDER_STYLES)) {
2440
                $this->set_outline_style($value);
2441
            } else if (preg_match("/[.0-9]+(?:px|pt|pc|em|ex|%|in|mm|cm)|(?:thin|medium|thick)/", $value)) {
2442
                $this->set_outline_width($value);
2443
            } else {
2444
                // must be color
2445
                $this->set_outline_color($value);
2446
            }
2447
        }
2448
2449
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
2450
        $this->_prop_cache["outline"] = null;
2451
        $this->_props["outline"] = $val;
2452
    }
2453
2454
    /**
2455
     * @param $val
2456
     */
2457
    function set_outline_width($val)
2458
    {
2459
        $this->_set_style_type_important('outline', '_width', $val);
2460
    }
2461
2462
    /**
2463
     * @param $val
2464
     */
2465
    function set_outline_color($val)
2466
    {
2467
        $this->_set_style_type_important('outline', '_color', $val);
2468
    }
2469
2470
    /**
2471
     * @param $val
2472
     */
2473
    function set_outline_style($val)
2474
    {
2475
        $this->_set_style_type_important('outline', '_style', $val);
2476
    }
2477
2478
    /**
2479
     * Sets the border spacing
2480
     *
2481
     * @link http://www.w3.org/TR/CSS21/box.html#border-properties
2482
     * @param float $val
2483
     */
2484
    function set_border_spacing($val)
2485
    {
2486
        $arr = explode(" ", $val);
2487
2488
        if (count($arr) == 1) {
2489
            $arr[1] = $arr[0];
2490
        }
2491
2492
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
2493
        $this->_prop_cache["border_spacing"] = null;
2494
        $this->_props["border_spacing"] = "$arr[0] $arr[1]";
2495
    }
2496
2497
    /**
2498
     * Sets the list style image
2499
     *
2500
     * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
2501
     * @param $val
2502
     */
2503
    function set_list_style_image($val)
2504
    {
2505
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
2506
        $this->_prop_cache["list_style_image"] = null;
2507
        $this->_props["list_style_image"] = $this->_image($val);
2508
    }
2509
2510
    /**
2511
     * Sets the list style
2512
     *
2513
     * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style
2514
     * @param $val
2515
     */
2516
    function set_list_style($val)
2517
    {
2518
        $important = isset($this->_important_props["list_style"]);
2519
        $arr = explode(" ", str_replace(",", " ", $val));
2520
2521
        static $types = array(
2522
            "disc", "circle", "square",
2523
            "decimal-leading-zero", "decimal", "1",
2524
            "lower-roman", "upper-roman", "a", "A",
2525
            "lower-greek",
2526
            "lower-latin", "upper-latin",
2527
            "lower-alpha", "upper-alpha",
2528
            "armenian", "georgian", "hebrew",
2529
            "cjk-ideographic", "hiragana", "katakana",
2530
            "hiragana-iroha", "katakana-iroha", "none"
2531
        );
2532
2533
        static $positions = array("inside", "outside");
2534
2535
        foreach ($arr as $value) {
2536
            /* http://www.w3.org/TR/CSS21/generate.html#list-style
2537
             * A value of 'none' for the 'list-style' property sets both 'list-style-type' and 'list-style-image' to 'none'
2538
             */
2539
            if ($value === "none") {
2540
                $this->_set_style("list_style_type", $value, $important);
2541
                $this->_set_style("list_style_image", $value, $important);
2542
                continue;
2543
            }
2544
2545
            //On setting or merging or inheriting list_style_image as well as list_style_type,
2546
            //and url exists, then url has precedence, otherwise fall back to list_style_type
2547
            //Firefox is wrong here (list_style_image gets overwritten on explicite list_style_type)
2548
            //Internet Explorer 7/8 and dompdf is right.
2549
2550
            if (mb_substr($value, 0, 3) === "url") {
2551
                $this->_set_style("list_style_image", $this->_image($value), $important);
2552
                continue;
2553
            }
2554
2555
            if (in_array($value, $types)) {
2556
                $this->_set_style("list_style_type", $value, $important);
2557
            } else if (in_array($value, $positions)) {
2558
                $this->_set_style("list_style_position", $value, $important);
2559
            }
2560
        }
2561
2562
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
2563
        $this->_prop_cache["list_style"] = null;
2564
        $this->_props["list_style"] = $val;
2565
    }
2566
2567
    /**
2568
     * @param $val
2569
     */
2570
    function set_size($val)
2571
    {
2572
        $length_re = "/(\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))/";
2573
2574
        $val = mb_strtolower($val);
2575
2576
        if ($val === "auto") {
2577
            return;
2578
        }
2579
2580
        $parts = preg_split("/\s+/", $val);
2581
2582
        $computed = array();
2583
        if (preg_match($length_re, $parts[0])) {
2584
            $computed[] = $this->length_in_pt($parts[0]);
2585
2586
            if (isset($parts[1]) && preg_match($length_re, $parts[1])) {
2587
                $computed[] = $this->length_in_pt($parts[1]);
2588
            } else {
2589
                $computed[] = $computed[0];
2590
            }
2591
2592
            if (isset($parts[2]) && $parts[2] === "landscape") {
2593
                $computed = array_reverse($computed);
2594
            }
2595
        } elseif (isset(CPDF::$PAPER_SIZES[$parts[0]])) {
2596
            $computed = array_slice(CPDF::$PAPER_SIZES[$parts[0]], 2, 2);
2597
2598
            if (isset($parts[1]) && $parts[1] === "landscape") {
2599
                $computed = array_reverse($computed);
2600
            }
2601
        } else {
2602
            return;
2603
        }
2604
2605
        $this->_props["size"] = $computed;
2606
    }
2607
2608
    /**
2609
     * Gets the CSS3 transform property
2610
     *
2611
     * @link http://www.w3.org/TR/css3-2d-transforms/#transform-property
2612
     * @return array|null
2613
     */
2614
    function get_transform()
2615
    {
2616
        $number = "\s*([^,\s]+)\s*";
2617
        $tr_value = "\s*([^,\s]+)\s*";
2618
        $angle = "\s*([^,\s]+(?:deg|rad)?)\s*";
2619
2620
        if (!preg_match_all("/[a-z]+\([^\)]+\)/i", $this->_props["transform"], $parts, PREG_SET_ORDER)) {
2621
            return null;
2622
        }
2623
2624
        $functions = array(
2625
            //"matrix"     => "\($number,$number,$number,$number,$number,$number\)",
2626
2627
            "translate" => "\($tr_value(?:,$tr_value)?\)",
2628
            "translateX" => "\($tr_value\)",
2629
            "translateY" => "\($tr_value\)",
2630
2631
            "scale" => "\($number(?:,$number)?\)",
2632
            "scaleX" => "\($number\)",
2633
            "scaleY" => "\($number\)",
2634
2635
            "rotate" => "\($angle\)",
2636
2637
            "skew" => "\($angle(?:,$angle)?\)",
2638
            "skewX" => "\($angle\)",
2639
            "skewY" => "\($angle\)",
2640
        );
2641
2642
        $transforms = array();
2643
2644
        foreach ($parts as $part) {
2645
            $t = $part[0];
2646
2647
            foreach ($functions as $name => $pattern) {
2648
                if (preg_match("/$name\s*$pattern/i", $t, $matches)) {
2649
                    $values = array_slice($matches, 1);
2650
2651
                    switch ($name) {
2652
                        // <angle> units
2653
                        case "rotate":
2654
                        case "skew":
2655
                        case "skewX":
2656
                        case "skewY":
2657
2658
                            foreach ($values as $i => $value) {
2659
                                if (strpos($value, "rad")) {
2660
                                    $values[$i] = rad2deg(floatval($value));
2661
                                } else {
2662
                                    $values[$i] = floatval($value);
2663
                                }
2664
                            }
2665
2666
                            switch ($name) {
2667
                                case "skew":
2668
                                    if (!isset($values[1])) {
2669
                                        $values[1] = 0;
2670
                                    }
2671
                                    break;
2672
                                case "skewX":
2673
                                    $name = "skew";
2674
                                    $values = array($values[0], 0);
2675
                                    break;
2676
                                case "skewY":
2677
                                    $name = "skew";
2678
                                    $values = array(0, $values[0]);
2679
                                    break;
2680
                            }
2681
                            break;
2682
2683
                        // <translation-value> units
2684
                        case "translate":
2685
                            $values[0] = $this->length_in_pt($values[0], (float)$this->length_in_pt($this->width));
2686
2687
                            if (isset($values[1])) {
2688
                                $values[1] = $this->length_in_pt($values[1], (float)$this->length_in_pt($this->height));
2689
                            } else {
2690
                                $values[1] = 0;
2691
                            }
2692
                            break;
2693
2694
                        case "translateX":
2695
                            $name = "translate";
2696
                            $values = array($this->length_in_pt($values[0], (float)$this->length_in_pt($this->width)), 0);
2697
                            break;
2698
2699
                        case "translateY":
2700
                            $name = "translate";
2701
                            $values = array(0, $this->length_in_pt($values[0], (float)$this->length_in_pt($this->height)));
2702
                            break;
2703
2704
                        // <number> units
2705
                        case "scale":
2706
                            if (!isset($values[1])) {
2707
                                $values[1] = $values[0];
2708
                            }
2709
                            break;
2710
2711
                        case "scaleX":
2712
                            $name = "scale";
2713
                            $values = array($values[0], 1.0);
2714
                            break;
2715
2716
                        case "scaleY":
2717
                            $name = "scale";
2718
                            $values = array(1.0, $values[0]);
2719
                            break;
2720
                    }
2721
2722
                    $transforms[] = array(
2723
                        $name,
2724
                        $values,
2725
                    );
2726
                }
2727
            }
2728
        }
2729
2730
        return $transforms;
2731
    }
2732
2733
    /**
2734
     * @param $val
2735
     */
2736
    function set_transform($val)
2737
    {
2738
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
2739
        $this->_prop_cache["transform"] = null;
2740
        $this->_props["transform"] = $val;
2741
    }
2742
2743
    /**
2744
     * @param $val
2745
     */
2746
    function set__webkit_transform($val)
2747
    {
2748
        $this->set_transform($val);
2749
    }
2750
2751
    /**
2752
     * @param $val
2753
     */
2754
    function set__webkit_transform_origin($val)
2755
    {
2756
        $this->set_transform_origin($val);
2757
    }
2758
2759
    /**
2760
     * Sets the CSS3 transform-origin property
2761
     *
2762
     * @link http://www.w3.org/TR/css3-2d-transforms/#transform-origin
2763
     * @param string $val
2764
     */
2765
    function set_transform_origin($val)
2766
    {
2767
        //see __set and __get, on all assignments clear cache, not needed on direct set through __set
2768
        $this->_prop_cache["transform_origin"] = null;
2769
        $this->_props["transform_origin"] = $val;
2770
    }
2771
2772
    /**
2773
     * Gets the CSS3 transform-origin property
2774
     *
2775
     * @link http://www.w3.org/TR/css3-2d-transforms/#transform-origin
2776
     * @return mixed[]
2777
     */
2778
    function get_transform_origin() {
2779
        $values = preg_split("/\s+/", $this->_props['transform_origin']);
2780
2781
        if (count($values) === 0) {
2782
            $values = preg_split("/\s+/", self::$_defaults["transform_origin"]);
2783
        }
2784
2785
        $values = array_map(function($value) {
2786
            if (in_array($value, array("top", "left"))) {
2787
                return 0;
2788
            } else if (in_array($value, array("bottom", "right"))) {
2789
                return "100%";
2790
            } else {
2791
                return $value;
2792
            }
2793
        }, $values);
2794
2795
        if (!isset($values[1])) {
2796
            $values[1] = $values[0];
2797
        }
2798
2799
        return $values;
2800
    }
2801
2802
    /**
2803
     * @param $val
2804
     * @return null
2805
     */
2806
    protected function parse_image_resolution($val)
2807
    {
2808
        // If exif data could be get:
2809
        // $re = '/^\s*(\d+|normal|auto)(?:\s*,\s*(\d+|normal))?\s*$/';
2810
2811
        $re = '/^\s*(\d+|normal|auto)\s*$/';
2812
2813
        if (!preg_match($re, $val, $matches)) {
2814
            return null;
2815
        }
2816
2817
        return $matches[1];
2818
    }
2819
2820
    /**
2821
     * auto | normal | dpi
2822
     *
2823
     * @param $val
2824
     */
2825
    function set_background_image_resolution($val)
2826
    {
2827
        $parsed = $this->parse_image_resolution($val);
2828
2829
        $this->_prop_cache["background_image_resolution"] = null;
2830
        $this->_props["background_image_resolution"] = $parsed;
2831
    }
2832
2833
    /**
2834
     * auto | normal | dpi
2835
     *
2836
     * @param $val
2837
     */
2838
    function set_image_resolution($val)
2839
    {
2840
        $parsed = $this->parse_image_resolution($val);
2841
2842
        $this->_prop_cache["image_resolution"] = null;
2843
        $this->_props["image_resolution"] = $parsed;
2844
    }
2845
2846
    /**
2847
     * @param $val
2848
     */
2849
    function set__dompdf_background_image_resolution($val)
2850
    {
2851
        $this->set_background_image_resolution($val);
2852
    }
2853
2854
    /**
2855
     * @param $val
2856
     */
2857
    function set__dompdf_image_resolution($val)
2858
    {
2859
        $this->set_image_resolution($val);
2860
    }
2861
2862
    /**
2863
     * @param $val
2864
     */
2865
    function set_z_index($val)
2866
    {
2867
        if (round($val) != $val && $val !== "auto") {
2868
            return;
2869
        }
2870
2871
        $this->_prop_cache["z_index"] = null;
2872
        $this->_props["z_index"] = $val;
2873
    }
2874
2875
    /**
2876
     * @param $val
2877
     */
2878
    function set_counter_increment($val)
2879
    {
2880
        $val = trim($val);
2881
        $value = null;
2882
2883
        if (in_array($val, array("none", "inherit"))) {
2884
            $value = $val;
2885
        } else {
2886
            if (preg_match_all("/(" . self::CSS_IDENTIFIER . ")(?:\s+(" . self::CSS_INTEGER . "))?/", $val, $matches, PREG_SET_ORDER)) {
2887
                $value = array();
2888
                foreach ($matches as $match) {
2889
                    $value[$match[1]] = isset($match[2]) ? $match[2] : 1;
2890
                }
2891
            }
2892
        }
2893
2894
        $this->_prop_cache["counter_increment"] = null;
2895
        $this->_props["counter_increment"] = $value;
2896
    }
2897
2898
    /**
2899
     * @param FontMetrics $fontMetrics
2900
     * @return $this
2901
     */
2902
    public function setFontMetrics(FontMetrics $fontMetrics)
2903
    {
2904
        $this->fontMetrics = $fontMetrics;
2905
        return $this;
2906
    }
2907
2908
    /**
2909
     * @return FontMetrics
2910
     */
2911
    public function getFontMetrics()
2912
    {
2913
        return $this->fontMetrics;
2914
    }
2915
2916
    /**
2917
     * Generate a string representation of the Style
2918
     *
2919
     * This dumps the entire property array into a string via print_r.  Useful
2920
     * for debugging.
2921
     *
2922
     * @return string
2923
     */
2924
    /*DEBUGCSS print: see below additional debugging util*/
2925
    function __toString()
2926
    {
2927
        return print_r(array_merge(array("parent_font_size" => $this->_parent_font_size),
2928
            $this->_props), true);
2929
    }
2930
2931
    /*DEBUGCSS*/
2932
    function debug_print()
2933
    {
2934
        /*DEBUGCSS*/
2935
        print "parent_font_size:" . $this->_parent_font_size . ";\n";
2936
        /*DEBUGCSS*/
2937
        foreach ($this->_props as $prop => $val) {
2938
            /*DEBUGCSS*/
2939
            print $prop . ':' . $val;
2940
            /*DEBUGCSS*/
2941
            if (isset($this->_important_props[$prop])) {
2942
                /*DEBUGCSS*/
2943
                print '!important';
2944
                /*DEBUGCSS*/
2945
            }
2946
            /*DEBUGCSS*/
2947
            print ";\n";
2948
            /*DEBUGCSS*/
2949
        }
2950
        /*DEBUGCSS*/
2951
    }
2952
}
2953