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

Implement page-break-before and page-break-after (without 'avoid').

This commit is contained in:
Simon Sapin 2012-01-20 11:55:06 +01:00
parent 604219e376
commit 461ab52bd5
6 changed files with 167 additions and 28 deletions

View File

@ -5,6 +5,8 @@ Version 0.4, released on 201X-XX-XX
page-based counters.
* All CSS 2.1 border styles
* Fix SVG images with non-pixel units. Requires CairoSVG 0.3
* Support for ``page-break-before`` and ``page-break-after``, except for
the value ``avoid``.
Version 0.3.1, released on 2011-12-14

View File

@ -510,6 +510,30 @@ def length_or_precentage(value):
return value
@validator('page-break-before')
@validator('page-break-after')
@single_keyword
def page_break(keyword):
"""Validation for the ``page-break-before`` and ``page-break-after``
properties.
"""
if keyword == 'avoid':
raise InvalidValues('value not supported yet')
return keyword in ('auto', 'always', 'left', 'right')
# Not very useful, might as well ignore the property anyway.
# Keep it for completeness.
@validator()
@single_keyword
def page_break_inside(keyword):
"""Validation for the ``page-break-inside`` property."""
if keyword == 'avoid':
raise InvalidValues('value not supported yet')
return keyword ('auto',)
@validator()
@single_keyword
def position(keyword):

View File

