2012-09-13 13:19:40 +04:00
|
|
|
|
"""
|
2012-09-16 12:25:02 +04:00
|
|
|
|
weasyprint.backgrounds
|
2012-09-13 13:19:40 +04:00
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from collections import namedtuple
|
2013-04-03 19:26:46 +04:00
|
|
|
|
from itertools import cycle
|
2012-09-13 13:19:40 +04:00
|
|
|
|
|
2019-03-10 21:34:16 +03:00
|
|
|
|
from ..formatting_structure import boxes
|
2013-04-03 20:15:32 +04:00
|
|
|
|
from . import replaced
|
2019-06-02 19:06:25 +03:00
|
|
|
|
from .percentages import percentage, resolve_radii_percentages
|
2012-09-13 13:19:40 +04:00
|
|
|
|
|
2013-04-03 19:26:46 +04:00
|
|
|
|
Background = namedtuple('Background', 'color, layers, image_rendering')
|
2013-03-31 01:49:47 +04:00
|
|
|
|
BackgroundLayer = namedtuple(
|
|
|
|
|
'BackgroundLayer',
|
|
|
|
|
'image, size, position, repeat, unbounded, '
|
2016-01-16 19:33:52 +03:00
|
|
|
|
'painting_area, positioning_area, clipped_boxes')
|
2012-09-13 13:19:40 +04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2020-01-26 00:33:47 +03:00
|
|
|
|
def layout_box_backgrounds(page, box, get_image_from_uri, layout_children=True,
|
|
|
|
|
style=None):
|
2012-09-13 13:19:40 +04:00
|
|
|
|
"""Fetch and position background images."""
|
2018-01-13 19:41:08 +03:00
|
|
|
|
from ..draw import get_color
|
|
|
|
|
|
2013-06-06 17:58:00 +04:00
|
|
|
|
# Resolve percentages in border-radius properties
|
2015-01-29 14:23:43 +03:00
|
|
|
|
resolve_radii_percentages(box)
|
2013-06-06 17:58:00 +04:00
|
|
|
|
|
2020-01-26 00:33:47 +03:00
|
|
|
|
if layout_children:
|
|
|
|
|
for child in box.all_children():
|
|
|
|
|
layout_box_backgrounds(page, child, get_image_from_uri)
|
|
|
|
|
|
|
|
|
|
if style is None:
|
|
|
|
|
style = box.style
|
2012-09-13 13:19:40 +04:00
|
|
|
|
|
2018-01-13 19:05:23 +03:00
|
|
|
|
if style['visibility'] == 'hidden':
|
2012-09-13 13:19:40 +04:00
|
|
|
|
box.background = None
|
2017-09-05 16:44:50 +03:00
|
|
|
|
if page != box: # Pages need a background for bleed box
|
|
|
|
|
return
|
2012-09-13 13:19:40 +04:00
|
|
|
|
|
2021-04-30 19:13:08 +03:00
|
|
|
|
images = [get_image_from_uri(url=value) if type_ == 'url' else value
|
2018-01-13 19:05:23 +03:00
|
|
|
|
for type_, value in style['background_image']]
|
2018-01-13 19:41:08 +03:00
|
|
|
|
color = get_color(style, 'background_color')
|
2013-03-19 21:29:58 +04:00
|
|
|
|
if color.alpha == 0 and not any(images):
|
2012-09-13 13:19:40 +04:00
|
|
|
|
box.background = None
|
2017-09-05 16:44:50 +03:00
|
|
|
|
if page != box: # Pages need a background for bleed box
|
|
|
|
|
return
|
2012-09-13 13:19:40 +04:00
|
|
|
|
|
2018-01-13 19:05:23 +03:00
|
|
|
|
layers = [
|
|
|
|
|
layout_background_layer(box, page, style['image_resolution'], *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']]))]
|
2013-04-03 19:26:46 +04:00
|
|
|
|
box.background = Background(
|
2018-01-13 19:05:23 +03:00
|
|
|
|
color=color, image_rendering=style['image_rendering'], layers=layers)
|
2013-04-03 19:26:46 +04:00
|
|
|
|
|
|
|
|
|
|
2013-07-26 19:26:30 +04:00
|
|
|
|
def layout_background_layer(box, page, resolution, image, size, clip, repeat,
|
|
|
|
|
origin, position, attachment):
|
2013-04-03 19:46:14 +04:00
|
|
|
|
|
2016-01-16 19:33:52 +03:00
|
|
|
|
# TODO: respect box-sizing for table cells?
|
|
|
|
|
clipped_boxes = []
|
|
|
|
|
painting_area = 0, 0, 0, 0
|
|
|
|
|
if box is page:
|
|
|
|
|
painting_area = 0, 0, page.margin_width(), page.margin_height()
|
|
|
|
|
# XXX: how does border-radius work on pages?
|
|
|
|
|
clipped_boxes = [box.rounded_border_box()]
|
|
|
|
|
elif isinstance(box, boxes.TableRowGroupBox):
|
|
|
|
|
clipped_boxes = []
|
|
|
|
|
total_height = 0
|
|
|
|
|
for row in box.children:
|
|
|
|
|
if row.children:
|
|
|
|
|
clipped_boxes += [
|
|
|
|
|
cell.rounded_border_box() for cell in row.children]
|
|
|
|
|
total_height = max(total_height, max(
|
|
|
|
|
cell.border_box_y() + cell.border_height()
|
|
|
|
|
for cell in row.children))
|
|
|
|
|
painting_area = [
|
|
|
|
|
box.border_box_x(), box.border_box_y(),
|
|
|
|
|
box.border_box_x() + box.border_width(), total_height]
|
|
|
|
|
elif isinstance(box, boxes.TableRowBox):
|
|
|
|
|
if box.children:
|
|
|
|
|
clipped_boxes = [
|
|
|
|
|
cell.rounded_border_box() for cell in box.children]
|
|
|
|
|
height = max(
|
|
|
|
|
cell.border_height() for cell in box.children)
|
|
|
|
|
painting_area = [
|
|
|
|
|
box.border_box_x(), box.border_box_y(),
|
|
|
|
|
box.border_box_x() + box.border_width(),
|
|
|
|
|
box.border_box_y() + height]
|
|
|
|
|
elif isinstance(box, (boxes.TableColumnGroupBox, boxes.TableColumnBox)):
|
|
|
|
|
cells = box.get_cells()
|
|
|
|
|
if cells:
|
|
|
|
|
clipped_boxes = [cell.rounded_border_box() for cell in cells]
|
2020-04-08 16:31:24 +03:00
|
|
|
|
min_x = min(cell.border_box_x() for cell in cells)
|
2016-01-18 21:27:50 +03:00
|
|
|
|
max_x = max(
|
|
|
|
|
cell.border_box_x() + cell.border_width()
|
2016-01-16 19:33:52 +03:00
|
|
|
|
for cell in cells)
|
|
|
|
|
painting_area = [
|
2020-04-08 16:31:24 +03:00
|
|
|
|
min_x, box.border_box_y(), max_x - min_x,
|
2016-01-18 21:27:50 +03:00
|
|
|
|
box.border_box_y() + box.border_height()]
|
2016-01-16 19:33:52 +03:00
|
|
|
|
else:
|
2013-04-03 19:46:14 +04:00
|
|
|
|
painting_area = box_rectangle(box, clip)
|
2013-12-10 16:33:17 +04:00
|
|
|
|
if clip == 'border-box':
|
2016-01-16 19:33:52 +03:00
|
|
|
|
clipped_boxes = [box.rounded_border_box()]
|
2013-12-10 16:33:17 +04:00
|
|
|
|
elif clip == 'padding-box':
|
2016-01-16 19:33:52 +03:00
|
|
|
|
clipped_boxes = [box.rounded_padding_box()]
|
2013-12-10 16:33:17 +04:00
|
|
|
|
else:
|
|
|
|
|
assert clip == 'content-box', clip
|
2016-01-16 19:33:52 +03:00
|
|
|
|
clipped_boxes = [box.rounded_content_box()]
|
2013-04-03 19:46:14 +04:00
|
|
|
|
|
2016-02-26 15:58:47 +03:00
|
|
|
|
if image is None or 0 in image.get_intrinsic_size(1, 1):
|
2013-04-03 19:46:14 +04:00
|
|
|
|
return BackgroundLayer(
|
2020-01-26 00:33:47 +03:00
|
|
|
|
image=None, unbounded=False, painting_area=painting_area,
|
2013-04-03 19:46:14 +04:00
|
|
|
|
size='unused', position='unused', repeat='unused',
|
2016-01-16 19:33:52 +03:00
|
|
|
|
positioning_area='unused', clipped_boxes=clipped_boxes)
|
2013-04-03 19:46:14 +04:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
2013-04-03 20:15:32 +04:00
|
|
|
|
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)
|
2013-04-03 19:46:14 +04:00
|
|
|
|
else:
|
2013-04-03 20:15:32 +04:00
|
|
|
|
size_width, size_height = size
|
2016-02-26 15:58:47 +03:00
|
|
|
|
iwidth, iheight = image.get_intrinsic_size(
|
2018-01-13 19:05:23 +03:00
|
|
|
|
resolution, box.style['font_size'])
|
2013-04-03 20:15:32 +04:00
|
|
|
|
image_width, image_height = replaced.default_image_sizing(
|
2013-07-26 19:26:30 +04:00
|
|
|
|
iwidth, iheight, image.intrinsic_ratio,
|
2019-06-02 19:06:25 +03:00
|
|
|
|
percentage(size_width, positioning_width),
|
|
|
|
|
percentage(size_height, positioning_height),
|
2013-04-03 20:15:32 +04:00
|
|
|
|
positioning_width, positioning_height)
|
2013-04-03 19:46:14 +04:00
|
|
|
|
|
|
|
|
|
origin_x, position_x, origin_y, position_y = position
|
|
|
|
|
ref_x = positioning_width - image_width
|
|
|
|
|
ref_y = positioning_height - image_height
|
2019-06-02 19:06:25 +03:00
|
|
|
|
position_x = percentage(position_x, ref_x)
|
|
|
|
|
position_y = percentage(position_y, ref_y)
|
2013-04-03 19:46:14 +04:00
|
|
|
|
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
|
|
|
|
|
|
2013-04-03 19:26:46 +04:00
|
|
|
|
return BackgroundLayer(
|
|
|
|
|
image=image,
|
2013-04-03 19:46:14 +04:00
|
|
|
|
size=(image_width, image_height),
|
|
|
|
|
position=(position_x, position_y),
|
2013-04-03 19:26:46 +04:00
|
|
|
|
repeat=repeat,
|
2020-01-26 00:33:47 +03:00
|
|
|
|
unbounded=False,
|
2013-04-03 19:46:14 +04:00
|
|
|
|
painting_area=painting_area,
|
2013-06-05 20:56:57 +04:00
|
|
|
|
positioning_area=positioning_area,
|
2016-01-16 19:33:52 +03:00
|
|
|
|
clipped_boxes=clipped_boxes)
|
2012-09-13 13:19:40 +04:00
|
|
|
|
|
|
|
|
|
|
2020-01-26 00:33:47 +03:00
|
|
|
|
def set_canvas_background(page, get_image_from_uri):
|
|
|
|
|
"""Set a ``background`` attribute on the PageBox,
|
2012-09-13 13:19:40 +04:00
|
|
|
|
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
|
2013-03-19 21:29:58 +04:00
|
|
|
|
if root_box.element_tag.lower() == 'html' and root_box.background is None:
|
2012-09-13 13:19:40 +04:00
|
|
|
|
for child in root_box.children:
|
|
|
|
|
if child.element_tag.lower() == 'body':
|
|
|
|
|
chosen_box = child
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if chosen_box.background:
|
2013-06-06 17:58:00 +04:00
|
|
|
|
painting_area = box_rectangle(page, 'padding-box')
|
2020-01-26 00:33:47 +03:00
|
|
|
|
original_background = page.background
|
|
|
|
|
layout_box_backgrounds(
|
|
|
|
|
page, page, get_image_from_uri, layout_children=False,
|
|
|
|
|
style=chosen_box.style)
|
|
|
|
|
page.canvas_background = page.background._replace(
|
2013-03-19 21:29:58 +04:00
|
|
|
|
# TODO: shouldn’t background-clip be considered here?
|
2013-03-31 01:49:47 +04:00
|
|
|
|
layers=[
|
2020-01-25 20:19:56 +03:00
|
|
|
|
layer._replace(painting_area=painting_area)
|
2020-01-26 00:33:47 +03:00
|
|
|
|
for layer in page.background.layers])
|
|
|
|
|
page.background = original_background
|
2012-09-13 13:19:40 +04:00
|
|
|
|
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)
|
2020-01-26 00:33:47 +03:00
|
|
|
|
set_canvas_background(page, get_image_from_uri)
|