PluginProbe
MBE eShip / trunk
MBE eShip vtrunk
2.8.1 trunk 1.0.0 1.1.0 1.1.3 1.2.1 1.2.2 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.6.0 1.7.0 1.7.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1.0 2.1.1 2.1.2 2.2.1 All 37 releases
mail-boxes-etc / lib / dompdf / src / FrameReflower / Block.php

Block.php in MBE eShip trunk, at lib/dompdf/src/FrameReflower/Block.php

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