mirror of
https://github.com/Kozea/WeasyPrint.git
synced 2024-10-05 08:27:22 +03:00
Merge branch 'master' of github.com:Kozea/WeasyPrint
This commit is contained in:
commit
86eda6fa6d
@ -126,7 +126,7 @@ as invalid with ``*``.
|
||||
.. _hyphenation:
|
||||
|
||||
CSS Text: hyphenation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
The experimental_ ``-weasy-hyphens`` property controls hyphenation
|
||||
@ -185,8 +185,11 @@ The following features are supported:
|
||||
* `CSS Transforms`_ (2D only)
|
||||
* The background part of `CSS Backgrounds and Borders Level 3`_,
|
||||
including multiple background layers per element/box.
|
||||
* ``linear-gradient()`` and ``radial-gradient()`` (as background images)
|
||||
from `CSS Images Level 3`_
|
||||
* ``linear-gradient()`` and ``radial-gradient()`` (as background images),
|
||||
from `CSS Images Level 3`_.
|
||||
* The ``image-resolution`` property from `CSS Images Level 3`_.
|
||||
The ``snap`` and ``from-image`` values are not supported yet,
|
||||
so the property only takes a single ``<resolution>`` value.
|
||||
* The ``box-sizing`` property from `CSS Basic User Interface`_:
|
||||
|
||||
.. _CSS Colors Level 3: http://www.w3.org/TR/css3-color/
|
||||
|
@ -138,6 +138,9 @@ INITIAL_VALUES = {
|
||||
# http://www.w3.org/TR/SVG/painting.html#ImageRenderingProperty
|
||||
'image_rendering': 'auto',
|
||||
|
||||
# http://www.w3.org/TR/css3-images/#the-image-resolution
|
||||
'image_resolution': 1, # dppx
|
||||
|
||||
# Proprietary
|
||||
'anchor': None, # computed value of 'none'
|
||||
'link': None, # computed value of 'none'
|
||||
@ -233,6 +236,7 @@ INHERITED = set("""
|
||||
hyphenate_limit_chars
|
||||
hyphenate_limit_zone
|
||||
image_rendering
|
||||
image_resolution
|
||||
lang
|
||||
link
|
||||
""".split())
|
||||
|
@ -195,12 +195,27 @@ ANGLE_TO_RADIANS = {
|
||||
|
||||
|
||||
def get_angle(token):
|
||||
"""Return whether the argument is an angle token."""
|
||||
"""Return the value in radians of an <angle> token, or None."""
|
||||
factor = ANGLE_TO_RADIANS.get(token.unit)
|
||||
if factor is not None:
|
||||
return token.value * factor
|
||||
|
||||
|
||||
# http://dev.w3.org/csswg/css-values/#resolution
|
||||
RESOLUTION_TO_DPPX = {
|
||||
'dppx': 1,
|
||||
'dpi': 1 / computed_values.LENGTHS_TO_PIXELS['in'],
|
||||
'dpcm': 1 / computed_values.LENGTHS_TO_PIXELS['cm'],
|
||||
}
|
||||
|
||||
|
||||
def get_resolution(token):
|
||||
"""Return the value in dppx of a <resolution> token, or None."""
|
||||
factor = RESOLUTION_TO_DPPX.get(token.unit)
|
||||
if factor is not None:
|
||||
return token.value * factor
|
||||
|
||||
|
||||
def safe_urljoin(base_url, url):
|
||||
if url_is_absolute(url):
|
||||
return iri_to_uri(url)
|
||||
@ -842,6 +857,13 @@ def font_weight(token):
|
||||
return token.value
|
||||
|
||||
|
||||
@validator()
|
||||
@single_token
|
||||
def image_resolution(token):
|
||||
# TODO: support 'snap' and 'from-image'
|
||||
return get_resolution(token)
|
||||
|
||||
|
||||
@validator('letter-spacing')
|
||||
@validator('word-spacing')
|
||||
@single_token
|
||||
|
@ -61,16 +61,23 @@ class ImageLoadingError(ValueError):
|
||||
class RasterImage(object):
|
||||
def __init__(self, image_surface):
|
||||
self.image_surface = image_surface
|
||||
self.intrinsic_width = image_surface.get_width()
|
||||
self.intrinsic_height = image_surface.get_height()
|
||||
self._intrinsic_width = image_surface.get_width()
|
||||
self._intrinsic_height = image_surface.get_height()
|
||||
self.intrinsic_ratio = (
|
||||
self.intrinsic_width / self.intrinsic_height
|
||||
if self.intrinsic_height != 0 else float('inf'))
|
||||
self._intrinsic_width / self._intrinsic_height
|
||||
if self._intrinsic_height != 0 else float('inf'))
|
||||
|
||||
def get_intrinsic_size(self, image_resolution):
|
||||
# Raster images are affected by the 'image-resolution' property.
|
||||
return (self._intrinsic_width / image_resolution,
|
||||
self._intrinsic_height / image_resolution)
|
||||
|
||||
def draw(self, context, concrete_width, concrete_height, image_rendering):
|
||||
if self.intrinsic_width > 0 and self.intrinsic_height > 0:
|
||||
context.scale(concrete_width / self.intrinsic_width,
|
||||
concrete_height / self.intrinsic_height)
|
||||
if 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])
|
||||
@ -106,9 +113,13 @@ class SVGImage(object):
|
||||
if not (svg.width > 0 and svg.height > 0):
|
||||
raise ImageLoadingError(
|
||||
'SVG images without an intrinsic size are not supported.')
|
||||
self.intrinsic_width = svg.width
|
||||
self.intrinsic_height = svg.height
|
||||
self.intrinsic_ratio = self.intrinsic_width / self.intrinsic_height
|
||||
self._intrinsic_width = svg.width
|
||||
self._intrinsic_height = svg.height
|
||||
self.intrinsic_ratio = self._intrinsic_width / self._intrinsic_height
|
||||
|
||||
def get_intrinsic_size(self, _image_resolution):
|
||||
# Vector images are affected by the 'image-resolution' property.
|
||||
return self._intrinsic_width, self._intrinsic_height
|
||||
|
||||
def _render(self):
|
||||
# Draw to a cairo surface but do not write to a file.
|
||||
@ -271,10 +282,6 @@ PATTERN_TYPES = dict(
|
||||
|
||||
|
||||
class Gradient(object):
|
||||
intrinsic_width = None
|
||||
intrinsic_height = None
|
||||
intrinsic_ratio = None
|
||||
|
||||
def __init__(self, color_stops, repeating):
|
||||
assert color_stops
|
||||
#: List of (r, g, b, a), list of Dimension
|
||||
@ -283,6 +290,12 @@ class Gradient(object):
|
||||
#: bool
|
||||
self.repeating = repeating
|
||||
|
||||
def get_intrinsic_size(self, _image_resolution):
|
||||
# Raster images are affected by the 'image-resolution' property.
|
||||
return None, None
|
||||
|
||||
intrinsic_ratio = None
|
||||
|
||||
def draw(self, context, concrete_width, concrete_height, _image_rendering):
|
||||
scale_y, type_, init, stop_positions, stop_colors = self.layout(
|
||||
concrete_width, concrete_height, context.user_to_device_distance)
|
||||
|
@ -72,7 +72,7 @@ def layout_box_backgrounds(page, box, get_image_from_uri):
|
||||
|
||||
box.background = Background(
|
||||
color=color, image_rendering=style.image_rendering, layers=[
|
||||
layout_background_layer(box, page, *layer)
|
||||
layout_background_layer(box, page, style.image_resolution, *layer)
|
||||
for layer in zip(images, *map(cycle, [
|
||||
style.background_size,
|
||||
style.background_clip,
|
||||
@ -93,8 +93,8 @@ def percentage(value, refer_to):
|
||||
return refer_to * value.value / 100
|
||||
|
||||
|
||||
def layout_background_layer(box, page, image, size, clip, repeat, origin,
|
||||
position, attachment):
|
||||
def layout_background_layer(box, page, resolution, image, size, clip, repeat,
|
||||
origin, position, attachment):
|
||||
|
||||
if box is not page:
|
||||
painting_area = box_rectangle(box, clip)
|
||||
@ -110,8 +110,7 @@ def layout_background_layer(box, page, image, size, clip, repeat, origin,
|
||||
# XXX: how does border-radius work on pages?
|
||||
rounded_box = box.rounded_border_box()
|
||||
|
||||
if (image is None or image.intrinsic_width == 0
|
||||
or image.intrinsic_height == 0):
|
||||
if image is None or 0 in image.get_intrinsic_size(1):
|
||||
return BackgroundLayer(
|
||||
image=None, unbounded=(box is page), painting_area=painting_area,
|
||||
size='unused', position='unused', repeat='unused',
|
||||
@ -136,9 +135,9 @@ def layout_background_layer(box, page, image, size, clip, repeat, origin,
|
||||
positioning_width, positioning_height, image.intrinsic_ratio)
|
||||
else:
|
||||
size_width, size_height = size
|
||||
iwidth, iheight = image.get_intrinsic_size(resolution)
|
||||
image_width, image_height = replaced.default_image_sizing(
|
||||
image.intrinsic_width, image.intrinsic_height,
|
||||
image.intrinsic_ratio,
|
||||
iwidth, iheight, image.intrinsic_ratio,
|
||||
percentage(size_width, positioning_width),
|
||||
percentage(size_height, positioning_height),
|
||||
positioning_width, positioning_height)
|
||||
|
@ -17,7 +17,8 @@ from .float import avoid_collisions, float_layout
|
||||
from .replaced import image_marker_layout
|
||||
from .min_max import handle_min_max_width, handle_min_max_height
|
||||
from .percentages import resolve_percentages, resolve_one_percentage
|
||||
from .preferred import shrink_to_fit, inline_preferred_minimum_width, trailing_whitespace_size
|
||||
from .preferred import (shrink_to_fit, inline_preferred_minimum_width,
|
||||
trailing_whitespace_size)
|
||||
from .tables import find_in_flow_baseline, table_wrapper_width
|
||||
from ..text import split_first_line
|
||||
from ..formatting_structure import boxes
|
||||
@ -248,8 +249,8 @@ def replaced_box_width(box, device_size):
|
||||
Compute and set the used width for replaced boxes (inline- or block-level)
|
||||
"""
|
||||
# http://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
|
||||
intrinsic_width = box.replacement.intrinsic_width
|
||||
intrinsic_height = box.replacement.intrinsic_height
|
||||
intrinsic_width, intrinsic_height = box.replacement.get_intrinsic_size(
|
||||
box.style.image_resolution)
|
||||
# TODO: update this when we have replaced elements that do not
|
||||
# always have an intrinsic width. (See commented code below.)
|
||||
assert intrinsic_width is not None
|
||||
@ -297,8 +298,8 @@ def replaced_box_height(box, device_size):
|
||||
Compute and set the used height for replaced boxes (inline- or block-level)
|
||||
"""
|
||||
# http://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height
|
||||
intrinsic_width = box.replacement.intrinsic_width
|
||||
intrinsic_height = box.replacement.intrinsic_height
|
||||
intrinsic_width, intrinsic_height = box.replacement.get_intrinsic_size(
|
||||
box.style.image_resolution)
|
||||
# TODO: update this when we have replaced elements that do not
|
||||
# always have intrinsic dimensions. (See commented code below.)
|
||||
assert intrinsic_width is not None
|
||||
|
@ -472,7 +472,8 @@ def replaced_preferred_width(box, outer=True):
|
||||
"""Return the preferred minimum width for an ``InlineReplacedBox``."""
|
||||
width = box.style.width
|
||||
if width == 'auto' or width.unit == '%':
|
||||
width = box.replacement.intrinsic_width
|
||||
width, _ = box.replacement.get_intrinsic_size(
|
||||
box.style.image_resolution)
|
||||
# TODO: handle the images with no intinsic width
|
||||
assert width is not None
|
||||
else:
|
||||
|
@ -23,9 +23,10 @@ def image_marker_layout(box):
|
||||
"""
|
||||
image = box.replacement
|
||||
one_em = box.style.font_size
|
||||
iwidth, iheight = image.get_intrinsic_size(box.style.image_resolution)
|
||||
box.width, box.height = default_image_sizing(
|
||||
image.intrinsic_width, image.intrinsic_height, image.intrinsic_ratio,
|
||||
box.width, box.height, default_width=one_em, default_height=one_em)
|
||||
iwidth, iheight, image.intrinsic_ratio, box.width, box.height,
|
||||
default_width=one_em, default_height=one_em)
|
||||
|
||||
|
||||
def default_image_sizing(intrinsic_width, intrinsic_height, intrinsic_ratio,
|
||||
|
@ -1411,6 +1411,33 @@ def test_images():
|
||||
''')
|
||||
|
||||
|
||||
def test_image_resolution():
|
||||
assert_same_rendering(20, 20, [
|
||||
('image_resolution_ref', '''
|
||||
<style>@page { size: 20px; margin: 2px; background: #fff }</style>
|
||||
<div style="font-size: 0">
|
||||
<img src="pattern.png" style="width: 8px"></div>
|
||||
'''),
|
||||
('image_resolution_img', '''
|
||||
<style>@page { size: 20px; margin: 2px; background: #fff }</style>
|
||||
<div style="image-resolution: .5dppx; font-size: 0">
|
||||
<img src="pattern.png"></div>
|
||||
'''),
|
||||
('image_resolution_content', '''
|
||||
<style>@page { size: 20px; margin: 2px; background: #fff }
|
||||
div::before { content: url(pattern.png) }
|
||||
</style>
|
||||
<div style="image-resolution: .5dppx; font-size: 0"></div>
|
||||
'''),
|
||||
('image_resolution_background', '''
|
||||
<style>@page { size: 20px; margin: 2px; background: #fff }
|
||||
</style>
|
||||
<div style="height: 16px; image-resolution: .5dppx;
|
||||
background: url(pattern.png) no-repeat"></div>
|
||||
'''),
|
||||
])
|
||||
|
||||
|
||||
@assert_no_logs
|
||||
def test_visibility():
|
||||
source = '''
|
||||
|
Loading…
Reference in New Issue
Block a user