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:
parent
604219e376
commit
461ab52bd5
2
CHANGES
2
CHANGES
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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():
|
||||
|
Loading…
Reference in New Issue
Block a user