source: branches/2.2/filemanager/tp/dompdf/include/style.cls.php @ 3019

Revision 3019, 61.8 KB checked in by amuller, 14 years ago (diff)

Ticket #1135 - Corrigindo CSS e adicionando filemanager

Line 
1<?php
2/**
3 * DOMPDF - PHP5 HTML to PDF renderer
4 *
5 * File: $RCSfile: style.cls.php,v $
6 * Created on: 2004-06-01
7 *
8 * Copyright (c) 2004 - Benj Carson <benjcarson@digitaljunkies.ca>
9 *
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.
14 *
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.
19 *
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
24 *
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.
29 *
30 * The latest version of DOMPDF might be available at:
31 * http://www.digitaljunkies.ca/dompdf
32 *
33 * @link http://www.digitaljunkies.ca/dompdf
34 * @copyright 2004 Benj Carson
35 * @author Benj Carson <benjcarson@digitaljunkies.ca>
36 * @contributor Helmut Tischer <htischer@weihenstephan.org>
37 * @package dompdf
38 * @version 0.5.1
39 *
40 * Changes
41 * @contributor Helmut Tischer <htischer@weihenstephan.org>
42 * @version 0.5.1.htischer.20090507
43 * - Fix px to pt conversion according to DOMPDF_DPI
44 * - Recognize css styles with !important attribute, and store !important attribute within style
45 * - Propagate !important by inherit and sequences of styles with merge.
46 * - Add missing style property cache flushes for consistent rendering, e.g. on explicte assignments
47 * - Add important set/get for access from outside of class
48 * - Fix font_family search path with multiple fonts list in css attribute:
49 *   On missing font, do not immediately fall back to default font,
50 *   but try subsequent fonts in search chain. Only when none found, explicitely
51 *   refer to default font.
52 * - Allow read of background individual properties
53 * - Add support for individual styles background-position, background-attachment, background-repeat
54 * - Complete style components of list-style
55 * - Add support for combined styles in addition to individual styles
56 *   like {border: red 1px solid;}, { border-width: 1px;}, { border-right-color: red; } ...
57 *   for font, background
58 * - Propagate attributes including !important from combined style to individual component
59 *   for border, background, padding, margin, font, list_style
60 * - Refactor common code of border, background, padding, margin, font, list_style
61 * - Refactor common code of list-style-image and background-image
62 * - special treatment of css images "none" instead of url(...), otherwise would prepend string "none" with path name
63 * - Added comments
64 * - Added debug output
65 * @contributor Helmut Tischer <htischer@weihenstephan.org>
66 * @version dompdf_trunk_with_helmut_mods.20090524
67 * - Allow superflous white space and string delimiter in font search path.
68 * - Restore lost change of default font of above
69 * @version 20090610
70 * - Allow absolute path from web server root as html image reference
71 * - More accurate handling of css property cache consistency
72 */
73
74/* $Id: style.cls.php 186 2009-10-19 22:42:06Z eclecticgeek@gmail.com $ */
75
76/**
77 * Represents CSS properties.
78 *
79 * The Style class is responsible for handling and storing CSS properties.
80 * It includes methods to resolve colours and lengths, as well as getters &
81 * setters for many CSS properites.
82 *
83 * Actual CSS parsing is performed in the {@link Stylesheet} class.
84 *
85 * @package dompdf
86 */
87class Style {
88
89  /**
90   * Default font size, in points.
91   *
92   * @var float
93   */
94  static $default_font_size = 12;
95
96  /**
97   * Default line height, as a fraction of the font size.
98   *
99   * @var float
100   */
101  static $default_line_height = 1.2;
102
103  /**
104   * List of all inline types.  Should really be a constant.
105   *
106   * @var array
107   */
108  static $INLINE_TYPES = array("inline");
109
110  /**
111   * List of all block types.  Should really be a constant.
112   *
113   * @var array
114   */
115  static $BLOCK_TYPES = array("block","inline-block", "table-cell", "list-item");
116
117  /**
118   * List of all table types.  Should really be a constant.
119   *
120   * @var array;
121   */
122  static $TABLE_TYPES = array("table", "inline-table");
123
124  /**
125   * List of valid border styles.  Should also really be a constant.
126   *
127   * @var array
128   */
129  static $BORDER_STYLES = array("none", "hidden", "dotted", "dashed", "solid",
130                                "double", "groove", "ridge", "inset", "outset");
131
132  /**
133   * Default style values.
134   *
135   * @link http://www.w3.org/TR/CSS21/propidx.html
136   *
137   * @var array
138   */
139  static protected $_defaults = null;
140
141  /**
142   * List of inherited properties
143   *
144   * @link http://www.w3.org/TR/CSS21/propidx.html
145   *
146   * @var array
147   */
148  static protected $_inherited = null;
149
150  /**
151   * The stylesheet this style belongs to
152   *
153   * @see Stylesheet
154   * @var Stylesheet
155   */
156  protected $_stylesheet; // stylesheet this style is attached to
157
158  /**
159   * Main array of all CSS properties & values
160   *
161   * @var array
162   */
163  protected $_props;
164
165  /* var instead of protected would allow access outside of class */
166  protected $_important_props;
167
168  /**
169   * Cached property values
170   *
171   * @var array
172   */
173  protected $_prop_cache;
174 
175  /**
176   * Font size of parent element in document tree.  Used for relative font
177   * size resolution.
178   *
179   * @var float
180   */
181  protected $_parent_font_size; // Font size of parent element
182 
183  // private members
184  /**
185   * True once the font size is resolved absolutely
186   *
187   * @var bool
188   */
189  private $__font_size_calculated; // Cache flag
190 
191  /**
192   * Class constructor
193   *
194   * @param Stylesheet $stylesheet the stylesheet this Style is associated with.
195   */
196  function __construct(Stylesheet $stylesheet) {
197    $this->_props = array();
198    $this->_important_props = array();
199    $this->_stylesheet = $stylesheet;
200    $this->_parent_font_size = null;
201    $this->__font_size_calculated = false;
202   
203    if ( !isset(self::$_defaults) ) {
204   
205      // Shorthand
206      $d =& self::$_defaults;
207   
208      // All CSS 2.1 properties, and their default values
209      $d["azimuth"] = "center";
210      $d["background_attachment"] = "scroll";
211      $d["background_color"] = "transparent";
212      $d["background_image"] = "none";
213      $d["background_position"] = "0% 0%";
214      $d["background_repeat"] = "repeat";
215      $d["background"] = "";
216      $d["border_collapse"] = "separate";
217      $d["border_color"] = "";
218      $d["border_spacing"] = "0";
219      $d["border_style"] = "";
220      $d["border_top"] = "";
221      $d["border_right"] = "";
222      $d["border_bottom"] = "";
223      $d["border_left"] = "";
224      $d["border_top_color"] = "";
225      $d["border_right_color"] = "";
226      $d["border_bottom_color"] = "";
227      $d["border_left_color"] = "";
228      $d["border_top_style"] = "none";
229      $d["border_right_style"] = "none";
230      $d["border_bottom_style"] = "none";
231      $d["border_left_style"] = "none";
232      $d["border_top_width"] = "medium";
233      $d["border_right_width"] = "medium";
234      $d["border_bottom_width"] = "medium";
235      $d["border_left_width"] = "medium";
236      $d["border_width"] = "medium";
237      $d["border"] = "";
238      $d["bottom"] = "auto";
239      $d["caption_side"] = "top";
240      $d["clear"] = "none";
241      $d["clip"] = "auto";
242      $d["color"] = "#000000";
243      $d["content"] = "normal";
244      $d["counter_increment"] = "none";
245      $d["counter_reset"] = "none";
246      $d["cue_after"] = "none";
247      $d["cue_before"] = "none";
248      $d["cue"] = "";
249      $d["cursor"] = "auto";
250      $d["direction"] = "ltr";
251      $d["display"] = "inline";
252      $d["elevation"] = "level";
253      $d["empty_cells"] = "show";
254      $d["float"] = "none";
255      $d["font_family"] = "serif";
256      $d["font_size"] = "medium";
257      $d["font_style"] = "normal";
258      $d["font_variant"] = "normal";
259      $d["font_weight"] = "normal";
260      $d["font"] = "";
261      $d["height"] = "auto";
262      $d["left"] = "auto";
263      $d["letter_spacing"] = "normal";
264      $d["line_height"] = "normal";
265      $d["list_style_image"] = "none";
266      $d["list_style_position"] = "outside";
267      $d["list_style_type"] = "disc";
268      $d["list_style"] = "";
269      $d["margin_right"] = "0";
270      $d["margin_left"] = "0";
271      $d["margin_top"] = "0";
272      $d["margin_bottom"] = "0";
273      $d["margin"] = "";
274      $d["max_height"] = "none";
275      $d["max_width"] = "none";
276      $d["min_height"] = "0";
277      $d["min_width"] = "0";
278      $d["orphans"] = "2";
279      $d["outline_color"] = "invert";
280      $d["outline_style"] = "none";
281      $d["outline_width"] = "medium";
282      $d["outline"] = "";
283      $d["overflow"] = "visible";
284      $d["padding_top"] = "0";
285      $d["padding_right"] = "0";
286      $d["padding_bottom"] = "0";
287      $d["padding_left"] = "0";
288      $d["padding"] = "";
289      $d["page_break_after"] = "auto";
290      $d["page_break_before"] = "auto";
291      $d["page_break_inside"] = "auto";
292      $d["pause_after"] = "0";
293      $d["pause_before"] = "0";
294      $d["pause"] = "";
295      $d["pitch_range"] = "50";
296      $d["pitch"] = "medium";
297      $d["play_during"] = "auto";
298      $d["position"] = "static";
299      $d["quotes"] = "";
300      $d["richness"] = "50";
301      $d["right"] = "auto";
302      $d["speak_header"] = "once";
303      $d["speak_numeral"] = "continuous";
304      $d["speak_punctuation"] = "none";
305      $d["speak"] = "normal";
306      $d["speech_rate"] = "medium";
307      $d["stress"] = "50";
308      $d["table_layout"] = "auto";
309      $d["text_align"] = "left";
310      $d["text_decoration"] = "none";
311      $d["text_indent"] = "0";
312      $d["text_transform"] = "none";
313      $d["top"] = "auto";
314      $d["unicode_bidi"] = "normal";
315      $d["vertical_align"] = "baseline";
316      $d["visibility"] = "visible";
317      $d["voice_family"] = "";
318      $d["volume"] = "medium";
319      $d["white_space"] = "normal";
320      $d["widows"] = "2";
321      $d["width"] = "auto";
322      $d["word_spacing"] = "normal";
323      $d["z_index"] = "auto";
324
325      // Properties that inherit by default
326      self::$_inherited = array("azimuth",
327                                 "border_collapse",
328                                 "border_spacing",
329                                 "caption_side",
330                                 "color",
331                                 "cursor",
332                                 "direction",
333                                 "elevation",
334                                 "empty_cells",
335                                 "font_family",
336                                 "font_size",
337                                 "font_style",
338                                 "font_variant",
339                                 "font_weight",
340                                 "font",
341                                 "letter_spacing",
342                                 "line_height",
343                                 "list_style_image",
344                                 "list_style_position",
345                                 "list_style_type",
346                                 "list_style",
347                                 "orphans",
348                                 "page_break_inside",
349                                 "pitch_range",
350                                 "pitch",
351                                 "quotes",
352                                 "richness",
353                                 "speak_header",
354                                 "speak_numeral",
355                                 "speak_punctuation",
356                                 "speak",
357                                 "speech_rate",
358                                 "stress",
359                                 "text_align",
360                                 "text_indent",
361                                 "text_transform",
362                                 "visibility",
363                                 "voice_family",
364                                 "volume",
365                                 "white_space",
366                                 "widows",
367                                 "word_spacing");
368    }
369
370  }
371
372  /**
373   * "Destructor": forcibly free all references held by this object
374   */
375  function dispose() {
376    unset($this->_stylesheet);
377  }
378 
379  /**
380   * returns the {@link Stylesheet} this Style is associated with.
381   *
382   * @return Stylesheet
383   */
384  function get_stylesheet() { return $this->_stylesheet; }
385 
386  /**
387   * Converts any CSS length value into an absolute length in points.
388   *
389   * length_in_pt() takes a single length (e.g. '1em') or an array of
390   * lengths and returns an absolute length.  If an array is passed, then
391   * the return value is the sum of all elements.
392   *
393   * If a reference size is not provided, the default font size is used
394   * ({@link Style::$default_font_size}).
395   *
396   * @param float|array $length   the length or array of lengths to resolve
397   * @param float       $ref_size  an absolute reference size to resolve percentage lengths
398   * @return float
399   */
400  function length_in_pt($length, $ref_size = null) {
401
402    if ( !is_array($length) )
403      $length = array($length);
404
405    if ( !isset($ref_size) )
406      $ref_size = self::$default_font_size;
407
408    $ret = 0;
409    foreach ($length as $l) {
410
411      if ( $l === "auto" )
412        return "auto";
413     
414      if ( $l === "none" )
415        return "none";
416
417      // Assume numeric values are already in points
418      if ( is_numeric($l) ) {
419        $ret += $l;
420        continue;
421      }
422       
423      if ( $l === "normal" ) {
424        $ret += $ref_size;
425        continue;
426      }
427
428      // Border lengths
429      if ( $l === "thin" ) {
430        $ret += 0.5;
431        continue;
432      }
433     
434      if ( $l === "medium" ) {
435        $ret += 1.5;
436        continue;
437      }
438   
439      if ( $l === "thick" ) {
440        $ret += 2.5;
441        continue;
442      }
443     
444      if ( ($i = mb_strpos($l, "pt"))  !== false ) {
445        $ret += mb_substr($l, 0, $i);
446        continue;
447      }
448
449      if ( ($i = mb_strpos($l, "px"))  !== false ) {
450        $ret += ( mb_substr($l, 0, $i)  * 72 ) / DOMPDF_DPI;
451        continue;
452      }
453
454      if ( ($i = mb_strpos($l, "em"))  !== false ) {
455        $ret += mb_substr($l, 0, $i) * $this->__get("font_size");
456        continue;
457      }
458     
459      // FIXME: em:ex ratio?
460      if ( ($i = mb_strpos($l, "ex"))  !== false ) {
461        $ret += mb_substr($l, 0, $i) * $this->__get("font_size");
462        continue;
463      }
464     
465      if ( ($i = mb_strpos($l, "%"))  !== false ) {
466        $ret += mb_substr($l, 0, $i)/100 * $ref_size;
467        continue;
468      }
469     
470      if ( ($i = mb_strpos($l, "in")) !== false ) {
471        $ret += mb_substr($l, 0, $i) * 72;
472        continue;
473      }
474         
475      if ( ($i = mb_strpos($l, "cm")) !== false ) {
476        $ret += mb_substr($l, 0, $i) * 72 / 2.54;
477        continue;
478      }
479
480      if ( ($i = mb_strpos($l, "mm")) !== false ) {
481        $ret += mb_substr($l, 0, $i) * 72 / 25.4;
482        continue;
483      }
484         
485      if ( ($i = mb_strpos($l, "pc")) !== false ) {
486        $ret += mb_substr($l, 0, $i) / 12;
487        continue;
488      }
489         
490      // Bogus value
491      $ret += $ref_size;
492    }
493
494    return $ret;
495  }
496
497 
498  /**
499   * Set inherited properties in this style using values in $parent
500   *
501   * @param Style $parent
502   */
503  function inherit(Style $parent) {
504
505    // Set parent font size
506    $this->_parent_font_size = $parent->get_font_size();
507   
508    foreach (self::$_inherited as $prop) {
509      //inherit the !important property also.
510      //if local property is also !important, don't inherit.
511      if ( isset($parent->_props[$prop]) &&
512           ( !isset($this->_props[$prop]) ||
513             ( isset($parent->_important_props[$prop]) && !isset($this->_important_props[$prop]) )
514           )
515         ) {
516        if ( isset($parent->_important_props[$prop]) ) {
517          $this->_important_props[$prop] = true;
518        }
519        //see __set and __get, on all assignments clear cache!
520                $this->_prop_cache[$prop] = null;
521                $this->_props[$prop] = $parent->_props[$prop];
522      }
523    }
524     
525    foreach (array_keys($this->_props) as $prop) {
526      if ( $this->_props[$prop] == "inherit" ) {
527        if ( isset($parent->_important_props[$prop]) ) {
528          $this->_important_props[$prop] = true;
529        }
530        //do not assign direct, but
531        //implicite assignment through __set, redirect to specialized, get value with __get
532        //This is for computing defaults if the parent setting is also missing.
533        //Therefore do not directly assign the value without __set
534        //set _important_props before that to be able to propagate.
535        //see __set and __get, on all assignments clear cache!
536                //$this->_prop_cache[$prop] = null;
537                //$this->_props[$prop] = $parent->_props[$prop];
538        //props_set for more obvious explicite assignment not implemented, because
539        //too many implicite uses.
540        // $this->props_set($prop, $parent->$prop);
541        $this->$prop = $parent->$prop;
542      }
543    }
544         
545    return $this;
546  }
547
548 
549  /**
550   * Override properties in this style with those in $style
551   *
552   * @param Style $style
553   */
554  function merge(Style $style) {
555    //treat the !important attribute
556    //if old rule has !important attribute, override with new rule only if
557    //the new rule is also !important
558    foreach($style->_props as $prop => $val ) {
559      if (isset($style->_important_props[$prop])) {
560            $this->_important_props[$prop] = true;
561        //see __set and __get, on all assignments clear cache!
562                $this->_prop_cache[$prop] = null;
563            $this->_props[$prop] = $val;
564          } else if ( !isset($this->_important_props[$prop]) ) {
565        //see __set and __get, on all assignments clear cache!
566                $this->_prop_cache[$prop] = null;
567            $this->_props[$prop] = $val;
568          }
569        }
570
571    if ( isset($style->_props["font_size"]) )
572      $this->__font_size_calculated = false;   
573  }
574
575 
576  /**
577   * Returns an array(r, g, b, "r"=> r, "g"=>g, "b"=>b, "hex"=>"#rrggbb")
578   * based on the provided CSS colour value.
579   *
580   * @param string $colour
581   * @return array
582   */
583  function munge_colour($colour) {
584    if ( is_array($colour) )
585      // Assume the array has the right format...
586      // FIXME: should/could verify this.
587      return $colour;
588   
589    $r = 0;
590    $g = 0;
591    $b = 0;
592
593    // Handle colour names
594    switch ($colour) {
595
596    case "maroon":
597      $r = 0x80;
598      break;
599
600    case "red":
601      $r = 0xff;
602      break;
603
604    case "orange":
605      $r = 0xff;
606      $g = 0xa5;
607      break;
608
609    case "yellow":
610      $r = 0xff;
611      $g = 0xff;
612      break;
613
614    case "olive":
615      $r = 0x80;
616      $g = 0x80;
617      break;
618
619    case "purple":
620      $r = 0x80;
621      $b = 0x80;
622      break;
623
624    case "fuchsia":
625      $r = 0xff;
626      $b = 0xff;
627      break;
628
629    case "white":
630      $r = $g = $b = 0xff;
631      break;
632
633    case "lime":
634      $g = 0xff;
635      break;
636
637    case "green":
638      $g = 0x80;
639      break;
640
641    case "navy":
642      $b = 0x80;
643      break;
644
645    case "blue":
646      $b = 0xff;
647      break;
648
649    case "aqua":
650      $g = 0xff;
651      $b = 0xff;
652      break;
653
654    case "teal":
655      $g = 0x80;
656      $b = 0x80;
657      break;
658
659    case "black":
660      break;
661
662    case "sliver":
663      $r = $g = $b = 0xc0;
664      break;
665
666    case "gray":
667    case "grey":
668      $r = $g = $b = 0x80;
669      break;
670
671    case "transparent":
672      return "transparent";
673     
674    default:
675      if ( mb_strlen($colour) == 4 && $colour{0} == "#" ) {
676        // #rgb format
677        $r = hexdec($colour{1} . $colour{1});
678        $g = hexdec($colour{2} . $colour{2});
679        $b = hexdec($colour{3} . $colour{3});
680
681      } else if ( mb_strlen($colour) == 7 && $colour{0} == "#" ) {
682        // #rrggbb format
683        $r = hexdec(mb_substr($colour, 1, 2));
684        $g = hexdec(mb_substr($colour, 3, 2));
685        $b = hexdec(mb_substr($colour, 5, 2));
686
687      } else if ( mb_strpos($colour, "rgb") !== false ) {
688        // rgb( r,g,b ) format
689        $i = mb_strpos($colour, "(");
690        $j = mb_strpos($colour, ")");
691       
692        // Bad colour value
693        if ($i === false || $j === false)
694          return null;
695
696        $triplet = explode(",", mb_substr($colour, $i+1, $j-$i-1));
697
698        if (count($triplet) != 3)
699          return null;
700       
701        foreach (array_keys($triplet) as $c) {
702          $triplet[$c] = trim($triplet[$c]);
703         
704          if ( $triplet[$c]{mb_strlen($triplet[$c]) - 1} == "%" )
705            $triplet[$c] = round($triplet[$c] * 0.255);
706        }
707
708        list($r, $g, $b) = $triplet;
709
710      } else {
711        // Who knows?
712        return null;
713      }
714     
715      // Clip to 0 - 1
716      $r = $r < 0 ? 0 : ($r > 255 ? 255 : $r);
717      $g = $g < 0 ? 0 : ($g > 255 ? 255 : $g);
718      $b = $b < 0 ? 0 : ($b > 255 ? 255 : $b);
719      break;
720     
721    }
722   
723    // Form array
724    $arr = array(0 => $r / 0xff, 1 => $g / 0xff, 2 => $b / 0xff,
725                 "r"=>$r / 0xff, "g"=>$g / 0xff, "b"=>$b / 0xff,
726                 "hex" => sprintf("#%02X%02X%02X", $r, $g, $b));
727    return $arr;
728     
729  }
730
731 
732  /**
733   * Alias for {@link Style::munge_colour()}
734   *
735   * @param string $color
736   * @return array
737   */
738  function munge_color($color) { return $this->munge_colour($color); }
739
740  /* direct access to _important_props array from outside would work only when declared as
741   * 'var $_important_props;' instead of 'protected $_important_props;'
742   * Don't call _set/__get on missing attribute. Therefore need a special access.
743   * Assume that __set will be also called when this is called, so do not check validity again.
744   * Only created, if !important exists -> always set true.
745   */
746  function important_set($prop) {
747      $prop = str_replace("-", "_", $prop);
748      $this->_important_props[$prop] = true;
749  }
750
751  function important_get($prop) {
752      isset($this->_important_props[$prop]);
753  }
754
755  /**
756   * PHP5 overloaded setter
757   *
758   * This function along with {@link Style::__get()} permit a user of the
759   * Style class to access any (CSS) property using the following syntax:
760   * <code>
761   *  Style->margin_top = "1em";
762   *  echo (Style->margin_top);
763   * </code>
764   *
765   * __set() automatically calls the provided set function, if one exists,
766   * otherwise it sets the property directly.  Typically, __set() is not
767   * called directly from outside of this class.
768   *
769   * On each modification clear cache to return accurate setting.
770   * Also affects direct settings not using __set
771   * For easier finding all assignments, attempted to allowing only explicite assignment:
772   * Very many uses, e.g. frame_reflower.cls.php -> for now leave as it is
773   * function __set($prop, $val) {
774   *   throw new DOMPDF_Exception("Implicite replacement of assignment by __set.  Not good.");
775   * }
776   * function props_set($prop, $val) { ... }
777   *
778   * @param string $prop  the property to set
779   * @param mixed  $val   the value of the property
780   *
781   */
782  function __set($prop, $val) {
783    global $_dompdf_warnings;
784
785    $prop = str_replace("-", "_", $prop);
786    $this->_prop_cache[$prop] = null;
787   
788    if ( !isset(self::$_defaults[$prop]) ) {
789      $_dompdf_warnings[] = "'$prop' is not a valid CSS2 property.";
790      return;
791    }
792   
793    if ( $prop !== "content" && is_string($val) && mb_strpos($val, "url") === false ) {
794      $val = mb_strtolower(trim(str_replace(array("\n", "\t"), array(" "), $val)));
795      $val = preg_replace("/([0-9]+) (pt|px|pc|em|ex|in|cm|mm|%)/S", "\\1\\2", $val);
796    }
797   
798    $method = "set_$prop";
799
800    if ( method_exists($this, $method) )
801      $this->$method($val);
802    else
803      $this->_props[$prop] = $val;
804   
805  }
806
807  /**
808   * PHP5 overloaded getter
809   *
810   * Along with {@link Style::__set()} __get() provides access to all CSS
811   * properties directly.  Typically __get() is not called directly outside
812   * of this class.
813   *
814   * On each modification clear cache to return accurate setting.
815   * Also affects direct settings not using __set
816   *
817   * @param string $prop
818   * @return mixed
819   */
820  function __get($prop) {
821   
822    if ( !isset(self::$_defaults[$prop]) )
823      throw new DOMPDF_Exception("'$prop' is not a valid CSS2 property.");
824
825    if ( isset($this->_prop_cache[$prop]) && $this->_prop_cache[$prop] != null)
826      return $this->_prop_cache[$prop];
827   
828    $method = "get_$prop";
829
830    // Fall back on defaults if property is not set
831    if ( !isset($this->_props[$prop]) )
832      $this->_props[$prop] = self::$_defaults[$prop];
833
834    if ( method_exists($this, $method) )
835      return $this->_prop_cache[$prop] = $this->$method();
836
837
838    return $this->_prop_cache[$prop] = $this->_props[$prop];
839  }
840
841
842  /**
843   * Getter for the 'font-family' CSS property.
844   *
845   * Uses the {@link Font_Metrics} class to resolve the font family into an
846   * actual font file.
847   *
848   * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-family
849   * @return string
850   */
851  function get_font_family() {
852 
853    $DEBUGCSS=DEBUGCSS; //=DEBUGCSS; Allow override of global setting for ad hoc debug
854       
855    // Select the appropriate font.  First determine the subtype, then check
856    // the specified font-families for a candidate.
857
858    // Resolve font-weight
859    $weight = $this->__get("font_weight");
860   
861    if ( is_numeric($weight) ) {
862
863      if ( $weight < 600 )
864        $weight = "normal";
865      else
866        $weight = "bold";
867
868    } else if ( $weight == "bold" || $weight == "bolder" ) {
869      $weight = "bold";
870
871    } else {
872      $weight = "normal";
873
874    }
875
876    // Resolve font-style
877    $font_style = $this->__get("font_style");
878
879    if ( $weight == "bold" && ($font_style == "italic" || $font_style == "oblique") )
880      $subtype = "bold_italic";
881    else if ( $weight == "bold" && $font_style != "italic" && $font_style != "oblique" )
882      $subtype = "bold";
883    else if ( $weight != "bold" && ($font_style == "italic" || $font_style == "oblique") )
884      $subtype = "italic";
885    else
886      $subtype = "normal";
887   
888    // Resolve the font family
889    if ($DEBUGCSS) {
890      print "<pre>[get_font_family:";
891      print '('.$this->_props["font_family"].'.'.$font_style.'.'.$this->__get("font_weight").'.'.$weight.'.'.$subtype.')';
892    }
893    $families = explode(",", $this->_props["font_family"]);
894    $families = array_map('trim',$families);
895    reset($families);
896
897    $font = null;
898    while ( current($families) ) {
899      list(,$family) = each($families);
900      //remove leading and trailing string delimiters, e.g. on font names with spaces;
901      //remove leading and trailing whitespace
902      $family=trim($family," \t\n\r\x0B\"'");
903      if ($DEBUGCSS) print '('.$family.')';
904      $font = Font_Metrics::get_font($family, $subtype);
905
906      if ( $font ) {
907        if ($DEBUGCSS)  print '('.$font.")get_font_family]\n</pre>";
908        return $font;
909      }
910    }
911
912    $family = null;
913    if ($DEBUGCSS)  print '(default)';
914    $font = Font_Metrics::get_font($family, $subtype);
915
916    if ( $font ) {
917      if ($DEBUGCSS) print '('.$font.")get_font_family]\n</pre>";
918      return $font;
919    }
920    throw new DOMPDF_Exception("Unable to find a suitable font replacement for: '" . $this->_props["font_family"] ."'");
921   
922  }
923
924  /**
925   * Returns the resolved font size, in points
926   *
927   * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size   
928   * @return float
929   */
930  function get_font_size() {
931
932    if ( $this->__font_size_calculated )
933      return $this->_props["font_size"];
934   
935    if ( !isset($this->_props["font_size"]) )
936      $fs = self::$_defaults["font_size"];
937    else
938      $fs = $this->_props["font_size"];
939   
940    if ( !isset($this->_parent_font_size) )
941      $this->_parent_font_size = self::$default_font_size;
942   
943    switch ($fs) {
944     
945    case "xx-small":
946      $fs = 3/5 * $this->_parent_font_size;
947      break;
948
949    case "x-small":
950      $fs = 3/4 * $this->_parent_font_size;
951      break;
952
953    case "smaller":
954    case "small":
955      $fs = 8/9 * $this->_parent_font_size;
956      break;
957
958    case "medium":
959      $fs = $this->_parent_font_size;
960      break;
961
962    case "larger":
963    case "large":
964      $fs = 6/5 * $this->_parent_font_size;
965      break;
966
967    case "x-large":
968      $fs = 3/2 * $this->_parent_font_size;
969      break;
970
971    case "xx-large":
972      $fs = 2/1 * $this->_parent_font_size;
973      break;
974
975    default:
976      break;
977    }
978
979    // Ensure relative sizes resolve to something
980    if ( ($i = mb_strpos($fs, "em")) !== false )
981      $fs = mb_substr($fs, 0, $i) * $this->_parent_font_size;
982
983    else if ( ($i = mb_strpos($fs, "ex")) !== false )
984      $fs = mb_substr($fs, 0, $i) * $this->_parent_font_size;
985
986    else
987      $fs = $this->length_in_pt($fs);
988
989    //see __set and __get, on all assignments clear cache!
990        $this->_prop_cache["font_size"] = null;
991    $this->_props["font_size"] = $fs;
992    $this->__font_size_calculated = true;
993    return $this->_props["font_size"];
994
995  }
996
997  /**
998   * @link http://www.w3.org/TR/CSS21/text.html#propdef-word-spacing
999   * @return float
1000   */
1001  function get_word_spacing() {
1002    if ( $this->_props["word_spacing"] === "normal" )
1003      return 0;
1004
1005    return $this->_props["word_spacing"];
1006  }
1007
1008  /**
1009   * @link http://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
1010   * @return float
1011   */
1012  function get_line_height() {
1013    if ( $this->_props["line_height"] === "normal" )
1014      return self::$default_line_height * $this->get_font_size();
1015
1016    if ( is_numeric($this->_props["line_height"]) )
1017      return $this->length_in_pt( $this->_props["line_height"] . "%", $this->get_font_size());
1018   
1019    return $this->length_in_pt( $this->_props["line_height"], $this->get_font_size() );
1020  }
1021
1022  /**
1023   * Returns the colour as an array
1024   *
1025   * The array has the following format:
1026   * <code>array(r,g,b, "r" => r, "g" => g, "b" => b, "hex" => "#rrggbb")</code>
1027   *
1028   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
1029   * @return array
1030   */
1031  function get_color() {
1032    return $this->munge_color( $this->_props["color"] );
1033  }
1034
1035  /**
1036   * Returns the background colour as an array
1037   *
1038   * The returned array has the same format as {@link Style::get_color()}
1039   *
1040   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
1041   * @return array
1042   */
1043  function get_background_color() {
1044    return $this->munge_color( $this->_props["background_color"] );
1045  }
1046 
1047  /**
1048   * Returns the background position as an array
1049   *
1050   * The returned array has the following format:
1051   * <code>array(x,y, "x" => x, "y" => y)</code>
1052   *
1053   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
1054   * @return array
1055   */
1056  function get_background_position() {
1057   
1058    $tmp = explode(" ", $this->_props["background_position"]);
1059
1060    switch ($tmp[0]) {
1061
1062    case "left":
1063      $x = "0%";
1064      break;
1065
1066    case "right":
1067      $x = "100%";
1068      break;
1069
1070    case "top":
1071      $y = "0%";
1072      break;
1073
1074    case "bottom":
1075      $y = "100%";
1076      break;
1077
1078    case "center":
1079      $x = "50%";
1080      $y = "50%";
1081      break;
1082
1083    default:
1084      $x = $tmp[0];
1085      break;
1086    }
1087
1088    if ( isset($tmp[1]) ) {
1089
1090      switch ($tmp[1]) {
1091      case "left":
1092        $x = "0%";
1093        break;
1094       
1095      case "right":
1096        $x = "100%";
1097        break;
1098       
1099      case "top":
1100        $y = "0%";
1101        break;
1102       
1103      case "bottom":
1104        $y = "100%";
1105        break;
1106       
1107      case "center":
1108        if ( $tmp[0] == "left" || $tmp[0] == "right" || $tmp[0] == "center" )
1109          $y = "50%";
1110        else
1111          $x = "50%";
1112        break;
1113       
1114      default:
1115        $y = $tmp[1];
1116        break;
1117      }
1118
1119    } else {
1120      $y = "50%";
1121    }
1122
1123    if ( !isset($x) )
1124      $x = "0%";
1125
1126    if ( !isset($y) )
1127      $y = "0%";
1128
1129    return array( 0 => $x, "x" => $x,
1130                  1 => $y, "y" => $y );
1131  }
1132
1133
1134  /**
1135   * Returns the background as it is currently stored
1136   *
1137   * (currently anyway only for completeness.
1138   * not used for further processing)
1139   *
1140   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
1141   * @return string
1142   */
1143  function get_background_attachment() {
1144    return $this->_props["background_attachment"];
1145  }
1146
1147
1148  /**
1149   * Returns the background_repeat as it is currently stored
1150   *
1151   * (currently anyway only for completeness.
1152   * not used for further processing)
1153   *
1154   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
1155   * @return string
1156   */
1157  function get_background_repeat() {
1158    return $this->_props["background_repeat"];
1159  }
1160
1161
1162  /**
1163   * Returns the background as it is currently stored
1164   *
1165   * (currently anyway only for completeness.
1166   * not used for further processing, but the individual get_background_xxx)
1167   *
1168   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background
1169   * @return string
1170   */
1171  function get_background() {
1172    return $this->_props["background"];
1173  }
1174
1175
1176  /**#@+
1177   * Returns the border colour as an array
1178   *
1179   * See {@link Style::get_color()}
1180   *
1181   * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties
1182   * @return array
1183   */
1184  function get_border_top_color() {
1185    if ( $this->_props["border_top_color"] === "" ) {
1186      //see __set and __get, on all assignments clear cache!
1187      $this->_prop_cache["border_top_color"] = null;
1188      $this->_props["border_top_color"] = $this->__get("color");
1189    }
1190    return $this->munge_color($this->_props["border_top_color"]);
1191  }
1192
1193  function get_border_right_color() {
1194    if ( $this->_props["border_right_color"] === "" ) {
1195      //see __set and __get, on all assignments clear cache!
1196      $this->_prop_cache["border_right_color"] = null;
1197      $this->_props["border_right_color"] = $this->__get("color");
1198    }
1199    return $this->munge_color($this->_props["border_right_color"]);
1200  }
1201
1202  function get_border_bottom_color() {
1203    if ( $this->_props["border_bottom_color"] === "" ) {
1204      //see __set and __get, on all assignments clear cache!
1205      $this->_prop_cache["border_bottom_color"] = null;
1206      $this->_props["border_bottom_color"] = $this->__get("color");
1207    }
1208    return $this->munge_color($this->_props["border_bottom_color"]);;
1209  }
1210
1211  function get_border_left_color() {
1212    if ( $this->_props["border_left_color"] === "" ) {
1213      //see __set and __get, on all assignments clear cache!
1214      $this->_prop_cache["border_left_color"] = null;
1215      $this->_props["border_left_color"] = $this->__get("color");
1216    }
1217    return $this->munge_color($this->_props["border_left_color"]);
1218  }
1219 
1220  /**#@-*/
1221
1222 /**#@+
1223   * Returns the border width, as it is currently stored
1224   *
1225   * @link http://www.w3.org/TR/CSS21/box.html#border-width-properties
1226   * @return float|string   
1227   */
1228  function get_border_top_width() {
1229    $style = $this->__get("border_top_style");
1230    return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_top_width"]) : 0;
1231  }
1232 
1233  function get_border_right_width() {
1234    $style = $this->__get("border_right_style");   
1235    return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_right_width"]) : 0;
1236  }
1237
1238  function get_border_bottom_width() {
1239    $style = $this->__get("border_bottom_style");
1240    return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_bottom_width"]) : 0;
1241  }
1242
1243  function get_border_left_width() {
1244    $style = $this->__get("border_left_style");
1245    return $style !== "none" && $style !== "hidden" ? $this->length_in_pt($this->_props["border_left_width"]) : 0;
1246  }
1247  /**#@-*/
1248
1249  /**
1250   * Return an array of all border properties.
1251   *
1252   * The returned array has the following structure:
1253   * <code>
1254   * array("top" => array("width" => [border-width],
1255   *                      "style" => [border-style],
1256   *                      "color" => [border-color (array)]),
1257   *       "bottom" ... )
1258   * </code>
1259   *
1260   * @return array
1261   */
1262  function get_border_properties() {
1263    return array("top" => array("width" => $this->__get("border_top_width"),
1264                                "style" => $this->__get("border_top_style"),
1265                                "color" => $this->__get("border_top_color")),
1266                 "bottom" => array("width" => $this->__get("border_bottom_width"),
1267                                   "style" => $this->__get("border_bottom_style"),
1268                                   "color" => $this->__get("border_bottom_color")),
1269                 "right" => array("width" => $this->__get("border_right_width"),
1270                                  "style" => $this->__get("border_right_style"),
1271                                  "color" => $this->__get("border_right_color")),
1272                 "left" => array("width" => $this->__get("border_left_width"),
1273                                 "style" => $this->__get("border_left_style"),
1274                                 "color" => $this->__get("border_left_color")));
1275  }
1276
1277  /**
1278   * Return a single border property
1279   *
1280   * @return mixed
1281   */
1282  protected function _get_border($side) {
1283    $color = $this->__get("border_" . $side . "_color");
1284   
1285    return $this->__get("border_" . $side . "_width") . " " .
1286      $this->__get("border_" . $side . "_style") . " " . $color["hex"];
1287  }
1288
1289  /**#@+
1290   * Return full border properties as a string
1291   *
1292   * Border properties are returned just as specified in CSS:
1293   * <pre>[width] [style] [color]</pre>
1294   * e.g. "1px solid blue"
1295   *
1296   * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
1297   * @return string
1298   */
1299  function get_border_top() { return $this->_get_border("top"); }
1300  function get_border_right() { return $this->_get_border("right"); }
1301  function get_border_bottom() { return $this->_get_border("bottom"); }
1302  function get_border_left() { return $this->_get_border("left"); }
1303  /**#@-*/
1304
1305
1306  /**
1307   * Returns border spacing as an array
1308   *
1309   * The array has the format (h_space,v_space)
1310   *
1311   * @link http://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing
1312   * @return array
1313   */
1314  function get_border_spacing() {
1315    return explode(" ", $this->_props["border_spacing"]);
1316  }
1317
1318/*==============================*/
1319
1320  /*
1321   !important attribute
1322   For basic functionality of the !important attribute with overloading
1323   of several styles of an element, changes in inherit(), merge() and _parse_properties()
1324   are sufficient [helpers var $_important_props, __construct(), important_set(), important_get()]
1325
1326   Only for combined attributes extra treatment needed. See below.
1327
1328   div { border: 1px red; }
1329   div { border: solid; } // Not combined! Only one occurence of same style per context
1330   //
1331   div { border: 1px red; }
1332   div a { border: solid; } // Adding to border style ok by inheritance
1333   //
1334   div { border-style: solid; } // Adding to border style ok because of different styles
1335   div { border: 1px red; }
1336   //
1337   div { border-style: solid; !important} // border: overrides, even though not !important
1338   div { border: 1px dashed red; }
1339   //
1340   div { border: 1px red; !important }
1341   div a { border-style: solid; } // Need to override because not set
1342
1343   Special treatment:
1344   At individual property like border-top-width need to check whether overriding value is also !important.
1345   Also store the !important condition for later overrides.
1346   Since not known who is initiating the override, need to get passed !importan as parameter.
1347   !important Paramter taken as in the original style in the css file.
1348   When poperty border !important given, do not mark subsets like border_style as important. Only
1349   individual properties.
1350
1351   Note:
1352   Setting individual property directly from css with e.g. set_border_top_style() is not needed, because
1353   missing set funcions handled by a generic handler __set(), including the !important.
1354   Setting individual property of as sub-property is handled below.
1355
1356   Implementation see at _set_style_side_type()
1357   Callers _set_style_sides_type(), _set_style_type, _set_style_type_important()
1358
1359   Related functionality for background, padding, margin, font, list_style
1360  */
1361
1362  /* Generalized set function for individual attribute of combined style.
1363   * With check for !important
1364   * Applicable for background, border, padding, margin, font, list_style
1365   * Note: $type has a leading underscore (or is empty), the others not.
1366   */
1367  protected function _set_style_side_type($style,$side,$type,$val,$important) {
1368    if ( !isset($this->_important_props[$style.'_'.$side.$type]) || $important) {
1369      //see __set and __get, on all assignments clear cache!
1370      $this->_prop_cache[$style.'_'.$side.$type] = null;
1371      if ($important) {
1372        $this->_important_props[$style.'_'.$side.$type] = true;
1373      }
1374      $this->_props[$style.'_'.$side.$type] = $val;
1375    }
1376  }
1377
1378  protected function _set_style_sides_type($style,$top,$right,$bottom,$left,$type,$important) {
1379      $this->_set_style_side_type($style,'top',$type,$top,$important);
1380      $this->_set_style_side_type($style,'right',$type,$right,$important);
1381      $this->_set_style_side_type($style,'bottom',$type,$bottom,$important);
1382      $this->_set_style_side_type($style,'left',$type,$left,$important);
1383  }
1384
1385  protected function _set_style_type($style,$type,$val,$important) {
1386    $arr = explode(" ", $val);
1387    switch (count($arr)) {
1388    case 1:
1389      $this->_set_style_sides_type($style,$arr[0],$arr[0],$arr[0],$arr[0],$type,$important);
1390      break;
1391    case 2:
1392      $this->_set_style_sides_type($style,$arr[0],$arr[1],$arr[0],$arr[1],$type,$important);
1393      break;
1394    case 3:
1395      $this->_set_style_sides_type($style,$arr[0],$arr[1],$arr[1],$arr[2],$type,$important);
1396      break;
1397    case 4:
1398      $this->_set_style_sides_type($style,$arr[0],$arr[1],$arr[2],$arr[3],$type,$important);
1399      break;
1400    default:
1401      break;
1402    }
1403    //see __set and __get, on all assignments clear cache!
1404        $this->_prop_cache[$style.$type] = null;
1405    $this->_props[$style.$type] = $val;
1406  }
1407
1408  protected function _set_style_type_important($style,$type,$val) {
1409    $this->_set_style_type($style,$type,$val,isset($this->_important_props[$style.$type]));
1410  }
1411
1412  /* Anyway only called if _important matches and is assigned
1413   * E.g. _set_style_side_type($style,$side,'',str_replace("none", "0px", $val),isset($this->_important_props[$style.'_'.$side]));
1414   */
1415  protected function _set_style_side_width_important($style,$side,$val) {
1416    //see __set and __get, on all assignments clear cache!
1417    $this->_prop_cache[$style.'_'.$side] = null;
1418    $this->_props[$style.'_'.$side] = str_replace("none", "0px", $val);
1419  }
1420
1421  protected function _set_style($style,$val,$important) {
1422    if ( !isset($this->_important_props[$style]) || $important) {
1423      if ($important) {
1424        $this->_important_props[$style] = true;
1425      }
1426      //see __set and __get, on all assignments clear cache!
1427          $this->_prop_cache[$style] = null;
1428      $this->_props[$style] = $val;
1429    }
1430  }
1431
1432  protected function _image($val) {
1433    $DEBUGCSS=DEBUGCSS;
1434   
1435    if ( mb_strpos($val, "url") === false ) {
1436      $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none
1437    }
1438    else {
1439      $val = preg_replace("/url\(['\"]?([^'\")]+)['\"]?\)/","\\1", trim($val));
1440
1441      // Resolve the url now in the context of the current stylesheet
1442      $parsed_url = explode_url($val);
1443      if ( $parsed_url["protocol"] == "" && $this->_stylesheet->get_protocol() == "" ) {
1444        if ($parsed_url["path"]{0} == '/' || $parsed_url["path"]{0} == '\\' ) {
1445          $path = $_SERVER["DOCUMENT_ROOT"].'/';
1446        } else {
1447          $path = $this->_stylesheet->get_base_path();
1448        }
1449        $path .= $parsed_url["path"] . $parsed_url["file"];
1450        $path = dompdf_realpath($path);
1451      } else {
1452        $path = build_url($this->_stylesheet->get_protocol(),
1453                          $this->_stylesheet->get_host(),
1454                          $this->_stylesheet->get_base_path(),
1455                          $val);
1456      }
1457    }
1458    if ($DEBUGCSS) {
1459      print "<pre>[_image\n";
1460      print_r($parsed_url);
1461      print $this->_stylesheet->get_protocol()."\n".$this->_stylesheet->get_base_path()."\n".$path."\n";
1462      print "_image]</pre>";;
1463    }
1464    return $path;
1465  }
1466
1467/*======================*/
1468
1469  /**
1470   * Sets colour
1471   *
1472   * The colour parameter can be any valid CSS colour value
1473   *   
1474   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
1475   * @param string $colour
1476   */
1477  function set_color($colour) {
1478    $col = $this->munge_colour($colour);
1479
1480    if ( is_null($col) )
1481      $col = self::$_defaults["color"];
1482
1483    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1484        $this->_prop_cache["color"] = null;
1485    $this->_props["color"] = $col["hex"];
1486  }
1487
1488  /**
1489   * Sets the background colour
1490   *
1491   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
1492   * @param string $colour
1493   */
1494  function set_background_color($colour) {
1495    $col = $this->munge_colour($colour);
1496    if ( is_null($col) )
1497      $col = self::$_defaults["background_color"];
1498
1499    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1500        $this->_prop_cache["background_color"] = null;
1501    $this->_props["background_color"] = is_array($col) ? $col["hex"] : $col;
1502  }
1503
1504  /**
1505   * Set the background image url
1506   *
1507   * @link http://www.w3.org/TR/CSS21/colors.html#background-properties
1508   * @param string $url
1509   */
1510  function set_background_image($val) {
1511    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1512        $this->_prop_cache["background_image"] = null;
1513    $this->_props["background_image"] = $this->_image($val);
1514  }
1515
1516  /**
1517   * Sets the background repeat
1518   *
1519   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
1520   * @param string $val
1521   */
1522  function set_background_repeat($val) {
1523    if ( is_null($val) )
1524      $val = self::$_defaults["background_repeat"];
1525    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1526    $this->_prop_cache["background_repeat"] = null;
1527    $this->_props["background_repeat"] = $val;
1528  }
1529
1530  /**
1531   * Sets the background attachment
1532   *
1533   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
1534   * @param string $val
1535   */
1536  function set_background_attachment($val) {
1537    if ( is_null($val) )
1538      $val = self::$_defaults["background_attachment"];
1539
1540    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1541        $this->_prop_cache["background_attachment"] = null;
1542    $this->_props["background_attachment"] = $val;
1543  }
1544
1545  /**
1546   * Sets the background position
1547   *
1548   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
1549   * @param string $val
1550   */
1551  function set_background_position($val) {
1552    if ( is_null($val) )
1553      $val = self::$_defaults["background_position"];
1554
1555    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1556        $this->_prop_cache["background_position"] = null;
1557    $this->_props["background_position"] = $val;
1558  }
1559
1560  /**
1561   * Sets the background - combined options
1562   *
1563   * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background
1564   * @param string $val
1565   */
1566  function set_background($val) {
1567    $col = null;
1568    $pos = array();
1569    $tmp = explode(" ", $val);
1570    $important = isset($this->_important_props["background"]);
1571    foreach($tmp as $attr) {
1572          if (mb_substr($attr, 0, 3) == "url" || $attr == "none") {
1573            $this->_set_style("background_image", $this->_image($attr), $important);
1574          } else if ($attr == "fixed" || $attr == "scroll") {
1575            $this->_set_style("background_attachment", $attr, $important);
1576          } else if ($attr == "repeat" || $attr == "repeat-x" || $attr == "repeat-y" || $attr == "no-repeat") {
1577            $this->_set_style("background_repeat", $attr, $important);
1578      } else if (($col = $this->munge_color($attr)) != null ) {
1579            $this->_set_style("background_color", is_array($col) ? $col["hex"] : $col, $important);
1580      } else {
1581                $pos[] = $attr;
1582          }
1583        }
1584        if (count($pos)) {
1585          $this->_set_style("background_position",implode(' ',$pos), $important);
1586        }
1587    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1588        $this->_prop_cache["background"] = null;
1589        $this->_props["background"] = $val;
1590  }
1591
1592  /**
1593   * Sets the font size
1594   *
1595   * $size can be any acceptable CSS size
1596   *
1597   * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
1598   * @param string|float $size
1599   */
1600  function set_font_size($size) {
1601    $this->__font_size_calculated = false;
1602    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1603        $this->_prop_cache["font_size"] = null;
1604    $this->_props["font_size"] = $size;
1605  }
1606
1607  /**
1608   * Sets the font style
1609   *
1610   * combined attributes
1611   * set individual attributes also, respecting !important mark
1612   * exactly this order, separate by space. Multiple fonts separated by comma:
1613   * font-style, font-variant, font-weight, font-size, line-height, font-family
1614   *
1615   * Other than with border and list, existing partial attributes should
1616   * reset when starting here, even when not mentioned.
1617   * If individual attribute is !important and explicite or implicite replacement is not,
1618   * keep individual attribute
1619   *
1620   * require whitespace as delimiters for single value attributes
1621   * On delimiter "/" treat first as font height, second as line height
1622   * treat all remaining at the end of line as font
1623   * font-style, font-variant, font-weight, font-size, line-height, font-family
1624   *
1625   * missing font-size and font-family might be not allowed, but accept it here and
1626   * use default (medium size, enpty font name)
1627   *
1628   * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style
1629   * @param $val
1630   */
1631  function set_font($val) {
1632    $this->__font_size_calculated = false;
1633    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1634        $this->_prop_cache["font"] = null;
1635        $this->_props["font"] = $val;
1636
1637    $important = isset($this->_important_props["font"]);
1638
1639        if ( preg_match("/^(italic|oblique|normal)\s*(.*)$/i",$val,$match) ) {
1640                $this->_set_style("font_style", $match[1], $important);
1641                $val = $match[2];
1642        } else {
1643                $this->_set_style("font_style", self::$_defaults["font_style"], $important);
1644        }
1645
1646        if ( preg_match("/^(small-caps|normal)\s*(.*)$/i",$val,$match) ) {
1647                $this->_set_style("font_variant", $match[1], $important);
1648                $val = $match[2];
1649        } else {
1650                $this->_set_style("font_variant", self::$_defaults["font_variant"], $important);
1651        }
1652
1653    //matching numeric value followed by unit -> this is indeed a subsequent font size. Skip!
1654        if ( preg_match("/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900|normal)\s*(.*)$/i",$val,$match) &&
1655         !preg_match("/^(?:pt|px|pc|em|ex|in|cm|mm|%)/",$match[2])
1656           ) {
1657                $this->_set_style("font_weight", $match[1], $important);
1658                $val = $match[2];
1659        } else {
1660                $this->_set_style("font_weight", self::$_defaults["font_weight"], $important);
1661        }
1662
1663        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) ) {
1664                $this->_set_style("font_size", $match[1], $important);
1665                $val = $match[2];
1666        if (preg_match("/^\/\s*(\d+\s*(?:pt|px|pc|em|ex|in|cm|mm|%))\s*(.*)$/i",$val,$match) ) {
1667                        $this->_set_style("line_height", $match[1], $important);
1668                        $val = $match[2];
1669        } else {
1670                        $this->_set_style("line_height", self::$_defaults["line_height"], $important);
1671        }
1672        } else {
1673                $this->_set_style("font_size", self::$_defaults["font_size"], $important);
1674                $this->_set_style("line_height", self::$_defaults["line_height"], $important);
1675        }
1676
1677        if(strlen($val) != 0) {
1678          $this->_set_style("font_family", $val, $important);
1679        } else {
1680          $this->_set_style("font_family", self::$_defaults["font_family"], $important);
1681        }
1682  }
1683
1684  /**#@+
1685   * Sets page break properties
1686   *
1687   * @link http://www.w3.org/TR/CSS21/page.html#page-breaks
1688   * @param string $break
1689   */
1690  function set_page_break_before($break) {
1691    if ($break === "left" || $break === "right")
1692      $break = "always";
1693
1694    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1695        $this->_prop_cache["page_break_before"] = null;
1696    $this->_props["page_break_before"] = $break;
1697  }
1698
1699  function set_page_break_after($break) {
1700    if ($break === "left" || $break === "right")
1701      $break = "always";
1702
1703    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1704        $this->_prop_cache["page_break_after"] = null;
1705    $this->_props["page_break_after"] = $break;
1706  }
1707  /**#@-*/
1708   
1709  //........................................................................
1710
1711  /**#@+
1712   * Sets the margin size
1713   *
1714   * @link http://www.w3.org/TR/CSS21/box.html#margin-properties
1715   * @param $val
1716   */
1717  function set_margin_top($val) {
1718    $this->_set_style_side_width_important('margin','top',$val);
1719  }
1720
1721  function set_margin_right($val) {
1722    $this->_set_style_side_width_important('margin','right',$val);
1723  }
1724
1725  function set_margin_bottom($val) {
1726    $this->_set_style_side_width_important('margin','bottom',$val);
1727  }
1728
1729  function set_margin_left($val) {
1730    $this->_set_style_side_width_important('margin','left',$val);
1731  }
1732 
1733  function set_margin($val) {
1734    $val = str_replace("none", "0px", $val);
1735    $this->_set_style_type_important('margin','',$val);
1736  }
1737  /**#@-*/
1738
1739  /**#@+
1740   * Sets the padding size
1741   *
1742   * @link http://www.w3.org/TR/CSS21/box.html#padding-properties
1743   * @param $val
1744   */
1745  function set_padding_top($val) {
1746    $this->_set_style_side_width_important('padding','top',$val);
1747  }
1748
1749  function set_padding_right($val) {
1750    $this->_set_style_side_width_important('padding','right',$val);
1751  }
1752
1753  function set_padding_bottom($val) {
1754    $this->_set_style_side_width_important('padding','bottom',$val);
1755  }
1756
1757  function set_padding_left($val) {
1758    $this->_set_style_side_width_important('padding','left',$val);
1759  }
1760
1761  function set_padding($val) {
1762    $val = str_replace("none", "0px", $val);
1763    $this->_set_style_type_important('padding','',$val);
1764  }
1765  /**#@-*/
1766
1767  /**
1768   * Sets a single border
1769   *
1770   * @param string $side
1771   * @param string $border_spec  ([width] [style] [color])
1772   */
1773  protected function _set_border($side, $border_spec, $important) {
1774    $border_spec = str_replace(",", " ", $border_spec);
1775    $arr = explode(" ", $border_spec);
1776
1777    // FIXME: handle partial values
1778 
1779    //For consistency of individal and combined properties, and with ie8 and firefox3
1780    //reset all attributes, even if only partially given   
1781    $this->_set_style_side_type('border',$side,'_style',self::$_defaults['border_'.$side.'_style'],$important);
1782    $this->_set_style_side_type('border',$side,'_width',self::$_defaults['border_'.$side.'_width'],$important);
1783    $this->_set_style_side_type('border',$side,'_color',self::$_defaults['border_'.$side.'_color'],$important);
1784
1785    foreach ($arr as $value) {
1786      $value = trim($value);
1787      if ( in_array($value, self::$BORDER_STYLES) ) {
1788        $this->_set_style_side_type('border',$side,'_style',$value,$important);
1789
1790      } else if ( preg_match("/[.0-9]+(?:px|pt|pc|em|ex|%|in|mm|cm)|(?:thin|medium|thick)/", $value ) ) {
1791        $this->_set_style_side_type('border',$side,'_width',$value,$important);
1792
1793      } else {
1794        // must be colour
1795        $this->_set_style_side_type('border',$side,'_color',$value,$important);
1796      }
1797    }
1798
1799    //see __set and __get, on all assignments clear cache!
1800        $this->_prop_cache['border_'.$side] = null;
1801    $this->_props['border_'.$side] = $border_spec;
1802  }
1803
1804  /**#@+
1805   * Sets the border styles
1806   *
1807   * @link http://www.w3.org/TR/CSS21/box.html#border-properties
1808   * @param string $val
1809   */
1810  function set_border_top($val) { $this->_set_border("top", $val, isset($this->_important_props['border_top'])); }
1811  function set_border_right($val) { $this->_set_border("right", $val, isset($this->_important_props['border_right'])); }
1812  function set_border_bottom($val) { $this->_set_border("bottom", $val, isset($this->_important_props['border_bottom'])); }
1813  function set_border_left($val) { $this->_set_border("left", $val, isset($this->_important_props['border_left'])); }
1814
1815  function set_border($val) {
1816    $important = isset($this->_important_props["border"]);
1817    $this->_set_border("top", $val, $important);
1818    $this->_set_border("right", $val, $important);
1819    $this->_set_border("bottom", $val, $important);
1820    $this->_set_border("left", $val, $important);
1821    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1822        $this->_prop_cache["border"] = null;
1823    $this->_props["border"] = $val;
1824  }
1825
1826  function set_border_width($val) {
1827    $this->_set_style_type_important('border','_width',$val);
1828  }
1829
1830  function set_border_color($val) {
1831    $this->_set_style_type_important('border','_color',$val);
1832  }
1833
1834  function set_border_style($val) {
1835    $this->_set_style_type_important('border','_style',$val);
1836  }
1837  /**#@-*/
1838
1839
1840  /**
1841   * Sets the border spacing
1842   *
1843   * @link http://www.w3.org/TR/CSS21/box.html#border-properties
1844   * @param float $val
1845   */
1846  function set_border_spacing($val) {
1847
1848    $arr = explode(" ", $val);
1849
1850    if ( count($arr) == 1 )
1851      $arr[1] = $arr[0];
1852
1853    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1854        $this->_prop_cache["border_spacing"] = null;
1855    $this->_props["border_spacing"] = $arr[0] . " " . $arr[1];
1856  }
1857
1858  /**
1859   * Sets the list style image
1860   *
1861   * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
1862   * @param $val
1863   */
1864  function set_list_style_image($val) {
1865    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1866        $this->_prop_cache["list_style_image"] = null;
1867    $this->_props["list_style_image"] = $this->_image($val);
1868  }
1869
1870  /**
1871   * Sets the list style
1872   *
1873   * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style
1874   * @param $val
1875   */
1876  function set_list_style($val) {
1877    $important = isset($this->_important_props["list_style"]);
1878    $arr = explode(" ", str_replace(",", " ", $val));
1879
1880    static $types = array("disc", "circle", "square", "decimal",
1881                   "decimal-leading-zero", "lower-roman",
1882                   "upper-roman", "lower-greek", "lower-latin",
1883                   "upper-latin", "armenian", "georgian",
1884                   "lower-alpha", "upper-alpha", "hebrew",
1885                   "cjk-ideographic", "hiragana", "katakana",
1886                   "hiragana-iroha", "katakana-iroha", "none");
1887
1888    static $positions = array("inside", "outside");
1889
1890    foreach ($arr as $value) {
1891      /* http://www.w3.org/TR/CSS21/generate.html#list-style
1892       * A value of 'none' for the 'list-style' property sets both 'list-style-type' and 'list-style-image' to 'none'
1893       */
1894      if ($value == "none") {
1895            $this->_set_style("list_style_type", $value, $important);
1896            $this->_set_style("list_style_image", $value, $important);
1897        continue;
1898      }
1899
1900      //On setting or merging or inheriting list_style_image as well as list_style_type,
1901      //and url exists, then url has precedence, otherwise fall back to list_style_type
1902      //Firefox is wrong here (list_style_image gets overwritten on explicite list_style_type)
1903      //Internet Explorer 7/8 and dompdf is right.
1904       
1905          if (mb_substr($value, 0, 3) == "url") {
1906            $this->_set_style("list_style_image", $this->_image($value), $important);
1907        continue;
1908      }
1909
1910      if ( in_array($value, $types) ) {
1911            $this->_set_style("list_style_type", $value, $important);
1912      } else if ( in_array($value, $positions) ) {
1913            $this->_set_style("list_style_position", $value, $important);
1914      }
1915    }
1916
1917    //see __set and __get, on all assignments clear cache, not needed on direct set through __set
1918        $this->_prop_cache["list_style"] = null;
1919        $this->_props["list_style"] = $val;
1920  }
1921
1922  /**
1923   * Generate a string representation of the Style
1924   *
1925   * This dumps the entire property array into a string via print_r.  Useful
1926   * for debugging.
1927   *
1928   * @return string
1929   */
1930 /*DEBUGCSS print: see below additional debugging util*/
1931  function __toString() {
1932    return print_r(array_merge(array("parent_font_size" => $this->_parent_font_size),
1933                               $this->_props), true);
1934  }
1935
1936/*DEBUGCSS*/  function debug_print()
1937/*DEBUGCSS*/  {
1938/*DEBUGCSS*/    print "parent_font_size:".$this->_parent_font_size . ";\n";
1939/*DEBUGCSS*/    foreach($this->_props as $prop => $val ) {
1940/*DEBUGCSS*/      print $prop.':'.$val;
1941/*DEBUGCSS*/      if (isset($this->_important_props[$prop])) {
1942/*DEBUGCSS*/            print '!important';
1943/*DEBUGCSS*/      }
1944/*DEBUGCSS*/      print ";\n";
1945/*DEBUGCSS*/    }
1946/*DEBUGCSS*/  }
1947}
1948?>
Note: See TracBrowser for help on using the repository browser.