"Fossies" - the Fresh Open Source Software Archive

Member "icingaweb2-2.9.1/library/vendor/dompdf/src/FrameReflower/AbstractFrameReflower.php" (27 Jul 2021, 17038 Bytes) of package /linux/www/icingaweb2-2.9.1.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    http://dompdf.github.com/
    5  * @author  Benj Carson <benjcarson@digitaljunkies.ca>
    6  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
    7  */
    8 namespace Dompdf\FrameReflower;
    9 
   10 use Dompdf\Adapter\CPDF;
   11 use Dompdf\Css\Style;
   12 use Dompdf\Dompdf;
   13 use Dompdf\Helpers;
   14 use Dompdf\Frame;
   15 use Dompdf\FrameDecorator\Block;
   16 use Dompdf\Frame\Factory;
   17 
   18 /**
   19  * Base reflower class
   20  *
   21  * Reflower objects are responsible for determining the width and height of
   22  * individual frames.  They also create line and page breaks as necessary.
   23  *
   24  * @package dompdf
   25  */
   26 abstract class AbstractFrameReflower
   27 {
   28 
   29     /**
   30      * Frame for this reflower
   31      *
   32      * @var Frame
   33      */
   34     protected $_frame;
   35 
   36     /**
   37      * Cached min/max size
   38      *
   39      * @var array
   40      */
   41     protected $_min_max_cache;
   42 
   43     /**
   44      * AbstractFrameReflower constructor.
   45      * @param Frame $frame
   46      */
   47     function __construct(Frame $frame)
   48     {
   49         $this->_frame = $frame;
   50         $this->_min_max_cache = null;
   51     }
   52 
   53     function dispose()
   54     {
   55     }
   56 
   57     /**
   58      * @return Dompdf
   59      */
   60     function get_dompdf()
   61     {
   62         return $this->_frame->get_dompdf();
   63     }
   64 
   65     /**
   66      * Collapse frames margins
   67      * http://www.w3.org/TR/CSS2/box.html#collapsing-margins
   68      */
   69     protected function _collapse_margins()
   70     {
   71         $frame = $this->_frame;
   72         $cb = $frame->get_containing_block();
   73         $style = $frame->get_style();
   74 
   75         // Margins of float/absolutely positioned/inline-block elements do not collapse.
   76         if (!$frame->is_in_flow() || $frame->is_inline_block()) {
   77             return;
   78         }
   79 
   80         $t = $style->length_in_pt($style->margin_top, $cb["h"]);
   81         $b = $style->length_in_pt($style->margin_bottom, $cb["h"]);
   82 
   83         // Handle 'auto' values
   84         if ($t === "auto") {
   85             $style->margin_top = "0pt";
   86             $t = 0;
   87         }
   88 
   89         if ($b === "auto") {
   90             $style->margin_bottom = "0pt";
   91             $b = 0;
   92         }
   93 
   94         // Collapse vertical margins:
   95         $n = $frame->get_next_sibling();
   96         if ( $n && !$n->is_block() & !$n->is_table() ) {
   97             while ($n = $n->get_next_sibling()) {
   98                 if ($n->is_block() || $n->is_table()) {
   99                     break;
  100                 }
  101 
  102                 if (!$n->get_first_child()) {
  103                     $n = null;
  104                     break;
  105                 }
  106             }
  107         }
  108 
  109         if ($n) {
  110             $n_style = $n->get_style();
  111             $n_t = (float)$n_style->length_in_pt($n_style->margin_top, $cb["h"]);
  112 
  113             $b = $this->_get_collapsed_margin_length($b, $n_t);
  114             $style->margin_bottom = $b . "pt";
  115             $n_style->margin_top = "0pt";
  116         }
  117 
  118         // Collapse our first child's margin, if there is no border or padding
  119         if ($style->border_top_width == 0 && $style->length_in_pt($style->padding_top) == 0) {
  120             $f = $this->_frame->get_first_child();
  121             if ( $f && !$f->is_block() && !$f->is_table() ) {
  122                 while ( $f = $f->get_next_sibling() ) {
  123                     if ( $f->is_block() || $f->is_table() ) {
  124                         break;
  125                     }
  126 
  127                     if ( !$f->get_first_child() ) {
  128                         $f = null;
  129                         break;
  130                     }
  131                 }
  132             }
  133 
  134             // Margin are collapsed only between block-level boxes
  135             if ($f) {
  136                 $f_style = $f->get_style();
  137                 $f_t = (float)$f_style->length_in_pt($f_style->margin_top, $cb["h"]);
  138 
  139                 $t = $this->_get_collapsed_margin_length($t, $f_t);
  140                 $style->margin_top = $t."pt";
  141                 $f_style->margin_top = "0pt";
  142             }
  143         }
  144 
  145         // Collapse our last child's margin, if there is no border or padding
  146         if ($style->border_bottom_width == 0 && $style->length_in_pt($style->padding_bottom) == 0) {
  147             $l = $this->_frame->get_last_child();
  148             if ( $l && !$l->is_block() && !$l->is_table() ) {
  149                 while ( $l = $l->get_prev_sibling() ) {
  150                     if ( $l->is_block() || $l->is_table() ) {
  151                         break;
  152                     }
  153 
  154                     if ( !$l->get_last_child() ) {
  155                         $l = null;
  156                         break;
  157                     }
  158                 }
  159             }
  160 
  161             // Margin are collapsed only between block-level boxes
  162             if ($l) {
  163                 $l_style = $l->get_style();
  164                 $l_b = (float)$l_style->length_in_pt($l_style->margin_bottom, $cb["h"]);
  165 
  166                 $b = $this->_get_collapsed_margin_length($b, $l_b);
  167                 $style->margin_bottom = $b."pt";
  168                 $l_style->margin_bottom = "0pt";
  169             }
  170         }
  171     }
  172 
  173     /**
  174      * Get the combined (collapsed) length of two adjoining margins.
  175      * 
  176      * See http://www.w3.org/TR/CSS2/box.html#collapsing-margins.
  177      * 
  178      * @param number $length1
  179      * @param number $length2
  180      * @return number
  181      */
  182     private function _get_collapsed_margin_length($length1, $length2)
  183     {
  184         if ($length1 < 0 && $length2 < 0) {
  185             return min($length1, $length2); // min(x, y) = - max(abs(x), abs(y)), if x < 0 && y < 0
  186         }
  187         
  188         if ($length1 < 0 || $length2 < 0) {
  189             return $length1 + $length2; // x + y = x - abs(y), if y < 0
  190         }
  191         
  192         return max($length1, $length2);
  193     }
  194 
  195     /**
  196      * @param Block|null $block
  197      * @return mixed
  198      */
  199     abstract function reflow(Block $block = null);
  200 
  201     /**
  202      * Required for table layout: Returns an array(0 => min, 1 => max, "min"
  203      * => min, "max" => max) of the minimum and maximum widths of this frame.
  204      * This provides a basic implementation.  Child classes should override
  205      * this if necessary.
  206      *
  207      * @return array|null
  208      */
  209     function get_min_max_width()
  210     {
  211         if (!is_null($this->_min_max_cache)) {
  212             return $this->_min_max_cache;
  213         }
  214 
  215         $style = $this->_frame->get_style();
  216 
  217         // Account for margins & padding
  218         $dims = array($style->padding_left,
  219             $style->padding_right,
  220             $style->border_left_width,
  221             $style->border_right_width,
  222             $style->margin_left,
  223             $style->margin_right);
  224 
  225         $cb_w = $this->_frame->get_containing_block("w");
  226         $delta = (float)$style->length_in_pt($dims, $cb_w);
  227 
  228         // Handle degenerate case
  229         if (!$this->_frame->get_first_child()) {
  230             return $this->_min_max_cache = array(
  231                 $delta, $delta,
  232                 "min" => $delta,
  233                 "max" => $delta,
  234             );
  235         }
  236 
  237         $low = array();
  238         $high = array();
  239 
  240         for ($iter = $this->_frame->get_children()->getIterator(); $iter->valid(); $iter->next()) {
  241             $inline_min = 0;
  242             $inline_max = 0;
  243 
  244             // Add all adjacent inline widths together to calculate max width
  245             while ($iter->valid() && in_array($iter->current()->get_style()->display, Style::$INLINE_TYPES)) {
  246                 $child = $iter->current();
  247 
  248                 $minmax = $child->get_min_max_width();
  249 
  250                 if (in_array($iter->current()->get_style()->white_space, array("pre", "nowrap"))) {
  251                     $inline_min += $minmax["min"];
  252                 } else {
  253                     $low[] = $minmax["min"];
  254                 }
  255 
  256                 $inline_max += $minmax["max"];
  257                 $iter->next();
  258             }
  259 
  260             if ($inline_max > 0) {
  261                 $high[] = $inline_max;
  262             }
  263             if ($inline_min > 0) {
  264                 $low[] = $inline_min;
  265             }
  266 
  267             if ($iter->valid()) {
  268                 list($low[], $high[]) = $iter->current()->get_min_max_width();
  269                 continue;
  270             }
  271         }
  272         $min = count($low) ? max($low) : 0;
  273         $max = count($high) ? max($high) : 0;
  274 
  275         // Use specified width if it is greater than the minimum defined by the
  276         // content.  If the width is a percentage ignore it for now.
  277         $width = $style->width;
  278         if ($width !== "auto" && !Helpers::is_percent($width)) {
  279             $width = (float)$style->length_in_pt($width, $cb_w);
  280             if ($min < $width) {
  281                 $min = $width;
  282             }
  283             if ($max < $width) {
  284                 $max = $width;
  285             }
  286         }
  287 
  288         $min += $delta;
  289         $max += $delta;
  290         return $this->_min_max_cache = array($min, $max, "min" => $min, "max" => $max);
  291     }
  292 
  293     /**
  294      * Parses a CSS string containing quotes and escaped hex characters
  295      *
  296      * @param $string string The CSS string to parse
  297      * @param $single_trim
  298      * @return string
  299      */
  300     protected function _parse_string($string, $single_trim = false)
  301     {
  302         if ($single_trim) {
  303             $string = preg_replace('/^[\"\']/', "", $string);
  304             $string = preg_replace('/[\"\']$/', "", $string);
  305         } else {
  306             $string = trim($string, "'\"");
  307         }
  308 
  309         $string = str_replace(array("\\\n", '\\"', "\\'"),
  310             array("", '"', "'"), $string);
  311 
  312         // Convert escaped hex characters into ascii characters (e.g. \A => newline)
  313         $string = preg_replace_callback("/\\\\([0-9a-fA-F]{0,6})/",
  314             function ($matches) { return \Dompdf\Helpers::unichr(hexdec($matches[1])); },
  315             $string);
  316         return $string;
  317     }
  318 
  319     /**
  320      * Parses a CSS "quotes" property
  321      *
  322      * @return array|null An array of pairs of quotes
  323      */
  324     protected function _parse_quotes()
  325     {
  326         // Matches quote types
  327         $re = '/(\'[^\']*\')|(\"[^\"]*\")/';
  328 
  329         $quotes = $this->_frame->get_style()->quotes;
  330 
  331         // split on spaces, except within quotes
  332         if (!preg_match_all($re, "$quotes", $matches, PREG_SET_ORDER)) {
  333             return null;
  334         }
  335 
  336         $quotes_array = array();
  337         foreach ($matches as $_quote) {
  338             $quotes_array[] = $this->_parse_string($_quote[0], true);
  339         }
  340 
  341         if (empty($quotes_array)) {
  342             $quotes_array = array('"', '"');
  343         }
  344 
  345         return array_chunk($quotes_array, 2);
  346     }
  347 
  348     /**
  349      * Parses the CSS "content" property
  350      *
  351      * @return string|null The resulting string
  352      */
  353     protected function _parse_content()
  354     {
  355         // Matches generated content
  356         $re = "/\n" .
  357             "\s(counters?\\([^)]*\\))|\n" .
  358             "\A(counters?\\([^)]*\\))|\n" .
  359             "\s([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\3|\n" .
  360             "\A([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\5|\n" .
  361             "\s([^\s\"']+)|\n" .
  362             "\A([^\s\"']+)\n" .
  363             "/xi";
  364 
  365         $content = $this->_frame->get_style()->content;
  366 
  367         $quotes = $this->_parse_quotes();
  368 
  369         // split on spaces, except within quotes
  370         if (!preg_match_all($re, $content, $matches, PREG_SET_ORDER)) {
  371             return null;
  372         }
  373 
  374         $text = "";
  375 
  376         foreach ($matches as $match) {
  377             if (isset($match[2]) && $match[2] !== "") {
  378                 $match[1] = $match[2];
  379             }
  380 
  381             if (isset($match[6]) && $match[6] !== "") {
  382                 $match[4] = $match[6];
  383             }
  384 
  385             if (isset($match[8]) && $match[8] !== "") {
  386                 $match[7] = $match[8];
  387             }
  388 
  389             if (isset($match[1]) && $match[1] !== "") {
  390                 // counters?(...)
  391                 $match[1] = mb_strtolower(trim($match[1]));
  392 
  393                 // Handle counter() references:
  394                 // http://www.w3.org/TR/CSS21/generate.html#content
  395 
  396                 $i = mb_strpos($match[1], ")");
  397                 if ($i === false) {
  398                     continue;
  399                 }
  400 
  401                 preg_match('/(counters?)(^\()*?\(\s*([^\s,]+)\s*(,\s*["\']?([^"\'\)]*)["\']?\s*(,\s*([^\s)]+)\s*)?)?\)/i', $match[1], $args);
  402                 $counter_id = $args[3];
  403                 if (strtolower($args[1]) == 'counter') {
  404                     // counter(name [,style])
  405                     if (isset($args[5])) {
  406                         $type = trim($args[5]);
  407                     } else {
  408                         $type = null;
  409                     }
  410                     $p = $this->_frame->lookup_counter_frame($counter_id);
  411 
  412                     $text .= $p->counter_value($counter_id, $type);
  413 
  414                 } else if (strtolower($args[1]) == 'counters') {
  415                     // counters(name, string [,style])
  416                     if (isset($args[5])) {
  417                         $string = $this->_parse_string($args[5]);
  418                     } else {
  419                         $string = "";
  420                     }
  421 
  422                     if (isset($args[7])) {
  423                         $type = trim($args[7]);
  424                     } else {
  425                         $type = null;
  426                     }
  427 
  428                     $p = $this->_frame->lookup_counter_frame($counter_id);
  429                     $tmp = array();
  430                     while ($p) {
  431                         // We only want to use the counter values when they actually increment the counter
  432                         if (array_key_exists($counter_id, $p->_counters)) {
  433                             array_unshift($tmp, $p->counter_value($counter_id, $type));
  434                         }
  435                         $p = $p->lookup_counter_frame($counter_id);
  436                     }
  437                     $text .= implode($string, $tmp);
  438                 } else {
  439                     // countertops?
  440                     continue;
  441                 }
  442 
  443             } else if (isset($match[4]) && $match[4] !== "") {
  444                 // String match
  445                 $text .= $this->_parse_string($match[4]);
  446             } else if (isset($match[7]) && $match[7] !== "") {
  447                 // Directive match
  448 
  449                 if ($match[7] === "open-quote") {
  450                     // FIXME: do something here
  451                     $text .= $quotes[0][0];
  452                 } else if ($match[7] === "close-quote") {
  453                     // FIXME: do something else here
  454                     $text .= $quotes[0][1];
  455                 } else if ($match[7] === "no-open-quote") {
  456                     // FIXME:
  457                 } else if ($match[7] === "no-close-quote") {
  458                     // FIXME:
  459                 } else if (mb_strpos($match[7], "attr(") === 0) {
  460                     $i = mb_strpos($match[7], ")");
  461                     if ($i === false) {
  462                         continue;
  463                     }
  464 
  465                     $attr = mb_substr($match[7], 5, $i - 5);
  466                     if ($attr == "") {
  467                         continue;
  468                     }
  469 
  470                     $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr);
  471                 } else {
  472                     continue;
  473                 }
  474             }
  475         }
  476 
  477         return $text;
  478     }
  479 
  480     /**
  481      * Sets the generated content of a generated frame
  482      */
  483     protected function _set_content()
  484     {
  485         $frame = $this->_frame;
  486         $style = $frame->get_style();
  487 
  488         // if the element was pushed to a new page use the saved counter value, otherwise use the CSS reset value
  489         if ($style->counter_reset && ($reset = $style->counter_reset) !== "none") {
  490             $vars = preg_split('/\s+/', trim($reset), 2);
  491             $frame->reset_counter($vars[0], (isset($frame->_counters['__' . $vars[0]]) ? $frame->_counters['__' . $vars[0]] : (isset($vars[1]) ? $vars[1] : 0)));
  492         }
  493 
  494         if ($style->counter_increment && ($increment = $style->counter_increment) !== "none") {
  495             $frame->increment_counters($increment);
  496         }
  497 
  498         if ($style->content && $frame->get_node()->nodeName === "dompdf_generated") {
  499             $content = $this->_parse_content();
  500             // add generated content to the font subset
  501             // FIXME: This is currently too late because the font subset has already been generated.
  502             //        See notes in issue #750.
  503             if ($frame->get_dompdf()->getOptions()->getIsFontSubsettingEnabled() && $frame->get_dompdf()->get_canvas() instanceof CPDF) {
  504                 $frame->get_dompdf()->get_canvas()->register_string_subset($style->font_family, $content);
  505             }
  506 
  507             $node = $frame->get_node()->ownerDocument->createTextNode($content);
  508 
  509             $new_style = $style->get_stylesheet()->create_style();
  510             $new_style->inherit($style);
  511 
  512             $new_frame = new Frame($node);
  513             $new_frame->set_style($new_style);
  514 
  515             Factory::decorate_frame($new_frame, $frame->get_dompdf(), $frame->get_root());
  516             $frame->append_child($new_frame);
  517         }
  518     }
  519 
  520     /**
  521      * Determine current frame width based on contents
  522      *
  523      * @return float
  524      */
  525     public function calculate_auto_width()
  526     {
  527         return $this->_frame->get_margin_width();
  528     }
  529 }