function get_min_max_width() { if (!is_null($this->_min_max_cache)) { return $this->_min_max_cache; } $style = $this->_frame->get_style(); // Account for margins & padding $dims = array($style->padding_left, $style->padding_right, $style->border_left_width, $style->border_right_width, $style->margin_left, $style->margin_right); $cb_w = $this->_frame->get_containing_block("w"); $delta = $style->length_in_pt($dims, $cb_w); // Handle degenerate case if (!$this->_frame->get_first_child()) { return $this->_min_max_cache = array($delta, $delta, "min" => $delta, "max" => $delta); } $low = array(); $high = array(); for ($iter = $this->_frame->get_children()->getIterator(); $iter->valid(); $iter->next()) { $inline_min = 0; $inline_max = 0; // Add all adjacent inline widths together to calculate max width while ($iter->valid() && in_array($iter->current()->get_style()->display, Style::$INLINE_TYPES)) { $child = $iter->current(); $minmax = $child->get_min_max_width(); if (in_array($iter->current()->get_style()->white_space, array("pre", "nowrap"))) { $inline_min += $minmax["min"]; } else { $low[] = $minmax["min"]; } $inline_max += $minmax["max"]; $iter->next(); } if ($inline_max > 0) { $high[] = $inline_max; } if ($inline_min > 0) { $low[] = $inline_min; } if ($iter->valid()) { list($low[], $high[]) = $iter->current()->get_min_max_width(); continue; } } $min = count($low) ? max($low) : 0; $max = count($high) ? max($high) : 0; // Use specified width if it is greater than the minimum defined by the // content. If the width is a percentage ignore it for now. $width = $style->width; if ($width !== "auto" && !Helpers::is_percent($width)) { $width = $style->length_in_pt($width, $cb_w); if ($min < $width) { $min = $width; } if ($max < $width) { $max = $width; } } $min += $delta; $max += $delta; return $this->_min_max_cache = array($min, $max, "min" => $min, "max" => $max); }
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"))) { $href = Helpers::build_url($this->_dompdf->getProtocol(), $this->_dompdf->getBaseHost(), $this->_dompdf->getBasePath(), $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 ($this->_dompdf->get_option("debugLayout") && $this->_dompdf->get_option("debugLayoutInline")) { $this->_debug_layout($child->get_border_box(), "blue"); if ($this->_dompdf->get_option("debugLayoutPaddingBox")) { $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"); } // Helpers::var_dump(get_class($frame->get_next_sibling())); // $last_row = get_class($frame->get_next_sibling()) !== 'Inline'; // 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")) { $href = Helpers::build_url($this->_dompdf->getProtocol(), $this->_dompdf->getBaseHost(), $this->_dompdf->getBasePath(), $href); $this->_canvas->add_link($href, $x, $y, $w, $h); } } }
/** * @param Frame $frame */ public 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, TableFrameDecorator::$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; } $node = $frame->get_node(); // Determine where this cell is going $colspan = $node->getAttribute("colspan"); $rowspan = $node->getAttribute("rowspan"); if (!$colspan) { $colspan = 1; $node->setAttribute("colspan", 1); } if (!$rowspan) { $rowspan = 1; $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"; } if (!$this->_columns_locked) { // Resolve the frame's width if ($this->_fixed_layout) { list($frame_min, $frame_max) = array(0, 1.0E-9); } else { list($frame_min, $frame_max) = $frame->get_min_max_width(); } $width = $style->width; $val = null; if (Helpers::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 // FIXME try to avoid putting this dummy value when table-layout:fixed $inc = $this->is_layout_fixed() ? 1.0E-9 : ($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) { // FIXME try to avoid putting this dummy value when table-layout:fixed $inc = $this->is_layout_fixed() ? 1.0E-9 : ($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; } }
/** * 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 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 AbstractFrameReflower::_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 Exception("Unable to split: frame is not a child of this one."); } $node = $this->_frame->get_node(); if ($node instanceof DOMElement && $node->hasAttribute("id")) { $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id")); $node->removeAttribute("id"); } $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; $orig_style->page_break_before = "auto"; } $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]]; } }