mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-05 00:21:15 +03:00
203 lines
6.8 KiB
Python
203 lines
6.8 KiB
Python
# coding: utf8
|
||
"""
|
||
weasyprint.backgrounds
|
||
----------------------
|
||
|
||
:copyright: Copyright 2011-2013 Simon Sapin and contributors, see AUTHORS.
|
||
:license: BSD, see LICENSE for details.
|
||
|
||
"""
|
||
|
||
from __future__ import division, unicode_literals
|
||
|
||
from collections import namedtuple
|
||
from itertools import cycle
|
||
|
||
from ..formatting_structure import boxes
|
||
from . import replaced
|
||
|
||
|
||
Background = namedtuple('Background', 'color, layers, image_rendering')
|
||
BackgroundLayer = namedtuple(
|
||
'BackgroundLayer',
|
||
'image, size, position, repeat, unbounded, '
|
||
'painting_area, positioning_area')
|
||
|
||
|
||
def box_rectangle(box, which_rectangle):
|
||
if which_rectangle == 'border-box':
|
||
return (
|
||
box.border_box_x(),
|
||
box.border_box_y(),
|
||
box.border_width(),
|
||
box.border_height(),
|
||
)
|
||
elif which_rectangle == 'padding-box':
|
||
return (
|
||
box.padding_box_x(),
|
||
box.padding_box_y(),
|
||
box.padding_width(),
|
||
box.padding_height(),
|
||
)
|
||
else:
|
||
assert which_rectangle == 'content-box', which_rectangle
|
||
return (
|
||
box.content_box_x(),
|
||
box.content_box_y(),
|
||
box.width,
|
||
box.height,
|
||
)
|
||
|
||
|
||
def layout_box_backgrounds(page, box, get_image_from_uri):
|
||
"""Fetch and position background images."""
|
||
for child in box.all_children():
|
||
layout_box_backgrounds(page, child, get_image_from_uri)
|
||
|
||
style = box.style
|
||
if style.visibility == 'hidden':
|
||
box.background = None
|
||
return
|
||
|
||
images = [get_image_from_uri(value) if type_ == 'url' else value
|
||
for type_, value in style.background_image]
|
||
color = style.get_color('background_color')
|
||
if color.alpha == 0 and not any(images):
|
||
box.background = None
|
||
return
|
||
|
||
box.background = Background(
|
||
color=color, image_rendering=style.image_rendering, layers=[
|
||
layout_background_layer(box, page, *layer)
|
||
for layer in zip(images, *map(cycle, [
|
||
style.background_size,
|
||
style.background_clip,
|
||
style.background_repeat,
|
||
style.background_origin,
|
||
style.background_position,
|
||
style.background_attachment]))])
|
||
|
||
|
||
def percentage(value, refer_to):
|
||
"""Return the evaluated percentage value, or the value unchanged."""
|
||
if value == 'auto':
|
||
return value
|
||
elif value.unit == 'px':
|
||
return value.value
|
||
else:
|
||
assert value.unit == '%'
|
||
return refer_to * value.value / 100
|
||
|
||
|
||
def layout_background_layer(box, page, image, size, clip, repeat, origin,
|
||
position, attachment):
|
||
|
||
if box is not page:
|
||
painting_area = box_rectangle(box, clip)
|
||
else:
|
||
painting_area = 0, 0, page.margin_width(), page.margin_height()
|
||
|
||
if (image is None or image.intrinsic_width == 0
|
||
or image.intrinsic_height == 0):
|
||
return BackgroundLayer(
|
||
image=None, unbounded=(box is page), painting_area=painting_area,
|
||
size='unused', position='unused', repeat='unused',
|
||
positioning_area='unused')
|
||
|
||
if attachment == 'fixed':
|
||
# Initial containing block
|
||
positioning_area = box_rectangle(page, 'content-box')
|
||
else:
|
||
positioning_area = box_rectangle(box, origin)
|
||
|
||
positioning_x, positioning_y, positioning_width, positioning_height = (
|
||
positioning_area)
|
||
painting_x, painting_y, painting_width, painting_height = (
|
||
painting_area)
|
||
intrinsic_width = image.intrinsic_width
|
||
intrinsic_height = image.intrinsic_height
|
||
|
||
if size == 'cover':
|
||
image_width, image_height = replaced.cover_constraint_image_sizing(
|
||
positioning_width, positioning_height, image.intrinsic_ratio)
|
||
elif size == 'contain':
|
||
image_width, image_height = replaced.contain_constraint_image_sizing(
|
||
positioning_width, positioning_height, image.intrinsic_ratio)
|
||
else:
|
||
size_width, size_height = size
|
||
image_width, image_height = replaced.default_image_sizing(
|
||
image.intrinsic_width, image.intrinsic_height,
|
||
image.intrinsic_ratio,
|
||
percentage(size_width, positioning_width),
|
||
percentage(size_height, positioning_height),
|
||
positioning_width, positioning_height)
|
||
|
||
origin_x, position_x, origin_y, position_y = position
|
||
ref_x = positioning_width - image_width
|
||
ref_y = positioning_height - image_height
|
||
position_x = percentage(position_x, ref_x)
|
||
position_y = percentage(position_y, ref_y)
|
||
if origin_x == 'right':
|
||
position_x = ref_x - position_x
|
||
if origin_y == 'bottom':
|
||
position_y = ref_y - position_y
|
||
|
||
repeat_x, repeat_y = repeat
|
||
|
||
if repeat_x == 'round':
|
||
n_repeats = max(1, round(positioning_width / image_width))
|
||
new_width = positioning_width / n_repeats
|
||
position_x = 0 # Ignore background-position for this dimension
|
||
if repeat_y != 'round' and size[1] == 'auto':
|
||
image_height *= new_width / image_width
|
||
image_width = new_width
|
||
if repeat_y == 'round':
|
||
n_repeats = max(1, round(positioning_height / image_height))
|
||
new_height = positioning_height / n_repeats
|
||
position_y = 0 # Ignore background-position for this dimension
|
||
if repeat_x != 'round' and size[0] == 'auto':
|
||
image_width *= new_height / image_height
|
||
image_height = new_height
|
||
|
||
return BackgroundLayer(
|
||
image=image,
|
||
size=(image_width, image_height),
|
||
position=(position_x, position_y),
|
||
repeat=repeat,
|
||
unbounded=(box is page),
|
||
painting_area=painting_area,
|
||
positioning_area=positioning_area)
|
||
|
||
|
||
def set_canvas_background(page):
|
||
"""Set a ``canvas_background`` attribute on the PageBox,
|
||
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
|
||
|
||
"""
|
||
assert not isinstance(page.children[0], boxes.MarginBox)
|
||
root_box = page.children[0]
|
||
chosen_box = root_box
|
||
if root_box.element_tag.lower() == 'html' and root_box.background is None:
|
||
for child in root_box.children:
|
||
if child.element_tag.lower() == 'body':
|
||
chosen_box = child
|
||
break
|
||
|
||
if chosen_box.background:
|
||
page.canvas_background = chosen_box.background._replace(
|
||
# TODO: shouldn’t background-clip be considered here?
|
||
layers=[
|
||
l._replace(painting_area=box_rectangle(page, 'padding-box'))
|
||
for l in chosen_box.background.layers])
|
||
chosen_box.background = None
|
||
else:
|
||
page.canvas_background = None
|
||
|
||
|
||
def layout_backgrounds(page, get_image_from_uri):
|
||
layout_box_backgrounds(page, page, get_image_from_uri)
|
||
set_canvas_background(page)
|