/** * @return Frame_Decorator */ function get_parent() { $p = $this->_frame->get_parent(); if ($p && $deco = $p->get_decorator()) { while ($tmp = $deco->get_decorator()) $deco = $tmp; return $deco; } else if ($p) return $p; else return null; }
function render(Frame $frame) { // Render background & borders //parent::render($frame); $p = $frame->get_parent(); $style = $frame->get_style(); $cb = $frame->get_containing_block(); list($x, $y) = $frame->get_padding_box(); $x += $style->length_in_pt($style->padding_left, $cb["w"]); $y += $style->length_in_pt($style->padding_top, $cb["h"]); $w = $style->length_in_pt($style->width, $cb["w"]); $h = $style->length_in_pt($style->height, $cb["h"]); $this->_canvas->image($frame->get_image_url(), $frame->get_image_ext(), $x, $y, $w, $h); }
/** * @return Frame_Decorator */ function get_parent() { $p = $this->_frame->get_parent(); if ($p && ($deco = $p->get_decorator())) { while ($tmp = $deco->get_decorator()) { $deco = $tmp; } return $deco; } else { if ($p) { return $p; } } return null; }
/** * Check if $frame will fit on the page. If the frame does not fit, * the frame tree is modified so that a page break occurs in the * correct location. * * @param Frame $frame the frame to check * @return Frame the frame following the page break */ function check_page_break(Frame $frame) { // Do not split if we have already or if the frame was already // pushed to the next page (prevents infinite loops) if ($this->_page_full || $frame->_already_pushed) { return false; } // If the frame is absolute of fixed it shouldn't break $p = $frame; do { if ($p->is_absolute()) { return false; } } while ($p = $p->get_parent()); $margin_height = $frame->get_margin_height(); // FIXME If the row is taller than the page and // if it the first of the page, we don't break if ($frame->get_style()->display === "table-row" && !$frame->get_prev_sibling() && $margin_height > $this->get_margin_height()) { return false; } // Determine the frame's maximum y value $max_y = $frame->get_position("y") + $margin_height; // If a split is to occur here, then the bottom margins & paddings of all // parents of $frame must fit on the page as well: $p = $frame->get_parent(); while ($p) { $style = $p->get_style(); $max_y += $style->length_in_pt(array($style->margin_bottom, $style->padding_bottom, $style->border_bottom_width)); $p = $p->get_parent(); } // Check if $frame flows off the page if ($max_y <= $this->_bottom_page_margin) { // no: do nothing return false; } dompdf_debug("page-break", "check_page_break"); dompdf_debug("page-break", "in_table: " . $this->_in_table); // yes: determine page break location $iter = $frame; $flg = false; $in_table = $this->_in_table; dompdf_debug("page-break", "Starting search"); while ($iter) { // echo "\nbacktrack: " .$iter->get_node()->nodeName ." ".spl_object_hash($iter->get_node()). ""; if ($iter === $this) { dompdf_debug("page-break", "reached root."); // We've reached the root in our search. Just split at $frame. break; } if ($this->_page_break_allowed($iter)) { dompdf_debug("page-break", "break allowed, splitting."); $iter->split(null, true); $this->_page_full = true; $this->_in_table = $in_table; $frame->_already_pushed = true; return true; } if (!$flg && ($next = $iter->get_last_child())) { dompdf_debug("page-break", "following last child."); if ($next->is_table()) { $this->_in_table++; } $iter = $next; continue; } if ($next = $iter->get_prev_sibling()) { dompdf_debug("page-break", "following prev sibling."); if ($next->is_table() && !$iter->is_table()) { $this->_in_table++; } else { if (!$next->is_table() && $iter->is_table()) { $this->_in_table--; } } $iter = $next; $flg = false; continue; } if ($next = $iter->get_parent()) { dompdf_debug("page-break", "following parent."); if ($iter->is_table()) { $this->_in_table--; } $iter = $next; $flg = true; continue; } break; } $this->_in_table = $in_table; // No valid page break found. Just break at $frame. dompdf_debug("page-break", "no valid break found, just splitting."); // If we are in a table, backtrack to the nearest top-level table row if ($this->_in_table) { $num_tables = $this->_in_table - 1; $iter = $frame; while ($iter && $num_tables && $iter->get_style()->display !== "table") { $iter = $iter->get_parent(); $num_tables--; } $iter = $frame; while ($iter && $iter->get_style()->display !== "table-row") { $iter = $iter->get_parent(); } } $frame->split(null, true); $this->_page_full = true; $frame->_already_pushed = true; return true; }
/** * 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; } } } } }
/** * Parses the CSS "content" property * * @return string The resulting string */ protected function _parse_content() { // Matches generated content $re = "/\n" . "\\s(counters?\\([^)]*\\))|\n" . "\\A(counters?\\([^)]*\\))|\n" . "\\s([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\3|\n" . "\\A([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\5|\n" . "\\s([^\\s\"']+)|\n" . "\\A([^\\s\"']+)\n" . "/xi"; $content = $this->_frame->get_style()->content; $quotes = $this->_parse_quotes(); // split on spaces, except within quotes if (!preg_match_all($re, $content, $matches, PREG_SET_ORDER)) { return; } $text = ""; foreach ($matches as $match) { if (isset($match[2]) && $match[2] !== "") { $match[1] = $match[2]; } if (isset($match[6]) && $match[6] !== "") { $match[4] = $match[6]; } if (isset($match[8]) && $match[8] !== "") { $match[7] = $match[8]; } if (isset($match[1]) && $match[1] !== "") { // counters?(...) $match[1] = mb_strtolower(trim($match[1])); // Handle counter() references: // http://www.w3.org/TR/CSS21/generate.html#content $i = mb_strpos($match[1], ")"); if ($i === false) { continue; } $args = explode(",", mb_substr($match[1], 8, $i - 8)); $counter_id = $args[0]; if ($match[1][7] === "(") { // counter(name [,style]) if (isset($args[1])) { $type = trim($args[1]); } else { $type = null; } $p = $this->_frame->find_block_parent(); $text .= $p->counter_value($counter_id, $type); } else { if ($match[1][7] === "s") { // counters(name, string [,style]) if (isset($args[1])) { $string = $this->_parse_string(trim($args[1])); } else { $string = ""; } if (isset($args[2])) { $type = $args[2]; } else { $type = null; } $p = $this->_frame->find_block_parent(); $tmp = ""; while ($p) { $tmp = $p->counter_value($counter_id, $type) . $string . $tmp; $p = $p->find_block_parent(); } $text .= $tmp; } else { // countertops? continue; } } } else { if (isset($match[4]) && $match[4] !== "") { // String match $text .= $this->_parse_string($match[4]); } else { if (isset($match[7]) && $match[7] !== "") { // Directive match if ($match[7] === "open-quote") { // FIXME: do something here $text .= $quotes[0][0]; } else { if ($match[7] === "close-quote") { // FIXME: do something else here $text .= $quotes[0][1]; } else { if ($match[7] === "no-open-quote") { // FIXME: } else { if ($match[7] === "no-close-quote") { // FIXME: } else { if (mb_strpos($match[7], "attr(") === 0) { $i = mb_strpos($match[7], ")"); if ($i === false) { continue; } $attr = mb_substr($match[7], 5, $i - 5); if ($attr == "") { continue; } $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr); } else { continue; } } } } } } } } } return $text; }
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(); $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; } } }
/** * Check if $frame will fit on the page. If the frame does not fit, * the frame tree is modified so that a page break occurs in the * correct location. * * @param Frame $frame the frame to check * @return Frame the frame following the page break */ function check_page_break(Frame $frame) { // Do not split if we have already if ($this->_page_full) { return false; } // Determine the frame's maximum y value $max_y = $frame->get_position("y") + $frame->get_margin_height(); // If a split is to occur here, then the bottom margins & paddings of all // parents of $frame must fit on the page as well: $p = $frame->get_parent(); while ($p) { $style = $p->get_style(); $max_y += $style->length_in_pt(array($style->margin_bottom, $style->padding_bottom, $style->border_bottom_width)); $p = $p->get_parent(); } // Check if $frame flows off the page if ($max_y <= $this->_bottom_page_margin) { // no: do nothing (?) return false; } // echo "check_page_break\n"; // yes: determine page break location $iter = $frame; $flg = false; $in_table = $this->_in_table; // echo "Starting search\n"; while ($iter) { // echo "\nbacktrack: " .$iter->get_node()->nodeName ." ".(string)$iter->get_node(). "\n"; if ($iter === $this) { // echo "reached root.\n"; // We've reached the root in our search. Just split at $frame. break; } if ($this->_page_break_allowed($iter)) { // echo "break allowed, splitting.\n"; $iter->split(); $this->_page_full = true; $this->_in_table = $in_table; return true; } if (!$flg && ($next = $iter->get_last_child())) { // echo "following last child.\n"; if (in_array($next->get_style()->display, Style::$TABLE_TYPES)) { $this->_in_table++; } $iter = $next; continue; } if ($next = $iter->get_prev_sibling()) { // echo "following prev sibling.\n"; if (in_array($next->get_style()->display, Style::$TABLE_TYPES) && !in_array($iter->get_style()->display, Style::$TABLE_TYPES)) { $this->_in_table++; } else { if (!in_array($next->get_style()->display, Style::$TABLE_TYPES) && in_array($iter->get_style()->display, Style::$TABLE_TYPES)) { $this->_in_table--; } } $iter = $next; $flg = false; continue; } if ($next = $iter->get_parent()) { // echo "following parent.\n"; if (in_array($iter->get_style()->display, Style::$TABLE_TYPES)) { $this->_in_table--; } $iter = $next; $flg = true; continue; } break; } $this->_in_table = $in_table; // No valid page break found. Just break at $frame. // echo "no valid break found, just splitting.\n"; // If we are in a table, backtrack to the nearest table row if ($this->_in_table) { $tr = $frame; while ($tr && $tr->get_style()->display != "table-row") { $tr = $tr->get_parent(); } $tr->split(); $this->_page_full = true; return true; } $frame->split(); $this->_page_full = true; return true; }
/** * Parses the CSS "content" property * * @return string|null The resulting string */ protected function _parse_content() { // Matches generated content $re = "/\n" . "\\s(counters?\\([^)]*\\))|\n" . "\\A(counters?\\([^)]*\\))|\n" . "\\s([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\3|\n" . "\\A([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\5|\n" . "\\s([^\\s\"']+)|\n" . "\\A([^\\s\"']+)\n" . "/xi"; $content = $this->_frame->get_style()->content; $quotes = $this->_parse_quotes(); // split on spaces, except within quotes if (!preg_match_all($re, $content, $matches, PREG_SET_ORDER)) { return null; } $text = ""; foreach ($matches as $match) { if (isset($match[2]) && $match[2] !== "") { $match[1] = $match[2]; } if (isset($match[6]) && $match[6] !== "") { $match[4] = $match[6]; } if (isset($match[8]) && $match[8] !== "") { $match[7] = $match[8]; } if (isset($match[1]) && $match[1] !== "") { // counters?(...) $match[1] = mb_strtolower(trim($match[1])); // Handle counter() references: // http://www.w3.org/TR/CSS21/generate.html#content $i = mb_strpos($match[1], ")"); if ($i === false) { continue; } preg_match('/(counters?)(^\\()*?\\(\\s*([^\\s,]+)\\s*(,\\s*["\']?([^"\'\\)]+)["\']?\\s*(,\\s*([^\\s)]+)\\s*)?)?\\)/i', $match[1], $args); $counter_id = $args[3]; if (strtolower($args[1]) == 'counter') { // counter(name [,style]) if (isset($args[5])) { $type = trim($args[5]); } else { $type = null; } $p = $this->_frame->lookup_counter_frame($counter_id); $text .= $p->counter_value($counter_id, $type); } else { if (strtolower($args[1]) == 'counters') { // counters(name, string [,style]) if (isset($args[5])) { $string = $this->_parse_string($args[5]); } else { $string = ""; } if (isset($args[7])) { $type = trim($args[7]); } else { $type = null; } $p = $this->_frame->lookup_counter_frame($counter_id); $tmp = array(); while ($p) { // We only want to use the counter values when they actually increment the counter if (array_key_exists($counter_id, $p->_counters)) { array_unshift($tmp, $p->counter_value($counter_id, $type)); } $p = $p->lookup_counter_frame($counter_id); } $text .= implode($string, $tmp); } else { // countertops? continue; } } } else { if (isset($match[4]) && $match[4] !== "") { // String match $text .= $this->_parse_string($match[4]); } else { if (isset($match[7]) && $match[7] !== "") { // Directive match if ($match[7] === "open-quote") { // FIXME: do something here $text .= $quotes[0][0]; } else { if ($match[7] === "close-quote") { // FIXME: do something else here $text .= $quotes[0][1]; } else { if ($match[7] === "no-open-quote") { // FIXME: } else { if ($match[7] === "no-close-quote") { // FIXME: } else { if (mb_strpos($match[7], "attr(") === 0) { $i = mb_strpos($match[7], ")"); if ($i === false) { continue; } $attr = mb_substr($match[7], 5, $i - 5); if ($attr == "") { continue; } $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr); } else { continue; } } } } } } } } } return $text; }
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 ) ); $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" && !Image_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_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); $dpi = $this->_dompdf->get_option("dpi"); $w = ((float)rtrim($width, "px") * 72) / $dpi; $h = ((float)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*(List_Bullet_Frame_Decorator::BULLET_SIZE /*-List_Bullet_Frame_Decorator::BULLET_THICKNESS*/ ))/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 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 -= Font_Metrics::get_text_width($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 check_page_break(Frame $frame) { if ($this->_page_full || $frame->_already_pushed) { return false; } $p = $frame; do { if ($p->is_absolute()) { return false; } } while ($p = $p->get_parent()); $margin_height = $frame->get_margin_height(); if ($frame->get_style()->display === "table-row" && !$frame->get_prev_sibling() && $margin_height > $this->get_margin_height()) { return false; } $max_y = $frame->get_position("y") + $margin_height; $p = $frame->get_parent(); while ($p) { $style = $p->get_style(); $max_y += $style->length_in_pt(array($style->margin_bottom, $style->padding_bottom, $style->border_bottom_width)); $p = $p->get_parent(); } if ($max_y <= $this->_bottom_page_margin) { return false; } dompdf_debug("page-break", "check_page_break"); dompdf_debug("page-break", "in_table: " . $this->_in_table); $iter = $frame; $flg = false; $in_table = $this->_in_table; dompdf_debug("page-break", "Starting search"); while ($iter) { if ($iter === $this) { dompdf_debug("page-break", "reached root."); break; } if ($this->_page_break_allowed($iter)) { dompdf_debug("page-break", "break allowed, splitting."); $iter->split(null, true); $this->_page_full = true; $this->_in_table = $in_table; $frame->_already_pushed = true; return true; } if (!$flg && ($next = $iter->get_last_child())) { dompdf_debug("page-break", "following last child."); if ($next->is_table()) { $this->_in_table++; } $iter = $next; continue; } if ($next = $iter->get_prev_sibling()) { dompdf_debug("page-break", "following prev sibling."); if ($next->is_table() && !$iter->is_table()) { $this->_in_table++; } else { if (!$next->is_table() && $iter->is_table()) { $this->_in_table--; } } $iter = $next; $flg = false; continue; } if ($next = $iter->get_parent()) { dompdf_debug("page-break", "following parent."); if ($iter->is_table()) { $this->_in_table--; } $iter = $next; $flg = true; continue; } break; } $this->_in_table = $in_table; dompdf_debug("page-break", "no valid break found, just splitting."); if ($this->_in_table) { $num_tables = $this->_in_table - 1; $iter = $frame; while ($iter && $num_tables && $iter->get_style()->display !== "table") { $iter = $iter->get_parent(); $num_tables--; } $iter = $frame; while ($iter && $iter->get_style()->display !== "table-row") { $iter = $iter->get_parent(); } } $frame->split(null, true); $this->_page_full = true; $frame->_already_pushed = true; return true; }
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)); if ($style->list_style_image !== "none" && !Image_Cache::is_broken($img = $frame->get_image_url())) { list($x, $y) = $frame->get_position(); 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; $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 * 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": case "a": case "i": case "A": case "I": $li = $frame->get_parent(); $pad = null; if ($bullet_style === "decimal-leading-zero") { $pad = strlen($li->get_parent()->get_node()->getAttribute("dompdf-children-count")); } $index = $frame->get_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 -= Font_Metrics::get_text_width($text, $font_family, $font_size, $spacing); $line_height = $style->line_height; $y += ($line_height - $font_size) / 4; $this->_canvas->text($x, $y, $text, $font_family, $font_size, $style->color, $spacing); case "none": break; } } }