function render(Frame $frame) { $style = $frame->get_style(); list($x, $y, $w, $h) = $frame->get_padding_box(); $this->_set_opacity($frame->get_opacity($style->opacity)); // Draw our background, border and content if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } $this->_render_border($frame); $this->_render_outline($frame); if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { $this->_debug_layout($frame->get_border_box(), "red"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5)); } } if (DEBUG_LAYOUT && DEBUG_LAYOUT_LINES && $frame->get_decorator()) { foreach ($frame->get_decorator()->get_lines() as $line) { $frame->_debug_layout(array($line["x"], $line["y"], $line["w"], $line["h"]), "orange"); } } }
function render(Frame $frame) { $style = $frame->get_style(); list($x, $y, $w, $h) = $frame->get_border_box(); $this->_set_opacity($frame->get_opacity($style->opacity)); if ($frame->get_node()->nodeName === "body") { $h = $frame->get_containing_block("h") - $style->length_in_pt(array($style->margin_top, $style->padding_top, $style->border_top_width, $style->border_bottom_width, $style->padding_bottom, $style->margin_bottom), $style->width); } // Draw our background, border and content if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } $this->_render_border($frame); $this->_render_outline($frame); if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { $this->_debug_layout($frame->get_border_box(), "red"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5)); } } if (DEBUG_LAYOUT && DEBUG_LAYOUT_LINES && $frame->get_decorator()) { foreach ($frame->get_decorator()->get_line_boxes() as $line) { $frame->_debug_layout(array($line->x, $line->y, $line->w, $line->h), "orange"); } } }
function apply_page_style(Frame $frame, $page_number) { $style = $frame->get_style(); $page_styles = $style->get_stylesheet()->get_page_styles(); if (count($page_styles) > 1) { $odd = $page_number % 2 == 1; $first = $page_number == 1; $style = clone $page_styles["base"]; if ($odd && isset($page_styles[":right"])) { $style->merge($page_styles[":right"]); } if ($odd && isset($page_styles[":odd"])) { $style->merge($page_styles[":odd"]); } if (!$odd && isset($page_styles[":left"])) { $style->merge($page_styles[":left"]); } if (!$odd && isset($page_styles[":even"])) { $style->merge($page_styles[":even"]); } if ($first && isset($page_styles[":first"])) { $style->merge($page_styles[":first"]); } $frame->set_style($style); } }
/** * Class constructor * * @param Frame $frame the bullet frame to decorate * @param DOMPDF $dompdf the document's dompdf object */ function __construct(Frame $frame, DOMPDF $dompdf) { $style = $frame->get_style(); $url = $style->list_style_image; $frame->get_node()->setAttribute("src", $url); $this->_img = new Image_Frame_Decorator($frame, $dompdf); parent::__construct($this->_img, $dompdf); list($width, $height) = dompdf_getimagesize($this->_img->get_image_url()); // Resample the bullet image to be consistent with 'auto' sized images // See also Image_Frame_Reflower::get_min_max_width // Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary. $this->_width = (double) rtrim($width, "px") * 72 / DOMPDF_DPI; $this->_height = (double) rtrim($height, "px") * 72 / DOMPDF_DPI; //If an image is taller as the containing block/box, the box should be extended. //Neighbour elements are overwriting the overlapping image areas. //Todo: Where can the box size be extended? //Code below has no effect. //See block_frame_reflower _calculate_restricted_height //See generated_frame_reflower, Dompdf:render() "list-item", "-dompdf-list-bullet"S. //Leave for now //if ($style->min_height < $this->_height ) { // $style->min_height = $this->_height; //} //$style->height = "auto"; }
function 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" && !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 add_frame_to_line(Frame $frame) { if (!$frame->is_in_flow()) { return; } $style = $frame->get_style(); $frame->set_containing_line($this->_line_boxes[$this->_cl]); if ($frame instanceof Inline_Frame_Decorator) { if ($frame->get_node()->nodeName === "br") { $this->maximize_line_height($style->length_in_pt($style->line_height), $frame); $this->add_line(true); } return; } if ($this->get_current_line_box()->w == 0 && $frame->is_text_node() && !$frame->is_pre()) { $frame->set_text(ltrim($frame->get_text())); $frame->recalculate_width(); } $w = $frame->get_margin_width(); if ($w == 0) { return; } $line = $this->_line_boxes[$this->_cl]; if ($line->left + $line->w + $line->right + $w > $this->get_containing_block("w")) { $this->add_line(); } $frame->position(); $current_line = $this->_line_boxes[$this->_cl]; $current_line->add_frame($frame); if ($frame->is_text_node()) { $current_line->wc += count(preg_split("/\\s+/", trim($frame->get_text()))); } $this->increase_line_width($w); $this->maximize_line_height($frame->get_margin_height(), $frame); }
function render(Frame $frame) { $style = $frame->get_style(); $bullet_style = $style->list_style_type; $bullet_size = List_Bullet_Frame_Decorator::BULLET_SIZE; $line_height = $style->length_in_pt($style->line_height, $frame->get_containing_block("w")); $fill = false; switch ($bullet_style) { default: case "disc": $fill = true; case "circle": if (!$fill) { $fill = false; } list($x, $y) = $frame->get_position(); $x += $bullet_size / 2; $y += $line_height / 2 + $bullet_size / 4; $r = $bullet_size / 4; $this->_canvas->circle($x, $y, $r, $style->color, null, null, $fill); break; case "square": list($x, $y) = $frame->get_position(); $w = $bullet_size / 2; $x += $bullet_size / 2 - $w / 2; $y += $line_height / 2; $this->_canvas->filled_rectangle($x, $y, $w, $w, $style->color); break; } }
function apply_page_style(Frame $frame, $page_number) { $style = $frame->get_style(); $page_styles = $style->get_stylesheet()->get_page_styles(); // http://www.w3.org/TR/CSS21/page.html#page-selectors if (count($page_styles) > 1) { $odd = $page_number % 2 == 1; $first = $page_number == 1; $style = clone $page_styles["base"]; // FIXME RTL if ($odd && isset($page_styles[":right"])) { $style->merge($page_styles[":right"]); } if ($odd && isset($page_styles[":odd"])) { $style->merge($page_styles[":odd"]); } // FIXME RTL if (!$odd && isset($page_styles[":left"])) { $style->merge($page_styles[":left"]); } if (!$odd && isset($page_styles[":even"])) { $style->merge($page_styles[":even"]); } if ($first && isset($page_styles[":first"])) { $style->merge($page_styles[":first"]); } $frame->set_style($style); } }
function render(Frame $frame) { $style = $frame->get_style(); list($x, $y) = $frame->get_position(); $cb = $frame->get_containing_block(); if (($ml = $style->margin_left) == "auto" || $ml == "none") { $ml = 0; } if (($pl = $style->padding_left) == "auto" || $pl == "none") { $pl = 0; } if (($bl = $style->border_left_width) == "auto" || $bl == "none") { $bl = 0; } $x += $style->length_in_pt(array($ml, $pl, $bl), $cb["w"]); $text = $frame->get_text(); $font = $style->font_family; $size = $style->font_size; $height = $style->height; $spacing = $frame->get_text_spacing() + $style->word_spacing; if (preg_replace("/[\\s]+/", "", $text) == "") { return; } $this->_canvas->text($x, $y, $text, $font, $size, $style->color, $spacing); // Handle text decoration: // http://www.w3.org/TR/CSS21/text.html#propdef-text-decoration // Draw all applicable text-decorations. Start with the root and work // our way down. $p = $frame; $stack = array(); while ($p = $p->get_parent()) { $stack[] = $p; } while (count($stack) > 0) { $f = array_pop($stack); $deco_y = $y; if (($text_deco = $f->get_style()->text_decoration) === "none") { continue; } $color = $f->get_style()->color; switch ($text_deco) { default: continue; case "underline": $deco_y += $height * (1 + self::UNDERLINE_OFFSET); break; case "overline": $deco_y += $height * self::OVERLINE_OFFSET; break; case "line-through": $deco_y -= $height * (0.25 + self::LINETHROUGH_OFFSET); break; } $dx = 0; $x1 = $x - self::DECO_EXTENSION; $x2 = $x + $style->width + $dx + self::DECO_EXTENSION; $this->_canvas->line($x1, $deco_y, $x2, $deco_y, $color, 0.5); } }
/** * Render frames recursively * * @param Frame $frame the frame to render */ function render(Frame $frame) { global $_dompdf_debug; if ($_dompdf_debug) { echo $frame; flush(); } $display = $frame->get_style()->display; switch ($display) { case "block": case "list-item": case "inline-block": case "table": case "table-row-group": case "table-header-group": case "table-footer-group": case "inline-table": $this->_render_frame("block", $frame); break; case "inline": if ($frame->get_node()->nodeName === "#text") { $this->_render_frame("text", $frame); } else { $this->_render_frame("inline", $frame); } break; case "table-cell": $this->_render_frame("table-cell", $frame); break; case "-dompdf-list-bullet": $this->_render_frame("list-bullet", $frame); break; case "-dompdf-image": $this->_render_frame("image", $frame); break; case "none": $node = $frame->get_node(); if ($node->nodeName === "script") { if ($node->getAttribute("type") === "text/php" || $node->getAttribute("language") === "php") { // Evaluate embedded php scripts $this->_render_frame("php", $frame); } elseif ($node->getAttribute("type") === "text/javascript" || $node->getAttribute("language") === "javascript") { // Insert JavaScript $this->_render_frame("javascript", $frame); } } // Don't render children, so skip to next iter return; default: break; } // Check for begin frame callback $this->_check_callbacks("begin_frame", $frame); foreach ($frame->get_children() as $child) { $this->render($child); } // Check for end frame callback $this->_check_callbacks("end_frame", $frame); }
function render(Frame $frame) { $style = $frame->get_style(); $node = $frame->get_node(); list($x, $y, $w, $h) = $frame->get_border_box(); $this->_set_opacity( $frame->get_opacity( $style->opacity ) ); if ( $node->nodeName === "body" ) { $h = $frame->get_containing_block("h") - $style->length_in_pt(array( $style->margin_top, $style->border_top_width, $style->border_bottom_width, $style->margin_bottom), $style->width); } // Handle anchors & links if ( $node->nodeName === "a" && $href = $node->getAttribute("href") ) { $this->_canvas->add_link($href, $x, $y, $w, $h); } // Draw our background, border and content list($tl, $tr, $br, $bl) = $style->get_computed_border_radius($w, $h); if ( $tl + $tr + $br + $bl > 0 ) { $this->_canvas->clipping_roundrectangle( $x, $y, $w, $h, $tl, $tr, $br, $bl ); } if ( ($bg = $style->background_color) !== "transparent" ) { $this->_canvas->filled_rectangle( $x, $y, $w, $h, $bg ); } if ( ($url = $style->background_image) && $url !== "none" ) { $this->_background_image($url, $x, $y, $w, $h, $style); } if ( $tl + $tr + $br + $bl > 0 ) { $this->_canvas->clipping_end(); } $border_box = array($x, $y, $w, $h); $this->_render_border($frame, $border_box); $this->_render_outline($frame, $border_box); if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { $this->_debug_layout($frame->get_border_box(), "red"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5)); } } if (DEBUG_LAYOUT && DEBUG_LAYOUT_LINES && $frame->get_decorator()) { foreach ($frame->get_decorator()->get_line_boxes() as $line) { $frame->_debug_layout(array($line->x, $line->y, $line->w, $line->h), "orange"); } } }
function __construct(Frame $frame, DOMPDF $dompdf) { $style = $frame->get_style(); $url = $style->list_style_image; $frame->get_node()->setAttribute("src", $url); $this->_img = new Image_Frame_Decorator($frame, $dompdf); parent::__construct($this->_img, $dompdf); list($width, $height) = dompdf_getimagesize($this->_img->get_image_url()); $this->_width = (double) rtrim($width, "px") * 72 / DOMPDF_DPI; $this->_height = (double) rtrim($height, "px") * 72 / DOMPDF_DPI; }
/** * Class constructor * * @param Frame $frame the bullet frame to decorate * @param DOMPDF $dompdf the document's dompdf object */ function __construct(Frame $frame, DOMPDF $dompdf) { $url = $frame->get_style()->list_style_image; $frame->get_node()->setAttribute("src", $url); $this->_img = new Image_Frame_Decorator($frame, $dompdf); parent::__construct($this->_img, $dompdf); list($width, $height) = getimagesize($this->_img->get_image_url()); // Resample the bullet image to be consistent with 'auto' sized images $this->_width = (double) rtrim($width, "px") * 72 / DOMPDF_DPI; $this->_height = (double) rtrim($height, "px") * 72 / DOMPDF_DPI; }
function render(Frame $frame) { $style = $frame->get_style(); list($x, $y, $w, $h) = $frame->get_padding_box(); // Draw our background, border and content if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $style->background_color); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } $this->_render_border($frame); }
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); }
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")); // 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) = 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 "none": break; } } }
/** * split this frame at $child. * * The current frame is cloned and $child and all children following * $child are added to the clone. The clone is then passed to the * current frame's parent->split() method. * * @param Frame $child * @param boolean $force_pagebreak */ function split($child = null, $force_pagebreak = false) { if (is_null($child)) { $this->get_parent()->split($this, $force_pagebreak); return; } if ($child->get_parent() !== $this) throw new DOMPDF_Exception("Unable to split: frame is not a child of this one."); $node = $this->_frame->get_node(); // mark the frame as splitted (don't use the find_***_parent cache) //$this->_splitted = true; $split = $this->copy($node->cloneNode()); $split->reset(); $split->get_original_style()->text_indent = 0; // The body's properties must be kept if ($node->nodeName !== "body") { // Style reset on the first and second parts $style = $this->_frame->get_style(); $style->margin_bottom = 0; $style->padding_bottom = 0; $style->border_bottom = 0; // second $orig_style = $split->get_original_style(); $orig_style->text_indent = 0; $orig_style->margin_top = 0; $orig_style->padding_top = 0; $orig_style->border_top = 0; } $this->get_parent()->insert_child_after($split, $this); // Add $frame and all following siblings to the new split node $iter = $child; while ($iter) { $frame = $iter; $iter = $iter->get_next_sibling(); $frame->reset(); $split->append_child($frame); } $this->get_parent()->split($split, $force_pagebreak); }
function render(Frame $frame) { $style = $frame->get_style(); // Draw our background, border and content if (($bg = $style->background_color) !== "transparent") { list($x, $y, $w, $h) = $frame->get_padding_box(); $this->_canvas->filled_rectangle($x, $y, $w, $h, $style->background_color); } // FIXME: need to enable & test this eventually // if ( ($img = $style->background_image) !== "none" ) { // list($x, $y, $w, $h) = $frame->get_padding_box(); // list($bx, $by) = $style->background_position; // $bx = $style->length_in_pt($bx, $w); // $by = $style->length_in_pt($by, $h); // $this->_canvas->image($img, $x + $bx, $y + $by); // } $this->_render_border($frame); }
/** * Parses a CSS "quotes" property * * @return array|null An array of pairs of quotes */ protected function _parse_quotes() { // Matches quote types $re = '/(\'[^\']*\')|(\\"[^\\"]*\\")/'; $quotes = $this->_frame->get_style()->quotes; // split on spaces, except within quotes if (!preg_match_all($re, "{$quotes}", $matches, PREG_SET_ORDER)) { return null; } $quotes_array = array(); foreach ($matches as &$_quote) { $quotes_array[] = $this->_parse_string($_quote[0], true); } if (empty($quotes_array)) { $quotes_array = array('"', '"'); } return array_chunk($quotes_array, 2); }
function render(Frame $frame) { $style = $frame->get_style(); $this->_set_opacity($frame->get_opacity($style->opacity)); $this->_render_border($frame); $this->_render_outline($frame); if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { $this->_debug_layout($frame->get_border_box(), "red"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5)); } } if (DEBUG_LAYOUT && DEBUG_LAYOUT_LINES && $frame->get_decorator()) { foreach ($frame->get_decorator()->get_line_boxes() as $line) { $frame->_debug_layout(array($line->x, $line->y, $line->w, $line->h), "orange"); } } }
function render(Frame $frame) { // Render background & borders $style = $frame->get_style(); $cb = $frame->get_containing_block(); list($x, $y, $w, $h) = $frame->get_border_box(); $this->_set_opacity($frame->get_opacity($style->opacity)); if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } $this->_render_border($frame); $this->_render_outline($frame); 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"]); $src = $frame->get_image_url(); if (Image_Cache::is_broken($src) && ($alt = $frame->get_node()->getAttribute("alt"))) { $font = $style->font_family; $size = $style->font_size; $spacing = $style->word_spacing; $this->_canvas->text($x, $y, $alt, $font, $size, $style->color, $spacing); } else { $this->_canvas->image($src, $x, $y, $w, $h, $style->image_resolution); } if ($msg = $frame->get_image_msg()) { $parts = preg_split("/\\s*\n\\s*/", $msg); $height = 10; $_y = $alt ? $y + $h - count($parts) * $height : $y; foreach ($parts as $i => $_part) { $this->_canvas->text($x, $_y + $i * $height, $_part, "times", $height * 0.8, array(0.5, 0.5, 0.5)); } } if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { $this->_debug_layout($frame->get_border_box(), "blue"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($frame->get_padding_box(), "blue", array(0.5, 0.5)); } } }
function render(Frame $frame) { $style = $frame->get_style(); $line_height = $style->length_in_pt($style->line_height, $frame->get_containing_block("w")); // Handle list-style-image if ($style->list_style_image != "none") { list($x, $y) = $frame->get_position(); $w = $frame->get_width(); $h = $frame->get_height(); $x += $w / 2; $y += $line_height / 2 - $h / 2; $this->_canvas->image($frame->get_image_url(), $frame->get_image_ext(), $x, $y, $w, $h); } else { $bullet_style = $style->list_style_type; $bullet_size = List_Bullet_Frame_Decorator::BULLET_SIZE; $fill = false; switch ($bullet_style) { default: case "disc": $fill = true; case "circle": if (!$fill) { $fill = false; } list($x, $y) = $frame->get_position(); $x += $bullet_size / 2 + List_Bullet_Frame_Decorator::BULLET_PADDING; $y += $line_height - $bullet_size; $r = $bullet_size / 2; $this->_canvas->circle($x, $y, $r, $style->color, 0.2, null, $fill); break; case "square": list($x, $y) = $frame->get_position(); $w = $bullet_size; $x += List_Bullet_Frame_Decorator::BULLET_PADDING; $y += $line_height - $w - List_Bullet_Frame_Decorator::BULLET_PADDING; $this->_canvas->filled_rectangle($x, $y, $w, $w, $style->color); break; } } }
function render(Frame $frame) { // Render background & borders $style = $frame->get_style(); $cb = $frame->get_containing_block(); list($x, $y, $w, $h) = $frame->get_border_box(); $this->_set_opacity($frame->get_opacity($style->opacity)); // Handle the last child 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); } $this->_render_border($frame); $this->_render_outline($frame); 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"]); if (strrpos($frame->get_image_url(), DOMPDF_LIB_DIR . "/res/broken_image.png", 0) !== false && ($alt = $frame->get_node()->getAttribute("alt"))) { $font = $style->font_family; $size = $style->font_size; $spacing = $style->word_spacing; $this->_canvas->text($x, $y, $alt, $font, $size, $style->color, $spacing); } else { $this->_canvas->image($frame->get_image_url(), $frame->get_image_ext(), $x, $y, $w, $h); } if (DEBUG_LAYOUT && DEBUG_LAYOUT_BLOCKS) { $this->_debug_layout($frame->get_border_box(), "blue"); if (DEBUG_LAYOUT_PADDINGBOX) { $this->_debug_layout($frame->get_padding_box(), "blue", array(0.5, 0.5)); } } }
/** * Decorate a Frame * * @param Frame $frame The frame to decorate * @param DOMPDF $dompdf The dompdf instance * @param Frame $root The frame to decorate * * @throws DOMPDF_Exception * @return Frame_Decorator * FIXME: this is admittedly a little smelly... */ static function decorate_frame(Frame $frame, DOMPDF $dompdf, Frame $root = null) { if (is_null($dompdf)) { throw new DOMPDF_Exception("The DOMPDF argument is required"); } $style = $frame->get_style(); // Floating (and more generally out-of-flow) elements are blocks // http://coding.smashingmagazine.com/2007/05/01/css-float-theory-things-you-should-know/ if (!$frame->is_in_flow() && in_array($style->display, Style::$INLINE_TYPES)) { $style->display = "block"; } $display = $style->display; switch ($display) { case "block": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "inline-block": $positioner = "Inline"; $decorator = "Block"; $reflower = "Block"; break; case "inline": $positioner = "Inline"; if ($frame->is_text_node()) { $decorator = "Text"; $reflower = "Text"; } else { $enable_css_float = $dompdf->get_option("enable_css_float"); if ($enable_css_float && $style->float !== "none") { $decorator = "Block"; $reflower = "Block"; } else { $decorator = "Inline"; $reflower = "Inline"; } } break; case "table": $positioner = "Block"; $decorator = "Table"; $reflower = "Table"; break; case "inline-table": $positioner = "Inline"; $decorator = "Table"; $reflower = "Table"; break; case "table-row-group": case "table-header-group": case "table-footer-group": $positioner = "Null"; $decorator = "Table_Row_Group"; $reflower = "Table_Row_Group"; break; case "table-row": $positioner = "Null"; $decorator = "Table_Row"; $reflower = "Table_Row"; break; case "table-cell": $positioner = "Table_Cell"; $decorator = "Table_Cell"; $reflower = "Table_Cell"; break; case "list-item": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "-dompdf-list-bullet": if ($style->list_style_position === "inside") { $positioner = "Inline"; } else { $positioner = "List_Bullet"; } if ($style->list_style_image !== "none") { $decorator = "List_Bullet_Image"; } else { $decorator = "List_Bullet"; } $reflower = "List_Bullet"; break; case "-dompdf-image": $positioner = "Inline"; $decorator = "Image"; $reflower = "Image"; break; case "-dompdf-br": $positioner = "Inline"; $decorator = "Inline"; $reflower = "Inline"; break; default: // FIXME: should throw some sort of warning or something? // FIXME: should throw some sort of warning or something? case "none": if ($style->_dompdf_keep !== "yes") { // Remove the node and the frame $frame->get_parent()->remove_child($frame); return; } $positioner = "Null"; $decorator = "Null"; $reflower = "Null"; break; } // Handle CSS position $position = $style->position; if ($position === "absolute") { $positioner = "Absolute"; } else { if ($position === "fixed") { $positioner = "Fixed"; } } $node = $frame->get_node(); // Handle nodeName if ($node->nodeName === "img") { $style->display = "-dompdf-image"; $decorator = "Image"; $reflower = "Image"; } $positioner .= "_Positioner"; $decorator .= "_Frame_Decorator"; $reflower .= "_Frame_Reflower"; $deco = new $decorator($frame, $dompdf); $deco->set_positioner(new $positioner($deco)); $deco->set_reflower(new $reflower($deco)); if ($root) { $deco->set_root($root); } if ($display === "list-item") { // Insert a list-bullet frame $xml = $dompdf->get_dom(); $bullet_node = $xml->createElement("bullet"); // arbitrary choice $b_f = new Frame($bullet_node); $node = $frame->get_node(); $parent_node = $node->parentNode; if ($parent_node) { if (!$parent_node->hasAttribute("dompdf-children-count")) { $xpath = new DOMXPath($xml); $count = $xpath->query("li", $parent_node)->length; $parent_node->setAttribute("dompdf-children-count", $count); } if (is_numeric($node->getAttribute("value"))) { $index = intval($node->getAttribute("value")); } else { if (!$parent_node->hasAttribute("dompdf-counter")) { $index = $parent_node->hasAttribute("start") ? $parent_node->getAttribute("start") : 1; } else { $index = $parent_node->getAttribute("dompdf-counter") + 1; } } $parent_node->setAttribute("dompdf-counter", $index); $bullet_node->setAttribute("dompdf-counter", $index); } $new_style = $dompdf->get_css()->create_style(); $new_style->display = "-dompdf-list-bullet"; $new_style->inherit($style); $b_f->set_style($new_style); $deco->prepend_child(Frame_Factory::decorate_frame($b_f, $dompdf, $root)); } return $deco; }
public function get_lowest_float_offset(Frame $child) { $style = $child->get_style(); $side = $style->clear; $float = $style->float; $y = 0; foreach ($this->_floating_frames as $key => $frame) { if ($side === "both" || $frame->get_style()->float === $side) { $y = max($y, $frame->get_position("y") + $frame->get_margin_height()); if ($float !== "none") { $this->remove_floating_frame($key); } } } return $y; }
function render(Frame $frame) { $style = $frame->get_style(); if (trim($frame->get_node()->nodeValue) === "" && $style->empty_cells === "hide") { return; } $this->_set_opacity($frame->get_opacity($style->opacity)); list($x, $y, $w, $h) = $frame->get_border_box(); // Draw our background, border and content if (($bg = $style->background_color) !== "transparent") { $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg); } if (($url = $style->background_image) && $url !== "none") { $this->_background_image($url, $x, $y, $w, $h, $style); } $table = Table_Frame_Decorator::find_parent_table($frame); if ($table->get_style()->border_collapse !== "collapse") { $this->_render_border($frame); $this->_render_outline($frame); return; } // The collapsed case is slightly complicated... // @todo Add support for outlines here $cellmap = $table->get_cellmap(); $cells = $cellmap->get_spanned_cells($frame); $num_rows = $cellmap->get_num_rows(); $num_cols = $cellmap->get_num_cols(); // Determine the top row spanned by this cell $i = $cells["rows"][0]; $top_row = $cellmap->get_row($i); // Determine if this cell borders on the bottom of the table. If so, // then we draw its bottom border. Otherwise the next row down will // draw its top border instead. if (in_array($num_rows - 1, $cells["rows"])) { $draw_bottom = true; $bottom_row = $cellmap->get_row($num_rows - 1); } else { $draw_bottom = false; } // Draw the horizontal borders foreach ($cells["columns"] as $j) { $bp = $cellmap->get_border_properties($i, $j); $y = $top_row["y"] - $bp["top"]["width"] / 2; $col = $cellmap->get_column($j); $x = $col["x"] - $bp["left"]["width"] / 2; $w = $col["used-width"] + ($bp["left"]["width"] + $bp["right"]["width"]) / 2; if ($bp["top"]["style"] !== "none" && $bp["top"]["width"] > 0) { $widths = array($bp["top"]["width"], $bp["right"]["width"], $bp["bottom"]["width"], $bp["left"]["width"]); $method = "_border_" . $bp["top"]["style"]; $this->{$method}($x, $y, $w, $bp["top"]["color"], $widths, "top", "square"); } if ($draw_bottom) { $bp = $cellmap->get_border_properties($num_rows - 1, $j); if ($bp["bottom"]["style"] === "none" || $bp["bottom"]["width"] <= 0) { continue; } $y = $bottom_row["y"] + $bottom_row["height"] + $bp["bottom"]["width"] / 2; $widths = array($bp["top"]["width"], $bp["right"]["width"], $bp["bottom"]["width"], $bp["left"]["width"]); $method = "_border_" . $bp["bottom"]["style"]; $this->{$method}($x, $y, $w, $bp["bottom"]["color"], $widths, "bottom", "square"); } } $j = $cells["columns"][0]; $left_col = $cellmap->get_column($j); if (in_array($num_cols - 1, $cells["columns"])) { $draw_right = true; $right_col = $cellmap->get_column($num_cols - 1); } else { $draw_right = false; } // Draw the vertical borders foreach ($cells["rows"] as $i) { $bp = $cellmap->get_border_properties($i, $j); $x = $left_col["x"] - $bp["left"]["width"] / 2; $row = $cellmap->get_row($i); $y = $row["y"] - $bp["top"]["width"] / 2; $h = $row["height"] + ($bp["top"]["width"] + $bp["bottom"]["width"]) / 2; if ($bp["left"]["style"] !== "none" && $bp["left"]["width"] > 0) { $widths = array($bp["top"]["width"], $bp["right"]["width"], $bp["bottom"]["width"], $bp["left"]["width"]); $method = "_border_" . $bp["left"]["style"]; $this->{$method}($x, $y, $h, $bp["left"]["color"], $widths, "left", "square"); } if ($draw_right) { $bp = $cellmap->get_border_properties($i, $num_cols - 1); if ($bp["right"]["style"] === "none" || $bp["right"]["width"] <= 0) { continue; } $x = $right_col["x"] + $right_col["used-width"] + $bp["right"]["width"] / 2; $widths = array($bp["top"]["width"], $bp["right"]["width"], $bp["bottom"]["width"], $bp["left"]["width"]); $method = "_border_" . $bp["right"]["style"]; $this->{$method}($x, $y, $h, $bp["right"]["color"], $widths, "right", "square"); } } }
function process_float(Frame $child, $cb_x, $cb_w) { if (!DOMPDF_ENABLE_CSS_FLOAT) { return; } $child_style = $child->get_style(); $root = $this->_frame->get_root(); // Handle "float" if ($child_style->float !== "none") { $root->add_floating_frame($child); // Remove next frame's beginning whitespace $next = $child->get_next_sibling(); if ($next && $next instanceof Text_Frame_Decorator) { $next->set_text(ltrim($next->get_text())); } $line_box = $this->_frame->get_current_line_box(); list($old_x, $old_y) = $child->get_position(); $float_x = $cb_x; $float_y = $old_y; $float_w = $child->get_margin_width(); if ($child_style->clear === "none") { switch ($child_style->float) { case "left": $float_x += $line_box->left; break; case "right": $float_x += $cb_w - $line_box->right - $float_w; break; } } else { if ($child_style->float === "right") { $float_x += $cb_w - $float_w; } } $line_box->get_float_offsets(); if ($child->_float_next_line) { $float_y += $line_box->h; } $child->set_position($float_x, $float_y); $child->move($float_x - $old_x, $float_y - $old_y, true); } }
/** * 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; }
static function decorate_frame(Frame $frame, $dompdf) { if (is_null($dompdf)) { throw new Exception("foo"); } switch ($frame->get_style()->display) { case "block": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "inline-block": $positioner = "Inline"; $decorator = "Block"; $reflower = "Block"; break; case "inline": $positioner = "Inline"; if ($frame->get_node()->nodeName == "#text") { $decorator = "Text"; $reflower = "Text"; } else { $decorator = "Inline"; $reflower = "Inline"; } break; case "table": $positioner = "Block"; $decorator = "Table"; $reflower = "Table"; break; case "inline-table": $positioner = "Inline"; $decorator = "Table"; $reflower = "Table"; break; case "table-row-group": case "table-header-group": case "table-footer-group": $positioner = "Null"; $decorator = "Table_Row_Group"; $reflower = "Table_Row_Group"; break; case "table-row": $positioner = "Null"; $decorator = "Table_Row"; $reflower = "Table_Row"; break; case "table-cell": $positioner = "Table_Cell"; $decorator = "Table_Cell"; $reflower = "Table_Cell"; break; case "list-item": $positioner = "Block"; $decorator = "Block"; $reflower = "Block"; break; case "-dompdf-list-bullet": if ($frame->get_style()->list_style_position == "inside") { $positioner = "Inline"; } else { $positioner = "List_Bullet"; } if ($frame->get_style()->list_style_image != "none") { $decorator = "List_Bullet_Image"; } else { $decorator = "List_Bullet"; } $reflower = "List_Bullet"; break; case "-dompdf-image": $positioner = "Inline"; $decorator = "Image"; $reflower = "Image"; break; case "-dompdf-br": $positioner = "Inline"; $decorator = "Inline"; $reflower = "Inline"; break; default: // FIXME: should throw some sort of warning or something? // FIXME: should throw some sort of warning or something? case "none": $positioner = "Null"; $decorator = "Null"; $reflower = "Null"; break; } $positioner .= "_Positioner"; $decorator .= "_Frame_Decorator"; $reflower .= "_Frame_Reflower"; $deco = new $decorator($frame, $dompdf); $deco->set_positioner(new $positioner($deco)); $reflow = new $reflower($deco); // Generated content is a special case if ($frame->get_node()->nodeName == "_dompdf_generated") { // Decorate the reflower $gen = new Generated_Frame_Reflower($deco); $gen->set_reflower($reflow); $reflow = $gen; } $deco->set_reflower($reflow); // Images are a special case // if ( $frame->get_node()->nodeName == "img" ) { // // FIXME: This is a hack // $node =$frame->get_node()->ownerDocument->createElement("img_sub"); // $node->setAttribute("src", $frame->get_node()->getAttribute("src")); // $img_frame = new Frame( $node ); // $style = $frame->get_style()->get_stylesheet()->create_style(); // $style->inherit($frame->get_style()); // $img_frame->set_style( $style ); // $img_deco = new Image_Frame_Decorator($img_frame, $dompdf); // $img_deco->set_reflower( new Image_Frame_Reflower($img_deco) ); // $deco->append_child($img_deco); // } return $deco; }
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); } } }