static function translate_attributes(Frame $frame) { $node = $frame->get_node(); $tag = $node->tagName; if (!isset(self::$__ATTRIBUTE_LOOKUP[$tag])) { return; } $valid_attrs = self::$__ATTRIBUTE_LOOKUP[$tag]; $attrs = $node->attributes; $style = rtrim($node->getAttribute(self::$_style_attr), "; "); if ($style != "") { $style .= ";"; } foreach ($attrs as $attr => $attr_node) { if (!isset($valid_attrs[$attr])) { continue; } $value = $attr_node->value; $target = $valid_attrs[$attr]; // Look up $value in $target, if $target is an array: if (is_array($target)) { if (isset($target[$value])) { $style .= " " . self::_resolve_target($node, $target[$value], $value); } } else { // otherwise use target directly $style .= " " . self::_resolve_target($node, $target, $value); } } if (!is_null($style)) { $style = ltrim($style); $node->setAttribute(self::$_style_attr, $style); } }
function render(Frame $frame) { $style = $frame->get_style(); list($x, $y, $w, $h) = $frame->get_border_box(); $this->_set_opacity($frame->get_opacity($style->opacity)); if ($frame->get_node()->nodeName === "body") { $h = $frame->get_containing_block("h") - $style->length_in_pt(array($style->margin_top, $style->padding_top, $style->border_top_width, $style->border_bottom_width, $style->padding_bottom, $style->margin_bottom), $style->width); } // Draw our background, border and content if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } $this->_render_border($frame); $this->_render_outline($frame); if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { $this->_debug_layout($frame->get_border_box(), "red"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5)); } } if (DEBUG_LAYOUT && DEBUG_LAYOUT_LINES && $frame->get_decorator()) { foreach ($frame->get_decorator()->get_line_boxes() as $line) { $frame->_debug_layout(array($line->x, $line->y, $line->w, $line->h), "orange"); } } }
/** * "Destructor": forcibly free all references held by this frame * * @param bool $recursive if true, call dispose on all children */ function dispose($recursive = false) { if ($recursive) { while ($child = $this->_first_child) { $child->dispose(true); } } // Remove this frame from the tree if ($this->_prev_sibling) { $this->_prev_sibling->_next_sibling = $this->_next_sibling; } if ($this->_next_sibling) { $this->_next_sibling->_prev_sibling = $this->_prev_sibling; } if ($this->_parent && $this->_parent->_first_child === $this) { $this->_parent->_first_child = $this->_next_sibling; } if ($this->_parent && $this->_parent->_last_child === $this) { $this->_parent->_last_child = $this->_prev_sibling; } if ($this->_parent) { $this->_parent->get_node()->removeChild($this->_node); } $this->_style->dispose(); unset($this->_style); $this->_original_style->dispose(); unset($this->_original_style); }
function render(Frame $frame) { if (!$this->_dompdf->get_option("enable_javascript")) { return; } $this->insert($frame->get_node()->nodeValue); }
function render(Frame $frame) { if (!DOMPDF_ENABLE_JAVASCRIPT) { return; } $this->insert($frame->get_node()->nodeValue); }
function add_frame_to_line(Frame $frame) { if (!$frame->is_in_flow()) { return; } $style = $frame->get_style(); $frame->set_containing_line($this->_line_boxes[$this->_cl]); if ($frame instanceof Inline_Frame_Decorator) { if ($frame->get_node()->nodeName === "br") { $this->maximize_line_height($style->length_in_pt($style->line_height), $frame); $this->add_line(true); } return; } if ($this->get_current_line_box()->w == 0 && $frame->is_text_node() && !$frame->is_pre()) { $frame->set_text(ltrim($frame->get_text())); $frame->recalculate_width(); } $w = $frame->get_margin_width(); if ($w == 0) { return; } $line = $this->_line_boxes[$this->_cl]; if ($line->left + $line->w + $line->right + $w > $this->get_containing_block("w")) { $this->add_line(); } $frame->position(); $current_line = $this->_line_boxes[$this->_cl]; $current_line->add_frame($frame); if ($frame->is_text_node()) { $current_line->wc += count(preg_split("/\\s+/", trim($frame->get_text()))); } $this->increase_line_width($w); $this->maximize_line_height($frame->get_margin_height(), $frame); }
/** * Class constructor * * @param Frame $frame the bullet frame to decorate * @param DOMPDF $dompdf the document's dompdf object */ function __construct(Frame $frame, DOMPDF $dompdf) { $style = $frame->get_style(); $url = $style->list_style_image; $frame->get_node()->setAttribute("src", $url); $this->_img = new Image_Frame_Decorator($frame, $dompdf); parent::__construct($this->_img, $dompdf); list($width, $height) = dompdf_getimagesize($this->_img->get_image_url()); // Resample the bullet image to be consistent with 'auto' sized images // See also Image_Frame_Reflower::get_min_max_width // Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary. $this->_width = (double) rtrim($width, "px") * 72 / DOMPDF_DPI; $this->_height = (double) rtrim($height, "px") * 72 / DOMPDF_DPI; //If an image is taller as the containing block/box, the box should be extended. //Neighbour elements are overwriting the overlapping image areas. //Todo: Where can the box size be extended? //Code below has no effect. //See block_frame_reflower _calculate_restricted_height //See generated_frame_reflower, Dompdf:render() "list-item", "-dompdf-list-bullet"S. //Leave for now //if ($style->min_height < $this->_height ) { // $style->min_height = $this->_height; //} //$style->height = "auto"; }
function __construct(Frame $frame, DOMPDF $dompdf) { if ($frame->get_node()->nodeName !== "#text") { throw new DOMPDF_Exception("Text_Decorator can only be applied to #text nodes."); } parent::__construct($frame, $dompdf); $this->_text_spacing = null; }
function render(Frame $frame) { $style = $frame->get_style(); $node = $frame->get_node(); list($x, $y, $w, $h) = $frame->get_border_box(); $this->_set_opacity( $frame->get_opacity( $style->opacity ) ); if ( $node->nodeName === "body" ) { $h = $frame->get_containing_block("h") - $style->length_in_pt(array( $style->margin_top, $style->border_top_width, $style->border_bottom_width, $style->margin_bottom), $style->width); } // Handle anchors & links if ( $node->nodeName === "a" && $href = $node->getAttribute("href") ) { $this->_canvas->add_link($href, $x, $y, $w, $h); } // Draw our background, border and content list($tl, $tr, $br, $bl) = $style->get_computed_border_radius($w, $h); if ( $tl + $tr + $br + $bl > 0 ) { $this->_canvas->clipping_roundrectangle( $x, $y, $w, $h, $tl, $tr, $br, $bl ); } if ( ($bg = $style->background_color) !== "transparent" ) { $this->_canvas->filled_rectangle( $x, $y, $w, $h, $bg ); } if ( ($url = $style->background_image) && $url !== "none" ) { $this->_background_image($url, $x, $y, $w, $h, $style); } if ( $tl + $tr + $br + $bl > 0 ) { $this->_canvas->clipping_end(); } $border_box = array($x, $y, $w, $h); $this->_render_border($frame, $border_box); $this->_render_outline($frame, $border_box); if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { $this->_debug_layout($frame->get_border_box(), "red"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5)); } } if (DEBUG_LAYOUT && DEBUG_LAYOUT_LINES && $frame->get_decorator()) { foreach ($frame->get_decorator()->get_line_boxes() as $line) { $frame->_debug_layout(array($line->x, $line->y, $line->w, $line->h), "orange"); } } }
function __construct(Frame $frame, DOMPDF $dompdf) { $style = $frame->get_style(); $url = $style->list_style_image; $frame->get_node()->setAttribute("src", $url); $this->_img = new Image_Frame_Decorator($frame, $dompdf); parent::__construct($this->_img, $dompdf); list($width, $height) = dompdf_getimagesize($this->_img->get_image_url()); $this->_width = (double) rtrim($width, "px") * 72 / DOMPDF_DPI; $this->_height = (double) rtrim($height, "px") * 72 / DOMPDF_DPI; }
/** * Class constructor * * @param Frame $frame the bullet frame to decorate * @param DOMPDF $dompdf the document's dompdf object */ function __construct(Frame $frame, DOMPDF $dompdf) { $url = $frame->get_style()->list_style_image; $frame->get_node()->setAttribute("src", $url); $this->_img = new Image_Frame_Decorator($frame, $dompdf); parent::__construct($this->_img, $dompdf); list($width, $height) = getimagesize($this->_img->get_image_url()); // Resample the bullet image to be consistent with 'auto' sized images $this->_width = (double) rtrim($width, "px") * 72 / DOMPDF_DPI; $this->_height = (double) rtrim($height, "px") * 72 / DOMPDF_DPI; }
/** * split this frame at $child. * * The current frame is cloned and $child and all children following * $child are added to the clone. The clone is then passed to the * current frame's parent->split() method. * * @param Frame $child * @param boolean $force_pagebreak */ function split($child = null, $force_pagebreak = false) { if (is_null($child)) { $this->get_parent()->split($this, $force_pagebreak); return; } if ($child->get_parent() !== $this) throw new DOMPDF_Exception("Unable to split: frame is not a child of this one."); $node = $this->_frame->get_node(); // mark the frame as splitted (don't use the find_***_parent cache) //$this->_splitted = true; $split = $this->copy($node->cloneNode()); $split->reset(); $split->get_original_style()->text_indent = 0; // The body's properties must be kept if ($node->nodeName !== "body") { // Style reset on the first and second parts $style = $this->_frame->get_style(); $style->margin_bottom = 0; $style->padding_bottom = 0; $style->border_bottom = 0; // second $orig_style = $split->get_original_style(); $orig_style->text_indent = 0; $orig_style->margin_top = 0; $orig_style->padding_top = 0; $orig_style->border_top = 0; } $this->get_parent()->insert_child_after($split, $this); // Add $frame and all following siblings to the new split node $iter = $child; while ($iter) { $frame = $iter; $iter = $iter->get_next_sibling(); $frame->reset(); $split->append_child($frame); } $this->get_parent()->split($split, $force_pagebreak); }
/** * split this frame at $child. * * The current frame is cloned and $child and all children following * $child are added to the clone. The clone is then passed to the * current frame's parent->split() method. * * @param Frame $child */ function split($child = null) { if (is_null($child)) { $this->get_parent()->split($this); return; } if ($child->get_parent() !== $this) { throw new DOMPDF_Exception("Unable to split: frame is not a child of this one."); } $split = $this->copy($this->_frame->get_node()->cloneNode()); $split->reset(); $this->get_parent()->insert_child_after($split, $this); // Add $frame and all following siblings to the new split node $iter = $child; while ($iter) { $frame = $iter; $iter = $iter->get_next_sibling(); $frame->reset(); $split->append_child($frame); } $this->get_parent()->split($split); }
/** * Determine if a page break is allowed before $frame * * @param Frame $frame the frame to check * @return bool true if a break is allowed, false otherwise */ protected function _page_break_allowed(Frame $frame) { /** * * http://www.w3.org/TR/CSS21/page.html#allowed-page-breaks * /* * In the normal flow, page breaks can occur at the following places: * * 1. In the vertical margin between block boxes. When a page * break occurs here, the used values of the relevant * 'margin-top' and 'margin-bottom' properties are set to '0'. * 2. Between line boxes inside a block box. * * These breaks are subject to the following rules: * * * Rule A: Breaking at (1) is allowed only if the * 'page-break-after' and 'page-break-before' properties of * all the elements generating boxes that meet at this margin * allow it, which is when at least one of them has the value * 'always', 'left', or 'right', or when all of them are * 'auto'. * * * Rule B: However, if all of them are 'auto' and the * nearest common ancestor of all the elements has a * 'page-break-inside' value of 'avoid', then breaking here is * not allowed. * * * Rule C: Breaking at (2) is allowed only if the number of * line boxes between the break and the start of the enclosing * block box is the value of 'orphans' or more, and the number * of line boxes between the break and the end of the box is * the value of 'widows' or more. * * * Rule D: In addition, breaking at (2) is allowed only if * the 'page-break-inside' property is 'auto'. * * If the above doesn't provide enough break points to keep * content from overflowing the page boxes, then rules B and D are * dropped in order to find additional breakpoints. * * If that still does not lead to sufficient break points, rules A * and C are dropped as well, to find still more break points. * * [endquote] * * We will also allow breaks between table rows. However, when * splitting a table, the table headers should carry over to the * next page (but they don't yet). */ $block_types = array("block", "list-item", "table"); // echo "\nbreak_allowed: " . $frame->get_node()->nodeName ."\n"; $display = $frame->get_style()->display; // Block Frames (1): if (in_array($display, $block_types)) { // Avoid breaks within table-cells if ($this->_in_table) { // echo "In table: " . $this->_in_table ."\n"; return false; } // Rules A & B if ($frame->get_style()->page_break_before == "avoid") { // echo "before: avoid\n"; return false; } // Find the preceeding block-level sibling $prev = $frame->get_prev_sibling(); while ($prev && !in_array($prev->get_style()->display, $block_types)) { $prev = $prev->get_prev_sibling(); } // Does the previous element allow a page break after? if ($prev && $prev->get_style()->page_break_after == "avoid") { // echo "after: avoid\n"; return false; } // If both $prev & $frame have the same parent, check the parent's // page_break_inside property. $parent = $frame->get_parent(); if ($prev && $parent && $parent->get_style()->page_break_inside == "avoid") { // echo "parent inside: avoid\n"; return false; } // To prevent cascading page breaks when a top-level element has // page-break-inside: avoid, ensure that at least one frame is // on the page before splitting. if ($parent->get_node()->nodeName == "body" && !$prev) { // We are the body's first child // echo "Body's first child.\n"; return false; } // If the frame is the first block-level frame, use the value from // $frame's parent instead. if (!$prev && $parent) { return $this->_page_break_allowed($parent); } // echo "block: break allowed\n"; return true; } else { if (in_array($display, Style::$INLINE_TYPES)) { // Avoid breaks within table-cells if ($this->_in_table) { // echo "In table: " . $this->_in_table ."\n"; return false; } // Rule C $block_parent = $frame->find_block_parent(); if (count($block_parent->get_lines()) < $frame->get_style()->orphans) { // echo "orphans\n"; return false; } // FIXME: Checking widows is tricky without having laid out the // remaining line boxes. Just ignore it for now... // Rule D if ($block_parent->get_style()->page_break_inside == "avoid") { // echo "parent->inside: avoid\n"; return false; } // To prevent cascading page breaks when a top-level element has // page-break-inside: avoid, ensure that at least one frame with // some content is on the page before splitting. $prev = $frame->get_prev_sibling(); while ($prev && ($prev->get_node()->nodeName == "#text" && trim($prev->get_node()->nodeValue) == "")) { $prev = $prev->get_prev_sibling(); } if ($block_parent->get_node()->nodeName == "body" && !$prev) { // We are the body's first child // echo "Body's first child.\n"; return false; } // Skip breaks on empty text nodes if ($frame->get_node()->nodeName == "#text" && $frame->get_node()->nodeValue == "") { return false; } // echo "inline: break allowed\n"; return true; // Table-rows } else { if ($display == "table-row") { // Simply check if the parent table's page_break_inside property is // not 'avoid' $p = Table_Frame_Decorator::find_parent_table($frame); if ($p->get_style()->page_break_inside == "avoid") { // echo "table: page-break-inside: avoid\n"; return false; } // Check the table's parent element $table_parent = $p->get_parent(); if ($table_parent->get_style()->page_break_inside == "avoid") { // echo "table->parent: page-break-inside: avoid\n"; return false; } // Avoid breaking after the first row of a table if ($p->get_first_child() === $frame) { // echo "table: first-row\n"; return false; } // echo "table-row/row-groups: break allowed\n"; return true; } else { if (in_array($display, Table_Frame_Decorator::$ROW_GROUPS)) { // Disallow breaks at row-groups: only split at row boundaries return false; } else { // echo "? " . $frame->get_style()->display . "\n"; return false; } } } } }
function add_frame_to_line(Frame $frame) { // Handle inline frames (which are effectively wrappers) if ($frame instanceof Inline_Frame_Decorator) { // Add each child of the inline frame to the line individually foreach ($frame->get_children() as $child) { $this->add_frame_to_line($child); } return; } if ($frame->get_margin_width() == 0) { return; } $w = $frame->get_margin_width(); // Debugging code: // pre_r("\nAdding frame to line:"); // pre_r("Me: " . $this->get_node()->nodeName . " (" . (string)$this->get_node() . ")"); // pre_r("Node: " . $frame->get_node()->nodeName . " (" . (string)$frame->get_node() . ")"); // if ( $frame->get_node()->nodeName == "#text" ) // pre_r($frame->get_node()->nodeValue); // pre_r("Line width: " . $this->_lines[$this->_cl]["w"]); // pre_r("Frame width: " . $w); // pre_r("Frame height: " . $frame->get_margin_height()); // pre_r("Containing block width: " . $this->get_containing_block("w")); // End debugging if ($this->_lines[$this->_cl]["w"] + $w >= $this->get_containing_block("w")) { $this->add_line(); } $frame->position(); $this->_lines[$this->_cl]["frames"][] = $frame; if ($frame->get_node()->nodeName == "#text") { $this->_lines[$this->_cl]["wc"] += count(preg_split("/\\s+/", $frame->get_text())); } $this->_lines[$this->_cl]["w"] += $w; $this->_lines[$this->_cl]["h"] = max($this->_lines[$this->_cl]["h"], $frame->get_margin_height()); }
function add_frame_to_line(Frame $frame) { // Handle inline frames (which are effectively wrappers) if ($frame instanceof Inline_Frame_Decorator) { // Handle line breaks if ($frame->get_node()->nodeName == "br") { $this->maximize_line_height($frame->get_style()->length_in_pt($frame->get_style()->line_height)); $this->add_line(); return; } // Add each child of the inline frame to the line individually foreach ($frame->get_children() as $child) { $this->add_frame_to_line($child); } return; } // Trim leading text if this is an empty line. Kinda a hack to put it here, // but what can you do... if ($this->_lines[$this->_cl]["w"] == 0 && $frame->get_node()->nodeName == "#text" && ($frame->get_style()->white_space != "pre" || $frame->get_style()->white_space != "pre-wrap")) { $frame->set_text(ltrim($frame->get_text())); $frame->recalculate_width(); } $w = $frame->get_margin_width(); if ($w == 0) { return; } // Debugging code: /* pre_r("\nAdding frame to line:"); // pre_r("Me: " . $this->get_node()->nodeName . " (" . (string)$this->get_node() . ")"); // pre_r("Node: " . $frame->get_node()->nodeName . " (" . (string)$frame->get_node() . ")"); if ( $frame->get_node()->nodeName == "#text" ) pre_r($frame->get_node()->nodeValue); pre_r("Line width: " . $this->_lines[$this->_cl]["w"]); pre_r("Frame: " . get_class($frame)); pre_r("Frame width: " . $w); pre_r("Frame height: " . $frame->get_margin_height()); pre_r("Containing block width: " . $this->get_containing_block("w")); */ // End debugging if ($this->_lines[$this->_cl]["w"] + $w > $this->get_containing_block("w")) { $this->add_line(); } $frame->position(); $this->_lines[$this->_cl]["frames"][] = $frame; if ($frame->get_node()->nodeName == "#text") { $this->_lines[$this->_cl]["wc"] += count(preg_split("/\\s+/", $frame->get_text())); } $this->_lines[$this->_cl]["w"] += $w; $this->_lines[$this->_cl]["h"] = max($this->_lines[$this->_cl]["h"], $frame->get_margin_height()); }
/** * Decorate a Frame * * @param $root Frame The frame to decorate * @param $dompdf DOMPDF The dompdf instance * @return Frame_Decorator * FIXME-notes: this is admittedly a little smelly... */ static function decorate_frame(Frame $frame, DOMPDF $dompdf) { if (is_null($dompdf)) { throw new Exception("foo"); } $style = $frame->get_style(); switch ($style->display) { case "block": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "inline-block": $positioner = "Inline"; $decorator = "Block"; $reflower = "Block"; break; case "inline": $positioner = "Inline"; if ($frame->is_text_node()) { $decorator = "Text"; $reflower = "Text"; } else { if (DOMPDF_ENABLE_CSS_FLOAT && $style->float !== "none") { $decorator = "Block"; $reflower = "Block"; } else { $decorator = "Inline"; $reflower = "Inline"; } } break; case "table": $positioner = "Block"; $decorator = "Table"; $reflower = "Table"; break; case "inline-table": $positioner = "Inline"; $decorator = "Table"; $reflower = "Table"; break; case "table-row-group": case "table-header-group": case "table-footer-group": $positioner = "Null"; $decorator = "Table_Row_Group"; $reflower = "Table_Row_Group"; break; case "table-row": $positioner = "Null"; $decorator = "Table_Row"; $reflower = "Table_Row"; break; case "table-cell": $positioner = "Table_Cell"; $decorator = "Table_Cell"; $reflower = "Table_Cell"; break; case "list-item": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "-dompdf-list-bullet": if ($style->list_style_position === "inside") { $positioner = "Inline"; } else { $positioner = "List_Bullet"; } if ($style->list_style_image !== "none") { $decorator = "List_Bullet_Image"; } else { $decorator = "List_Bullet"; } $reflower = "List_Bullet"; break; case "-dompdf-image": $positioner = "Inline"; $decorator = "Image"; $reflower = "Image"; break; case "-dompdf-br": $positioner = "Inline"; $decorator = "Inline"; $reflower = "Inline"; break; default: // Fixme-note: should throw some sort of warning or something? // Fixme-note: should throw some sort of warning or something? case "none": $positioner = "Null"; $decorator = "Null"; $reflower = "Null"; break; } // Handle CSS position $position = $style->position; if ($position === "absolute") { $positioner = "Absolute"; } else { if ($position === "fixed") { $positioner = "Fixed"; } } // Handle nodeName $node_name = $frame->get_node()->nodeName; if ($node_name === "img") { $style->display = "-dompdf-image"; $decorator = "Image"; $reflower = "Image"; } $positioner .= "_Positioner"; $decorator .= "_Frame_Decorator"; $reflower .= "_Frame_Reflower"; $deco = new $decorator($frame, $dompdf); $deco->set_positioner(new $positioner($deco)); $reflow = new $reflower($deco); $deco->set_reflower($reflow); return $deco; }
function render(Frame $frame) { $style = $frame->get_style(); if (!$frame->get_first_child()) { return; } $bp = $style->get_border_properties(); $widths = array($style->length_in_pt($bp["top"]["width"]), $style->length_in_pt($bp["right"]["width"]), $style->length_in_pt($bp["bottom"]["width"]), $style->length_in_pt($bp["left"]["width"])); list($x, $y) = $frame->get_first_child()->get_position(); $w = null; $h = 0; $this->_set_opacity($frame->get_opacity($style->opacity)); $first_row = true; foreach ($frame->get_children() as $child) { list($child_x, $child_y, $child_w, $child_h) = $child->get_padding_box(); if (!is_null($w) && $child_x < $x + $w) { if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } if ($first_row) { if ($bp["left"]["style"] !== "none" && $bp["left"]["color"] !== "transparent" && $bp["left"]["width"] > 0) { $method = "_border_" . $bp["left"]["style"]; $this->{$method}($x, $y, $h + $widths[0] + $widths[2], $bp["left"]["color"], $widths, "left"); } $first_row = false; } if ($bp["top"]["style"] !== "none" && $bp["top"]["color"] !== "transparent" && $bp["top"]["width"] > 0) { $method = "_border_" . $bp["top"]["style"]; $this->{$method}($x, $y, $w + $widths[1] + $widths[3], $bp["top"]["color"], $widths, "top"); } if ($bp["bottom"]["style"] !== "none" && $bp["bottom"]["color"] !== "transparent" && $bp["bottom"]["width"] > 0) { $method = "_border_" . $bp["bottom"]["style"]; $this->{$method}($x, $y + $h + $widths[0] + $widths[2], $w + $widths[1] + $widths[3], $bp["bottom"]["color"], $widths, "bottom"); } $link_node = null; if ($frame->get_node()->nodeName === "a") { $link_node = $frame->get_node(); } else { if ($frame->get_parent()->get_node()->nodeName === "a") { $link_node = $frame->get_parent()->get_node(); } } if ($link_node && ($href = $link_node->getAttribute("href"))) { $this->_canvas->add_link($href, $x, $y, $w, $h); } $x = $child_x; $y = $child_y; $w = $child_w; $h = $child_h; continue; } if (is_null($w)) { $w = $child_w; } else { $w += $child_w; } $h = max($h, $child_h); } if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x + $widths[3], $y + $widths[0], $w, $h, $bg); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x + $widths[3], $y + $widths[0], $w, $h, $style); } $w += $widths[1] + $widths[3]; $h += $widths[0] + $widths[2]; $left_margin = $style->length_in_pt($style->margin_left); $x += $left_margin; if ($first_row && $bp["left"]["style"] !== "none" && $bp["left"]["color"] !== "transparent" && $widths[3] > 0) { $method = "_border_" . $bp["left"]["style"]; $this->{$method}($x, $y, $h, $bp["left"]["color"], $widths, "left"); } if ($bp["top"]["style"] !== "none" && $bp["top"]["color"] !== "transparent" && $widths[0] > 0) { $method = "_border_" . $bp["top"]["style"]; $this->{$method}($x, $y, $w, $bp["top"]["color"], $widths, "top"); } if ($bp["bottom"]["style"] !== "none" && $bp["bottom"]["color"] !== "transparent" && $widths[2] > 0) { $method = "_border_" . $bp["bottom"]["style"]; $this->{$method}($x, $y + $h, $w, $bp["bottom"]["color"], $widths, "bottom"); } if ($bp["right"]["style"] !== "none" && $bp["right"]["color"] !== "transparent" && $widths[1] > 0) { $method = "_border_" . $bp["right"]["style"]; $this->{$method}($x + $w, $y, $h, $bp["right"]["color"], $widths, "right"); } $link_node = null; if ($frame->get_node()->nodeName === "a") { $link_node = $frame->get_node(); if (($name = $link_node->getAttribute("name")) || ($name = $link_node->getAttribute("id"))) { $this->_canvas->add_named_dest($name); } } if ($frame->get_parent() && $frame->get_parent()->get_node()->nodeName === "a") { $link_node = $frame->get_parent()->get_node(); } if ($link_node) { if ($href = $link_node->getAttribute("href")) { $this->_canvas->add_link($href, $x, $y, $w, $h); } } if (DEBUG_LAYOUT && DEBUG_LAYOUT_INLINE) { $this->_debug_layout($child->get_border_box(), "blue"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($child->get_padding_box(), "blue", array(0.5, 0.5)); } } }
function render(Frame $frame) { $style = $frame->get_style(); $font_size = $style->get_font_size(); $line_height = $style->length_in_pt($style->line_height, $frame->get_containing_block("w")); $this->_set_opacity($frame->get_opacity($style->opacity)); // Handle list-style-image // If list style image is requested but missing, fall back to predefined types if ($style->list_style_image !== "none" && strcmp($img = $frame->get_image_url(), DOMPDF_LIB_DIR . "/res/broken_image.png") != 0) { list($x, $y) = $frame->get_position(); //For expected size and aspect, instead of box size, use image natural size scaled to DPI. // Resample the bullet image to be consistent with 'auto' sized images // See also Image_Frame_Reflower::get_min_max_width // Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary. //$w = $frame->get_width(); //$h = $frame->get_height(); list($width, $height) = dompdf_getimagesize($img); $w = (double) rtrim($width, "px") * 72 / DOMPDF_DPI; $h = (double) rtrim($height, "px") * 72 / DOMPDF_DPI; $x -= $w; $y -= ($line_height - $font_size) / 2; //Reverse hinting of list_bullet_positioner $this->_canvas->image($img, $frame->get_image_ext(), $x, $y, $w, $h); } else { $bullet_style = $style->list_style_type; $fill = false; switch ($bullet_style) { default: case "disc": $fill = true; case "circle": list($x, $y) = $frame->get_position(); $r = $font_size * List_Bullet_Frame_Decorator::BULLET_SIZE / 2; $x -= $font_size * (List_Bullet_Frame_Decorator::BULLET_SIZE / 2); $y += $font_size * (1 - List_Bullet_Frame_Decorator::BULLET_DESCENT) / 2; $o = $font_size * List_Bullet_Frame_Decorator::BULLET_THICKNESS; $this->_canvas->circle($x, $y, $r, $style->color, $o, null, $fill); break; case "square": list($x, $y) = $frame->get_position(); $w = $font_size * List_Bullet_Frame_Decorator::BULLET_SIZE; $x -= $w; $y += $font_size * (1 - List_Bullet_Frame_Decorator::BULLET_DESCENT - List_Bullet_Frame_Decorator::BULLET_SIZE) / 2; $this->_canvas->filled_rectangle($x, $y, $w, $w, $style->color); break; case "decimal-leading-zero": case "decimal": case "lower-alpha": case "lower-latin": case "lower-roman": case "lower-greek": case "upper-alpha": case "upper-latin": case "upper-roman": case "1": // HTML 4.0 compatibility // HTML 4.0 compatibility case "a": case "i": case "A": case "I": list($x, $y) = $frame->get_position(); $pad = null; if ($bullet_style === "decimal-leading-zero") { $pad = strlen($frame->get_parent()->get_parent()->get_node()->getAttribute("dompdf-children-count")); } $index = $frame->get_node()->getAttribute("dompdf-counter"); $text = $this->make_counter($index, $bullet_style, $pad); $font_family = $style->font_family; $spacing = 0; //$frame->get_text_spacing() + $style->word_spacing; if (trim($text) == "") { return; } $x -= Font_Metrics::get_text_width($text, $font_family, $font_size, $spacing); $this->_canvas->text($x, $y, $text, $font_family, $font_size, $style->color, $spacing); case "none": break; } } }
function render(Frame $frame) { $style = $frame->get_style(); if (trim($frame->get_node()->nodeValue) === "" && $style->empty_cells === "hide") { return; } $this->_set_opacity($frame->get_opacity($style->opacity)); list($x, $y, $w, $h) = $frame->get_border_box(); // Draw our background, border and content if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } $table = Table_Frame_Decorator::find_parent_table($frame); if ($table->get_style()->border_collapse !== "collapse") { $this->_render_border($frame); $this->_render_outline($frame); return; } // The collapsed case is slightly complicated... // @todo Add support for outlines here $cellmap = $table->get_cellmap(); $cells = $cellmap->get_spanned_cells($frame); $num_rows = $cellmap->get_num_rows(); $num_cols = $cellmap->get_num_cols(); // Determine the top row spanned by this cell $i = $cells["rows"][0]; $top_row = $cellmap->get_row($i); // Determine if this cell borders on the bottom of the table. If so, // then we draw its bottom border. Otherwise the next row down will // draw its top border instead. if (in_array($num_rows - 1, $cells["rows"])) { $draw_bottom = true; $bottom_row = $cellmap->get_row($num_rows - 1); } else { $draw_bottom = false; } // Draw the horizontal borders foreach ($cells["columns"] as $j) { $bp = $cellmap->get_border_properties($i, $j); $y = $top_row["y"] - $bp["top"]["width"] / 2; $col = $cellmap->get_column($j); $x = $col["x"] - $bp["left"]["width"] / 2; $w = $col["used-width"] + ($bp["left"]["width"] + $bp["right"]["width"]) / 2; if ($bp["top"]["style"] !== "none" && $bp["top"]["width"] > 0) { $widths = array($bp["top"]["width"], $bp["right"]["width"], $bp["bottom"]["width"], $bp["left"]["width"]); $method = "_border_" . $bp["top"]["style"]; $this->{$method}($x, $y, $w, $bp["top"]["color"], $widths, "top", "square"); } if ($draw_bottom) { $bp = $cellmap->get_border_properties($num_rows - 1, $j); if ($bp["bottom"]["style"] === "none" || $bp["bottom"]["width"] <= 0) { continue; } $y = $bottom_row["y"] + $bottom_row["height"] + $bp["bottom"]["width"] / 2; $widths = array($bp["top"]["width"], $bp["right"]["width"], $bp["bottom"]["width"], $bp["left"]["width"]); $method = "_border_" . $bp["bottom"]["style"]; $this->{$method}($x, $y, $w, $bp["bottom"]["color"], $widths, "bottom", "square"); } } $j = $cells["columns"][0]; $left_col = $cellmap->get_column($j); if (in_array($num_cols - 1, $cells["columns"])) { $draw_right = true; $right_col = $cellmap->get_column($num_cols - 1); } else { $draw_right = false; } // Draw the vertical borders foreach ($cells["rows"] as $i) { $bp = $cellmap->get_border_properties($i, $j); $x = $left_col["x"] - $bp["left"]["width"] / 2; $row = $cellmap->get_row($i); $y = $row["y"] - $bp["top"]["width"] / 2; $h = $row["height"] + ($bp["top"]["width"] + $bp["bottom"]["width"]) / 2; if ($bp["left"]["style"] !== "none" && $bp["left"]["width"] > 0) { $widths = array($bp["top"]["width"], $bp["right"]["width"], $bp["bottom"]["width"], $bp["left"]["width"]); $method = "_border_" . $bp["left"]["style"]; $this->{$method}($x, $y, $h, $bp["left"]["color"], $widths, "left", "square"); } if ($draw_right) { $bp = $cellmap->get_border_properties($i, $num_cols - 1); if ($bp["right"]["style"] === "none" || $bp["right"]["width"] <= 0) { continue; } $x = $right_col["x"] + $right_col["used-width"] + $bp["right"]["width"] / 2; $widths = array($bp["top"]["width"], $bp["right"]["width"], $bp["bottom"]["width"], $bp["left"]["width"]); $method = "_border_" . $bp["right"]["style"]; $this->{$method}($x, $y, $h, $bp["right"]["color"], $widths, "right", "square"); } } }
function render(Frame $frame) { $style = $frame->get_style(); if (!$frame->get_first_child()) { return; } // No children, no service // Draw the left border if applicable $bp = $style->get_border_properties(); $widths = array($style->length_in_pt($bp["top"]["width"]), $style->length_in_pt($bp["right"]["width"]), $style->length_in_pt($bp["bottom"]["width"]), $style->length_in_pt($bp["left"]["width"])); // Draw the background & border behind each child. To do this we need // to figure out just how much space each child takes: list($x, $y) = $frame->get_first_child()->get_position(); $w = null; $h = 0; // $x += $widths[3]; // $y += $widths[0]; $this->_set_opacity($frame->get_opacity($style->opacity)); $first_row = true; foreach ($frame->get_children() as $child) { list($child_x, $child_y, $child_w, $child_h) = $child->get_padding_box(); if (!is_null($w) && $child_x < $x + $w) { //This branch seems to be supposed to being called on the first part //of an inline html element, and the part after the if clause for the //parts after a line break. //But because $w initially mostly is 0, and gets updated only on the next //round, this seem to be never executed and the common close always. // The next child is on another line. Draw the background & // borders on this line. // Background: if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } // If this is the first row, draw the left border if ($first_row) { if ($bp["left"]["style"] !== "none" && $bp["left"]["color"] !== "transparent" && $bp["left"]["width"] > 0) { $method = "_border_" . $bp["left"]["style"]; $this->{$method}($x, $y, $h + $widths[0] + $widths[2], $bp["left"]["color"], $widths, "left"); } $first_row = false; } // Draw the top & bottom borders if ($bp["top"]["style"] !== "none" && $bp["top"]["color"] !== "transparent" && $bp["top"]["width"] > 0) { $method = "_border_" . $bp["top"]["style"]; $this->{$method}($x, $y, $w + $widths[1] + $widths[3], $bp["top"]["color"], $widths, "top"); } if ($bp["bottom"]["style"] !== "none" && $bp["bottom"]["color"] !== "transparent" && $bp["bottom"]["width"] > 0) { $method = "_border_" . $bp["bottom"]["style"]; $this->{$method}($x, $y + $h + $widths[0] + $widths[2], $w + $widths[1] + $widths[3], $bp["bottom"]["color"], $widths, "bottom"); } // Handle anchors & links $link_node = null; if ($frame->get_node()->nodeName === "a") { $link_node = $frame->get_node(); } else { if ($frame->get_parent()->get_node()->nodeName === "a") { $link_node = $frame->get_parent()->get_node(); } } if ($link_node && ($href = $link_node->getAttribute("href"))) { $this->_canvas->add_link($href, $x, $y, $w, $h); } $x = $child_x; $y = $child_y; $w = $child_w; $h = $child_h; continue; } if (is_null($w)) { $w = $child_w; } else { $w += $child_w; } $h = max($h, $child_h); if (DEBUG_LAYOUT && DEBUG_LAYOUT_INLINE) { $this->_debug_layout($child->get_border_box(), "blue"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($child->get_padding_box(), "blue", array(0.5, 0.5)); } } } // Handle the last child if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x + $widths[3], $y + $widths[0], $w, $h, $bg); } //On continuation lines (after line break) of inline elements, the style got copied. //But a non repeatable background image should not be repeated on the next line. //But removing the background image above has never an effect, and removing it below //removes it always, even on the initial line. //Need to handle it elsewhere, e.g. on certain ...clone()... usages. // Repeat not given: default is Style::__construct // ... && (!($repeat = $style->background_repeat) || $repeat === "repeat" ... //different position? $this->_background_image($url, $x, $y, $w, $h, $style); if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x + $widths[3], $y + $widths[0], $w, $h, $style); } // Add the border widths $w += $widths[1] + $widths[3]; $h += $widths[0] + $widths[2]; // make sure the border and background start inside the left margin $left_margin = $style->length_in_pt($style->margin_left); $x += $left_margin; // If this is the first row, draw the left border too if ($first_row && $bp["left"]["style"] !== "none" && $bp["left"]["color"] !== "transparent" && $widths[3] > 0) { $method = "_border_" . $bp["left"]["style"]; $this->{$method}($x, $y, $h, $bp["left"]["color"], $widths, "left"); } // Draw the top & bottom borders if ($bp["top"]["style"] !== "none" && $bp["top"]["color"] !== "transparent" && $widths[0] > 0) { $method = "_border_" . $bp["top"]["style"]; $this->{$method}($x, $y, $w, $bp["top"]["color"], $widths, "top"); } if ($bp["bottom"]["style"] !== "none" && $bp["bottom"]["color"] !== "transparent" && $widths[2] > 0) { $method = "_border_" . $bp["bottom"]["style"]; $this->{$method}($x, $y + $h, $w, $bp["bottom"]["color"], $widths, "bottom"); } // pre_var_dump(get_class($frame->get_next_sibling())); // $last_row = get_class($frame->get_next_sibling()) !== 'Inline_Frame_Decorator'; // Draw the right border if this is the last row if ($bp["right"]["style"] !== "none" && $bp["right"]["color"] !== "transparent" && $widths[1] > 0) { $method = "_border_" . $bp["right"]["style"]; $this->{$method}($x + $w, $y, $h, $bp["right"]["color"], $widths, "right"); } // Only two levels of links frames $link_node = null; if ($frame->get_node()->nodeName === "a") { $link_node = $frame->get_node(); if (($name = $link_node->getAttribute("name")) || ($name = $link_node->getAttribute("id"))) { $this->_canvas->add_named_dest($name); } } if ($frame->get_parent() && $frame->get_parent()->get_node()->nodeName === "a") { $link_node = $frame->get_parent()->get_node(); } // Handle anchors & links if ($link_node) { if ($href = $link_node->getAttribute("href")) { $this->_canvas->add_link($href, $x, $y, $w, $h); } } }
function render(Frame $frame) { $style = $frame->get_style(); if (!$frame->get_first_child()) { return; } // No children, no service // Draw the left border if applicable $bp = $style->get_border_properties(); $widths = array($style->length_in_pt($bp["top"]["width"]), $style->length_in_pt($bp["right"]["width"]), $style->length_in_pt($bp["bottom"]["width"]), $style->length_in_pt($bp["left"]["width"])); // Draw the background & border behind each child. To do this we need // to figure out just how much space each child takes: list($x, $y) = $frame->get_first_child()->get_position(); $w = null; $h = 0; // $x += $widths[3]; // $y += $widths[0]; $first_row = true; foreach ($frame->get_children() as $child) { list($child_x, $child_y, $child_w, $child_h) = $child->get_padding_box(); $child_h += $widths[2]; if (!is_null($w) && $child_x < $x + $w) { // The next child is on another line. Draw the background & // borders on this line. // Background: if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $style->background_color); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } // If this is the first row, draw the left border if ($first_row) { if ($bp["left"]["style"] != "none" && $bp["left"]["width"] > 0) { $method = "_border_" . $bp["left"]["style"]; $this->{$method}($x, $y, $h + $widths[0] + $widths[2], $bp["left"]["color"], $widths, "left"); } $first_row = false; } // Draw the top & bottom borders if ($bp["top"]["style"] != "none" && $bp["top"]["width"] > 0) { $method = "_border_" . $bp["top"]["style"]; $this->{$method}($x, $y, $w + $widths[1] + $widths[3], $bp["top"]["color"], $widths, "top"); } if ($bp["bottom"]["style"] != "none" && $bp["bottom"]["width"] > 0) { $method = "_border_" . $bp["bottom"]["style"]; $this->{$method}($x, $y + $h + $widths[0] + $widths[2], $w + $widths[1] + $widths[3], $bp["bottom"]["color"], $widths, "bottom"); } // Handle anchors & links if ($frame->get_node()->nodeName == "a") { if ($href = $frame->get_node()->getAttribute("href")) { $this->_canvas->add_link($href, $x, $y, $w, $h); } } $x = $child_x; $y = $child_y; $w = $child_w; $h = $child_h; continue; } if (is_null($w)) { $w = $child_w; } else { $w += $child_w; } $h = max($h, $child_h); } // Handle the last child if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x + $widths[3], $y + $widths[0], $w, $h, $style->background_color); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x + $widths[3], $y + $widths[0], $w, $h, $style); } // Add the border widths $w += $widths[1] + $widths[3]; $h += $widths[0] + $widths[2]; // If this is the first row, draw the left border too if ($first_row && $bp["left"]["style"] != "none" && $widths[3] > 0) { $method = "_border_" . $bp["left"]["style"]; $this->{$method}($x, $y, $h, $bp["left"]["color"], $widths, "left"); } // Draw the top & bottom borders if ($bp["top"]["style"] != "none" && $widths[0] > 0) { $method = "_border_" . $bp["top"]["style"]; $this->{$method}($x, $y, $w, $bp["top"]["color"], $widths, "top"); } if ($bp["bottom"]["style"] != "none" && $widths[2] > 0) { $method = "_border_" . $bp["bottom"]["style"]; $this->{$method}($x, $y + $h, $w, $bp["bottom"]["color"], $widths, "bottom"); } // Draw the right border if ($bp["right"]["style"] != "none" && $widths[1] > 0) { $method = "_border_" . $bp["right"]["style"]; $this->{$method}($x + $w, $y, $h, $bp["right"]["color"], $widths, "right"); } // Handle anchors & links if ($frame->get_node()->nodeName == "a") { if ($name = $frame->get_node()->getAttribute("name")) { $this->_canvas->add_named_dest($name); } if ($href = $frame->get_node()->getAttribute("href")) { $this->_canvas->add_link($href, $x, $y, $w, $h); } } }
/** * split this frame at $child. * The current frame is cloned and $child and all children following * $child are added to the clone. The clone is then passed to the * current frame's parent->split() method. * * @param Frame $child * @param boolean $force_pagebreak * * @throws DOMPDF_Exception * @return void */ function split(Frame $child = null, $force_pagebreak = false) { // decrement any counters that were incremented on the current node, unless that node is the body $style = $this->_frame->get_style(); if ($this->_frame->get_node()->nodeName !== "body" && $style->counter_increment && ($decrement = $style->counter_increment) !== "none") { $this->decrement_counters($decrement); } if (is_null($child)) { // check for counter increment on :before content (always a child of the selected element @link Frame_Reflower::_set_content) // this can push the current node to the next page before counter rules have bubbled up (but only if // it's been rendered, thus the position check) if (!$this->is_text_node() && $this->get_node()->hasAttribute("dompdf_before_frame_id")) { foreach ($this->_frame->get_children() as $child) { if ($this->get_node()->getAttribute("dompdf_before_frame_id") == $child->get_id() && $child->get_position('x') !== NULL) { $style = $child->get_style(); if ($style->counter_increment && ($decrement = $style->counter_increment) !== "none") { $this->decrement_counters($decrement); } } } } $this->get_parent()->split($this, $force_pagebreak); return; } if ($child->get_parent() !== $this) { throw new DOMPDF_Exception("Unable to split: frame is not a child of this one."); } $node = $this->_frame->get_node(); $split = $this->copy($node->cloneNode()); $split->reset(); $split->get_original_style()->text_indent = 0; $split->_splitted = true; // The body's properties must be kept if ($node->nodeName !== "body") { // Style reset on the first and second parts $style = $this->_frame->get_style(); $style->margin_bottom = 0; $style->padding_bottom = 0; $style->border_bottom = 0; // second $orig_style = $split->get_original_style(); $orig_style->text_indent = 0; $orig_style->margin_top = 0; $orig_style->padding_top = 0; $orig_style->border_top = 0; } $this->get_parent()->insert_child_after($split, $this); // Add $frame and all following siblings to the new split node $iter = $child; while ($iter) { $frame = $iter; $iter = $iter->get_next_sibling(); $frame->reset(); $split->append_child($frame); } $this->get_parent()->split($split, $force_pagebreak); // If this node resets a counter save the current value to use when rendering on the next page if ($style->counter_reset && ($reset = $style->counter_reset) !== "none") { $vars = preg_split('/\\s+/', trim($reset), 2); $split->_counters['__' . $vars[0]] = $this->lookup_counter_frame($vars[0])->_counters[$vars[0]]; } }
/** * Render frames recursively * * @param Frame $frame the frame to render */ private function _render(Frame $frame, $depth = 0) { //$sRet = ''; $this->_level = $depth; $node = $frame->get_node(); $properties = $frame->get_style(); $this->countTags = array_count_values(self::$openTags); switch ($node->nodeName) { case 'p': case 'div': self::$WordML .= $this->closePreviousTags($depth, $node->nodeName); if (self::$openPs) { self::$WordML .= '</w:p><w:p>'; } else { self::$WordML .= '<w:p>'; self::$openPs = true; } self::$WordML .= $this->generatePPr($properties); self::$openTags[$depth] = $node->nodeName; break; case 'ul': case 'ol': self::$openTags[$depth] = $node->nodeName; break; case 'li': self::$WordML .= $this->closePreviousTags($depth, $node->nodeName); if (self::$openPs) { self::$WordML .= '</w:p><w:p>'; } else { self::$WordML .= '<w:p>'; self::$openPs = true; } self::$WordML .= $this->generateListPr($properties); self::$openTags[$depth] = $node->nodeName; break; case 'table': self::$openTable = array(); //TODO cambiar para tablas anidadas self::$WordML .= $this->closePreviousTags($depth, $node->nodeName); if (self::$openPs) { self::$WordML .= '</w:p><w:tbl>'; self::$openPs = false; } else { self::$WordML .= '<w:tbl>'; } self::$WordML .= $this->generateTblPr($properties); self::$openTags[$depth] = $node->nodeName; break; case 'tr': self::$WordML .= $this->closePreviousTags($depth, $node->nodeName); array_push(self::$openTable, array()); self::$WordML .= '<w:tr>'; self::$WordML .= $this->generateTrPr($properties); self::$openTags[$depth] = $node->nodeName; break; case 'td': self::$WordML .= $this->closePreviousTags($depth, $node->nodeName); $firstRow = false; //Now we have to deal with the details associated to the rowspan and colspan of this table cell $colspan = (int) $node->getAttribute('colspan'); $colspan = empty($colspan) ? 1 : $colspan; $rowspan = (int) $node->getAttribute('rowspan'); $rowspan = empty($rowspan) ? 1 : $rowspan; $row = count(self::$openTable) - 1; $column = count(self::$openTable[$row]); $vmerge = $this->countEmptyColumns($row, $column); for ($k = 0; $k < $colspan; $k++) { array_push(self::$openTable[count(self::$openTable) - 1], $rowspan); } if ($vmerge > 0) { self::$WordML .= '<w:tc><w:tcPr><w:gridSpan w:val="' . $vmerge . '" /><w:vMerge w:val="continue" /></w:tcPr><w:p /></w:tc>'; } self::$WordML .= '<w:tc>'; self::$WordML .= $this->generateTcPr($properties, $colspan, $rowspan, $firstRow); //FIXME self::$openTags[$depth] = $node->nodeName; break; case 'th': self::$WordML .= $this->closePreviousTags($depth, $node->nodeName); $firstRow = true; //Now we have to deal with the details associated to the rowspan and colspan of this table cell $colspan = (int) $node->getAttribute('colspan'); $colspan = empty($colspan) ? 1 : $colspan; $rowspan = (int) $node->getAttribute('rowspan'); $rowspan = empty($rowspan) ? 1 : $rowspan; $row = count(self::$openTable) - 1; $column = count(self::$openTable[$row]); $vmerge = $this->countEmptyColumns($row, $column); for ($k = 0; $k < $colspan; $k++) { array_push(self::$openTable[count(self::$openTable) - 1], $rowspan); } if ($vmerge > 0) { self::$WordML .= '<w:tc><w:tcPr><w:gridSpan w:val="' . $vmerge . '" /><w:vMerge w:val="continue" /></w:tcPr><w:p /></w:tc>'; } self::$WordML .= '<w:tc>'; self::$WordML .= $this->generateTcPr($properties, $colspan, $rowspan, $firstRow); //FIXME self::$openTags[$depth] = $node->nodeName; break; case '#text': self::$WordML .= $this->closePreviousTags($depth, $node->nodeName); if (self::$openPs) { self::$WordML .= '<w:r>'; } else { self::$WordML .= '<w:p>'; self::$WordML .= $this->generatePPr($properties); self::$WordML .= '<w:r>'; self::$openPs = true; } self::$WordML .= $this->generateRPr($properties); self::$WordML .= '<w:t>' . htmlspecialchars($node->nodeValue); self::$WordML .= '</w:t></w:r>'; self::$openTags[$depth] = $node->nodeName; break; case 'br': self::$WordML .= $this->closePreviousTags($depth, $node->nodeName); if (self::$openPs) { self::$WordML .= '<w:r><w:br /><w:t></w:t></w:r>'; } else { self::$WordML .= '<w:p />'; } break; case 'close': //if(strpos(self::$WordML, '#<w:gridCol/>#') !== false) self::$WordML = str_replace('#<w:gridCol/>#', '<w:gridCol/>', self::$WordML); //TODO forzar limpieza de columnas de tabla? self::$WordML .= $this->closePreviousTags($depth, $node->nodeName); break; default: self::$openTags[$depth] = $node->nodeName; break; } ++$depth; foreach ($frame->get_children() as $child) { //var_dump(gettype($child)); $this->_render($child, $depth); } }
function add_frame_to_line(Frame $frame) { $style = $frame->get_style(); if (in_array($style->position, array("absolute", "fixed")) || DOMPDF_ENABLE_CSS_FLOAT && $style->float !== "none") { return; } $frame->set_containing_line($this->_lines[$this->_cl]); /* // Adds a new line after a block, only if certain conditions are met if ((($frame instanceof Inline_Frame_Decorator && $frame->get_node()->nodeName !== "br") || $frame instanceof Text_Frame_Decorator && trim($frame->get_text())) && ($frame->get_prev_sibling() && $frame->get_prev_sibling()->get_style()->display === "block" && $this->_lines[$this->_cl]["w"] > 0 )) { $this->maximize_line_height( $style->length_in_pt($style->line_height), $frame ); $this->add_line(); // Add each child of the inline frame to the line individually foreach ($frame->get_children() as $child) $this->add_frame_to_line( $child ); } else*/ // Handle inline frames (which are effectively wrappers) if ($frame instanceof Inline_Frame_Decorator) { // Handle line breaks if ($frame->get_node()->nodeName === "br") { $this->maximize_line_height($style->length_in_pt($style->line_height), $frame); $this->add_line(true); } return; } // Trim leading text if this is an empty line. Kinda a hack to put it here, // but what can you do... if ($this->_lines[$this->_cl]["w"] == 0 && $frame->get_node()->nodeName === "#text" && ($style->white_space !== "pre" || $style->white_space !== "pre-wrap")) { $frame->set_text(ltrim($frame->get_text())); $frame->recalculate_width(); } $w = $frame->get_margin_width(); if ($w == 0) { return; } // Debugging code: /* pre_r("\n<h3>Adding frame to line:</h3>"); // pre_r("Me: " . $this->get_node()->nodeName . " (" . spl_object_hash($this->get_node()) . ")"); // pre_r("Node: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")"); if ( $frame->get_node()->nodeName === "#text" ) pre_r('"'.$frame->get_node()->nodeValue.'"'); pre_r("Line width: " . $this->_lines[$this->_cl]["w"]); pre_r("Frame: " . get_class($frame)); pre_r("Frame width: " . $w); pre_r("Frame height: " . $frame->get_margin_height()); pre_r("Containing block width: " . $this->get_containing_block("w")); */ // End debugging $line = $this->_lines[$this->_cl]; if ($line["left"] + $line["w"] + $line["right"] + $w > $this->get_containing_block("w")) { $this->add_line(); } $frame->position(); $current_line =& $this->_lines[$this->_cl]; $current_line["frames"][] = $frame; if ($frame->get_node()->nodeName === "#text") { $current_line["wc"] += count(preg_split("/\\s+/", trim($frame->get_text()))); } $this->increase_line_width($w); $this->maximize_line_height($frame->get_margin_height(), $frame); }
function render(Frame $frame) { $this->evaluate($frame->get_node()->nodeValue); }
static function decorate_frame(Frame $frame, $dompdf) { if (is_null($dompdf)) { throw new Exception("foo"); } switch ($frame->get_style()->display) { case "block": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "inline-block": $positioner = "Inline"; $decorator = "Block"; $reflower = "Block"; break; case "inline": $positioner = "Inline"; if ($frame->get_node()->nodeName == "#text") { $decorator = "Text"; $reflower = "Text"; } else { $decorator = "Inline"; $reflower = "Inline"; } break; case "table": $positioner = "Block"; $decorator = "Table"; $reflower = "Table"; break; case "inline-table": $positioner = "Inline"; $decorator = "Table"; $reflower = "Table"; break; case "table-row-group": case "table-header-group": case "table-footer-group": $positioner = "Null"; $decorator = "Table_Row_Group"; $reflower = "Table_Row_Group"; break; case "table-row": $positioner = "Null"; $decorator = "Table_Row"; $reflower = "Table_Row"; break; case "table-cell": $positioner = "Table_Cell"; $decorator = "Table_Cell"; $reflower = "Table_Cell"; break; case "list-item": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "-dompdf-list-bullet": if ($frame->get_style()->list_style_position == "inside") { $positioner = "Inline"; } else { $positioner = "List_Bullet"; } if ($frame->get_style()->list_style_image != "none") { $decorator = "List_Bullet_Image"; } else { $decorator = "List_Bullet"; } $reflower = "List_Bullet"; break; case "-dompdf-image": $positioner = "Inline"; $decorator = "Image"; $reflower = "Image"; break; case "-dompdf-br": $positioner = "Inline"; $decorator = "Inline"; $reflower = "Inline"; break; default: // FIXME: should throw some sort of warning or something? // FIXME: should throw some sort of warning or something? case "none": $positioner = "Null"; $decorator = "Null"; $reflower = "Null"; break; } $positioner .= "_Positioner"; $decorator .= "_Frame_Decorator"; $reflower .= "_Frame_Reflower"; $deco = new $decorator($frame, $dompdf); $deco->set_positioner(new $positioner($deco)); $reflow = new $reflower($deco); // Generated content is a special case if ($frame->get_node()->nodeName == "_dompdf_generated") { // Decorate the reflower $gen = new Generated_Frame_Reflower($deco); $gen->set_reflower($reflow); $reflow = $gen; } $deco->set_reflower($reflow); // Images are a special case // if ( $frame->get_node()->nodeName == "img" ) { // // FIXME: This is a hack // $node =$frame->get_node()->ownerDocument->createElement("img_sub"); // $node->setAttribute("src", $frame->get_node()->getAttribute("src")); // $img_frame = new Frame( $node ); // $style = $frame->get_style()->get_stylesheet()->create_style(); // $style->inherit($frame->get_style()); // $img_frame->set_style( $style ); // $img_deco = new Image_Frame_Decorator($img_frame, $dompdf); // $img_deco->set_reflower( new Image_Frame_Reflower($img_deco) ); // $deco->append_child($img_deco); // } return $deco; }
/** * Decorate a Frame * * @param Frame $frame The frame to decorate * @param DOMPDF $dompdf The dompdf instance * @param Frame $root The frame to decorate * * @throws DOMPDF_Exception * @return Frame_Decorator * FIXME: this is admittedly a little smelly... */ static function decorate_frame(Frame $frame, DOMPDF $dompdf, Frame $root = null) { if (is_null($dompdf)) { throw new DOMPDF_Exception("The DOMPDF argument is required"); } $style = $frame->get_style(); // Floating (and more generally out-of-flow) elements are blocks // http://coding.smashingmagazine.com/2007/05/01/css-float-theory-things-you-should-know/ if (!$frame->is_in_flow() && in_array($style->display, Style::$INLINE_TYPES)) { $style->display = "block"; } $display = $style->display; switch ($display) { case "block": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "inline-block": $positioner = "Inline"; $decorator = "Block"; $reflower = "Block"; break; case "inline": $positioner = "Inline"; if ($frame->is_text_node()) { $decorator = "Text"; $reflower = "Text"; } else { $enable_css_float = $dompdf->get_option("enable_css_float"); if ($enable_css_float && $style->float !== "none") { $decorator = "Block"; $reflower = "Block"; } else { $decorator = "Inline"; $reflower = "Inline"; } } break; case "table": $positioner = "Block"; $decorator = "Table"; $reflower = "Table"; break; case "inline-table": $positioner = "Inline"; $decorator = "Table"; $reflower = "Table"; break; case "table-row-group": case "table-header-group": case "table-footer-group": $positioner = "Null"; $decorator = "Table_Row_Group"; $reflower = "Table_Row_Group"; break; case "table-row": $positioner = "Null"; $decorator = "Table_Row"; $reflower = "Table_Row"; break; case "table-cell": $positioner = "Table_Cell"; $decorator = "Table_Cell"; $reflower = "Table_Cell"; break; case "list-item": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "-dompdf-list-bullet": if ($style->list_style_position === "inside") { $positioner = "Inline"; } else { $positioner = "List_Bullet"; } if ($style->list_style_image !== "none") { $decorator = "List_Bullet_Image"; } else { $decorator = "List_Bullet"; } $reflower = "List_Bullet"; break; case "-dompdf-image": $positioner = "Inline"; $decorator = "Image"; $reflower = "Image"; break; case "-dompdf-br": $positioner = "Inline"; $decorator = "Inline"; $reflower = "Inline"; break; default: // FIXME: should throw some sort of warning or something? // FIXME: should throw some sort of warning or something? case "none": $positioner = "Null"; $decorator = "Null"; $reflower = "Null"; break; } // Handle CSS position $position = $style->position; if ($position === "absolute") { $positioner = "Absolute"; } else { if ($position === "fixed") { $positioner = "Fixed"; } } $node = $frame->get_node(); // Handle nodeName if ($node->nodeName === "img") { $style->display = "-dompdf-image"; $decorator = "Image"; $reflower = "Image"; } $positioner .= "_Positioner"; $decorator .= "_Frame_Decorator"; $reflower .= "_Frame_Reflower"; $deco = new $decorator($frame, $dompdf); $deco->set_positioner(new $positioner($deco)); $deco->set_reflower(new $reflower($deco)); if ($root) { $deco->set_root($root); } if ($display === "list-item") { // Insert a list-bullet frame $xml = $dompdf->get_dom(); $bullet_node = $xml->createElement("bullet"); // arbitrary choice $b_f = new Frame($bullet_node); $node = $frame->get_node(); $parent_node = $node->parentNode; if ($parent_node) { if (!$parent_node->hasAttribute("dompdf-children-count")) { $xpath = new DOMXPath($xml); $count = $xpath->query("li", $parent_node)->length; $parent_node->setAttribute("dompdf-children-count", $count); } if (is_numeric($node->getAttribute("value"))) { $index = intval($node->getAttribute("value")); } else { if (!$parent_node->hasAttribute("dompdf-counter")) { $index = $parent_node->hasAttribute("start") ? $parent_node->getAttribute("start") : 1; } else { $index = $parent_node->getAttribute("dompdf-counter") + 1; } } $parent_node->setAttribute("dompdf-counter", $index); $bullet_node->setAttribute("dompdf-counter", $index); } $new_style = $dompdf->get_css()->create_style(); $new_style->display = "-dompdf-list-bullet"; $new_style->inherit($style); $b_f->set_style($new_style); $deco->prepend_child(Frame_Factory::decorate_frame($b_f, $dompdf, $root)); } return $deco; }
/** * Determine if a page break is allowed before $frame * http://www.w3.org/TR/CSS21/page.html#allowed-page-breaks * * In the normal flow, page breaks can occur at the following places: * * 1. In the vertical margin between block boxes. When a page * break occurs here, the used values of the relevant * 'margin-top' and 'margin-bottom' properties are set to '0'. * 2. Between line boxes inside a block box. * * These breaks are subject to the following rules: * * * Rule A: Breaking at (1) is allowed only if the * 'page-break-after' and 'page-break-before' properties of * all the elements generating boxes that meet at this margin * allow it, which is when at least one of them has the value * 'always', 'left', or 'right', or when all of them are * 'auto'. * * * Rule B: However, if all of them are 'auto' and the * nearest common ancestor of all the elements has a * 'page-break-inside' value of 'avoid', then breaking here is * not allowed. * * * Rule C: Breaking at (2) is allowed only if the number of * line boxes between the break and the start of the enclosing * block box is the value of 'orphans' or more, and the number * of line boxes between the break and the end of the box is * the value of 'widows' or more. * * * Rule D: In addition, breaking at (2) is allowed only if * the 'page-break-inside' property is 'auto'. * * If the above doesn't provide enough break points to keep * content from overflowing the page boxes, then rules B and D are * dropped in order to find additional breakpoints. * * If that still does not lead to sufficient break points, rules A * and C are dropped as well, to find still more break points. * * We will also allow breaks between table rows. However, when * splitting a table, the table headers should carry over to the * next page (but they don't yet). * * @param Frame $frame the frame to check * @return bool true if a break is allowed, false otherwise */ protected function _page_break_allowed(Frame $frame) { $block_types = array("block", "list-item", "table", "-dompdf-image"); dompdf_debug("page-break", "_page_break_allowed(" . $frame->get_node()->nodeName . ")"); $display = $frame->get_style()->display; // Block Frames (1): if (in_array($display, $block_types)) { // Avoid breaks within table-cells if ($this->_in_table) { dompdf_debug("page-break", "In table: " . $this->_in_table); return false; } // Rules A & B if ($frame->get_style()->page_break_before === "avoid") { dompdf_debug("page-break", "before: avoid"); return false; } // Find the preceeding block-level sibling $prev = $frame->get_prev_sibling(); while ($prev && !in_array($prev->get_style()->display, $block_types)) { $prev = $prev->get_prev_sibling(); } // Does the previous element allow a page break after? if ($prev && $prev->get_style()->page_break_after === "avoid") { dompdf_debug("page-break", "after: avoid"); return false; } // If both $prev & $frame have the same parent, check the parent's // page_break_inside property. $parent = $frame->get_parent(); if ($prev && $parent && $parent->get_style()->page_break_inside === "avoid") { dompdf_debug("page-break", "parent inside: avoid"); return false; } // To prevent cascading page breaks when a top-level element has // page-break-inside: avoid, ensure that at least one frame is // on the page before splitting. if ($parent->get_node()->nodeName === "body" && !$prev) { // We are the body's first child dompdf_debug("page-break", "Body's first child."); return false; } // If the frame is the first block-level frame, use the value from // $frame's parent instead. if (!$prev && $parent) { return $this->_page_break_allowed($parent); } dompdf_debug("page-break", "block: break allowed"); return true; } else { if (in_array($display, Style::$INLINE_TYPES)) { // Avoid breaks within table-cells if ($this->_in_table) { dompdf_debug("page-break", "In table: " . $this->_in_table); return false; } // Rule C $block_parent = $frame->find_block_parent(); if (count($block_parent->get_line_boxes()) < $frame->get_style()->orphans) { dompdf_debug("page-break", "orphans"); return false; } // FIXME: Checking widows is tricky without having laid out the // remaining line boxes. Just ignore it for now... // Rule D $p = $block_parent; while ($p) { if ($p->get_style()->page_break_inside === "avoid") { dompdf_debug("page-break", "parent->inside: avoid"); return false; } $p = $p->find_block_parent(); } // To prevent cascading page breaks when a top-level element has // page-break-inside: avoid, ensure that at least one frame with // some content is on the page before splitting. $prev = $frame->get_prev_sibling(); while ($prev && ($prev->is_text_node() && trim($prev->get_node()->nodeValue) == "")) { $prev = $prev->get_prev_sibling(); } if ($block_parent->get_node()->nodeName === "body" && !$prev) { // We are the body's first child dompdf_debug("page-break", "Body's first child."); return false; } // Skip breaks on empty text nodes if ($frame->is_text_node() && $frame->get_node()->nodeValue == "") { return false; } dompdf_debug("page-break", "inline: break allowed"); return true; // Table-rows } else { if ($display === "table-row") { // Simply check if the parent table's page_break_inside property is // not 'avoid' $p = Table_Frame_Decorator::find_parent_table($frame); while ($p) { if ($p->get_style()->page_break_inside === "avoid") { dompdf_debug("page-break", "parent->inside: avoid"); return false; } $p = $p->find_block_parent(); } // Avoid breaking after the first row of a table if ($p && $p->get_first_child() === $frame) { dompdf_debug("page-break", "table: first-row"); return false; } // If this is a nested table, prevent the page from breaking if ($this->_in_table > 1) { dompdf_debug("page-break", "table: nested table"); return false; } dompdf_debug("page-break", "table-row/row-groups: break allowed"); return true; } else { if (in_array($display, Table_Frame_Decorator::$ROW_GROUPS)) { // Disallow breaks at row-groups: only split at row boundaries return false; } else { dompdf_debug("page-break", "? " . $frame->get_style()->display . ""); return false; } } } } }
function add_frame(Frame $frame) { $style = $frame->get_style(); $display = $style->display; $collapse = $this->_table->get_style()->border_collapse == "collapse"; // Recursively add the frames within tables, table-row-groups and table-rows if ($display == "table-row" || $display == "table" || $display == "inline-table" || in_array($display, Table_Frame_Decorator::$ROW_GROUPS)) { $start_row = $this->__row; foreach ($frame->get_children() as $child) { $this->add_frame($child); } if ($display == "table-row") { $this->add_row(); } $num_rows = $this->__row - $start_row - 1; $key = $frame->get_id(); // Row groups always span across the entire table $this->_frames[$key]["columns"] = range(0, max(0, $this->_num_cols - 1)); $this->_frames[$key]["rows"] = range($start_row, max(0, $this->__row - 1)); $this->_frames[$key]["frame"] = $frame; if ($display != "table-row" && $collapse) { $bp = $style->get_border_properties(); // Resolve the borders for ($i = 0; $i < $num_rows + 1; $i++) { $this->_resolve_border($start_row + $i, 0, "vertical", $bp["left"]); $this->_resolve_border($start_row + $i, $this->_num_cols, "vertical", $bp["right"]); } for ($j = 0; $j < $this->_num_cols; $j++) { $this->_resolve_border($start_row, $j, "horizontal", $bp["top"]); $this->_resolve_border($this->__row, $j, "horizontal", $bp["bottom"]); } } return; } // Determine where this cell is going $colspan = $frame->get_node()->getAttribute("colspan"); $rowspan = $frame->get_node()->getAttribute("rowspan"); if (!$colspan) { $colspan = 1; $frame->get_node()->setAttribute("colspan", 1); } if (!$rowspan) { $rowspan = 1; $frame->get_node()->setAttribute("rowspan", 1); } $key = $frame->get_id(); $bp = $style->get_border_properties(); // Add the frame to the cellmap $max_left = $max_right = 0; // Find the next available column (fix by Ciro Mondueri) $ac = $this->__col; while (isset($this->_cells[$this->__row][$ac])) { $ac++; } $this->__col = $ac; // Rows: for ($i = 0; $i < $rowspan; $i++) { $row = $this->__row + $i; $this->_frames[$key]["rows"][] = $row; for ($j = 0; $j < $colspan; $j++) { $this->_cells[$row][$this->__col + $j] = $frame; } if ($collapse) { // Resolve vertical borders $max_left = max($max_left, $this->_resolve_border($row, $this->__col, "vertical", $bp["left"])); $max_right = max($max_right, $this->_resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"])); } } $max_top = $max_bottom = 0; // Columns: for ($j = 0; $j < $colspan; $j++) { $col = $this->__col + $j; $this->_frames[$key]["columns"][] = $col; if ($collapse) { // Resolve horizontal borders $max_top = max($max_top, $this->_resolve_border($this->__row, $col, "horizontal", $bp["top"])); $max_bottom = max($max_bottom, $this->_resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"])); } } $this->_frames[$key]["frame"] = $frame; // Handle seperated border model if (!$collapse) { list($h, $v) = $this->_table->get_style()->border_spacing; // Border spacing is effectively a margin between cells $v = $style->length_in_pt($v) / 2; $h = $style->length_in_pt($h) / 2; $style->margin = "{$v} {$h}"; // The additional 1/2 width gets added to the table proper } else { // Drop the frame's actual border $style->border_left_width = $max_left / 2; $style->border_right_width = $max_right / 2; $style->border_top_width = $max_top / 2; $style->border_bottom_width = $max_bottom / 2; $style->margin = "none"; } // Resolve the frame's width list($frame_min, $frame_max) = $frame->get_min_max_width(); $width = $style->width; if (is_percent($width)) { $var = "percent"; $val = (double) rtrim($width, "% ") / $colspan; } else { if ($width !== "auto") { $var = "absolute"; $val = $style->length_in_pt($frame_min) / $colspan; } } $min = 0; $max = 0; for ($cs = 0; $cs < $colspan; $cs++) { // Resolve the frame's width(s) with other cells $col =& $this->get_column($this->__col + $cs); // Note: $var is either 'percent' or 'absolute'. We compare the // requested percentage or absolute values with the existing widths // and adjust accordingly. if (isset($var) && $val > $col[$var]) { $col[$var] = $val; $col["auto"] = false; } $min += $col["min-width"]; $max += $col["max-width"]; } if ($frame_min > $min) { // The frame needs more space. Expand each sub-column $inc = ($frame_min - $min) / $colspan; for ($c = 0; $c < $colspan; $c++) { $col =& $this->get_column($this->__col + $c); $col["min-width"] += $inc; } } if ($frame_max > $max) { $inc = ($frame_max - $max) / $colspan; for ($c = 0; $c < $colspan; $c++) { $col =& $this->get_column($this->__col + $c); $col["max-width"] += $inc; } } $this->__col += $colspan; if ($this->__col > $this->_num_cols) { $this->_num_cols = $this->__col; } }