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

Merge branch 'master' of github.com:Kozea/WeasyPrint

This commit is contained in:
Guillaume Ayoub 2019-06-02 17:55:51 +02:00
commit 846515b109
11 changed files with 146 additions and 76 deletions

26
weasyprint/calc.py Normal file
View File

@ -0,0 +1,26 @@
"""
weasyprint.calc
-----------------------------
Resolve percentages into fixed values.
:copyright: Copyright 2011-2019 Simon Sapin and contributors, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
def percentage(value, refer_to):
"""Return the percentage of the reference value, or the value unchanged.
``refer_to`` is the length for 100%. If ``refer_to`` is not a number, it
just replaces percentages.
"""
if value is None or value == 'auto':
return value
elif value.unit == 'px':
return value.value
else:
assert value.unit == '%'
return refer_to * value.value / 100.

View File

@ -273,7 +273,7 @@ def background_image(computer, name, values):
length(computer, name, pos) if pos is not None else None
for pos in value.stop_positions)
if type_ == 'radial-gradient':
value.center, = background_position(
value.center, = compute_position(
computer, name, (value.center,))
if value.size_type == 'explicit':
value.size = length_or_percentage_tuple(
@ -282,7 +282,8 @@ def background_image(computer, name, values):
@register_computer('background-position')
def background_position(computer, name, values):
@register_computer('object-position')
def compute_position(computer, name, values):
"""Compute lengths in background-position."""
return tuple(
(origin_x, length(computer, name, pos_x),

View File

@ -139,6 +139,10 @@ INITIAL_VALUES = {
# Images 3/4 (CR/WD): https://www.w3.org/TR/css4-images/
'image_resolution': 1, # dppx
'image_rendering': 'auto',
# https://drafts.csswg.org/css-images-3/
'object_fit': 'fill',
'object_position': (('left', Dimension(50, '%'),
'top', Dimension(50, '%')),),
# Paged Media 3 (WD): https://www.w3.org/TR/css-page-3/
'size': None, # set to A4 in computed_values

View File

@ -266,10 +266,11 @@ def parse_2d_position(tokens):
BACKGROUND_POSITION_PERCENTAGES[keyword_1])
def parse_background_position(tokens):
"""Parse background position.
def parse_position(tokens):
"""Parse background-position and object-position.
See http://dev.w3.org/csswg/css3-background/#the-background-position
https://drafts.csswg.org/css-images-3/#propdef-object-position
"""
result = parse_2d_position(tokens)
@ -324,7 +325,7 @@ def parse_radial_gradient_parameters(arguments):
token = stack.pop()
keyword = get_keyword(token)
if keyword == 'at':
position = parse_background_position(stack[::-1])
position = parse_position(stack[::-1])
if position is None:
return
break

View File

@ -19,8 +19,8 @@ from ..utils import (
InvalidValues, check_var_function, comma_separated_list, get_angle,
get_content_list, get_content_list_token, get_custom_ident, get_image,
get_keyword, get_length, get_resolution, get_single_keyword, get_url,
parse_2d_position, parse_background_position, parse_function,
single_keyword, single_token)
parse_2d_position, parse_function, parse_position, single_keyword,
single_token)
PREFIX = '-weasy-'
PROPRIETARY = set()
@ -200,7 +200,14 @@ def transform_origin(tokens):
@comma_separated_list
def background_position(tokens):
"""``background-position`` property validation."""
return parse_background_position(tokens)
return parse_position(tokens)
@property()
@comma_separated_list
def object_position(tokens):
"""``object-position`` property validation."""
return parse_position(tokens)
@property()
@ -812,6 +819,15 @@ def font_weight(token):
return token.int_value
@property()
@single_keyword
def object_fit(keyword):
# TODO: Figure out what the spec means by "'scale-down' flag".
# As of this writing, neither Firefox nor chrome support
# anything other than a single keyword as is done here.
return keyword in ('fill', 'contain', 'cover', 'none', 'scale-down')
@property(unstable=True)
@single_token
def image_resolution(token):

View File

