1<?php
2/**
3 * @package dompdf
4 * @link    http://dompdf.github.com/
5 * @author  Benj Carson <benjcarson@digitaljunkies.ca>
6 * @author  Helmut Tischer <htischer@weihenstephan.org>
7 * @author  Fabien Ménager <fabien.menager@gmail.com>
8 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
9 */
10namespace Dompdf\Css;
11
12use Dompdf\Adapter\CPDF;
13use Dompdf\Exception;
14use Dompdf\Helpers;
15use Dompdf\FontMetrics;
16use 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 */
29class 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("Implicit 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 occurrence 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 Parameter 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 functions 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 explicit or implicit 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, empty 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 individual 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 explicit 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