svg-ultralight 0.47.0__py3-none-any.whl → 0.50.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of svg-ultralight might be problematic. Click here for more details.
- svg_ultralight/__init__.py +108 -105
- svg_ultralight/animate.py +40 -40
- svg_ultralight/attrib_hints.py +13 -14
- svg_ultralight/bounding_boxes/__init__.py +5 -5
- svg_ultralight/bounding_boxes/bound_helpers.py +189 -201
- svg_ultralight/bounding_boxes/padded_text_initializers.py +207 -206
- svg_ultralight/bounding_boxes/supports_bounds.py +166 -166
- svg_ultralight/bounding_boxes/type_bound_collection.py +71 -71
- svg_ultralight/bounding_boxes/type_bound_element.py +65 -65
- svg_ultralight/bounding_boxes/type_bounding_box.py +396 -396
- svg_ultralight/bounding_boxes/type_padded_text.py +411 -411
- svg_ultralight/constructors/__init__.py +14 -14
- svg_ultralight/constructors/new_element.py +115 -115
- svg_ultralight/font_tools/__init__.py +5 -5
- svg_ultralight/font_tools/comp_results.py +295 -293
- svg_ultralight/font_tools/font_info.py +793 -784
- svg_ultralight/image_ops.py +156 -156
- svg_ultralight/inkscape.py +261 -261
- svg_ultralight/layout.py +290 -291
- svg_ultralight/main.py +183 -198
- svg_ultralight/metadata.py +122 -122
- svg_ultralight/nsmap.py +36 -36
- svg_ultralight/py.typed +5 -0
- svg_ultralight/query.py +254 -249
- svg_ultralight/read_svg.py +58 -0
- svg_ultralight/root_elements.py +87 -87
- svg_ultralight/string_conversion.py +244 -244
- svg_ultralight/strings/__init__.py +21 -13
- svg_ultralight/strings/svg_strings.py +106 -67
- svg_ultralight/transformations.py +140 -141
- svg_ultralight/unit_conversion.py +247 -248
- {svg_ultralight-0.47.0.dist-info → svg_ultralight-0.50.1.dist-info}/METADATA +208 -214
- svg_ultralight-0.50.1.dist-info/RECORD +34 -0
- svg_ultralight-0.50.1.dist-info/WHEEL +4 -0
- svg_ultralight-0.47.0.dist-info/RECORD +0 -34
- svg_ultralight-0.47.0.dist-info/WHEEL +0 -5
- svg_ultralight-0.47.0.dist-info/top_level.txt +0 -1
|
@@ -1,67 +1,106 @@
|
|
|
1
|
-
"""Explicit string formatting calls for arguments that aren't floats or strings.
|
|
2
|
-
|
|
3
|
-
:author: Shay Hill
|
|
4
|
-
:created: 10/30/2020
|
|
5
|
-
|
|
6
|
-
The `string_conversion` module will format floats or strings. Some other formatters can
|
|
7
|
-
make things easier.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
from typing import TYPE_CHECKING
|
|
13
|
-
|
|
14
|
-
from svg_ultralight.string_conversion import format_number
|
|
15
|
-
|
|
16
|
-
if TYPE_CHECKING:
|
|
17
|
-
from collections.abc import Iterable
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
_MAX_8BIT = 255
|
|
21
|
-
_BIG_INT = 2**32 - 1
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _float_to_8bit_int(clipped_float: float) -> int:
|
|
25
|
-
"""Convert a float between 0 and 255 to an int between 0 and 255.
|
|
26
|
-
|
|
27
|
-
:param float_: a float in the closed interval [0 .. 255]. Values outside this
|
|
28
|
-
range will be clipped.
|
|
29
|
-
:return: an int in the closed interval [0 .. 255]
|
|
30
|
-
|
|
31
|
-
Convert color floats [0 .. 255] to ints [0 .. 255] without rounding, which "short
|
|
32
|
-
changes" 0 and 255.
|
|
33
|
-
"""
|
|
34
|
-
clipped_float = min(_MAX_8BIT, max(0, clipped_float))
|
|
35
|
-
if clipped_float % 1:
|
|
36
|
-
high_int = int(clipped_float / _MAX_8BIT * _BIG_INT)
|
|
37
|
-
return high_int >> 24
|
|
38
|
-
return int(clipped_float)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
:param
|
|
45
|
-
:return:
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
:
|
|
65
|
-
""
|
|
66
|
-
|
|
67
|
-
|
|
1
|
+
"""Explicit string formatting calls for arguments that aren't floats or strings.
|
|
2
|
+
|
|
3
|
+
:author: Shay Hill
|
|
4
|
+
:created: 10/30/2020
|
|
5
|
+
|
|
6
|
+
The `string_conversion` module will format floats or strings. Some other formatters can
|
|
7
|
+
make things easier.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from svg_ultralight.string_conversion import format_number
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Iterable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_MAX_8BIT = 255
|
|
21
|
+
_BIG_INT = 2**32 - 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _float_to_8bit_int(clipped_float: float) -> int:
|
|
25
|
+
"""Convert a float between 0 and 255 to an int between 0 and 255.
|
|
26
|
+
|
|
27
|
+
:param float_: a float in the closed interval [0 .. 255]. Values outside this
|
|
28
|
+
range will be clipped.
|
|
29
|
+
:return: an int in the closed interval [0 .. 255]
|
|
30
|
+
|
|
31
|
+
Convert color floats [0 .. 255] to ints [0 .. 255] without rounding, which "short
|
|
32
|
+
changes" 0 and 255.
|
|
33
|
+
"""
|
|
34
|
+
clipped_float = min(_MAX_8BIT, max(0, clipped_float))
|
|
35
|
+
if clipped_float % 1:
|
|
36
|
+
high_int = int(clipped_float / _MAX_8BIT * _BIG_INT)
|
|
37
|
+
return high_int >> 24
|
|
38
|
+
return int(clipped_float)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def svg_ints(floats: Iterable[float]) -> str:
|
|
42
|
+
"""Space-delimited ints.
|
|
43
|
+
|
|
44
|
+
:param floats: and number of floats
|
|
45
|
+
:return: each float rounded to an int, space delimited
|
|
46
|
+
"""
|
|
47
|
+
return " ".join(str(round(x)) for x in floats)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def svg_floats(floats: Iterable[float]) -> str:
|
|
51
|
+
"""Space-delimited floats.
|
|
52
|
+
|
|
53
|
+
:param floats: and number of floats
|
|
54
|
+
:return: each float formatted, space delimited
|
|
55
|
+
|
|
56
|
+
matrix strings, svg viewBox, and other attributes need space-delimited floats.
|
|
57
|
+
"""
|
|
58
|
+
return " ".join(format_number(x) for x in floats)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def svg_float_tuples(tuples: Iterable[tuple[float, float]]) -> str:
|
|
62
|
+
"""Space-delimited tuples.
|
|
63
|
+
|
|
64
|
+
:param tuples: [(a, b), (c, d)]
|
|
65
|
+
:return: "a,b c,d"
|
|
66
|
+
"""
|
|
67
|
+
tuple_strings = [",".join(format_number(n) for n in t) for t in tuples]
|
|
68
|
+
return " ".join(tuple_strings)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ===================================================================================
|
|
72
|
+
# Specific string formats
|
|
73
|
+
# ===================================================================================
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def svg_color_tuple(rgb_floats: tuple[float, float, float]) -> str:
|
|
77
|
+
"""Turn an rgb tuple (0-255, 0-255, 0-255) into an svg color definition.
|
|
78
|
+
|
|
79
|
+
:param rgb_floats: (0-255, 0-255, 0-255)
|
|
80
|
+
:return: "rgb(128,128,128)"
|
|
81
|
+
"""
|
|
82
|
+
r, g, b = map(_float_to_8bit_int, rgb_floats)
|
|
83
|
+
return f"rgb({r},{g},{b})"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def svg_matrix(floats: Iterable[float]) -> str:
|
|
87
|
+
"""Create a matrix string for the svg transform attribute.
|
|
88
|
+
|
|
89
|
+
a: scale x
|
|
90
|
+
b: skew y
|
|
91
|
+
c: skew x
|
|
92
|
+
d: scale y
|
|
93
|
+
e: translate x
|
|
94
|
+
f: translate y
|
|
95
|
+
:return: "matrix(a,b,c,d,e,f)"
|
|
96
|
+
|
|
97
|
+
The matrix() function defines a transformation in the 2D space. The six values
|
|
98
|
+
represent a 3x3 matrix that is used to perform linear transformations such as
|
|
99
|
+
translation, scaling, rotation, and skewing on SVG elements.
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
a, b, c, d, e, f = floats
|
|
103
|
+
except ValueError as e:
|
|
104
|
+
msg = "svg_matrix() needs exactly 6 floats."
|
|
105
|
+
raise ValueError(msg) from e
|
|
106
|
+
return f"matrix({svg_floats((a, b, c, d, e, f))})"
|
|
@@ -1,141 +1,140 @@
|
|
|
1
|
-
"""Math and conversion for svg-style transformation matrices.
|
|
2
|
-
|
|
3
|
-
:author: Shay Hill
|
|
4
|
-
:created: 2024-05-05
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import numbers
|
|
10
|
-
import re
|
|
11
|
-
from contextlib import suppress
|
|
12
|
-
from typing import TYPE_CHECKING, cast
|
|
13
|
-
|
|
14
|
-
from svg_ultralight.
|
|
15
|
-
|
|
16
|
-
if TYPE_CHECKING:
|
|
17
|
-
from lxml.etree import (
|
|
18
|
-
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
RE_MATRIX = re.compile(r"matrix\(([^)]+)\)")
|
|
23
|
-
|
|
24
|
-
_Matrix = tuple[float, float, float, float, float, float]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def mat_dot(mat1: _Matrix, mat2: _Matrix) -> _Matrix:
|
|
28
|
-
"""Matrix multiplication for svg-style matrices.
|
|
29
|
-
|
|
30
|
-
:param mat1: transformation matrix (sx, 0, 0, sy, tx, ty)
|
|
31
|
-
:param mat2: transformation matrix (sx, 0, 0, sy, tx, ty)
|
|
32
|
-
|
|
33
|
-
Svg uses an unusual matrix format. For 3x3 transformation matrix
|
|
34
|
-
|
|
35
|
-
[[00, 01, 02],
|
|
36
|
-
[10, 11, 12],
|
|
37
|
-
[20, 21, 22]]
|
|
38
|
-
|
|
39
|
-
The svg matrix is
|
|
40
|
-
(00, 10, 01, 11, 02, 12)
|
|
41
|
-
|
|
42
|
-
Values 10 and 01 are only used for skewing, which is not supported by a bounding
|
|
43
|
-
box. Values 00 and 11 will always be identical for symmetric scaling, which is
|
|
44
|
-
the only scaling implemented in my BoundingBox classes. However, all six values
|
|
45
|
-
are implemented in case this function is used in other contexts.
|
|
46
|
-
"""
|
|
47
|
-
aa = sum(mat1[x] * mat2[y] for x, y in ((0, 0), (2, 1)))
|
|
48
|
-
bb = sum(mat1[x] * mat2[y] for x, y in ((1, 0), (3, 1)))
|
|
49
|
-
cc = sum(mat1[x] * mat2[y] for x, y in ((0, 2), (2, 3)))
|
|
50
|
-
dd = sum(mat1[x] * mat2[y] for x, y in ((1, 2), (3, 3)))
|
|
51
|
-
ee = sum(mat1[x] * mat2[y] for x, y in ((0, 4), (2, 5))) + mat1[4]
|
|
52
|
-
ff = sum(mat1[x] * mat2[y] for x, y in ((1, 4), (3, 5))) + mat1[5]
|
|
53
|
-
return (aa, bb, cc, dd, ee, ff)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def mat_apply(matrix: _Matrix, point: tuple[float, float]) -> tuple[float, float]:
|
|
57
|
-
"""Apply an svg-style transformation matrix to a point.
|
|
58
|
-
|
|
59
|
-
:param mat1: transformation matrix (a, b, c, d, e, f) describing a 3x3 matrix
|
|
60
|
-
with an implied third row of (0, 0, 1)
|
|
61
|
-
[[a, c, e], [b, d, f], [0, 0, 1]]
|
|
62
|
-
:param mat2: point (x, y)
|
|
63
|
-
"""
|
|
64
|
-
a, b, c, d, e, f = matrix
|
|
65
|
-
x, y = point
|
|
66
|
-
result_x = a * x + c * y + e
|
|
67
|
-
result_y = b * x + d * y + f
|
|
68
|
-
return result_x, result_y
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def mat_invert(tmat: _Matrix) -> _Matrix:
|
|
72
|
-
"""Invert a 2D transformation matrix in svg format."""
|
|
73
|
-
a, b, c, d, e, f = tmat
|
|
74
|
-
det = a * d - b * c
|
|
75
|
-
if det == 0:
|
|
76
|
-
msg = "Matrix is not invertible"
|
|
77
|
-
raise ValueError(msg)
|
|
78
|
-
return (
|
|
79
|
-
d / det,
|
|
80
|
-
-b / det,
|
|
81
|
-
-c / det,
|
|
82
|
-
a / det,
|
|
83
|
-
(c * f - d * e) / det,
|
|
84
|
-
(b * e - a * f) / det,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def get_transform_matrix(elem: EtreeElement) -> _Matrix:
|
|
89
|
-
"""Get the transformation matrix from an svg element.
|
|
90
|
-
|
|
91
|
-
:param element: svg element
|
|
92
|
-
"""
|
|
93
|
-
transform = elem.attrib.get("transform")
|
|
94
|
-
if not transform:
|
|
95
|
-
return (1, 0, 0, 1, 0, 0)
|
|
96
|
-
values_str = ""
|
|
97
|
-
with suppress(AttributeError):
|
|
98
|
-
values_str = cast("re.Match[str]", RE_MATRIX.match(transform)).group(1)
|
|
99
|
-
with suppress(ValueError):
|
|
100
|
-
aa, bb, cc, dd, ee, ff = (float(val) for val in values_str.split())
|
|
101
|
-
return (aa, bb, cc, dd, ee, ff)
|
|
102
|
-
msg = f"Could not parse transformation matrix from {transform}"
|
|
103
|
-
raise ValueError(msg)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def new_transformation_matrix(
|
|
107
|
-
transformation: _Matrix | None = None,
|
|
108
|
-
*,
|
|
109
|
-
scale: tuple[float, float] | float | None = None,
|
|
110
|
-
dx: float | None = None,
|
|
111
|
-
dy: float | None = None,
|
|
112
|
-
) -> _Matrix:
|
|
113
|
-
"""Create a new transformation matrix.
|
|
114
|
-
|
|
115
|
-
This takes the standard arguments in the BoundingBox classes and returns an
|
|
116
|
-
svg-style transformation matrix.
|
|
117
|
-
"""
|
|
118
|
-
transformation = transformation or (1, 0, 0, 1, 0, 0)
|
|
119
|
-
|
|
120
|
-
if isinstance(scale, (float, int, numbers.Real)):
|
|
121
|
-
scale_x, scale_y = (scale, scale)
|
|
122
|
-
elif scale is None:
|
|
123
|
-
scale_x, scale_y = (1, 1)
|
|
124
|
-
else:
|
|
125
|
-
scale_x, scale_y = scale
|
|
126
|
-
|
|
127
|
-
dx = dx or 0
|
|
128
|
-
dy = dy or 0
|
|
129
|
-
return mat_dot((float(scale_x), 0, 0, float(scale_y), dx, dy), transformation)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def transform_element(elem: EtreeElement, matrix: _Matrix) -> EtreeElement:
|
|
133
|
-
"""Apply a transformation matrix to an svg element.
|
|
134
|
-
|
|
135
|
-
:param elem: svg element
|
|
136
|
-
:param matrix: transformation matrix
|
|
137
|
-
"""
|
|
138
|
-
current = get_transform_matrix(elem)
|
|
139
|
-
|
|
140
|
-
elem
|
|
141
|
-
return elem
|
|
1
|
+
"""Math and conversion for svg-style transformation matrices.
|
|
2
|
+
|
|
3
|
+
:author: Shay Hill
|
|
4
|
+
:created: 2024-05-05
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numbers
|
|
10
|
+
import re
|
|
11
|
+
from contextlib import suppress
|
|
12
|
+
from typing import TYPE_CHECKING, cast
|
|
13
|
+
|
|
14
|
+
from svg_ultralight.strings import svg_matrix
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from lxml.etree import (
|
|
18
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
RE_MATRIX = re.compile(r"matrix\(([^)]+)\)")
|
|
23
|
+
|
|
24
|
+
_Matrix = tuple[float, float, float, float, float, float]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def mat_dot(mat1: _Matrix, mat2: _Matrix) -> _Matrix:
|
|
28
|
+
"""Matrix multiplication for svg-style matrices.
|
|
29
|
+
|
|
30
|
+
:param mat1: transformation matrix (sx, 0, 0, sy, tx, ty)
|
|
31
|
+
:param mat2: transformation matrix (sx, 0, 0, sy, tx, ty)
|
|
32
|
+
|
|
33
|
+
Svg uses an unusual matrix format. For 3x3 transformation matrix
|
|
34
|
+
|
|
35
|
+
[[00, 01, 02],
|
|
36
|
+
[10, 11, 12],
|
|
37
|
+
[20, 21, 22]]
|
|
38
|
+
|
|
39
|
+
The svg matrix is
|
|
40
|
+
(00, 10, 01, 11, 02, 12)
|
|
41
|
+
|
|
42
|
+
Values 10 and 01 are only used for skewing, which is not supported by a bounding
|
|
43
|
+
box. Values 00 and 11 will always be identical for symmetric scaling, which is
|
|
44
|
+
the only scaling implemented in my BoundingBox classes. However, all six values
|
|
45
|
+
are implemented in case this function is used in other contexts.
|
|
46
|
+
"""
|
|
47
|
+
aa = sum(mat1[x] * mat2[y] for x, y in ((0, 0), (2, 1)))
|
|
48
|
+
bb = sum(mat1[x] * mat2[y] for x, y in ((1, 0), (3, 1)))
|
|
49
|
+
cc = sum(mat1[x] * mat2[y] for x, y in ((0, 2), (2, 3)))
|
|
50
|
+
dd = sum(mat1[x] * mat2[y] for x, y in ((1, 2), (3, 3)))
|
|
51
|
+
ee = sum(mat1[x] * mat2[y] for x, y in ((0, 4), (2, 5))) + mat1[4]
|
|
52
|
+
ff = sum(mat1[x] * mat2[y] for x, y in ((1, 4), (3, 5))) + mat1[5]
|
|
53
|
+
return (aa, bb, cc, dd, ee, ff)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def mat_apply(matrix: _Matrix, point: tuple[float, float]) -> tuple[float, float]:
|
|
57
|
+
"""Apply an svg-style transformation matrix to a point.
|
|
58
|
+
|
|
59
|
+
:param mat1: transformation matrix (a, b, c, d, e, f) describing a 3x3 matrix
|
|
60
|
+
with an implied third row of (0, 0, 1)
|
|
61
|
+
[[a, c, e], [b, d, f], [0, 0, 1]]
|
|
62
|
+
:param mat2: point (x, y)
|
|
63
|
+
"""
|
|
64
|
+
a, b, c, d, e, f = matrix
|
|
65
|
+
x, y = point
|
|
66
|
+
result_x = a * x + c * y + e
|
|
67
|
+
result_y = b * x + d * y + f
|
|
68
|
+
return result_x, result_y
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def mat_invert(tmat: _Matrix) -> _Matrix:
|
|
72
|
+
"""Invert a 2D transformation matrix in svg format."""
|
|
73
|
+
a, b, c, d, e, f = tmat
|
|
74
|
+
det = a * d - b * c
|
|
75
|
+
if det == 0:
|
|
76
|
+
msg = "Matrix is not invertible"
|
|
77
|
+
raise ValueError(msg)
|
|
78
|
+
return (
|
|
79
|
+
d / det,
|
|
80
|
+
-b / det,
|
|
81
|
+
-c / det,
|
|
82
|
+
a / det,
|
|
83
|
+
(c * f - d * e) / det,
|
|
84
|
+
(b * e - a * f) / det,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_transform_matrix(elem: EtreeElement) -> _Matrix:
|
|
89
|
+
"""Get the transformation matrix from an svg element.
|
|
90
|
+
|
|
91
|
+
:param element: svg element
|
|
92
|
+
"""
|
|
93
|
+
transform = elem.attrib.get("transform")
|
|
94
|
+
if not transform:
|
|
95
|
+
return (1, 0, 0, 1, 0, 0)
|
|
96
|
+
values_str = ""
|
|
97
|
+
with suppress(AttributeError):
|
|
98
|
+
values_str = cast("re.Match[str]", RE_MATRIX.match(transform)).group(1)
|
|
99
|
+
with suppress(ValueError):
|
|
100
|
+
aa, bb, cc, dd, ee, ff = (float(val) for val in values_str.split())
|
|
101
|
+
return (aa, bb, cc, dd, ee, ff)
|
|
102
|
+
msg = f"Could not parse transformation matrix from {transform}"
|
|
103
|
+
raise ValueError(msg)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def new_transformation_matrix(
|
|
107
|
+
transformation: _Matrix | None = None,
|
|
108
|
+
*,
|
|
109
|
+
scale: tuple[float, float] | float | None = None,
|
|
110
|
+
dx: float | None = None,
|
|
111
|
+
dy: float | None = None,
|
|
112
|
+
) -> _Matrix:
|
|
113
|
+
"""Create a new transformation matrix.
|
|
114
|
+
|
|
115
|
+
This takes the standard arguments in the BoundingBox classes and returns an
|
|
116
|
+
svg-style transformation matrix.
|
|
117
|
+
"""
|
|
118
|
+
transformation = transformation or (1, 0, 0, 1, 0, 0)
|
|
119
|
+
|
|
120
|
+
if isinstance(scale, (float, int, numbers.Real)):
|
|
121
|
+
scale_x, scale_y = (scale, scale)
|
|
122
|
+
elif scale is None:
|
|
123
|
+
scale_x, scale_y = (1, 1)
|
|
124
|
+
else:
|
|
125
|
+
scale_x, scale_y = scale
|
|
126
|
+
|
|
127
|
+
dx = dx or 0
|
|
128
|
+
dy = dy or 0
|
|
129
|
+
return mat_dot((float(scale_x), 0, 0, float(scale_y), dx, dy), transformation)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def transform_element(elem: EtreeElement, matrix: _Matrix) -> EtreeElement:
|
|
133
|
+
"""Apply a transformation matrix to an svg element.
|
|
134
|
+
|
|
135
|
+
:param elem: svg element
|
|
136
|
+
:param matrix: transformation matrix
|
|
137
|
+
"""
|
|
138
|
+
current = get_transform_matrix(elem)
|
|
139
|
+
elem.attrib["transform"] = svg_matrix(mat_dot(matrix, current))
|
|
140
|
+
return elem
|