diff --git a/tests/draw/svg/test_opacity.py b/tests/draw/svg/test_opacity.py
index b3f780de..c137722b 100644
--- a/tests/draw/svg/test_opacity.py
+++ b/tests/draw/svg/test_opacity.py
@@ -31,7 +31,7 @@ def test_fill_opacity(assert_same_renderings):
assert_same_renderings(
opacity_source % '''
+ fill="blue" opacity="50%" />
''',
@@ -59,7 +59,7 @@ def test_stroke_opacity(assert_same_renderings):
''',
opacity_source % '''
+ stroke="lime" fill="blue" stroke-opacity="50%" />
''',
)
@@ -82,7 +82,6 @@ def test_stroke_fill_opacity(assert_same_renderings):
)
-@pytest.mark.xfail
@assert_no_logs
def test_pattern_gradient_stroke_fill_opacity(assert_same_renderings):
assert_same_renderings(
@@ -141,6 +140,6 @@ def test_translate_opacity(assert_same_renderings):
''',
opacity_source % '''
+ fill="blue" opacity="50%" />
''',
)
diff --git a/tests/draw/test_opacity.py b/tests/draw/test_opacity.py
index ce88f4dc..3d56665a 100644
--- a/tests/draw/test_opacity.py
+++ b/tests/draw/test_opacity.py
@@ -11,23 +11,26 @@ opacity_source = '''
@assert_no_logs
-def test_opacity_1(assert_same_renderings):
+def test_opacity_zero(assert_same_renderings):
assert_same_renderings(
opacity_source % '
',
opacity_source % '',
+ opacity_source % '',
)
@assert_no_logs
-def test_opacity_2(assert_same_renderings):
+def test_opacity_normal_range(assert_same_renderings):
assert_same_renderings(
opacity_source % '',
opacity_source % '',
+ opacity_source % '',
+ opacity_source % '',
)
@assert_no_logs
-def test_opacity_3(assert_same_renderings):
+def test_opacity_nested(assert_same_renderings):
assert_same_renderings(
opacity_source % '',
opacity_source % '',
@@ -37,3 +40,21 @@ def test_opacity_3(assert_same_renderings):
''', # 0.9 * 0.666666 == 0.6
)
+
+
+@assert_no_logs
+def test_opacity_percent_clamp_down(assert_same_renderings):
+ assert_same_renderings(
+ opacity_source % '',
+ opacity_source % '',
+ opacity_source % '',
+ )
+
+
+@assert_no_logs
+def test_opacity_percent_clamp_up(assert_same_renderings):
+ assert_same_renderings(
+ opacity_source % '',
+ opacity_source % '',
+ opacity_source % '',
+ )
diff --git a/weasyprint/css/validation/properties.py b/weasyprint/css/validation/properties.py
index ef2e1ea0..0e6d120c 100644
--- a/weasyprint/css/validation/properties.py
+++ b/weasyprint/css/validation/properties.py
@@ -1029,6 +1029,8 @@ def opacity(token):
"""Validation for the ``opacity`` property."""
if token.type == 'number':
return min(1, max(0, token.value))
+ if token.type == 'percentage':
+ return min(1, max(0, token.value / 100))
@property()
diff --git a/weasyprint/svg/__init__.py b/weasyprint/svg/__init__.py
index 4f0cc611..fc911cca 100644
--- a/weasyprint/svg/__init__.py
+++ b/weasyprint/svg/__init__.py
@@ -19,7 +19,8 @@ from .path import path
from .shapes import circle, ellipse, line, polygon, polyline, rect
from .text import text
from .utils import (
- PointError, color, normalize, parse_url, preserve_ratio, size, transform)
+ PointError, alpha_value, color, normalize, parse_url, preserve_ratio, size,
+ transform)
TAGS = {
'a': text,
@@ -412,7 +413,7 @@ class SVG:
self.transform(node.get('transform'), font_size)
# Create substream for opacity
- opacity = float(node.get('opacity', 1))
+ opacity = alpha_value(node.get('opacity', 1))
if fill_stroke and 0 <= opacity < 1:
original_streams.append(self.stream)
box = self.calculate_bounding_box(node, font_size)
@@ -662,7 +663,7 @@ class SVG:
# Get fill data
fill_source, fill_color = self.get_paint(node.get('fill', 'black'))
- fill_opacity = float(node.get('fill-opacity', 1))
+ fill_opacity = alpha_value(node.get('fill-opacity', 1))
fill_drawn = draw_gradient_or_pattern(
self, node, fill_source, font_size, fill_opacity, stroke=False)
if fill_color and not fill_drawn:
@@ -673,7 +674,7 @@ class SVG:
# Get stroke data
stroke_source, stroke_color = self.get_paint(node.get('stroke'))
- stroke_opacity = float(node.get('stroke-opacity', 1))
+ stroke_opacity = alpha_value(node.get('stroke-opacity', 1))
stroke_drawn = draw_gradient_or_pattern(
self, node, stroke_source, font_size, stroke_opacity, stroke=True)
if stroke_color and not stroke_drawn:
diff --git a/weasyprint/svg/defs.py b/weasyprint/svg/defs.py
index a311ab8e..efe31b25 100644
--- a/weasyprint/svg/defs.py
+++ b/weasyprint/svg/defs.py
@@ -5,7 +5,7 @@ from math import ceil, hypot
from ..matrix import Matrix
from .bounding_box import is_valid_bounding_box
-from .utils import color, parse_url, size, transform
+from .utils import alpha_value, color, parse_url, size, transform
def use(svg, node, font_size):
@@ -83,7 +83,7 @@ def draw_gradient(svg, node, gradient, font_size, opacity, stroke):
positions.append(max(
positions[-1] if positions else 0,
size(child.get('offset'), font_size, 1)))
- stop_opacity = float(child.get('stop-opacity', 1)) * opacity
+ stop_opacity = alpha_value(child.get('stop-opacity', 1)) * opacity
stop_color = color(child.get('stop-color', 'black'))
if stop_opacity < 1:
stop_color = tuple(
diff --git a/weasyprint/svg/utils.py b/weasyprint/svg/utils.py
index aa86982e..a6d15ff3 100644
--- a/weasyprint/svg/utils.py
+++ b/weasyprint/svg/utils.py
@@ -57,6 +57,17 @@ def size(string, font_size=None, percentage_reference=None):
return 0
+def alpha_value(value):
+ """Return opacity between 0 and 1 from str, number or percentage."""
+ ratio = 1
+ if isinstance(value, str):
+ value = value.strip()
+ if value.endswith('%'):
+ ratio = 100
+ value = value[:-1].strip()
+ return min(1, max(0, float(value) / ratio))
+
+
def point(svg, string, font_size):
"""Pop first two size values from a string."""
match = re.match('(.*?) (.*?)(?: |$)', string)