Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions tests/draw/svg/test_transform.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Test how SVG shapes are transformed."""

import pytest

from ...testing_utils import assert_no_logs


Expand Down Expand Up @@ -147,6 +149,31 @@ def test_transform_rotate_cx_cy(assert_pixels):
''')


@pytest.mark.parametrize(
'angle', ['90deg', '100grad', '1.5707963267948966rad', '.25turn'])
@assert_no_logs
def test_transform_rotate_angle_units(assert_same_renderings, angle):
assert_same_renderings('''
<style>
@page { size: 9px }
svg { display: block }
</style>
<svg width="9px" height="9px" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="-7" width="4" height="5" transform="rotate(90)"
stroke-width="2" stroke="red" fill="none" />
</svg>
''', f'''
<style>
@page {{ size: 9px }}
svg {{ display: block }}
</style>
<svg width="9px" height="9px" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="-7" width="4" height="5" transform="rotate({angle})"
stroke-width="2" stroke="red" fill="none" />
</svg>
''')


@assert_no_logs
def test_transform_skew(assert_pixels):
assert_pixels('''
Expand Down
42 changes: 26 additions & 16 deletions weasyprint/svg/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import re
from contextlib import suppress
from math import cos, radians, sin, tan
from math import cos, sin, tan
from urllib.parse import urlparse

from tinycss2.color5 import parse_color

from ..css.units import ANGLE_TO_RADIANS
from ..matrix import Matrix


Expand Down Expand Up @@ -57,6 +58,16 @@ def size(string, font_size=None, percentage_reference=None):
return 0


def angle(string):
Comment thread
moreaki marked this conversation as resolved.
"""Compute an angle in radians from an SVG transform value."""
string = normalize(string).split(' ', 1)[0]
# Sort units by length to match grad between rad.
for unit in sorted(ANGLE_TO_RADIANS, key=len, reverse=True):
if string.endswith(unit):
return float(string[:-len(unit)]) * ANGLE_TO_RADIANS[unit]
return float(string) * ANGLE_TO_RADIANS['deg']


def alpha_value(value):
"""Return opacity between 0 and 1 from str, number or percentage."""
ratio = 1
Expand Down Expand Up @@ -164,38 +175,37 @@ def transform(transform_string, transform_origin, font_size, normalized_diagonal

transformations = re.findall(r'(\w+) ?\( ?(.*?) ?\)', normalize(transform_string))
for transformation_type, transformation in transformations:
values = [
size(value, font_size, normalized_diagonal)
for value in transformation.split(' ')]
values = [value for value in transformation.split(' ') if value]
if transformation_type == 'matrix':
values = [size(value, font_size, normalized_diagonal) for value in values]
matrix = Matrix(*values) @ matrix
elif transformation_type == 'rotate':
if len(values) == 3:
matrix = Matrix(e=values[1], f=values[2]) @ matrix
matrix = Matrix(
cos(radians(float(values[0]))),
sin(radians(float(values[0]))),
-sin(radians(float(values[0]))),
cos(radians(float(values[0])))) @ matrix
rotate_x = size(values[1], font_size, normalized_diagonal)
rotate_y = size(values[2], font_size, normalized_diagonal)
matrix = Matrix(e=rotate_x, f=rotate_y) @ matrix
rotation = angle(values[0])
cos_r, sin_r = cos(rotation), sin(rotation)
matrix = Matrix(cos_r, sin_r, -sin_r, cos_r) @ matrix
if len(values) == 3:
matrix = Matrix(e=-values[1], f=-values[2]) @ matrix
matrix = Matrix(e=-rotate_x, f=-rotate_y) @ matrix
elif transformation_type.startswith('skew'):
if len(values) == 1:
values.append(0)
values.append('0')
if transformation_type in ('skewX', 'skew'):
matrix = Matrix(
c=tan(radians(float(values.pop(0))))) @ matrix
matrix = Matrix(c=tan(angle(values.pop(0)))) @ matrix
if transformation_type in ('skewY', 'skew'):
matrix = Matrix(
b=tan(radians(float(values.pop(0))))) @ matrix
matrix = Matrix(b=tan(angle(values.pop(0)))) @ matrix
elif transformation_type.startswith('translate'):
values = [size(value, font_size, normalized_diagonal) for value in values]
if len(values) == 1:
values.append(0)
if transformation_type in ('translateX', 'translate'):
matrix = Matrix(e=values.pop(0)) @ matrix
if transformation_type in ('translateY', 'translate'):
matrix = Matrix(f=values.pop(0)) @ matrix
elif transformation_type.startswith('scale'):
values = [size(value, font_size, normalized_diagonal) for value in values]
if len(values) == 1:
values.append(values[0])
if transformation_type in ('scaleX', 'scale'):
Expand Down
Loading