PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.4.3
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.4.3
2.4.3 2.4.4 2.4.2 2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 All 60 releases
easy-invoice / vendor / dompdf / dompdf / src / FrameReflower / Block.php

Block.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.4.3, at vendor/dompdf/dompdf/src/FrameReflower/Block.php

949 lines 33.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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\FrameDecorator\AbstractFrameDecorator;
10 use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
11 use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator;
12 use Dompdf\FrameDecorator\Text as TextFrameDecorator;
13 use Dompdf\Exception;
14 use Dompdf\Css\Style;
15 use Dompdf\Helpers;
16
17 /**
18 * Reflows block frames
19 *
20 * @package dompdf
21 */
22 class Block extends AbstractFrameReflower
23 {
24 // Minimum line width to justify, as fraction of available width
25 const MIN_JUSTIFY_WIDTH = 0.80;
26
27 /**
28 * Frame for this reflower
29 *
30 * @var BlockFrameDecorator
31 */
32 protected $_frame;
33
34 function __construct(BlockFrameDecorator $frame)
35 {
36 parent::__construct($frame);
37 }
38
39 /**
40 * Calculate the ideal used value for the width property as per:
41 * http://www.w3.org/TR/CSS21/visudet.html#Computing_widths_and_margins
42 *
43 * @param float $width
44 *
45 * @return array
46 */
47 protected function _calculate_width($width)
48 {
49 $frame = $this->_frame;
50 $style = $frame->get_style();
51 $absolute = $frame->is_absolute();
52
53 $cb = $frame->get_containing_block();
54 $w = $cb["w"];
55
56 $rm = $style->length_in_pt($style->margin_right, $w);
57 $lm = $style->length_in_pt($style->margin_left, $w);
58
59 $left = $style->length_in_pt($style->left, $w);
60 $right = $style->length_in_pt($style->right, $w);
61
62 // Handle 'auto' values
63 $dims = [$style->border_left_width,
64 $style->border_right_width,
65 $style->padding_left,
66 $style->padding_right,
67 $width !== "auto" ? $width : 0,
68 $rm !== "auto" ? $rm : 0,
69 $lm !== "auto" ? $lm : 0];
70
71 // absolutely positioned boxes take the 'left' and 'right' properties into account
72 if ($absolute) {
73 $dims[] = $left !== "auto" ? $left : 0;
74 $dims[] = $right !== "auto" ? $right : 0;
75 }
76
77 $sum = (float)$style->length_in_pt($dims, $w);
78
79 // Compare to the containing block
80 $diff = $w - $sum;
81
82 if ($absolute) {
83 // Absolutely positioned
84 // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width
85
86 if ($width === "auto" || $left === "auto" || $right === "auto") {
87 // "all of the three are 'auto'" logic + otherwise case
88 if ($lm === "auto") {
89 $lm = 0;
90 }
91 if ($rm === "auto") {
92 $rm = 0;
93 }
94
95 $block_parent = $frame->find_block_parent();
96 $parent_content = $block_parent->get_content_box();
97 $line = $block_parent->get_current_line_box();
98
99 // TODO: This is the in-flow inline position. Use the in-flow
100 // block position if the original display type is block-level
101 $inflow_x = $parent_content["x"] - $cb["x"] + $line->left + $line->w;
102
103 if ($width === "auto" && $left === "auto" && $right === "auto") {
104 // rule 3, per instruction preceding rule set
105 // shrink-to-fit width
106 $left = $inflow_x;
107 [$min, $max] = $this->get_min_max_child_width();
108 $width = min(max($min, $diff - $left), $max);
109 $right = $diff - $left - $width;
110 } elseif ($width === "auto" && $left === "auto") {
111 // rule 1
112 // shrink-to-fit width
113 [$min, $max] = $this->get_min_max_child_width();
114 $width = min(max($min, $diff), $max);
115 $left = $diff - $width;
116 } elseif ($width === "auto" && $right === "auto") {
117 // rule 3
118 // shrink-to-fit width
119 [$min, $max] = $this->get_min_max_child_width();
120 $width = min(max($min, $diff), $max);
121 $right = $diff - $width;
122 } elseif ($left === "auto" && $right === "auto") {
123 // rule 2
124 $left = $inflow_x;
125 $right = $diff - $left;
126 } elseif ($left === "auto") {
127 // rule 4
128 $left = $diff;
129 } elseif ($width === "auto") {
130 // rule 5
131 $width = max($diff, 0);
132 } else {
133 // $right === "auto"
134 // rule 6
135 $right = $diff;
136 }
137 } else {
138 // "none of the three are 'auto'" logic described in paragraph preceding the rules
139 if ($diff >= 0) {
140 if ($lm === "auto" && $rm === "auto") {
141 $lm = $rm = $diff / 2;
142 } elseif ($lm === "auto") {
143 $lm = $diff;
144 } elseif ($rm === "auto") {
145 $rm = $diff;
146 }
147 } else {
148 // over-constrained, solve for right
149 $right = $right + $diff;
150
151 if ($lm === "auto") {
152 $lm = 0;
153 }
154 if ($rm === "auto") {
155 $rm = 0;
156 }
157 }
158 }
159 } elseif ($style->float !== "none" || $style->display === "inline-block") {
160 // Shrink-to-fit width for float and inline block
161 // https://www.w3.org/TR/CSS21/visudet.html#float-width
162 // https://www.w3.org/TR/CSS21/visudet.html#inlineblock-width
163
164 if ($width === "auto") {
165 [$min, $max] = $this->get_min_max_child_width();
166 $width = min(max($min, $diff), $max);
167 }
168 if ($lm === "auto") {
169 $lm = 0;
170 }
171 if ($rm === "auto") {
172 $rm = 0;
173 }
174 } else {
175 // Block-level, normal flow
176 // https://www.w3.org/TR/CSS21/visudet.html#blockwidth
177
178 if ($diff >= 0) {
179 // Find auto properties and get them to take up the slack
180 if ($width === "auto") {
181 $width = $diff;
182
183 if ($lm === "auto") {
184 $lm = 0;
185 }
186 if ($rm === "auto") {
187 $rm = 0;
188 }
189 } elseif ($lm === "auto" && $rm === "auto") {
190 $lm = $rm = $diff / 2;
191 } elseif ($lm === "auto") {
192 $lm = $diff;
193 } elseif ($rm === "auto") {
194 $rm = $diff;
195 }
196 } else {
197 // We are over constrained--set margin-right to the difference
198 $rm = (float) $rm + $diff;
199
200 if ($width === "auto") {
201 $width = 0;
202 }
203 if ($lm === "auto") {
204 $lm = 0;
205 }
206 }
207 }
208
209 return [
210 "width" => $width,
211 "margin_left" => $lm,
212 "margin_right" => $rm,
213 "left" => $left,
214 "right" => $right,
215 ];
216 }
217
218 /**
219 * Call the above function, but resolve max/min widths
220 *
221 * @throws Exception
222 * @return array
223 */
224 protected function _calculate_restricted_width()
225 {
226 $frame = $this->_frame;
227 $style = $frame->get_style();
228 $cb = $frame->get_containing_block();
229
230 if (!isset($cb["w"])) {
231 throw new Exception("Box property calculation requires containing block width");
232 }
233
234 $width = $style->length_in_pt($style->width, $cb["w"]);
235
236 $values = $this->_calculate_width($width);
237 $margin_left = $values["margin_left"];
238 $margin_right = $values["margin_right"];
239 $width = $values["width"];
240 $left = $values["left"];
241 $right = $values["right"];
242
243 // Handle min/max width
244 // https://www.w3.org/TR/CSS21/visudet.html#min-max-widths
245 $min_width = $this->resolve_min_width($cb["w"]);
246 $max_width = $this->resolve_max_width($cb["w"]);
247
248 if ($width > $max_width) {
249 $values = $this->_calculate_width($max_width);
250 $margin_left = $values["margin_left"];
251 $margin_right = $values["margin_right"];
252 $width = $values["width"];
253 $left = $values["left"];
254 $right = $values["right"];
255 }
256
257 if ($width < $min_width) {
258 $values = $this->_calculate_width($min_width);
259 $margin_left = $values["margin_left"];
260 $margin_right = $values["margin_right"];
261 $width = $values["width"];
262 $left = $values["left"];
263 $right = $values["right"];
264 }
265
266 return [$width, $margin_left, $margin_right, $left, $right];
267 }
268
269 /**
270 * Determine the unrestricted height of content within the block
271 * not by adding each line's height, but by getting the last line's position.
272 * This because lines could have been pushed lower by a clearing element.
273 *
274 * @return float
275 */
276 protected function _calculate_content_height(): float
277 {
278 $height = 0.0;
279 $lines = $this->_frame->get_line_boxes();
280 if (count($lines) > 0) {
281 $last_line = end($lines);
282 $content_box = $this->_frame->get_content_box();
283 $height = $last_line->y + $last_line->h - $content_box["y"];
284 }
285 return $height;
286 }
287
288 /**
289 * Determine the frame's restricted height
290 *
291 * @return array
292 */
293 protected function _calculate_restricted_height()
294 {
295 $frame = $this->_frame;
296 $style = $frame->get_style();
297 $content_height = $this->_calculate_content_height();
298 $cb = $frame->get_containing_block();
299
300 $height = $style->length_in_pt($style->height, $cb["h"]);
301 $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]);
302 $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]);
303
304 $top = $style->length_in_pt($style->top, $cb["h"]);
305 $bottom = $style->length_in_pt($style->bottom, $cb["h"]);
306
307 if ($frame->is_absolute()) {
308 // Absolutely positioned
309 // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height
310
311 $h_dims = [
312 $top !== "auto" ? $top : 0,
313 $height !== "auto" ? $height : 0,
314 $bottom !== "auto" ? $bottom : 0
315 ];
316 $w_dims = [
317 $style->margin_top !== "auto" ? $style->margin_top : 0,
318 $style->padding_top,
319 $style->border_top_width,
320 $style->border_bottom_width,
321 $style->padding_bottom,
322 $style->margin_bottom !== "auto" ? $style->margin_bottom : 0
323 ];
324
325 $sum = (float)$style->length_in_pt($h_dims, $cb["h"])
326 + (float)$style->length_in_pt($w_dims, $cb["w"]);
327
328 $diff = $cb["h"] - $sum;
329
330 if ($height === "auto" || $top === "auto" || $bottom === "auto") {
331 // "all of the three are 'auto'" logic + otherwise case
332 if ($margin_top === "auto") {
333 $margin_top = 0;
334 }
335 if ($margin_bottom === "auto") {
336 $margin_bottom = 0;
337 }
338
339 $block_parent = $frame->find_block_parent();
340 $current_line = $block_parent->get_current_line_box();
341
342 // TODO: This is the in-flow inline position. Use the in-flow
343 // block position if the original display type is block-level
344 $inflow_y = $current_line->y - $cb["y"];
345
346 if ($height === "auto" && $top === "auto" && $bottom === "auto") {
347 // rule 3, per instruction preceding rule set
348 $top = $inflow_y;
349 $height = $content_height;
350 $bottom = $diff - $top - $height;
351 } elseif ($height === "auto" && $top === "auto") {
352 // rule 1
353 $height = $content_height;
354 $top = $diff - $height;
355 } elseif ($height === "auto" && $bottom === "auto") {
356 // rule 3
357 $height = $content_height;
358 $bottom = $diff - $height;
359 } elseif ($top === "auto" && $bottom === "auto") {
360 // rule 2
361 $top = $inflow_y;
362 $bottom = $diff - $top;
363 } elseif ($top === "auto") {
364 // rule 4
365 $top = $diff;
366 } elseif ($height === "auto") {
367 // rule 5
368 $height = max($diff, 0);
369 } else {
370 // $bottom === "auto"
371 // rule 6
372 $bottom = $diff;
373 }
374 } else {
375 // "none of the three are 'auto'" logic described in paragraph preceding the rules
376 if ($diff >= 0) {
377 if ($margin_top === "auto" && $margin_bottom === "auto") {
378 $margin_top = $margin_bottom = $diff / 2;
379 } elseif ($margin_top === "auto") {
380 $margin_top = $diff;
381 } elseif ($margin_bottom === "auto") {
382 $margin_bottom = $diff;
383 }
384 } else {
385 // over-constrained, solve for bottom
386 $bottom = $bottom + $diff;
387
388 if ($margin_top === "auto") {
389 $margin_top = 0;
390 }
391 if ($margin_bottom === "auto") {
392 $margin_bottom = 0;
393 }
394 }
395 }
396 } else {
397 // https://www.w3.org/TR/CSS21/visudet.html#normal-block
398 // https://www.w3.org/TR/CSS21/visudet.html#block-root-margin
399
400 if ($height === "auto") {
401 $height = $content_height;
402 }
403 if ($margin_top === "auto") {
404 $margin_top = 0;
405 }
406 if ($margin_bottom === "auto") {
407 $margin_bottom = 0;
408 }
409
410 // Handle min/max height
411 // https://www.w3.org/TR/CSS21/visudet.html#min-max-heights
412 $min_height = $this->resolve_min_height($cb["h"]);
413 $max_height = $this->resolve_max_height($cb["h"]);
414 $height = Helpers::clamp($height, $min_height, $max_height);
415 }
416
417 // TODO: Need to also take min/max height into account for absolute
418 // positioning, using similar logic to the `_calculate_width`/
419 // `calculate_restricted_width` split above. The non-absolute case
420 // can simply clamp height within min/max, as margins and offsets are
421 // not affected
422
423 return [$height, $margin_top, $margin_bottom, $top, $bottom];
424 }
425
426 /**
427 * Adjust the justification of each of our lines.
428 * http://www.w3.org/TR/CSS21/text.html#propdef-text-align
429 */
430 protected function _text_align()
431 {
432 $style = $this->_frame->get_style();
433 $w = $this->_frame->get_containing_block("w");
434 $width = (float)$style->length_in_pt($style->width, $w);
435 $text_indent = (float)$style->length_in_pt($style->text_indent, $w);
436
437 switch ($style->text_align) {
438 default:
439 case "left":
440 foreach ($this->_frame->get_line_boxes() as $line) {
441 if (!$line->inline) {
442 continue;
443 }
444
445 $line->trim_trailing_ws();
446
447 if ($line->left) {
448 foreach ($line->frames_to_align() as $frame) {
449 $frame->move($line->left, 0);
450 }
451 }
452 }
453 break;
454
455 case "right":
456 foreach ($this->_frame->get_line_boxes() as $i => $line) {
457 if (!$line->inline) {
458 continue;
459 }
460
461 $line->trim_trailing_ws();
462
463 $indent = $i === 0 ? $text_indent : 0;
464 $dx = $width - $line->w - $line->right - $indent;
465
466 foreach ($line->frames_to_align() as $frame) {
467 $frame->move($dx, 0);
468 }
469 }
470 break;
471
472 case "justify":
473 // We justify all lines except the last one, unless the frame
474 // has been split, in which case the actual last line is part of
475 // the split-off frame
476 $lines = $this->_frame->get_line_boxes();
477 $last_line_index = $this->_frame->is_split ? null : count($lines) - 1;
478
479 foreach ($lines as $i => $line) {
480 if (!$line->inline) {
481 continue;
482 }
483
484 $line->trim_trailing_ws();
485
486 if ($line->left) {
487 foreach ($line->frames_to_align() as $frame) {
488 $frame->move($line->left, 0);
489 }
490 }
491
492 if ($line->br || $i === $last_line_index) {
493 continue;
494 }
495
496 $frames = $line->get_frames();
497 $other_frame_count = 0;
498
499 foreach ($frames as $frame) {
500 if (!($frame instanceof TextFrameDecorator)) {
501 $other_frame_count++;
502 }
503 }
504
505 $word_count = $line->wc + $other_frame_count;
506
507 // Set the spacing for each child
508 if ($word_count > 1) {
509 $indent = $i === 0 ? $text_indent : 0;
510 $spacing = ($width - $line->get_width() - $indent) / ($word_count - 1);
511 } else {
512 $spacing = 0;
513 }
514
515 $dx = 0;
516 foreach ($frames as $frame) {
517 if ($frame instanceof TextFrameDecorator) {
518 $text = $frame->get_text();
519 $spaces = mb_substr_count($text, " ");
520
521 $frame->move($dx, 0);
522 $frame->set_text_spacing($spacing);
523
524 $dx += $spaces * $spacing;
525 } else {
526 $frame->move($dx, 0);
527 }
528 }
529
530 // The line (should) now occupy the entire width
531 $line->w = $width;
532 }
533 break;
534
535 case "center":
536 case "centre":
537 foreach ($this->_frame->get_line_boxes() as $i => $line) {
538 if (!$line->inline) {
539 continue;
540 }
541
542 $line->trim_trailing_ws();
543
544 $indent = $i === 0 ? $text_indent : 0;
545 $dx = ($width + $line->left - $line->w - $line->right - $indent) / 2;
546
547 foreach ($line->frames_to_align() as $frame) {
548 $frame->move($dx, 0);
549 }
550 }
551 break;
552 }
553 }
554
555 /**
556 * Align inline children vertically.
557 * Aligns each child vertically after each line is reflowed
558 */
559 function vertical_align()
560 {
561 $fontMetrics = $this->get_dompdf()->getFontMetrics();
562
563 foreach ($this->_frame->get_line_boxes() as $line) {
564 $height = $line->h;
565
566 // Move all markers to the top of the line box
567 foreach ($line->get_list_markers() as $marker) {
568 $x = $marker->get_position("x");
569 $marker->set_position($x, $line->y);
570 }
571
572 foreach ($line->frames_to_align() as $frame) {
573 $style = $frame->get_style();
574 $isInlineBlock = $style->display !== "inline"
575 && $style->display !== "-dompdf-list-bullet";
576
577 $baseline = $fontMetrics->getFontBaseline($style->font_family, $style->font_size);
578 $y_offset = 0;
579
580 //FIXME: The 0.8 ratio applied to the height is arbitrary (used to accommodate descenders?)
581 if ($isInlineBlock) {
582 // Workaround: Skip vertical alignment if the frame is the
583 // only one one the line, excluding empty text frames, which
584 // may be the result of trailing white space
585 // FIXME: This special case should be removed once vertical
586 // alignment is properly fixed
587 $skip = true;
588
589 foreach ($line->get_frames() as $other) {
590 if ($other !== $frame
591 && !($other->is_text_node() && $other->get_node()->nodeValue === "")
592 ) {
593 $skip = false;
594 break;
595 }
596 }
597
598 if ($skip) {
599 continue;
600 }
601
602 $marginHeight = $frame->get_margin_height();
603 $imageHeightDiff = $height * 0.8 - $marginHeight;
604
605 $align = $frame->get_style()->vertical_align;
606 if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) {
607 switch ($align) {
608 case "middle":
609 $y_offset = $imageHeightDiff / 2;
610 break;
611
612 case "sub":
613 $y_offset = 0.3 * $height + $imageHeightDiff;
614 break;
615
616 case "super":
617 $y_offset = -0.2 * $height + $imageHeightDiff;
618 break;
619
620 case "text-top": // FIXME: this should be the height of the frame minus the height of the text
621 $y_offset = $height - $style->line_height;
622 break;
623
624 case "top":
625 break;
626
627 case "text-bottom": // FIXME: align bottom of image with the descender?
628 case "bottom":
629 $y_offset = 0.3 * $height + $imageHeightDiff;
630 break;
631
632 case "baseline":
633 default:
634 $y_offset = $imageHeightDiff;
635 break;
636 }
637 } else {
638 $y_offset = $baseline - (float)$style->length_in_pt($align, $style->font_size) - $marginHeight;
639 }
640 } else {
641 $parent = $frame->get_parent();
642 if ($parent instanceof TableCellFrameDecorator) {
643 $align = "baseline";
644 } else {
645 $align = $parent->get_style()->vertical_align;
646 }
647 if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) {
648 switch ($align) {
649 case "middle":
650 $y_offset = ($height * 0.8 - $baseline) / 2;
651 break;
652
653 case "sub":
654 $y_offset = $height * 0.8 - $baseline * 0.5;
655 break;
656
657 case "super":
658 $y_offset = $height * 0.8 - $baseline * 1.4;
659 break;
660
661 case "text-top":
662 case "top": // Not strictly accurate, but good enough for now
663 break;
664
665 case "text-bottom":
666 case "bottom":
667 $y_offset = $height * 0.8 - $baseline;
668 break;
669
670 case "baseline":
671 default:
672 $y_offset = $height * 0.8 - $baseline;
673 break;
674 }
675 } else {
676 $y_offset = $height * 0.8 - $baseline - (float)$style->length_in_pt($align, $style->font_size);
677 }
678 }
679
680 if ($y_offset !== 0) {
681 $frame->move(0, $y_offset);
682 }
683 }
684 }
685 }
686
687 /**
688 * @param AbstractFrameDecorator $child
689 */
690 function process_clear(AbstractFrameDecorator $child)
691 {
692 $child_style = $child->get_style();
693 $root = $this->_frame->get_root();
694
695 // Handle "clear"
696 if ($child_style->clear !== "none") {
697 //TODO: this is a WIP for handling clear/float frames that are in between inline frames
698 if ($child->get_prev_sibling() !== null) {
699 $this->_frame->add_line();
700 }
701 if ($child_style->float !== "none" && $child->get_next_sibling()) {
702 $this->_frame->set_current_line_number($this->_frame->get_current_line_number() - 1);
703 }
704
705 $lowest_y = $root->get_lowest_float_offset($child);
706
707 // If a float is still applying, we handle it
708 if ($lowest_y) {
709 if ($child->is_in_flow()) {
710 $line_box = $this->_frame->get_current_line_box();
711 $line_box->y = $lowest_y + $child->get_margin_height();
712 $line_box->left = 0;
713 $line_box->right = 0;
714 }
715
716 $child->move(0, $lowest_y - $child->get_position("y"));
717 }
718 }
719 }
720
721 /**
722 * @param AbstractFrameDecorator $child
723 * @param float $cb_x
724 * @param float $cb_w
725 */
726 function process_float(AbstractFrameDecorator $child, $cb_x, $cb_w)
727 {
728 $child_style = $child->get_style();
729 $root = $this->_frame->get_root();
730
731 // Handle "float"
732 if ($child_style->float !== "none") {
733 $root->add_floating_frame($child);
734
735 // Remove next frame's beginning whitespace
736 $next = $child->get_next_sibling();
737 if ($next && $next instanceof TextFrameDecorator) {
738 $next->set_text(ltrim($next->get_text()));
739 }
740
741 $line_box = $this->_frame->get_current_line_box();
742 list($old_x, $old_y) = $child->get_position();
743
744 $float_x = $cb_x;
745 $float_y = $old_y;
746 $float_w = $child->get_margin_width();
747
748 if ($child_style->clear === "none") {
749 switch ($child_style->float) {
750 case "left":
751 $float_x += $line_box->left;
752 break;
753 case "right":
754 $float_x += ($cb_w - $line_box->right - $float_w);
755 break;
756 }
757 } else {
758 if ($child_style->float === "right") {
759 $float_x += ($cb_w - $float_w);
760 }
761 }
762
763 if ($cb_w < $float_x + $float_w - $old_x) {
764 // TODO handle when floating elements don't fit
765 }
766
767 $line_box->get_float_offsets();
768
769 if ($child->_float_next_line) {
770 $float_y += $line_box->h;
771 }
772
773 $child->set_position($float_x, $float_y);
774 $child->move($float_x - $old_x, $float_y - $old_y, true);
775 }
776 }
777
778 /**
779 * @param BlockFrameDecorator|null $block
780 */
781 function reflow(?BlockFrameDecorator $block = null)
782 {
783
784 // Check if a page break is forced
785 $page = $this->_frame->get_root();
786 $page->check_forced_page_break($this->_frame);
787
788 // Bail if the page is full
789 if ($page->is_full()) {
790 return;
791 }
792
793 $this->determine_absolute_containing_block();
794
795 // Counters and generated content
796 $this->_set_content();
797
798 // Inherit any dangling list markers
799 if ($block && $this->_frame->is_in_flow()) {
800 $this->_frame->inherit_dangling_markers($block);
801 }
802
803 // Collapse margins if required
804 $this->_collapse_margins();
805
806 $style = $this->_frame->get_style();
807 $cb = $this->_frame->get_containing_block();
808
809 // Determine the constraints imposed by this frame: calculate the width
810 // of the content area:
811 [$width, $margin_left, $margin_right, $left, $right] = $this->_calculate_restricted_width();
812
813 // Store the calculated properties
814 $style->set_used("width", $width);
815 $style->set_used("margin_left", $margin_left);
816 $style->set_used("margin_right", $margin_right);
817 $style->set_used("left", $left);
818 $style->set_used("right", $right);
819
820 $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]);
821 $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]);
822
823 $auto_top = $style->top === "auto";
824 $auto_margin_top = $margin_top === "auto";
825
826 // Update the position
827 $this->_frame->position();
828 [$x, $y] = $this->_frame->get_position();
829
830 // Adjust the first line based on the text-indent property
831 $indent = (float)$style->length_in_pt($style->text_indent, $cb["w"]);
832 $this->_frame->increase_line_width($indent);
833
834 // Determine the content edge
835 $top = (float)$style->length_in_pt([
836 $margin_top !== "auto" ? $margin_top : 0,
837 $style->border_top_width,
838 $style->padding_top
839 ], $cb["w"]);
840 $bottom = (float)$style->length_in_pt([
841 $margin_bottom !== "auto" ? $margin_bottom : 0,
842 $style->border_bottom_width,
843 $style->padding_bottom
844 ], $cb["w"]);
845
846 $cb_x = $x + (float)$margin_left + (float)$style->length_in_pt([$style->border_left_width,
847 $style->padding_left], $cb["w"]);
848
849 $cb_y = $y + $top;
850
851 $height = $style->length_in_pt($style->height, $cb["h"]);
852 if ($height === "auto") {
853 $height = ($cb["h"] + $cb["y"]) - $bottom - $cb_y;
854 }
855
856 // Set the y position of the first line in this block
857 $line_box = $this->_frame->get_current_line_box();
858 $line_box->y = $cb_y;
859 $line_box->get_float_offsets();
860
861 // Set the containing blocks and reflow each child
862 foreach ($this->_frame->get_children() as $child) {
863 $child->set_containing_block($cb_x, $cb_y, $width, $height);
864 $this->process_clear($child);
865 $child->reflow($this->_frame);
866
867 // Check for a page break before the child
868 $page->check_page_break($child);
869
870 // Don't add the child to the line if a page break has occurred
871 // before it (possibly via a descendant), in which case it has been
872 // reset, including its position
873 if ($page->is_full() && $child->get_position("x") === null) {
874 break;
875 }
876
877 $this->process_float($child, $cb_x, $width);
878 }
879
880 // Stop reflow if a page break has occurred before the frame, in which
881 // case it has been reset, including its position
882 if ($page->is_full() && $this->_frame->get_position("x") === null) {
883 return;
884 }
885
886 // Determine our height
887 [$height, $margin_top, $margin_bottom, $top, $bottom] = $this->_calculate_restricted_height();
888
889 $style->set_used("height", $height);
890 $style->set_used("margin_top", $margin_top);
891 $style->set_used("margin_bottom", $margin_bottom);
892 $style->set_used("top", $top);
893 $style->set_used("bottom", $bottom);
894
895 if ($this->_frame->is_absolute()) {
896 if ($auto_top) {
897 $this->_frame->move(0, $top);
898 }
899 if ($auto_margin_top) {
900 $this->_frame->move(0, $margin_top, true);
901 }
902 }
903
904 $this->_text_align();
905 $this->vertical_align();
906
907 // Handle relative positioning
908 foreach ($this->_frame->get_children() as $child) {
909 $this->position_relative($child);
910 }
911
912 if ($block && $this->_frame->is_in_flow()) {
913 $block->add_frame_to_line($this->_frame);
914
915 if ($this->_frame->is_block_level()) {
916 $block->add_line();
917 }
918 }
919 }
920
921 public function get_min_max_content_width(): array
922 {
923 // TODO: While the containing block is not set yet on the frame, it can
924 // already be determined in some cases due to fixed dimensions on the
925 // ancestor forming the containing block. In such cases, percentage
926 // values could be resolved here
927 $style = $this->_frame->get_style();
928 $width = $style->width;
929 $fixed_width = $width !== "auto" && !Helpers::is_percent($width);
930
931 // If the frame has a specified width, then we don't need to check
932 // its children
933 if ($fixed_width) {
934 $min = (float) $style->length_in_pt($width, 0);
935 $max = $min;
936 } else {
937 [$min, $max] = $this->get_min_max_child_width();
938 }
939
940 // Handle min/max width style properties
941 $min_width = $this->resolve_min_width(null);
942 $max_width = $this->resolve_max_width(null);
943 $min = Helpers::clamp($min, $min_width, $max_width);
944 $max = Helpers::clamp($max, $min_width, $max_width);
945
946 return [$min, $max];
947 }
948 }
949