@ -50,8 +50,9 @@ def block_level_layout(document, box, max_position_y, skip_stack,
return block_box_layout(document, box, max_position_y, skip_stack,
containing_block, device_size, page_is_empty)
elif isinstance(box, boxes.BlockReplacedBox):
return block_replaced_box_layout(
box, containing_block, device_size), None
box = block_replaced_box_layout(
box, containing_block, device_size)
return box, None, 'any'
else:
raise TypeError('Layout for %s not handled yet' % type(box).__name__)
@ -142,6 +143,7 @@ def block_level_width(box, containing_block):
box.margin_right = margin_sum - margin_l
# TODO: rename this to block_container_something
def block_level_height(document, box, max_position_y, skip_stack,
device_size, page_is_empty):
"""Set the ``box`` height."""
@ -160,6 +162,7 @@ def block_level_height(document, box, max_position_y, skip_stack,
initial_position_y = position_y
new_children = []
next_page = 'any'
if skip_stack is None:
skip = 0
@ -191,7 +194,7 @@ def block_level_height(document, box, max_position_y, skip_stack,
if new_position_y > max_position_y and not page_is_empty:
if not new_children:
# Page break before any content, cancel the whole box.
return None, None
return None, None, 'any'
# Page break here, resume before this line
resume_at = (index, skip_stack)
is_page_break = True
@ -205,8 +208,17 @@ def block_level_height(document, box, max_position_y, skip_stack,
if is_page_break:
break
else:
page_break = child.style.page_break_before
if page_break in ('always', 'left', 'right'):
next_page = 'any' if page_break == 'always' else page_break
resume_at = (index, None)
# Force break only once
# TODO: refactor to avoid doing this?
child.style.page_break_before = 'auto'
break
new_containing_block = box
new_child, resume_at = block_level_layout(
new_child, resume_at, next_page = block_level_layout(
document, child, max_position_y, skip_stack,
new_containing_block, device_size, page_is_empty)
skip_stack = None
@ -217,7 +229,7 @@ def block_level_height(document, box, max_position_y, skip_stack,
else:
# This was the first child of this box, cancel the box
# completly
return None, None
return None, None, 'any'
new_position_y = position_y + new_child.margin_height()
# Bottom borders may overflow here
# TODO: back-track somehow when all lines fit but not borders
@ -227,6 +239,13 @@ def block_level_height(document, box, max_position_y, skip_stack,
if resume_at is not None:
resume_at = (index, resume_at)
break
page_break = child.style.page_break_after
if page_break in ('always', 'left', 'right'):
next_page = 'any' if page_break == 'always' else page_break
# Resume after this
resume_at = (index + 1, None)
break
else:
resume_at = None
@ -242,7 +261,7 @@ def block_level_height(document, box, max_position_y, skip_stack,
box.outside_list_marker = None
box.reset_spacing('top')
new_box.reset_spacing('bottom')
return new_box, resume_at
return new_box, resume_at, next_page
def block_table_wrapper(document, wrapper, max_position_y, skip_stack,

View File

@ -430,13 +430,30 @@ def make_margin_boxes(document, page, counter_values):
# content_to_boxes() only produces inline-level boxes, no need to
# run other post-processors from build.build_formatting_structure()
box = build.inline_in_block(box)
box, resume_at = block_level_height(document, box,
box, resume_at, next_page = block_level_height(document, box,
max_position_y=float('inf'), skip_stack=None,
device_size=page.style.size, page_is_empty=True)
assert resume_at is None
yield box
def make_empty_page(document, page_type):
root_box = document.formatting_structure
style = document.style_for(page_type)
page = boxes.PageBox(page_type, style, root_box.style.direction)
device_size = page.style.size
page.outer_width, page.outer_height = device_size
resolve_percentages(page, device_size)
page.position_x = 0
page.position_y = 0
page.width = page.outer_width - page.horizontal_surroundings()
page.height = page.outer_height - page.vertical_surroundings()
return page
def make_page(document, page_type, resume_at):
"""Take just enough content from the beginning to fill one page.
@ -450,19 +467,9 @@ def make_page(document, page_type, resume_at):
or ``None`` for the first page.
"""
page = make_empty_page(document, page_type)
root_box = document.formatting_structure
style = document.style_for(page_type)
page = boxes.PageBox(page_type, style, root_box.style.direction)
device_size = page.style.size
page.outer_width, page.outer_height = device_size
resolve_percentages(page, device_size)
page.position_x = 0
page.position_y = 0
page.width = page.outer_width - page.horizontal_surroundings()
page.height = page.outer_height - page.vertical_surroundings()
root_box.position_x = page.content_box_x()
root_box.position_y = page.content_box_y()
@ -472,25 +479,41 @@ def make_page(document, page_type, resume_at):
# TODO: handle cases where the root element is something else.
# See http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
assert isinstance(root_box, boxes.BlockBox)
root_box, resume_at = block_level_layout(
root_box, resume_at, next_page = block_level_layout(
document, root_box, page_content_bottom, resume_at,
initial_containing_block, device_size, page_is_empty=True)
assert root_box
page = page.copy_with_children([root_box])
return page, resume_at
return page, resume_at, next_page
def make_all_pages(document):
"""Return a list of laid out pages without margin boxes."""
root_box = document.formatting_structure
prefix = 'first_'
right_page = root_box.style.direction == 'ltr'
# Special case the root box
page_break = root_box.style.page_break_before
if page_break == 'right':
right_page = True
if page_break == 'left':
right_page = False
else:
right_page = root_box.style.direction == 'ltr'
resume_at = None
next_page = 'any'
while True:
page_type = prefix + ('right_page' if right_page else 'left_page')
page, resume_at = make_page(document, page_type, resume_at)
if ((next_page == 'left' and right_page) or
(next_page == 'right' and not right_page)):
page = make_empty_page(document, page_type)
else:
page, resume_at, next_page = make_page(
document, page_type, resume_at)
assert next_page
yield page
if resume_at is None:
return

View File

@ -104,7 +104,7 @@ def table_layout(document, table, max_position_y, containing_block,
# The computed height is a minimum
computed_cell_height = cell.height
cell.height = 'auto'
cell, _ = block_level_height(
cell, _, _ = block_level_height(
document, cell,
max_position_y=float('inf'),
skip_stack=None,
@ -216,9 +216,9 @@ def table_layout(document, table, max_position_y, containing_block,
and not page_is_empty):
# If the table does not fit, put it on the next page.
# (No page break inside tables yet.)
return None, None
return None, None, 'any'
else:
return table, None
return table, None, 'any'
def add_top_padding(box, extra_padding):

View File

@ -521,16 +521,20 @@ def test_forced_line_breaks():
assert [line.height for line in lines] == [42] * 7
#@SUITE.test
@SUITE.test
def test_page_breaks():
"""Test the page breaks."""
pages = parse('''
<style>
@page { -weasy-size: 100px; margin: 10px }
body { margin: 0 }
div { height: 30px }
div { height: 30px; font-size: 20px; }
</style>
<div/><div/><div/><div/><div/>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
''')
page_divs = []
for page in pages:
@ -542,6 +546,73 @@ def test_page_breaks():
positions_y = [[div.position_y for div in divs] for divs in page_divs]
assert positions_y == [[10, 40], [10, 40], [10]]
# Same as above, but no content inside each <div>.
# TODO: This currently gives no page break. Should it?
# pages = parse('''
# <style>
# @page { -weasy-size: 100px; margin: 10px }
# body { margin: 0 }
# div { height: 30px }
# </style>
# <div/><div/><div/><div/><div/>
# ''')
# page_divs = []
# for page in pages:
# divs = body_children(page)
# assert all([div.element_tag == 'div' for div in divs])
# assert all([div.position_x == 10 for div in divs])
# page_divs.append(divs)
# positions_y = [[div.position_y for div in divs] for divs in page_divs]
# assert positions_y == [[10, 40], [10, 40], [10]]
page_1, page_2, page_3, page_4 = parse('''
<style>
@page { margin: 10px }
@page :left { margin-left: 50px }
@page :right { margin-right: 50px }
html { page-break-before: left }
div { page-break-after: left }
ul { page-break-before: always }
</style>
<div>1</div>
<p>2</p>
<p>3</p>
<ul><li>4</li></ul>
''')
# The first page is a right page on rtl, but not here because of
# page-break-before on the root element.
assert page_1.margin_left == 50 # left page
assert page_1.margin_right == 10
html, = page_1.children
body, = html.children
div, = body.children
line, = div.children
text, = line.children
assert div.element_tag == 'div'
assert text.text == '1'
assert page_2.margin_left == 10
assert page_2.margin_right == 50 # right page
assert not page_2.children # empty page to get to a left page
assert page_3.margin_left == 50 # left page
assert page_3.margin_right == 10
html, = page_3.children
body, = html.children
p_1, p_2 = body.children
assert p_1.element_tag == 'p'
assert p_2.element_tag == 'p'
assert page_4.margin_left == 10
assert page_4.margin_right == 50 # right page
html, = page_4.children
body, = html.children
ulist, = body.children
assert ulist.element_tag == 'ul'
@SUITE.test
def test_inlinebox_spliting():