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 / Cellmap.php

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

1,000 lines 27.6 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;
8
9 use Dompdf\FrameDecorator\AbstractFrameDecorator;
10 use Dompdf\FrameDecorator\Table as TableFrameDecorator;
11 use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator;
12
13 /**
14 * Maps table cells to the table grid.
15 *
16 * This class resolves borders in tables with collapsed borders and helps
17 * place row & column spanned table cells.
18 *
19 * @package dompdf
20 */
21 class Cellmap
22 {
23 /**
24 * Border style weight lookup for collapsed border resolution.
25 */
26 protected const BORDER_STYLE_SCORE = [
27 "double" => 8,
28 "solid" => 7,
29 "dashed" => 6,
30 "dotted" => 5,
31 "ridge" => 4,
32 "outset" => 3,
33 "groove" => 2,
34 "inset" => 1,
35 "none" => 0
36 ];
37
38 /**
39 * The table object this cellmap is attached to.
40 *
41 * @var TableFrameDecorator
42 */
43 protected $_table;
44
45 /**
46 * The total number of rows in the table
47 *
48 * @var int
49 */
50 protected $_num_rows;
51
52 /**
53 * The total number of columns in the table
54 *
55 * @var int
56 */
57 protected $_num_cols;
58
59 /**
60 * 2D array mapping <row,column> to frames
61 *
62 * @var Frame[][]
63 */
64 protected $_cells;
65
66 /**
67 * 1D array of column dimensions
68 *
69 * @var array
70 */
71 protected $_columns;
72
73 /**
74 * 1D array of row dimensions
75 *
76 * @var array
77 */
78 protected $_rows;
79
80 /**
81 * 2D array of border specs
82 *
83 * @var array
84 */
85 protected $_borders;
86
87 /**
88 * 1D Array mapping frames to (multiple) <row, col> pairs, keyed on frame_id.
89 *
90 * @var array[]
91 */
92 protected $_frames;
93
94 /**
95 * Current column when adding cells, 0-based
96 *
97 * @var int
98 */
99 private $__col;
100
101 /**
102 * Current row when adding cells, 0-based
103 *
104 * @var int
105 */
106 private $__row;
107
108 /**
109 * Tells whether the columns' width can be modified
110 *
111 * @var bool
112 */
113 private $_columns_locked = false;
114
115 /**
116 * Tells whether the table has table-layout:fixed
117 *
118 * @var bool
119 */
120 private $_fixed_layout = false;
121
122 /**
123 * @param TableFrameDecorator $table
124 */
125 public function __construct(TableFrameDecorator $table)
126 {
127 $this->_table = $table;
128 $this->reset();
129 }
130
131 public function reset(): void
132 {
133 $this->_num_rows = 0;
134 $this->_num_cols = 0;
135
136 $this->_cells = [];
137 $this->_frames = [];
138
139 if (!$this->_columns_locked) {
140 $this->_columns = [];
141 }
142
143 $this->_rows = [];
144
145 $this->_borders = [];
146
147 $this->__col = $this->__row = 0;
148 }
149
150 public function lock_columns(): void
151 {
152 $this->_columns_locked = true;
153 }
154
155 /**
156 * @return bool
157 */
158 public function is_columns_locked()
159 {
160 return $this->_columns_locked;
161 }
162
163 /**
164 * @param bool $fixed
165 */
166 public function set_layout_fixed(bool $fixed)
167 {
168 $this->_fixed_layout = $fixed;
169 }
170
171 /**
172 * @return bool
173 */
174 public function is_layout_fixed()
175 {
176 return $this->_fixed_layout;
177 }
178
179 /**
180 * @return int
181 */
182 public function get_num_rows()
183 {
184 return $this->_num_rows;
185 }
186
187 /**
188 * @return int
189 */
190 public function get_num_cols()
191 {
192 return $this->_num_cols;
193 }
194
195 /**
196 * @return array
197 */
198 public function &get_columns()
199 {
200 return $this->_columns;
201 }
202
203 /**
204 * @param $columns
205 */
206 public function set_columns($columns)
207 {
208 $this->_columns = $columns;
209 }
210
211 /**
212 * @param int $i
213 *
214 * @return mixed
215 */
216 public function &get_column($i)
217 {
218 if (!isset($this->_columns[$i])) {
219 $this->_columns[$i] = [
220 "x" => 0,
221 "min-width" => 0,
222 "max-width" => 0,
223 "used-width" => null,
224 "absolute" => 0,
225 "percent" => 0,
226 "auto" => true,
227 ];
228 }
229
230 return $this->_columns[$i];
231 }
232
233 /**
234 * @return array
235 */
236 public function &get_rows()
237 {
238 return $this->_rows;
239 }
240
241 /**
242 * @param int $j
243 *
244 * @return mixed
245 */
246 public function &get_row($j)
247 {
248 if (!isset($this->_rows[$j])) {
249 $this->_rows[$j] = [
250 "y" => 0,
251 "first-column" => 0,
252 "height" => null,
253 ];
254 }
255
256 return $this->_rows[$j];
257 }
258
259 /**
260 * @param int $i
261 * @param int $j
262 * @param mixed $h_v
263 * @param null|mixed $prop
264 *
265 * @return mixed
266 */
267 public function get_border($i, $j, $h_v, $prop = null)
268 {
269 if (!isset($this->_borders[$i][$j][$h_v])) {
270 $this->_borders[$i][$j][$h_v] = [
271 "width" => 0,
272 "style" => "solid",
273 "color" => "black",
274 ];
275 }
276
277 if (isset($prop)) {
278 return $this->_borders[$i][$j][$h_v][$prop];
279 }
280
281 return $this->_borders[$i][$j][$h_v];
282 }
283
284 /**
285 * @param int $i
286 * @param int $j
287 *
288 * @return array
289 */
290 public function get_border_properties($i, $j)
291 {
292 return [
293 "top" => $this->get_border($i, $j, "horizontal"),
294 "right" => $this->get_border($i, $j + 1, "vertical"),
295 "bottom" => $this->get_border($i + 1, $j, "horizontal"),
296 "left" => $this->get_border($i, $j, "vertical"),
297 ];
298 }
299
300 /**
301 * @param Frame $frame
302 *
303 * @return array|null
304 */
305 public function get_spanned_cells(Frame $frame)
306 {
307 $key = $frame->get_id();
308
309 if (isset($this->_frames[$key])) {
310 return $this->_frames[$key];
311 }
312
313 return null;
314 }
315
316 /**
317 * @param Frame $frame
318 *
319 * @return bool
320 */
321 public function frame_exists_in_cellmap(Frame $frame)
322 {
323 $key = $frame->get_id();
324
325 return isset($this->_frames[$key]);
326 }
327
328 /**
329 * @param Frame $frame
330 *
331 * @return array
332 * @throws Exception
333 */
334 public function get_frame_position(Frame $frame)
335 {
336 global $_dompdf_warnings;
337
338 $key = $frame->get_id();
339
340 if (!isset($this->_frames[$key])) {
341 throw new Exception("Frame not found in cellmap");
342 }
343
344 // Positions are stored relative to the table position
345 [$table_x, $table_y] = $this->_table->get_position();
346 $col = $this->_frames[$key]["columns"][0];
347 $row = $this->_frames[$key]["rows"][0];
348
349 if (!isset($this->_columns[$col])) {
350 $_dompdf_warnings[] = "Frame not found in columns array. Check your table layout for missing or extra TDs.";
351 $x = $table_x;
352 } else {
353 $x = $table_x + $this->_columns[$col]["x"];
354 }
355
356 if (!isset($this->_rows[$row])) {
357 $_dompdf_warnings[] = "Frame not found in row array. Check your table layout for missing or extra TDs.";
358 $y = $table_y;
359 } else {
360 $y = $table_y + $this->_rows[$row]["y"];
361 }
362
363 return [$x, $y, "x" => $x, "y" => $y];
364 }
365
366 /**
367 * @param Frame $frame
368 *
369 * @return int
370 * @throws Exception
371 */
372 public function get_frame_width(Frame $frame)
373 {
374 $key = $frame->get_id();
375
376 if (!isset($this->_frames[$key])) {
377 throw new Exception("Frame not found in cellmap");
378 }
379
380 $cols = $this->_frames[$key]["columns"];
381 $w = 0;
382 foreach ($cols as $i) {
383 $w += $this->_columns[$i]["used-width"];
384 }
385
386 return $w;
387 }
388
389 /**
390 * @param Frame $frame
391 *
392 * @return int
393 * @throws Exception
394 * @throws Exception
395 */
396 public function get_frame_height(Frame $frame)
397 {
398 $key = $frame->get_id();
399
400 if (!isset($this->_frames[$key])) {
401 throw new Exception("Frame not found in cellmap");
402 }
403
404 $rows = $this->_frames[$key]["rows"];
405 $h = 0;
406 foreach ($rows as $i) {
407 if (!isset($this->_rows[$i])) {
408 throw new Exception("The row #$i could not be found, please file an issue in the tracker with the HTML code");
409 }
410
411 $h += $this->_rows[$i]["height"];
412 }
413
414 return $h;
415 }
416
417 /**
418 * @param int $j
419 * @param mixed $width
420 */
421 public function set_column_width($j, $width)
422 {
423 if ($this->_columns_locked) {
424 return;
425 }
426
427 $col =& $this->get_column($j);
428 $col["used-width"] = $width;
429 $next_col =& $this->get_column($j + 1);
430 $next_col["x"] = $col["x"] + $width;
431 }
432
433 /**
434 * @param int $i
435 * @param long $height
436 */
437 public function set_row_height($i, $height)
438 {
439 $row =& $this->get_row($i);
440 if ($height > $row["height"]) {
441 $row["height"] = $height;
442 }
443 $next_row =& $this->get_row($i + 1);
444 $next_row["y"] = $row["y"] + $row["height"];
445 }
446
447 /**
448 * https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution
449 *
450 * @param int $i
451 * @param int $j
452 * @param string $h_v `horizontal` or `vertical`
453 * @param array $border_spec
454 */
455 protected function resolve_border(int $i, int $j, string $h_v, array $border_spec): void
456 {
457 if (!isset($this->_borders[$i][$j][$h_v])) {
458 $this->_borders[$i][$j][$h_v] = $border_spec;
459 return;
460 }
461
462 $border = $this->_borders[$i][$j][$h_v];
463
464 $n_width = $border_spec["width"];
465 $n_style = $border_spec["style"];
466 $o_width = $border["width"];
467 $o_style = $border["style"];
468
469 if ($o_style === "hidden") {
470 return;
471 }
472
473 // A style of `none` has lowest priority independent of its specified
474 // width here, as its resolved width is always 0
475 if ($n_style === "hidden" || $n_width > $o_width
476 || ($o_width == $n_width
477 && isset(self::BORDER_STYLE_SCORE[$n_style])
478 && isset(self::BORDER_STYLE_SCORE[$o_style])
479 && self::BORDER_STYLE_SCORE[$n_style] > self::BORDER_STYLE_SCORE[$o_style])
480 ) {
481 $this->_borders[$i][$j][$h_v] = $border_spec;
482 }
483 }
484
485 /**
486 * Get the resolved border properties for the given frame.
487 *
488 * @param AbstractFrameDecorator $frame
489 *
490 * @return array[]
491 */
492 protected function get_resolved_border(AbstractFrameDecorator $frame): array
493 {
494 $key = $frame->get_id();
495 $columns = $this->_frames[$key]["columns"];
496 $rows = $this->_frames[$key]["rows"];
497
498 $first_col = $columns[0];
499 $last_col = $columns[count($columns) - 1];
500 $first_row = $rows[0];
501 $last_row = $rows[count($rows) - 1];
502
503 $max_top = null;
504 $max_bottom = null;
505 $max_left = null;
506 $max_right = null;
507
508 foreach ($columns as $col) {
509 $top = $this->_borders[$first_row][$col]["horizontal"];
510 $bottom = $this->_borders[$last_row + 1][$col]["horizontal"];
511
512 if ($max_top === null || $top["width"] > $max_top["width"]) {
513 $max_top = $top;
514 }
515 if ($max_bottom === null || $bottom["width"] > $max_bottom["width"]) {
516 $max_bottom = $bottom;
517 }
518 }
519
520 foreach ($rows as $row) {
521 $left = $this->_borders[$row][$first_col]["vertical"];
522 $right = $this->_borders[$row][$last_col + 1]["vertical"];
523
524 if ($max_left === null || $left["width"] > $max_left["width"]) {
525 $max_left = $left;
526 }
527 if ($max_right === null || $right["width"] > $max_right["width"]) {
528 $max_right = $right;
529 }
530 }
531
532 return [$max_top, $max_right, $max_bottom, $max_left];
533 }
534
535 /**
536 * @param AbstractFrameDecorator $frame
537 */
538 public function add_frame(Frame $frame): void
539 {
540 $style = $frame->get_style();
541 $display = $style->display;
542
543 $collapse = $this->_table->get_style()->border_collapse === "collapse";
544
545 // Recursively add the frames within the table, its row groups and rows
546 if ($frame === $this->_table
547 || $display === "table-row"
548 || in_array($display, TableFrameDecorator::ROW_GROUPS, true)
549 ) {
550 $start_row = $this->__row;
551
552 foreach ($frame->get_children() as $child) {
553 $this->add_frame($child);
554 }
555
556 if ($display === "table-row") {
557 $this->add_row();
558 }
559
560 $num_rows = $this->__row - $start_row - 1;
561 $key = $frame->get_id();
562
563 // Row groups always span across the entire table
564 $this->_frames[$key]["columns"] = range(0, max(0, $this->_num_cols - 1));
565 $this->_frames[$key]["rows"] = range($start_row, max(0, $this->__row - 1));
566 $this->_frames[$key]["frame"] = $frame;
567
568 if ($collapse) {
569 $bp = $style->get_border_properties();
570
571 // Resolve vertical borders
572 for ($i = 0; $i < $num_rows + 1; $i++) {
573 $this->resolve_border($start_row + $i, 0, "vertical", $bp["left"]);
574 $this->resolve_border($start_row + $i, $this->_num_cols, "vertical", $bp["right"]);
575 }
576
577 // Resolve horizontal borders
578 for ($j = 0; $j < $this->_num_cols; $j++) {
579 $this->resolve_border($start_row, $j, "horizontal", $bp["top"]);
580 $this->resolve_border($this->__row, $j, "horizontal", $bp["bottom"]);
581 }
582
583 if ($frame === $this->_table) {
584 // Clear borders because the cells are now using them. The
585 // border width still needs to be set to half the resolved
586 // width so that the table is positioned properly
587 [$top, $right, $bottom, $left] = $this->get_resolved_border($frame);
588
589 $style->set_used("border_top_width", $top["width"] / 2);
590 $style->set_used("border_right_width", $right["width"] / 2);
591 $style->set_used("border_bottom_width", $bottom["width"] / 2);
592 $style->set_used("border_left_width", $left["width"] / 2);
593 $style->set_used("border_style", "none");
594 } else {
595 // Clear borders for rows and row groups
596 $style->set_used("border_width", 0);
597 $style->set_used("border_style", "none");
598 }
599 }
600
601 if ($frame === $this->_table) {
602 // Apply resolved borders to table cells and calculate column
603 // widths after all frames have been added
604 $this->calculate_column_widths();
605 }
606 return;
607 }
608
609 // Add the frame to the cellmap
610 $key = $frame->get_id();
611 $node = $frame->get_node();
612 $bp = $style->get_border_properties();
613
614 // Determine where this cell is going
615 $colspan = max((int) $node->getAttribute("colspan"), 1);
616 $rowspan = max((int) $node->getAttribute("rowspan"), 1);
617
618 // Find the next available column (fix by Ciro Mondueri)
619 $ac = $this->__col;
620 while (isset($this->_cells[$this->__row][$ac])) {
621 $ac++;
622 }
623
624 $this->__col = $ac;
625
626 // Rows:
627 for ($i = 0; $i < $rowspan; $i++) {
628 $row = $this->__row + $i;
629
630 $this->_frames[$key]["rows"][] = $row;
631
632 for ($j = 0; $j < $colspan; $j++) {
633 $this->_cells[$row][$this->__col + $j] = $frame;
634 }
635
636 if ($collapse) {
637 // Resolve vertical borders
638 $this->resolve_border($row, $this->__col, "vertical", $bp["left"]);
639 $this->resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"]);
640 }
641 }
642
643 // Columns:
644 for ($j = 0; $j < $colspan; $j++) {
645 $col = $this->__col + $j;
646 $this->_frames[$key]["columns"][] = $col;
647
648 if ($collapse) {
649 // Resolve horizontal borders
650 $this->resolve_border($this->__row, $col, "horizontal", $bp["top"]);
651 $this->resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"]);
652 }
653 }
654
655 $this->_frames[$key]["frame"] = $frame;
656
657 $this->__col += $colspan;
658 if ($this->__col > $this->_num_cols) {
659 $this->_num_cols = $this->__col;
660 }
661 }
662
663 /**
664 * Apply resolved borders to table cells and calculate column widths.
665 */
666 protected function calculate_column_widths(): void
667 {
668 $table = $this->_table;
669 $table_style = $table->get_style();
670 $collapse = $table_style->border_collapse === "collapse";
671
672 if ($collapse) {
673 $v_spacing = 0;
674 $h_spacing = 0;
675 } else {
676 // The additional 1/2 width gets added to the table proper
677 [$h, $v] = $table_style->border_spacing;
678 $v_spacing = $v / 2;
679 $h_spacing = $h / 2;
680 }
681
682 foreach ($this->_frames as $frame_info) {
683 /** @var TableCellFrameDecorator */
684 $frame = $frame_info["frame"];
685 $style = $frame->get_style();
686 $display = $style->display;
687
688 if ($display !== "table-cell") {
689 continue;
690 }
691
692 if ($collapse) {
693 // Set the resolved border at half width
694 [$top, $right, $bottom, $left] = $this->get_resolved_border($frame);
695
696 $style->set_used("border_top_width", $top["width"] / 2);
697 $style->set_used("border_top_style", $top["style"]);
698 $style->set_used("border_top_color", $top["color"]);
699 $style->set_used("border_right_width", $right["width"] / 2);
700 $style->set_used("border_right_style", $right["style"]);
701 $style->set_used("border_right_color", $right["color"]);
702 $style->set_used("border_bottom_width", $bottom["width"] / 2);
703 $style->set_used("border_bottom_style", $bottom["style"]);
704 $style->set_used("border_bottom_color", $bottom["color"]);
705 $style->set_used("border_left_width", $left["width"] / 2);
706 $style->set_used("border_left_style", $left["style"]);
707 $style->set_used("border_left_color", $left["color"]);
708 $style->set_used("margin", 0);
709 } else {
710 // Border spacing is effectively a margin between cells
711 $style->set_used("margin_top", $v_spacing);
712 $style->set_used("margin_bottom", $v_spacing);
713 $style->set_used("margin_left", $h_spacing);
714 $style->set_used("margin_right", $h_spacing);
715 }
716
717 if ($this->_columns_locked) {
718 continue;
719 }
720
721 $node = $frame->get_node();
722 $colspan = max((int) $node->getAttribute("colspan"), 1);
723 $first_col = $frame_info["columns"][0];
724
725 // Resolve the frame's width
726 if ($this->_fixed_layout) {
727 list($frame_min, $frame_max) = [0, 10e-10];
728 } else {
729 list($frame_min, $frame_max) = $frame->get_min_max_width();
730 }
731
732 $width = $style->width;
733
734 $val = null;
735 if (Helpers::is_percent($width) && $colspan === 1) {
736 $var = "percent";
737 $val = (float)rtrim($width, "% ");
738 } elseif ($width !== "auto" && $colspan === 1) {
739 $var = "absolute";
740 $val = $frame_min;
741 }
742
743 $min = 0;
744 $max = 0;
745 for ($cs = 0; $cs < $colspan; $cs++) {
746
747 // Resolve the frame's width(s) with other cells
748 $col =& $this->get_column($first_col + $cs);
749
750 // Note: $var is either 'percent' or 'absolute'. We compare the
751 // requested percentage or absolute values with the existing widths
752 // and adjust accordingly.
753 if (isset($var) && $val > $col[$var]) {
754 $col[$var] = $val;
755 $col["auto"] = false;
756 }
757
758 $min += $col["min-width"];
759 $max += $col["max-width"];
760 }
761
762 if ($frame_min > $min && $colspan === 1) {
763 // The frame needs more space. Expand each sub-column
764 // FIXME try to avoid putting this dummy value when table-layout:fixed
765 $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_min - $min));
766 for ($c = 0; $c < $colspan; $c++) {
767 $col =& $this->get_column($first_col + $c);
768 $col["min-width"] += $inc;
769 }
770 }
771
772 if ($frame_max > $max) {
773 // FIXME try to avoid putting this dummy value when table-layout:fixed
774 $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_max - $max) / $colspan);
775 for ($c = 0; $c < $colspan; $c++) {
776 $col =& $this->get_column($first_col + $c);
777 $col["max-width"] += $inc;
778 }
779 }
780 }
781
782 // Adjust absolute columns so that the absolute (and max) width is the
783 // largest minimum width of all cells. This accounts for cells without
784 // absolute width within an absolute column
785 foreach ($this->_columns as &$col) {
786 if ($col["absolute"] > 0) {
787 $col["absolute"] = $col["min-width"];
788 $col["max-width"] = $col["min-width"];
789 }
790 }
791 }
792
793 protected function add_row(): void
794 {
795 $this->__row++;
796 $this->_num_rows++;
797
798 // Find the next available column
799 $i = 0;
800 while (isset($this->_cells[$this->__row][$i])) {
801 $i++;
802 }
803
804 $this->__col = $i;
805 }
806
807 /**
808 * Remove a row from the cellmap.
809 *
810 * @param Frame
811 */
812 public function remove_row(Frame $row)
813 {
814 $key = $row->get_id();
815 if (!isset($this->_frames[$key])) {
816 return; // Presumably this row has already been removed
817 }
818
819 $this->__row = $this->_num_rows--;
820
821 $rows = $this->_frames[$key]["rows"];
822 $columns = $this->_frames[$key]["columns"];
823
824 // Remove all frames from this row
825 foreach ($rows as $r) {
826 foreach ($columns as $c) {
827 if (isset($this->_cells[$r][$c])) {
828 $id = $this->_cells[$r][$c]->get_id();
829
830 $this->_cells[$r][$c] = null;
831 unset($this->_cells[$r][$c]);
832
833 // has multiple rows?
834 if (isset($this->_frames[$id]) && count($this->_frames[$id]["rows"]) > 1) {
835 // remove just the desired row, but leave the frame
836 if (($row_key = array_search($r, $this->_frames[$id]["rows"])) !== false) {
837 unset($this->_frames[$id]["rows"][$row_key]);
838 }
839 continue;
840 }
841
842 $this->_frames[$id] = null;
843 unset($this->_frames[$id]);
844 }
845 }
846
847 $this->_rows[$r] = null;
848 unset($this->_rows[$r]);
849 }
850
851 $this->_frames[$key] = null;
852 unset($this->_frames[$key]);
853 }
854
855 /**
856 * Remove a row group from the cellmap.
857 *
858 * @param Frame $group The group to remove
859 */
860 public function remove_row_group(Frame $group)
861 {
862 $key = $group->get_id();
863 if (!isset($this->_frames[$key])) {
864 return; // Presumably this row has already been removed
865 }
866
867 $iter = $group->get_first_child();
868 while ($iter) {
869 $this->remove_row($iter);
870 $iter = $iter->get_next_sibling();
871 }
872
873 $this->_frames[$key] = null;
874 unset($this->_frames[$key]);
875 }
876
877 /**
878 * Update a row group after rows have been removed
879 *
880 * @param Frame $group The group to update
881 * @param Frame $last_row The last row in the row group
882 */
883 public function update_row_group(Frame $group, Frame $last_row)
884 {
885 $g_key = $group->get_id();
886
887 $first_index = $this->_frames[$g_key]["rows"][0];
888 $last_index = $first_index;
889 $row = $last_row;
890 while ($row = $row->get_prev_sibling()) {
891 $last_index++;
892 }
893
894 $this->_frames[$g_key]["rows"] = range($first_index, $last_index);
895 }
896
897 public function assign_x_positions(): void
898 {
899 // Pre-condition: widths must be resolved and assigned to columns and
900 // column[0]["x"] must be set.
901
902 if ($this->_columns_locked) {
903 return;
904 }
905
906 $x = $this->_columns[0]["x"];
907 foreach (array_keys($this->_columns) as $j) {
908 $this->_columns[$j]["x"] = $x;
909 $x += $this->_columns[$j]["used-width"];
910 }
911 }
912
913 public function assign_frame_heights(): void
914 {
915 // Pre-condition: widths and heights of each column & row must be
916 // calcluated
917 foreach ($this->_frames as $arr) {
918 $frame = $arr["frame"];
919
920 $h = 0.0;
921 foreach ($arr["rows"] as $row) {
922 if (!isset($this->_rows[$row])) {
923 // The row has been removed because of a page split, so skip it.
924 continue;
925 }
926
927 $h += $this->_rows[$row]["height"];
928 }
929
930 if ($frame instanceof TableCellFrameDecorator) {
931 $frame->set_cell_height($h);
932 } else {
933 $frame->get_style()->set_used("height", $h);
934 }
935 }
936 }
937
938 /**
939 * Re-adjust frame height if the table height is larger than its content
940 */
941 public function set_frame_heights(float $table_height, float $content_height): void
942 {
943 // Distribute the increased height proportionally amongst each row
944 foreach ($this->_frames as $arr) {
945 $frame = $arr["frame"];
946
947 $h = 0.0;
948 foreach ($arr["rows"] as $row) {
949 if (!isset($this->_rows[$row])) {
950 continue;
951 }
952
953 $h += $this->_rows[$row]["height"];
954 }
955
956 if ($content_height > 0) {
957 $new_height = ($h / $content_height) * $table_height;
958 } else {
959 $new_height = 0.0;
960 }
961
962 if ($frame instanceof TableCellFrameDecorator) {
963 $frame->set_cell_height($new_height);
964 } else {
965 $frame->get_style()->set_used("height", $new_height);
966 }
967 }
968 }
969
970 /**
971 * Used for debugging:
972 *
973 * @return string
974 */
975 public function __toString(): string
976 {
977 $str = "";
978 $str .= "Columns:<br/>";
979 $str .= Helpers::pre_r($this->_columns, true);
980 $str .= "Rows:<br/>";
981 $str .= Helpers::pre_r($this->_rows, true);
982
983 $str .= "Frames:<br/>";
984 $arr = [];
985 foreach ($this->_frames as $key => $val) {
986 $arr[$key] = ["columns" => $val["columns"], "rows" => $val["rows"]];
987 }
988
989 $str .= Helpers::pre_r($arr, true);
990
991 if (php_sapi_name() == "cli") {
992 $str = strip_tags(str_replace(["<br/>", "<b>", "</b>"],
993 ["\n", chr(27) . "[01;33m", chr(27) . "[0m"],
994 $str));
995 }
996
997 return $str;
998 }
999 }
1000