4 * @link http://dompdf.github.com/
5 * @author Benj Carson <benjcarson@digitaljunkies.ca>
6 * @author Fabien Ménager <fabien.menager@gmail.com>
7 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
14 * Translates HTML 4.0 attributes into CSS rules
18 class AttributeTranslator
20 static $_style_attr = "_html_style_attribute";
22 // Munged data originally from
23 // http://www.w3.org/TR/REC-html40/index/attributes.html
24 // http://www.cs.tut.fi/~jkorpela/html2css.html
25 static private $__ATTRIBUTE_LOOKUP = array(
26 //'caption' => array ( 'align' => '', ),
29 'bottom' => 'vertical-align: baseline;',
30 'middle' => 'vertical-align: middle;',
31 'top' => 'vertical-align: top;',
32 'left' => 'float: left;',
33 'right' => 'float: right;'
35 'border' => 'border: %0.2Fpx solid;',
36 'height' => 'height: %spx;',
37 'hspace' => 'padding-left: %1$0.2Fpx; padding-right: %1$0.2Fpx;',
38 'vspace' => 'padding-top: %1$0.2Fpx; padding-bottom: %1$0.2Fpx;',
39 'width' => 'width: %spx;',
43 'left' => 'margin-left: 0; margin-right: auto;',
44 'center' => 'margin-left: auto; margin-right: auto;',
45 'right' => 'margin-left: auto; margin-right: 0;'
47 'bgcolor' => 'background-color: %s;',
48 'border' => '!set_table_border',
49 'cellpadding' => '!set_table_cellpadding', //'border-spacing: %0.2F; border-collapse: separate;',
50 'cellspacing' => '!set_table_cellspacing',
52 'void' => 'border-style: none;',
53 'above' => 'border-top-style: solid;',
54 'below' => 'border-bottom-style: solid;',
55 'hsides' => 'border-left-style: solid; border-right-style: solid;',
56 'vsides' => 'border-top-style: solid; border-bottom-style: solid;',
57 'lhs' => 'border-left-style: solid;',
58 'rhs' => 'border-right-style: solid;',
59 'box' => 'border-style: solid;',
60 'border' => 'border-style: solid;'
62 'rules' => '!set_table_rules',
63 'width' => 'width: %s;',
66 'align' => '!set_hr_align', // Need to grab width to set 'left' & 'right' correctly
67 'noshade' => 'border-style: solid;',
68 'size' => '!set_hr_size', //'border-width: %0.2F px;',
69 'width' => 'width: %s;',
72 'align' => 'text-align: %s;',
75 'align' => 'text-align: %s;',
78 'align' => 'text-align: %s;',
81 'align' => 'text-align: %s;',
84 'align' => 'text-align: %s;',
87 'align' => 'text-align: %s;',
90 'align' => 'text-align: %s;',
92 //TODO: translate more form element attributes
94 'size' => '!set_input_width'
97 'align' => 'text-align: %s;',
103 // 'colgroup' => array(
108 'align' => '!set_table_row_align',
109 'valign' => '!set_table_row_valign',
112 'align' => 'text-align: %s;',
113 'bgcolor' => '!set_background_color',
114 'height' => 'height: %s;',
115 'nowrap' => 'white-space: nowrap;',
116 'valign' => 'vertical-align: %s;',
117 'width' => 'width: %s;',
120 'align' => '!set_table_row_align',
121 'valign' => '!set_table_row_valign',
124 'align' => 'text-align: %s;',
125 'bgcolor' => '!set_background_color',
126 'height' => 'height: %s;',
127 'nowrap' => 'white-space: nowrap;',
128 'valign' => 'vertical-align: %s;',
129 'width' => 'width: %s;',
132 'align' => '!set_table_row_align',
133 'valign' => '!set_table_row_valign',
136 'align' => '!set_table_row_align',
137 'bgcolor' => '!set_table_row_bgcolor',
138 'valign' => '!set_table_row_valign',
141 'background' => 'background-image: url(%s);',
142 'bgcolor' => '!set_background_color',
143 'link' => '!set_body_link',
144 'text' => '!set_color',
147 'clear' => 'clear: %s;',
150 'color' => '!set_color',
151 'face' => 'font-family: %s;',
152 'size' => '!set_basefont_size',
155 'color' => '!set_color',
156 'face' => 'font-family: %s;',
157 'size' => '!set_font_size',
160 'compact' => 'margin: 0.5em 0;',
163 'compact' => 'margin: 0.5em 0;',
166 'compact' => 'margin: 0.5em 0;',
169 'compact' => 'margin: 0.5em 0;',
170 'start' => 'counter-reset: -dompdf-default-counter %d;',
171 'type' => 'list-style-type: %s;',
174 'compact' => 'margin: 0.5em 0;',
175 'type' => 'list-style-type: %s;',
178 'type' => 'list-style-type: %s;',
179 'value' => 'counter-reset: -dompdf-default-counter %d;',
182 'width' => 'width: %s;',
186 static protected $_last_basefont_size = 3;
187 static protected $_font_size_lookup = array(
188 // For basefont support
202 // For basefont support
210 * @param Frame $frame
212 static function translate_attributes(Frame
$frame)
214 $node = $frame->get_node();
215 $tag = $node->nodeName
;
217 if (!isset(self
::$__ATTRIBUTE_LOOKUP[$tag])) {
221 $valid_attrs = self
::$__ATTRIBUTE_LOOKUP[$tag];
222 $attrs = $node->attributes
;
223 $style = rtrim($node->getAttribute(self
::$_style_attr), "; ");
228 foreach ($attrs as $attr => $attr_node) {
229 if (!isset($valid_attrs[$attr])) {
233 $value = $attr_node->value
;
235 $target = $valid_attrs[$attr];
237 // Look up $value in $target, if $target is an array:
238 if (is_array($target)) {
239 if (isset($target[$value])) {
240 $style .= " " . self
::_resolve_target($node, $target[$value], $value);
243 // otherwise use target directly
244 $style .= " " . self
::_resolve_target($node, $target, $value);
248 if (!is_null($style)) {
249 $style = ltrim($style);
250 $node->setAttribute(self
::$_style_attr, $style);
256 * @param \DOMNode $node
257 * @param string $target
258 * @param string $value
262 static protected function _resolve_target(\DOMNode
$node, $target, $value)
264 if ($target[0] === "!") {
266 $func = "_" . mb_substr($target, 1);
268 return self
::$func($node, $value);
271 return $value ?
sprintf($target, $value) : "";
275 * @param \DOMElement $node
276 * @param string $new_style
278 static function append_style(\DOMElement
$node, $new_style)
280 $style = rtrim($node->getAttribute(self
::$_style_attr), ";");
281 $style .= $new_style;
282 $style = ltrim($style, ";");
283 $node->setAttribute(self
::$_style_attr, $style);
287 * @param \DOMNode $node
289 * @return \DOMNodeList|\DOMElement[]
291 static protected function get_cell_list(\DOMNode
$node)
293 $xpath = new \
DOMXpath($node->ownerDocument
);
295 switch ($node->nodeName
) {
298 $query = "tr/td | thead/tr/td | tbody/tr/td | tfoot/tr/td | tr/th | thead/tr/th | tbody/tr/th | tfoot/tr/th";
304 $query = "tr/td | tr/th";
312 return $xpath->query($query, $node);
316 * @param string $value
320 static protected function _get_valid_color($value)
322 if (preg_match('/^#?([0-9A-F]{6})$/i', $value, $matches)) {
323 $value = "#$matches[1]";
330 * @param \DOMElement $node
331 * @param string $value
335 static protected function _set_color(\DOMElement
$node, $value)
337 $value = self
::_get_valid_color($value);
339 return "color: $value;";
343 * @param \DOMElement $node
344 * @param string $value
348 static protected function _set_background_color(\DOMElement
$node, $value)
350 $value = self
::_get_valid_color($value);
352 return "background-color: $value;";
356 * @param \DOMElement $node
357 * @param string $value
361 static protected function _set_table_cellpadding(\DOMElement
$node, $value)
363 $cell_list = self
::get_cell_list($node);
365 foreach ($cell_list as $cell) {
366 self
::append_style($cell, "; padding: {$value}px;");
373 * @param \DOMElement $node
374 * @param string $value
378 static protected function _set_table_border(\DOMElement
$node, $value)
380 $cell_list = self
::get_cell_list($node);
382 foreach ($cell_list as $cell) {
383 $style = rtrim($cell->getAttribute(self
::$_style_attr));
384 $style .= "; border-width: " . ($value > 0 ?
1 : 0) . "pt; border-style: inset;";
385 $style = ltrim($style, ";");
386 $cell->setAttribute(self
::$_style_attr, $style);
389 $style = rtrim($node->getAttribute(self
::$_style_attr), ";");
390 $style .= "; border-width: $value" . "px; ";
392 return ltrim($style, "; ");
396 * @param \DOMElement $node
397 * @param string $value
401 static protected function _set_table_cellspacing(\DOMElement
$node, $value)
403 $style = rtrim($node->getAttribute(self
::$_style_attr), ";");
406 $style .= "; border-collapse: collapse;";
408 $style .= "; border-spacing: {$value}px; border-collapse: separate;";
411 return ltrim($style, ";");
415 * @param \DOMElement $node
416 * @param string $value
418 * @return null|string
420 static protected function _set_table_rules(\DOMElement
$node, $value)
422 $new_style = "; border-collapse: collapse;";
426 $new_style .= "border-style: none;";
430 // FIXME: unsupported
434 $new_style .= "border-style: solid none solid none; border-width: 1px; ";
438 $new_style .= "border-style: none solid none solid; border-width: 1px; ";
442 $new_style .= "border-style: solid; border-width: 1px; ";
450 $cell_list = self
::get_cell_list($node);
452 foreach ($cell_list as $cell) {
453 $style = $cell->getAttribute(self
::$_style_attr);
454 $style .= $new_style;
455 $cell->setAttribute(self
::$_style_attr, $style);
458 $style = rtrim($node->getAttribute(self
::$_style_attr), ";");
459 $style .= "; border-collapse: collapse; ";
461 return ltrim($style, "; ");
465 * @param \DOMElement $node
466 * @param string $value
470 static protected function _set_hr_size(\DOMElement
$node, $value)
472 $style = rtrim($node->getAttribute(self
::$_style_attr), ";");
473 $style .= "; border-width: " . max(0, $value - 2) . "; ";
475 return ltrim($style, "; ");
479 * @param \DOMElement $node
480 * @param string $value
482 * @return null|string
484 static protected function _set_hr_align(\DOMElement
$node, $value)
486 $style = rtrim($node->getAttribute(self
::$_style_attr), ";");
487 $width = $node->getAttribute("width");
493 $remainder = 100 - (double)rtrim($width, "% ");
497 $style .= "; margin-right: $remainder %;";
501 $style .= "; margin-left: $remainder %;";
505 $style .= "; margin-left: auto; margin-right: auto;";
512 return ltrim($style, "; ");
516 * @param \DOMElement $node
517 * @param string $value
519 * @return null|string
521 static protected function _set_input_width(\DOMElement
$node, $value)
523 if (empty($value)) { return null; }
525 if ($node->hasAttribute("type") && in_array(strtolower($node->getAttribute("type")), array("text","password"))) {
526 return sprintf("width: %Fem", (((int)$value * .65)+
2));
528 return sprintf("width: %upx;", (int)$value);
533 * @param \DOMElement $node
534 * @param string $value
538 static protected function _set_table_row_align(\DOMElement
$node, $value)
540 $cell_list = self
::get_cell_list($node);
542 foreach ($cell_list as $cell) {
543 self
::append_style($cell, "; text-align: $value;");
550 * @param \DOMElement $node
551 * @param string $value
555 static protected function _set_table_row_valign(\DOMElement
$node, $value)
557 $cell_list = self
::get_cell_list($node);
559 foreach ($cell_list as $cell) {
560 self
::append_style($cell, "; vertical-align: $value;");
567 * @param \DOMElement $node
568 * @param string $value
572 static protected function _set_table_row_bgcolor(\DOMElement
$node, $value)
574 $cell_list = self
::get_cell_list($node);
575 $value = self
::_get_valid_color($value);
577 foreach ($cell_list as $cell) {
578 self
::append_style($cell, "; background-color: $value;");
585 * @param \DOMElement $node
586 * @param string $value
590 static protected function _set_body_link(\DOMElement
$node, $value)
592 $a_list = $node->getElementsByTagName("a");
593 $value = self
::_get_valid_color($value);
595 foreach ($a_list as $a) {
596 self
::append_style($a, "; color: $value;");
603 * @param \DOMElement $node
604 * @param string $value
608 static protected function _set_basefont_size(\DOMElement
$node, $value)
610 // FIXME: ? we don't actually set the font size of anything here, just
611 // the base size for later modification by <font> tags.
612 self
::$_last_basefont_size = $value;
618 * @param \DOMElement $node
619 * @param string $value
623 static protected function _set_font_size(\DOMElement
$node, $value)
625 $style = $node->getAttribute(self
::$_style_attr);
627 if ($value[0] === "-" ||
$value[0] === "+") {
628 $value = self
::$_last_basefont_size +
(int)$value;
631 if (isset(self
::$_font_size_lookup[$value])) {
632 $style .= "; font-size: " . self
::$_font_size_lookup[$value] . ";";
634 $style .= "; font-size: $value;";
637 return ltrim($style, "; ");