Skip to content

Commit c2ca83f

Browse files
committed
Save correct context boxes for broken out-of-flow boxes
Fix #2571.
1 parent c77f7da commit c2ca83f

File tree

9 files changed

+102
-41
lines changed

9 files changed

+102
-41
lines changed

tests/draw/test_float.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,3 +957,49 @@ def test_float_split_large_parent(assert_pixels):
957957
<section>
958958
<div>bb bb bb bb bb</div>
959959
<article>aa</article>''')
960+
961+
962+
@assert_no_logs
963+
def test_float_split_in_stacking_context(assert_pixels):
964+
assert_pixels('''
965+
RRRR
966+
RRRR
967+
BBBB
968+
BBBB
969+
____
970+
971+
BBBB
972+
BBBB
973+
RRRR
974+
RRRR
975+
____
976+
977+
RRRR
978+
RRRR
979+
____
980+
____
981+
____
982+
''', '''
983+
<style>
984+
@page {
985+
size: 4px 5px;
986+
}
987+
body {
988+
color: red;
989+
font: 2px / 1 weasyprint;
990+
}
991+
section {
992+
display: flow-root;
993+
}
994+
div {
995+
color: blue;
996+
float: left;
997+
}
998+
article {
999+
clear: left;
1000+
}
1001+
</style>
1002+
<section>
1003+
aa
1004+
<div>bb bb</div>
1005+
cc cc''')

weasyprint/layout/__init__.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,13 @@ def __init__(self, style_for, get_image_from_uri, font_config,
229229
self.font_config = font_config
230230
self.counter_style = counter_style
231231
self.target_collector = target_collector
232-
self._excluded_shapes_lists = []
232+
self._excluded_shapes_root_boxes = []
233+
self._excluded_shapes = {}
233234
self.footnotes = []
234235
self.page_footnotes = {}
235236
self.current_page_footnotes = []
236237
self.reported_footnotes = []
237238
self.current_footnote_area = None # Not initialized yet
238-
self.excluded_shapes = None # Not initialized yet
239239
self.page_bottom = None
240240
self.string_set = defaultdict(lambda: defaultdict(list))
241241
self.running_elements = defaultdict(lambda: defaultdict(list))
@@ -257,34 +257,39 @@ def overflows(bottom, position_y):
257257
# The 1e-9 value comes from PEP 485.
258258
return position_y > bottom * (1 + 1e-9)
259259

260-
def create_block_formatting_context(self):
261-
self.excluded_shapes = []
262-
self._excluded_shapes_lists.append(self.excluded_shapes)
260+
@property
261+
def excluded_shapes(self):
262+
return self._excluded_shapes[self._excluded_shapes_root_boxes[-1]]
263263

264-
def finish_block_formatting_context(self, root_box):
264+
@excluded_shapes.setter
265+
def excluded_shapes(self, excluded_shapes):
266+
self._excluded_shapes[self._excluded_shapes_root_boxes[-1]] = excluded_shapes
267+
268+
def create_block_formatting_context(self, root_box, new_list=None):
269+
assert root_box not in self._excluded_shapes_root_boxes
270+
self._excluded_shapes_root_boxes.append(root_box)
271+
if root_box not in self._excluded_shapes:
272+
self._excluded_shapes[root_box] = [] if new_list is None else new_list
273+
274+
def finish_block_formatting_context(self, root_box=None):
265275
# See https://www.w3.org/TR/CSS2/visudet.html#root-height
266-
if root_box.style['height'] == 'auto' and self.excluded_shapes:
276+
if root_box and root_box.style['height'] == 'auto' and self.excluded_shapes:
267277
box_bottom = root_box.content_box_y() + root_box.height
268278
max_shape_bottom = max([
269279
shape.position_y + shape.margin_height()
270280
for shape in self.excluded_shapes] + [box_bottom])
271281
root_box.height += max_shape_bottom - box_bottom
272-
self._excluded_shapes_lists.pop()
273-
if self._excluded_shapes_lists:
274-
self.excluded_shapes = self._excluded_shapes_lists[-1]
275-
else:
276-
self.excluded_shapes = None
282+
self._excluded_shapes.pop(self._excluded_shapes_root_boxes.pop())
277283

278-
def create_flex_formatting_context(self):
279-
self.excluded_shapes = FakeList()
280-
self._excluded_shapes_lists.append(self.excluded_shapes)
284+
def create_flex_formatting_context(self, root_box):
285+
self.create_block_formatting_context(root_box, FakeList())
281286

282287
def finish_flex_formatting_context(self, root_box):
283-
self._excluded_shapes_lists.pop()
284-
if self._excluded_shapes_lists:
285-
self.excluded_shapes = self._excluded_shapes_lists[-1]
286-
else:
287-
self.excluded_shapes = None
288+
self.finish_block_formatting_context(root_box)
289+
290+
def add_broken_out_of_flow(self, new_box, box, containing_block, resume_at):
291+
self.broken_out_of_flow[new_box] = (
292+
box, containing_block, self._excluded_shapes_root_boxes[-1], resume_at)
288293

289294
def get_string_set_for(self, page, name, keyword='first'):
290295
"""Resolve value of string function."""

weasyprint/layout/absolute.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,7 @@ def absolute_layout(context, placeholder, containing_block, fixed_boxes,
237237
context, box, containing_block, fixed_boxes, bottom_space, skip_stack)
238238
placeholder.set_laid_out_box(new_box)
239239
if resume_at:
240-
context.broken_out_of_flow[placeholder] = (
241-
box, containing_block, resume_at)
240+
context.add_broken_out_of_flow(placeholder, box, containing_block, resume_at)
242241

243242

244243
def absolute_box_layout(context, box, containing_block, fixed_boxes,
@@ -259,7 +258,6 @@ def absolute_box_layout(context, box, containing_block, fixed_boxes,
259258
resolve_percentages(box, (cb_width, cb_height))
260259
resolve_position_percentages(box, (cb_width, cb_height))
261260

262-
context.create_block_formatting_context()
263261
if isinstance(box, boxes.BlockReplacedBox):
264262
new_box = absolute_replaced(
265263
context, box, cb_x, cb_y, cb_width, cb_height)
@@ -269,7 +267,6 @@ def absolute_box_layout(context, box, containing_block, fixed_boxes,
269267
new_box, resume_at = absolute_block(
270268
context, box, containing_block, fixed_boxes, bottom_space,
271269
skip_stack, cb_x, cb_y, cb_width, cb_height)
272-
context.finish_block_formatting_context(new_box)
273270

274271
return new_box, resume_at
275272

weasyprint/layout/block.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ def block_container_layout(context, box, bottom_space, skip_stack,
662662
assert isinstance(box, boxes.BlockContainerBox)
663663

664664
if box.establishes_formatting_context():
665-
context.create_block_formatting_context()
665+
context.create_block_formatting_context(box)
666666

667667
# TODO: merge this with _in_flow_layout, flex_layout…
668668
is_start = skip_stack is None
@@ -728,8 +728,8 @@ def block_container_layout(context, box, bottom_space, skip_stack,
728728
absolute_boxes, fixed_boxes, adjoining_margins,
729729
bottom_space))
730730
if out_of_flow_resume_at:
731-
context.broken_out_of_flow[new_child] = (
732-
child, box, out_of_flow_resume_at)
731+
context.add_broken_out_of_flow(
732+
new_child, child, box, out_of_flow_resume_at)
733733
if child.is_outside_marker:
734734
new_child.position_x = box.border_box_x()
735735
if child.style['direction'] == 'rtl':
@@ -778,6 +778,8 @@ def block_container_layout(context, box, bottom_space, skip_stack,
778778
context, box.children[skip:], absolute_boxes, fixed_boxes)
779779
for footnote in new_footnotes:
780780
context.unlayout_footnote(footnote)
781+
if box.establishes_formatting_context():
782+
context.finish_block_formatting_context()
781783
return (
782784
None, None, {'break': 'any', 'page': page}, [], False,
783785
max_lines)
@@ -802,6 +804,8 @@ def block_container_layout(context, box, bottom_space, skip_stack,
802804
not page_is_empty):
803805
remove_placeholders(
804806
context, [*new_children, *box.children[skip:]], absolute_boxes, fixed_boxes)
807+
if box.establishes_formatting_context():
808+
context.finish_block_formatting_context()
805809
return None, None, {'break': 'any', 'page': None}, [], False, max_lines
806810

807811
if collapsing_with_children:
@@ -870,7 +874,7 @@ def block_container_layout(context, box, bottom_space, skip_stack,
870874
for child in new_box.children:
871875
relative_positioning(child, (new_box.width, new_box.height))
872876

873-
if new_box.establishes_formatting_context():
877+
if box.establishes_formatting_context():
874878
context.finish_block_formatting_context(new_box)
875879

876880
if discard or not box_is_fragmented:

weasyprint/layout/flex.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def flex_layout(context, box, bottom_space, skip_stack, containing_block, page_i
2020
from . import block
2121

2222
# TODO: merge this with block_container_layout.
23-
context.create_flex_formatting_context()
23+
context.create_flex_formatting_context(box)
2424
resume_at = None
2525

2626
is_start = skip_stack is None

weasyprint/layout/float.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,11 @@ def float_layout(context, box, containing_block, absolute_boxes, fixed_boxes,
5858
table_wrapper_width(context, box, (cb_width, cb_height))
5959

6060
if isinstance(box, boxes.BlockContainerBox):
61-
context.create_block_formatting_context()
6261
box, resume_at, _, _, _, _ = block_container_layout(
6362
context, box, bottom_space=bottom_space,
6463
skip_stack=skip_stack, page_is_empty=True,
6564
absolute_boxes=absolute_boxes, fixed_boxes=fixed_boxes,
6665
adjoining_margins=None, discard=False, max_lines=None)
67-
context.finish_block_formatting_context(box)
6866
elif isinstance(box, boxes.FlexContainerBox):
6967
box, resume_at, _, _, _ = flex_layout(
7068
context, box, bottom_space=bottom_space,

weasyprint/layout/grid.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ def _resolve_tracks_sizes(sizing_functions, box_size, children_positions,
577577

578578
def grid_layout(context, box, bottom_space, skip_stack, containing_block,
579579
page_is_empty, absolute_boxes, fixed_boxes):
580-
context.create_block_formatting_context()
580+
context.create_block_formatting_context(box)
581581

582582
# Define explicit grid
583583
grid_areas = box.style['grid_template_areas']

weasyprint/layout/inline.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,9 @@ def get_next_linebox(context, linebox, position_y, bottom_space, skip_stack,
178178
fixed_boxes, bottom_space, skip_stack=None)
179179
float_children.append(new_waiting_float)
180180
if waiting_float_resume_at:
181-
context.broken_out_of_flow[new_waiting_float] = (
182-
waiting_float, containing_block, waiting_float_resume_at)
181+
context.add_broken_out_of_flow(
182+
new_waiting_float, waiting_float, containing_block,
183+
waiting_float_resume_at)
183184
if float_children:
184185
line.children += tuple(float_children)
185186

@@ -574,8 +575,8 @@ def _out_of_flow_layout(context, box, containing_block, index, child,
574575
context, child, containing_block, absolute_boxes, fixed_boxes,
575576
bottom_space, skip_stack=None)
576577
if float_resume_at:
577-
context.broken_out_of_flow[child] = (
578-
child, containing_block, float_resume_at)
578+
context.add_broken_out_of_flow(
579+
child, child, containing_block, float_resume_at)
579580
waiting_children.append((index, new_child, child))
580581
child = new_child
581582

weasyprint/layout/page.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Layout for pages and CSS3 margin boxes."""
22

33
import copy
4-
from collections import namedtuple
4+
from collections import defaultdict, namedtuple
55
from math import inf
66

77
from ..css import AnonymousStyle
@@ -606,7 +606,7 @@ def make_page(context, root_box, page_type, resume_at, page_number,
606606

607607
# https://www.w3.org/TR/css-display-4/#root
608608
assert isinstance(root_box, boxes.BlockLevelBox)
609-
context.create_block_formatting_context()
609+
context.create_block_formatting_context(root_box)
610610
context.current_page = page_number
611611
context.current_page_footnotes = []
612612
context.current_footnote_area = footnote_area
@@ -627,15 +627,19 @@ def make_page(context, root_box, page_type, resume_at, page_number,
627627
adjoining_margins = []
628628
positioned_boxes = [] # Mixed absolute and fixed
629629
out_of_flow_boxes = []
630+
excluded_shapes = defaultdict(list)
630631
broken_out_of_flow = {}
631632
context_out_of_flow = context.broken_out_of_flow.values()
632633
context.broken_out_of_flow = broken_out_of_flow
633-
for box, containing_block, skip_stack in context_out_of_flow:
634+
for box, containing_block, context_box, skip_stack in context_out_of_flow:
635+
if context_box != root_box:
636+
context.create_block_formatting_context(context_box)
634637
box.position_y = root_box.content_box_y()
635638
if box.is_floated():
636639
out_of_flow_box, out_of_flow_resume_at = float_layout(
637640
context, box, containing_block, positioned_boxes,
638641
positioned_boxes, 0, skip_stack)
642+
excluded_shapes[context_box].append(out_of_flow_box)
639643
else:
640644
assert box.is_absolutely_positioned()
641645
out_of_flow_box, out_of_flow_resume_at = absolute_box_layout(
@@ -644,8 +648,14 @@ def make_page(context, root_box, page_type, resume_at, page_number,
644648
out_of_flow_boxes.append(out_of_flow_box)
645649
page_is_empty = False
646650
if out_of_flow_resume_at:
647-
broken_out_of_flow[out_of_flow_box] = (
648-
box, containing_block, out_of_flow_resume_at)
651+
context.add_broken_out_of_flow(
652+
out_of_flow_box, box, containing_block, out_of_flow_resume_at)
653+
if context_box != root_box:
654+
context.finish_block_formatting_context()
655+
656+
# Set excluded shapes from broken out of flow for in-flow content.
657+
for context_box, shapes in excluded_shapes.items():
658+
context._excluded_shapes[context_box] = shapes
649659

650660
# Display in-flow content.
651661
initial_root_box = root_box

0 commit comments

Comments
 (0)