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