@ -16,7 +16,7 @@ import warnings
import cairocffi as cairo
from . import CSS
from . import CSS, calc
from .css import get_all_computed_styles
from .css.targets import TargetCollector
from .draw import draw_page, stacked
@ -26,7 +26,6 @@ from .formatting_structure.build import build_formatting_structure
from .html import W3C_DATE_RE
from .images import get_image_from_uri as original_get_image_from_uri
from .layout import layout_document
from .layout.backgrounds import percentage
from .logger import LOGGER, PROGRESS_LOGGER
from .pdf import write_pdf_metadata
@ -53,8 +52,10 @@ def _get_matrix(box):
border_width = box.border_width()
border_height = box.border_height()
origin_x, origin_y = box.style['transform_origin']
origin_x = box.border_box_x() + percentage(origin_x, border_width)
origin_y = box.border_box_y() + percentage(origin_y, border_height)
offset_x = calc.percentage(origin_x, border_width)
offset_y = calc.percentage(origin_y, border_height)
origin_x = box.border_box_x() + offset_x
origin_y = box.border_box_y() + offset_y
matrix = cairo.Matrix()
matrix.translate(origin_x, origin_y)
@ -66,8 +67,8 @@ def _get_matrix(box):
elif name == 'translate':
translate_x, translate_y = args
matrix.translate(
percentage(translate_x, border_width),
percentage(translate_y, border_height),
calc.percentage(translate_x, border_width),
calc.percentage(translate_y, border_height),
)
else:
if name == 'skewx':

View File

@ -17,6 +17,7 @@ import cairocffi as cairo
from .formatting_structure import boxes
from .images import SVGImage
from .layout import replaced
from .layout.backgrounds import BackgroundLayer
from .stacking import StackingContext
from .text import show_first_line
@ -973,12 +974,14 @@ def draw_replacedbox(context, box):
if box.style['visibility'] != 'visible' or not box.width or not box.height:
return
draw_width, draw_height, draw_x, draw_y = replaced.replacedbox_layout(box)
with stacked(context):
rounded_box_path(context, box.rounded_content_box())
context.clip()
context.translate(box.content_box_x(), box.content_box_y())
context.translate(draw_x, draw_y)
box.replacement.draw(
context, box.width, box.height, box.style['image_rendering'])
context, draw_width, draw_height, box.style['image_rendering'])
def draw_inline_level(context, page, box, enable_hinting):

View File

@ -17,6 +17,7 @@ import cairocffi
import cairosvg.parser
import cairosvg.surface
from . import calc
from .logger import LOGGER
from .urls import URLFetchingError, fetch
@ -68,16 +69,23 @@ class RasterImage(object):
self._intrinsic_height / image_resolution)
def draw(self, context, concrete_width, concrete_height, image_rendering):
if concrete_width > 0 and concrete_height > 0 and \
self._intrinsic_width > 0 and self._intrinsic_height > 0:
# Use the real intrinsic size here,
# not affected by 'image-resolution'.
context.scale(concrete_width / self._intrinsic_width,
concrete_height / self._intrinsic_height)
context.set_source_surface(self.image_surface)
context.get_source().set_filter(
IMAGE_RENDERING_TO_FILTER[image_rendering])
context.paint()
has_size = (
concrete_width > 0
and concrete_height > 0
and self._intrinsic_width > 0
and self._intrinsic_height > 0
)
if not has_size:
return
# Use the real intrinsic size here,
# not affected by 'image-resolution'.
context.scale(concrete_width / self._intrinsic_width,
concrete_height / self._intrinsic_height)
context.set_source_surface(self.image_surface)
context.get_source().set_filter(
IMAGE_RENDERING_TO_FILTER[image_rendering])
context.paint()
class ScaledSVGSurface(cairosvg.surface.SVGSurface):
@ -226,17 +234,6 @@ def get_image_from_uri(cache, url_fetcher, url, forced_mime_type=None):
return image
def percentage(value, refer_to):
"""Return the evaluated percentage value, or the value unchanged."""
if value is None:
return value
elif value.unit == 'px':
return value.value
else:
assert value.unit == '%'
return refer_to * value.value / 100
def process_color_stops(gradient_line_size, positions):
"""
Gradient line size: distance between the starting point and ending point.
@ -248,7 +245,7 @@ def process_color_stops(gradient_line_size, positions):
Return processed color stops, as a list of floats in px.
"""
positions = [percentage(position, gradient_line_size)
positions = [calc.percentage(position, gradient_line_size)
for position in positions]
# First and last default to 100%
if positions[0] is None:
@ -437,8 +434,8 @@ class RadialGradient(Gradient):
if len(self.colors) == 1:
return 1, 'solid', self.colors[0], [], []
origin_x, center_x, origin_y, center_y = self.center
center_x = percentage(center_x, width)
center_y = percentage(center_y, height)
center_x = calc.percentage(center_x, width)
center_y = calc.percentage(center_y, height)
if origin_x == 'right':
center_x = width - center_x
if origin_y == 'bottom':
@ -505,7 +502,9 @@ class RadialGradient(Gradient):
def _resolve_size(self, width, height, center_x, center_y):
if self.size_type == 'explicit':
size_x, size_y = self.size
return percentage(size_x, width), percentage(size_y, height)
size_x = calc.percentage(size_x, width)
size_y = calc.percentage(size_y, height)
return size_x, size_y
left = abs(center_x)
right = abs(width - center_x)
top = abs(center_y)

View File

@ -10,6 +10,7 @@
from collections import namedtuple
from itertools import cycle
from .. import calc
from ..formatting_structure import boxes
from . import replaced
from .percentages import resolve_radii_percentages
@ -83,17 +84,6 @@ def layout_box_backgrounds(page, box, get_image_from_uri):
color=color, image_rendering=style['image_rendering'], layers=layers)
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, resolution, image, size, clip, repeat,
origin, position, attachment):
@ -177,15 +167,15 @@ def layout_background_layer(box, page, resolution, image, size, clip, repeat,
resolution, box.style['font_size'])
image_width, image_height = replaced.default_image_sizing(
iwidth, iheight, image.intrinsic_ratio,
percentage(size_width, positioning_width),
percentage(size_height, positioning_height),
calc.percentage(size_width, positioning_width),
calc.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)
position_x = calc.percentage(position_x, ref_x)
position_y = calc.percentage(position_y, ref_y)
if origin_x == 'right':
position_x = ref_x - position_x
if origin_y == 'bottom':

