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 / FrameDecorator / Table.php

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

344 lines 9.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\FrameDecorator;
8
9 use Dompdf\Cellmap;
10 use DOMNode;
11 use Dompdf\Css\Style;
12 use Dompdf\Dompdf;
13 use Dompdf\Frame;
14
15 /**
16 * Decorates Frames for table layout
17 *
18 * @package dompdf
19 */
20 class Table extends AbstractFrameDecorator
21 {
22 public const VALID_CHILDREN = Style::TABLE_INTERNAL_TYPES;
23
24 /**
25 * List of all row-group display types.
26 */
27 public const ROW_GROUPS = [
28 "table-row-group",
29 "table-header-group",
30 "table-footer-group"
31 ];
32
33 /**
34 * The Cellmap object for this table. The cellmap maps table cells
35 * to rows and columns, and aids in calculating column widths.
36 *
37 * @var Cellmap
38 */
39 protected $_cellmap;
40
41 /**
42 * Table header rows. Each table header is duplicated when a table
43 * spans pages.
44 *
45 * @var TableRowGroup[]
46 */
47 protected $_headers;
48
49 /**
50 * Table footer rows. Each table footer is duplicated when a table
51 * spans pages.
52 *
53 * @var TableRowGroup[]
54 */
55 protected $_footers;
56
57 /**
58 * Class constructor
59 *
60 * @param Frame $frame the frame to decorate
61 * @param Dompdf $dompdf
62 */
63 public function __construct(Frame $frame, Dompdf $dompdf)
64 {
65 parent::__construct($frame, $dompdf);
66 $this->_cellmap = new Cellmap($this);
67
68 if ($frame->get_style()->table_layout === "fixed") {
69 $this->_cellmap->set_layout_fixed(true);
70 }
71
72 $this->_headers = [];
73 $this->_footers = [];
74 }
75
76 public function reset()
77 {
78 parent::reset();
79 $this->_cellmap->reset();
80 $this->_headers = [];
81 $this->_footers = [];
82 $this->_reflower->reset();
83 }
84
85 //........................................................................
86
87 /**
88 * Split the table at $row. $row and all subsequent rows will be
89 * added to the clone. This method is overridden in order to remove
90 * frames from the cellmap properly.
91 */
92 public function split(?Frame $child = null, bool $page_break = false, bool $forced = false): void
93 {
94 if (is_null($child)) {
95 parent::split($child, $page_break, $forced);
96 return;
97 }
98
99 // If $child is a header or if it is the first non-header row, do
100 // not duplicate headers, simply move the table to the next page.
101 if (count($this->_headers)
102 && !in_array($child, $this->_headers, true)
103 && !in_array($child->get_prev_sibling(), $this->_headers, true)
104 ) {
105 $first_header = null;
106
107 // Insert copies of the table headers before $child
108 foreach ($this->_headers as $header) {
109
110 $new_header = $header->deep_copy();
111
112 if (is_null($first_header)) {
113 $first_header = $new_header;
114 }
115
116 $this->insert_child_before($new_header, $child);
117 }
118
119 parent::split($first_header, $page_break, $forced);
120
121 } elseif (in_array($child->get_style()->display, self::ROW_GROUPS, true)) {
122
123 // Individual rows should have already been handled
124 parent::split($child, $page_break, $forced);
125
126 } else {
127
128 $iter = $child;
129
130 while ($iter) {
131 $this->_cellmap->remove_row($iter);
132 $iter = $iter->get_next_sibling();
133 }
134
135 parent::split($child, $page_break, $forced);
136 }
137 }
138
139 public function copy(DOMNode $node)
140 {
141 $deco = parent::copy($node);
142
143 // In order to keep columns' widths through pages
144 $deco->_cellmap->set_columns($this->_cellmap->get_columns());
145 $deco->_cellmap->lock_columns();
146
147 return $deco;
148 }
149
150 /**
151 * Static function to locate the parent table of a frame
152 *
153 * @param Frame $frame
154 *
155 * @return Table the table that is an ancestor of $frame
156 */
157 public static function find_parent_table(Frame $frame)
158 {
159 while ($frame = $frame->get_parent()) {
160 if ($frame->is_table()) {
161 break;
162 }
163 }
164
165 return $frame;
166 }
167
168 /**
169 * Return this table's Cellmap
170 *
171 * @return Cellmap
172 */
173 public function get_cellmap()
174 {
175 return $this->_cellmap;
176 }
177
178 //........................................................................
179
180 /**
181 * Check for text nodes between valid table children that only contain white
182 * space, except if white space is to be preserved.
183 *
184 * @param AbstractFrameDecorator $frame
185 *
186 * @return bool
187 */
188 private function isEmptyTextNode(AbstractFrameDecorator $frame): bool
189 {
190 // This is based on the white-space pattern in `FrameReflower\Text`,
191 // i.e. only match on collapsible white space
192 $wsPattern = '/^[^\S\xA0\x{202F}\x{2007}]*$/u';
193 $validChildOrNull = function ($frame) {
194 return $frame === null
195 || in_array($frame->get_style()->display, self::VALID_CHILDREN, true);
196 };
197
198 return $frame instanceof Text
199 && !$frame->is_pre()
200 && preg_match($wsPattern, $frame->get_text())
201 && $validChildOrNull($frame->get_prev_sibling())
202 && $validChildOrNull($frame->get_next_sibling());
203 }
204
205 /**
206 * Restructure tree so that the table has the correct structure. Misplaced
207 * children are appropriately wrapped in anonymous row groups, rows, and
208 * cells.
209 *
210 * https://www.w3.org/TR/CSS21/tables.html#anonymous-boxes
211 */
212 public function normalize(): void
213 {
214 $column_caption = ["table-column-group", "table-column", "table-caption"];
215 $children = iterator_to_array($this->get_children());
216 $tbody = null;
217
218 foreach ($children as $child) {
219 $display = $child->get_style()->display;
220
221 if (in_array($display, self::ROW_GROUPS, true)) {
222 // Reset anonymous tbody
223 $tbody = null;
224
225 // Add headers and footers
226 if ($display === "table-header-group") {
227 $this->_headers[] = $child;
228 } elseif ($display === "table-footer-group") {
229 $this->_footers[] = $child;
230 }
231 continue;
232 }
233
234 if (in_array($display, $column_caption, true)) {
235 continue;
236 }
237
238 // Remove empty text nodes between valid children
239 if ($this->isEmptyTextNode($child)) {
240 $this->remove_child($child);
241 continue;
242 }
243
244 // Catch consecutive misplaced frames within a single anonymous group
245 if ($tbody === null) {
246 $tbody = $this->create_anonymous_child("tbody", "table-row-group");
247 $this->insert_child_before($tbody, $child);
248 }
249
250 $tbody->append_child($child);
251 }
252
253 // Handle empty table: Make sure there is at least one row group
254 if (!$this->get_first_child()) {
255 $tbody = $this->create_anonymous_child("tbody", "table-row-group");
256 $this->append_child($tbody);
257 }
258
259 foreach ($this->get_children() as $child) {
260 $display = $child->get_style()->display;
261
262 if (in_array($display, self::ROW_GROUPS, true)) {
263 $this->normalizeRowGroup($child);
264 }
265 }
266 }
267
268 private function normalizeRowGroup(AbstractFrameDecorator $frame): void
269 {
270 $children = iterator_to_array($frame->get_children());
271 $tr = null;
272
273 foreach ($children as $child) {
274 $display = $child->get_style()->display;
275
276 if ($display === "table-row") {
277 // Reset anonymous tr
278 $tr = null;
279 continue;
280 }
281
282 // Remove empty text nodes between valid children
283 if ($this->isEmptyTextNode($child)) {
284 $frame->remove_child($child);
285 continue;
286 }
287
288 // Catch consecutive misplaced frames within a single anonymous row
289 if ($tr === null) {
290 $tr = $frame->create_anonymous_child("tr", "table-row");
291 $frame->insert_child_before($tr, $child);
292 }
293
294 $tr->append_child($child);
295 }
296
297 // Handle empty row group: Make sure there is at least one row
298 if (!$frame->get_first_child()) {
299 $tr = $frame->create_anonymous_child("tr", "table-row");
300 $frame->append_child($tr);
301 }
302
303 foreach ($frame->get_children() as $child) {
304 $this->normalizeRow($child);
305 }
306 }
307
308 private function normalizeRow(AbstractFrameDecorator $frame): void
309 {
310 $children = iterator_to_array($frame->get_children());
311 $td = null;
312
313 foreach ($children as $child) {
314 $display = $child->get_style()->display;
315
316 if ($display === "table-cell") {
317 // Reset anonymous td
318 $td = null;
319 continue;
320 }
321
322 // Remove empty text nodes between valid children
323 if ($this->isEmptyTextNode($child)) {
324 $frame->remove_child($child);
325 continue;
326 }
327
328 // Catch consecutive misplaced frames within a single anonymous cell
329 if ($td === null) {
330 $td = $frame->create_anonymous_child("td", "table-cell");
331 $frame->insert_child_before($td, $child);
332 }
333
334 $td->append_child($child);
335 }
336
337 // Handle empty row: Make sure there is at least one cell
338 if (!$frame->get_first_child()) {
339 $td = $frame->create_anonymous_child("td", "table-cell");
340 $frame->append_child($td);
341 }
342 }
343 }
344