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:
commit
846515b109
26
weasyprint/calc.py
Normal file
26
weasyprint/calc.py
Normal 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.
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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':
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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':
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user