function reflow(BlockFrameDecorator $block = null) { $page = $this->_frame->get_root(); if ($page->is_full()) { return; } $this->_frame->position(); $style = $this->_frame->get_style(); $cb = $this->_frame->get_containing_block(); foreach ($this->_frame->get_children() as $child) { if ($page->is_full()) { return; } $child->set_containing_block($cb); $child->reflow(); } if ($page->is_full()) { return; } $table = TableFrameDecorator::find_parent_table($this->_frame); $cellmap = $table->get_cellmap(); $style->width = $cellmap->get_frame_width($this->_frame); $style->height = $cellmap->get_frame_height($this->_frame); $this->_frame->set_position($cellmap->get_frame_position($this->_frame)); }
function reflow(BlockFrameDecorator $block = null) { $page = $this->_frame->get_root(); $style = $this->_frame->get_style(); // Our width is equal to the width of our parent table $table = TableFrameDecorator::find_parent_table($this->_frame); $cb = $this->_frame->get_containing_block(); foreach ($this->_frame->get_children() as $child) { // Bail if the page is full if ($page->is_full()) { return; } $child->set_containing_block($cb["x"], $cb["y"], $cb["w"], $cb["h"]); $child->reflow(); // Check if a split has occured $page->check_page_break($child); } if ($page->is_full()) { return; } $cellmap = $table->get_cellmap(); $style->width = $cellmap->get_frame_width($this->_frame); $style->height = $cellmap->get_frame_height($this->_frame); $this->_frame->set_position($cellmap->get_frame_position($this->_frame)); if ($table->get_style()->border_collapse === "collapse") { // Unset our borders because our cells are now using them $style->border_style = "none"; } }
function reflow(BlockFrameDecorator $block = null) { $style = $this->_frame->get_style(); $table = TableFrameDecorator::find_parent_table($this->_frame); $cellmap = $table->get_cellmap(); list($x, $y) = $cellmap->get_frame_position($this->_frame); $this->_frame->set_position($x, $y); $cells = $cellmap->get_spanned_cells($this->_frame); $w = 0; foreach ($cells["columns"] as $i) { $col = $cellmap->get_column($i); $w += $col["used-width"]; } //FIXME? $h = $this->_frame->get_containing_block("h"); $left_space = $style->length_in_pt(array($style->margin_left, $style->padding_left, $style->border_left_width), $w); $right_space = $style->length_in_pt(array($style->padding_right, $style->margin_right, $style->border_right_width), $w); $top_space = $style->length_in_pt(array($style->margin_top, $style->padding_top, $style->border_top_width), $h); $bottom_space = $style->length_in_pt(array($style->margin_bottom, $style->padding_bottom, $style->border_bottom_width), $h); $style->width = $cb_w = $w - $left_space - $right_space; $content_x = $x + $left_space; $content_y = $line_y = $y + $top_space; // Adjust the first line based on the text-indent property $indent = $style->length_in_pt($style->text_indent, $w); $this->_frame->increase_line_width($indent); $page = $this->_frame->get_root(); // Set the y position of the first line in the cell $line_box = $this->_frame->get_current_line_box(); $line_box->y = $line_y; // Set the containing blocks and reflow each child foreach ($this->_frame->get_children() as $child) { if ($page->is_full()) { break; } $child->set_containing_block($content_x, $content_y, $cb_w, $h); $this->process_clear($child); $child->reflow($this->_frame); $this->process_float($child, $x + $left_space, $w - $right_space - $left_space); } // Determine our height $style_height = $style->length_in_pt($style->height, $h); $this->_frame->set_content_height($this->_calculate_content_height()); $height = max($style_height, $this->_frame->get_content_height()); // Let the cellmap know our height $cell_height = $height / count($cells["rows"]); if ($style_height <= $height) { $cell_height += $top_space + $bottom_space; } foreach ($cells["rows"] as $i) { $cellmap->set_row_height($i, $cell_height); } $style->height = $height; $this->_text_align(); $this->vertical_align(); }
/** * Remove all non table-cell frames from this row and move them after * the table. */ function normalise() { // Find our table parent $p = TableFrameDecorator::find_parent_table($this); $erroneous_frames = array(); foreach ($this->get_children() as $child) { $display = $child->get_style()->display; if ($display !== "table-cell") { $erroneous_frames[] = $child; } } // dump the extra nodes after the table. foreach ($erroneous_frames as $frame) { $p->move_after($frame); } }
function position(AbstractFrameDecorator $frame) { $table = Table::find_parent_table($frame); $cellmap = $table->get_cellmap(); $frame->set_position($cellmap->get_frame_position($frame)); }
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"); } } }
function position() { $table = Table::find_parent_table($this->_frame); $cellmap = $table->get_cellmap(); $this->_frame->set_position($cellmap->get_frame_position($this->_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; } } } } }