Skip to content

Commit

Permalink
fix: handle float rounding when extending boxes over page breaks
Browse files Browse the repository at this point in the history
When a box would break over the edge of a page, its height is extended
to the bottom of that page (per
https://www.w3.org/TR/css-break-3/#box-splitting , primarily to allow
backgrounds and borders to continue to the end of the page).

When this happened, sometimes the values that would be calculated for
the height of the extended element would be rounded *over* the
calculated height that remained on the page, forcing the entire
containing box to wrap to the next page.

Rather than trying to carefully manage the order of operations to try to
be safe in IEEE floats for directions, we apply a small "fudge factor":
if an element fits very nearly (within a thousandth of a pixel) into the
remaining space, it is still accepted.
  • Loading branch information
aschmitz authored and liZe committed Feb 20, 2022
1 parent d6c1900 commit 9b0a672
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 1 deletion.
34 changes: 34 additions & 0 deletions tests/layout/test_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,40 @@ def test_page_breaks(html):
for page_child in page_children] == [[10, 40], [10, 40], [10]]


@assert_no_logs
def test_page_breaks_box_split():
# If floats round the wrong way, a block that gets filled to the end of a
# page due to breaking over the page may be forced onto the next page
# because it is slightly taller than can fit on the previous page, even if
# it wouldn't have been without being filled. These numbers aren't ideal,
# but they do seem to trigger the issue.
page_1, page_2 = render_pages('''
<style>
@page { size: 982.4146981627297px; margin: 0 }
div { font-size: 5px; height: 200.0123456789px; margin: 0; padding: 0 }
figure { margin: 0; padding: 0 }
</style>
<div>text</div>
<div>text</div><!-- no page break here -->
<section>
<div>line1</div>
<div>line2</div><!-- page break here -->
<div>line3</div>
<div>line4</div>
</section>
''')
html, = page_1.children
body, = html.children
assert len(body.children) == 3
div1, div2, section = body.children
assert len(section.children) == 2

html, = page_2.children
body, = html.children
section, = body.children
assert len(section.children) == 2


@assert_no_logs
def test_page_breaks_complex_1():
page_1, page_2, page_3, page_4 = render_pages('''
Expand Down
8 changes: 7 additions & 1 deletion weasyprint/layout/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,13 @@ def _in_flow_layout(context, box, index, child, new_children, page_is_empty,
new_position_y = (
new_child.border_box_y() + new_child.border_height())

if (new_content_position_y > context.page_bottom - bottom_space and
# Use a small fudge factor on this due to box splitting setting the
# height of some elements to the remaining height of the page:
# https://www.w3.org/TR/css-break-3/#box-splitting
# (Occasionally the order of this calculation would otherwise come
# out with unequal float values, forcing the box to the next page.)
if (new_content_position_y >
context.page_bottom - bottom_space + 0.001 and
not page_is_empty_with_no_children):
# The child content overflows the page area, display it on the
# next page.
Expand Down

0 comments on commit 9b0a672

Please sign in to comment.