"Fossies" - the Fresh Open Source Software Archive

Member "icingaweb2-2.11.4/library/vendor/dompdf/vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php" (26 Jan 2023, 22554 Bytes) of package /linux/www/icingaweb2-2.11.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 <?php
    2 /**
    3  * @package dompdf
    4  * @link    https://github.com/dompdf/dompdf
    5  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
    6  */
    7 namespace Dompdf\FrameReflower;
    8 
    9 use Dompdf\Dompdf;
   10 use Dompdf\Helpers;
   11 use Dompdf\Frame;
   12 use Dompdf\Frame\Factory;
   13 use Dompdf\FrameDecorator\AbstractFrameDecorator;
   14 use Dompdf\FrameDecorator\Block;
   15 
   16 /**
   17  * Base reflower class
   18  *
   19  * Reflower objects are responsible for determining the width and height of
   20  * individual frames.  They also create line and page breaks as necessary.
   21  *
   22  * @package dompdf
   23  */
   24 abstract class AbstractFrameReflower
   25 {
   26 
   27     /**
   28      * Frame for this reflower
   29      *
   30      * @var AbstractFrameDecorator
   31      */
   32     protected $_frame;
   33 
   34     /**
   35      * Cached min/max child size
   36      *
   37      * @var array
   38      */
   39     protected $_min_max_child_cache;
   40 
   41     /**
   42      * Cached min/max size
   43      *
   44      * @var array
   45      */
   46     protected $_min_max_cache;
   47 
   48     /**
   49      * AbstractFrameReflower constructor.
   50      * @param AbstractFrameDecorator $frame
   51      */
   52     function __construct(AbstractFrameDecorator $frame)
   53     {
   54         $this->_frame = $frame;
   55         $this->_min_max_child_cache = null;
   56         $this->_min_max_cache = null;
   57     }
   58 
   59     /**
   60      * @return Dompdf
   61      */
   62     function get_dompdf()
   63     {
   64         return $this->_frame->get_dompdf();
   65     }
   66 
   67     public function reset(): void
   68     {
   69         $this->_min_max_child_cache = null;
   70         $this->_min_max_cache = null;
   71     }
   72 
   73     /**
   74      * Determine the actual containing block for absolute and fixed position.
   75      *
   76      * https://www.w3.org/TR/CSS21/visudet.html#containing-block-details
   77      */
   78     protected function determine_absolute_containing_block(): void
   79     {
   80         $frame = $this->_frame;
   81         $style = $frame->get_style();
   82 
   83         switch ($style->position) {
   84             case "absolute":
   85                 $parent = $frame->find_positioned_parent();
   86                 if ($parent !== $frame->get_root()) {
   87                     $parent_style = $parent->get_style();
   88                     $parent_padding_box = $parent->get_padding_box();
   89                     //FIXME: an accurate measure of the positioned parent height
   90                     //       is not possible until reflow has completed;
   91                     //       we'll fall back to the parent's containing block,
   92                     //       which is wrong for auto-height parents
   93                     if ($parent_style->height === "auto") {
   94                         $parent_containing_block = $parent->get_containing_block();
   95                         $containing_block_height = $parent_containing_block["h"] -
   96                             (float)$parent_style->length_in_pt([
   97                                 $parent_style->margin_top,
   98                                 $parent_style->margin_bottom,
   99                                 $parent_style->border_top_width,
  100                                 $parent_style->border_bottom_width
  101                             ], $parent_containing_block["w"]);
  102                     } else {
  103                         $containing_block_height = $parent_padding_box["h"];
  104                     }
  105                     $frame->set_containing_block($parent_padding_box["x"], $parent_padding_box["y"], $parent_padding_box["w"], $containing_block_height);
  106                     break;
  107                 }
  108             case "fixed":
  109                 $initial_cb = $frame->get_root()->get_first_child()->get_containing_block();
  110                 $frame->set_containing_block($initial_cb["x"], $initial_cb["y"], $initial_cb["w"], $initial_cb["h"]);
  111                 break;
  112             default:
  113                 // Nothing to do, containing block already set via parent
  114                 break;
  115         }
  116     }
  117 
  118     /**
  119      * Collapse frames margins
  120      * http://www.w3.org/TR/CSS21/box.html#collapsing-margins
  121      */
  122     protected function _collapse_margins(): void
  123     {
  124         $frame = $this->_frame;
  125 
  126         // Margins of float/absolutely positioned/inline-level elements do not collapse
  127         if (!$frame->is_in_flow() || $frame->is_inline_level()
  128             || $frame->get_root() === $frame || $frame->get_parent() === $frame->get_root()
  129         ) {
  130             return;
  131         }
  132 
  133         $cb = $frame->get_containing_block();
  134         $style = $frame->get_style();
  135 
  136         $t = $style->length_in_pt($style->margin_top, $cb["w"]);
  137         $b = $style->length_in_pt($style->margin_bottom, $cb["w"]);
  138 
  139         // Handle 'auto' values
  140         if ($t === "auto") {
  141             $style->set_used("margin_top", 0.0);
  142             $t = 0.0;
  143         }
  144 
  145         if ($b === "auto") {
  146             $style->set_used("margin_bottom", 0.0);
  147             $b = 0.0;
  148         }
  149 
  150         // Collapse vertical margins:
  151         $n = $frame->get_next_sibling();
  152         if ( $n && !($n->is_block_level() && $n->is_in_flow()) ) {
  153             while ($n = $n->get_next_sibling()) {
  154                 if ($n->is_block_level() && $n->is_in_flow()) {
  155                     break;
  156                 }
  157 
  158                 if (!$n->get_first_child()) {
  159                     $n = null;
  160                     break;
  161                 }
  162             }
  163         }
  164 
  165         if ($n) {
  166             $n_style = $n->get_style();
  167             $n_t = (float)$n_style->length_in_pt($n_style->margin_top, $cb["w"]);
  168 
  169             $b = $this->get_collapsed_margin_length($b, $n_t);
  170             $style->set_used("margin_bottom", $b);
  171             $n_style->set_used("margin_top", 0.0);
  172         }
  173 
  174         // Collapse our first child's margin, if there is no border or padding
  175         if ($style->border_top_width == 0 && $style->length_in_pt($style->padding_top) == 0) {
  176             $f = $this->_frame->get_first_child();
  177             if ( $f && !($f->is_block_level() && $f->is_in_flow()) ) {
  178                 while ($f = $f->get_next_sibling()) {
  179                     if ($f->is_block_level() && $f->is_in_flow()) {
  180                         break;
  181                     }
  182 
  183                     if (!$f->get_first_child()) {
  184                         $f = null;
  185                         break;
  186                     }
  187                 }
  188             }
  189 
  190             // Margins are collapsed only between block-level boxes
  191             if ($f) {
  192                 $f_style = $f->get_style();
  193                 $f_t = (float)$f_style->length_in_pt($f_style->margin_top, $cb["w"]);
  194 
  195                 $t = $this->get_collapsed_margin_length($t, $f_t);
  196                 $style->set_used("margin_top", $t);
  197                 $f_style->set_used("margin_top", 0.0);
  198             }
  199         }
  200 
  201         // Collapse our last child's margin, if there is no border or padding
  202         if ($style->border_bottom_width == 0 && $style->length_in_pt($style->padding_bottom) == 0) {
  203             $l = $this->_frame->get_last_child();
  204             if ( $l && !($l->is_block_level() && $l->is_in_flow()) ) {
  205                 while ($l = $l->get_prev_sibling()) {
  206                     if ($l->is_block_level() && $l->is_in_flow()) {
  207                         break;
  208                     }
  209 
  210                     if (!$l->get_last_child()) {
  211                         $l = null;
  212                         break;
  213                     }
  214                 }
  215             }
  216 
  217             // Margins are collapsed only between block-level boxes
  218             if ($l) {
  219                 $l_style = $l->get_style();
  220                 $l_b = (float)$l_style->length_in_pt($l_style->margin_bottom, $cb["w"]);
  221 
  222                 $b = $this->get_collapsed_margin_length($b, $l_b);
  223                 $style->set_used("margin_bottom", $b);
  224                 $l_style->set_used("margin_bottom", 0.0);
  225             }
  226         }
  227     }
  228 
  229     /**
  230      * Get the combined (collapsed) length of two adjoining margins.
  231      *
  232      * See http://www.w3.org/TR/CSS21/box.html#collapsing-margins.
  233      *
  234      * @param float $l1
  235      * @param float $l2
  236      *
  237      * @return float
  238      */
  239     private function get_collapsed_margin_length(float $l1, float $l2): float
  240     {
  241         if ($l1 < 0 && $l2 < 0) {
  242             return min($l1, $l2); // min(x, y) = - max(abs(x), abs(y)), if x < 0 && y < 0
  243         }
  244         
  245         if ($l1 < 0 || $l2 < 0) {
  246             return $l1 + $l2; // x + y = x - abs(y), if y < 0
  247         }
  248         
  249         return max($l1, $l2);
  250     }
  251 
  252     /**
  253      * Handle relative positioning according to
  254      * https://www.w3.org/TR/CSS21/visuren.html#relative-positioning.
  255      *
  256      * @param AbstractFrameDecorator $frame The frame to handle.
  257      */
  258     protected function position_relative(AbstractFrameDecorator $frame): void
  259     {
  260         $style = $frame->get_style();
  261 
  262         if ($style->position === "relative") {
  263             $cb = $frame->get_containing_block();
  264             $top = $style->length_in_pt($style->top, $cb["h"]);
  265             $right = $style->length_in_pt($style->right, $cb["w"]);
  266             $bottom = $style->length_in_pt($style->bottom, $cb["h"]);
  267             $left = $style->length_in_pt($style->left, $cb["w"]);
  268 
  269             // FIXME RTL case:
  270             // if ($left !== "auto" && $right !== "auto") $left = -$right;
  271             if ($left === "auto" && $right === "auto") {
  272                 $left = 0;
  273             } elseif ($left === "auto") {
  274                 $left = -$right;
  275             }
  276 
  277             if ($top === "auto" && $bottom === "auto") {
  278                 $top = 0;
  279             } elseif ($top === "auto") {
  280                 $top = -$bottom;
  281             }
  282 
  283             $frame->move($left, $top);
  284         }
  285     }
  286 
  287     /**
  288      * @param Block|null $block
  289      */
  290     abstract function reflow(Block $block = null);
  291 
  292     /**
  293      * Resolve the `min-width` property.
  294      *
  295      * Resolves to 0 if not set or if a percentage and the containing-block
  296      * width is not defined.
  297      *
  298      * @param float|null $cbw Width of the containing block.
  299      *
  300      * @return float
  301      */
  302     protected function resolve_min_width(?float $cbw): float
  303     {
  304         $style = $this->_frame->get_style();
  305         $min_width = $style->min_width;
  306 
  307         return $min_width !== "auto"
  308             ? $style->length_in_pt($min_width, $cbw ?? 0)
  309             : 0.0;
  310     }
  311 
  312     /**
  313      * Resolve the `max-width` property.
  314      *
  315      * Resolves to `INF` if not set or if a percentage and the containing-block
  316      * width is not defined.
  317      *
  318      * @param float|null $cbw Width of the containing block.
  319      *
  320      * @return float
  321      */
  322     protected function resolve_max_width(?float $cbw): float
  323     {
  324         $style = $this->_frame->get_style();
  325         $max_width = $style->max_width;
  326 
  327         return $max_width !== "none"
  328             ? $style->length_in_pt($max_width, $cbw ?? INF)
  329             : INF;
  330     }
  331 
  332     /**
  333      * Resolve the `min-height` property.
  334      *
  335      * Resolves to 0 if not set or if a percentage and the containing-block
  336      * height is not defined.
  337      *
  338      * @param float|null $cbh Height of the containing block.
  339      *
  340      * @return float
  341      */
  342     protected function resolve_min_height(?float $cbh): float
  343     {
  344         $style = $this->_frame->get_style();
  345         $min_height = $style->min_height;
  346 
  347         return $min_height !== "auto"
  348             ? $style->length_in_pt($min_height, $cbh ?? 0)
  349             : 0.0;
  350     }
  351 
  352     /**
  353      * Resolve the `max-height` property.
  354      *
  355      * Resolves to `INF` if not set or if a percentage and the containing-block
  356      * height is not defined.
  357      *
  358      * @param float|null $cbh Height of the containing block.
  359      *
  360      * @return float
  361      */
  362     protected function resolve_max_height(?float $cbh): float
  363     {
  364         $style = $this->_frame->get_style();
  365         $max_height = $style->max_height;
  366 
  367         return $max_height !== "none"
  368             ? $style->length_in_pt($style->max_height, $cbh ?? INF)
  369             : INF;
  370     }
  371 
  372     /**
  373      * Get the minimum and maximum preferred width of the contents of the frame,
  374      * as requested by its children.
  375      *
  376      * @return array A two-element array of min and max width.
  377      */
  378     public function get_min_max_child_width(): array
  379     {
  380         if (!is_null($this->_min_max_child_cache)) {
  381             return $this->_min_max_child_cache;
  382         }
  383 
  384         $low = [];
  385         $high = [];
  386 
  387         for ($iter = $this->_frame->get_children(); $iter->valid(); $iter->next()) {
  388             $inline_min = 0;
  389             $inline_max = 0;
  390 
  391             // Add all adjacent inline widths together to calculate max width
  392             while ($iter->valid() && ($iter->current()->is_inline_level() || $iter->current()->get_style()->display === "-dompdf-image")) {
  393                 /** @var AbstractFrameDecorator */
  394                 $child = $iter->current();
  395                 $child->get_reflower()->_set_content();
  396                 $minmax = $child->get_min_max_width();
  397 
  398                 if (in_array($child->get_style()->white_space, ["pre", "nowrap"], true)) {
  399                     $inline_min += $minmax["min"];
  400                 } else {
  401                     $low[] = $minmax["min"];
  402                 }
  403 
  404                 $inline_max += $minmax["max"];
  405                 $iter->next();
  406             }
  407 
  408             if ($inline_min > 0) {
  409                 $low[] = $inline_min;
  410             }
  411             if ($inline_max > 0) {
  412                 $high[] = $inline_max;
  413             }
  414 
  415             // Skip children with absolute position
  416             if ($iter->valid() && !$iter->current()->is_absolute()) {
  417                 /** @var AbstractFrameDecorator */
  418                 $child = $iter->current();
  419                 $child->get_reflower()->_set_content();
  420                 list($low[], $high[]) = $child->get_min_max_width();
  421             }
  422         }
  423 
  424         $min = count($low) ? max($low) : 0;
  425         $max = count($high) ? max($high) : 0;
  426 
  427         return $this->_min_max_child_cache = [$min, $max];
  428     }
  429 
  430     /**
  431      * Get the minimum and maximum preferred content-box width of the frame.
  432      *
  433      * @return array A two-element array of min and max width.
  434      */
  435     public function get_min_max_content_width(): array
  436     {
  437         return $this->get_min_max_child_width();
  438     }
  439 
  440     /**
  441      * Get the minimum and maximum preferred border-box width of the frame.
  442      *
  443      * Required for shrink-to-fit width calculation, as used in automatic table
  444      * layout, absolute positioning, float and inline-block. This provides a
  445      * basic implementation. Child classes should override this or
  446      * `get_min_max_content_width` as necessary.
  447      *
  448      * @return array An array `[0 => min, 1 => max, "min" => min, "max" => max]`
  449      *         of min and max width.
  450      */
  451     public function get_min_max_width(): array
  452     {
  453         if (!is_null($this->_min_max_cache)) {
  454             return $this->_min_max_cache;
  455         }
  456 
  457         $style = $this->_frame->get_style();
  458         [$min, $max] = $this->get_min_max_content_width();
  459 
  460         // Account for margins, borders, and padding
  461         $dims = [
  462             $style->padding_left,
  463             $style->padding_right,
  464             $style->border_left_width,
  465             $style->border_right_width,
  466             $style->margin_left,
  467             $style->margin_right
  468         ];
  469 
  470         // The containing block is not defined yet, treat percentages as 0
  471         $delta = (float) $style->length_in_pt($dims, 0);
  472         $min += $delta;
  473         $max += $delta;
  474 
  475         return $this->_min_max_cache = [$min, $max, "min" => $min, "max" => $max];
  476     }
  477 
  478     /**
  479      * Parses a CSS string containing quotes and escaped hex characters
  480      *
  481      * @param $string string The CSS string to parse
  482      * @param $single_trim
  483      * @return string
  484      */
  485     protected function _parse_string($string, $single_trim = false)
  486     {
  487         if ($single_trim) {
  488             $string = preg_replace('/^[\"\']/', "", $string);
  489             $string = preg_replace('/[\"\']$/', "", $string);
  490         } else {
  491             $string = trim($string, "'\"");
  492         }
  493 
  494         $string = str_replace(["\\\n", '\\"', "\\'"],
  495             ["", '"', "'"], $string);
  496 
  497         // Convert escaped hex characters into ascii characters (e.g. \A => newline)
  498         $string = preg_replace_callback("/\\\\([0-9a-fA-F]{0,6})/",
  499             function ($matches) { return \Dompdf\Helpers::unichr(hexdec($matches[1])); },
  500             $string);
  501         return $string;
  502     }
  503 
  504     /**
  505      * Parses a CSS "quotes" property
  506      *
  507      * https://www.w3.org/TR/css-content-3/#quotes
  508      *
  509      * @return array An array of pairs of quotes
  510      */
  511     protected function _parse_quotes(): array
  512     {
  513         $quotes = $this->_frame->get_style()->quotes;
  514 
  515         if ($quotes === "none") {
  516             return [];
  517         }
  518 
  519         if ($quotes === "auto") {
  520             // TODO: Use typographically appropriate quotes for the current
  521             // language here
  522             return [['"', '"'], ["'", "'"]];
  523         }
  524 
  525         // Matches quote types
  526         $re = '/(\'[^\']*\')|(\"[^\"]*\")/';
  527 
  528         // Split on spaces, except within quotes
  529         if (!preg_match_all($re, $quotes, $matches, PREG_SET_ORDER)) {
  530             return [];
  531         }
  532 
  533         $quotes_array = [];
  534         foreach ($matches as $_quote) {
  535             $quotes_array[] = $this->_parse_string($_quote[0], true);
  536         }
  537 
  538         return array_chunk($quotes_array, 2);
  539     }
  540 
  541     /**
  542      * Parses the CSS "content" property
  543      *
  544      * https://www.w3.org/TR/CSS21/generate.html#content
  545      *
  546      * @return string The resulting string
  547      */
  548     protected function _parse_content(): string
  549     {
  550         $style = $this->_frame->get_style();
  551         $content = $style->content;
  552 
  553         if ($content === "normal" || $content === "none") {
  554             return "";
  555         }
  556 
  557         $quotes = $this->_parse_quotes();
  558         $text = "";
  559 
  560         foreach ($content as $val) {
  561             // String
  562             if (in_array(mb_substr($val, 0, 1), ['"', "'"], true)) {
  563                 $text .= $this->_parse_string($val);
  564                 continue;
  565             }
  566 
  567             $val = mb_strtolower($val);
  568 
  569             // Keywords
  570             if ($val === "open-quote") {
  571                 // FIXME: Take quotation depth into account
  572                 if (isset($quotes[0][0])) {
  573                     $text .= $quotes[0][0];
  574                 }
  575                 continue;
  576             } elseif ($val === "close-quote") {
  577                 // FIXME: Take quotation depth into account
  578                 if (isset($quotes[0][1])) {
  579                     $text .= $quotes[0][1];
  580                 }
  581                 continue;
  582             } elseif ($val === "no-open-quote") {
  583                 // FIXME: Increment quotation depth
  584                 continue;
  585             } elseif ($val === "no-close-quote") {
  586                 // FIXME: Decrement quotation depth
  587                 continue;
  588             }
  589 
  590             // attr()
  591             if (mb_substr($val, 0, 5) === "attr(") {
  592                 $i = mb_strpos($val, ")");
  593                 if ($i === false) {
  594                     continue;
  595                 }
  596 
  597                 $attr = trim(mb_substr($val, 5, $i - 5));
  598                 if ($attr === "") {
  599                     continue;
  600                 }
  601 
  602                 $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr);
  603                 continue;
  604             }
  605 
  606             // counter()/counters()
  607             if (mb_substr($val, 0, 7) === "counter") {
  608                 // Handle counter() references:
  609                 // http://www.w3.org/TR/CSS21/generate.html#content
  610 
  611                 $i = mb_strpos($val, ")");
  612                 if ($i === false) {
  613                     continue;
  614                 }
  615 
  616                 preg_match('/(counters?)(^\()*?\(\s*([^\s,]+)\s*(,\s*["\']?([^"\'\)]*)["\']?\s*(,\s*([^\s)]+)\s*)?)?\)/i', $val, $args);
  617                 $counter_id = $args[3];
  618 
  619                 if (strtolower($args[1]) === "counter") {
  620                     // counter(name [,style])
  621                     if (isset($args[5])) {
  622                         $type = trim($args[5]);
  623                     } else {
  624                         $type = "decimal";
  625                     }
  626                     $p = $this->_frame->lookup_counter_frame($counter_id);
  627 
  628                     $text .= $p->counter_value($counter_id, $type);
  629                 } elseif (strtolower($args[1]) === "counters") {
  630                     // counters(name, string [,style])
  631                     if (isset($args[5])) {
  632                         $string = $this->_parse_string($args[5]);
  633                     } else {
  634                         $string = "";
  635                     }
  636 
  637                     if (isset($args[7])) {
  638                         $type = trim($args[7]);
  639                     } else {
  640                         $type = "decimal";
  641                     }
  642 
  643                     $p = $this->_frame->lookup_counter_frame($counter_id);
  644                     $tmp = [];
  645                     while ($p) {
  646                         // We only want to use the counter values when they actually increment the counter
  647                         if (array_key_exists($counter_id, $p->_counters)) {
  648                             array_unshift($tmp, $p->counter_value($counter_id, $type));
  649                         }
  650                         $p = $p->lookup_counter_frame($counter_id);
  651                     }
  652                     $text .= implode($string, $tmp);
  653                 } else {
  654                     // countertops?
  655                 }
  656 
  657                 continue;
  658             }
  659         }
  660 
  661         return $text;
  662     }
  663 
  664     /**
  665      * Handle counters and set generated content if the frame is a
  666      * generated-content frame.
  667      */
  668     protected function _set_content(): void
  669     {
  670         $frame = $this->_frame;
  671 
  672         if ($frame->content_set) {
  673             return;
  674         }
  675 
  676         $style = $frame->get_style();
  677 
  678         if (($reset = $style->counter_reset) !== "none") {
  679             $frame->reset_counters($reset);
  680         }
  681 
  682         if (($increment = $style->counter_increment) !== "none") {
  683             $frame->increment_counters($increment);
  684         }
  685 
  686         if ($frame->get_node()->nodeName === "dompdf_generated") {
  687             $content = $this->_parse_content();
  688 
  689             if ($content !== "") {
  690                 $node = $frame->get_node()->ownerDocument->createTextNode($content);
  691 
  692                 $new_style = $style->get_stylesheet()->create_style();
  693                 $new_style->inherit($style);
  694 
  695                 $new_frame = new Frame($node);
  696                 $new_frame->set_style($new_style);
  697 
  698                 Factory::decorate_frame($new_frame, $frame->get_dompdf(), $frame->get_root());
  699                 $frame->append_child($new_frame);
  700             }
  701         }
  702 
  703         $frame->content_set = true;
  704     }
  705 }