1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-10-05 00:21:15 +03:00

Support break-after in table. Related to #209

This commit is contained in:
grewn0uille 2019-08-20 17:33:35 +02:00
parent b8c19a2878
commit 4b396d5f58
3 changed files with 165 additions and 47 deletions

View File

@ -731,6 +731,20 @@ def establishes_formatting_context(box):
)
def combine_break_values(values):
result = 'auto'
for value in values:
if value in ('left', 'right', 'recto', 'verso') or (value, result) in (
('page', 'auto'),
('page', 'avoid'),
('avoid', 'auto'),
('page', 'avoid-page'),
('avoid-page', 'auto')):
result = value
return result
def block_level_page_break(sibling_before, sibling_after):
"""Return the value of ``page-break-before`` or ``page-break-after``
that "wins" for boxes that meet at the margin between two sibling boxes.
@ -747,8 +761,12 @@ def block_level_page_break(sibling_before, sibling_after):
"""
values = []
# https://drafts.csswg.org/css-break-3/#possible-breaks
block_parallel_box_types = (
boxes.BlockLevelBox, boxes.TableRowGroupBox, boxes.TableRowBox)
box = sibling_before
while isinstance(box, boxes.BlockLevelBox):
while isinstance(box, block_parallel_box_types):
values.append(box.style['break_after'])
if not (isinstance(box, boxes.ParentBox) and box.children):
break
@ -756,23 +774,13 @@ def block_level_page_break(sibling_before, sibling_after):
values.reverse() # Have them in tree order
box = sibling_after
while isinstance(box, boxes.BlockLevelBox):
while isinstance(box, block_parallel_box_types):
values.append(box.style['break_before'])
if not (isinstance(box, boxes.ParentBox) and box.children):
break
box = box.children[0]
result = 'auto'
for value in values:
if value in ('left', 'right', 'recto', 'verso') or (value, result) in (
('page', 'auto'),
('page', 'avoid'),
('avoid', 'auto'),
('page', 'avoid-page'),
('avoid-page', 'auto')):
result = value
return result
return combine_break_values(values)
def block_level_page_name(sibling_before, sibling_after):

View File

