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

Fix painting area for page and canvas backgrounds.

This commit is contained in:
Simon Sapin 2012-01-27 16:53:16 +01:00
parent 03327e2291
commit a598c61571
5 changed files with 116 additions and 87 deletions

View File

@ -104,6 +104,9 @@ class StyleDict(object):
def __setitem__(self, key, value):
self._storage[key] = value
def update(self, other):
self._storage.update(other)
def __contains__(self, key):
return key in self._parent or key in self._storage

View File

@ -213,3 +213,8 @@ TABLE_WRAPPER_BOX_PROPERTIES = set('''
left
right
'''.split())
BACKGROUND_INITIAL = dict(
(name, value) for name, value in INITIAL_VALUES.iteritems()
if name.startswith('background'))

View File

@ -58,21 +58,18 @@ def draw_page(document, page, context):
The context should be scaled so that lengths are in CSS pixels.
"""
draw_page_background(document, context, page)
draw_box(document, context, page)
draw_box(document, context, page, page)
def draw_box(document, context, box):
def draw_box(document, context, page, box):
"""Draw a ``box`` on ``context``."""
if box.style.visibility == 'visible':
if has_background(box):
draw_background(document, context, box)
draw_box_background(document, context, page, box)
draw_border(context, box)
marker_box = getattr(box, 'outside_list_marker', None)
if marker_box:
draw_box(document, context, marker_box)
draw_box(document, context, page, marker_box)
if isinstance(box, boxes.TextBox):
draw_text(context, box)
@ -83,117 +80,114 @@ def draw_box(document, context, box):
if isinstance(box, boxes.TableBox):
for child in box.column_groups:
draw_box(document, context, child)
draw_box(document, context, page, child)
# XXX TODO: check the painting order for page boxes
if box is page:
draw_canvas_background(document, context, page)
if isinstance(box, boxes.ParentBox):
for child in box.children:
draw_box(document, context, child)
draw_box(document, context, page, child)
def has_background(box):
"""Return whether the given box has any background."""
return box.style.background_color.alpha > 0 or \
box.style.background_image != 'none'
def background_positioning_area(page, box, style):
if style.background_attachment == 'fixed' and box is not page:
# Initial containing block
return (
page.content_box_x(),
page.content_box_y(),
page.width,
page.height,
)
else:
# CSS3: box.style.background_origin
return (
box.border_box_x(),
box.border_box_y(),
box.border_width(),
box.border_height(),
)
def draw_page_background(document, context, page):
"""Draw the backgrounds for the page box (from @page style) and for the
page area (from the root element).
If the root element is "html" and has no background, the page areas
background is taken from its "body" child.
In both cases the background position is the same as if it was drawn on
the element.
See http://www.w3.org/TR/CSS21/colors.html#background
"""
# TODO: this one should have its origin at (0, 0), not the border box
# of the 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 after the content for painting order,
# so the box for the root element is the first child.
def draw_canvas_background(document, context, page):
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':
for child in root_box.children:
if child.element_tag.lower() == 'body':
# This must be drawn now, before anything on the root element.
draw_background(document, context, child, clip=False)
style = root_box.canvas_background
draw_background(document, context, style,
painting_area=(
page.padding_box_x(),
page.padding_box_y(),
page.padding_width(),
page.padding_height(),
),
positioning_area=background_positioning_area(page, root_box, style)
)
def draw_background(document, context, box, clip=True):
def draw_box_background(document, context, page, box):
"""Draw the box background color and image to a ``cairo.Context``."""
if getattr(box, 'background_drawn', False):
return
draw_background(document, context, box.style,
# CSS3: box.style.background_clip
painting_area=(
box.border_box_x(),
box.border_box_y(),
box.border_width(),
box.border_height(),
) if box is not page else None,
positioning_area=background_positioning_area(page, box, box.style)
)
box.background_drawn = True
if not has_background(box):
def draw_background(document, context, style, painting_area, positioning_area):
"""Draw the background color and image to a ``cairo.Context``."""
bg_color = style.background_color
bg_image = style.background_image
if bg_image == 'none':
image = None
else:
image = document.get_image_from_uri(bg_image)
if bg_color.alpha == 0 and image is None:
# No background.
return
with context.stacked():
bg_x = box.border_box_x()
bg_y = box.border_box_y()
bg_width = box.border_width()
bg_height = box.border_height()
bg_attachement = box.style.background_attachment
if bg_attachement == 'fixed':
# There should not be any clip yet
x1, y1, x2, y2 = context.clip_extents()
page_width = x2 - x1
page_height = y2 - y1
if clip:
context.rectangle(bg_x, bg_y, bg_width, bg_height)
if painting_area:
context.rectangle(*painting_area)
context.clip()
#else: unrestricted, whole page box
# Background color
bg_color = box.style.background_color
if bg_color.alpha > 0:
context.set_source_colorvalue(bg_color)
context.paint()
if bg_attachement == 'scroll':
# Change coordinates to make the rest easier.
context.translate(bg_x, bg_y)
else:
assert bg_attachement == 'fixed'
bg_width = page_width
bg_height = page_height
# Background image
bg_image = box.style.background_image
if bg_image == 'none':
return
image = document.get_image_from_uri(bg_image)
if image is None:
return
pattern, image_width, image_height = image
bg_position_x, bg_position_y = box.style.background_position
(positioning_x, positioning_y,
positioning_width, positioning_height) = positioning_area
context.translate(positioning_x, positioning_y)
percentage = get_percentage_value(bg_position_x)
if percentage is not None:
bg_position_x = (bg_width - image_width) * percentage / 100.
def percentage(value, refer_to):
percentage_value = get_percentage_value(value)
if percentage_value is None:
return value
else:
return refer_to * percentage_value / 100
percentage = get_percentage_value(bg_position_y)
if percentage is not None:
bg_position_y = (bg_height - image_height) * percentage / 100.
bg_position_x, bg_position_y = style.background_position
context.translate(
percentage(bg_position_x, positioning_width - image_width),
percentage(bg_position_y, positioning_height - image_height),
)
context.translate(bg_position_x, bg_position_y)
bg_repeat = box.style.background_repeat
bg_repeat = style.background_repeat
if bg_repeat in ('repeat-x', 'repeat-y'):
# Get the current clip rectangle
# Get the current clip rectangle. This is the same as
# painting_area, but in new coordinates after translate()
clip_x1, clip_y1, clip_x2, clip_y2 = context.clip_extents()
clip_width = clip_x2 - clip_x1
clip_height = clip_y2 - clip_y1

View File

@ -58,6 +58,7 @@ def build_formatting_structure(document):
box = anonymous_table_boxes(box)
box = inline_in_block(box)
box = block_in_inline(box)
box = set_canvas_background(box)
return box
@ -803,7 +804,7 @@ def _inner_block_in_inline(box, skip_stack=None):
If one is found, return ``(new_box, block_level_box, resume_at)``.
``new_box`` contains all of ``box`` content before the block-level box.
``resume_at`` can be passed as ``skip_stack`` in a new call to
this function to resume the search just after thes block-level box.
this function to resume the search just after the block-level box.
If no block-level box is found after the position marked by
``skip_stack``, return ``(new_box, None, None)``
@ -850,3 +851,28 @@ def _inner_block_in_inline(box, skip_stack=None):
box = box.copy_with_children(new_children)
return box, block_level_box, resume_at
def set_canvas_background(root_box):
"""
Set a ``canvas_background`` attribute on the box for the root element,
with style for the canvas background, taken from the root elememt
or a <body> child of the root element.
See http://www.w3.org/TR/CSS21/colors.html#background
"""
chosen_box = root_box
if (root_box.element_tag.lower() == 'html' and
root_box.style.background_color.alpha == 0 and
root_box.style.background_image == 'none'):
for child in root_box.children:
if child.element_tag.lower() == 'body':
chosen_box = child
break
style = chosen_box.style
style_without_background = style.copy()
style_without_background.update(properties.BACKGROUND_INITIAL)
chosen_box.style = style_without_background
root_box.canvas_background = style
return root_box

View File

@ -1096,11 +1096,12 @@ def test_margin_boxes():
_+_+_+_+_+_+_+_+_+_+_+_+_+_+_,
], '''
<style>
html { background: white; height: 100% }
html { height: 100% }
body { background: #f00; height: 100% }
@page {
-weasy-size: 15px;
margin: 4px 6px 7px 5px;
background: white;
@top-left-corner {
margin: 1px;