Exemple #1
0
 function __construct(Frame $frame, Dompdf $dompdf)
 {
     if (!$frame->is_text_node()) {
         throw new Exception("Text_Decorator can only be applied to #text nodes.");
     }
     parent::__construct($frame, $dompdf);
     $this->_text_spacing = null;
 }
Exemple #2
0
 /**
  * Decorate a Frame
  *
  * @param Frame $frame   The frame to decorate
  * @param Dompdf $dompdf The dompdf instance
  * @param Frame $root    The frame to decorate
  *
  * @throws Exception
  * @return AbstractFrameDecorator
  * FIXME: this is admittedly a little smelly...
  */
 static function decorate_frame(Frame $frame, Dompdf $dompdf, Frame $root = null)
 {
     if (is_null($dompdf)) {
         throw new 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 = "TableRowGroup";
             $reflower = "TableRowGroup";
             break;
         case "table-row":
             $positioner = "Null";
             $decorator = "TableRow";
             $reflower = "TableRow";
             break;
         case "table-cell":
             $positioner = "TableCell";
             $decorator = "TableCell";
             $reflower = "TableCell";
             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 = "ListBullet";
             }
             if ($style->list_style_image !== "none") {
                 $decorator = "ListBulletImage";
             } else {
                 $decorator = "ListBullet";
             }
             $reflower = "ListBullet";
             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 = "Dompdf\\Positioner\\{$positioner}";
     $decorator = "Dompdf\\FrameDecorator\\{$decorator}";
     $reflower = "Dompdf\\FrameReflower\\{$reflower}";
     /** @var AbstractFrameDecorator $deco */
     $deco = new $decorator($frame, $dompdf);
     $deco->set_positioner(new $positioner($deco));
     $deco->set_reflower(new $reflower($deco, $dompdf->getFontMetrics()));
     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(Factory::decorate_frame($b_f, $dompdf, $root));
     }
     return $deco;
 }
Exemple #3
0
 /**
  * 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;
                 }
             }
         }
     }
 }