1
1
mirror of https://github.com/Kozea/WeasyPrint.git synced 2024-10-04 07:57:52 +03:00

Create margin boxes (not laid-out correctly yet.)

This commit is contained in:
Simon Sapin 2011-12-28 15:34:30 +01:00
parent e1f59a3667
commit c4234ec80b
10 changed files with 228 additions and 98 deletions

View File

@ -580,17 +580,3 @@ def get_all_computed_styles(document, medium,
element, pseudo_type)
return computed_styles
def page_style(document, page_number):
"""Return the StyleDict for a page box."""
# First page is a right page.
# TODO: this "should depend on the major writing direction of the
# document".
first_is_right = True
is_right = (page_number % 2) == (1 if first_is_right else 0)
page_type = 'right_page' if is_right else 'left_page'
if page_number == 1:
page_type = 'first_' + page_type
return document.computed_styles[page_type, None]

View File

@ -280,15 +280,12 @@ def border_width(computer, name, value):
@register_computer('content')
def content(computer, name, values):
"""Compute the ``content`` property."""
if computer.pseudo_type in ('before', 'after'):
if values in ('normal', 'none'):
return 'none'
else:
return [('STRING', computer.element.get(value, ''))
if type_ == 'attr' else (type_, value)
for type_, value in values]
if values in ('normal', 'none'):
return values
else:
return 'normal'
return [('STRING', computer.element.get(value, ''))
if type_ == 'attr' else (type_, value)
for type_, value in values]
@register_computer('display')

View File

@ -115,9 +115,9 @@ def draw_page_background(document, context, page):
# TODO: more tests for this, see
# http://www.w3.org/TR/css3-page/#page-properties
draw_background(document, context, page, clip=False)
# Margin boxes come before the content for painting order,
# so the box for the root element is the last child.
root_box = page.children[-1]
# Margin boxes come after the content for painting order,
# so the box for the root element is the first child.
root_box = page.children[0]
if has_background(root_box):
draw_background(document, context, root_box, clip=False)
elif root_box.element_tag.lower() == 'html':

View File

