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:
parent
e1f59a3667
commit
c4234ec80b
@ -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]
|
||||
|
@ -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')
|
||||
|
@ -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':
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user