weasyprint 65.1__py3-none-any.whl → 67.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- weasyprint/__init__.py +17 -7
- weasyprint/__main__.py +21 -10
- weasyprint/anchors.py +4 -4
- weasyprint/css/__init__.py +732 -67
- weasyprint/css/computed_values.py +65 -170
- weasyprint/css/counters.py +1 -1
- weasyprint/css/functions.py +206 -0
- weasyprint/css/html5_ua.css +3 -7
- weasyprint/css/html5_ua_form.css +2 -2
- weasyprint/css/media_queries.py +3 -1
- weasyprint/css/properties.py +6 -2
- weasyprint/css/{utils.py → tokens.py} +306 -397
- weasyprint/css/units.py +91 -0
- weasyprint/css/validation/__init__.py +1 -1
- weasyprint/css/validation/descriptors.py +47 -19
- weasyprint/css/validation/expanders.py +7 -8
- weasyprint/css/validation/properties.py +341 -357
- weasyprint/document.py +20 -19
- weasyprint/draw/__init__.py +56 -63
- weasyprint/draw/border.py +121 -69
- weasyprint/draw/color.py +1 -1
- weasyprint/draw/text.py +60 -41
- weasyprint/formatting_structure/boxes.py +24 -5
- weasyprint/formatting_structure/build.py +33 -45
- weasyprint/images.py +76 -62
- weasyprint/layout/__init__.py +32 -26
- weasyprint/layout/absolute.py +7 -6
- weasyprint/layout/background.py +7 -7
- weasyprint/layout/block.py +195 -152
- weasyprint/layout/column.py +19 -24
- weasyprint/layout/flex.py +54 -26
- weasyprint/layout/float.py +12 -7
- weasyprint/layout/grid.py +284 -90
- weasyprint/layout/inline.py +121 -68
- weasyprint/layout/page.py +45 -12
- weasyprint/layout/percent.py +14 -10
- weasyprint/layout/preferred.py +105 -63
- weasyprint/layout/replaced.py +9 -6
- weasyprint/layout/table.py +16 -9
- weasyprint/pdf/__init__.py +58 -18
- weasyprint/pdf/anchors.py +3 -4
- weasyprint/pdf/fonts.py +126 -69
- weasyprint/pdf/metadata.py +36 -4
- weasyprint/pdf/pdfa.py +19 -3
- weasyprint/pdf/pdfua.py +7 -115
- weasyprint/pdf/pdfx.py +83 -0
- weasyprint/pdf/stream.py +57 -49
- weasyprint/pdf/tags.py +307 -0
- weasyprint/stacking.py +14 -15
- weasyprint/svg/__init__.py +59 -32
- weasyprint/svg/bounding_box.py +4 -2
- weasyprint/svg/defs.py +4 -9
- weasyprint/svg/images.py +11 -3
- weasyprint/svg/text.py +11 -2
- weasyprint/svg/utils.py +15 -8
- weasyprint/text/constants.py +1 -1
- weasyprint/text/ffi.py +4 -3
- weasyprint/text/fonts.py +13 -5
- weasyprint/text/line_break.py +146 -43
- weasyprint/urls.py +41 -13
- {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/METADATA +5 -6
- weasyprint-67.0.dist-info/RECORD +77 -0
- weasyprint/draw/stack.py +0 -13
- weasyprint-65.1.dist-info/RECORD +0 -74
- {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/WHEEL +0 -0
- {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/entry_points.txt +0 -0
- {weasyprint-65.1.dist-info → weasyprint-67.0.dist-info}/licenses/LICENSE +0 -0
weasyprint/layout/block.py
CHANGED
|
@@ -15,10 +15,10 @@ from .replaced import block_replaced_box_layout
|
|
|
15
15
|
from .table import table_layout, table_wrapper_width
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def block_level_layout(context, box, bottom_space, skip_stack,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
def block_level_layout(context, box, bottom_space, skip_stack, containing_block,
|
|
19
|
+
page_is_empty=True, absolute_boxes=None, fixed_boxes=None,
|
|
20
|
+
adjoining_margins=None, first_letter_style=None,
|
|
21
|
+
first_line_style=None, discard=False, max_lines=None):
|
|
22
22
|
"""Lay out the block-level ``box``."""
|
|
23
23
|
absolute_boxes = [] if absolute_boxes is None else absolute_boxes
|
|
24
24
|
fixed_boxes = [] if fixed_boxes is None else fixed_boxes
|
|
@@ -58,14 +58,14 @@ def block_level_layout(context, box, bottom_space, skip_stack,
|
|
|
58
58
|
|
|
59
59
|
return block_level_layout_switch(
|
|
60
60
|
context, box, bottom_space, skip_stack, containing_block,
|
|
61
|
-
page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins,
|
|
62
|
-
max_lines)
|
|
61
|
+
page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins,
|
|
62
|
+
first_letter_style, first_line_style, discard, max_lines)
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def block_level_layout_switch(context, box, bottom_space, skip_stack,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
max_lines):
|
|
65
|
+
def block_level_layout_switch(context, box, bottom_space, skip_stack, containing_block,
|
|
66
|
+
page_is_empty, absolute_boxes, fixed_boxes,
|
|
67
|
+
adjoining_margins, first_letter_style, first_line_style,
|
|
68
|
+
discard, max_lines):
|
|
69
69
|
"""Call the layout function corresponding to the ``box`` type."""
|
|
70
70
|
if isinstance(box, boxes.TableBox):
|
|
71
71
|
result = table_layout(
|
|
@@ -75,7 +75,7 @@ def block_level_layout_switch(context, box, bottom_space, skip_stack,
|
|
|
75
75
|
return block_box_layout(
|
|
76
76
|
context, box, bottom_space, skip_stack, containing_block,
|
|
77
77
|
page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins,
|
|
78
|
-
discard, max_lines)
|
|
78
|
+
first_letter_style, first_line_style, discard, max_lines)
|
|
79
79
|
elif isinstance(box, boxes.BlockReplacedBox):
|
|
80
80
|
result = block_replaced_box_layout(context, box, containing_block)
|
|
81
81
|
elif isinstance(box, boxes.FlexBox):
|
|
@@ -93,13 +93,15 @@ def block_level_layout_switch(context, box, bottom_space, skip_stack,
|
|
|
93
93
|
|
|
94
94
|
def block_box_layout(context, box, bottom_space, skip_stack,
|
|
95
95
|
containing_block, page_is_empty, absolute_boxes,
|
|
96
|
-
fixed_boxes, adjoining_margins,
|
|
96
|
+
fixed_boxes, adjoining_margins, first_letter_style,
|
|
97
|
+
first_line_style, discard, max_lines):
|
|
97
98
|
"""Lay out the block ``box``."""
|
|
98
99
|
if (box.style['column_width'] != 'auto' or
|
|
99
100
|
box.style['column_count'] != 'auto'):
|
|
100
101
|
result = columns_layout(
|
|
101
|
-
context, box, bottom_space, skip_stack, containing_block,
|
|
102
|
-
|
|
102
|
+
context, box, bottom_space, skip_stack, containing_block, page_is_empty,
|
|
103
|
+
absolute_boxes, fixed_boxes, adjoining_margins, first_letter_style,
|
|
104
|
+
first_line_style)
|
|
103
105
|
resume_at = result[1]
|
|
104
106
|
# TODO: this condition and the whole relayout are probably wrong
|
|
105
107
|
if resume_at is None:
|
|
@@ -112,9 +114,9 @@ def block_box_layout(context, box, bottom_space, skip_stack,
|
|
|
112
114
|
context, [new_box], absolute_boxes, fixed_boxes)
|
|
113
115
|
bottom_space += columns_bottom_space
|
|
114
116
|
result = columns_layout(
|
|
115
|
-
context, box, bottom_space, skip_stack,
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
context, box, bottom_space, skip_stack, containing_block,
|
|
118
|
+
page_is_empty, absolute_boxes, fixed_boxes, adjoining_margins,
|
|
119
|
+
first_letter_style, first_line_style)
|
|
118
120
|
return (*result, None)
|
|
119
121
|
elif box.is_table_wrapper:
|
|
120
122
|
table_wrapper_width(
|
|
@@ -122,8 +124,9 @@ def block_box_layout(context, box, bottom_space, skip_stack,
|
|
|
122
124
|
block_level_width(box, containing_block)
|
|
123
125
|
|
|
124
126
|
result = block_container_layout(
|
|
125
|
-
context, box, bottom_space, skip_stack, page_is_empty,
|
|
126
|
-
|
|
127
|
+
context, box, bottom_space, skip_stack, page_is_empty, absolute_boxes,
|
|
128
|
+
fixed_boxes, adjoining_margins, first_letter_style, first_line_style, discard,
|
|
129
|
+
max_lines)
|
|
127
130
|
# TODO: columns and flex items shouldn't be block boxes, this condition
|
|
128
131
|
# would then be useless when this is fixed.
|
|
129
132
|
if not (new_box := result[0]) or new_box.is_column or new_box.is_flex_item:
|
|
@@ -298,9 +301,13 @@ def _out_of_flow_layout(context, box, index, child, new_children,
|
|
|
298
301
|
return stop, resume_at, new_child, out_of_flow_resume_at
|
|
299
302
|
|
|
300
303
|
|
|
301
|
-
def _break_line(context, box, line, new_children,
|
|
302
|
-
|
|
303
|
-
|
|
304
|
+
def _break_line(context, box, line, new_children, next_lines, page_is_empty, index,
|
|
305
|
+
skip_stack, resume_at, absolute_boxes, fixed_boxes):
|
|
306
|
+
"""Break line where allowed by orphans and widows.
|
|
307
|
+
|
|
308
|
+
Return (abort, stop, resume_at).
|
|
309
|
+
|
|
310
|
+
"""
|
|
304
311
|
over_orphans = len(new_children) - box.style['orphans']
|
|
305
312
|
if over_orphans < 0 and not page_is_empty:
|
|
306
313
|
# Reached the bottom of the page before we had
|
|
@@ -309,12 +316,7 @@ def _break_line(context, box, line, new_children, lines_iterator,
|
|
|
309
316
|
return True, False, resume_at
|
|
310
317
|
# How many lines we need on the next page to satisfy widows
|
|
311
318
|
# -1 for the current line.
|
|
312
|
-
needed = box.style['widows'] - 1
|
|
313
|
-
if needed:
|
|
314
|
-
for _ in lines_iterator:
|
|
315
|
-
needed -= 1
|
|
316
|
-
if needed == 0:
|
|
317
|
-
break
|
|
319
|
+
needed = max(box.style['widows'] - 1 - next_lines, 0)
|
|
318
320
|
if needed > over_orphans and not page_is_empty:
|
|
319
321
|
# Total number of lines < orphans + widows
|
|
320
322
|
remove_placeholders(context, line.children, absolute_boxes, fixed_boxes)
|
|
@@ -333,7 +335,7 @@ def _break_line(context, box, line, new_children, lines_iterator,
|
|
|
333
335
|
def _linebox_layout(context, box, index, child, new_children, page_is_empty,
|
|
334
336
|
absolute_boxes, fixed_boxes, adjoining_margins,
|
|
335
337
|
bottom_space, position_y, skip_stack, first_letter_style,
|
|
336
|
-
draw_bottom_decoration, max_lines):
|
|
338
|
+
first_line_style, draw_bottom_decoration, max_lines):
|
|
337
339
|
abort = stop = False
|
|
338
340
|
resume_at = None
|
|
339
341
|
new_footnotes = []
|
|
@@ -344,8 +346,8 @@ def _linebox_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
344
346
|
position_y += collapse_margin(adjoining_margins)
|
|
345
347
|
new_containing_block = box
|
|
346
348
|
lines_iterator = iter_line_boxes(
|
|
347
|
-
context, child, position_y, bottom_space, skip_stack,
|
|
348
|
-
|
|
349
|
+
context, child, position_y, bottom_space, skip_stack, new_containing_block,
|
|
350
|
+
absolute_boxes, fixed_boxes, first_letter_style, first_line_style)
|
|
349
351
|
for i, (line, resume_at) in enumerate(lines_iterator):
|
|
350
352
|
# Break box if we reached max-lines
|
|
351
353
|
if max_lines is not None:
|
|
@@ -366,24 +368,52 @@ def _linebox_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
366
368
|
else:
|
|
367
369
|
offset_y = 0
|
|
368
370
|
|
|
369
|
-
# Allow overflow if the first line of the page is higher
|
|
370
|
-
#
|
|
371
|
-
# page and can advance in the context.
|
|
371
|
+
# Allow overflow if the first line of the page is higher than the page itself so
|
|
372
|
+
# that we put *something* on this page and can advance in the context.
|
|
372
373
|
overflow = (
|
|
373
374
|
(new_children or not page_is_empty) and
|
|
374
375
|
context.overflows_page(bottom_space, new_position_y + offset_y))
|
|
375
376
|
if overflow:
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
377
|
+
# If we couldn’t break the line before but can break now, first try to
|
|
378
|
+
# report footnotes and see if we don’t overflow.
|
|
379
|
+
could_break_before = can_break_now = True
|
|
380
|
+
next_lines = len(tuple(lines_iterator))
|
|
381
|
+
if len(new_children) + 1 < box.style['orphans']:
|
|
382
|
+
can_break_now = False
|
|
383
|
+
elif next_lines < box.style['widows']:
|
|
384
|
+
can_break_now = False
|
|
385
|
+
if len(new_children) < box.style['orphans']:
|
|
386
|
+
could_break_before = False
|
|
387
|
+
elif next_lines + 1 < box.style['widows']:
|
|
388
|
+
could_break_before = False
|
|
389
|
+
report = not context.in_column and can_break_now and not could_break_before
|
|
390
|
+
reported_footnotes = 0
|
|
391
|
+
while report and context.current_page_footnotes:
|
|
392
|
+
context.report_footnote(context.current_page_footnotes[-1])
|
|
393
|
+
reported_footnotes += 1
|
|
394
|
+
if not context.overflows_page(bottom_space, new_position_y + offset_y):
|
|
395
|
+
new_children.append(line)
|
|
396
|
+
stop = True
|
|
397
|
+
break
|
|
398
|
+
else:
|
|
399
|
+
abort, stop, resume_at = _break_line(
|
|
400
|
+
context, box, line, new_children, next_lines,
|
|
401
|
+
page_is_empty, index, skip_stack, resume_at, absolute_boxes,
|
|
402
|
+
fixed_boxes)
|
|
403
|
+
|
|
404
|
+
# Revert reported footnotes, as they’ve been reported starting from the last
|
|
405
|
+
# one.
|
|
406
|
+
if reported_footnotes >= 2:
|
|
407
|
+
extra = context.reported_footnotes[-1:-reported_footnotes-1:-1]
|
|
408
|
+
context.reported_footnotes[-reported_footnotes:] = extra
|
|
409
|
+
|
|
380
410
|
break
|
|
381
411
|
|
|
382
412
|
# TODO: this is incomplete.
|
|
383
413
|
# See https://drafts.csswg.org/css-page-3/#allowed-pg-brk
|
|
384
414
|
# "When an unforced page break occurs here, both the adjoining
|
|
385
415
|
# ‘margin-top’ and ‘margin-bottom’ are set to zero."
|
|
386
|
-
# See
|
|
416
|
+
# See issue #115.
|
|
387
417
|
elif page_is_empty and context.overflows_page(
|
|
388
418
|
bottom_space, new_position_y):
|
|
389
419
|
# Remove the top border when a page is empty and the box is
|
|
@@ -413,9 +443,10 @@ def _linebox_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
413
443
|
# even try.
|
|
414
444
|
if new_children or not page_is_empty:
|
|
415
445
|
if footnote.style['footnote_policy'] == 'line':
|
|
446
|
+
next_lines = len(tuple(lines_iterator))
|
|
416
447
|
abort, stop, resume_at = _break_line(
|
|
417
448
|
context, box, line, new_children,
|
|
418
|
-
|
|
449
|
+
next_lines, page_is_empty, index,
|
|
419
450
|
skip_stack, resume_at, absolute_boxes,
|
|
420
451
|
fixed_boxes)
|
|
421
452
|
break_linebox = True
|
|
@@ -437,15 +468,14 @@ def _linebox_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
437
468
|
|
|
438
469
|
|
|
439
470
|
def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
440
|
-
absolute_boxes, fixed_boxes, adjoining_margins,
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
next_page, max_lines):
|
|
471
|
+
absolute_boxes, fixed_boxes, adjoining_margins, bottom_space,
|
|
472
|
+
position_y, skip_stack, first_letter_style, first_line_style,
|
|
473
|
+
discard, next_page, max_lines):
|
|
444
474
|
abort = stop = False
|
|
445
475
|
|
|
476
|
+
# Find possible page break between in-flow siblings.
|
|
446
477
|
last_in_flow_child = find_last_in_flow_child(new_children)
|
|
447
478
|
if last_in_flow_child is not None:
|
|
448
|
-
# Between in-flow siblings
|
|
449
479
|
page_break = block_level_page_break(last_in_flow_child, child)
|
|
450
480
|
page_name = block_level_page_name(last_in_flow_child, child)
|
|
451
481
|
if page_name or force_page_break(page_break, context):
|
|
@@ -459,16 +489,15 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
459
489
|
else:
|
|
460
490
|
page_break = 'auto'
|
|
461
491
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if last_in_flow_child is None and collapsing_with_children:
|
|
492
|
+
# Resolve percentages and collapsing top margins.
|
|
493
|
+
if not box.is_table_wrapper:
|
|
494
|
+
resolve_percentages(child, box)
|
|
495
|
+
if last_in_flow_child is None and box.top_margin_collapses():
|
|
467
496
|
# TODO: add the adjoining descendants' margin top to
|
|
468
|
-
# [child.margin_top]
|
|
497
|
+
# [child.margin_top].
|
|
469
498
|
old_collapsed_margin = collapse_margin(adjoining_margins)
|
|
470
499
|
# TODO: the margin-top value is set afterwards in
|
|
471
|
-
# block_level_layout, we shouldn’t duplicate this code
|
|
500
|
+
# block_level_layout, we shouldn’t duplicate this code.
|
|
472
501
|
child_margin_top = child.margin_top
|
|
473
502
|
if child_margin_top == 'auto':
|
|
474
503
|
child_margin_top = 0
|
|
@@ -502,81 +531,76 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
502
531
|
if box.is_table_wrapper: # should not be a special case
|
|
503
532
|
collapsed_margin = collapse_margin(adjoining_margins)
|
|
504
533
|
child.position_y += collapsed_margin
|
|
505
|
-
position_y += collapsed_margin
|
|
506
534
|
adjoining_margins = []
|
|
507
535
|
elif not isinstance(child, boxes.BlockBox): # blocks handle that themselves
|
|
508
536
|
if child.style['margin_top'] == 'auto':
|
|
509
537
|
margin_top = 0
|
|
510
538
|
else:
|
|
511
|
-
margin_top = percentage(
|
|
539
|
+
margin_top = percentage(
|
|
540
|
+
child.style['margin_top'], child.style, box.width)
|
|
512
541
|
adjoining_margins.append(margin_top)
|
|
513
542
|
offset_y = collapse_margin(adjoining_margins) - margin_top
|
|
514
543
|
child.position_y += offset_y
|
|
515
|
-
position_y += offset_y
|
|
516
544
|
adjoining_margins = []
|
|
517
545
|
|
|
518
546
|
page_is_empty_with_no_children = page_is_empty and not any(
|
|
519
547
|
child for child in new_children
|
|
520
548
|
if not isinstance(child, AbsolutePlaceholder))
|
|
521
549
|
|
|
522
|
-
if not getattr(child, 'first_letter_style', None):
|
|
523
|
-
child.first_letter_style = first_letter_style
|
|
524
550
|
(new_child, resume_at, next_page, next_adjoining_margins,
|
|
525
551
|
collapsing_through, max_lines) = block_level_layout(
|
|
526
|
-
context, child, bottom_space, skip_stack,
|
|
527
|
-
|
|
528
|
-
|
|
552
|
+
context, child, bottom_space, skip_stack, box, page_is_empty_with_no_children,
|
|
553
|
+
absolute_boxes, fixed_boxes, adjoining_margins, first_letter_style,
|
|
554
|
+
first_line_style, discard, max_lines)
|
|
529
555
|
|
|
556
|
+
# Check that child doesn’t overflow and set next position_y.
|
|
530
557
|
if new_child is not None:
|
|
531
558
|
if not collapsing_through:
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
new_position_y = (
|
|
535
|
-
new_child.border_box_y() + new_child.border_height())
|
|
559
|
+
# Find content position and check that it doesn’t overflow.
|
|
560
|
+
new_content_position_y = new_child.content_box_y() + new_child.height
|
|
536
561
|
content_page_overflow = context.overflows_page(
|
|
537
562
|
bottom_space, new_content_position_y)
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
563
|
+
|
|
564
|
+
# Update bottom space to include new child bottom spacing and check that it
|
|
565
|
+
# doesn’t overflow.
|
|
566
|
+
bottom_space += new_child.padding_bottom + new_child.border_bottom_width
|
|
567
|
+
if not box.bottom_margin_collapses():
|
|
568
|
+
bottom_space += new_child.margin_bottom
|
|
569
|
+
new_position_y = new_child.border_box_y() + new_child.border_height()
|
|
570
|
+
border_page_overflow = context.overflows_page(bottom_space, new_position_y)
|
|
571
|
+
|
|
572
|
+
can_break = not (page_is_empty_with_no_children or box.is_monolithic())
|
|
542
573
|
if can_break and content_page_overflow:
|
|
543
|
-
#
|
|
544
|
-
|
|
545
|
-
remove_placeholders(
|
|
546
|
-
context, [new_child], absolute_boxes, fixed_boxes)
|
|
574
|
+
# Child content overflows the page area, display it on the next page.
|
|
575
|
+
remove_placeholders(context, [new_child], absolute_boxes, fixed_boxes)
|
|
547
576
|
new_child = None
|
|
548
577
|
elif can_break and border_page_overflow:
|
|
549
|
-
#
|
|
550
|
-
#
|
|
551
|
-
remove_placeholders(
|
|
552
|
-
context, [new_child], absolute_boxes, fixed_boxes)
|
|
553
|
-
bottom_space += (
|
|
554
|
-
new_child.padding_bottom + new_child.border_bottom_width)
|
|
578
|
+
# Child border/padding/margin overflows the page area, do the layout
|
|
579
|
+
# again with a bottom_space value that includes them.
|
|
580
|
+
remove_placeholders(context, [new_child], absolute_boxes, fixed_boxes)
|
|
555
581
|
(new_child, resume_at, next_page, next_adjoining_margins,
|
|
556
582
|
collapsing_through, max_lines) = block_level_layout(
|
|
557
|
-
context, child, bottom_space, skip_stack,
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
max_lines)
|
|
583
|
+
context, child, bottom_space, skip_stack, box,
|
|
584
|
+
page_is_empty_with_no_children, absolute_boxes, fixed_boxes,
|
|
585
|
+
adjoining_margins, discard, max_lines)
|
|
561
586
|
if new_child:
|
|
562
|
-
position_y = (
|
|
563
|
-
new_child.border_box_y() + new_child.border_height())
|
|
587
|
+
position_y = new_child.border_box_y() + new_child.border_height()
|
|
564
588
|
else:
|
|
565
589
|
position_y = new_position_y
|
|
566
590
|
|
|
591
|
+
# Use the new child adjoining margins.
|
|
567
592
|
adjoining_margins = next_adjoining_margins
|
|
568
593
|
if new_child:
|
|
569
594
|
adjoining_margins.append(new_child.margin_bottom)
|
|
570
595
|
|
|
596
|
+
# Handle clearance.
|
|
571
597
|
if new_child and new_child.clearance:
|
|
572
598
|
position_y = new_child.border_box_y() + new_child.border_height()
|
|
573
599
|
|
|
574
|
-
skip_stack = None
|
|
575
|
-
|
|
576
600
|
if new_child is None:
|
|
577
|
-
# Nothing fits in the remaining space of this page: break
|
|
601
|
+
# Nothing fits in the remaining space of this page: break.
|
|
578
602
|
if avoid_page_break(page_break, context):
|
|
579
|
-
# TODO: fill the blank space at the bottom of the page
|
|
603
|
+
# TODO: fill the blank space at the bottom of the page.
|
|
580
604
|
result = find_earlier_page_break(
|
|
581
605
|
context, new_children, absolute_boxes, fixed_boxes)
|
|
582
606
|
if result:
|
|
@@ -586,11 +610,10 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
586
610
|
abort, stop, resume_at, position_y, adjoining_margins,
|
|
587
611
|
next_page, new_children, max_lines)
|
|
588
612
|
else:
|
|
589
|
-
# We did not find any page break opportunity
|
|
613
|
+
# We did not find any page break opportunity.
|
|
590
614
|
if not page_is_empty:
|
|
591
|
-
# The page has content *before* this block:
|
|
592
|
-
#
|
|
593
|
-
# in the parent.
|
|
615
|
+
# The page has content *before* this block: cancel the block and try
|
|
616
|
+
# to find a break in the parent.
|
|
594
617
|
abort = True
|
|
595
618
|
return (
|
|
596
619
|
abort, stop, resume_at, position_y, adjoining_margins,
|
|
@@ -599,26 +622,26 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
599
622
|
# ignore this 'avoid' and break anyway.
|
|
600
623
|
|
|
601
624
|
if all(child.is_absolutely_positioned() for child in new_children):
|
|
602
|
-
# This box has only rendered absolute children, keep them
|
|
603
|
-
#
|
|
604
|
-
|
|
605
|
-
remove_placeholders(
|
|
606
|
-
context, new_children, absolute_boxes, fixed_boxes)
|
|
625
|
+
# This box has only rendered absolute children, keep them for the next page.
|
|
626
|
+
# This is for example useful for list markers.
|
|
627
|
+
remove_placeholders(context, new_children, absolute_boxes, fixed_boxes)
|
|
607
628
|
new_children = []
|
|
608
629
|
|
|
609
630
|
if new_children:
|
|
631
|
+
# We already have children, keep them and stop the box rendering.
|
|
610
632
|
resume_at = {index: None}
|
|
611
633
|
stop = True
|
|
612
634
|
else:
|
|
613
|
-
# This was the first child of this box, cancel the box completly
|
|
635
|
+
# This was the first child of this box, cancel the box completly.
|
|
614
636
|
abort = True
|
|
615
637
|
return (
|
|
616
638
|
abort, stop, resume_at, position_y, adjoining_margins, next_page,
|
|
617
639
|
new_children, max_lines)
|
|
618
640
|
|
|
619
|
-
#
|
|
620
|
-
# May be used in find_earlier_page_break()
|
|
641
|
+
# Index in its non-laid-out parent, not in future new parent.
|
|
642
|
+
# May be used in find_earlier_page_break().
|
|
621
643
|
new_child.index = index
|
|
644
|
+
|
|
622
645
|
new_children.append(new_child)
|
|
623
646
|
if resume_at is not None:
|
|
624
647
|
resume_at = {index: resume_at}
|
|
@@ -629,40 +652,34 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
|
|
|
629
652
|
new_children, max_lines)
|
|
630
653
|
|
|
631
654
|
|
|
632
|
-
def block_container_layout(context, box, bottom_space, skip_stack,
|
|
633
|
-
|
|
634
|
-
|
|
655
|
+
def block_container_layout(context, box, bottom_space, skip_stack, page_is_empty,
|
|
656
|
+
absolute_boxes, fixed_boxes, adjoining_margins,
|
|
657
|
+
first_letter_style, first_line_style, discard, max_lines):
|
|
635
658
|
"""Set the ``box`` height."""
|
|
636
659
|
assert isinstance(box, boxes.BlockContainerBox)
|
|
637
660
|
|
|
638
661
|
if box.establishes_formatting_context():
|
|
639
|
-
context.create_block_formatting_context()
|
|
662
|
+
context.create_block_formatting_context(box)
|
|
640
663
|
|
|
641
664
|
# TODO: merge this with _in_flow_layout, flex_layout…
|
|
642
665
|
is_start = skip_stack is None
|
|
643
666
|
box.remove_decoration(start=not is_start, end=False)
|
|
644
667
|
|
|
645
668
|
discard |= box.style['continue'] == 'discard'
|
|
646
|
-
draw_bottom_decoration =
|
|
647
|
-
discard or box.style['box_decoration_break'] == 'clone')
|
|
669
|
+
draw_bottom_decoration = discard or box.style['box_decoration_break'] == 'clone'
|
|
648
670
|
|
|
649
671
|
if adjoining_margins is None:
|
|
650
672
|
adjoining_margins = []
|
|
651
673
|
|
|
652
674
|
if draw_bottom_decoration:
|
|
653
|
-
bottom_space +=
|
|
654
|
-
box.padding_bottom + box.border_bottom_width + box.margin_bottom)
|
|
675
|
+
bottom_space += box.padding_bottom + box.border_bottom_width + box.margin_bottom
|
|
655
676
|
|
|
656
677
|
adjoining_margins.append(box.margin_top)
|
|
657
678
|
this_box_adjoining_margins = adjoining_margins
|
|
658
679
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
box.is_for_root_element)
|
|
663
|
-
if collapsing_with_children:
|
|
664
|
-
# Not counting margins in adjoining_margins, if any
|
|
665
|
-
# (there are not padding or borders, see above)
|
|
680
|
+
if box.top_margin_collapses():
|
|
681
|
+
# Not counting margins in adjoining_margins, if any (there are not padding or
|
|
682
|
+
# borders, see above).
|
|
666
683
|
position_y = box.position_y
|
|
667
684
|
else:
|
|
668
685
|
box.position_y += collapse_margin(adjoining_margins) - box.margin_top
|
|
@@ -672,92 +689,118 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
672
689
|
position_x = box.content_box_x()
|
|
673
690
|
|
|
674
691
|
if box.style['position'] == 'relative':
|
|
675
|
-
# New containing block, use a new absolute list
|
|
692
|
+
# New containing block, use a new absolute list.
|
|
676
693
|
absolute_boxes = []
|
|
677
694
|
|
|
678
695
|
new_children = []
|
|
679
696
|
next_page = {'break': 'any', 'page': None}
|
|
680
|
-
all_footnotes = []
|
|
681
|
-
broken_out_of_flow = {}
|
|
682
|
-
|
|
683
|
-
last_in_flow_child = None
|
|
684
697
|
|
|
685
698
|
if box.style['max_lines'] != 'none':
|
|
686
699
|
max_lines = min(box.style['max_lines'], max_lines or inf)
|
|
687
700
|
|
|
701
|
+
if box.first_line_style is not None:
|
|
702
|
+
box_first_line_style = {
|
|
703
|
+
key: box.first_line_style[key] for key in box.first_line_style.cascaded}
|
|
704
|
+
if first_line_style is None:
|
|
705
|
+
first_line_style = box_first_line_style
|
|
706
|
+
else:
|
|
707
|
+
first_line_style |= box_first_line_style
|
|
708
|
+
|
|
709
|
+
if box.first_letter_style is not None:
|
|
710
|
+
box_first_letter_style = {
|
|
711
|
+
key: box.first_letter_style[key] for key in box.first_letter_style.cascaded}
|
|
712
|
+
if first_letter_style is None:
|
|
713
|
+
first_letter_style = box_first_letter_style
|
|
714
|
+
else:
|
|
715
|
+
first_letter_style |= box_first_letter_style
|
|
716
|
+
|
|
717
|
+
# Layout box children.
|
|
688
718
|
if is_start:
|
|
689
719
|
skip = 0
|
|
690
|
-
first_letter_style = getattr(box, 'first_letter_style', None)
|
|
691
720
|
else:
|
|
692
721
|
(skip, skip_stack), = skip_stack.items()
|
|
693
|
-
first_letter_style = None
|
|
694
722
|
for index, child in enumerate(box.children[skip:], start=(skip or 0)):
|
|
695
723
|
child.position_x = position_x
|
|
696
724
|
child.position_y = position_y # doesn’t count adjoining_margins
|
|
697
725
|
new_footnotes = []
|
|
698
726
|
|
|
699
727
|
if not child.is_in_normal_flow():
|
|
728
|
+
# Layout out-of-flow child.
|
|
700
729
|
abort = False
|
|
701
730
|
stop, resume_at, new_child, out_of_flow_resume_at = (
|
|
702
731
|
_out_of_flow_layout(
|
|
703
732
|
context, box, index, child, new_children, page_is_empty,
|
|
704
|
-
absolute_boxes, fixed_boxes, adjoining_margins,
|
|
705
|
-
bottom_space))
|
|
733
|
+
absolute_boxes, fixed_boxes, adjoining_margins, bottom_space))
|
|
706
734
|
if out_of_flow_resume_at:
|
|
707
|
-
|
|
708
|
-
child, box, out_of_flow_resume_at)
|
|
735
|
+
context.add_broken_out_of_flow(
|
|
736
|
+
new_child, child, box, out_of_flow_resume_at)
|
|
737
|
+
if child.is_outside_marker:
|
|
738
|
+
new_child.position_x = box.border_box_x()
|
|
739
|
+
if child.style['direction'] == 'rtl':
|
|
740
|
+
new_child.position_x += box.width + box.padding_right
|
|
709
741
|
|
|
710
742
|
elif isinstance(child, boxes.LineBox):
|
|
743
|
+
# Layout line child.
|
|
711
744
|
(abort, stop, resume_at, position_y,
|
|
712
745
|
new_footnotes, max_lines) = _linebox_layout(
|
|
713
746
|
context, box, index, child, new_children, page_is_empty,
|
|
714
747
|
absolute_boxes, fixed_boxes, adjoining_margins, bottom_space,
|
|
715
|
-
position_y, skip_stack, first_letter_style,
|
|
748
|
+
position_y, skip_stack, first_letter_style, first_line_style,
|
|
716
749
|
draw_bottom_decoration, max_lines)
|
|
717
750
|
draw_bottom_decoration |= resume_at is None
|
|
718
751
|
adjoining_margins = []
|
|
719
|
-
|
|
752
|
+
first_letter_style = first_line_style = None
|
|
720
753
|
|
|
721
754
|
else:
|
|
755
|
+
# Layout in-flow child.
|
|
722
756
|
(abort, stop, resume_at, position_y, adjoining_margins,
|
|
723
757
|
next_page, new_children, new_max_lines) = _in_flow_layout(
|
|
724
758
|
context, box, index, child, new_children, page_is_empty,
|
|
725
759
|
absolute_boxes, fixed_boxes, adjoining_margins, bottom_space,
|
|
726
|
-
position_y, skip_stack, first_letter_style,
|
|
727
|
-
|
|
728
|
-
next_page, max_lines)
|
|
760
|
+
position_y, skip_stack, first_letter_style, first_line_style,
|
|
761
|
+
discard, next_page, max_lines)
|
|
729
762
|
skip_stack = None
|
|
763
|
+
first_letter_style = first_line_style = None
|
|
730
764
|
|
|
765
|
+
# Handle max-lines.
|
|
731
766
|
if None not in (new_max_lines, max_lines):
|
|
732
767
|
max_lines = new_max_lines
|
|
733
768
|
if max_lines <= 0:
|
|
769
|
+
# Maximum number of lines reached, stop box rendering.
|
|
734
770
|
stop = True
|
|
735
771
|
last_child = (child == box.children[-1])
|
|
736
772
|
if not last_child:
|
|
773
|
+
# This is not the last line, recursively find the last line to
|
|
774
|
+
# set ellipsis on it.
|
|
737
775
|
children = new_children
|
|
738
776
|
while children:
|
|
739
777
|
last_child = children[-1]
|
|
740
778
|
if isinstance(last_child, boxes.LineBox):
|
|
741
|
-
last_child.block_ellipsis =
|
|
742
|
-
box.style['block_ellipsis'])
|
|
779
|
+
last_child.block_ellipsis = box.style['block_ellipsis']
|
|
743
780
|
elif isinstance(last_child, boxes.ParentBox):
|
|
744
781
|
children = last_child.children
|
|
745
782
|
continue
|
|
746
783
|
break
|
|
747
784
|
|
|
748
785
|
if abort:
|
|
786
|
+
# Abort the rendering of box.
|
|
749
787
|
page = child.page_values()[0]
|
|
750
788
|
remove_placeholders(
|
|
751
789
|
context, box.children[skip:], absolute_boxes, fixed_boxes)
|
|
752
790
|
for footnote in new_footnotes:
|
|
753
791
|
context.unlayout_footnote(footnote)
|
|
792
|
+
if box.establishes_formatting_context():
|
|
793
|
+
context.finish_block_formatting_context()
|
|
754
794
|
return (
|
|
755
795
|
None, None, {'break': 'any', 'page': page}, [], False,
|
|
756
796
|
max_lines)
|
|
757
797
|
elif stop:
|
|
798
|
+
# Stop after the rendering of child.
|
|
758
799
|
if box.height != 'auto':
|
|
759
|
-
|
|
760
|
-
|
|
800
|
+
box_bottom = box.position_y + box.border_height()
|
|
801
|
+
bottom_margin = collapse_margin(adjoining_margins)
|
|
802
|
+
if context.overflows(box_bottom, position_y - bottom_margin):
|
|
803
|
+
# Box height is fixed and it overflows the page, forget
|
|
761
804
|
# overflowing children.
|
|
762
805
|
resume_at = None
|
|
763
806
|
adjoining_margins = []
|
|
@@ -766,7 +809,7 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
766
809
|
else:
|
|
767
810
|
resume_at = None
|
|
768
811
|
|
|
769
|
-
box_is_fragmented = resume_at is not None
|
|
812
|
+
box_is_fragmented = resume_at is not None or box.force_fragmentation
|
|
770
813
|
if box.style['continue'] == 'discard':
|
|
771
814
|
resume_at = None
|
|
772
815
|
|
|
@@ -775,40 +818,37 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
775
818
|
not page_is_empty):
|
|
776
819
|
remove_placeholders(
|
|
777
820
|
context, [*new_children, *box.children[skip:]], absolute_boxes, fixed_boxes)
|
|
821
|
+
if box.establishes_formatting_context():
|
|
822
|
+
context.finish_block_formatting_context()
|
|
778
823
|
return None, None, {'break': 'any', 'page': None}, [], False, max_lines
|
|
779
824
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
if collapsing_with_children:
|
|
784
|
-
box.position_y += (
|
|
785
|
-
collapse_margin(this_box_adjoining_margins) - box.margin_top)
|
|
825
|
+
if box.top_margin_collapses():
|
|
826
|
+
box.position_y += collapse_margin(this_box_adjoining_margins) - box.margin_top
|
|
786
827
|
|
|
828
|
+
# Detect collapsing-through situation and collapse margins accordingly.
|
|
787
829
|
last_in_flow_child = find_last_in_flow_child(new_children)
|
|
788
830
|
collapsing_through = False
|
|
789
831
|
if last_in_flow_child is None:
|
|
832
|
+
# No in-flow child in box, collapse its top and bottom margins.
|
|
790
833
|
collapsed_margin = collapse_margin(adjoining_margins)
|
|
791
|
-
# Top and bottom margins of this box
|
|
792
834
|
if (box.height in ('auto', 0) and
|
|
793
835
|
get_clearance(context, box, collapsed_margin) is None and
|
|
794
836
|
all(value == 0 for value in (
|
|
795
837
|
box.min_height, box.border_top_width, box.padding_top,
|
|
796
838
|
box.border_bottom_width, box.padding_bottom))):
|
|
839
|
+
# Collapse through the box.
|
|
797
840
|
collapsing_through = True
|
|
798
841
|
else:
|
|
842
|
+
# Don’t collapse through the box, update position and reset margins.
|
|
799
843
|
position_y += collapsed_margin
|
|
800
844
|
adjoining_margins = []
|
|
801
845
|
else:
|
|
802
|
-
#
|
|
846
|
+
# In-flow children, collapse last child bottom margin and box bottom margin.
|
|
803
847
|
if box.height != 'auto':
|
|
804
|
-
# Not adjoining
|
|
848
|
+
# Not adjoining, reset margins.
|
|
805
849
|
adjoining_margins = []
|
|
806
850
|
|
|
807
|
-
if
|
|
808
|
-
box.padding_bottom or
|
|
809
|
-
box.establishes_formatting_context() or
|
|
810
|
-
box.is_for_root_element or
|
|
811
|
-
box.is_table_wrapper):
|
|
851
|
+
if not box.bottom_margin_collapses():
|
|
812
852
|
position_y += collapse_margin(adjoining_margins)
|
|
813
853
|
adjoining_margins = []
|
|
814
854
|
|
|
@@ -831,7 +871,10 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
831
871
|
float_box.position_y + float_box.margin_height()
|
|
832
872
|
for float_box in context.excluded_shapes)
|
|
833
873
|
position_y = max(max_float_position_y, position_y)
|
|
834
|
-
|
|
874
|
+
if position_y == new_box.content_box_y() == inf:
|
|
875
|
+
new_box.height = 0
|
|
876
|
+
else:
|
|
877
|
+
new_box.height = position_y - new_box.content_box_y()
|
|
835
878
|
|
|
836
879
|
if new_box.style['position'] == 'relative':
|
|
837
880
|
# New containing block, resolve the layout of the absolute descendants
|
|
@@ -843,7 +886,7 @@ def block_container_layout(context, box, bottom_space, skip_stack,
|
|
|
843
886
|
for child in new_box.children:
|
|
844
887
|
relative_positioning(child, (new_box.width, new_box.height))
|
|
845
888
|
|
|
846
|
-
if
|
|
889
|
+
if box.establishes_formatting_context():
|
|
847
890
|
context.finish_block_formatting_context(new_box)
|
|
848
891
|
|
|
849
892
|
if discard or not box_is_fragmented:
|