@ -89,7 +89,7 @@ class Box(object):
self.style = style.copy()
def __repr__(self):
return '<%s %s %i>' % (
return '<%s %s %s>' % (
type(self).__name__, self.element_tag, self.sourceline)
@classmethod
@ -262,32 +262,6 @@ class ParentBox(Box):
child.translate(dx, dy)
class PageBox(ParentBox):
"""Box for a page.
Initially the whole document will be in the box for the root element.
During layout a new page box is created after every page break.
"""
def __init__(self, page_number, style, direction):
self._direction = direction
# starting at 1 for the first page.
self.page_number = page_number
# Page boxes are not linked to any element.
super(PageBox, self).__init__(
element_tag=None, sourceline=None, style=style, children=[])
def __repr__(self):
return '<%s %s>' % (type(self).__name__, self.page_number)
@property
def direction(self):
"""
The direction of the page box (containing block to the root element)
is that of the root element.
"""
return self._direction
class BlockLevelBox(Box):
"""A box that participates in an block formatting context.
@ -525,3 +499,42 @@ class TableCaptionBox(BlockBox):
proper_table_child = True
internal_table_or_caption = True
proper_parents = (TableBox, InlineTableBox)
class PageBox(ParentBox):
"""Box for a page.
Initially the whole document will be in the box for the root element.
During layout a new page box is created after every page break.
"""
def __init__(self, page_number, style, direction):
self._direction = direction
# starting at 1 for the first page.
self.page_number = page_number
# Page boxes are not linked to any element.
super(PageBox, self).__init__(
element_tag=None, sourceline=None, style=style, children=[])
def __repr__(self):
return '<%s %s>' % (type(self).__name__, self.page_number)
@property
def direction(self):
"""
The direction of the page box (containing block to the root element)
is that of the root element.
"""
return self._direction
class MarginBox(BlockContainerBox):
"""Box in page margins, as defined in CSS3 Paged Media"""
def __init__(self, at_keyword, style, children=[]):
self.at_keyword = at_keyword
# Margin boxes are not linked to any element.
super(MarginBox, self).__init__(
element_tag=None, sourceline=None, style=style, children=children)
def __repr__(self):
return '<%s %s>' % (type(self).__name__, self.at_keyword)

View File

@ -153,7 +153,7 @@ def pseudo_to_box(document, state, element, pseudo_type):
# differ from the computer value?
display = style.display
content = style.content
if 'none' in (display, content):
if 'none' in (display, content) or content == 'normal':
return
box = BOX_TYPE_FROM_DISPLAY[display](
@ -164,8 +164,16 @@ def pseudo_to_box(document, state, element, pseudo_type):
children = []
if display == 'list-item':
children.extend(add_box_marker(document, counter_values, box))
children.extend(content_to_boxes(
document, style, box, quote_depth, counter_values))
yield box.copy_with_children(children)
def content_to_boxes(document, style, parent_box, quote_depth, counter_values):
"""Takes the value of a ``content`` property and yield boxes."""
texts = []
for type_, value in content:
for type_, value in style.content:
if type_ == 'STRING':
texts.append(value)
elif type_ == 'URI':
@ -173,10 +181,9 @@ def pseudo_to_box(document, state, element, pseudo_type):
if image is not None:
text = u''.join(texts)
if text:
children.append(boxes.TextBox.anonymous_from(box, text))
yield boxes.TextBox.anonymous_from(parent_box, text)
texts = []
children.append(boxes.InlineReplacedBox.anonymous_from(
box, image))
yield boxes.InlineReplacedBox.anonymous_from(parent_box, image)
elif type_ == 'counter':
counter_name, counter_style = value
counter_value = counter_values.get(counter_name, [0])[-1]
@ -200,9 +207,7 @@ def pseudo_to_box(document, state, element, pseudo_type):
quote_depth[0] += 1
text = u''.join(texts)
if text:
children.append(boxes.TextBox.anonymous_from(box, text))
yield box.copy_with_children(children)
yield boxes.TextBox.anonymous_from(parent_box, text)
def update_counters(state, style):

View File

@ -415,7 +415,7 @@ def split_text_box(document, box, available_width, skip):
assert isinstance(box, boxes.TextBox)
font_size = box.style.font_size
text = box.text[skip:]
if font_size == 0 or available_width <= 0 or not text:
if font_size == 0 or not text:
return None, None, False
fragment = TextFragment(text, box.style,

View File

@ -24,14 +24,81 @@ Layout for page boxes and margin boxes.
from __future__ import division
from .. import css
from .blocks import block_level_layout
from .blocks import block_level_layout, block_level_height
from .percentages import resolve_percentages
from ..formatting_structure import boxes
from ..formatting_structure import boxes, build
def make_margin_boxes(document, page_number):
return []
MARGIN_BOXES = (
# Order recommended on http://dev.w3.org/csswg/css3-page/#painting
# center/middle on top (last in tree order), then corner, ther others
'@top-left',
'@top-right',
'@bottom-left',
'@bottom-right',
'@left-top',
'@right-bottom',
'@right-top',
'@right-bottom'
'@top-left-corner',
'@top-right-corner',
'@bottom-left-corner',
'@bottom-right-corner',
'@bottom-center',
'@top-center',
'@left-middle',
'@right-middle',
)
def empty_margin_boxes(document, page, page_type):
for at_keyword in MARGIN_BOXES:
style = document.style_for(page_type, at_keyword)
if style is not None and style.content not in ('normal', 'none'):
box = boxes.MarginBox(at_keyword, style)
# TODO: Actual layout
box.position_x = 0
box.position_y = 0
containing_block = 0, 0
resolve_percentages(box, containing_block)
for name in ['width', 'height', 'margin_top', 'margin_bottom',
'margin_left', 'margin_right']:
if getattr(box, name) == 'auto':
setattr(box, name, 0)
yield box
def make_margin_boxes(document, page, page_type):
for box in empty_margin_boxes(document, page, page_type):
# TODO: get actual counter values at the time of the last page break
counter_values = {}
quote_depth = [0]
children = build.content_to_boxes(
document, box.style, page, quote_depth, counter_values)
box = box.copy_with_children(children)
# content_to_boxes() only produces inline-level boxes
box = build.inline_in_block(box)
box, resume_at = 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 page_type_for_number(page_number):
"""Return a page type such as ``'first_right_page'`` from a page number."""
# First page is a right page.
# TODO: this "should depend on the major writing direction of the
# document".
first_is_right = True
is_right = (page_number % 2) == (1 if first_is_right else 0)
page_type = 'right_page' if is_right else 'left_page'
if page_number == 1:
page_type = 'first_' + page_type
return page_type
def make_page(document, page_number, resume_at):
@ -42,15 +109,14 @@ def make_page(document, page_number, resume_at):
"""
root_box = document.formatting_structure
style = css.page_style(document, page_number)
page_type = page_type_for_number(page_number)
style = document.style_for(page_type)
page = boxes.PageBox(page_number, style, root_box.style.direction)
device_size = page.style.size
page.outer_width, page.outer_height = device_size
sheet = lambda: 1 # dummy object holding attributes.
sheet.width, sheet.height = device_size
resolve_percentages(page, sheet)
resolve_percentages(page, device_size)
page.position_x = 0
page.position_y = 0
@ -70,8 +136,8 @@ def make_page(document, page_number, resume_at):
initial_containing_block, device_size, page_is_empty=True)
assert root_box
children = make_margin_boxes(document, page_number)
children.append(root_box)
children = [root_box]
children.extend(make_margin_boxes(document, page, page_type))
page = page.copy_with_children(children)

View File

