1
1
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:
Guillaume Ayoub 2013-12-31 16:46:30 +01:00
commit 86eda6fa6d
9 changed files with 104 additions and 33 deletions

View File

@ -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/

View File

@ -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())

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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,

View File

@ -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 = '''