Skip to content

Commit 908ab7c

Browse files
committed
Handle page breaks for spanning items
1 parent 1c5eff6 commit 908ab7c

File tree

2 files changed

+129
-30
lines changed

2 files changed

+129
-30
lines changed

tests/layout/test_grid.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,90 @@ def test_grid_break_multiple():
14211421
assert not div8.children
14221422

14231423

1424+
@assert_no_logs
1425+
def test_grid_break_span():
1426+
page1, page2 = render_pages('''
1427+
<style>
1428+
@page { size: 10px 11px }
1429+
body { font: 2px / 1 weasyprint }
1430+
section { display: grid; grid-template-columns: 1fr 1fr }
1431+
</style>
1432+
<section>
1433+
<div>1</div>
1434+
<div>2</div>
1435+
<div>3</div>
1436+
<div>4</div>
1437+
<div>5</div>
1438+
<div>6</div>
1439+
<div style="grid-row: span 3">7a<br>7b<br>7c<br>7d</div>
1440+
<div>8</div>
1441+
<div>9</div>
1442+
<div>10</div>
1443+
<div>11</div>
1444+
<div>12</div>
1445+
</section>
1446+
''')
1447+
html, = page1.children
1448+
body, = html.children
1449+
section, = body.children
1450+
assert section.height == 11
1451+
div1, div2, div3, div4, div5, div6, div7, div8, div9 = section.children
1452+
assert div1.position_x == div3.position_x == div5.position_x == div7.position_x == 0
1453+
assert div2.position_x == div4.position_x == div6.position_x == div8.position_x == 5
1454+
assert div1.position_y == div2.position_y == 0
1455+
assert div1.height == div2.height == 2
1456+
assert div3.position_y == div4.position_y == 2
1457+
assert div3.height == div4.height == 2
1458+
assert div5.position_y == div6.position_y == 4
1459+
assert div5.height == div6.height == 2
1460+
assert div7.position_y == div8.position_y == 6
1461+
assert div7.height == 5
1462+
assert 2.6 < div8.height < 2.7
1463+
assert 2.3 < div9.height < 2.4
1464+
line1, line2 = div7.children
1465+
text, br = line1.children
1466+
assert text.text == '7a'
1467+
text, br = line2.children
1468+
assert text.text == '7b'
1469+
line, = div8.children
1470+
text, = line.children
1471+
assert text.text == '8'
1472+
line, = div9.children
1473+
text, = line.children
1474+
assert text.text == '9'
1475+
1476+
# TODO: div7 height could be only 4, but algorithm is not specified.
1477+
html, = page2.children
1478+
body, = html.children
1479+
section, = body.children
1480+
assert 6.6 < section.height < 6.7
1481+
div7, div9, div10, div11, div12 = section.children
1482+
assert div7.position_x == 0
1483+
assert div8.position_x == 5
1484+
assert 4.6 < div7.height < 4.7
1485+
line1, line2 = div7.children
1486+
text, br = line1.children
1487+
assert text.text == '7c'
1488+
text, = line2.children
1489+
assert text.text == '7d'
1490+
assert not div9.children
1491+
assert div10.position_x == 5
1492+
assert 1.3 < div10.position_y < 1.4
1493+
line, = div10.children
1494+
text, = line.children
1495+
assert text.text == '10'
1496+
assert div11.position_x == 0
1497+
assert div12.position_x == 5
1498+
assert 4.6 < div11.position_y < 4.7
1499+
assert 4.6 < div12.position_y < 4.7
1500+
line, = div11.children
1501+
text, = line.children
1502+
assert text.text == '11'
1503+
line, = div12.children
1504+
text, = line.children
1505+
assert text.text == '12'
1506+
1507+
14241508
@assert_no_logs
14251509
def test_grid_break_item_avoid():
14261510
page1, page2 = render_pages('''

weasyprint/layout/grid.py

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,56 +1139,61 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
11391139
this_page_children = []
11401140
resume_row = None
11411141
if skip_stack:
1142-
skip_row = next(iter(skip_stack))
1142+
first_skip_row = min(skip_stack)
1143+
last_skip_row = max(skip_stack)
11431144
skip_height = (
1144-
sum(size for size, _ in rows_sizes[:skip_row]) +
1145-
(len(rows_sizes[:skip_row]) - 1) * row_gap)
1145+
sum(size for size, _ in rows_sizes[:last_skip_row]) +
1146+
(len(rows_sizes[:last_skip_row]) - 1) * row_gap)
11461147
else:
1147-
skip_row = 0
1148-
skip_height = 0
1148+
first_skip_row = last_skip_row = skip_height = 0
11491149
resume_at = None
11501150
total_height = (
1151-
sum(size for size, _ in rows_sizes[skip_row:]) +
1152-
(len(rows_sizes[skip_row:]) - 1) * row_gap)
1151+
sum(size for size, _ in rows_sizes[last_skip_row:]) +
1152+
(len(rows_sizes[last_skip_row:]) - 1) * row_gap)
1153+
1154+
def _add_page_children(max_row=inf):
1155+
for j, child in enumerate(children):
1156+
_, y, _, _ = children_positions[child]
1157+
if not first_skip_row <= y < max_row:
1158+
# Item in previous or next rows.
1159+
continue
1160+
child_skip_stack = (skip_stack or {}).get(y)
1161+
if child_skip_stack is None or j in child_skip_stack:
1162+
# No skip stack for this row or child in skip stack.
1163+
this_page_children.append((j, child))
1164+
11531165
row_lines_positions = (
1154-
[*rows_positions[skip_row + 1:], box.content_box_y() + total_height])
1155-
for i, row_y in enumerate(row_lines_positions, start=skip_row):
1166+
[*rows_positions[last_skip_row + 1:], box.content_box_y() + total_height])
1167+
for i, row_y in enumerate(row_lines_positions, start=first_skip_row):
11561168
# TODO: handle break-before and break-after for rows.
11571169
overflows = context.overflows_page(bottom_space, row_y - skip_height)
1170+
if overflows:
1171+
resume_row = i
11581172
if overflows and not page_is_empty:
11591173
if box.style['break_inside'] == 'avoid':
11601174
# Avoid breaks inside grid container, break before.
11611175
return None, None, {'break': 'any', 'page': None}, [], False
1162-
resume_row = i
11631176
if page_breaks_by_row[i]['inside'] == 'avoid':
11641177
# Break before current row.
11651178
if resume_row == 0:
11661179
# First row, break before grid container.
11671180
return None, None, {'break': 'any', 'page': None}, [], False
11681181
# Mark all children before and in current row as drawn on the page.
1169-
for j, child in enumerate(children):
1170-
_, y, _, _ = children_positions[child]
1171-
if skip_row <= y < resume_row:
1172-
this_page_children.append((j, child))
1182+
_add_page_children(resume_row)
11731183
resume_at = {resume_row: None}
11741184
else:
11751185
# Break inside current row.
11761186
# Mark all children before current row as drawn on the page.
1177-
for j, child in enumerate(children):
1178-
_, y, _, _ = children_positions[child]
1179-
if skip_row <= y <= resume_row:
1180-
this_page_children.append((j, child))
1187+
_add_page_children(resume_row + 1)
11811188
break
11821189
page_is_empty = False
11831190
else:
1184-
for j, child in enumerate(children):
1185-
_, y, _, _ = children_positions[child]
1186-
if skip_row <= y:
1187-
this_page_children.append((j, child))
1191+
# Mark all children as drawn on the page.
1192+
_add_page_children()
11881193
if box.height == 'auto':
11891194
box.height = (
1190-
sum(size for size, _ in rows_sizes[skip_row:resume_row]) +
1191-
(len(rows_sizes[skip_row:resume_row]) - 1) * row_gap)
1195+
sum(size for size, _ in rows_sizes[last_skip_row:resume_row]) +
1196+
(len(rows_sizes[last_skip_row:resume_row]) - 1) * row_gap)
11921197

11931198
# Lay out grid items.
11941199
justify_items = set(box.style['justify_items'])
@@ -1219,6 +1224,13 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
12191224
height = (
12201225
sum(size for size, _ in rows_sizes[y:y+height]) +
12211226
(height - 1) * row_gap)
1227+
if y < last_skip_row:
1228+
# Shift split spanning item from its row to last skip row.
1229+
child_skip_height = (
1230+
sum(size for size, _ in rows_sizes[y:last_skip_row]) +
1231+
max(0, len(rows_sizes[y:last_skip_row]) - 1) * row_gap)
1232+
child.position_y += child_skip_height
1233+
height -= child_skip_height
12221234

12231235
# TODO: Apply auto margin.
12241236
if child.margin_top == 'auto':
@@ -1266,18 +1278,21 @@ def grid_layout(context, box, bottom_space, skip_stack, containing_block,
12661278
page_is_empty, absolute_boxes, fixed_boxes)[:3]
12671279
if new_child:
12681280
page_is_empty = False
1269-
if child_resume_at:
1281+
if child_resume_at or resume_row == y:
1282+
# Child is broken.
12701283
if resume_at is None:
12711284
resume_at = {}
12721285
if y not in resume_at:
12731286
resume_at[y] = {}
1287+
if child_resume_at:
1288+
# There is some content left for next page.
12741289
resume_at[y][i] = child_resume_at
1290+
elif resume_row == y:
1291+
# Only display the bottom of an empty cell.
1292+
assert isinstance(new_child, boxes.ParentBox)
1293+
previous_skip_child = max(child_skip_stack) if child_skip_stack else 0
1294+
resume_at[y][i] = {previous_skip_child + len(new_child.children): None}
12751295
else:
1276-
if resume_at is None:
1277-
resume_at = {}
1278-
if y not in resume_at:
1279-
resume_at[y] = {}
1280-
resume_at[y][i] = {0: None}
12811296
continue
12821297

12831298
if justify_self & {'normal', 'stretch'}:

0 commit comments

Comments
 (0)