"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 }