vendor/dompdf/dompdf/src/Css/Style.php line 2834

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