@ -20,7 +20,7 @@ def table_layout(context, table, max_position_y, skip_stack,
fixed_boxes):
"""Layout for a table box."""
# Avoid a circular import
from .blocks import block_container_layout
from .blocks import block_container_layout, combine_break_values
column_widths = table.column_widths
@ -62,6 +62,7 @@ def table_layout(context, table, max_position_y, skip_stack,
def group_layout(group, position_y, max_position_y,
page_is_empty, skip_stack):
resume_at = None
next_page = {'break': None, 'page': None}
original_page_is_empty = page_is_empty
resolve_percentages(group, containing_block=table)
group.position_x = rows_x
@ -77,7 +78,8 @@ def table_layout(context, table, max_position_y, skip_stack,
else:
skip, skip_stack = skip_stack
assert not skip_stack # No breaks inside rows for now
for index_row, row in group.enumerate_skip(skip):
for i, row in enumerate(group.children[skip:]):
index_row = i + skip
resolve_percentages(row, containing_block=table)
row.position_x = rows_x
row.position_y = position_y
@ -215,12 +217,20 @@ def table_layout(context, table, max_position_y, skip_stack,
new_group_children.append(row)
page_is_empty = False
if row.style['break_after'] in (
'page', 'recto', 'verso', 'left', 'right'):
if index_row < len(group.children) - 1:
resume_at = (index_row + 1, None)
break
else:
next_page['break'] = row.style['break_after']
# Do not keep the row group if we made a page break
# before any of its rows or with 'avoid'
if resume_at and not original_page_is_empty and (
group.style['break_inside'] in ('avoid', 'avoid-page') or
not new_group_children):
return None, None
return None, None, next_page
group = group.copy_with_children(
new_group_children,
@ -241,7 +251,7 @@ def table_layout(context, table, max_position_y, skip_stack,
# The last border spacing is outside of the group.
group.height -= border_spacing_y
return group, resume_at
return group, resume_at, next_page
def body_groups_layout(skip_stack, position_y, max_position_y,
page_is_empty):
@ -251,10 +261,12 @@ def table_layout(context, table, max_position_y, skip_stack,
skip, skip_stack = skip_stack
new_table_children = []
resume_at = None
for index_group, group in table.enumerate_skip(skip):
next_page = {'break': 'any', 'page': None}
for i, group in enumerate(table.children[skip:]):
index_group = i + skip
if group.is_header or group.is_footer:
continue
new_group, resume_at = group_layout(
new_group, resume_at, next_page = group_layout(
group, position_y, max_position_y, page_is_empty, skip_stack)
skip_stack = None
@ -266,10 +278,19 @@ def table_layout(context, table, max_position_y, skip_stack,
position_y += new_group.height + border_spacing_y
page_is_empty = False
if resume_at:
resume_at = (index_group, resume_at)
next_page['break'] = combine_break_values(
(next_page['break'], group.style['break_after']))
if resume_at or next_page['break'] in (
'page', 'recto', 'verso', 'left', 'right'):
if resume_at is None:
if index_group < len(table.children) - 1:
resume_at = (index_group + 1, None)
else:
resume_at = (index_group, resume_at)
break
return new_table_children, resume_at, position_y
return new_table_children, resume_at, next_page, position_y
# Layout for row groups, rows and cells
position_y = table.content_box_y() + border_spacing_y
@ -278,7 +299,7 @@ def table_layout(context, table, max_position_y, skip_stack,
def all_groups_layout():
if table.children and table.children[0].is_header:
header = table.children[0]
header, resume_at = group_layout(
header, resume_at, next_page = group_layout(
header, position_y, max_position_y,
skip_stack=None, page_is_empty=False)
if header and not resume_at:
@ -290,7 +311,7 @@ def table_layout(context, table, max_position_y, skip_stack,
if table.children and table.children[-1].is_footer:
footer = table.children[-1]
footer, resume_at = group_layout(
footer, resume_at, next_page = group_layout(
footer, position_y, max_position_y,
skip_stack=None, page_is_empty=False)
if footer and not resume_at:
@ -311,54 +332,60 @@ def table_layout(context, table, max_position_y, skip_stack,
if header and footer:
# Try with both the header and footer
new_table_children, resume_at, end_position_y = body_groups_layout(
skip_stack,
position_y=position_y + header_height,
max_position_y=max_position_y - footer_height,
page_is_empty=avoid_breaks)
new_table_children, resume_at, next_page, end_position_y = (
body_groups_layout(
skip_stack,
position_y=position_y + header_height,
max_position_y=max_position_y - footer_height,
page_is_empty=avoid_breaks))
if new_table_children or not page_is_empty:
footer.translate(dy=end_position_y - footer.position_y)
end_position_y += footer_height
return (header, new_table_children, footer,
end_position_y, resume_at)
end_position_y, resume_at, next_page)
else:
# We could not fit any content, drop the footer
footer = None
if header and not footer:
# Try with just the header
new_table_children, resume_at, end_position_y = body_groups_layout(
skip_stack,
position_y=position_y + header_height,
max_position_y=max_position_y,
page_is_empty=avoid_breaks)
new_table_children, resume_at, next_page, end_position_y = (
body_groups_layout(
skip_stack,
position_y=position_y + header_height,
max_position_y=max_position_y,
page_is_empty=avoid_breaks))
if new_table_children or not page_is_empty:
return (header, new_table_children, footer,
end_position_y, resume_at)
end_position_y, resume_at, next_page)
else:
# We could not fit any content, drop the header
header = None
if footer and not header:
# Try with just the footer
new_table_children, resume_at, end_position_y = body_groups_layout(
skip_stack,
position_y=position_y,
max_position_y=max_position_y - footer_height,
page_is_empty=avoid_breaks)
new_table_children, resume_at, next_page, end_position_y = (
body_groups_layout(
skip_stack,
position_y=position_y,
max_position_y=max_position_y - footer_height,
page_is_empty=avoid_breaks))
if new_table_children or not page_is_empty:
footer.translate(dy=end_position_y - footer.position_y)
end_position_y += footer_height
return (header, new_table_children, footer,
end_position_y, resume_at)
end_position_y, resume_at, next_page)
else:
# We could not fit any content, drop the footer
footer = None
assert not (header or footer)
new_table_children, resume_at, end_position_y = body_groups_layout(
skip_stack, position_y, max_position_y, page_is_empty)
return header, new_table_children, footer, end_position_y, resume_at
new_table_children, resume_at, next_page, end_position_y = (
body_groups_layout(
skip_stack, position_y, max_position_y, page_is_empty))
return (
header, new_table_children, footer, end_position_y, resume_at,
next_page)
def get_column_cells(table, column):
"""Closure getting the column cells."""
@ -369,7 +396,7 @@ def table_layout(context, table, max_position_y, skip_stack,
for cell in row.children
if cell.grid_x == column.grid_x]
header, new_table_children, footer, position_y, resume_at = \
header, new_table_children, footer, position_y, resume_at, next_page = \
all_groups_layout()
table = table.copy_with_children(
([header] if header is not None else []) +
@ -413,7 +440,10 @@ def table_layout(context, table, max_position_y, skip_stack,
group.width = last.position_x + last.width - first.position_x
group.height = columns_height
next_page = {'break': 'any', 'page': table.style['page']}
next_page['break'] = combine_break_values(
(next_page['break'], table.style['break_after']))
next_page['page'] = table.style['page']
if resume_at and not page_is_empty and (
table.style['break_inside'] in ('avoid', 'avoid-page') or
not new_table_children):

View File

@ -2064,6 +2064,86 @@ def test_table_page_breaks_complex():
]
@assert_no_logs
def test_table_page_break_after():
page1, page2, page3, page4, page5, page6 = render_pages('''
<style>
@page { size: 1000px }
h1 { height: 30px}
td { height: 40px }
table { table-layout: fixed; width: 100% }
</style>
<h1>Dummy title</h1>
<table>
<tbody>
<tr><td>row 1</td></tr>
<tr><td>row 2</td></tr>
<tr><td>row 3</td></tr>
</tbody>
<tbody>
<tr style="break-after: page"><td>row 1</td></tr>
<tr><td>row 2</td></tr>
<tr><td>row 3</td></tr>
</tbody>
<tbody>
<tr><td>row 1</td></tr>
<tr><td>row 2</td></tr>
<tr style="break-after: page"><td>row 3</td></tr>
</tbody>
<tbody style="break-after: right">
<tr><td>row 1</td></tr>
<tr><td>row 2</td></tr>
<tr><td>row 3</td></tr>
</tbody>
<tbody style="break-after: page">
<tr><td>row 1</td></tr>
<tr><td>row 2</td></tr>
<tr><td>row 3</td></tr>
</tbody>
</table>
<p>bla bla</p>
''')
html, = page1.children
body, = html.children
h1, table_wrapper = body.children
table, = table_wrapper.children
table_group1, table_group2 = table.children
assert len(table_group1.children) == 3
assert len(table_group2.children) == 1
html, = page2.children
body, = html.children
table_wrapper, = body.children
table, = table_wrapper.children
table_group1, table_group2 = table.children
assert len(table_group1.children) == 2
assert len(table_group2.children) == 3
html, = page3.children
body, = html.children
table_wrapper, = body.children
table, = table_wrapper.children
table_group, = table.children
assert len(table_group.children) == 3
html, = page4.children
assert not html.children
html, = page5.children
body, = html.children
table_wrapper, = body.children
table, = table_wrapper.children
table_group, = table.children
assert len(table_group.children) == 3
html, = page6.children
body, = html.children
p, = body.children
assert p.element_tag == 'p'
@assert_no_logs
@pytest.mark.parametrize('vertical_align, table_position_y', (
('top', 8),