function render(Frame $frame) { if (!$this->_dompdf->get_option("enable_javascript")) { return; } $this->insert($frame->get_node()->nodeValue); }
/** * 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, $dompdf); parent::__construct($this->_img, $dompdf); list($width, $height) = Helpers::dompdf_getimagesize($this->_img->get_image_url()); // Resample the bullet image to be consistent with 'auto' sized images // See also Image::get_min_max_width // Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary. $dpi = $this->_dompdf->get_option("dpi"); $this->_width = (double) rtrim($width, "px") * 72 / $dpi; $this->_height = (double) rtrim($height, "px") * 72 / $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"; }
/** * "Destructor": forcibly free all references held by this frame * * @param bool $recursive if true, call dispose on all children */ public 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(); $this->_style = null; unset($this->_style); $this->_original_style->dispose(); $this->_original_style = null; unset($this->_original_style); }
function render(Frame $frame) { if (!$this->_dompdf->getOptions()->getIsJavascriptEnabled()) { return; } $this->insert($frame->get_node()->nodeValue); }
/** * @param Frame $frame */ static function translate_attributes(Frame $frame) { $node = $frame->get_node(); $tag = $node->nodeName; 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(); $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"))) { $href = Helpers::build_url($this->_dompdf->getProtocol(), $this->_dompdf->getBaseHost(), $this->_dompdf->getBasePath(), $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 ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutBlocks()) { $this->_debug_layout($frame->get_border_box(), "red"); if ($this->_dompdf->getOptions()->getDebugLayoutPaddingBox()) { $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5)); } } if ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutLines() && $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 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); } } }
function render(Frame $frame) { $this->evaluate($frame->get_node()->nodeValue); }
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)); $li = $frame->get_parent(); // Don't render bullets twice if if was split if ($li->_splitted) { return; } // Handle list-style-image // If list style image is requested but missing, fall back to predefined types if ($style->list_style_image !== "none" && !Cache::is_broken($img = $frame->get_image_url())) { 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::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) = Helpers::dompdf_getimagesize($img, $this->_dompdf->getHttpContext()); $dpi = $this->_dompdf->getOptions()->getDpi(); $w = (double) rtrim($width, "px") * 72 / $dpi; $h = (double) rtrim($height, "px") * 72 / $dpi; $x -= $w; $y -= ($line_height - $font_size) / 2; //Reverse hinting of list_bullet_positioner $this->_canvas->image($img, $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 * ListBulletFrameDecorator::BULLET_SIZE / 2; $x -= $font_size * (ListBulletFrameDecorator::BULLET_SIZE / 2); $y += $font_size * (1 - ListBulletFrameDecorator::BULLET_DESCENT) / 2; $o = $font_size * ListBulletFrameDecorator::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 * ListBulletFrameDecorator::BULLET_SIZE; $x -= $w; $y += $font_size * (1 - ListBulletFrameDecorator::BULLET_DESCENT - ListBulletFrameDecorator::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": $pad = null; if ($bullet_style === "decimal-leading-zero") { $pad = strlen($li->get_parent()->get_node()->getAttribute("dompdf-children-count")); } $node = $frame->get_node(); if (!$node->hasAttribute("dompdf-counter")) { return; } $index = $node->getAttribute("dompdf-counter"); $text = $this->make_counter($index, $bullet_style, $pad); if (trim($text) == "") { return; } $spacing = 0; $font_family = $style->font_family; $line = $li->get_containing_line(); list($x, $y) = array($frame->get_position("x"), $line->y); $x -= $this->_dompdf->getFontMetrics()->getTextWidth($text, $font_family, $font_size, $spacing); // Take line-height into account $line_height = $style->line_height; $y += ($line_height - $font_size) / 4; // FIXME I thought it should be 2, but 4 gives better results $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::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"); } } }
/** * @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]]; } }
/** * @param Frame $frame */ 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]); /* // Adds a new line after a block, only if certain conditions are met if ((($frame instanceof Inline && $frame->get_node()->nodeName !== "br") || $frame instanceof Text && trim($frame->get_text())) && ($frame->get_prev_sibling() && $frame->get_prev_sibling()->get_style()->display === "block" && $this->_line_boxes[$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) { // 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->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; } // Debugging code: /* Helpers::pre_r("\n<h3>Adding frame to line:</h3>"); // Helpers::pre_r("Me: " . $this->get_node()->nodeName . " (" . spl_object_hash($this->get_node()) . ")"); // Helpers::pre_r("Node: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")"); if ( $frame->is_text_node() ) Helpers::pre_r('"'.$frame->get_node()->nodeValue.'"'); Helpers::pre_r("Line width: " . $this->_line_boxes[$this->_cl]->w); Helpers::pre_r("Frame: " . get_class($frame)); Helpers::pre_r("Frame width: " . $w); Helpers::pre_r("Frame height: " . $frame->get_margin_height()); Helpers::pre_r("Containing block width: " . $this->get_containing_block("w")); */ // End debugging $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); }
/** * 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"); Helpers::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) { Helpers::dompdf_debug("page-break", "In table: " . $this->_in_table); return false; } // Rules A & B if ($frame->get_style()->page_break_before === "avoid") { Helpers::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") { Helpers::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") { Helpers::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 Helpers::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); } Helpers::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) { Helpers::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) { Helpers::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") { Helpers::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 Helpers::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; } Helpers::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::find_parent_table($frame); while ($p) { if ($p->get_style()->page_break_inside === "avoid") { Helpers::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) { Helpers::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) { Helpers::dompdf_debug("page-break", "table: nested table"); return false; } Helpers::dompdf_debug("page-break", "table-row/row-groups: break allowed"); return true; } else { if (in_array($display, Table::$ROW_GROUPS)) { // Disallow breaks at row-groups: only split at row boundaries return false; } else { Helpers::dompdf_debug("page-break", "? " . $frame->get_style()->display . ""); return false; } } } } }