1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @package dompdf |
4
|
|
|
* @link http://dompdf.github.com/ |
5
|
|
|
* @author Benj Carson <[email protected]> |
6
|
|
|
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License |
7
|
|
|
*/ |
8
|
|
|
namespace Dompdf\FrameDecorator; |
9
|
|
|
|
10
|
|
|
use Dompdf\Css\Style; |
11
|
|
|
use Dompdf\Dompdf; |
12
|
|
|
use Dompdf\Helpers; |
13
|
|
|
use Dompdf\Frame; |
14
|
|
|
use Dompdf\FrameDecorator\Table as TableFrameDecorator; |
15
|
|
|
use Dompdf\FrameDecorator\TableRow as TableRowFrameDecorator; |
16
|
|
|
use Dompdf\Renderer; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Decorates frames for page layout |
20
|
|
|
* |
21
|
|
|
* @access private |
22
|
|
|
* @package dompdf |
23
|
|
|
*/ |
24
|
|
|
class Page extends AbstractFrameDecorator |
25
|
|
|
{ |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* y value of bottom page margin |
29
|
|
|
* |
30
|
|
|
* @var float |
31
|
|
|
*/ |
32
|
|
|
protected $_bottom_page_margin; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* Flag indicating page is full. |
36
|
|
|
* |
37
|
|
|
* @var bool |
38
|
|
|
*/ |
39
|
|
|
protected $_page_full; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Number of tables currently being reflowed |
43
|
|
|
* |
44
|
|
|
* @var int |
45
|
|
|
*/ |
46
|
|
|
protected $_in_table; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* The pdf renderer |
50
|
|
|
* |
51
|
|
|
* @var Renderer |
52
|
|
|
*/ |
53
|
|
|
protected $_renderer; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* This page's floating frames |
57
|
|
|
* |
58
|
|
|
* @var array |
59
|
|
|
*/ |
60
|
|
|
protected $_floating_frames = array(); |
61
|
|
|
|
62
|
|
|
//........................................................................ |
|
|
|
|
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Class constructor |
66
|
|
|
* |
67
|
|
|
* @param Frame $frame the frame to decorate |
68
|
|
|
* @param Dompdf $dompdf |
69
|
|
|
*/ |
70
|
|
|
function __construct(Frame $frame, Dompdf $dompdf) |
|
|
|
|
71
|
|
|
{ |
72
|
|
|
parent::__construct($frame, $dompdf); |
73
|
|
|
$this->_page_full = false; |
74
|
|
|
$this->_in_table = 0; |
75
|
|
|
$this->_bottom_page_margin = null; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Set the renderer used for this pdf |
80
|
|
|
* |
81
|
|
|
* @param Renderer $renderer the renderer to use |
82
|
|
|
*/ |
83
|
|
|
function set_renderer($renderer) |
|
|
|
|
84
|
|
|
{ |
85
|
|
|
$this->_renderer = $renderer; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Return the renderer used for this pdf |
90
|
|
|
* |
91
|
|
|
* @return Renderer |
92
|
|
|
*/ |
93
|
|
|
function get_renderer() |
|
|
|
|
94
|
|
|
{ |
95
|
|
|
return $this->_renderer; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Set the frame's containing block. Overridden to set $this->_bottom_page_margin. |
100
|
|
|
* |
101
|
|
|
* @param float $x |
102
|
|
|
* @param float $y |
103
|
|
|
* @param float $w |
104
|
|
|
* @param float $h |
105
|
|
|
*/ |
106
|
|
|
function set_containing_block($x = null, $y = null, $w = null, $h = null) |
|
|
|
|
107
|
|
|
{ |
108
|
|
|
parent::set_containing_block($x, $y, $w, $h); |
109
|
|
|
//$w = $this->get_containing_block("w"); |
|
|
|
|
110
|
|
|
if (isset($h)) { |
111
|
|
|
$this->_bottom_page_margin = $h; |
112
|
|
|
} // - $this->_frame->get_style()->length_in_pt($this->_frame->get_style()->margin_bottom, $w); |
|
|
|
|
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Returns true if the page is full and is no longer accepting frames. |
117
|
|
|
* |
118
|
|
|
* @return bool |
119
|
|
|
*/ |
120
|
|
|
function is_full() |
|
|
|
|
121
|
|
|
{ |
122
|
|
|
return $this->_page_full; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* Start a new page by resetting the full flag. |
127
|
|
|
*/ |
128
|
|
|
function next_page() |
|
|
|
|
129
|
|
|
{ |
130
|
|
|
$this->_floating_frames = array(); |
131
|
|
|
$this->_renderer->new_page(); |
132
|
|
|
$this->_page_full = false; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Indicate to the page that a table is currently being reflowed. |
137
|
|
|
*/ |
138
|
|
|
function table_reflow_start() |
|
|
|
|
139
|
|
|
{ |
140
|
|
|
$this->_in_table++; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Indicate to the page that table reflow is finished. |
145
|
|
|
*/ |
146
|
|
|
function table_reflow_end() |
|
|
|
|
147
|
|
|
{ |
148
|
|
|
$this->_in_table--; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* Return whether we are currently in a nested table or not |
153
|
|
|
* |
154
|
|
|
* @return bool |
155
|
|
|
*/ |
156
|
|
|
function in_nested_table() |
|
|
|
|
157
|
|
|
{ |
158
|
|
|
return $this->_in_table > 1; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Check if a forced page break is required before $frame. This uses the |
163
|
|
|
* frame's page_break_before property as well as the preceeding frame's |
164
|
|
|
* page_break_after property. |
165
|
|
|
* |
166
|
|
|
* @link http://www.w3.org/TR/CSS21/page.html#forced |
167
|
|
|
* |
168
|
|
|
* @param Frame $frame the frame to check |
169
|
|
|
* |
170
|
|
|
* @return bool true if a page break occured |
171
|
|
|
*/ |
172
|
|
|
function check_forced_page_break(Frame $frame) |
|
|
|
|
173
|
|
|
{ |
174
|
|
|
|
175
|
|
|
// Skip check if page is already split |
176
|
|
|
if ($this->_page_full) { |
177
|
|
|
return null; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
$block_types = array("block", "list-item", "table", "inline"); |
181
|
|
|
$page_breaks = array("always", "left", "right"); |
182
|
|
|
|
183
|
|
|
$style = $frame->get_style(); |
184
|
|
|
|
185
|
|
|
if (!in_array($style->display, $block_types)) { |
|
|
|
|
186
|
|
|
return false; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
// Find the previous block-level sibling |
190
|
|
|
$prev = $frame->get_prev_sibling(); |
191
|
|
|
|
192
|
|
|
while ($prev && !in_array($prev->get_style()->display, $block_types)) { |
|
|
|
|
193
|
|
|
$prev = $prev->get_prev_sibling(); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
View Code Duplication |
if (in_array($style->page_break_before, $page_breaks)) { |
|
|
|
|
197
|
|
|
// Prevent cascading splits |
198
|
|
|
$frame->split(null, true); |
|
|
|
|
199
|
|
|
// We have to grab the style again here because split() resets |
200
|
|
|
// $frame->style to the frame's orignal style. |
201
|
|
|
$frame->get_style()->page_break_before = "auto"; |
|
|
|
|
202
|
|
|
$this->_page_full = true; |
203
|
|
|
|
204
|
|
|
return true; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
View Code Duplication |
if ($prev && in_array($prev->get_style()->page_break_after, $page_breaks)) { |
|
|
|
|
208
|
|
|
// Prevent cascading splits |
209
|
|
|
$frame->split(null, true); |
|
|
|
|
210
|
|
|
$prev->get_style()->page_break_after = "auto"; |
|
|
|
|
211
|
|
|
$this->_page_full = true; |
212
|
|
|
|
213
|
|
|
return true; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
if ($prev && $prev->get_last_child() && $frame->get_node()->nodeName != "body") { |
217
|
|
|
$prev_last_child = $prev->get_last_child(); |
218
|
|
View Code Duplication |
if (in_array($prev_last_child->get_style()->page_break_after, $page_breaks)) { |
|
|
|
|
219
|
|
|
$frame->split(null, true); |
|
|
|
|
220
|
|
|
$prev_last_child->get_style()->page_break_after = "auto"; |
|
|
|
|
221
|
|
|
$this->_page_full = true; |
222
|
|
|
|
223
|
|
|
return true; |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
return false; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Determine if a page break is allowed before $frame |
232
|
|
|
* http://www.w3.org/TR/CSS21/page.html#allowed-page-breaks |
233
|
|
|
* |
234
|
|
|
* In the normal flow, page breaks can occur at the following places: |
235
|
|
|
* |
236
|
|
|
* 1. In the vertical margin between block boxes. When a page |
237
|
|
|
* break occurs here, the used values of the relevant |
238
|
|
|
* 'margin-top' and 'margin-bottom' properties are set to '0'. |
239
|
|
|
* 2. Between line boxes inside a block box. |
240
|
|
|
* |
241
|
|
|
* These breaks are subject to the following rules: |
242
|
|
|
* |
243
|
|
|
* * Rule A: Breaking at (1) is allowed only if the |
244
|
|
|
* 'page-break-after' and 'page-break-before' properties of |
245
|
|
|
* all the elements generating boxes that meet at this margin |
246
|
|
|
* allow it, which is when at least one of them has the value |
247
|
|
|
* 'always', 'left', or 'right', or when all of them are |
248
|
|
|
* 'auto'. |
249
|
|
|
* |
250
|
|
|
* * Rule B: However, if all of them are 'auto' and the |
251
|
|
|
* nearest common ancestor of all the elements has a |
252
|
|
|
* 'page-break-inside' value of 'avoid', then breaking here is |
253
|
|
|
* not allowed. |
254
|
|
|
* |
255
|
|
|
* * Rule C: Breaking at (2) is allowed only if the number of |
256
|
|
|
* line boxes between the break and the start of the enclosing |
257
|
|
|
* block box is the value of 'orphans' or more, and the number |
258
|
|
|
* of line boxes between the break and the end of the box is |
259
|
|
|
* the value of 'widows' or more. |
260
|
|
|
* |
261
|
|
|
* * Rule D: In addition, breaking at (2) is allowed only if |
262
|
|
|
* the 'page-break-inside' property is 'auto'. |
263
|
|
|
* |
264
|
|
|
* If the above doesn't provide enough break points to keep |
265
|
|
|
* content from overflowing the page boxes, then rules B and D are |
266
|
|
|
* dropped in order to find additional breakpoints. |
267
|
|
|
* |
268
|
|
|
* If that still does not lead to sufficient break points, rules A |
269
|
|
|
* and C are dropped as well, to find still more break points. |
270
|
|
|
* |
271
|
|
|
* We will also allow breaks between table rows. However, when |
272
|
|
|
* splitting a table, the table headers should carry over to the |
273
|
|
|
* next page (but they don't yet). |
274
|
|
|
* |
275
|
|
|
* @param Frame $frame the frame to check |
276
|
|
|
* |
277
|
|
|
* @return bool true if a break is allowed, false otherwise |
278
|
|
|
*/ |
279
|
|
|
protected function _page_break_allowed(Frame $frame) |
280
|
|
|
{ |
281
|
|
|
$block_types = array("block", "list-item", "table", "-dompdf-image"); |
282
|
|
|
Helpers::dompdf_debug("page-break", "_page_break_allowed(" . $frame->get_node()->nodeName . ")"); |
283
|
|
|
$display = $frame->get_style()->display; |
|
|
|
|
284
|
|
|
|
285
|
|
|
// Block Frames (1): |
|
|
|
|
286
|
|
|
if (in_array($display, $block_types)) { |
287
|
|
|
|
288
|
|
|
// Avoid breaks within table-cells |
289
|
|
|
if ($this->_in_table) { |
290
|
|
|
Helpers::dompdf_debug("page-break", "In table: " . $this->_in_table); |
291
|
|
|
|
292
|
|
|
return false; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
// Rules A & B |
296
|
|
|
|
297
|
|
|
if ($frame->get_style()->page_break_before === "avoid") { |
|
|
|
|
298
|
|
|
Helpers::dompdf_debug("page-break", "before: avoid"); |
299
|
|
|
|
300
|
|
|
return false; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
// Find the preceeding block-level sibling |
304
|
|
|
$prev = $frame->get_prev_sibling(); |
305
|
|
|
while ($prev && !in_array($prev->get_style()->display, $block_types)) { |
|
|
|
|
306
|
|
|
$prev = $prev->get_prev_sibling(); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
// Does the previous element allow a page break after? |
310
|
|
|
if ($prev && $prev->get_style()->page_break_after === "avoid") { |
|
|
|
|
311
|
|
|
Helpers::dompdf_debug("page-break", "after: avoid"); |
312
|
|
|
|
313
|
|
|
return false; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
// If both $prev & $frame have the same parent, check the parent's |
317
|
|
|
// page_break_inside property. |
318
|
|
|
$parent = $frame->get_parent(); |
319
|
|
|
if ($prev && $parent && $parent->get_style()->page_break_inside === "avoid") { |
|
|
|
|
320
|
|
|
Helpers::dompdf_debug("page-break", "parent inside: avoid"); |
321
|
|
|
|
322
|
|
|
return false; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
// To prevent cascading page breaks when a top-level element has |
326
|
|
|
// page-break-inside: avoid, ensure that at least one frame is |
327
|
|
|
// on the page before splitting. |
328
|
|
View Code Duplication |
if ($parent->get_node()->nodeName === "body" && !$prev) { |
|
|
|
|
329
|
|
|
// We are the body's first child |
330
|
|
|
Helpers::dompdf_debug("page-break", "Body's first child."); |
331
|
|
|
|
332
|
|
|
return false; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
// If the frame is the first block-level frame, use the value from |
336
|
|
|
// $frame's parent instead. |
337
|
|
|
if (!$prev && $parent) { |
338
|
|
|
return $this->_page_break_allowed($parent); |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
Helpers::dompdf_debug("page-break", "block: break allowed"); |
342
|
|
|
|
343
|
|
|
return true; |
344
|
|
|
|
345
|
|
|
} // Inline frames (2): |
|
|
|
|
346
|
|
|
else { |
347
|
|
|
if (in_array($display, Style::$INLINE_TYPES)) { |
|
|
|
|
348
|
|
|
|
349
|
|
|
// Avoid breaks within table-cells |
350
|
|
|
if ($this->_in_table) { |
351
|
|
|
Helpers::dompdf_debug("page-break", "In table: " . $this->_in_table); |
352
|
|
|
|
353
|
|
|
return false; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
// Rule C |
357
|
|
|
$block_parent = $frame->find_block_parent(); |
|
|
|
|
358
|
|
|
if (count($block_parent->get_line_boxes()) < $frame->get_style()->orphans) { |
|
|
|
|
359
|
|
|
Helpers::dompdf_debug("page-break", "orphans"); |
360
|
|
|
|
361
|
|
|
return false; |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
// FIXME: Checking widows is tricky without having laid out the |
365
|
|
|
// remaining line boxes. Just ignore it for now... |
366
|
|
|
|
367
|
|
|
// Rule D |
368
|
|
|
$p = $block_parent; |
369
|
|
View Code Duplication |
while ($p) { |
|
|
|
|
370
|
|
|
if ($p->get_style()->page_break_inside === "avoid") { |
371
|
|
|
Helpers::dompdf_debug("page-break", "parent->inside: avoid"); |
372
|
|
|
|
373
|
|
|
return false; |
374
|
|
|
} |
375
|
|
|
$p = $p->find_block_parent(); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
// To prevent cascading page breaks when a top-level element has |
379
|
|
|
// page-break-inside: avoid, ensure that at least one frame with |
380
|
|
|
// some content is on the page before splitting. |
381
|
|
|
$prev = $frame->get_prev_sibling(); |
382
|
|
|
while ($prev && ($prev->is_text_node() && trim($prev->get_node()->nodeValue) == "")) { |
383
|
|
|
$prev = $prev->get_prev_sibling(); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
View Code Duplication |
if ($block_parent->get_node()->nodeName === "body" && !$prev) { |
|
|
|
|
387
|
|
|
// We are the body's first child |
388
|
|
|
Helpers::dompdf_debug("page-break", "Body's first child."); |
389
|
|
|
|
390
|
|
|
return false; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
// Skip breaks on empty text nodes |
394
|
|
|
if ($frame->is_text_node() && $frame->get_node()->nodeValue == "") { |
395
|
|
|
return false; |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
Helpers::dompdf_debug("page-break", "inline: break allowed"); |
399
|
|
|
|
400
|
|
|
return true; |
401
|
|
|
|
402
|
|
|
// Table-rows |
403
|
|
|
} else { |
404
|
|
|
if ($display === "table-row") { |
405
|
|
|
// Simply check if the parent table's page_break_inside property is |
406
|
|
|
// not 'avoid' |
407
|
|
|
$table = Table::find_parent_table($frame); |
408
|
|
|
|
409
|
|
|
$p = $table; |
410
|
|
View Code Duplication |
while ($p) { |
|
|
|
|
411
|
|
|
if ($p->get_style()->page_break_inside === "avoid") { |
412
|
|
|
Helpers::dompdf_debug("page-break", "parent->inside: avoid"); |
413
|
|
|
|
414
|
|
|
return false; |
415
|
|
|
} |
416
|
|
|
$p = $p->find_block_parent(); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
// Avoid breaking after the first row of a table |
420
|
|
|
if ($table && $table->get_first_child() === $frame || $table->get_first_child()->get_first_child() === $frame) { |
421
|
|
|
Helpers::dompdf_debug("page-break", "table: first-row"); |
422
|
|
|
|
423
|
|
|
return false; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
// If this is a nested table, prevent the page from breaking |
427
|
|
|
if ($this->_in_table > 1) { |
428
|
|
|
Helpers::dompdf_debug("page-break", "table: nested table"); |
429
|
|
|
|
430
|
|
|
return false; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
Helpers::dompdf_debug("page-break", "table-row/row-groups: break allowed"); |
434
|
|
|
|
435
|
|
|
return true; |
436
|
|
|
} else { |
437
|
|
|
if (in_array($display, Table::$ROW_GROUPS)) { |
438
|
|
|
|
439
|
|
|
// Disallow breaks at row-groups: only split at row boundaries |
440
|
|
|
return false; |
441
|
|
|
|
442
|
|
|
} else { |
443
|
|
|
Helpers::dompdf_debug("page-break", "? " . $frame->get_style()->display . ""); |
|
|
|
|
444
|
|
|
|
445
|
|
|
return false; |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
} |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* Check if $frame will fit on the page. If the frame does not fit, |
455
|
|
|
* the frame tree is modified so that a page break occurs in the |
456
|
|
|
* correct location. |
457
|
|
|
* |
458
|
|
|
* @param Frame $frame the frame to check |
459
|
|
|
* |
460
|
|
|
* @return bool |
461
|
|
|
*/ |
462
|
|
|
function check_page_break(Frame $frame) |
|
|
|
|
463
|
|
|
{ |
464
|
|
|
//FIXME: should not need to do this since we're tracking table status in `$this->_in_table` |
465
|
|
|
$p = $frame; |
466
|
|
|
$in_table = false; |
467
|
|
|
while ($p) { |
468
|
|
|
if ($p->is_table()) { $in_table = true; break; } |
469
|
|
|
$p = $p->get_parent(); |
470
|
|
|
} |
471
|
|
|
// Do not split if we have already or if the frame was already |
472
|
|
|
// pushed to the next page (prevents infinite loops) |
473
|
|
|
if ($in_table) { |
474
|
|
|
if ($this->_page_full && $frame->_already_pushed) { |
475
|
|
|
return false; |
476
|
|
|
} |
477
|
|
|
} elseif ($this->_page_full || $frame->_already_pushed) { |
478
|
|
|
return false; |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
//FIXME: work-around for infinite loop due to tables |
482
|
|
|
if ($in_table && $frame->_already_pushed) { |
483
|
|
|
return false; |
484
|
|
|
} |
485
|
|
|
$p = $frame; |
486
|
|
|
do { |
487
|
|
|
$display = $p->get_style()->display; |
|
|
|
|
488
|
|
|
if ($display == "table-row") { |
489
|
|
|
if ($p->_already_pushed) { return false; } |
490
|
|
|
} |
491
|
|
|
} while ($p = $p->get_parent()); |
492
|
|
|
|
493
|
|
|
// If the frame is absolute of fixed it shouldn't break |
494
|
|
|
$p = $frame; |
495
|
|
|
do { |
496
|
|
|
if ($p->is_absolute()) { |
497
|
|
|
return false; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
// FIXME If the row is taller than the page and |
501
|
|
|
// if it the first of the page, we don't break |
502
|
|
|
$display = $p->get_style()->display; |
|
|
|
|
503
|
|
|
if ($display === "table-row" |
504
|
|
|
&& !$p->get_prev_sibling() |
505
|
|
|
&& $p->get_margin_height() > $this->get_margin_height() |
506
|
|
|
) { |
507
|
|
|
return false; |
508
|
|
|
} |
509
|
|
|
} while ($p = $p->get_parent()); |
510
|
|
|
|
511
|
|
|
$margin_height = $frame->get_margin_height(); |
512
|
|
|
|
513
|
|
|
// Determine the frame's maximum y value |
514
|
|
|
$max_y = (float)$frame->get_position("y") + $margin_height; |
515
|
|
|
|
516
|
|
|
// If a split is to occur here, then the bottom margins & paddings of all |
517
|
|
|
// parents of $frame must fit on the page as well: |
518
|
|
|
$p = $frame->get_parent(); |
519
|
|
|
while ($p) { |
520
|
|
|
$max_y += $p->get_style()->computed_bottom_spacing(); |
521
|
|
|
$p = $p->get_parent(); |
522
|
|
|
} |
523
|
|
|
|
524
|
|
|
|
525
|
|
|
// Check if $frame flows off the page |
526
|
|
|
if ($max_y <= $this->_bottom_page_margin) { |
527
|
|
|
// no: do nothing |
528
|
|
|
return false; |
529
|
|
|
} |
530
|
|
|
|
531
|
|
|
Helpers::dompdf_debug("page-break", "check_page_break"); |
532
|
|
|
Helpers::dompdf_debug("page-break", "in_table: " . $this->_in_table); |
533
|
|
|
|
534
|
|
|
// yes: determine page break location |
535
|
|
|
$iter = $frame; |
536
|
|
|
$flg = false; |
537
|
|
|
|
538
|
|
|
$in_table = $this->_in_table; |
539
|
|
|
|
540
|
|
|
Helpers::dompdf_debug("page-break", "Starting search"); |
541
|
|
|
while ($iter) { |
542
|
|
|
// echo "\nbacktrack: " .$iter->get_node()->nodeName ." ".spl_object_hash($iter->get_node()). ""; |
|
|
|
|
543
|
|
|
if ($iter === $this) { |
544
|
|
|
Helpers::dompdf_debug("page-break", "reached root."); |
545
|
|
|
// We've reached the root in our search. Just split at $frame. |
546
|
|
|
break; |
547
|
|
|
} |
548
|
|
|
|
549
|
|
|
if ($this->_page_break_allowed($iter)) { |
550
|
|
|
Helpers::dompdf_debug("page-break", "break allowed, splitting."); |
551
|
|
|
$iter->split(null, true); |
|
|
|
|
552
|
|
|
$this->_page_full = true; |
553
|
|
|
$this->_in_table = $in_table; |
554
|
|
|
$frame->_already_pushed = true; |
555
|
|
|
|
556
|
|
|
return true; |
557
|
|
|
} |
558
|
|
|
|
559
|
|
View Code Duplication |
if (!$flg && $next = $iter->get_last_child()) { |
|
|
|
|
560
|
|
|
Helpers::dompdf_debug("page-break", "following last child."); |
561
|
|
|
|
562
|
|
|
if ($next->is_table()) { |
563
|
|
|
$this->_in_table++; |
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
$iter = $next; |
567
|
|
|
continue; |
568
|
|
|
} |
569
|
|
|
|
570
|
|
|
if ($next = $iter->get_prev_sibling()) { |
571
|
|
|
Helpers::dompdf_debug("page-break", "following prev sibling."); |
572
|
|
|
|
573
|
|
|
if ($next->is_table() && !$iter->is_table()) { |
574
|
|
|
$this->_in_table++; |
575
|
|
|
} else if (!$next->is_table() && $iter->is_table()) { |
576
|
|
|
$this->_in_table--; |
577
|
|
|
} |
578
|
|
|
|
579
|
|
|
$iter = $next; |
580
|
|
|
$flg = false; |
581
|
|
|
continue; |
582
|
|
|
} |
583
|
|
|
|
584
|
|
View Code Duplication |
if ($next = $iter->get_parent()) { |
|
|
|
|
585
|
|
|
Helpers::dompdf_debug("page-break", "following parent."); |
586
|
|
|
|
587
|
|
|
if ($iter->is_table()) { |
588
|
|
|
$this->_in_table--; |
589
|
|
|
} |
590
|
|
|
|
591
|
|
|
$iter = $next; |
592
|
|
|
$flg = true; |
593
|
|
|
continue; |
594
|
|
|
} |
595
|
|
|
|
596
|
|
|
break; |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
$this->_in_table = $in_table; |
600
|
|
|
|
601
|
|
|
// No valid page break found. Just break at $frame. |
602
|
|
|
Helpers::dompdf_debug("page-break", "no valid break found, just splitting."); |
603
|
|
|
|
604
|
|
|
// If we are in a table, backtrack to the nearest top-level table row |
605
|
|
|
if ($this->_in_table) { |
606
|
|
|
$iter = $frame; |
607
|
|
|
while ($iter && $iter->get_style()->display !== "table-row" && $iter->get_style()->display !== 'table-row-group' && $iter->_already_pushed === false) { |
|
|
|
|
608
|
|
|
$iter = $iter->get_parent(); |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
if ($iter) { |
612
|
|
|
$iter->split(null, true); |
613
|
|
|
} else { |
614
|
|
|
return false; |
615
|
|
|
} |
616
|
|
|
} else { |
617
|
|
|
$frame->split(null, true); |
|
|
|
|
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
$this->_page_full = true; |
621
|
|
|
$frame->_already_pushed = true; |
622
|
|
|
|
623
|
|
|
return true; |
624
|
|
|
} |
625
|
|
|
|
626
|
|
|
//........................................................................ |
|
|
|
|
627
|
|
|
|
628
|
|
|
/** |
629
|
|
|
* @param Frame|null $frame |
630
|
|
|
* @param bool $force_pagebreak |
631
|
|
|
*/ |
632
|
|
|
function split(Frame $frame = null, $force_pagebreak = false) |
|
|
|
|
633
|
|
|
{ |
634
|
|
|
// Do nothing |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
/** |
638
|
|
|
* Add a floating frame |
639
|
|
|
* |
640
|
|
|
* @param Frame $frame |
641
|
|
|
* |
642
|
|
|
* @return void |
643
|
|
|
*/ |
644
|
|
|
function add_floating_frame(Frame $frame) |
|
|
|
|
645
|
|
|
{ |
646
|
|
|
array_unshift($this->_floating_frames, $frame); |
647
|
|
|
} |
648
|
|
|
|
649
|
|
|
/** |
650
|
|
|
* @return Frame[] |
651
|
|
|
*/ |
652
|
|
|
function get_floating_frames() |
|
|
|
|
653
|
|
|
{ |
654
|
|
|
return $this->_floating_frames; |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
/** |
658
|
|
|
* @param $key |
659
|
|
|
*/ |
660
|
|
|
public function remove_floating_frame($key) |
661
|
|
|
{ |
662
|
|
|
unset($this->_floating_frames[$key]); |
663
|
|
|
} |
664
|
|
|
|
665
|
|
|
/** |
666
|
|
|
* @param Frame $child |
667
|
|
|
* @return int|mixed |
668
|
|
|
*/ |
669
|
|
|
public function get_lowest_float_offset(Frame $child) |
670
|
|
|
{ |
671
|
|
|
$style = $child->get_style(); |
672
|
|
|
$side = $style->clear; |
|
|
|
|
673
|
|
|
$float = $style->float; |
|
|
|
|
674
|
|
|
|
675
|
|
|
$y = 0; |
676
|
|
|
|
677
|
|
|
if ($float === "none") { |
678
|
|
|
foreach ($this->_floating_frames as $key => $frame) { |
679
|
|
|
if ($side === "both" || $frame->get_style()->float === $side) { |
680
|
|
|
$y = max($y, $frame->get_position("y") + $frame->get_margin_height()); |
681
|
|
|
} |
682
|
|
|
$this->remove_floating_frame($key); |
683
|
|
|
} |
684
|
|
|
} |
685
|
|
|
|
686
|
|
|
if ($y > 0) { |
687
|
|
|
$y++; // add 1px buffer from float |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
return $y; |
691
|
|
|
} |
692
|
|
|
} |
693
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.