Incluido dompdf.
[CLab.git] / include / dompdf / include / style.cls.php
blob0b0e40439adfe1ae8556076776573cb81219b7a5
1 <?php
2 /**
3 * DOMPDF - PHP5 HTML to PDF renderer
5 * File: $RCSfile: style.cls.php,v $
6 * Created on: 2004-06-01
8 * Copyright (c) 2004 - Benj Carson <benjcarson@digitaljunkies.ca>
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this library in the file LICENSE.LGPL; if not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23 * 02111-1307 USA
25 * Alternatively, you may distribute this software under the terms of the
26 * PHP License, version 3.0 or later. A copy of this license should have
27 * been distributed with this file in the file LICENSE.PHP . If this is not
28 * the case, you can obtain a copy at http://www.php.net/license/3_0.txt.
30 * The latest version of DOMPDF might be available at:
31 * http://www.digitaljunkies.ca/dompdf
33 * @link http://www.digitaljunkies.ca/dompdf
34 * @copyright 2004 Benj Carson
35 * @author Benj Carson <benjcarson@digitaljunkies.ca>
36 * @package dompdf
37 * @version 0.5.1
40 /* $Id: style.cls.php,v 1.18 2006/07/07 21:31:04 benjcarson Exp $ */
42 /**
43 * Represents CSS properties.
45 * The Style class is responsible for handling and storing CSS properties.
46 * It includes methods to resolve colours and lengths, as well as getters &
47 * setters for many CSS properites.
49 * Actual CSS parsing is performed in the {@link Stylesheet} class.
51 * @package dompdf
53 class Style {
55 /**
56 * Default font size, in points.
58 * @var float
60 static $default_font_size = 12;
62 /**
63 * Default line height, as a fraction of the font size.
65 * @var float
67 static $default_line_height = 1.2;
69 /**
70 * List of all inline types. Should really be a constant.
72 * @var array
74 static $INLINE_TYPES = array("inline");
76 /**
77 * List of all block types. Should really be a constant.
79 * @var array
81 static $BLOCK_TYPES = array("block","inline-block", "table-cell", "list-item");
83 /**
84 * List of all table types. Should really be a constant.
86 * @var array;
88 static $TABLE_TYPES = array("table", "inline-table");
90 /**
91 * List of valid border styles. Should also really be a constant.
93 * @var array
95 static $BORDER_STYLES = array("none", "hidden", "dotted", "dashed", "solid",
96 "double", "groove", "ridge", "inset", "outset");
98 /**
99 * Default style values.
101 * @link http://www.w3.org/TR/CSS21/propidx.html
103 * @var array
105 static protected $_defaults = null;
108 * List of inherited properties
110 * @link http://www.w3.org/TR/CSS21/propidx.html
112 * @var array
114 static protected $_inherited = null;
117 * The stylesheet this style belongs to
119 * @see Stylesheet
120 * @var Stylesheet
122 protected $_stylesheet; // stylesheet this style is attached to
125 * Main array of all CSS properties & values
127 * @var array
129 protected $_props;
132 * Cached property values
134 * @var array
136 protected $_prop_cache;
139 * Font size of parent element in document tree. Used for relative font
140 * size resolution.
142 * @var float
144 protected $_parent_font_size; // Font size of parent element
146 // private members
148 * True once the font size is resolved absolutely
150 * @var bool
152 private $__font_size_calculated; // Cache flag
155 * Class constructor
157 * @param Stylesheet $stylesheet the stylesheet this Style is associated with.
159 function __construct(Stylesheet $stylesheet) {
160 $this->_props = array();
161 $this->_stylesheet = $stylesheet;
162 $this->_parent_font_size = null;
163 $this->__font_size_calculated = false;
165 if ( !isset(self::$_defaults) ) {
167 // Shorthand
168 $d =& self::$_defaults;
170 // All CSS 2.1 properties, and their default values
171 $d["azimuth"] = "center";
172 $d["background_attachment"] = "scroll";
173 $d["background_color"] = "transparent";
174 $d["background_image"] = "none";
175 $d["background_position"] = "0% 0%";
176 $d["background_repeat"] = "repeat";
177 $d["background"] = "";
178 $d["border_collapse"] = "separate";
179 $d["border_color"] = "";
180 $d["border_spacing"] = "0";
181 $d["border_style"] = "";
182 $d["border_top"] = "";
183 $d["border_right"] = "";
184 $d["border_bottom"] = "";
185 $d["border_left"] = "";
186 $d["border_top_color"] = "";
187 $d["border_right_color"] = "";
188 $d["border_bottom_color"] = "";
189 $d["border_left_color"] = "";
190 $d["border_top_style"] = "none";
191 $d["border_right_style"] = "none";
192 $d["border_bottom_style"] = "none";
193 $d["border_left_style"] = "none";
194 $d["border_top_width"] = "medium";
195 $d["border_right_width"] = "medium";
196 $d["border_bottom_width"] = "medium";
197 $d["border_left_width"] = "medium";
198 $d["border_width"] = "medium";
199 $d["border"] = "";
200 $d["bottom"] = "auto";
201 $d["caption_side"] = "top";
202 $d["clear"] = "none";
203 $d["clip"] = "auto";
204 $d["color"] = "#000000";
205 $d["content"] = "normal";
206 $d["counter_increment"] = "none";
207 $d["counter_reset"] = "none";
208 $d["cue_after"] = "none";
209 $d["cue_before"] = "none";
210 $d["cue"] = "";
211 $d["cursor"] = "auto";
212 $d["direction"] = "ltr";
213 $d["display"] = "inline";
214 $d["elevation"] = "level";
215 $d["empty_cells"] = "show";
216 $d["float"] = "none";
217 $d["font_family"] = "serif";
218 $d["font_size"] = "medium";
219 $d["font_style"] = "normal";
220 $d["font_variant"] = "normal";
221 $d["font_weight"] = "normal";
222 $d["font"] = "";
223 $d["height"] = "auto";
224 $d["left"] = "auto";
225 $d["letter_spacing"] = "normal";
226 $d["line_height"] = "normal";
227 $d["list_style_image"] = "none";
228 $d["list_style_position"] = "outside";
229 $d["list_style_type"] = "disc";
230 $d["list_style"] = "";
231 $d["margin_right"] = "0";
232 $d["margin_left"] = "0";
233 $d["margin_top"] = "0";
234 $d["margin_bottom"] = "0";
235 $d["margin"] = "";
236 $d["max_height"] = "none";
237 $d["max_width"] = "none";
238 $d["min_height"] = "0";
239 $d["min_width"] = "0";
240 $d["orphans"] = "2";
241 $d["outline_color"] = "invert";
242 $d["outline_style"] = "none";
243 $d["outline_width"] = "medium";
244 $d["outline"] = "";
245 $d["overflow"] = "visible";
246 $d["padding_top"] = "0";
247 $d["padding_right"] = "0";
248 $d["padding_bottom"] = "0";
249 $d["padding_left"] = "0";
250 $d["padding"] = "";
251 $d["page_break_after"] = "auto";
252 $d["page_break_before"] = "auto";
253 $d["page_break_inside"] = "auto";
254 $d["pause_after"] = "0";
255 $d["pause_before"] = "0";
256 $d["pause"] = "";
257 $d["pitch_range"] = "50";
258 $d["pitch"] = "medium";
259 $d["play_during"] = "auto";
260 $d["position"] = "static";
261 $d["quotes"] = "";
262 $d["richness"] = "50";
263 $d["right"] = "auto";
264 $d["speak_header"] = "once";
265 $d["speak_numeral"] = "continuous";
266 $d["speak_punctuation"] = "none";
267 $d["speak"] = "normal";
268 $d["speech_rate"] = "medium";
269 $d["stress"] = "50";
270 $d["table_layout"] = "auto";
271 $d["text_align"] = "left";
272 $d["text_decoration"] = "none";
273 $d["text_indent"] = "0";
274 $d["text_transform"] = "none";
275 $d["top"] = "auto";
276 $d["unicode_bidi"] = "normal";
277 $d["vertical_align"] = "baseline";
278 $d["visibility"] = "visible";
279 $d["voice_family"] = "";
280 $d["volume"] = "medium";
281 $d["white_space"] = "normal";
282 $d["widows"] = "2";
283 $d["width"] = "auto";
284 $d["word_spacing"] = "normal";
285 $d["z_index"] = "auto";
287 // Properties that inherit by default
288 self::$_inherited = array("azimuth",
289 "border_collapse",
290 "border_spacing",
291 "caption_side",
292 "color",
293 "cursor",
294 "direction",
295 "elevation",
296 "empty_cells",
297 "font_family",
298 "font_size",
299 "font_style",
300 "font_variant",
301 "font_weight",
302 "font",
303 "letter_spacing",
304 "line_height",
305 "list_style_image",
306 "list_style_position",
307 "list_style_type",
308 "list_style",
309 "orphans",
310 "page_break_inside",
311 "pitch_range",
312 "pitch",
313 "quotes",
314 "richness",
315 "speak_header",
316 "speak_numeral",
317 "speak_punctuation",
318 "speak",
319 "speech_rate",
320 "stress",
321 "text_align",
322 "text_indent",
323 "text_transform",
324 "visibility",
325 "voice_family",
326 "volume",
327 "white_space",
328 "widows",
329 "word_spacing");
335 * "Destructor": forcibly free all references held by this object
337 function dispose() {
338 unset($this->_stylesheet);
342 * returns the {@link Stylesheet} this Style is associated with.
344 * @return Stylesheet
346 function get_stylesheet() { return $this->_stylesheet; }
349 * Converts any CSS length value into an absolute length in points.
351 * length_in_pt() takes a single length (e.g. '1em') or an array of
352 * lengths and returns an absolute length. If an array is passed, then
353 * the return value is the sum of all elements.
355 * If a reference size is not provided, the default font size is used
356 * ({@link Style::$default_font_size}).
358 * @param float|array $length the length or array of lengths to resolve
359 * @param float $ref_size an absolute reference size to resolve percentage lengths
360 * @return float
362 function length_in_pt($length, $ref_size = null) {
364 if ( !is_array($length) )
365 $length = array($length);
367 if ( !isset($ref_size) )
368 $ref_size = self::$default_font_size;
370 $ret = 0;
371 foreach ($length as $l) {
373 if ( $l === "auto" )
374 return "auto";
376 if ( $l === "none" )
377 return "none";
379 // Assume numeric values are already in points
380 if ( is_numeric($l) ) {
381 $ret += $l;
382 continue;
385 if ( $l === "normal" ) {
386 $ret += $ref_size;
387 continue;
390 // Border lengths
391 if ( $l === "thin" ) {
392 $ret += 0.5;
393 continue;
396 if ( $l === "medium" ) {
397 $ret += 1.5;
398 continue;
401 if ( $l === "thick" ) {
402 $ret += 2.5;
403 continue;
406 if ( ($i = mb_strpos($l, "pt")) !== false ) {
407 $ret += mb_substr($l, 0, $i);
408 continue;
411 if ( ($i = mb_strpos($l, "px")) !== false ) {
412 $ret += mb_substr($l, 0, $i);
413 continue;
416 if ( ($i = mb_strpos($l, "em")) !== false ) {
417 $ret += mb_substr($l, 0, $i) * $this->__get("font_size");
418 continue;
421 // FIXME: em:ex ratio?
422 if ( ($i = mb_strpos($l, "ex")) !== false ) {
423 $ret += mb_substr($l, 0, $i) * $this->__get("font_size");
424 continue;
427 if ( ($i = mb_strpos($l, "%")) !== false ) {
428 $ret += mb_substr($l, 0, $i)/100 * $ref_size;
429 continue;
432 if ( ($i = mb_strpos($l, "in")) !== false ) {
433 $ret += mb_substr($l, 0, $i) * 72;
434 continue;
437 if ( ($i = mb_strpos($l, "cm")) !== false ) {
438 $ret += mb_substr($l, 0, $i) * 72 / 2.54;
439 continue;
442 if ( ($i = mb_strpos($l, "mm")) !== false ) {
443 $ret += mb_substr($l, 0, $i) * 72 / 25.4;
444 continue;
447 if ( ($i = mb_strpos($l, "pc")) !== false ) {
448 $ret += mb_substr($l, 0, $i) / 12;
449 continue;
452 // Bogus value
453 $ret += $ref_size;
456 return $ret;
461 * Set inherited properties in this style using values in $parent
463 * @param Style $parent
465 function inherit(Style $parent) {
467 // Set parent font size
468 $this->_parent_font_size = $parent->get_font_size();
470 foreach (self::$_inherited as $prop) {
471 if ( !isset($this->_props[$prop]) && isset($parent->_props[$prop]) )
472 $this->_props[$prop] = $parent->_props[$prop];
475 foreach (array_keys($this->_props) as $prop) {
476 if ( $this->_props[$prop] == "inherit" )
477 $this->$prop = $parent->$prop;
480 return $this;
485 * Override properties in this style with those in $style
487 * @param Style $style
489 function merge(Style $style) {
490 $this->_props = array_merge($this->_props, $style->_props);
492 if ( isset($style->_props["font_size"]) )
493 $this->__font_size_calculated = false;
498 * Returns an array(r, g, b, "r"=> r, "g"=>g, "b"=>b, "hex"=>"#rrggbb")
499 * based on the provided CSS colour value.
501 * @param string $colour
502 * @return array
504 function munge_colour($colour) {
505 if ( is_array($colour) )
506 // Assume the array has the right format...
507 // FIXME: should/could verify this.
508 return $colour;
510 $r = 0;
511 $g = 0;
512 $b = 0;
514 // Handle colour names
515 switch ($colour) {
517 case "maroon":
518 $r = 0x80;
519 break;
521 case "red":
522 $r = 0xff;
523 break;
525 case "orange":
526 $r = 0xff;
527 $g = 0xa5;
528 break;
530 case "yellow":
531 $r = 0xff;
532 $g = 0xff;
533 break;
535 case "olive":
536 $r = 0x80;
537 $g = 0x80;
538 break;
540 case "purple":
541 $r = 0x80;
542 $b = 0x80;
543 break;
545 case "fuchsia":
546 $r = 0xff;
547 $b = 0xff;
548 break;
550 case "white":
551 $r = $g = $b = 0xff;
552 break;
554 case "lime":
555 $g = 0xff;
556 break;
558 case "green":
559 $g = 0x80;
560 break;
562 case "navy":
563 $b = 0x80;
564 break;
566 case "blue":
567 $b = 0xff;
568 break;
570 case "aqua":
571 $g = 0xff;
572 $b = 0xff;
573 break;
575 case "teal":
576 $g = 0x80;
577 $b = 0x80;
578 break;
580 case "black":
581 break;
583 case "sliver":
584 $r = $g = $b = 0xc0;
585 break;
587 case "gray":
588 case "grey":
589 $r = $g = $b = 0x80;
590 break;
592 case "transparent":
593 return "transparent";
595 default:
597 if ( mb_strlen($colour) == 4 && $colour{0} == "#" ) {
598 // #rgb format
599 $r = hexdec($colour{1} . $colour{1});
600 $g = hexdec($colour{2} . $colour{2});
601 $b = hexdec($colour{3} . $colour{3});
603 } else if ( mb_strlen($colour) == 7 && $colour{0} == "#" ) {
604 // #rrggbb format
605 $r = hexdec(mb_substr($colour, 1, 2));
606 $g = hexdec(mb_substr($colour, 3, 2));
607 $b = hexdec(mb_substr($colour, 5, 2));
609 } else if ( mb_strpos($colour, "rgb") !== false ) {
611 // rgb( r,g,b ) format
612 $i = mb_strpos($colour, "(");
613 $j = mb_strpos($colour, ")");
615 // Bad colour value
616 if ($i === false || $j === false)
617 return null;
619 $triplet = explode(",", mb_substr($colour, $i+1, $j-$i-1));
621 if (count($triplet) != 3)
622 return null;
624 foreach (array_keys($triplet) as $c) {
625 $triplet[$c] = trim($triplet[$c]);
627 if ( $triplet[$c]{mb_strlen($triplet[$c]) - 1} == "%" )
628 $triplet[$c] = round($triplet[$c] * 0.255);
631 list($r, $g, $b) = $triplet;
633 } else {
634 // Who knows?
635 return null;
638 // Clip to 0 - 1
639 $r = $r < 0 ? 0 : ($r > 255 ? 255 : $r);
640 $g = $g < 0 ? 0 : ($g > 255 ? 255 : $g);
641 $b = $b < 0 ? 0 : ($b > 255 ? 255 : $b);
642 break;
646 // Form array
647 $arr = array(0 => $r / 0xff, 1 => $g / 0xff, 2 => $b / 0xff,
648 "r"=>$r / 0xff, "g"=>$g / 0xff, "b"=>$b / 0xff,
649 "hex" => sprintf("#%02X%02X%02X", $r, $g, $b));
650 return $arr;
656 * Alias for {@link Style::munge_colour()}
658 * @param string $color
659 * @return array
661 function munge_color($color) { return $this->munge_colour($color); }
665 * PHP5 overloaded setter
667 * This function along with {@link Style::__get()} permit a user of the
668 * Style class to access any (CSS) property using the following syntax:
669 * <code>
670 * Style->margin_top = "1em";
671 * echo (Style->margin_top);
672 * </code>
674 * __set() automatically calls the provided set function, if one exists,
675 * otherwise it sets the property directly. Typically, __set() is not
676 * called directly from outside of this class.
678 * @param string $prop the property to set
679 * @param mixed $val the value of the property
682 function __set($prop, $val) {
683 global $_dompdf_warnings;
685 $prop = str_replace("-", "_", $prop);
686 $this->_prop_cache[$prop] = null;
688 if ( !isset(self::$_defaults[$prop]) ) {
689 $_dompdf_warnings[] = "'$prop' is not a valid CSS2 property.";
690 return;
693 if ( $prop !== "content" && is_string($val) && mb_strpos($val, "url") === false ) {
694 $val = mb_strtolower(trim(str_replace(array("\n", "\t"), array(" "), $val)));
695 $val = preg_replace("/([0-9]+) (pt|px|pc|em|ex|in|cm|mm|%)/S", "\\1\\2", $val);
698 $method = "set_$prop";
700 if ( method_exists($this, $method) )
701 $this->$method($val);
702 else
703 $this->_props[$prop] = $val;
708 * PHP5 overloaded getter
710 * Along with {@link Style::__set()} __get() provides access to all CSS
711 * properties directly. Typically __get() is not called directly outside
712 * of this class.
714 * @param string $prop
715 * @return mixed
717 function __get($prop) {
719 if ( !isset(self::$_defaults[$prop]) )
720 throw new DOMPDF_Exception("'$prop' is not a valid CSS2 property.");
722 if ( isset($this->_prop_cache[$prop]) )
723 return $this->_prop_cache[$prop];
725 $method = "get_$prop";
727 // Fall back on defaults if property is not set
728 if ( !isset($this->_props[$prop]) )
729 $this->_props[$prop] = self::$_defaults[$prop];
731 if ( method_exists($this, $method) )
732 return $this->_prop_cache[$prop] = $this->$method();
735 return $this->_prop_cache[$prop] = $this->_props[$prop];
740 * Getter for the 'font-family' CSS property.
742 * Uses the {@link Font_Metrics} class to resolve the font family into an
743 * actual font file.
745 * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-family
746 * @return string
748 function get_font_family() {
749 // Select the appropriate font. First determine the subtype, then check
750 // the specified font-families for a candidate.
752 // Resolve font-weight
753 $weight = $this->__get("font_weight");
755 if ( is_numeric($weight) ) {
757 if ( $weight < 700 )
758 $weight = "normal";
759 else
760 $weight = "bold";
762 } else if ( $weight == "bold" || $weight == "bolder" ) {
763 $weight = "bold";
765 } else {
766 $weight = "normal";
770 // Resolve font-style
771 $font_style = $this->__get("font_style");
773 if ( $weight == "bold" && $font_style == "italic" )
774 $subtype = "bold_italic";
775 else if ( $weight == "bold" && $font_style != "italic" )
776 $subtype = "bold";
777 else if ( $weight != "bold" && $font_style == "italic" )
778 $subtype = "italic";
779 else
780 $subtype = "normal";
782 // Resolve the font family
783 $families = explode(",", $this->_props["font_family"]);
784 reset($families);
785 $font = null;
786 while ( current($families) ) {
787 list(,$family) = each($families);
788 $font = Font_Metrics::get_font($family, $subtype);
790 if ( $font )
791 return $font;
793 throw new DOMPDF_Exception("Unable to find a suitable font replacement for: '" . $this->_props["font_family"] ."'");
798 * Returns the resolved font size, in points
800 * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
801 * @return float
803 function get_font_size() {
805 if ( $this->__font_size_calculated )
806 return $this->_props["font_size"];
808 if ( !isset($this->_props["font_size"]) )
809 $fs = self::$_defaults["font_size"];
810 else
811 $fs = $this->_props["font_size"];
813 if ( !isset($this->_parent_font_size) )
814 $this->_parent_font_size = self::$default_font_size;
816 switch ($fs) {
818 case "xx-small":
819 $fs = 3/5 * $this->_parent_font_size;
820 break;
822 case "x-small":
823 $fs = 3/4 * $this->_parent_font_size;
824 break;
826 case "smaller":
827 case "small":
828 $fs = 8/9 * $this->_parent_font_size;
829 break;
831 case "medium":
832 $fs = $this->_parent_font_size;
833 break;
835 case "larger":
836 case "large":
837 $fs = 6/5 * $this->_parent_font_size;
838 break;
840 case "x-large":
841 $fs = 3/2 * $this->_parent_font_size;
842 break;
844 case "xx-large":
845 $fs = 2/1 * $this->_parent_font_size;
846 break;
848 default:
849 break;
852 // Ensure relative sizes resolve to something
853 if ( ($i = mb_strpos($fs, "em")) !== false )
854 $fs = mb_substr($fs, 0, $i) * $this->_parent_font_size;
856 else if ( ($i = mb_strpos($fs, "ex")) !== false )
857 $fs = mb_substr($fs, 0, $i) * $this->_parent_font_size;
859 else
860 $fs = $this->length_in_pt($fs);
862 $this->_props["font_size"] = $fs;
863 $this->__font_size_calculated = true;
864 return $this->_props["font_size"];
869 * @link http://www.w3.org/TR/CSS21/text.html#propdef-word-spacing
870 * @return float
872 function get_word_spacing() {
873 if ( $this->_props["word_spacing"] === "normal" )
874 return 0;
876 return $this->_props["word_spacing"];
880 * @link http://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
881 * @return float
883 function get_line_height() {
884 if ( $this->_props["line_height"] === "normal" )
885 return self::$default_line_height * $this->get_font_size();
887 if ( is_numeric($this->_props["line_height"]) )
888 return $this->length_in_pt( $this->_props["line_height"] . "%", $this->get_font_size());
890 return $this->length_in_pt( $this->_props["line_height"], $this->get_font_size() );
894 * Returns the colour as an array
896 * The array has the following format:
897 * <code>array(r,g,b, "r" => r, "g" => g, "b" => b, "hex" => "#rrggbb")</code>
899 * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
900 * @return array
902 function get_color() {
903 return $this->munge_color( $this->_props["color"] );
907 * Returns the background colour as an array
909 * The returned array has the same format as {@link Style::get_color()}
911 * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
912 * @return array
914 function get_background_color() {
915 return $this->munge_color( $this->_props["background_color"] );
919 * Returns the background position as an array
921 * The returned array has the following format:
922 * <code>array(x,y, "x" => x, "y" => y)</code>
924 * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
925 * @return array
927 function get_background_position() {
929 $tmp = explode(" ", $this->_props["background_position"]);
931 switch ($tmp[0]) {
933 case "left":
934 $x = "0%";
935 break;
937 case "right":
938 $x = "100%";
939 break;
941 case "top":
942 $y = "0%";
943 break;
945 case "bottom":
946 $y = "100%";
947 break;
949 case "center":
950 $x = "50%";
951 $y = "50%";
952 break;
954 default:
955 $x = $tmp[0];
956 break;
959 if ( isset($tmp[1]) ) {
961 switch ($tmp[1]) {
962 case "left":
963 $x = "0%";
964 break;
966 case "right":
967 $x = "100%";
968 break;
970 case "top":
971 $y = "0%";
972 break;
974 case "bottom":
975 $y = "100%";
976 break;
978 case "center":
979 if ( $tmp[0] == "left" || $tmp[0] == "right" || $tmp[0] == "center" )
980 $y = "50%";
981 else
982 $x = "50%";
983 break;
985 default:
986 $y = $tmp[1];
987 break;
990 } else {
991 $y = "50%";
994 if ( !isset($x) )
995 $x = "0%";
997 if ( !isset($y) )
998 $y = "0%";
1000 return array( 0 => $x, "x" => $x,
1001 1 => $y, "y" => $y );
1005 /**#@+
1006 * Returns the border colour as an array
1008 * See {@link Style::get_color()}
1010 * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties
1011 * @return array
1013 function get_border_top_color() {
1014 if ( $this->_props["border_top_color"] === "" )
1015 $this->_props["border_top_color"] = $this->__get("color");
1016 return $this->munge_color($this->_props["border_top_color"]);
1019 function get_border_right_color() {
1020 if ( $this->_props["border_right_color"] === "" )
1021 $this->_props["border_right_color"] = $this->__get("color");
1022 return $this->munge_color($this->_props["border_right_color"]);
1025 function get_border_bottom_color() {
1026 if ( $this->_props["border_bottom_color"] === "" )
1027 $this->_props["border_bottom_color"] = $this->__get("color");
1028 return $this->munge_color($this->_props["border_bottom_color"]);;
1031 function get_border_left_color() {
1032 if ( $this->_props["border_left_color"] === "" )
1033 $this->_props["border_left_color"] = $this->__get("color");
1034 return $this->munge_color($this->_props["border_left_color"]);
1037 /**#@-*/
1039 /**#@+
1040 * Returns the border width, as it is currently stored
1042 * @link http://www.w3.org/TR/CSS21/box.html#border-width-properties
1043 * @return float|string
1045 function get_border_top_width() {
1046 $style = $this->__get("border_top_style");
1047 return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_top_width"]) : 0;
1050 function get_border_right_width() {
1051 $style = $this->__get("border_right_style");
1052 return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_right_width"]) : 0;
1055 function get_border_bottom_width() {
1056 $style = $this->__get("border_bottom_style");
1057 return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_bottom_width"]) : 0;
1060 function get_border_left_width() {
1061 $style = $this->__get("border_left_style");
1062 return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_left_width"]) : 0;
1064 /**#@-*/
1067 * Return an array of all border properties.
1069 * The returned array has the following structure:
1070 * <code>
1071 * array("top" => array("width" => [border-width],
1072 * "style" => [border-style],
1073 * "color" => [border-color (array)]),
1074 * "bottom" ... )
1075 * </code>
1077 * @return array
1079 function get_border_properties() {
1080 return array("top" => array("width" => $this->__get("border_top_width"),
1081 "style" => $this->__get("border_top_style"),
1082 "color" => $this->__get("border_top_color")),
1083 "bottom" => array("width" => $this->__get("border_bottom_width"),
1084 "style" => $this->__get("border_bottom_style"),
1085 "color" => $this->__get("border_bottom_color")),
1086 "right" => array("width" => $this->__get("border_right_width"),
1087 "style" => $this->__get("border_right_style"),
1088 "color" => $this->__get("border_right_color")),
1089 "left" => array("width" => $this->__get("border_left_width"),
1090 "style" => $this->__get("border_left_style"),
1091 "color" => $this->__get("border_left_color")));
1095 * Return a single border property
1097 * @return mixed
1099 protected function _get_border($side) {
1100 $color = $this->__get("border_" . $side . "_color");
1102 return $this->__get("border_" . $side . "_width") . " " .
1103 $this->__get("border_" . $side . "_style") . " " . $color["hex"];
1106 /**#@+
1107 * Return full border properties as a string
1109 * Border properties are returned just as specified in CSS:
1110 * <pre>[width] [style] [color]</pre>
1111 * e.g. "1px solid blue"
1113 * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
1114 * @return string
1116 function get_border_top() { return $this->_get_border("top"); }
1117 function get_border_right() { return $this->_get_border("right"); }
1118 function get_border_bottom() { return $this->_get_border("bottom"); }
1119 function get_border_left() { return $this->_get_border("left"); }
1120 /**#@-*/
1124 * Returns border spacing as an array
1126 * The array has the format (h_space,v_space)
1128 * @link http://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing
1129 * @return array
1131 function get_border_spacing() {
1132 return explode(" ", $this->_props["border_spacing"]);
1137 * Sets colour
1139 * The colour parameter can be any valid CSS colour value
1141 * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
1142 * @param string $colour
1144 function set_color($colour) {
1145 $col = $this->munge_colour($colour);
1147 if ( is_null($col) )
1148 $col = self::$_defaults["color"];
1150 $this->_props["color"] = $col["hex"];
1154 * Sets the background colour
1156 * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
1157 * @param string $colour
1159 function set_background_color($colour) {
1160 $col = $this->munge_colour($colour);
1161 if ( is_null($col) )
1162 $col = self::$_defaults["background_color"];
1164 $this->_props["background_color"] = is_array($col) ? $col["hex"] : $col;
1168 * Set the background image url
1170 * @link http://www.w3.org/TR/CSS21/colors.html#background-properties
1171 * @param string $url
1173 function set_background_image($val) {
1175 if ( mb_strpos($val, "url") !== false ) {
1176 $val = preg_replace("/url\(['\"]?([^'\")]+)['\"]?\)/","\\1", trim($val));
1177 } else {
1178 $val = "none";
1181 // Resolve the url now in the context of the current stylesheet
1182 $parsed_url = explode_url($val);
1183 if ( $parsed_url["protocol"] == "" && $this->_stylesheet->get_protocol() == "" )
1184 $url = realpath($this->_stylesheet->get_base_path() . $parsed_url["file"]);
1185 else
1186 $url = build_url($this->_stylesheet->get_protocol(),
1187 $this->_stylesheet->get_host(),
1188 $this->_stylesheet->get_base_path(),
1189 $val);
1191 $this->_props["background_image"] = $url;
1195 * Sets the font size
1197 * $size can be any acceptable CSS size
1199 * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
1200 * @param string|float $size
1202 function set_font_size($size) {
1203 $this->__font_size_calculated = false;
1204 $this->_props["font_size"] = $size;
1207 /**#@+
1208 * Sets page break properties
1210 * @link http://www.w3.org/TR/CSS21/page.html#page-breaks
1211 * @param string $break
1213 function set_page_break_before($break) {
1214 if ($break === "left" || $break === "right")
1215 $break = "always";
1217 $this->_props["page_break_before"] = $break;
1220 function set_page_break_after($break) {
1221 if ($break === "left" || $break === "right")
1222 $break = "always";
1224 $this->_props["page_break_after"] = $break;
1226 /**#@-*/
1228 //........................................................................
1230 /**#@+
1231 * Sets the margin size
1233 * @link http://www.w3.org/TR/CSS21/box.html#margin-properties
1234 * @param $val
1236 function set_margin_top($val) {
1237 $this->_props["margin_top"] = str_replace("none", "0px", $val);
1240 function set_margin_right($val) {
1241 $this->_props["margin_right"] = str_replace("none", "0px", $val);
1244 function set_margin_bottom($val) {
1245 $this->_props["margin_bottom"] = str_replace("none", "0px", $val);
1248 function set_margin_left($val) {
1249 $this->_props["margin_left"] = str_replace("none", "0px", $val);
1252 function set_margin($val) {
1253 $val = str_replace("none", "0px", $val);
1254 $margins = explode(" ", $val);
1256 switch (count($margins)) {
1258 case 1:
1259 $this->_props["margin_top"] = $margins[0];
1260 $this->_props["margin_right"] = $margins[0];
1261 $this->_props["margin_bottom"] = $margins[0];
1262 $this->_props["margin_left"] = $margins[0];
1263 break;
1265 case 2:
1266 $this->_props["margin_top"] = $margins[0];
1267 $this->_props["margin_bottom"] = $margins[0];
1269 $this->_props["margin_right"] = $margins[1];
1270 $this->_props["margin_left"] = $margins[1];
1271 break;
1273 case 3:
1274 $this->_props["margin_top"] = $margins[0];
1275 $this->_props["margin_right"] = $margins[1];
1276 $this->_props["margin_bottom"] = $margins[1];
1277 $this->_props["margin_left"] = $margins[2];
1278 break;
1280 case 4:
1281 $this->_props["margin_top"] = $margins[0];
1282 $this->_props["margin_right"] = $margins[1];
1283 $this->_props["margin_bottom"] = $margins[2];
1284 $this->_props["margin_left"] = $margins[3];
1285 break;
1287 default:
1288 break;
1291 $this->_props["margin"] = $val;
1294 /**#@-*/
1297 /**#@+
1298 * Sets the padding size
1300 * @link http://www.w3.org/TR/CSS21/box.html#padding-properties
1301 * @param $val
1303 function set_padding_top($val) {
1304 $this->_props["padding_top"] = str_replace("none", "0px", $val);
1307 function set_padding_right($val) {
1308 $this->_props["padding_right"] = str_replace("none", "0px", $val);
1311 function set_padding_bottom($val) {
1312 $this->_props["padding_bottom"] = str_replace("none", "0px", $val);
1315 function set_padding_left($val) {
1316 $this->_props["padding_left"] = str_replace("none", "0px", $val);
1319 function set_padding($val) {
1320 $val = str_replace("none", "0px", $val);
1321 $paddings = explode(" ", $val);
1323 switch (count($paddings)) {
1325 case 1:
1326 $this->_props["padding_top"] = $paddings[0];
1327 $this->_props["padding_right"] = $paddings[0];
1328 $this->_props["padding_bottom"] = $paddings[0];
1329 $this->_props["padding_left"] = $paddings[0];
1330 break;
1332 case 2:
1333 $this->_props["padding_top"] = $paddings[0];
1334 $this->_props["padding_bottom"] = $paddings[0];
1336 $this->_props["padding_right"] = $paddings[1];
1337 $this->_props["padding_left"] = $paddings[1];
1338 break;
1340 case 3:
1341 $this->_props["padding_top"] = $paddings[0];
1342 $this->_props["padding_right"] = $paddings[1];
1343 $this->_props["padding_bottom"] = $paddings[1];
1344 $this->_props["padding_left"] = $paddings[2];
1345 break;
1347 case 4:
1348 $this->_props["padding_top"] = $paddings[0];
1349 $this->_props["padding_right"] = $paddings[1];
1350 $this->_props["padding_bottom"] = $paddings[2];
1351 $this->_props["padding_left"] = $paddings[3];
1352 break;
1354 default:
1355 break;
1358 $this->_props["padding"] = $val;
1360 /**#@-*/
1363 * Sets a single border
1365 * @param string $side
1366 * @param string $border_spec ([width] [style] [color])
1368 protected function _set_border($side, $border_spec) {
1369 $border_spec = str_replace(",", " ", $border_spec);
1370 $arr = explode(" ", $border_spec);
1372 // FIXME: handle partial values
1374 $p = "border_" . $side;
1375 $p_width = $p . "_width";
1376 $p_style = $p . "_style";
1377 $p_color = $p . "_color";
1379 foreach ($arr as $value) {
1380 $value = trim($value);
1381 if ( in_array($value, self::$BORDER_STYLES) ) {
1382 $this->_props[$p_style] = $value;
1384 } else if ( preg_match("/[.0-9]+(?:px|pt|pc|em|ex|%|in|mm|cm)|(?:none|normal|thin|medium|thick)/", $value ) ) {
1385 $this->_props[$p_width] = str_replace("none", "0px", $value);
1387 } else {
1388 // must be colour
1389 $this->_props[$p_color] = $value;
1393 $this->_props[$p] = $border_spec;
1396 /**#@+
1397 * Sets the border styles
1399 * @link http://www.w3.org/TR/CSS21/box.html#border-properties
1400 * @param string $val
1402 function set_border_top($val) { $this->_set_border("top", $val); }
1403 function set_border_right($val) { $this->_set_border("right", $val); }
1404 function set_border_bottom($val) { $this->_set_border("bottom", $val); }
1405 function set_border_left($val) { $this->_set_border("left", $val); }
1407 function set_border($val) {
1408 $this->_set_border("top", $val);
1409 $this->_set_border("right", $val);
1410 $this->_set_border("bottom", $val);
1411 $this->_set_border("left", $val);
1412 $this->_props["border"] = $val;
1415 function set_border_width($val) {
1416 $arr = explode(" ", $val);
1418 switch (count($arr)) {
1420 case 1:
1421 $this->_props["border_top_width"] = $arr[0];
1422 $this->_props["border_right_width"] = $arr[0];
1423 $this->_props["border_bottom_width"] = $arr[0];
1424 $this->_props["border_left_width"] = $arr[0];
1425 break;
1427 case 2:
1428 $this->_props["border_top_width"] = $arr[0];
1429 $this->_props["border_bottom_width"] = $arr[0];
1431 $this->_props["border_right_width"] = $arr[1];
1432 $this->_props["border_left_width"] = $arr[1];
1433 break;
1435 case 3:
1436 $this->_props["border_top_width"] = $arr[0];
1437 $this->_props["border_right_width"] = $arr[1];
1438 $this->_props["border_bottom_width"] = $arr[1];
1439 $this->_props["border_left_width"] = $arr[2];
1440 break;
1442 case 4:
1443 $this->_props["border_top_width"] = $arr[0];
1444 $this->_props["border_right_width"] = $arr[1];
1445 $this->_props["border_bottom_width"] = $arr[2];
1446 $this->_props["border_left_width"] = $arr[3];
1447 break;
1449 default:
1450 break;
1453 $this->_props["border_width"] = $val;
1456 function set_border_color($val) {
1458 $arr = explode(" ", $val);
1460 switch (count($arr)) {
1462 case 1:
1463 $this->_props["border_top_color"] = $arr[0];
1464 $this->_props["border_right_color"] = $arr[0];
1465 $this->_props["border_bottom_color"] = $arr[0];
1466 $this->_props["border_left_color"] = $arr[0];
1467 break;
1469 case 2:
1470 $this->_props["border_top_color"] = $arr[0];
1471 $this->_props["border_bottom_color"] = $arr[0];
1473 $this->_props["border_right_color"] = $arr[1];
1474 $this->_props["border_left_color"] = $arr[1];
1475 break;
1477 case 3:
1478 $this->_props["border_top_color"] = $arr[0];
1479 $this->_props["border_right_color"] = $arr[1];
1480 $this->_props["border_bottom_color"] = $arr[1];
1481 $this->_props["border_left_color"] = $arr[2];
1482 break;
1484 case 4:
1485 $this->_props["border_top_color"] = $arr[0];
1486 $this->_props["border_right_color"] = $arr[1];
1487 $this->_props["border_bottom_color"] = $arr[2];
1488 $this->_props["border_left_color"] = $arr[3];
1489 break;
1491 default:
1492 break;
1495 $this->_props["border_color"] = $val;
1499 function set_border_style($val) {
1500 $arr = explode(" ", $val);
1502 switch (count($arr)) {
1504 case 1:
1505 $this->_props["border_top_style"] = $arr[0];
1506 $this->_props["border_right_style"] = $arr[0];
1507 $this->_props["border_bottom_style"] = $arr[0];
1508 $this->_props["border_left_style"] = $arr[0];
1509 break;
1511 case 2:
1512 $this->_props["border_top_style"] = $arr[0];
1513 $this->_props["border_bottom_style"] = $arr[0];
1515 $this->_props["border_right_style"] = $arr[1];
1516 $this->_props["border_left_style"] = $arr[1];
1517 break;
1519 case 3:
1520 $this->_props["border_top_style"] = $arr[0];
1521 $this->_props["border_right_style"] = $arr[1];
1522 $this->_props["border_bottom_style"] = $arr[1];
1523 $this->_props["border_left_style"] = $arr[2];
1524 break;
1526 case 4:
1527 $this->_props["border_top_style"] = $arr[0];
1528 $this->_props["border_right_style"] = $arr[1];
1529 $this->_props["border_bottom_style"] = $arr[2];
1530 $this->_props["border_left_style"] = $arr[3];
1531 break;
1533 default:
1534 break;
1537 $this->_props["border_style"] = $val;
1539 /**#@-*/
1543 * Sets the border spacing
1545 * @link http://www.w3.org/TR/CSS21/box.html#border-properties
1546 * @param float $val
1548 function set_border_spacing($val) {
1550 $arr = explode(" ", $val);
1552 if ( count($arr) == 1 )
1553 $arr[1] = $arr[0];
1555 $this->_props["border_spacing"] = $arr[0] . " " . $arr[1];
1559 * Sets the list style image
1561 * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
1562 * @param $val
1564 function set_list_style_image($val) {
1566 // Strip url(' ... ') from url values
1567 if ( mb_strpos($val, "url") !== false ) {
1568 $val = preg_replace("/url\(['\"]?([^'\")]+)['\"]?\)/","\\1", trim($val));
1569 } else {
1570 $val = "none";
1573 $this->_props["list_style_image"] = $val;
1577 * Sets the list style
1579 * This is not currently implemented
1581 * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style
1582 * @param $val
1584 function set_list_style($val) {
1585 $arr = explode(" ", str_replace(",", " ", $val));
1587 $types = array("disc", "circle", "square", "decimal",
1588 "decimal-leading-zero", "lower-roman",
1589 "upper-roman", "lower-greek", "lower-latin",
1590 "upper-latin", "armenian", "georgian",
1591 "lower-alpha", "upper-alpha", "none");
1593 $positions = array("inside", "outside");
1595 foreach ($arr as $value) {
1596 if ( mb_strpos($value, "url") !== false ) {
1597 $this->set_list_style_image($value);
1598 continue;
1601 if ( in_array($value, $types) ) {
1602 $this->_props["list_style_type"] = $value;
1603 } else if ( in_array($value, $positions) ) {
1604 $this->_props["list_style_position"] = $value;
1610 * Generate a string representation of the Style
1612 * This dumps the entire property array into a string via print_r. Useful
1613 * for debugging.
1615 * @return string
1617 function __toString() {
1618 return print_r(array_merge(array("parent_font_size" => $this->_parent_font_size),
1619 $this->_props), true);