View File

@ -9,26 +9,10 @@
"""
from .. import calc
from ..formatting_structure import boxes
def _percentage(value, refer_to):
"""Get the value corresponding to the value/percentage and the reference
``refer_to`` is the length for 100%. If ``refer_to`` is not a number, it
just replaces percentages.
"""
if value == 'auto':
result = value
elif value.unit == 'px':
result = value.value
else:
assert value.unit == '%'
result = value.value * refer_to / 100.
return result
def resolve_one_percentage(box, property_name, refer_to,
main_flex_direction=None):
"""Set a used length value from a computed length value.
@ -40,7 +24,7 @@ def resolve_one_percentage(box, property_name, refer_to,
# box.style has computed values
value = box.style[property_name]
# box attributes are used values
percentage = _percentage(value, refer_to)
percentage = calc.percentage(value, refer_to)
setattr(box, property_name, percentage)
if property_name in ('min_width', 'min_height') and percentage == 'auto':
if (main_flex_direction is None or
@ -146,6 +130,6 @@ def resolve_radii_percentages(box):
for corner in corners:
property_name = 'border_%s_radius' % corner
rx, ry = box.style[property_name]
rx = _percentage(rx, box.border_width())
ry = _percentage(ry, box.border_height())
rx = calc.percentage(rx, box.border_width())
ry = calc.percentage(ry, box.border_height())
setattr(box, property_name, (rx, ry))

View File

@ -10,6 +10,8 @@
"""
from .. import calc
def image_marker_layout(box):
"""Layout the :class:`boxes.ImageMarkerBox` ``box``.
@ -97,3 +99,46 @@ def _constraint_image_sizing(
return constraint_height * intrinsic_ratio, constraint_height
else:
return constraint_width, constraint_width / intrinsic_ratio
def replacedbox_layout(box):
# TODO: respect box-sizing ?
object_fit = box.style['object_fit']
position = box.style['object_position']
image = box.replacement
iwidth, iheight = image.get_intrinsic_size(
box.style['image_resolution'], box.style['font_size'])
if object_fit == 'fill':
draw_width, draw_height = box.width, box.height
else:
if object_fit == 'contain' or object_fit == 'scale-down':
draw_width, draw_height = contain_constraint_image_sizing(
box.width, box.height, box.replacement.intrinsic_ratio)
elif object_fit == 'cover':
draw_width, draw_height = cover_constraint_image_sizing(
box.width, box.height, box.replacement.intrinsic_ratio)
else:
assert object_fit == 'none', object_fit
draw_width, draw_height = iwidth, iheight
if object_fit == 'scale-down':
draw_width = min(draw_width, iwidth)
draw_height = min(draw_height, iheight)
origin_x, position_x, origin_y, position_y = position[0]
ref_x = box.width - draw_width
ref_y = box.height - draw_height
position_x = calc.percentage(position_x, ref_x)
position_y = calc.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
position_x += box.content_box_x()
position_y += box.content_box_y()
return draw_width, draw_height, position_x, position_y