@ -61,9 +61,12 @@ def resolve_one_percentage(box, property_name, refer_to,
def resolve_percentages(box, containing_block):
"""Set used values as attributes of the box object."""
# cb = containing block
cb_width = containing_block.width
cb_height = containing_block.height
if isinstance(containing_block, boxes.Box):
# cb is short for containing block
cb_width = containing_block.width
cb_height = containing_block.height
else:
cb_width, cb_height = containing_block
if isinstance(box, boxes.PageBox):
maybe_height = cb_height
else:

View File

@ -26,8 +26,9 @@ import contextlib
from attest import Tests, assert_hook # pylint: disable=W0611
from .testing_utils import resource_filename, TestPNGDocument, assert_no_logs
from ..css import validation, page_style
from ..css import validation
from ..formatting_structure import boxes, build, counters
from ..layout.pages import page_type_for_number
SUITE = Tests()
@ -392,7 +393,8 @@ def test_page_style():
def assert_page_margins(page_number, top, right, bottom, left):
"""Check the page margin values."""
style = page_style(document, page_number)
page_type = page_type_for_number(page_number)
style = document.style_for(page_type)
assert style.margin_top == top
assert style.margin_right == right
assert style.margin_bottom == bottom
@ -710,6 +712,21 @@ def test_colspan_rowspan():
@SUITE.test
def test_before_after():
"""Test the :before and :after pseudo-elements."""
assert_tree(parse_all('''
<style>
p:before { content: normal }
div:before { content: none }
section:before { color: black }
</style>
<p></p>
<div></div>
<section></section>
'''), [
# No content in pseudo-element, no box generated
('p', 'Block', []),
('div', 'Block', []),
('section', 'Block', [])])
assert_tree(parse_all('''
<style>
p:before { content: 'a' 'b' }
@ -1066,3 +1083,39 @@ def test_counter_styles():
Ս Վ Տ Ր Ց Ւ Փ Ք
ՔՋՂԹ 10000 10001
'''.split()
@SUITE.test
def test_margin_boxes():
"""
Test that the correct margin boxes are created.
"""
document = TestPNGDocument.from_string('''
<style>
@page {
-weasy-size: 30px;
margin: 10px;
@top-center { content: "Title" }
}
@page :first {
@bottom-left { content: "foo" }
@bottom-left-corner { content: "baz" }
}
</style>
<p>lorem ipsum
''')
page_1, page_2 = document.pages
assert page_1.children[0].element_tag == 'html'
assert page_2.children[0].element_tag == 'html'
margin_boxes_1 = [box.at_keyword for box in page_1.children[1:]]
margin_boxes_2 = [box.at_keyword for box in page_2.children[1:]]
# Order matters, see http://dev.w3.org/csswg/css3-page/#painting
assert margin_boxes_1 == ['@bottom-left', '@bottom-left-corner',
'@top-center']
assert margin_boxes_2 == ['@top-center']
html, top_center = page_2.children
line_box, = top_center.children
text_box, = line_box.children
assert text_box.text == 'Title'

View File

@ -451,28 +451,30 @@ def test_linebox_text():
@SUITE.test
def test_linebox_positions():
"""Test the position of line boxes."""
page = u'''
<style>
p { width:%(width)spx; font-family:%(fonts)s;}
</style>
<p>this is test for <strong>Weasyprint</strong></p>'''
page, = parse(page % {'fonts': FONTS, 'width': 165})
paragraph, = body_children(page)
lines = list(paragraph.children)
assert len(lines) == 2
for width, expected_lines in [(165, 2), (1, 5), (0, 5)]:
page = u'''
<style>
p { width:%(width)spx; font-family:%(fonts)s;
line-height: 20px }
</style>
<p>this is test for <strong>Weasyprint</strong></p>'''
page, = parse(page % {'fonts': FONTS, 'width': width})
paragraph, = body_children(page)
lines = list(paragraph.children)
assert len(lines) == expected_lines
ref_position_y = lines[0].position_y
ref_position_x = lines[0].position_x
for line in lines:
assert ref_position_y == line.position_y
assert ref_position_x == line.position_x
for box in line.children:
assert ref_position_x == box.position_x
ref_position_x += box.width
assert ref_position_y == box.position_y
assert ref_position_x - line.position_x <= line.width
ref_position_x = line.position_x
ref_position_y += line.height
ref_position_y = lines[0].position_y
ref_position_x = lines[0].position_x
for line in lines:
assert ref_position_y == line.position_y
assert ref_position_x == line.position_x
for box in line.children:
assert ref_position_x == box.position_x
ref_position_x += box.width
assert ref_position_y == box.position_y
assert ref_position_x - line.position_x <= line.width
ref_position_x = line.position_x
ref_position_y += line.height
@SUITE.test
@ -605,6 +607,11 @@ def test_inlinebox_spliting():
assert len(parts) > 1
assert original_text == get_joined_text(parts)
# test with width = 0
parts = list(get_parts(document, inlinebox, 0, parent))
assert len(parts) > 1
assert original_text == get_joined_text(parts)
# with margin-border-padding
content = '''<strong style="border:10px solid; margin:10px; padding:10px">
WeasyPrint is a free software visual rendering engine