File manager - Edit - /home/opticamezl/www/newok/Css.tar
Back
Style.php 0000604 00000260713 15173453464 0006376 0 ustar 00 <?php /** * @package dompdf * @link http://dompdf.github.com/ * @author Benj Carson <benjcarson@digitaljunkies.ca> * @author Helmut Tischer <htischer@weihenstephan.org> * @author Fabien Ménager <fabien.menager@gmail.com> * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Css; use Dompdf\Adapter\CPDF; use Dompdf\Exception; use Dompdf\Helpers; use Dompdf\FontMetrics; use Dompdf\Frame; /** * Represents CSS properties. * * The Style class is responsible for handling and storing CSS properties. * It includes methods to resolve colors and lengths, as well as getters & * setters for many CSS properites. * * Actual CSS parsing is performed in the {@link Stylesheet} class. * * @package dompdf */ class Style { const CSS_IDENTIFIER = "-?[_a-zA-Z]+[_a-zA-Z0-9-]*"; const CSS_INTEGER = "-?\d+"; /** * Default font size, in points. * * @var float */ static $default_font_size = 12; /** * Default line height, as a fraction of the font size. * * @var float */ static $default_line_height = 1.2; /** * Default "absolute" font sizes relative to the default font-size * http://www.w3.org/TR/css3-fonts/#font-size-the-font-size-property * @var array<float> */ static $font_size_keywords = array( "xx-small" => 0.6, // 3/5 "x-small" => 0.75, // 3/4 "small" => 0.889, // 8/9 "medium" => 1, // 1 "large" => 1.2, // 6/5 "x-large" => 1.5, // 3/2 "xx-large" => 2.0, // 2/1 ); /** * List of valid vertical-align keywords. Should also really be a constant. * * @var array */ static $vertical_align_keywords = array("baseline", "bottom", "middle", "sub", "super", "text-bottom", "text-top", "top"); /** * List of all inline types. Should really be a constant. * * @var array */ static $INLINE_TYPES = array("inline"); /** * List of all block types. Should really be a constant. * * @var array */ static $BLOCK_TYPES = array("block", "inline-block", "table-cell", "list-item"); /** * List of all positionned types. Should really be a constant. * * @var array */ static $POSITIONNED_TYPES = array("relative", "absolute", "fixed"); /** * List of all table types. Should really be a constant. * * @var array; */ static $TABLE_TYPES = array("table", "inline-table"); /** * List of valid border styles. Should also really be a constant. * * @var array */ static $BORDER_STYLES = array("none", "hidden", "dotted", "dashed", "solid", "double", "groove", "ridge", "inset", "outset"); /** * Default style values. * * @link http://www.w3.org/TR/CSS21/propidx.html * * @var array */ static protected $_defaults = null; /** * List of inherited properties * * @link http://www.w3.org/TR/CSS21/propidx.html * * @var array */ static protected $_inherited = null; /** * Caches method_exists result * * @var array<bool> */ static protected $_methods_cache = array(); /** * The stylesheet this style belongs to * * @see Stylesheet * @var Stylesheet */ protected $_stylesheet; // stylesheet this style is attached to /** * Media queries attached to the style * * @var int */ protected $_media_queries; /** * Main array of all CSS properties & values * * @var array */ protected $_props; /* var instead of protected would allow access outside of class */ protected $_important_props; /** * Cached property values * * @var array */ protected $_prop_cache; /** * Font size of parent element in document tree. Used for relative font * size resolution. * * @var float */ protected $_parent_font_size; // Font size of parent element protected $_font_family; /** * @var Frame */ protected $_frame; /** * The origin of the style * * @var int */ protected $_origin = Stylesheet::ORIG_AUTHOR; // private members /** * True once the font size is resolved absolutely * * @var bool */ private $__font_size_calculated; // Cache flag /** * The computed bottom spacing */ private $_computed_bottom_spacing = null; /** * The computed border radius */ private $_computed_border_radius = null; /** * @var bool */ public $_has_border_radius = false; /** * @var FontMetrics */ private $fontMetrics; /** * Class constructor * * @param Stylesheet $stylesheet the stylesheet this Style is associated with. * @param int $origin */ public function __construct(Stylesheet $stylesheet, $origin = Stylesheet::ORIG_AUTHOR) { $this->setFontMetrics($stylesheet->getFontMetrics()); $this->_props = array(); $this->_important_props = array(); $this->_stylesheet = $stylesheet; $this->_media_queries = array(); $this->_origin = $origin; $this->_parent_font_size = null; $this->__font_size_calculated = false; if (!isset(self::$_defaults)) { // Shorthand $d =& self::$_defaults; // All CSS 2.1 properties, and their default values $d["azimuth"] = "center"; $d["background_attachment"] = "scroll"; $d["background_color"] = "transparent"; $d["background_image"] = "none"; $d["background_image_resolution"] = "normal"; $d["_dompdf_background_image_resolution"] = $d["background_image_resolution"]; $d["background_position"] = "0% 0%"; $d["background_repeat"] = "repeat"; $d["background"] = ""; $d["border_collapse"] = "separate"; $d["border_color"] = ""; $d["border_spacing"] = "0"; $d["border_style"] = ""; $d["border_top"] = ""; $d["border_right"] = ""; $d["border_bottom"] = ""; $d["border_left"] = ""; $d["border_top_color"] = ""; $d["border_right_color"] = ""; $d["border_bottom_color"] = ""; $d["border_left_color"] = ""; $d["border_top_style"] = "none"; $d["border_right_style"] = "none"; $d["border_bottom_style"] = "none"; $d["border_left_style"] = "none"; $d["border_top_width"] = "medium"; $d["border_right_width"] = "medium"; $d["border_bottom_width"] = "medium"; $d["border_left_width"] = "medium"; $d["border_width"] = "medium"; $d["border_bottom_left_radius"] = ""; $d["border_bottom_right_radius"] = ""; $d["border_top_left_radius"] = ""; $d["border_top_right_radius"] = ""; $d["border_radius"] = ""; $d["border"] = ""; $d["bottom"] = "auto"; $d["caption_side"] = "top"; $d["clear"] = "none"; $d["clip"] = "auto"; $d["color"] = "#000000"; $d["content"] = "normal"; $d["counter_increment"] = "none"; $d["counter_reset"] = "none"; $d["cue_after"] = "none"; $d["cue_before"] = "none"; $d["cue"] = ""; $d["cursor"] = "auto"; $d["direction"] = "ltr"; $d["display"] = "inline"; $d["elevation"] = "level"; $d["empty_cells"] = "show"; $d["float"] = "none"; $d["font_family"] = $stylesheet->get_dompdf()->getOptions()->getDefaultFont(); $d["font_size"] = "medium"; $d["font_style"] = "normal"; $d["font_variant"] = "normal"; $d["font_weight"] = "normal"; $d["font"] = ""; $d["height"] = "auto"; $d["image_resolution"] = "normal"; $d["_dompdf_image_resolution"] = $d["image_resolution"]; $d["_dompdf_keep"] = ""; $d["left"] = "auto"; $d["letter_spacing"] = "normal"; $d["line_height"] = "normal"; $d["list_style_image"] = "none"; $d["list_style_position"] = "outside"; $d["list_style_type"] = "disc"; $d["list_style"] = ""; $d["margin_right"] = "0"; $d["margin_left"] = "0"; $d["margin_top"] = "0"; $d["margin_bottom"] = "0"; $d["margin"] = ""; $d["max_height"] = "none"; $d["max_width"] = "none"; $d["min_height"] = "0"; $d["min_width"] = "0"; $d["opacity"] = "1.0"; // CSS3 $d["orphans"] = "2"; $d["outline_color"] = ""; // "invert" special color is not supported $d["outline_style"] = "none"; $d["outline_width"] = "medium"; $d["outline"] = ""; $d["overflow"] = "visible"; $d["padding_top"] = "0"; $d["padding_right"] = "0"; $d["padding_bottom"] = "0"; $d["padding_left"] = "0"; $d["padding"] = ""; $d["page_break_after"] = "auto"; $d["page_break_before"] = "auto"; $d["page_break_inside"] = "auto"; $d["pause_after"] = "0"; $d["pause_before"] = "0"; $d["pause"] = ""; $d["pitch_range"] = "50"; $d["pitch"] = "medium"; $d["play_during"] = "auto"; $d["position"] = "static"; $d["quotes"] = ""; $d["richness"] = "50"; $d["right"] = "auto"; $d["size"] = "auto"; // @page $d["speak_header"] = "once"; $d["speak_numeral"] = "continuous"; $d["speak_punctuation"] = "none"; $d["speak"] = "normal"; $d["speech_rate"] = "medium"; $d["stress"] = "50"; $d["table_layout"] = "auto"; $d["text_align"] = "left"; $d["text_decoration"] = "none"; $d["text_indent"] = "0"; $d["text_transform"] = "none"; $d["top"] = "auto"; $d["transform"] = "none"; // CSS3 $d["transform_origin"] = "50% 50%"; // CSS3 $d["_webkit_transform"] = $d["transform"]; // CSS3 $d["_webkit_transform_origin"] = $d["transform_origin"]; // CSS3 $d["unicode_bidi"] = "normal"; $d["vertical_align"] = "baseline"; $d["visibility"] = "visible"; $d["voice_family"] = ""; $d["volume"] = "medium"; $d["white_space"] = "normal"; $d["word_wrap"] = "normal"; $d["widows"] = "2"; $d["width"] = "auto"; $d["word_spacing"] = "normal"; $d["z_index"] = "auto"; // for @font-face $d["src"] = ""; $d["unicode_range"] = ""; // Properties that inherit by default self::$_inherited = array( "azimuth", "background_image_resolution", "border_collapse", "border_spacing", "caption_side", "color", "cursor", "direction", "elevation", "empty_cells", "font_family", "font_size", "font_style", "font_variant", "font_weight", "font", "image_resolution", "letter_spacing", "line_height", "list_style_image", "list_style_position", "list_style_type", "list_style", "orphans", "page_break_inside", "pitch_range", "pitch", "quotes", "richness", "speak_header", "speak_numeral", "speak_punctuation", "speak", "speech_rate", "stress", "text_align", "text_indent", "text_transform", "visibility", "voice_family", "volume", "white_space", "word_wrap", "widows", "word_spacing", ); } } /** * "Destructor": forcibly free all references held by this object */ function dispose() { } /** * @param $media_queries */ function set_media_queries($media_queries) { $this->_media_queries = $media_queries; } /** * @return array|int */ function get_media_queries() { return $this->_media_queries; } /** * @param Frame $frame */ function set_frame(Frame $frame) { $this->_frame = $frame; } /** * @return Frame */ function get_frame() { return $this->_frame; } /** * @param $origin */ function set_origin($origin) { $this->_origin = $origin; } /** * @return int */ function get_origin() { return $this->_origin; } /** * returns the {@link Stylesheet} this Style is associated with. * * @return Stylesheet */ function get_stylesheet() { return $this->_stylesheet; } /** * Converts any CSS length value into an absolute length in points. * * length_in_pt() takes a single length (e.g. '1em') or an array of * lengths and returns an absolute length. If an array is passed, then * the return value is the sum of all elements. If any of the lengths * provided are "auto" or "none" then that value is returned. * * If a reference size is not provided, the default font size is used * ({@link Style::$default_font_size}). * * @param float|string|array $length the numeric length (or string measurement) or array of lengths to resolve * @param float $ref_size an absolute reference size to resolve percentage lengths * @return float|string */ function length_in_pt($length, $ref_size = null) { static $cache = array(); if (!isset($ref_size)) { $ref_size = self::$default_font_size; } if (!is_array($length)) { $key = $length . "/$ref_size"; //Early check on cache, before converting $length to array if (isset($cache[$key])) { return $cache[$key]; } $length = array($length); } else { $key = implode("@", $length) . "/$ref_size"; if (isset($cache[$key])) { return $cache[$key]; } } $ret = 0; foreach ($length as $l) { if ($l === "auto") { return "auto"; } if ($l === "none") { return "none"; } // Assume numeric values are already in points if (is_numeric($l)) { $ret += $l; continue; } if ($l === "normal") { $ret += (float)$ref_size; continue; } // Border lengths if ($l === "thin") { $ret += 0.5; continue; } if ($l === "medium") { $ret += 1.5; continue; } if ($l === "thick") { $ret += 2.5; continue; } if (($i = mb_strpos($l, "px")) !== false) { $dpi = $this->_stylesheet->get_dompdf()->getOptions()->getDpi(); $ret += ((float)mb_substr($l, 0, $i) * 72) / $dpi; continue; } if (($i = mb_strpos($l, "pt")) !== false) { $ret += (float)mb_substr($l, 0, $i); continue; } if (($i = mb_strpos($l, "%")) !== false) { $ret += (float)mb_substr($l, 0, $i) / 100 * (float)$ref_size; continue; } if (($i = mb_strpos($l, "rem")) !== false) { if ($this->_stylesheet->get_dompdf()->getTree()->get_root()->get_style() === null) { // Interpreting it as "em", see https://github.com/dompdf/dompdf/issues/1406 $ret += (float)mb_substr($l, 0, $i) * $this->__get("font_size"); } else { $ret += (float)mb_substr($l, 0, $i) * $this->_stylesheet->get_dompdf()->getTree()->get_root()->get_style()->font_size; } continue; } if (($i = mb_strpos($l, "em")) !== false) { $ret += (float)mb_substr($l, 0, $i) * $this->__get("font_size"); continue; } if (($i = mb_strpos($l, "cm")) !== false) { $ret += (float)mb_substr($l, 0, $i) * 72 / 2.54; continue; } if (($i = mb_strpos($l, "mm")) !== false) { $ret += (float)mb_substr($l, 0, $i) * 72 / 25.4; continue; } // FIXME: em:ex ratio? if (($i = mb_strpos($l, "ex")) !== false) { $ret += (float)mb_substr($l, 0, $i) * $this->__get("font_size") / 2; continue; } if (($i = mb_strpos($l, "in")) !== false) { $ret += (float)mb_substr($l, 0, $i) * 72; continue; } if (($i = mb_strpos($l, "pc")) !== false) { $ret += (float)mb_substr($l, 0, $i) * 12; continue; } // Bogus value $ret += (float)$ref_size; } return $cache[$key] = $ret; } /** * Set inherited properties in this style using values in $parent * * @param Style $parent * * @return Style */ function inherit(Style $parent) { // Set parent font size $this->_parent_font_size = $parent->get_font_size(); foreach (self::$_inherited as $prop) { //inherit the !important property also. //if local property is also !important, don't inherit. if (isset($parent->_props[$prop]) && (!isset($this->_props[$prop]) || (isset($parent->_important_props[$prop]) && !isset($this->_important_props[$prop])) ) ) { if (isset($parent->_important_props[$prop])) { $this->_important_props[$prop] = true; } //see __set and __get, on all assignments clear cache! $this->_prop_cache[$prop] = null; $this->_props[$prop] = $parent->_props[$prop]; } } foreach ($this->_props as $prop => $value) { if ($value === "inherit") { if (isset($parent->_important_props[$prop])) { $this->_important_props[$prop] = true; } //do not assign direct, but //implicite assignment through __set, redirect to specialized, get value with __get //This is for computing defaults if the parent setting is also missing. //Therefore do not directly assign the value without __set //set _important_props before that to be able to propagate. //see __set and __get, on all assignments clear cache! //$this->_prop_cache[$prop] = null; //$this->_props[$prop] = $parent->_props[$prop]; //props_set for more obvious explicite assignment not implemented, because //too many implicite uses. // $this->props_set($prop, $parent->$prop); $this->__set($prop, $parent->__get($prop)); } } return $this; } /** * Override properties in this style with those in $style * * @param Style $style */ function merge(Style $style) { $shorthand_properties = array("background", "border", "border_bottom", "border_color", "border_left", "border_radius", "border_right", "border_style", "border_top", "border_width", "flex", "font", "list_style", "margin", "padding", "transform"); //treat the !important attribute //if old rule has !important attribute, override with new rule only if //the new rule is also !important foreach ($style->_props as $prop => $val) { $can_merge = false; if (isset($style->_important_props[$prop])) { $this->_important_props[$prop] = true; $can_merge = true; } else if (!isset($this->_important_props[$prop])) { $can_merge = true; } if ($can_merge) { //see __set and __get, on all assignments clear cache! $this->_prop_cache[$prop] = null; $this->_props[$prop] = $val; // Clear out "inherit" shorthand properties if specific properties have been set $shorthands = array_filter($shorthand_properties, function($el) use ($prop) { return ( strpos($prop, $el."_") !== false ); }); foreach ($shorthands as $shorthand) { if (array_key_exists($shorthand, $this->_props) && $this->_props[$shorthand] === "inherit") { unset($this->_props[$shorthand]); } } } } if (isset($style->_props["font_size"])) { $this->__font_size_calculated = false; } } /** * Returns an array(r, g, b, "r"=> r, "g"=>g, "b"=>b, "hex"=>"#rrggbb") * based on the provided CSS color value. * * @param string $color * @return array */ function munge_color($color) { return Color::parse($color); } /* direct access to _important_props array from outside would work only when declared as * 'var $_important_props;' instead of 'protected $_important_props;' * Don't call _set/__get on missing attribute. Therefore need a special access. * Assume that __set will be also called when this is called, so do not check validity again. * Only created, if !important exists -> always set true. */ function important_set($prop) { $prop = str_replace("-", "_", $prop); $this->_important_props[$prop] = true; } /** * @param $prop * @return bool */ function important_get($prop) { return isset($this->_important_props[$prop]); } /** * PHP5 overloaded setter * * This function along with {@link Style::__get()} permit a user of the * Style class to access any (CSS) property using the following syntax: * <code> * Style->margin_top = "1em"; * echo (Style->margin_top); * </code> * * __set() automatically calls the provided set function, if one exists, * otherwise it sets the property directly. Typically, __set() is not * called directly from outside of this class. * * On each modification clear cache to return accurate setting. * Also affects direct settings not using __set * For easier finding all assignments, attempted to allowing only explicite assignment: * Very many uses, e.g. AbstractFrameReflower.php -> for now leave as it is * function __set($prop, $val) { * throw new Exception("Implicite replacement of assignment by __set. Not good."); * } * function props_set($prop, $val) { ... } * * @param string $prop the property to set * @param mixed $val the value of the property * */ function __set($prop, $val) { $prop = str_replace("-", "_", $prop); $this->_prop_cache[$prop] = null; if (!isset(self::$_defaults[$prop])) { global $_dompdf_warnings; $_dompdf_warnings[] = "'$prop' is not a valid CSS2 property."; return; } if ($prop !== "content" && is_string($val) && strlen($val) > 5 && mb_strpos($val, "url") === false) { $val = mb_strtolower(trim(str_replace(array("\n", "\t"), array(" "), $val))); $val = preg_replace("/([0-9]+) (pt|px|pc|em|ex|in|cm|mm|%)/S", "\\1\\2", $val); } $method = "set_$prop"; if (!isset(self::$_methods_cache[$method])) { self::$_methods_cache[$method] = method_exists($this, $method); } if (self::$_methods_cache[$method]) { $this->$method($val); } else { $this->_props[$prop] = $val; } } /** * PHP5 overloaded getter * Along with {@link Style::__set()} __get() provides access to all CSS * properties directly. Typically __get() is not called directly outside * of this class. * On each modification clear cache to return accurate setting. * Also affects direct settings not using __set * * @param string $prop * * @throws Exception * @return mixed */ function __get($prop) { if (!isset(self::$_defaults[$prop])) { throw new Exception("'$prop' is not a valid CSS2 property."); } if (isset($this->_prop_cache[$prop]) && $this->_prop_cache[$prop] != null) { return $this->_prop_cache[$prop]; } $method = "get_$prop"; // Fall back on defaults if property is not set if (!isset($this->_props[$prop])) { $this->_props[$prop] = self::$_defaults[$prop]; } if (!isset(self::$_methods_cache[$method])) { self::$_methods_cache[$method] = method_exists($this, $method); } if (self::$_methods_cache[$method]) { return $this->_prop_cache[$prop] = $this->$method(); } return $this->_prop_cache[$prop] = $this->_props[$prop]; } /** * Similar to __get() without storing the result. Useful for accessing * properties while loading stylesheets. * * @param $prop * @return string * @throws Exception */ function get_prop($prop) { if (!isset(self::$_defaults[$prop])) { throw new Exception("'$prop' is not a valid CSS2 property."); } $method = "get_$prop"; // Fall back on defaults if property is not set if (!isset($this->_props[$prop])) { return self::$_defaults[$prop]; } if (method_exists($this, $method)) { return $this->$method(); } return $this->_props[$prop]; } /** * @return float|null|string */ function computed_bottom_spacing() { if ($this->_computed_bottom_spacing !== null) { return $this->_computed_bottom_spacing; } return $this->_computed_bottom_spacing = $this->length_in_pt( array( $this->margin_bottom, $this->padding_bottom, $this->border_bottom_width ) ); } /** * @return string */ function get_font_family_raw() { return trim($this->_props["font_family"], " \t\n\r\x0B\"'"); } /** * Getter for the 'font-family' CSS property. * Uses the {@link FontMetrics} class to resolve the font family into an * actual font file. * * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-family * @throws Exception * * @return string */ function get_font_family() { if (isset($this->_font_family)) { return $this->_font_family; } $DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss(); // Select the appropriate font. First determine the subtype, then check // the specified font-families for a candidate. // Resolve font-weight $weight = $this->__get("font_weight"); if (is_numeric($weight)) { if ($weight < 600) { $weight = "normal"; } else { $weight = "bold"; } } else if ($weight === "bold" || $weight === "bolder") { $weight = "bold"; } else { $weight = "normal"; } // Resolve font-style $font_style = $this->__get("font_style"); if ($weight === "bold" && ($font_style === "italic" || $font_style === "oblique")) { $subtype = "bold_italic"; } else if ($weight === "bold" && $font_style !== "italic" && $font_style !== "oblique") { $subtype = "bold"; } else if ($weight !== "bold" && ($font_style === "italic" || $font_style === "oblique")) { $subtype = "italic"; } else { $subtype = "normal"; } // Resolve the font family if ($DEBUGCSS) { print "<pre>[get_font_family:"; print '(' . $this->_props["font_family"] . '.' . $font_style . '.' . $this->__get("font_weight") . '.' . $weight . '.' . $subtype . ')'; } $families = preg_split("/\s*,\s*/", $this->_props["font_family"]); $font = null; foreach ($families as $family) { //remove leading and trailing string delimiters, e.g. on font names with spaces; //remove leading and trailing whitespace $family = trim($family, " \t\n\r\x0B\"'"); if ($DEBUGCSS) { print '(' . $family . ')'; } $font = $this->getFontMetrics()->getFont($family, $subtype); if ($font) { if ($DEBUGCSS) { print '(' . $font . ")get_font_family]\n</pre>"; } return $this->_font_family = $font; } } $family = null; if ($DEBUGCSS) { print '(default)'; } $font = $this->getFontMetrics()->getFont($family, $subtype); if ($font) { if ($DEBUGCSS) { print '(' . $font . ")get_font_family]\n</pre>"; } return $this->_font_family = $font; } throw new Exception("Unable to find a suitable font replacement for: '" . $this->_props["font_family"] . "'"); } /** * Returns the resolved font size, in points * * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size * @return float */ function get_font_size() { if ($this->__font_size_calculated) { return $this->_props["font_size"]; } if (!isset($this->_props["font_size"])) { $fs = self::$_defaults["font_size"]; } else { $fs = $this->_props["font_size"]; } if (!isset($this->_parent_font_size)) { $this->_parent_font_size = self::$default_font_size; } switch ((string)$fs) { case "xx-small": case "x-small": case "small": case "medium": case "large": case "x-large": case "xx-large": $fs = self::$default_font_size * self::$font_size_keywords[$fs]; break; case "smaller": $fs = 8 / 9 * $this->_parent_font_size; break; case "larger": $fs = 6 / 5 * $this->_parent_font_size; break; default: break; } // Ensure relative sizes resolve to something if (($i = mb_strpos($fs, "em")) !== false) { $fs = (float)mb_substr($fs, 0, $i) * $this->_parent_font_size; } else if (($i = mb_strpos($fs, "ex")) !== false) { $fs = (float)mb_substr($fs, 0, $i) * $this->_parent_font_size; } else { $fs = (float)$this->length_in_pt($fs); } //see __set and __get, on all assignments clear cache! $this->_prop_cache["font_size"] = null; $this->_props["font_size"] = $fs; $this->__font_size_calculated = true; return $this->_props["font_size"]; } /** * @link http://www.w3.org/TR/CSS21/text.html#propdef-word-spacing * @return float */ function get_word_spacing() { if ($this->_props["word_spacing"] === "normal") { return 0; } return $this->_props["word_spacing"]; } /** * @link http://www.w3.org/TR/CSS21/text.html#propdef-letter-spacing * @return float */ function get_letter_spacing() { if ($this->_props["letter_spacing"] === "normal") { return 0; } return $this->_props["letter_spacing"]; } /** * @link http://www.w3.org/TR/CSS21/visudet.html#propdef-line-height * @return float */ function get_line_height() { if (array_key_exists("line_height", $this->_props) === false) { $this->_props["line_height"] = self::$_defaults["line_height"]; } $line_height = $this->_props["line_height"]; if ($line_height === "normal") { return self::$default_line_height * $this->get_font_size(); } if (is_numeric($line_height)) { return $this->length_in_pt($line_height . "em", $this->get_font_size()); } return $this->length_in_pt($line_height, $this->_parent_font_size); } /** * Returns the color as an array * * The array has the following format: * <code>array(r,g,b, "r" => r, "g" => g, "b" => b, "hex" => "#rrggbb")</code> * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color * @return array */ function get_color() { return $this->munge_color($this->_props["color"]); } /** * Returns the background color as an array * * The returned array has the same format as {@link Style::get_color()} * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color * @return array */ function get_background_color() { return $this->munge_color($this->_props["background_color"]); } /** * Returns the background position as an array * * The returned array has the following format: * <code>array(x,y, "x" => x, "y" => y)</code> * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position * @return array */ function get_background_position() { $tmp = explode(" ", $this->_props["background_position"]); switch ($tmp[0]) { case "left": $x = "0%"; break; case "right": $x = "100%"; break; case "top": $y = "0%"; break; case "bottom": $y = "100%"; break; case "center": $x = "50%"; $y = "50%"; break; default: $x = $tmp[0]; break; } if (isset($tmp[1])) { switch ($tmp[1]) { case "left": $x = "0%"; break; case "right": $x = "100%"; break; case "top": $y = "0%"; break; case "bottom": $y = "100%"; break; case "center": if ($tmp[0] === "left" || $tmp[0] === "right" || $tmp[0] === "center") { $y = "50%"; } else { $x = "50%"; } break; default: $y = $tmp[1]; break; } } else { $y = "50%"; } if (!isset($x)) { $x = "0%"; } if (!isset($y)) { $y = "0%"; } return array( 0 => $x, "x" => $x, 1 => $y, "y" => $y, ); } /** * Returns the background as it is currently stored * * (currently anyway only for completeness. * not used for further processing) * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment * @return string */ function get_background_attachment() { return $this->_props["background_attachment"]; } /** * Returns the background_repeat as it is currently stored * * (currently anyway only for completeness. * not used for further processing) * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat * @return string */ function get_background_repeat() { return $this->_props["background_repeat"]; } /** * Returns the background as it is currently stored * * (currently anyway only for completeness. * not used for further processing, but the individual get_background_xxx) * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background * @return string */ function get_background() { return $this->_props["background"]; } /**#@+ * Returns the border color as an array * * See {@link Style::get_color()} * * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties * @return array */ function get_border_top_color() { if ($this->_props["border_top_color"] === "") { //see __set and __get, on all assignments clear cache! $this->_prop_cache["border_top_color"] = null; $this->_props["border_top_color"] = $this->__get("color"); } return $this->munge_color($this->_props["border_top_color"]); } /** * @return array */ function get_border_right_color() { if ($this->_props["border_right_color"] === "") { //see __set and __get, on all assignments clear cache! $this->_prop_cache["border_right_color"] = null; $this->_props["border_right_color"] = $this->__get("color"); } return $this->munge_color($this->_props["border_right_color"]); } /** * @return array */ function get_border_bottom_color() { if ($this->_props["border_bottom_color"] === "") { //see __set and __get, on all assignments clear cache! $this->_prop_cache["border_bottom_color"] = null; $this->_props["border_bottom_color"] = $this->__get("color"); } return $this->munge_color($this->_props["border_bottom_color"]); } /** * @return array */ function get_border_left_color() { if ($this->_props["border_left_color"] === "") { //see __set and __get, on all assignments clear cache! $this->_prop_cache["border_left_color"] = null; $this->_props["border_left_color"] = $this->__get("color"); } return $this->munge_color($this->_props["border_left_color"]); } /**#@-*/ /**#@+ * Returns the border width, as it is currently stored * * @link http://www.w3.org/TR/CSS21/box.html#border-width-properties * @return float|string */ function get_border_top_width() { $style = $this->__get("border_top_style"); return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_top_width"]) : 0; } /** * @return float|int|string */ function get_border_right_width() { $style = $this->__get("border_right_style"); return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_right_width"]) : 0; } /** * @return float|int|string */ function get_border_bottom_width() { $style = $this->__get("border_bottom_style"); return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_bottom_width"]) : 0; } /** * @return float|int|string */ function get_border_left_width() { $style = $this->__get("border_left_style"); return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_left_width"]) : 0; } /**#@-*/ /** * Return an array of all border properties. * * The returned array has the following structure: * <code> * array("top" => array("width" => [border-width], * "style" => [border-style], * "color" => [border-color (array)]), * "bottom" ... ) * </code> * * @return array */ function get_border_properties() { return array( "top" => array( "width" => $this->__get("border_top_width"), "style" => $this->__get("border_top_style"), "color" => $this->__get("border_top_color"), ), "bottom" => array( "width" => $this->__get("border_bottom_width"), "style" => $this->__get("border_bottom_style"), "color" => $this->__get("border_bottom_color"), ), "right" => array( "width" => $this->__get("border_right_width"), "style" => $this->__get("border_right_style"), "color" => $this->__get("border_right_color"), ), "left" => array( "width" => $this->__get("border_left_width"), "style" => $this->__get("border_left_style"), "color" => $this->__get("border_left_color"), ), ); } /** * Return a single border property * * @param string $side * * @return mixed */ protected function _get_border($side) { $color = $this->__get("border_" . $side . "_color"); return $this->__get("border_" . $side . "_width") . " " . $this->__get("border_" . $side . "_style") . " " . $color["hex"]; } /**#@+ * Return full border properties as a string * * Border properties are returned just as specified in CSS: * <pre>[width] [style] [color]</pre> * e.g. "1px solid blue" * * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties * @return string */ function get_border_top() { return $this->_get_border("top"); } /** * @return mixed */ function get_border_right() { return $this->_get_border("right"); } /** * @return mixed */ function get_border_bottom() { return $this->_get_border("bottom"); } /** * @return mixed */ function get_border_left() { return $this->_get_border("left"); } /** * @param $w * @param $h * @return array|null */ function get_computed_border_radius($w, $h) { if (!empty($this->_computed_border_radius)) { return $this->_computed_border_radius; } $w = (float)$w; $h = (float)$h; $rTL = (float)$this->__get("border_top_left_radius"); $rTR = (float)$this->__get("border_top_right_radius"); $rBL = (float)$this->__get("border_bottom_left_radius"); $rBR = (float)$this->__get("border_bottom_right_radius"); if ($rTL + $rTR + $rBL + $rBR == 0) { return $this->_computed_border_radius = array( 0, 0, 0, 0, "top-left" => 0, "top-right" => 0, "bottom-right" => 0, "bottom-left" => 0, ); } $t = (float)$this->__get("border_top_width"); $r = (float)$this->__get("border_right_width"); $b = (float)$this->__get("border_bottom_width"); $l = (float)$this->__get("border_left_width"); $rTL = min($rTL, $h - $rBL - $t / 2 - $b / 2, $w - $rTR - $l / 2 - $r / 2); $rTR = min($rTR, $h - $rBR - $t / 2 - $b / 2, $w - $rTL - $l / 2 - $r / 2); $rBL = min($rBL, $h - $rTL - $t / 2 - $b / 2, $w - $rBR - $l / 2 - $r / 2); $rBR = min($rBR, $h - $rTR - $t / 2 - $b / 2, $w - $rBL - $l / 2 - $r / 2); return $this->_computed_border_radius = array( $rTL, $rTR, $rBR, $rBL, "top-left" => $rTL, "top-right" => $rTR, "bottom-right" => $rBR, "bottom-left" => $rBL, ); } /** * Returns the outline color as an array * * See {@link Style::get_color()} * * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties * @return array */ function get_outline_color() { if ($this->_props["outline_color"] === "") { //see __set and __get, on all assignments clear cache! $this->_prop_cache["outline_color"] = null; $this->_props["outline_color"] = $this->__get("color"); } return $this->munge_color($this->_props["outline_color"]); } /**#@+ * Returns the outline width, as it is currently stored * @return float|string */ function get_outline_width() { $style = $this->__get("outline_style"); return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["outline_width"]) : 0; } /**#@+ * Return full outline properties as a string * * Outline properties are returned just as specified in CSS: * <pre>[width] [style] [color]</pre> * e.g. "1px solid blue" * * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties * @return string */ function get_outline() { $color = $this->__get("outline_color"); return $this->__get("outline_width") . " " . $this->__get("outline_style") . " " . $color["hex"]; } /**#@-*/ /** * Returns border spacing as an array * * The array has the format (h_space,v_space) * * @link http://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing * @return array */ function get_border_spacing() { $arr = explode(" ", $this->_props["border_spacing"]); if (count($arr) == 1) { $arr[1] = $arr[0]; } return $arr; } /*==============================*/ /* !important attribute For basic functionality of the !important attribute with overloading of several styles of an element, changes in inherit(), merge() and _parse_properties() are sufficient [helpers var $_important_props, __construct(), important_set(), important_get()] Only for combined attributes extra treatment needed. See below. div { border: 1px red; } div { border: solid; } // Not combined! Only one occurence of same style per context // div { border: 1px red; } div a { border: solid; } // Adding to border style ok by inheritance // div { border-style: solid; } // Adding to border style ok because of different styles div { border: 1px red; } // div { border-style: solid; !important} // border: overrides, even though not !important div { border: 1px dashed red; } // div { border: 1px red; !important } div a { border-style: solid; } // Need to override because not set Special treatment: At individual property like border-top-width need to check whether overriding value is also !important. Also store the !important condition for later overrides. Since not known who is initiating the override, need to get passed !important as parameter. !important Paramter taken as in the original style in the css file. When property border !important given, do not mark subsets like border_style as important. Only individual properties. Note: Setting individual property directly from css with e.g. set_border_top_style() is not needed, because missing set funcions handled by a generic handler __set(), including the !important. Setting individual property of as sub-property is handled below. Implementation see at _set_style_side_type() Callers _set_style_sides_type(), _set_style_type, _set_style_type_important() Related functionality for background, padding, margin, font, list_style */ /** * Generalized set function for individual attribute of combined style. * With check for !important * Applicable for background, border, padding, margin, font, list_style * * Note: $type has a leading underscore (or is empty), the others not. * * @param $style * @param $side * @param $type * @param $val * @param $important */ protected function _set_style_side_type($style, $side, $type, $val, $important) { $prop = $style . '_' . $side . $type; if (!isset($this->_important_props[$prop]) || $important) { if ($side === "bottom") { $this->_computed_bottom_spacing = null; //reset computed cache, border style can disable/enable border calculations } //see __set and __get, on all assignments clear cache! $this->_prop_cache[$prop] = null; if ($important) { $this->_important_props[$prop] = true; } $this->_props[$prop] = $val; } } /** * @param $style * @param $top * @param $right * @param $bottom * @param $left * @param $type * @param $important */ protected function _set_style_sides_type($style, $top, $right, $bottom, $left, $type, $important) { $this->_set_style_side_type($style, 'top', $type, $top, $important); $this->_set_style_side_type($style, 'right', $type, $right, $important); $this->_set_style_side_type($style, 'bottom', $type, $bottom, $important); $this->_set_style_side_type($style, 'left', $type, $left, $important); } /** * @param $style * @param $type * @param $val * @param $important */ protected function _set_style_type($style, $type, $val, $important) { $val = preg_replace("/\s*\,\s*/", ",", $val); // when rgb() has spaces $arr = explode(" ", $val); switch (count($arr)) { case 1: $this->_set_style_sides_type($style, $arr[0], $arr[0], $arr[0], $arr[0], $type, $important); break; case 2: $this->_set_style_sides_type($style, $arr[0], $arr[1], $arr[0], $arr[1], $type, $important); break; case 3: $this->_set_style_sides_type($style, $arr[0], $arr[1], $arr[2], $arr[1], $type, $important); break; case 4: $this->_set_style_sides_type($style, $arr[0], $arr[1], $arr[2], $arr[3], $type, $important); break; } //see __set and __get, on all assignments clear cache! $this->_prop_cache[$style . $type] = null; $this->_props[$style . $type] = $val; } /** * @param $style * @param $type * @param $val */ protected function _set_style_type_important($style, $type, $val) { $this->_set_style_type($style, $type, $val, isset($this->_important_props[$style . $type])); } /** * Anyway only called if _important matches and is assigned * E.g. _set_style_side_type($style,$side,'',str_replace("none", "0px", $val),isset($this->_important_props[$style.'_'.$side])); * * @param $style * @param $side * @param $val */ protected function _set_style_side_width_important($style, $side, $val) { if ($side === "bottom") { $this->_computed_bottom_spacing = null; //reset cache for any bottom width changes } //see __set and __get, on all assignments clear cache! $this->_prop_cache[$style . '_' . $side] = null; $this->_props[$style . '_' . $side] = str_replace("none", "0px", $val); } /** * @param $style * @param $val * @param $important */ protected function _set_style($style, $val, $important) { if (!isset($this->_important_props[$style]) || $important) { if ($important) { $this->_important_props[$style] = true; } //see __set and __get, on all assignments clear cache! $this->_prop_cache[$style] = null; $this->_props[$style] = $val; } } /** * @param $val * @return string */ protected function _image($val) { $DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss(); $parsed_url = "none"; if (mb_strpos($val, "url") === false) { $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none } else { $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val)); // Resolve the url now in the context of the current stylesheet $parsed_url = Helpers::explode_url($val); if ($parsed_url["protocol"] == "" && $this->_stylesheet->get_protocol() == "") { if ($parsed_url["path"][0] === '/' || $parsed_url["path"][0] === '\\') { $path = $_SERVER["DOCUMENT_ROOT"] . '/'; } else { $path = $this->_stylesheet->get_base_path(); } $path .= $parsed_url["path"] . $parsed_url["file"]; $path = realpath($path); // If realpath returns FALSE then specifically state that there is no background image if (!$path) { $path = 'none'; } } else { $path = Helpers::build_url($this->_stylesheet->get_protocol(), $this->_stylesheet->get_host(), $this->_stylesheet->get_base_path(), $val); } } if ($DEBUGCSS) { print "<pre>[_image\n"; print_r($parsed_url); print $this->_stylesheet->get_protocol() . "\n" . $this->_stylesheet->get_base_path() . "\n" . $path . "\n"; print "_image]</pre>";; } return $path; } /*======================*/ /** * Sets color * * The color parameter can be any valid CSS color value * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color * @param string $color */ function set_color($color) { $col = $this->munge_color($color); if (is_null($col) || !isset($col["hex"])) { $color = "inherit"; } else { $color = $col["hex"]; } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["color"] = null; $this->_props["color"] = $color; } /** * Sets the background color * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color * @param string $color */ function set_background_color($color) { $col = $this->munge_color($color); if (is_null($col)) { return; //$col = self::$_defaults["background_color"]; } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["background_color"] = null; $this->_props["background_color"] = is_array($col) ? $col["hex"] : $col; } /** * Set the background image url * @link http://www.w3.org/TR/CSS21/colors.html#background-properties * * @param string $val */ function set_background_image($val) { //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["background_image"] = null; $this->_props["background_image"] = $this->_image($val); } /** * Sets the background repeat * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat * @param string $val */ function set_background_repeat($val) { if (is_null($val)) { $val = self::$_defaults["background_repeat"]; } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["background_repeat"] = null; $this->_props["background_repeat"] = $val; } /** * Sets the background attachment * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment * @param string $val */ function set_background_attachment($val) { if (is_null($val)) { $val = self::$_defaults["background_attachment"]; } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["background_attachment"] = null; $this->_props["background_attachment"] = $val; } /** * Sets the background position * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position * @param string $val */ function set_background_position($val) { if (is_null($val)) { $val = self::$_defaults["background_position"]; } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["background_position"] = null; $this->_props["background_position"] = $val; } /** * Sets the background - combined options * * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background * @param string $val */ function set_background($val) { $val = trim($val); $important = isset($this->_important_props["background"]); if ($val === "none") { $this->_set_style("background_image", "none", $important); $this->_set_style("background_color", "transparent", $important); } else { $pos = array(); $tmp = preg_replace("/\s*\,\s*/", ",", $val); // when rgb() has spaces $tmp = preg_split("/\s+/", $tmp); foreach ($tmp as $attr) { if (mb_substr($attr, 0, 3) === "url" || $attr === "none") { $this->_set_style("background_image", $this->_image($attr), $important); } elseif ($attr === "fixed" || $attr === "scroll") { $this->_set_style("background_attachment", $attr, $important); } elseif ($attr === "repeat" || $attr === "repeat-x" || $attr === "repeat-y" || $attr === "no-repeat") { $this->_set_style("background_repeat", $attr, $important); } elseif (($col = $this->munge_color($attr)) != null) { $this->_set_style("background_color", is_array($col) ? $col["hex"] : $col, $important); } else { $pos[] = $attr; } } if (count($pos)) { $this->_set_style("background_position", implode(" ", $pos), $important); } } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["background"] = null; $this->_props["background"] = $val; } /** * Sets the font size * * $size can be any acceptable CSS size * * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size * @param string|float $size */ function set_font_size($size) { $this->__font_size_calculated = false; //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["font_size"] = null; $this->_props["font_size"] = $size; } /** * Sets the font style * * combined attributes * set individual attributes also, respecting !important mark * exactly this order, separate by space. Multiple fonts separated by comma: * font-style, font-variant, font-weight, font-size, line-height, font-family * * Other than with border and list, existing partial attributes should * reset when starting here, even when not mentioned. * If individual attribute is !important and explicite or implicite replacement is not, * keep individual attribute * * require whitespace as delimiters for single value attributes * On delimiter "/" treat first as font height, second as line height * treat all remaining at the end of line as font * font-style, font-variant, font-weight, font-size, line-height, font-family * * missing font-size and font-family might be not allowed, but accept it here and * use default (medium size, enpty font name) * * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style * @param $val */ function set_font($val) { $this->__font_size_calculated = false; //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["font"] = null; $this->_props["font"] = $val; $important = isset($this->_important_props["font"]); if (preg_match("/^(italic|oblique|normal)\s*(.*)$/i", $val, $match)) { $this->_set_style("font_style", $match[1], $important); $val = $match[2]; } else { $this->_set_style("font_style", self::$_defaults["font_style"], $important); } if (preg_match("/^(small-caps|normal)\s*(.*)$/i", $val, $match)) { $this->_set_style("font_variant", $match[1], $important); $val = $match[2]; } else { $this->_set_style("font_variant", self::$_defaults["font_variant"], $important); } //matching numeric value followed by unit -> this is indeed a subsequent font size. Skip! if (preg_match("/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900|normal)\s*(.*)$/i", $val, $match) && !preg_match("/^(?:pt|px|pc|em|ex|in|cm|mm|%)/", $match[2]) ) { $this->_set_style("font_weight", $match[1], $important); $val = $match[2]; } else { $this->_set_style("font_weight", self::$_defaults["font_weight"], $important); } if (preg_match("/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))(?:\/|\s*)(.*)$/i", $val, $match)) { $this->_set_style("font_size", $match[1], $important); $val = $match[2]; if (preg_match("/^(?:\/|\s*)(\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%)?)\s*(.*)$/i", $val, $match)) { $this->_set_style("line_height", $match[1], $important); $val = $match[2]; } else { $this->_set_style("line_height", self::$_defaults["line_height"], $important); } } else { $this->_set_style("font_size", self::$_defaults["font_size"], $important); $this->_set_style("line_height", self::$_defaults["line_height"], $important); } if (strlen($val) != 0) { $this->_set_style("font_family", $val, $important); } else { $this->_set_style("font_family", self::$_defaults["font_family"], $important); } } /** * Sets page break properties * * @link http://www.w3.org/TR/CSS21/page.html#page-breaks * @param string $break */ function set_page_break_before($break) { if ($break === "left" || $break === "right") { $break = "always"; } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["page_break_before"] = null; $this->_props["page_break_before"] = $break; } /** * @param $break */ function set_page_break_after($break) { if ($break === "left" || $break === "right") { $break = "always"; } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["page_break_after"] = null; $this->_props["page_break_after"] = $break; } /** * Sets the margin size * * @link http://www.w3.org/TR/CSS21/box.html#margin-properties * @param $val */ function set_margin_top($val) { $this->_set_style_side_width_important('margin', 'top', $val); } /** * @param $val */ function set_margin_right($val) { $this->_set_style_side_width_important('margin', 'right', $val); } /** * @param $val */ function set_margin_bottom($val) { $this->_set_style_side_width_important('margin', 'bottom', $val); } /** * @param $val */ function set_margin_left($val) { $this->_set_style_side_width_important('margin', 'left', $val); } /** * @param $val */ function set_margin($val) { $val = str_replace("none", "0px", $val); $this->_set_style_type_important('margin', '', $val); } /** * Sets the padding size * * @link http://www.w3.org/TR/CSS21/box.html#padding-properties * @param $val */ function set_padding_top($val) { $this->_set_style_side_width_important('padding', 'top', $val); } /** * @param $val */ function set_padding_right($val) { $this->_set_style_side_width_important('padding', 'right', $val); } /** * @param $val */ function set_padding_bottom($val) { $this->_set_style_side_width_important('padding', 'bottom', $val); } /** * @param $val */ function set_padding_left($val) { $this->_set_style_side_width_important('padding', 'left', $val); } /** * @param $val */ function set_padding($val) { $val = str_replace("none", "0px", $val); $this->_set_style_type_important('padding', '', $val); } /**#@-*/ /** * Sets a single border * * @param string $side * @param string $border_spec ([width] [style] [color]) * @param boolean $important */ protected function _set_border($side, $border_spec, $important) { $border_spec = preg_replace("/\s*\,\s*/", ",", $border_spec); //$border_spec = str_replace(",", " ", $border_spec); // Why did we have this ?? rbg(10, 102, 10) > rgb(10 102 10) $arr = explode(" ", $border_spec); // FIXME: handle partial values //For consistency of individal and combined properties, and with ie8 and firefox3 //reset all attributes, even if only partially given $this->_set_style_side_type('border', $side, '_style', self::$_defaults['border_' . $side . '_style'], $important); $this->_set_style_side_type('border', $side, '_width', self::$_defaults['border_' . $side . '_width'], $important); $this->_set_style_side_type('border', $side, '_color', self::$_defaults['border_' . $side . '_color'], $important); foreach ($arr as $value) { $value = trim($value); if (in_array($value, self::$BORDER_STYLES)) { $this->_set_style_side_type('border', $side, '_style', $value, $important); } else if (preg_match("/[.0-9]+(?:px|pt|pc|em|ex|%|in|mm|cm)|(?:thin|medium|thick)/", $value)) { $this->_set_style_side_type('border', $side, '_width', $value, $important); } else { // must be color $this->_set_style_side_type('border', $side, '_color', $value, $important); } } //see __set and __get, on all assignments clear cache! $this->_prop_cache['border_' . $side] = null; $this->_props['border_' . $side] = $border_spec; } /** * Sets the border styles * * @link http://www.w3.org/TR/CSS21/box.html#border-properties * @param string $val */ function set_border_top($val) { $this->_set_border("top", $val, isset($this->_important_props['border_top'])); } /** * @param $val */ function set_border_right($val) { $this->_set_border("right", $val, isset($this->_important_props['border_right'])); } /** * @param $val */ function set_border_bottom($val) { $this->_set_border("bottom", $val, isset($this->_important_props['border_bottom'])); } /** * @param $val */ function set_border_left($val) { $this->_set_border("left", $val, isset($this->_important_props['border_left'])); } /** * @param $val */ function set_border($val) { $important = isset($this->_important_props["border"]); $this->_set_border("top", $val, $important); $this->_set_border("right", $val, $important); $this->_set_border("bottom", $val, $important); $this->_set_border("left", $val, $important); //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["border"] = null; $this->_props["border"] = $val; } /** * @param $val */ function set_border_width($val) { $this->_set_style_type_important('border', '_width', $val); } /** * @param $val */ function set_border_color($val) { $this->_set_style_type_important('border', '_color', $val); } /** * @param $val */ function set_border_style($val) { $this->_set_style_type_important('border', '_style', $val); } /** * Sets the border radius size * * http://www.w3.org/TR/css3-background/#corners * * @param $val */ function set_border_top_left_radius($val) { $this->_set_border_radius_corner($val, "top_left"); } /** * @param $val */ function set_border_top_right_radius($val) { $this->_set_border_radius_corner($val, "top_right"); } /** * @param $val */ function set_border_bottom_left_radius($val) { $this->_set_border_radius_corner($val, "bottom_left"); } /** * @param $val */ function set_border_bottom_right_radius($val) { $this->_set_border_radius_corner($val, "bottom_right"); } /** * @param $val */ function set_border_radius($val) { $val = preg_replace("/\s*\,\s*/", ",", $val); // when border-radius has spaces $arr = explode(" ", $val); switch (count($arr)) { case 1: $this->_set_border_radii($arr[0], $arr[0], $arr[0], $arr[0]); break; case 2: $this->_set_border_radii($arr[0], $arr[1], $arr[0], $arr[1]); break; case 3: $this->_set_border_radii($arr[0], $arr[1], $arr[2], $arr[1]); break; case 4: $this->_set_border_radii($arr[0], $arr[1], $arr[2], $arr[3]); break; } } /** * @param $val1 * @param $val2 * @param $val3 * @param $val4 */ protected function _set_border_radii($val1, $val2, $val3, $val4) { $this->_set_border_radius_corner($val1, "top_left"); $this->_set_border_radius_corner($val2, "top_right"); $this->_set_border_radius_corner($val3, "bottom_right"); $this->_set_border_radius_corner($val4, "bottom_left"); } /** * @param $val * @param $corner */ protected function _set_border_radius_corner($val, $corner) { $this->_has_border_radius = true; //see __set and __get, on all assignments clear cache! $this->_prop_cache["border_" . $corner . "_radius"] = null; $this->_props["border_" . $corner . "_radius"] = $val; } /** * @return float|int|string */ function get_border_top_left_radius() { return $this->_get_border_radius_corner("top_left"); } /** * @return float|int|string */ function get_border_top_right_radius() { return $this->_get_border_radius_corner("top_right"); } /** * @return float|int|string */ function get_border_bottom_left_radius() { return $this->_get_border_radius_corner("bottom_left"); } /** * @return float|int|string */ function get_border_bottom_right_radius() { return $this->_get_border_radius_corner("bottom_right"); } /** * @param $corner * @return float|int|string */ protected function _get_border_radius_corner($corner) { if (!isset($this->_props["border_" . $corner . "_radius"]) || empty($this->_props["border_" . $corner . "_radius"])) { return 0; } return $this->length_in_pt($this->_props["border_" . $corner . "_radius"]); } /** * Sets the outline styles * * @link http://www.w3.org/TR/CSS21/ui.html#dynamic-outlines * @param string $val */ function set_outline($val) { $important = isset($this->_important_props["outline"]); $props = array( "outline_style", "outline_width", "outline_color", ); foreach ($props as $prop) { $_val = self::$_defaults[$prop]; if (!isset($this->_important_props[$prop]) || $important) { //see __set and __get, on all assignments clear cache! $this->_prop_cache[$prop] = null; if ($important) { $this->_important_props[$prop] = true; } $this->_props[$prop] = $_val; } } $val = preg_replace("/\s*\,\s*/", ",", $val); // when rgb() has spaces $arr = explode(" ", $val); foreach ($arr as $value) { $value = trim($value); if (in_array($value, self::$BORDER_STYLES)) { $this->set_outline_style($value); } else if (preg_match("/[.0-9]+(?:px|pt|pc|em|ex|%|in|mm|cm)|(?:thin|medium|thick)/", $value)) { $this->set_outline_width($value); } else { // must be color $this->set_outline_color($value); } } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["outline"] = null; $this->_props["outline"] = $val; } /** * @param $val */ function set_outline_width($val) { $this->_set_style_type_important('outline', '_width', $val); } /** * @param $val */ function set_outline_color($val) { $this->_set_style_type_important('outline', '_color', $val); } /** * @param $val */ function set_outline_style($val) { $this->_set_style_type_important('outline', '_style', $val); } /** * Sets the border spacing * * @link http://www.w3.org/TR/CSS21/box.html#border-properties * @param float $val */ function set_border_spacing($val) { $arr = explode(" ", $val); if (count($arr) == 1) { $arr[1] = $arr[0]; } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["border_spacing"] = null; $this->_props["border_spacing"] = "$arr[0] $arr[1]"; } /** * Sets the list style image * * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image * @param $val */ function set_list_style_image($val) { //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["list_style_image"] = null; $this->_props["list_style_image"] = $this->_image($val); } /** * Sets the list style * * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style * @param $val */ function set_list_style($val) { $important = isset($this->_important_props["list_style"]); $arr = explode(" ", str_replace(",", " ", $val)); static $types = array( "disc", "circle", "square", "decimal-leading-zero", "decimal", "1", "lower-roman", "upper-roman", "a", "A", "lower-greek", "lower-latin", "upper-latin", "lower-alpha", "upper-alpha", "armenian", "georgian", "hebrew", "cjk-ideographic", "hiragana", "katakana", "hiragana-iroha", "katakana-iroha", "none" ); static $positions = array("inside", "outside"); foreach ($arr as $value) { /* http://www.w3.org/TR/CSS21/generate.html#list-style * A value of 'none' for the 'list-style' property sets both 'list-style-type' and 'list-style-image' to 'none' */ if ($value === "none") { $this->_set_style("list_style_type", $value, $important); $this->_set_style("list_style_image", $value, $important); continue; } //On setting or merging or inheriting list_style_image as well as list_style_type, //and url exists, then url has precedence, otherwise fall back to list_style_type //Firefox is wrong here (list_style_image gets overwritten on explicite list_style_type) //Internet Explorer 7/8 and dompdf is right. if (mb_substr($value, 0, 3) === "url") { $this->_set_style("list_style_image", $this->_image($value), $important); continue; } if (in_array($value, $types)) { $this->_set_style("list_style_type", $value, $important); } else if (in_array($value, $positions)) { $this->_set_style("list_style_position", $value, $important); } } //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["list_style"] = null; $this->_props["list_style"] = $val; } /** * @param $val */ function set_size($val) { $length_re = "/(\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))/"; $val = mb_strtolower($val); if ($val === "auto") { return; } $parts = preg_split("/\s+/", $val); $computed = array(); if (preg_match($length_re, $parts[0])) { $computed[] = $this->length_in_pt($parts[0]); if (isset($parts[1]) && preg_match($length_re, $parts[1])) { $computed[] = $this->length_in_pt($parts[1]); } else { $computed[] = $computed[0]; } if (isset($parts[2]) && $parts[2] === "landscape") { $computed = array_reverse($computed); } } elseif (isset(CPDF::$PAPER_SIZES[$parts[0]])) { $computed = array_slice(CPDF::$PAPER_SIZES[$parts[0]], 2, 2); if (isset($parts[1]) && $parts[1] === "landscape") { $computed = array_reverse($computed); } } else { return; } $this->_props["size"] = $computed; } /** * Gets the CSS3 transform property * * @link http://www.w3.org/TR/css3-2d-transforms/#transform-property * @return array|null */ function get_transform() { $number = "\s*([^,\s]+)\s*"; $tr_value = "\s*([^,\s]+)\s*"; $angle = "\s*([^,\s]+(?:deg|rad)?)\s*"; if (!preg_match_all("/[a-z]+\([^\)]+\)/i", $this->_props["transform"], $parts, PREG_SET_ORDER)) { return null; } $functions = array( //"matrix" => "\($number,$number,$number,$number,$number,$number\)", "translate" => "\($tr_value(?:,$tr_value)?\)", "translateX" => "\($tr_value\)", "translateY" => "\($tr_value\)", "scale" => "\($number(?:,$number)?\)", "scaleX" => "\($number\)", "scaleY" => "\($number\)", "rotate" => "\($angle\)", "skew" => "\($angle(?:,$angle)?\)", "skewX" => "\($angle\)", "skewY" => "\($angle\)", ); $transforms = array(); foreach ($parts as $part) { $t = $part[0]; foreach ($functions as $name => $pattern) { if (preg_match("/$name\s*$pattern/i", $t, $matches)) { $values = array_slice($matches, 1); switch ($name) { // <angle> units case "rotate": case "skew": case "skewX": case "skewY": foreach ($values as $i => $value) { if (strpos($value, "rad")) { $values[$i] = rad2deg(floatval($value)); } else { $values[$i] = floatval($value); } } switch ($name) { case "skew": if (!isset($values[1])) { $values[1] = 0; } break; case "skewX": $name = "skew"; $values = array($values[0], 0); break; case "skewY": $name = "skew"; $values = array(0, $values[0]); break; } break; // <translation-value> units case "translate": $values[0] = $this->length_in_pt($values[0], (float)$this->length_in_pt($this->width)); if (isset($values[1])) { $values[1] = $this->length_in_pt($values[1], (float)$this->length_in_pt($this->height)); } else { $values[1] = 0; } break; case "translateX": $name = "translate"; $values = array($this->length_in_pt($values[0], (float)$this->length_in_pt($this->width)), 0); break; case "translateY": $name = "translate"; $values = array(0, $this->length_in_pt($values[0], (float)$this->length_in_pt($this->height))); break; // <number> units case "scale": if (!isset($values[1])) { $values[1] = $values[0]; } break; case "scaleX": $name = "scale"; $values = array($values[0], 1.0); break; case "scaleY": $name = "scale"; $values = array(1.0, $values[0]); break; } $transforms[] = array( $name, $values, ); } } } return $transforms; } /** * @param $val */ function set_transform($val) { //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["transform"] = null; $this->_props["transform"] = $val; } /** * @param $val */ function set__webkit_transform($val) { $this->set_transform($val); } /** * @param $val */ function set__webkit_transform_origin($val) { $this->set_transform_origin($val); } /** * Sets the CSS3 transform-origin property * * @link http://www.w3.org/TR/css3-2d-transforms/#transform-origin * @param string $val */ function set_transform_origin($val) { //see __set and __get, on all assignments clear cache, not needed on direct set through __set $this->_prop_cache["transform_origin"] = null; $this->_props["transform_origin"] = $val; } /** * Gets the CSS3 transform-origin property * * @link http://www.w3.org/TR/css3-2d-transforms/#transform-origin * @return mixed[] */ function get_transform_origin() { $values = preg_split("/\s+/", $this->_props['transform_origin']); if (count($values) === 0) { $values = preg_split("/\s+/", self::$_defaults["transform_origin"]); } $values = array_map(function($value) { if (in_array($value, array("top", "left"))) { return 0; } else if (in_array($value, array("bottom", "right"))) { return "100%"; } else { return $value; } }, $values); if (!isset($values[1])) { $values[1] = $values[0]; } return $values; } /** * @param $val * @return null */ protected function parse_image_resolution($val) { // If exif data could be get: // $re = '/^\s*(\d+|normal|auto)(?:\s*,\s*(\d+|normal))?\s*$/'; $re = '/^\s*(\d+|normal|auto)\s*$/'; if (!preg_match($re, $val, $matches)) { return null; } return $matches[1]; } /** * auto | normal | dpi * * @param $val */ function set_background_image_resolution($val) { $parsed = $this->parse_image_resolution($val); $this->_prop_cache["background_image_resolution"] = null; $this->_props["background_image_resolution"] = $parsed; } /** * auto | normal | dpi * * @param $val */ function set_image_resolution($val) { $parsed = $this->parse_image_resolution($val); $this->_prop_cache["image_resolution"] = null; $this->_props["image_resolution"] = $parsed; } /** * @param $val */ function set__dompdf_background_image_resolution($val) { $this->set_background_image_resolution($val); } /** * @param $val */ function set__dompdf_image_resolution($val) { $this->set_image_resolution($val); } /** * @param $val */ function set_z_index($val) { if (round($val) != $val && $val !== "auto") { return; } $this->_prop_cache["z_index"] = null; $this->_props["z_index"] = $val; } /** * @param $val */ function set_counter_increment($val) { $val = trim($val); $value = null; if (in_array($val, array("none", "inherit"))) { $value = $val; } else { if (preg_match_all("/(" . self::CSS_IDENTIFIER . ")(?:\s+(" . self::CSS_INTEGER . "))?/", $val, $matches, PREG_SET_ORDER)) { $value = array(); foreach ($matches as $match) { $value[$match[1]] = isset($match[2]) ? $match[2] : 1; } } } $this->_prop_cache["counter_increment"] = null; $this->_props["counter_increment"] = $value; } /** * @param FontMetrics $fontMetrics * @return $this */ public function setFontMetrics(FontMetrics $fontMetrics) { $this->fontMetrics = $fontMetrics; return $this; } /** * @return FontMetrics */ public function getFontMetrics() { return $this->fontMetrics; } /** * Generate a string representation of the Style * * This dumps the entire property array into a string via print_r. Useful * for debugging. * * @return string */ /*DEBUGCSS print: see below additional debugging util*/ function __toString() { return print_r(array_merge(array("parent_font_size" => $this->_parent_font_size), $this->_props), true); } /*DEBUGCSS*/ function debug_print() { /*DEBUGCSS*/ print "parent_font_size:" . $this->_parent_font_size . ";\n"; /*DEBUGCSS*/ foreach ($this->_props as $prop => $val) { /*DEBUGCSS*/ print $prop . ':' . $val; /*DEBUGCSS*/ if (isset($this->_important_props[$prop])) { /*DEBUGCSS*/ print '!important'; /*DEBUGCSS*/ } /*DEBUGCSS*/ print ";\n"; /*DEBUGCSS*/ } /*DEBUGCSS*/ } } AttributeTranslator.php 0000604 00000042652 15173453464 0011313 0 ustar 00 <?php /** * @package dompdf * @link http://dompdf.github.com/ * @author Benj Carson <benjcarson@digitaljunkies.ca> * @author Fabien Ménager <fabien.menager@gmail.com> * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Css; use Dompdf\Frame; /** * Translates HTML 4.0 attributes into CSS rules * * @package dompdf */ class AttributeTranslator { static $_style_attr = "_html_style_attribute"; // Munged data originally from // http://www.w3.org/TR/REC-html40/index/attributes.html // http://www.cs.tut.fi/~jkorpela/html2css.html static private $__ATTRIBUTE_LOOKUP = array( //'caption' => array ( 'align' => '', ), 'img' => array( 'align' => array( 'bottom' => 'vertical-align: baseline;', 'middle' => 'vertical-align: middle;', 'top' => 'vertical-align: top;', 'left' => 'float: left;', 'right' => 'float: right;' ), 'border' => 'border: %0.2Fpx solid;', 'height' => 'height: %spx;', 'hspace' => 'padding-left: %1$0.2Fpx; padding-right: %1$0.2Fpx;', 'vspace' => 'padding-top: %1$0.2Fpx; padding-bottom: %1$0.2Fpx;', 'width' => 'width: %spx;', ), 'table' => array( 'align' => array( 'left' => 'margin-left: 0; margin-right: auto;', 'center' => 'margin-left: auto; margin-right: auto;', 'right' => 'margin-left: auto; margin-right: 0;' ), 'bgcolor' => 'background-color: %s;', 'border' => '!set_table_border', 'cellpadding' => '!set_table_cellpadding', //'border-spacing: %0.2F; border-collapse: separate;', 'cellspacing' => '!set_table_cellspacing', 'frame' => array( 'void' => 'border-style: none;', 'above' => 'border-top-style: solid;', 'below' => 'border-bottom-style: solid;', 'hsides' => 'border-left-style: solid; border-right-style: solid;', 'vsides' => 'border-top-style: solid; border-bottom-style: solid;', 'lhs' => 'border-left-style: solid;', 'rhs' => 'border-right-style: solid;', 'box' => 'border-style: solid;', 'border' => 'border-style: solid;' ), 'rules' => '!set_table_rules', 'width' => 'width: %s;', ), 'hr' => array( 'align' => '!set_hr_align', // Need to grab width to set 'left' & 'right' correctly 'noshade' => 'border-style: solid;', 'size' => '!set_hr_size', //'border-width: %0.2F px;', 'width' => 'width: %s;', ), 'div' => array( 'align' => 'text-align: %s;', ), 'h1' => array( 'align' => 'text-align: %s;', ), 'h2' => array( 'align' => 'text-align: %s;', ), 'h3' => array( 'align' => 'text-align: %s;', ), 'h4' => array( 'align' => 'text-align: %s;', ), 'h5' => array( 'align' => 'text-align: %s;', ), 'h6' => array( 'align' => 'text-align: %s;', ), //TODO: translate more form element attributes 'input' => array( 'size' => '!set_input_width' ), 'p' => array( 'align' => 'text-align: %s;', ), // 'col' => array( // 'align' => '', // 'valign' => '', // ), // 'colgroup' => array( // 'align' => '', // 'valign' => '', // ), 'tbody' => array( 'align' => '!set_table_row_align', 'valign' => '!set_table_row_valign', ), 'td' => array( 'align' => 'text-align: %s;', 'bgcolor' => '!set_background_color', 'height' => 'height: %s;', 'nowrap' => 'white-space: nowrap;', 'valign' => 'vertical-align: %s;', 'width' => 'width: %s;', ), 'tfoot' => array( 'align' => '!set_table_row_align', 'valign' => '!set_table_row_valign', ), 'th' => array( 'align' => 'text-align: %s;', 'bgcolor' => '!set_background_color', 'height' => 'height: %s;', 'nowrap' => 'white-space: nowrap;', 'valign' => 'vertical-align: %s;', 'width' => 'width: %s;', ), 'thead' => array( 'align' => '!set_table_row_align', 'valign' => '!set_table_row_valign', ), 'tr' => array( 'align' => '!set_table_row_align', 'bgcolor' => '!set_table_row_bgcolor', 'valign' => '!set_table_row_valign', ), 'body' => array( 'background' => 'background-image: url(%s);', 'bgcolor' => '!set_background_color', 'link' => '!set_body_link', 'text' => '!set_color', ), 'br' => array( 'clear' => 'clear: %s;', ), 'basefont' => array( 'color' => '!set_color', 'face' => 'font-family: %s;', 'size' => '!set_basefont_size', ), 'font' => array( 'color' => '!set_color', 'face' => 'font-family: %s;', 'size' => '!set_font_size', ), 'dir' => array( 'compact' => 'margin: 0.5em 0;', ), 'dl' => array( 'compact' => 'margin: 0.5em 0;', ), 'menu' => array( 'compact' => 'margin: 0.5em 0;', ), 'ol' => array( 'compact' => 'margin: 0.5em 0;', 'start' => 'counter-reset: -dompdf-default-counter %d;', 'type' => 'list-style-type: %s;', ), 'ul' => array( 'compact' => 'margin: 0.5em 0;', 'type' => 'list-style-type: %s;', ), 'li' => array( 'type' => 'list-style-type: %s;', 'value' => 'counter-reset: -dompdf-default-counter %d;', ), 'pre' => array( 'width' => 'width: %s;', ), ); static protected $_last_basefont_size = 3; static protected $_font_size_lookup = array( // For basefont support -3 => "4pt", -2 => "5pt", -1 => "6pt", 0 => "7pt", 1 => "8pt", 2 => "10pt", 3 => "12pt", 4 => "14pt", 5 => "18pt", 6 => "24pt", 7 => "34pt", // For basefont support 8 => "48pt", 9 => "44pt", 10 => "52pt", 11 => "60pt", ); /** * @param Frame $frame */ static function translate_attributes(Frame $frame) { $node = $frame->get_node(); $tag = $node->nodeName; if (!isset(self::$__ATTRIBUTE_LOOKUP[$tag])) { return; } $valid_attrs = self::$__ATTRIBUTE_LOOKUP[$tag]; $attrs = $node->attributes; $style = rtrim($node->getAttribute(self::$_style_attr), "; "); if ($style != "") { $style .= ";"; } foreach ($attrs as $attr => $attr_node) { if (!isset($valid_attrs[$attr])) { continue; } $value = $attr_node->value; $target = $valid_attrs[$attr]; // Look up $value in $target, if $target is an array: if (is_array($target)) { if (isset($target[$value])) { $style .= " " . self::_resolve_target($node, $target[$value], $value); } } else { // otherwise use target directly $style .= " " . self::_resolve_target($node, $target, $value); } } if (!is_null($style)) { $style = ltrim($style); $node->setAttribute(self::$_style_attr, $style); } } /** * @param \DOMNode $node * @param string $target * @param string $value * * @return string */ static protected function _resolve_target(\DOMNode $node, $target, $value) { if ($target[0] === "!") { // Function call $func = "_" . mb_substr($target, 1); return self::$func($node, $value); } return $value ? sprintf($target, $value) : ""; } /** * @param \DOMElement $node * @param string $new_style */ static function append_style(\DOMElement $node, $new_style) { $style = rtrim($node->getAttribute(self::$_style_attr), ";"); $style .= $new_style; $style = ltrim($style, ";"); $node->setAttribute(self::$_style_attr, $style); } /** * @param \DOMNode $node * * @return \DOMNodeList|\DOMElement[] */ static protected function get_cell_list(\DOMNode $node) { $xpath = new \DOMXpath($node->ownerDocument); switch ($node->nodeName) { default: case "table": $query = "tr/td | thead/tr/td | tbody/tr/td | tfoot/tr/td | tr/th | thead/tr/th | tbody/tr/th | tfoot/tr/th"; break; case "tbody": case "tfoot": case "thead": $query = "tr/td | tr/th"; break; case "tr": $query = "td | th"; break; } return $xpath->query($query, $node); } /** * @param string $value * * @return string */ static protected function _get_valid_color($value) { if (preg_match('/^#?([0-9A-F]{6})$/i', $value, $matches)) { $value = "#$matches[1]"; } return $value; } /** * @param \DOMElement $node * @param string $value * * @return string */ static protected function _set_color(\DOMElement $node, $value) { $value = self::_get_valid_color($value); return "color: $value;"; } /** * @param \DOMElement $node * @param string $value * * @return string */ static protected function _set_background_color(\DOMElement $node, $value) { $value = self::_get_valid_color($value); return "background-color: $value;"; } /** * @param \DOMElement $node * @param string $value * * @return null */ static protected function _set_table_cellpadding(\DOMElement $node, $value) { $cell_list = self::get_cell_list($node); foreach ($cell_list as $cell) { self::append_style($cell, "; padding: {$value}px;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return string */ static protected function _set_table_border(\DOMElement $node, $value) { $cell_list = self::get_cell_list($node); foreach ($cell_list as $cell) { $style = rtrim($cell->getAttribute(self::$_style_attr)); $style .= "; border-width: " . ($value > 0 ? 1 : 0) . "pt; border-style: inset;"; $style = ltrim($style, ";"); $cell->setAttribute(self::$_style_attr, $style); } $style = rtrim($node->getAttribute(self::$_style_attr), ";"); $style .= "; border-width: $value" . "px; "; return ltrim($style, "; "); } /** * @param \DOMElement $node * @param string $value * * @return string */ static protected function _set_table_cellspacing(\DOMElement $node, $value) { $style = rtrim($node->getAttribute(self::$_style_attr), ";"); if ($value == 0) { $style .= "; border-collapse: collapse;"; } else { $style .= "; border-spacing: {$value}px; border-collapse: separate;"; } return ltrim($style, ";"); } /** * @param \DOMElement $node * @param string $value * * @return null|string */ static protected function _set_table_rules(\DOMElement $node, $value) { $new_style = "; border-collapse: collapse;"; switch ($value) { case "none": $new_style .= "border-style: none;"; break; case "groups": // FIXME: unsupported return null; case "rows": $new_style .= "border-style: solid none solid none; border-width: 1px; "; break; case "cols": $new_style .= "border-style: none solid none solid; border-width: 1px; "; break; case "all": $new_style .= "border-style: solid; border-width: 1px; "; break; default: // Invalid value return null; } $cell_list = self::get_cell_list($node); foreach ($cell_list as $cell) { $style = $cell->getAttribute(self::$_style_attr); $style .= $new_style; $cell->setAttribute(self::$_style_attr, $style); } $style = rtrim($node->getAttribute(self::$_style_attr), ";"); $style .= "; border-collapse: collapse; "; return ltrim($style, "; "); } /** * @param \DOMElement $node * @param string $value * * @return string */ static protected function _set_hr_size(\DOMElement $node, $value) { $style = rtrim($node->getAttribute(self::$_style_attr), ";"); $style .= "; border-width: " . max(0, $value - 2) . "; "; return ltrim($style, "; "); } /** * @param \DOMElement $node * @param string $value * * @return null|string */ static protected function _set_hr_align(\DOMElement $node, $value) { $style = rtrim($node->getAttribute(self::$_style_attr), ";"); $width = $node->getAttribute("width"); if ($width == "") { $width = "100%"; } $remainder = 100 - (double)rtrim($width, "% "); switch ($value) { case "left": $style .= "; margin-right: $remainder %;"; break; case "right": $style .= "; margin-left: $remainder %;"; break; case "center": $style .= "; margin-left: auto; margin-right: auto;"; break; default: return null; } return ltrim($style, "; "); } /** * @param \DOMElement $node * @param string $value * * @return null|string */ static protected function _set_input_width(\DOMElement $node, $value) { if (empty($value)) { return null; } if ($node->hasAttribute("type") && in_array(strtolower($node->getAttribute("type")), array("text","password"))) { return sprintf("width: %Fem", (((int)$value * .65)+2)); } else { return sprintf("width: %upx;", (int)$value); } } /** * @param \DOMElement $node * @param string $value * * @return null */ static protected function _set_table_row_align(\DOMElement $node, $value) { $cell_list = self::get_cell_list($node); foreach ($cell_list as $cell) { self::append_style($cell, "; text-align: $value;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return null */ static protected function _set_table_row_valign(\DOMElement $node, $value) { $cell_list = self::get_cell_list($node); foreach ($cell_list as $cell) { self::append_style($cell, "; vertical-align: $value;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return null */ static protected function _set_table_row_bgcolor(\DOMElement $node, $value) { $cell_list = self::get_cell_list($node); $value = self::_get_valid_color($value); foreach ($cell_list as $cell) { self::append_style($cell, "; background-color: $value;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return null */ static protected function _set_body_link(\DOMElement $node, $value) { $a_list = $node->getElementsByTagName("a"); $value = self::_get_valid_color($value); foreach ($a_list as $a) { self::append_style($a, "; color: $value;"); } return null; } /** * @param \DOMElement $node * @param string $value * * @return null */ static protected function _set_basefont_size(\DOMElement $node, $value) { // FIXME: ? we don't actually set the font size of anything here, just // the base size for later modification by <font> tags. self::$_last_basefont_size = $value; return null; } /** * @param \DOMElement $node * @param string $value * * @return string */ static protected function _set_font_size(\DOMElement $node, $value) { $style = $node->getAttribute(self::$_style_attr); if ($value[0] === "-" || $value[0] === "+") { $value = self::$_last_basefont_size + (int)$value; } if (isset(self::$_font_size_lookup[$value])) { $style .= "; font-size: " . self::$_font_size_lookup[$value] . ";"; } else { $style .= "; font-size: $value;"; } return ltrim($style, "; "); } } Color.php 0000604 00000023003 15173453464 0006341 0 ustar 00 <?php /** * @package dompdf * @link http://dompdf.github.com/ * @author Benj Carson <benjcarson@digitaljunkies.ca> * @author Fabien Ménager <fabien.menager@gmail.com> * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Css; use Dompdf\Helpers; class Color { static $cssColorNames = array( "aliceblue" => "F0F8FF", "antiquewhite" => "FAEBD7", "aqua" => "00FFFF", "aquamarine" => "7FFFD4", "azure" => "F0FFFF", "beige" => "F5F5DC", "bisque" => "FFE4C4", "black" => "000000", "blanchedalmond" => "FFEBCD", "blue" => "0000FF", "blueviolet" => "8A2BE2", "brown" => "A52A2A", "burlywood" => "DEB887", "cadetblue" => "5F9EA0", "chartreuse" => "7FFF00", "chocolate" => "D2691E", "coral" => "FF7F50", "cornflowerblue" => "6495ED", "cornsilk" => "FFF8DC", "crimson" => "DC143C", "cyan" => "00FFFF", "darkblue" => "00008B", "darkcyan" => "008B8B", "darkgoldenrod" => "B8860B", "darkgray" => "A9A9A9", "darkgreen" => "006400", "darkgrey" => "A9A9A9", "darkkhaki" => "BDB76B", "darkmagenta" => "8B008B", "darkolivegreen" => "556B2F", "darkorange" => "FF8C00", "darkorchid" => "9932CC", "darkred" => "8B0000", "darksalmon" => "E9967A", "darkseagreen" => "8FBC8F", "darkslateblue" => "483D8B", "darkslategray" => "2F4F4F", "darkslategrey" => "2F4F4F", "darkturquoise" => "00CED1", "darkviolet" => "9400D3", "deeppink" => "FF1493", "deepskyblue" => "00BFFF", "dimgray" => "696969", "dimgrey" => "696969", "dodgerblue" => "1E90FF", "firebrick" => "B22222", "floralwhite" => "FFFAF0", "forestgreen" => "228B22", "fuchsia" => "FF00FF", "gainsboro" => "DCDCDC", "ghostwhite" => "F8F8FF", "gold" => "FFD700", "goldenrod" => "DAA520", "gray" => "808080", "green" => "008000", "greenyellow" => "ADFF2F", "grey" => "808080", "honeydew" => "F0FFF0", "hotpink" => "FF69B4", "indianred" => "CD5C5C", "indigo" => "4B0082", "ivory" => "FFFFF0", "khaki" => "F0E68C", "lavender" => "E6E6FA", "lavenderblush" => "FFF0F5", "lawngreen" => "7CFC00", "lemonchiffon" => "FFFACD", "lightblue" => "ADD8E6", "lightcoral" => "F08080", "lightcyan" => "E0FFFF", "lightgoldenrodyellow" => "FAFAD2", "lightgray" => "D3D3D3", "lightgreen" => "90EE90", "lightgrey" => "D3D3D3", "lightpink" => "FFB6C1", "lightsalmon" => "FFA07A", "lightseagreen" => "20B2AA", "lightskyblue" => "87CEFA", "lightslategray" => "778899", "lightslategrey" => "778899", "lightsteelblue" => "B0C4DE", "lightyellow" => "FFFFE0", "lime" => "00FF00", "limegreen" => "32CD32", "linen" => "FAF0E6", "magenta" => "FF00FF", "maroon" => "800000", "mediumaquamarine" => "66CDAA", "mediumblue" => "0000CD", "mediumorchid" => "BA55D3", "mediumpurple" => "9370DB", "mediumseagreen" => "3CB371", "mediumslateblue" => "7B68EE", "mediumspringgreen" => "00FA9A", "mediumturquoise" => "48D1CC", "mediumvioletred" => "C71585", "midnightblue" => "191970", "mintcream" => "F5FFFA", "mistyrose" => "FFE4E1", "moccasin" => "FFE4B5", "navajowhite" => "FFDEAD", "navy" => "000080", "oldlace" => "FDF5E6", "olive" => "808000", "olivedrab" => "6B8E23", "orange" => "FFA500", "orangered" => "FF4500", "orchid" => "DA70D6", "palegoldenrod" => "EEE8AA", "palegreen" => "98FB98", "paleturquoise" => "AFEEEE", "palevioletred" => "DB7093", "papayawhip" => "FFEFD5", "peachpuff" => "FFDAB9", "peru" => "CD853F", "pink" => "FFC0CB", "plum" => "DDA0DD", "powderblue" => "B0E0E6", "purple" => "800080", "red" => "FF0000", "rosybrown" => "BC8F8F", "royalblue" => "4169E1", "saddlebrown" => "8B4513", "salmon" => "FA8072", "sandybrown" => "F4A460", "seagreen" => "2E8B57", "seashell" => "FFF5EE", "sienna" => "A0522D", "silver" => "C0C0C0", "skyblue" => "87CEEB", "slateblue" => "6A5ACD", "slategray" => "708090", "slategrey" => "708090", "snow" => "FFFAFA", "springgreen" => "00FF7F", "steelblue" => "4682B4", "tan" => "D2B48C", "teal" => "008080", "thistle" => "D8BFD8", "tomato" => "FF6347", "turquoise" => "40E0D0", "violet" => "EE82EE", "wheat" => "F5DEB3", "white" => "FFFFFF", "whitesmoke" => "F5F5F5", "yellow" => "FFFF00", "yellowgreen" => "9ACD32", ); /** * @param $color * @return array|mixed|null|string */ static function parse($color) { if (is_array($color)) { // Assume the array has the right format... // FIXME: should/could verify this. return $color; } static $cache = array(); $color = strtolower($color); if (isset($cache[$color])) { return $cache[$color]; } if (in_array($color, array("transparent", "inherit"))) { return $cache[$color] = $color; } if (isset(self::$cssColorNames[$color])) { return $cache[$color] = self::getArray(self::$cssColorNames[$color]); } $length = mb_strlen($color); // #rgb format if ($length == 4 && $color[0] === "#") { return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]); } // #rgba format else if ($length == 5 && $color[0] === "#") { $alpha = round(hexdec($color[4] . $color[4])/255, 2); return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3], $alpha); } // #rrggbb format else if ($length == 7 && $color[0] === "#") { return $cache[$color] = self::getArray(mb_substr($color, 1, 6)); } // #rrggbbaa format else if ($length == 9 && $color[0] === "#") { $alpha = round(hexdec(mb_substr($color, 7, 2))/255, 2); return $cache[$color] = self::getArray(mb_substr($color, 1, 8), $alpha); } // rgb( r,g,b ) / rgba( r,g,b,α ) format else if (mb_strpos($color, "rgb") !== false) { $i = mb_strpos($color, "("); $j = mb_strpos($color, ")"); // Bad color value if ($i === false || $j === false) { return null; } $triplet = explode(",", mb_substr($color, $i + 1, $j - $i - 1)); // alpha transparency // FIXME: not currently using transparency $alpha = 1.0; if (count($triplet) == 4) { $alpha = (float)(trim(array_pop($triplet))); // bad value, set to fully opaque if ($alpha > 1.0 || $alpha < 0.0) { $alpha = 1.0; } } if (count($triplet) != 3) { return null; } foreach (array_keys($triplet) as $c) { $triplet[$c] = trim($triplet[$c]); if (Helpers::is_percent($triplet[$c])) { $triplet[$c] = round((float)$triplet[$c] * 2.55); } } return $cache[$color] = self::getArray(vsprintf("%02X%02X%02X", $triplet), $alpha); } // cmyk( c,m,y,k ) format // http://www.w3.org/TR/css3-gcpm/#cmyk-colors else if (mb_strpos($color, "cmyk") !== false) { $i = mb_strpos($color, "("); $j = mb_strpos($color, ")"); // Bad color value if ($i === false || $j === false) { return null; } $values = explode(",", mb_substr($color, $i + 1, $j - $i - 1)); if (count($values) != 4) { return null; } $values = array_map(function($c) { return min(1.0, max(0.0, floatval(trim($c)))); }, $values); return $cache[$color] = self::getArray($values); } return null; } /** * @param $color * @param float $alpha * @return array */ static function getArray($color, $alpha = 1.0) { $c = array(null, null, null, null, "alpha" => $alpha, "hex" => null); if (is_array($color)) { $c = $color; $c["c"] = $c[0]; $c["m"] = $c[1]; $c["y"] = $c[2]; $c["k"] = $c[3]; $c["alpha"] = $alpha; $c["hex"] = "cmyk($c[0],$c[1],$c[2],$c[3])"; } else { $c[0] = hexdec(mb_substr($color, 0, 2)) / 0xff; $c[1] = hexdec(mb_substr($color, 2, 2)) / 0xff; $c[2] = hexdec(mb_substr($color, 4, 2)) / 0xff; $c["r"] = $c[0]; $c["g"] = $c[1]; $c["b"] = $c[2]; $c["alpha"] = $alpha; $c["hex"] = sprintf("#%s%02X", mb_substr($color, 0, 6), round($alpha * 255)); } return $c; } } Stylesheet.php 0000604 00000174204 15173453464 0007426 0 ustar 00 <?php /** * @package dompdf * @link http://dompdf.github.com/ * @author Benj Carson <benjcarson@digitaljunkies.ca> * @author Helmut Tischer <htischer@weihenstephan.org> * @author Fabien Ménager <fabien.menager@gmail.com> * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace Dompdf\Css; use DOMElement; use DOMXPath; use Dompdf\Dompdf; use Dompdf\Helpers; use Dompdf\Exception; use Dompdf\FontMetrics; use Dompdf\Frame\FrameTree; /** * The master stylesheet class * * The Stylesheet class is responsible for parsing stylesheets and style * tags/attributes. It also acts as a registry of the individual Style * objects generated by the current set of loaded CSS files and style * elements. * * @see Style * @package dompdf */ class Stylesheet { /** * The location of the default built-in CSS file. */ const DEFAULT_STYLESHEET = "/lib/res/html.css"; /** * User agent stylesheet origin * * @var int */ const ORIG_UA = 1; /** * User normal stylesheet origin * * @var int */ const ORIG_USER = 2; /** * Author normal stylesheet origin * * @var int */ const ORIG_AUTHOR = 3; /* * The highest possible specificity is 0x01000000 (and that is only for author * stylesheets, as it is for inline styles). Origin precedence can be achieved by * adding multiples of 0x10000000 to the actual specificity. Important * declarations are handled in Style; though technically they should be handled * here so that user important declarations can be made to take precedence over * user important declarations, this doesn't matter in practice as Dompdf does * not support user stylesheets, and user agent stylesheets can not include * important declarations. */ private static $_stylesheet_origins = array( self::ORIG_UA => 0x00000000, // user agent declarations self::ORIG_USER => 0x10000000, // user normal declarations self::ORIG_AUTHOR => 0x30000000, // author normal declarations ); /* * Non-CSS presentational hints (i.e. HTML 4 attributes) are handled as if added * to the beginning of an author stylesheet, i.e. anything in author stylesheets * should override them. */ const SPEC_NON_CSS = 0x20000000; /** * Current dompdf instance * * @var Dompdf */ private $_dompdf; /** * Array of currently defined styles * * @var Style[] */ private $_styles; /** * Base protocol of the document being parsed * Used to handle relative urls. * * @var string */ private $_protocol; /** * Base hostname of the document being parsed * Used to handle relative urls. * * @var string */ private $_base_host; /** * Base path of the document being parsed * Used to handle relative urls. * * @var string */ private $_base_path; /** * The styles defined by @page rules * * @var array<Style> */ private $_page_styles; /** * List of loaded files, used to prevent recursion * * @var array */ private $_loaded_files; /** * Current stylesheet origin * * @var int */ private $_current_origin = self::ORIG_UA; /** * Accepted CSS media types * List of types and parsing rules for future extensions: * http://www.w3.org/TR/REC-html40/types.html * screen, tty, tv, projection, handheld, print, braille, aural, all * The following are non standard extensions for undocumented specific environments. * static, visual, bitmap, paged, dompdf * Note, even though the generated pdf file is intended for print output, * the desired content might be different (e.g. screen or projection view of html file). * Therefore allow specification of content by dompdf setting Options::defaultMediaType. * If given, replace media "print" by Options::defaultMediaType. * (Previous version $ACCEPTED_MEDIA_TYPES = $ACCEPTED_GENERIC_MEDIA_TYPES + $ACCEPTED_DEFAULT_MEDIA_TYPE) */ static $ACCEPTED_DEFAULT_MEDIA_TYPE = "print"; static $ACCEPTED_GENERIC_MEDIA_TYPES = array("all", "static", "visual", "bitmap", "paged", "dompdf"); static $VALID_MEDIA_TYPES = array("all", "aural", "bitmap", "braille", "dompdf", "embossed", "handheld", "paged", "print", "projection", "screen", "speech", "static", "tty", "tv", "visual"); /** * @var FontMetrics */ private $fontMetrics; /** * The class constructor. * * The base protocol, host & path are initialized to those of * the current script. */ function __construct(Dompdf $dompdf) { $this->_dompdf = $dompdf; $this->setFontMetrics($dompdf->getFontMetrics()); $this->_styles = array(); $this->_loaded_files = array(); list($this->_protocol, $this->_base_host, $this->_base_path) = Helpers::explode_url($_SERVER["SCRIPT_FILENAME"]); $this->_page_styles = array("base" => null); } /** * Set the base protocol * * @param string $protocol */ function set_protocol($protocol) { $this->_protocol = $protocol; } /** * Set the base host * * @param string $host */ function set_host($host) { $this->_base_host = $host; } /** * Set the base path * * @param string $path */ function set_base_path($path) { $this->_base_path = $path; } /** * Return the Dompdf object * * @return Dompdf */ function get_dompdf() { return $this->_dompdf; } /** * Return the base protocol for this stylesheet * * @return string */ function get_protocol() { return $this->_protocol; } /** * Return the base host for this stylesheet * * @return string */ function get_host() { return $this->_base_host; } /** * Return the base path for this stylesheet * * @return string */ function get_base_path() { return $this->_base_path; } /** * Return the array of page styles * * @return Style[] */ function get_page_styles() { return $this->_page_styles; } /** * Add a new Style object to the stylesheet * add_style() adds a new Style object to the current stylesheet, or * merges a new Style with an existing one. * * @param string $key the Style's selector * @param Style $style the Style to be added * * @throws \Dompdf\Exception */ function add_style($key, Style $style) { if (!is_string($key)) { throw new Exception("CSS rule must be keyed by a string."); } if (!isset($this->_styles[$key])) { $this->_styles[$key] = array(); } $new_style = clone $style; $new_style->set_origin($this->_current_origin); $this->_styles[$key][] = $new_style; } /** * lookup a specifc Style collection * * lookup() returns the Style collection specified by $key, or null if the Style is * not found. * * @param string $key the selector of the requested Style * @return Style * * @Fixme _styles is a two dimensional array. It should produce wrong results */ function lookup($key) { if (!isset($this->_styles[$key])) { return null; } return $this->_styles[$key]; } /** * create a new Style object associated with this stylesheet * * @param Style $parent The style of this style's parent in the DOM tree * @return Style */ function create_style(Style $parent = null) { return new Style($this, $this->_current_origin); } /** * load and parse a CSS string * * @param string $css * @param int $origin */ function load_css(&$css, $origin = self::ORIG_AUTHOR) { if ($origin) { $this->_current_origin = $origin; } $this->_parse_css($css); } /** * load and parse a CSS file * * @param string $file * @param int $origin */ function load_css_file($file, $origin = self::ORIG_AUTHOR) { if ($origin) { $this->_current_origin = $origin; } // Prevent circular references if (isset($this->_loaded_files[$file])) { return; } $this->_loaded_files[$file] = true; if (strpos($file, "data:") === 0) { $parsed = Helpers::parse_data_uri($file); $css = $parsed["data"]; } else { $parsed_url = Helpers::explode_url($file); list($this->_protocol, $this->_base_host, $this->_base_path, $filename) = $parsed_url; // Fix submitted by Nick Oostveen for aliased directory support: if ($this->_protocol == "") { $file = $this->_base_path . $filename; } else { $file = Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $filename); } list($css, $http_response_header) = Helpers::getFileContent($file, $this->_dompdf->getHttpContext()); $good_mime_type = true; // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/ if (isset($http_response_header) && !$this->_dompdf->getQuirksmode()) { foreach ($http_response_header as $_header) { if (preg_match("@Content-Type:\s*([\w/]+)@i", $_header, $matches) && ($matches[1] !== "text/css") ) { $good_mime_type = false; } } } if (!$good_mime_type || $css == "") { Helpers::record_warnings(E_USER_WARNING, "Unable to load css file $file", __FILE__, __LINE__); return; } } $this->_parse_css($css); } /** * @link http://www.w3.org/TR/CSS21/cascade.html#specificity * * @param string $selector * @param int $origin : * - Stylesheet::ORIG_UA: user agent style sheet * - Stylesheet::ORIG_USER: user style sheet * - Stylesheet::ORIG_AUTHOR: author style sheet * * @return int */ private function _specificity($selector, $origin = self::ORIG_AUTHOR) { // http://www.w3.org/TR/CSS21/cascade.html#specificity // ignoring the ":" pseudoclass modifiers // also ignored in _css_selector_to_xpath $a = ($selector === "!attr") ? 1 : 0; $b = min(mb_substr_count($selector, "#"), 255); $c = min(mb_substr_count($selector, ".") + mb_substr_count($selector, "["), 255); $d = min(mb_substr_count($selector, " ") + mb_substr_count($selector, ">") + mb_substr_count($selector, "+"), 255); //If a normal element name is at the beginning of the string, //a leading whitespace might have been removed on whitespace collapsing and removal //therefore there might be one whitespace less as selected element names //this can lead to a too small specificity //see _css_selector_to_xpath if (!in_array($selector[0], array(" ", ">", ".", "#", "+", ":", "[")) && $selector !== "*") { $d++; } if ($this->_dompdf->getOptions()->getDebugCss()) { /*DEBUGCSS*/ print "<pre>\n"; /*DEBUGCSS*/ printf("_specificity(): 0x%08x \"%s\"\n", self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d)), $selector); /*DEBUGCSS*/ print "</pre>"; } return self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d)); } /** * Converts a CSS selector to an XPath query. * * @param string $selector * @param bool $first_pass * * @throws Exception * @return string */ private function _css_selector_to_xpath($selector, $first_pass = false) { // Collapse white space and strip whitespace around delimiters //$search = array("/\\s+/", "/\\s+([.>#+:])\\s+/"); //$replace = array(" ", "\\1"); //$selector = preg_replace($search, $replace, trim($selector)); // Initial query (non-absolute) $query = "//"; // Will contain :before and :after $pseudo_elements = array(); // Will contain :link, etc $pseudo_classes = array(); // Parse the selector //$s = preg_split("/([ :>.#+])/", $selector, -1, PREG_SPLIT_DELIM_CAPTURE); $delimiters = array(" ", ">", ".", "#", "+", ":", "[", "("); // Add an implicit * at the beginning of the selector // if it begins with an attribute selector if ($selector[0] === "[") { $selector = "*$selector"; } // Add an implicit space at the beginning of the selector if there is no // delimiter there already. if (!in_array($selector[0], $delimiters)) { $selector = " $selector"; } $tok = ""; $len = mb_strlen($selector); $i = 0; while ($i < $len) { $s = $selector[$i]; $i++; // Eat characters up to the next delimiter $tok = ""; $in_attr = false; $in_func = false; while ($i < $len) { $c = $selector[$i]; $c_prev = $selector[$i - 1]; if (!$in_func && !$in_attr && in_array($c, $delimiters) && !(($c == $c_prev) == ":")) { break; } if ($c_prev === "[") { $in_attr = true; } if ($c_prev === "(") { $in_func = true; } $tok .= $selector[$i++]; if ($in_attr && $c === "]") { $in_attr = false; break; } if ($in_func && $c === ")") { $in_func = false; break; } } switch ($s) { case " ": case ">": // All elements matching the next token that are direct children of // the current token $expr = $s === " " ? "descendant" : "child"; if (mb_substr($query, -1, 1) !== "/") { $query .= "/"; } // Tag names are case-insensitive $tok = strtolower($tok); if (!$tok) { $tok = "*"; } $query .= "$expr::$tok"; $tok = ""; break; case ".": case "#": // All elements matching the current token with a class/id equal to // the _next_ token. $attr = $s === "." ? "class" : "id"; // empty class/id == * if (mb_substr($query, -1, 1) === "/") { $query .= "*"; } // Match multiple classes: $tok contains the current selected // class. Search for class attributes with class="$tok", // class=".* $tok .*" and class=".* $tok" // This doesn't work because libxml only supports XPath 1.0... //$query .= "[matches(@$attr,\"^${tok}\$|^${tok}[ ]+|[ ]+${tok}\$|[ ]+${tok}[ ]+\")]"; // Query improvement by Michael Sheakoski <michael@mjsdigital.com>: $query .= "[contains(concat(' ', @$attr, ' '), concat(' ', '$tok', ' '))]"; $tok = ""; break; case "+": // All sibling elements that folow the current token if (mb_substr($query, -1, 1) !== "/") { $query .= "/"; } $query .= "following-sibling::$tok"; $tok = ""; break; case ":": $i2 = $i - strlen($tok) - 2; // the char before ":" if (($i2 < 0 || !isset($selector[$i2]) || (in_array($selector[$i2], $delimiters) && $selector[$i2] != ":")) && substr($query, -1) != "*") { $query .= "*"; } $last = false; // Pseudo-classes switch ($tok) { case "first-child": $query .= "[1]"; $tok = ""; break; case "last-child": $query .= "[not(following-sibling::*)]"; $tok = ""; break; case "first-of-type": $query .= "[position() = 1]"; $tok = ""; break; case "last-of-type": $query .= "[position() = last()]"; $tok = ""; break; // an+b, n, odd, and even /** @noinspection PhpMissingBreakStatementInspection */ case "nth-last-of-type": $last = true; case "nth-of-type": //FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally? $descendant_delimeter = strrpos($query, "::"); $isChild = substr($query, $descendant_delimeter-5, 5) == "child"; $el = substr($query, $descendant_delimeter+2); $query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . $el; $pseudo_classes[$tok] = true; $p = $i + 1; $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p)); // 1 if (preg_match("/^\d+$/", $nth)) { $condition = "position() = $nth"; } // odd elseif ($nth === "odd") { $condition = "(position() mod 2) = 1"; } // even elseif ($nth === "even") { $condition = "(position() mod 2) = 0"; } // an+b else { $condition = $this->_selector_an_plus_b($nth, $last); } $query .= "[$condition]"; $tok = ""; break; /** @noinspection PhpMissingBreakStatementInspection */ case "nth-last-child": $last = true; case "nth-child": //FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally? $descendant_delimeter = strrpos($query, "::"); $isChild = substr($query, $descendant_delimeter-5, 5) == "child"; $el = substr($query, $descendant_delimeter+2); $query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . "*"; $pseudo_classes[$tok] = true; $p = $i + 1; $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p)); // 1 if (preg_match("/^\d+$/", $nth)) { $condition = "position() = $nth"; } // odd elseif ($nth === "odd") { $condition = "(position() mod 2) = 1"; } // even elseif ($nth === "even") { $condition = "(position() mod 2) = 0"; } // an+b else { $condition = $this->_selector_an_plus_b($nth, $last); } $query .= "[$condition]"; if ($el != "*") { $query .= "[name() = '$el']"; } $tok = ""; break; //TODO: bit of a hack attempt at matches support, currently only matches against elements case "matches": $pseudo_classes[$tok] = true; $p = $i + 1; $matchList = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p)); // Tag names are case-insensitive $elements = array_map("trim", explode(",", strtolower($matchList))); foreach ($elements as &$element) { $element = "name() = '$element'"; } $query .= "[" . implode(" or ", $elements) . "]"; $tok = ""; break; case "link": $query .= "[@href]"; $tok = ""; break; case "first-line": case ":first-line": case "first-letter": case ":first-letter": // TODO $el = trim($tok, ":"); $pseudo_elements[$el] = true; break; // N/A case "focus": case "active": case "hover": case "visited": $query .= "[false()]"; $tok = ""; break; /* Pseudo-elements */ case "before": case ":before": case "after": case ":after": $pos = trim($tok, ":"); $pseudo_elements[$pos] = true; if (!$first_pass) { $query .= "/*[@$pos]"; } $tok = ""; break; case "empty": $query .= "[not(*) and not(normalize-space())]"; $tok = ""; break; case "disabled": case "checked": $query .= "[@$tok]"; $tok = ""; break; case "enabled": $query .= "[not(@disabled)]"; $tok = ""; break; // the selector is not handled, until we support all possible selectors force an empty set (silent failure) default: $query = "/../.."; // go up two levels because generated content starts on the body element $tok = ""; break; } break; case "[": // Attribute selectors. All with an attribute matching the following token(s) $attr_delimiters = array("=", "]", "~", "|", "$", "^", "*"); $tok_len = mb_strlen($tok); $j = 0; $attr = ""; $op = ""; $value = ""; while ($j < $tok_len) { if (in_array($tok[$j], $attr_delimiters)) { break; } $attr .= $tok[$j++]; } switch ($tok[$j]) { case "~": case "|": case "$": case "^": case "*": $op .= $tok[$j++]; if ($tok[$j] !== "=") { throw new Exception("Invalid CSS selector syntax: invalid attribute selector: $selector"); } $op .= $tok[$j]; break; case "=": $op = "="; break; } // Read the attribute value, if required if ($op != "") { $j++; while ($j < $tok_len) { if ($tok[$j] === "]") { break; } $value .= $tok[$j++]; } } if ($attr == "") { throw new Exception("Invalid CSS selector syntax: missing attribute name"); } $value = trim($value, "\"'"); switch ($op) { case "": $query .= "[@$attr]"; break; case "=": $query .= "[@$attr=\"$value\"]"; break; case "~=": // FIXME: this will break if $value contains quoted strings // (e.g. [type~="a b c" "d e f"]) $values = explode(" ", $value); $query .= "["; foreach ($values as $val) { $query .= "@$attr=\"$val\" or "; } $query = rtrim($query, " or ") . "]"; break; case "|=": $values = explode("-", $value); $query .= "["; foreach ($values as $val) { $query .= "starts-with(@$attr, \"$val\") or "; } $query = rtrim($query, " or ") . "]"; break; case "$=": $query .= "[substring(@$attr, string-length(@$attr)-" . (strlen($value) - 1) . ")=\"$value\"]"; break; case "^=": $query .= "[starts-with(@$attr,\"$value\")]"; break; case "*=": $query .= "[contains(@$attr,\"$value\")]"; break; } break; } } $i++; // case ":": // // Pseudo selectors: ignore for now. Partially handled directly // // below. // // Skip until the next special character, leaving the token as-is // while ( $i < $len ) { // if ( in_array($selector[$i], $delimiters) ) // break; // $i++; // } // break; // default: // // Add the character to the token // $tok .= $selector[$i++]; // break; // } // } // Trim the trailing '/' from the query if (mb_strlen($query) > 2) { $query = rtrim($query, "/"); } return array("query" => $query, "pseudo_elements" => $pseudo_elements); } /** * https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb * * @param $expr * @param bool $last * @return string */ protected function _selector_an_plus_b($expr, $last = false) { $expr = preg_replace("/\s/", "", $expr); if (!preg_match("/^(?P<a>-?[0-9]*)?n(?P<b>[-+]?[0-9]+)?$/", $expr, $matches)) { return "false()"; } $a = ((isset($matches["a"]) && $matches["a"] !== "") ? intval($matches["a"]) : 1); $b = ((isset($matches["b"]) && $matches["b"] !== "") ? intval($matches["b"]) : 0); $position = ($last ? "(last()-position()+1)" : "position()"); if ($b == 0) { return "($position mod $a) = 0"; } else { $compare = (($a < 0) ? "<=" : ">="); $b2 = -$b; if ($b2 >= 0) { $b2 = "+$b2"; } return "($position $compare $b) and ((($position $b2) mod " . abs($a) . ") = 0)"; } } /** * applies all current styles to a particular document tree * * apply_styles() applies all currently loaded styles to the provided * {@link FrameTree}. Aside from parsing CSS, this is the main purpose * of this class. * * @param \Dompdf\Frame\FrameTree $tree */ function apply_styles(FrameTree $tree) { // Use XPath to select nodes. This would be easier if we could attach // Frame objects directly to DOMNodes using the setUserData() method, but // we can't do that just yet. Instead, we set a _node attribute_ in // Frame->set_id() and use that as a handle on the Frame object via // FrameTree::$_registry. // We create a scratch array of styles indexed by frame id. Once all // styles have been assigned, we order the cached styles by specificity // and create a final style object to assign to the frame. // FIXME: this is not particularly robust... $styles = array(); $xp = new DOMXPath($tree->get_dom()); $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); // Add generated content foreach ($this->_styles as $selector => $selector_styles) { /** @var Style $style */ foreach ($selector_styles as $style) { if (strpos($selector, ":before") === false && strpos($selector, ":after") === false) { continue; } $query = $this->_css_selector_to_xpath($selector, true); // Retrieve the nodes, limit to body for generated content //TODO: If we use a context node can we remove the leading dot? $nodes = @$xp->query('.' . $query["query"]); if ($nodes == null) { Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__); continue; } /** @var \DOMElement $node */ foreach ($nodes as $node) { // Only DOMElements get styles if ($node->nodeType != XML_ELEMENT_NODE) { continue; } foreach (array_keys($query["pseudo_elements"], true, true) as $pos) { // Do not add a new pseudo element if another one already matched if ($node->hasAttribute("dompdf_{$pos}_frame_id")) { continue; } if (($src = $this->_image($style->get_prop('content'))) !== "none") { $new_node = $node->ownerDocument->createElement("img_generated"); $new_node->setAttribute("src", $src); } else { $new_node = $node->ownerDocument->createElement("dompdf_generated"); } $new_node->setAttribute($pos, $pos); $new_frame_id = $tree->insert_node($node, $new_node, $pos); $node->setAttribute("dompdf_{$pos}_frame_id", $new_frame_id); } } } } // Apply all styles in stylesheet foreach ($this->_styles as $selector => $selector_styles) { /** @var Style $style */ foreach ($selector_styles as $style) { $query = $this->_css_selector_to_xpath($selector); // Retrieve the nodes $nodes = @$xp->query($query["query"]); if ($nodes == null) { Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__); continue; } $spec = $this->_specificity($selector, $style->get_origin()); foreach ($nodes as $node) { // Retrieve the node id // Only DOMElements get styles if ($node->nodeType != XML_ELEMENT_NODE) { continue; } $id = $node->getAttribute("frame_id"); // Assign the current style to the scratch array $styles[$id][$spec][] = $style; } } } // Set the page width, height, and orientation based on the canvas paper size $canvas = $this->_dompdf->getCanvas(); $paper_width = $canvas->get_width(); $paper_height = $canvas->get_height(); $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait"); if ($this->_page_styles["base"] && is_array($this->_page_styles["base"]->size)) { $paper_width = $this->_page_styles['base']->size[0]; $paper_height = $this->_page_styles['base']->size[1]; $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait"); } // Now create the styles and assign them to the appropriate frames. (We // iterate over the tree using an implicit FrameTree iterator.) $root_flg = false; foreach ($tree->get_frames() as $frame) { // Helpers::pre_r($frame->get_node()->nodeName . ":"); if (!$root_flg && $this->_page_styles["base"]) { $style = $this->_page_styles["base"]; } else { $style = $this->create_style(); } // Find nearest DOMElement parent $p = $frame; while ($p = $p->get_parent()) { if ($p->get_node()->nodeType == XML_ELEMENT_NODE) { break; } } // Styles can only be applied directly to DOMElements; anonymous // frames inherit from their parent if ($frame->get_node()->nodeType != XML_ELEMENT_NODE) { if ($p) { $style->inherit($p->get_style()); } $frame->set_style($style); continue; } $id = $frame->get_id(); // Handle HTML 4.0 attributes AttributeTranslator::translate_attributes($frame); if (($str = $frame->get_node()->getAttribute(AttributeTranslator::$_style_attr)) !== "") { $styles[$id][self::SPEC_NON_CSS][] = $this->_parse_properties($str); } // Locate any additional style attributes if (($str = $frame->get_node()->getAttribute("style")) !== "") { // Destroy CSS comments $str = preg_replace("'/\*.*?\*/'si", "", $str); $spec = $this->_specificity("!attr", self::ORIG_AUTHOR); $styles[$id][$spec][] = $this->_parse_properties($str); } // Grab the applicable styles if (isset($styles[$id])) { /** @var array[][] $applied_styles */ $applied_styles = $styles[$frame->get_id()]; // Sort by specificity ksort($applied_styles); if ($DEBUGCSS) { $debug_nodename = $frame->get_node()->nodeName; print "<pre>\n[$debug_nodename\n"; foreach ($applied_styles as $spec => $arr) { printf("specificity: 0x%08x\n", $spec); /** @var Style $s */ foreach ($arr as $s) { print "[\n"; $s->debug_print(); print "]\n"; } } } // Merge the new styles with the inherited styles $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType(); foreach ($applied_styles as $arr) { /** @var Style $s */ foreach ($arr as $s) { $media_queries = $s->get_media_queries(); foreach ($media_queries as $media_query) { list($media_query_feature, $media_query_value) = $media_query; // if any of the Style's media queries fail then do not apply the style //TODO: When the media query logic is fully developed we should not apply the Style when any of the media queries fail or are bad, per https://www.w3.org/TR/css3-mediaqueries/#error-handling if (in_array($media_query_feature, self::$VALID_MEDIA_TYPES)) { if ((strlen($media_query_feature) === 0 && !in_array($media_query, $acceptedmedia)) || (in_array($media_query, $acceptedmedia) && $media_query_value == "not")) { continue (3); } } else { switch ($media_query_feature) { case "height": if ($paper_height !== (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "min-height": if ($paper_height < (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "max-height": if ($paper_height > (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "width": if ($paper_width !== (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "min-width": //if (min($paper_width, $media_query_width) === $paper_width) { if ($paper_width < (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "max-width": //if (max($paper_width, $media_query_width) === $paper_width) { if ($paper_width > (float)$style->length_in_pt($media_query_value)) { continue (3); } break; case "orientation": if ($paper_orientation !== $media_query_value) { continue (3); } break; default: Helpers::record_warnings(E_USER_WARNING, "Unknown media query: $media_query_feature", __FILE__, __LINE__); break; } } } $style->merge($s); } } } // Inherit parent's styles if required if ($p) { if ($DEBUGCSS) { print "inherit:\n"; print "[\n"; $p->get_style()->debug_print(); print "]\n"; } $style->inherit($p->get_style()); } if ($DEBUGCSS) { print "DomElementStyle:\n"; print "[\n"; $style->debug_print(); print "]\n"; print "/$debug_nodename]\n</pre>"; } /*DEBUGCSS print: see below different print debugging method Helpers::pre_r($frame->get_node()->nodeName . ":"); echo "<pre>"; echo $style; echo "</pre>";*/ $frame->set_style($style); if (!$root_flg && $this->_page_styles["base"]) { $root_flg = true; // set the page width, height, and orientation based on the parsed page style if ($style->size !== "auto") { list($paper_width, $paper_height) = $style->size; } $paper_width = $paper_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->margin_right); $paper_height = $paper_height - (float)$style->length_in_pt($style->margin_top) - (float)$style->length_in_pt($style->margin_bottom); $paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait"); } } // We're done! Clean out the registry of all styles since we // won't be needing this later. foreach (array_keys($this->_styles) as $key) { $this->_styles[$key] = null; unset($this->_styles[$key]); } } /** * parse a CSS string using a regex parser * Called by {@link Stylesheet::parse_css()} * * @param string $str * * @throws Exception */ private function _parse_css($str) { $str = trim($str); // Destroy comments and remove HTML comments $css = preg_replace(array( "'/\*.*?\*/'si", "/^<!--/", "/-->$/" ), "", $str); // FIXME: handle '{' within strings, e.g. [attr="string {}"] // Something more legible: $re = "/\s* # Skip leading whitespace \n" . "( @([^\s{]+)\s*([^{;]*) (?:;|({)) )? # Match @rules followed by ';' or '{' \n" . "(?(1) # Only parse sub-sections if we're in an @rule... \n" . " (?(4) # ...and if there was a leading '{' \n" . " \s*( (?:(?>[^{}]+) ({)? # Parse rulesets and individual @page rules \n" . " (?(6) (?>[^}]*) }) \s*)+? \n" . " ) \n" . " }) # Balancing '}' \n" . "| # Branch to match regular rules (not preceded by '@') \n" . "([^{]*{[^}]*})) # Parse normal rulesets \n" . "/xs"; if (preg_match_all($re, $css, $matches, PREG_SET_ORDER) === false) { // An error occurred throw new Exception("Error parsing css file: preg_match_all() failed."); } // After matching, the array indicies are set as follows: // // [0] => complete text of match // [1] => contains '@import ...;' or '@media {' if applicable // [2] => text following @ for cases where [1] is set // [3] => media types or full text following '@import ...;' // [4] => '{', if present // [5] => rulesets within media rules // [6] => '{', within media rules // [7] => individual rules, outside of media rules // $media_query_regex = "/(?:((only|not)?\s*(" . implode("|", self::$VALID_MEDIA_TYPES) . "))|(\s*\(\s*((?:(min|max)-)?([\w\-]+))\s*(?:\:\s*(.*?)\s*)?\)))/isx"; //Helpers::pre_r($matches); foreach ($matches as $match) { $match[2] = trim($match[2]); if ($match[2] !== "") { // Handle @rules switch ($match[2]) { case "import": $this->_parse_import($match[3]); break; case "media": $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType(); $media_queries = preg_split("/\s*,\s*/", mb_strtolower(trim($match[3]))); foreach ($media_queries as $media_query) { if (in_array($media_query, $acceptedmedia)) { //if we have a media type match go ahead and parse the stylesheet $this->_parse_sections($match[5]); break; } elseif (!in_array($media_query, self::$VALID_MEDIA_TYPES)) { // otherwise conditionally parse the stylesheet assuming there are parseable media queries if (preg_match_all($media_query_regex, $media_query, $media_query_matches, PREG_SET_ORDER) !== false) { $mq = array(); foreach ($media_query_matches as $media_query_match) { if (empty($media_query_match[1]) === false) { $media_query_feature = strtolower($media_query_match[3]); $media_query_value = strtolower($media_query_match[2]); $mq[] = array($media_query_feature, $media_query_value); } else if (empty($media_query_match[4]) === false) { $media_query_feature = strtolower($media_query_match[5]); $media_query_value = (array_key_exists(8, $media_query_match) ? strtolower($media_query_match[8]) : null); $mq[] = array($media_query_feature, $media_query_value); } } $this->_parse_sections($match[5], $mq); break; } } } break; case "page": //This handles @page to be applied to page oriented media //Note: This has a reduced syntax: //@page { margin:1cm; color:blue; } //Not a sequence of styles like a full.css, but only the properties //of a single style, which is applied to the very first "root" frame before //processing other styles of the frame. //Working properties: // margin (for margin around edge of paper) // font-family (default font of pages) // color (default text color of pages) //Non working properties: // border // padding // background-color //Todo:Reason is unknown //Other properties (like further font or border attributes) not tested. //If a border or background color around each paper sheet is desired, //assign it to the <body> tag, possibly only for the css of the correct media type. // If the page has a name, skip the style. $page_selector = trim($match[3]); $key = null; switch ($page_selector) { case "": $key = "base"; break; case ":left": case ":right": case ":odd": case ":even": /** @noinspection PhpMissingBreakStatementInspection */ case ":first": $key = $page_selector; default: continue; } // Store the style for later... if (empty($this->_page_styles[$key])) { $this->_page_styles[$key] = $this->_parse_properties($match[5]); } else { $this->_page_styles[$key]->merge($this->_parse_properties($match[5])); } break; case "font-face": $this->_parse_font_face($match[5]); break; default: // ignore everything else break; } continue; } if ($match[7] !== "") { $this->_parse_sections($match[7]); } } } /** * See also style.cls Style::_image(), refactoring?, works also for imported css files * * @param $val * @return string */ protected function _image($val) { $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); $parsed_url = "none"; if (mb_strpos($val, "url") === false) { $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none } else { $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val)); // Resolve the url now in the context of the current stylesheet $parsed_url = Helpers::explode_url($val); if ($parsed_url["protocol"] == "" && $this->get_protocol() == "") { if ($parsed_url["path"][0] === '/' || $parsed_url["path"][0] === '\\') { $path = $_SERVER["DOCUMENT_ROOT"] . '/'; } else { $path = $this->get_base_path(); } $path .= $parsed_url["path"] . $parsed_url["file"]; $path = realpath($path); // If realpath returns FALSE then specifically state that there is no background image // FIXME: Is this causing problems for imported CSS files? There are some './none' references when running the test cases. if (!$path) { $path = 'none'; } } else { $path = Helpers::build_url($this->get_protocol(), $this->get_host(), $this->get_base_path(), $val); } } if ($DEBUGCSS) { print "<pre>[_image\n"; print_r($parsed_url); print $this->get_protocol() . "\n" . $this->get_base_path() . "\n" . $path . "\n"; print "_image]</pre>";; } return $path; } /** * parse @import{} sections * * @param string $url the url of the imported CSS file */ private function _parse_import($url) { $arr = preg_split("/[\s\n,]/", $url, -1, PREG_SPLIT_NO_EMPTY); $url = array_shift($arr); $accept = false; if (count($arr) > 0) { $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType(); // @import url media_type [media_type...] foreach ($arr as $type) { if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) { $accept = true; break; } } } else { // unconditional import $accept = true; } if ($accept) { // Store our current base url properties in case the new url is elsewhere $protocol = $this->_protocol; $host = $this->_base_host; $path = $this->_base_path; // $url = str_replace(array('"',"url", "(", ")"), "", $url); // If the protocol is php, assume that we will import using file:// // $url = Helpers::build_url($protocol == "php://" ? "file://" : $protocol, $host, $path, $url); // Above does not work for subfolders and absolute urls. // Todo: As above, do we need to replace php or file to an empty protocol for local files? $url = $this->_image($url); $this->load_css_file($url); // Restore the current base url $this->_protocol = $protocol; $this->_base_host = $host; $this->_base_path = $path; } } /** * parse @font-face{} sections * http://www.w3.org/TR/css3-fonts/#the-font-face-rule * * @param string $str CSS @font-face rules */ private function _parse_font_face($str) { $descriptors = $this->_parse_properties($str); preg_match_all("/(url|local)\s*\([\"\']?([^\"\'\)]+)[\"\']?\)\s*(format\s*\([\"\']?([^\"\'\)]+)[\"\']?\))?/i", $descriptors->src, $src); $sources = array(); $valid_sources = array(); foreach ($src[0] as $i => $value) { $source = array( "local" => strtolower($src[1][$i]) === "local", "uri" => $src[2][$i], "format" => strtolower($src[4][$i]), "path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]), ); if (!$source["local"] && in_array($source["format"], array("", "truetype"))) { $valid_sources[] = $source; } $sources[] = $source; } // No valid sources if (empty($valid_sources)) { return; } $style = array( "family" => $descriptors->get_font_family_raw(), "weight" => $descriptors->font_weight, "style" => $descriptors->font_style, ); $this->getFontMetrics()->registerFont($style, $valid_sources[0]["path"], $this->_dompdf->getHttpContext()); } /** * parse regular CSS blocks * * _parse_properties() creates a new Style object based on the provided * CSS rules. * * @param string $str CSS rules * @return Style */ private function _parse_properties($str) { $properties = preg_split("/;(?=(?:[^\(]*\([^\)]*\))*(?![^\)]*\)))/", $str); $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); if ($DEBUGCSS) { print '[_parse_properties'; } // Create the style $style = new Style($this, Stylesheet::ORIG_AUTHOR); foreach ($properties as $prop) { // If the $prop contains an url, the regex may be wrong // @todo: fix the regex so that it works everytime /*if (strpos($prop, "url(") === false) { if (preg_match("/([a-z-]+)\s*:\s*[^:]+$/i", $prop, $m)) $prop = $m[0]; }*/ //A css property can have " ! important" appended (whitespace optional) //strip this off to decode core of the property correctly. //Pass on in the style to allow proper handling: //!important properties can only be overridden by other !important ones. //$style->$prop_name = is a shortcut of $style->__set($prop_name,$value);. //If no specific set function available, set _props["prop_name"] //style is always copied completely, or $_props handled separately //Therefore set a _important_props["prop_name"]=true to indicate the modifier /* Instead of short code, prefer the typical case with fast code $important = preg_match("/(.*?)!\s*important/",$prop,$match); if ( $important ) { $prop = $match[1]; } $prop = trim($prop); */ if ($DEBUGCSS) print '('; $important = false; $prop = trim($prop); if (substr($prop, -9) === 'important') { $prop_tmp = rtrim(substr($prop, 0, -9)); if (substr($prop_tmp, -1) === '!') { $prop = rtrim(substr($prop_tmp, 0, -1)); $important = true; } } if ($prop === "") { if ($DEBUGCSS) print 'empty)'; continue; } $i = mb_strpos($prop, ":"); if ($i === false) { if ($DEBUGCSS) print 'novalue' . $prop . ')'; continue; } $prop_name = rtrim(mb_strtolower(mb_substr($prop, 0, $i))); $value = ltrim(mb_substr($prop, $i + 1)); if ($DEBUGCSS) print $prop_name . ':=' . $value . ($important ? '!IMPORTANT' : '') . ')'; //New style, anyway empty //if ($important || !$style->important_get($prop_name) ) { //$style->$prop_name = array($value,$important); //assignment might be replaced by overloading through __set, //and overloaded functions might check _important_props, //therefore set _important_props first. if ($important) { $style->important_set($prop_name); } //For easier debugging, don't use overloading of assignments with __set $style->$prop_name = $value; //$style->props_set($prop_name, $value); } if ($DEBUGCSS) print '_parse_properties]'; return $style; } /** * parse selector + rulesets * * @param string $str CSS selectors and rulesets * @param array $media_queries */ private function _parse_sections($str, $media_queries = array()) { // Pre-process: collapse all whitespace and strip whitespace around '>', // '.', ':', '+', '#' $patterns = array("/[\\s\n]+/", "/\\s+([>.:+#])\\s+/"); $replacements = array(" ", "\\1"); $str = preg_replace($patterns, $replacements, $str); $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss(); $sections = explode("}", $str); if ($DEBUGCSS) print '[_parse_sections'; foreach ($sections as $sect) { $i = mb_strpos($sect, "{"); if ($i === false) { continue; } //$selectors = explode(",", mb_substr($sect, 0, $i)); $selectors = preg_split("/,(?![^\(]*\))/", mb_substr($sect, 0, $i),0, PREG_SPLIT_NO_EMPTY); if ($DEBUGCSS) print '[section'; $style = $this->_parse_properties(trim(mb_substr($sect, $i + 1))); // Assign it to the selected elements foreach ($selectors as $selector) { $selector = trim($selector); if ($selector == "") { if ($DEBUGCSS) print '#empty#'; continue; } if ($DEBUGCSS) print '#' . $selector . '#'; //if ($DEBUGCSS) { if (strpos($selector,'p') !== false) print '!!!p!!!#'; } //FIXME: tag the selector with a hash of the media query to separate it from non-conditional styles (?), xpath comments are probably not what we want to do here if (count($media_queries) > 0) { $style->set_media_queries($media_queries); } $this->add_style($selector, $style); } if ($DEBUGCSS) { print 'section]'; } } if ($DEBUGCSS) { print '_parse_sections]'; } } /** * @return string */ public static function getDefaultStylesheet() { $dir = realpath(__DIR__ . "/../.."); return $dir . self::DEFAULT_STYLESHEET; } /** * @param FontMetrics $fontMetrics * @return $this */ public function setFontMetrics(FontMetrics $fontMetrics) { $this->fontMetrics = $fontMetrics; return $this; } /** * @return FontMetrics */ public function getFontMetrics() { return $this->fontMetrics; } /** * dumps the entire stylesheet as a string * * Generates a string of each selector and associated style in the * Stylesheet. Useful for debugging. * * @return string */ function __toString() { $str = ""; foreach ($this->_styles as $selector => $selector_styles) { /** @var Style $style */ foreach ($selector_styles as $style) { $str .= "$selector => " . $style->__toString() . "\n"; } } return $str; } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.23 | Generation time: 0 |
proxy
|
phpinfo
|
Settings