svg-ultralight 0.27.0__py3-none-any.whl → 0.29.0__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 +4 -0
- svg_ultralight/animate.py +2 -2
- svg_ultralight/bounding_boxes/bound_helpers.py +2 -0
- svg_ultralight/bounding_boxes/supports_bounds.py +22 -0
- svg_ultralight/bounding_boxes/type_bound_confederation.py +71 -0
- svg_ultralight/bounding_boxes/type_bound_element.py +23 -153
- svg_ultralight/bounding_boxes/type_bounding_box.py +246 -53
- svg_ultralight/bounding_boxes/type_padded_text.py +32 -8
- svg_ultralight/query.py +2 -1
- svg_ultralight/transformations.py +108 -0
- {svg_ultralight-0.27.0.dist-info → svg_ultralight-0.29.0.dist-info}/METADATA +1 -1
- {svg_ultralight-0.27.0.dist-info → svg_ultralight-0.29.0.dist-info}/RECORD +14 -12
- {svg_ultralight-0.27.0.dist-info → svg_ultralight-0.29.0.dist-info}/WHEEL +0 -0
- {svg_ultralight-0.27.0.dist-info → svg_ultralight-0.29.0.dist-info}/top_level.txt +0 -0
svg_ultralight/__init__.py
CHANGED
|
@@ -10,6 +10,7 @@ from svg_ultralight.bounding_boxes.bound_helpers import (
|
|
|
10
10
|
new_element_union,
|
|
11
11
|
)
|
|
12
12
|
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
13
|
+
from svg_ultralight.bounding_boxes.type_bound_confederation import BoundConfederation
|
|
13
14
|
from svg_ultralight.bounding_boxes.type_bound_element import BoundElement
|
|
14
15
|
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
15
16
|
from svg_ultralight.bounding_boxes.type_padded_text import PaddedText
|
|
@@ -37,8 +38,10 @@ from svg_ultralight.string_conversion import (
|
|
|
37
38
|
format_numbers,
|
|
38
39
|
format_numbers_in_string,
|
|
39
40
|
)
|
|
41
|
+
from svg_ultralight.transformations import transform_element
|
|
40
42
|
|
|
41
43
|
__all__ = [
|
|
44
|
+
"BoundConfederation",
|
|
42
45
|
"BoundElement",
|
|
43
46
|
"BoundingBox",
|
|
44
47
|
"NSMAP",
|
|
@@ -59,6 +62,7 @@ __all__ = [
|
|
|
59
62
|
"new_svg_root",
|
|
60
63
|
"new_svg_root_around_bounds",
|
|
61
64
|
"pad_text",
|
|
65
|
+
"transform_element",
|
|
62
66
|
"update_element",
|
|
63
67
|
"write_pdf",
|
|
64
68
|
"write_pdf_from_svg",
|
svg_ultralight/animate.py
CHANGED
|
@@ -34,7 +34,7 @@ def write_gif(
|
|
|
34
34
|
:param loop: how many times to loop gif. 0 -> forever
|
|
35
35
|
:effects: write file to gif
|
|
36
36
|
"""
|
|
37
|
-
images = [Image.open(x) for x in pngs]
|
|
38
|
-
images[0].save(
|
|
37
|
+
images = [Image.open(x) for x in pngs] # type: ignore
|
|
38
|
+
images[0].save( # type: ignore
|
|
39
39
|
gif, save_all=True, append_images=images[1:], duration=duration, loop=loop
|
|
40
40
|
)
|
|
@@ -18,6 +18,8 @@ from svg_ultralight.constructors import new_element
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
19
|
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
20
20
|
|
|
21
|
+
_Matrix = tuple[float, float, float, float, float, float]
|
|
22
|
+
|
|
21
23
|
|
|
22
24
|
def new_element_union(
|
|
23
25
|
*elems: EtreeElement | SupportsBounds, **attributes: float | str
|
|
@@ -16,13 +16,19 @@ Attributes:
|
|
|
16
16
|
:created: 2023-02-15
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
19
21
|
from typing import Protocol
|
|
20
22
|
|
|
23
|
+
_Matrix = tuple[float, float, float, float, float, float]
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
class SupportsBounds(Protocol):
|
|
23
27
|
"""Protocol for objects that can have bounds.
|
|
24
28
|
|
|
25
29
|
Attributes:
|
|
30
|
+
transformation (_Matrix): An svg-style transformation matrix.
|
|
31
|
+
transform (method): Apply a transformation to the object.
|
|
26
32
|
x (float): The minimum x coordinate.
|
|
27
33
|
x2 (float): The maximum x coordinate.
|
|
28
34
|
cx (float): The center x coordinate.
|
|
@@ -39,6 +45,22 @@ class SupportsBounds(Protocol):
|
|
|
39
45
|
set width and height.
|
|
40
46
|
"""
|
|
41
47
|
|
|
48
|
+
@property
|
|
49
|
+
def transformation(self) -> _Matrix:
|
|
50
|
+
"""Return an svg-style transformation matrix."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def transform(
|
|
54
|
+
self,
|
|
55
|
+
transformation: _Matrix | None = None,
|
|
56
|
+
*,
|
|
57
|
+
scale: float | None = None,
|
|
58
|
+
dx: float | None = None,
|
|
59
|
+
dy: float | None = None,
|
|
60
|
+
):
|
|
61
|
+
"""Apply a transformation to the object."""
|
|
62
|
+
...
|
|
63
|
+
|
|
42
64
|
@property
|
|
43
65
|
def x(self) -> float:
|
|
44
66
|
"""Return minimum x coordinate."""
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""A class to hold a list of bound elements and transform them together.
|
|
2
|
+
|
|
3
|
+
:author: Shay Hill
|
|
4
|
+
:created: 2024-05-05
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import dataclasses
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from lxml.etree import _Element as EtreeElement # type: ignore
|
|
13
|
+
|
|
14
|
+
from svg_ultralight.bounding_boxes.bound_helpers import new_bbox_union
|
|
15
|
+
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
16
|
+
from svg_ultralight.bounding_boxes.type_bounding_box import HasBoundingBox
|
|
17
|
+
from svg_ultralight.transformations import new_transformation_matrix, transform_element
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
21
|
+
|
|
22
|
+
_Matrix = tuple[float, float, float, float, float, float]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclasses.dataclass
|
|
26
|
+
class BoundConfederation(HasBoundingBox):
|
|
27
|
+
"""A class to hold a list of bound elements and transform them together.
|
|
28
|
+
|
|
29
|
+
This will transform the individual elements in place.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
blems: list[SupportsBounds | EtreeElement] = dataclasses.field(init=False)
|
|
33
|
+
bbox: BoundingBox = dataclasses.field(init=False)
|
|
34
|
+
|
|
35
|
+
def __init__(self, *blems: SupportsBounds | EtreeElement) -> None:
|
|
36
|
+
"""Initialize the bound confederation.
|
|
37
|
+
|
|
38
|
+
:param blems: bound elements to be transformed together
|
|
39
|
+
"""
|
|
40
|
+
self.blems = list(blems)
|
|
41
|
+
self.bbox = new_bbox_union(*self.blems)
|
|
42
|
+
|
|
43
|
+
def transform(
|
|
44
|
+
self,
|
|
45
|
+
transformation: _Matrix | None = None,
|
|
46
|
+
*,
|
|
47
|
+
scale: float | None = None,
|
|
48
|
+
dx: float | None = None,
|
|
49
|
+
dy: float | None = None,
|
|
50
|
+
):
|
|
51
|
+
"""Transform each bound element in self.blems.
|
|
52
|
+
|
|
53
|
+
:param transformation: 2D transformation matrix
|
|
54
|
+
:param scale: optional scale factor
|
|
55
|
+
:param dx: optional x translation
|
|
56
|
+
:param dy: optional y translation
|
|
57
|
+
|
|
58
|
+
Keep track of all compounding transformations in order to have a value for
|
|
59
|
+
self.scale (required for membersh and to provide access to cumulative
|
|
60
|
+
transforms should this be useful for any reason. This means all
|
|
61
|
+
transformations must be applied to two bounding boxes: a persistant bbox to
|
|
62
|
+
keep track of the scale property and a temporary bbox to isolate each
|
|
63
|
+
transformation.
|
|
64
|
+
"""
|
|
65
|
+
tmat = new_transformation_matrix(transformation, scale=scale, dx=dx, dy=dy)
|
|
66
|
+
self.bbox.transform(tmat)
|
|
67
|
+
for blem in self.blems:
|
|
68
|
+
if isinstance(blem, EtreeElement):
|
|
69
|
+
_ = transform_element(blem, tmat)
|
|
70
|
+
else:
|
|
71
|
+
blem.transform(tmat)
|
|
@@ -15,15 +15,18 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
from typing import TYPE_CHECKING
|
|
17
17
|
|
|
18
|
-
from svg_ultralight.bounding_boxes.
|
|
18
|
+
from svg_ultralight.bounding_boxes.type_bounding_box import HasBoundingBox
|
|
19
|
+
from svg_ultralight.transformations import new_transformation_matrix, transform_element
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
21
22
|
from lxml.etree import _Element as EtreeElement # type: ignore
|
|
22
23
|
|
|
23
24
|
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
24
25
|
|
|
26
|
+
_Matrix = tuple[float, float, float, float, float, float]
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
class BoundElement(HasBoundingBox):
|
|
27
30
|
"""An element with a bounding box.
|
|
28
31
|
|
|
29
32
|
Updates the element when x, y, x2, y2, width, or height are set.
|
|
@@ -43,154 +46,21 @@ class BoundElement(SupportsBounds):
|
|
|
43
46
|
def _update_elem(self):
|
|
44
47
|
self.elem.attrib["transform"] = self.bbox.transform_string
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
:param
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def x2(self) -> float:
|
|
65
|
-
"""The x coordinate of the right edge of the bounding box.
|
|
66
|
-
|
|
67
|
-
:return: the x coordinate of the right edge of the bounding box
|
|
68
|
-
"""
|
|
69
|
-
return self.bbox.x2
|
|
70
|
-
|
|
71
|
-
@x2.setter
|
|
72
|
-
def x2(self, value: float):
|
|
73
|
-
"""Set the x coordinate of the right edge of the bounding box.
|
|
74
|
-
|
|
75
|
-
:param value: the new x coordinate of the right edge of the bounding box
|
|
76
|
-
"""
|
|
77
|
-
self.bbox.x2 = value
|
|
78
|
-
self._update_elem()
|
|
79
|
-
|
|
80
|
-
@property
|
|
81
|
-
def cx(self) -> float:
|
|
82
|
-
"""The x coordinate of the center of the bounding box.
|
|
83
|
-
|
|
84
|
-
:return: the x coordinate of the center of the bounding box
|
|
85
|
-
"""
|
|
86
|
-
return self.bbox.cx
|
|
87
|
-
|
|
88
|
-
@cx.setter
|
|
89
|
-
def cx(self, value: float):
|
|
90
|
-
"""Set the x coordinate of the center of the bounding box.
|
|
91
|
-
|
|
92
|
-
:param value: the new x coordinate of the center of the bounding box
|
|
93
|
-
"""
|
|
94
|
-
self.bbox.cx = value
|
|
95
|
-
self._update_elem()
|
|
96
|
-
|
|
97
|
-
@property
|
|
98
|
-
def y(self) -> float:
|
|
99
|
-
"""The y coordinate of the top edge of the bounding box.
|
|
100
|
-
|
|
101
|
-
:return: the y coordinate of the top edge of the bounding box
|
|
102
|
-
"""
|
|
103
|
-
return self.bbox.y
|
|
104
|
-
|
|
105
|
-
@y.setter
|
|
106
|
-
def y(self, value: float):
|
|
107
|
-
"""Set the y coordinate of the top edge of the bounding box.
|
|
108
|
-
|
|
109
|
-
:param value: the new y coordinate of the top edge of the bounding box
|
|
110
|
-
"""
|
|
111
|
-
self.bbox.y = value
|
|
112
|
-
self._update_elem()
|
|
113
|
-
|
|
114
|
-
@property
|
|
115
|
-
def y2(self) -> float:
|
|
116
|
-
"""The y coordinate of the bottom edge of the bounding box.
|
|
117
|
-
|
|
118
|
-
:return: the y coordinate of the bottom edge of the bounding box
|
|
119
|
-
"""
|
|
120
|
-
return self.bbox.y2
|
|
121
|
-
|
|
122
|
-
@y2.setter
|
|
123
|
-
def y2(self, value: float):
|
|
124
|
-
"""Set the y coordinate of the bottom edge of the bounding box.
|
|
125
|
-
|
|
126
|
-
:param value: the new y coordinate of the bottom edge of the bounding box
|
|
127
|
-
"""
|
|
128
|
-
self.bbox.y2 = value
|
|
129
|
-
self._update_elem()
|
|
130
|
-
|
|
131
|
-
@property
|
|
132
|
-
def cy(self) -> float:
|
|
133
|
-
"""The y coordinate of the center of the bounding box.
|
|
134
|
-
|
|
135
|
-
:return: the y coordinate of the center of the bounding box
|
|
136
|
-
"""
|
|
137
|
-
return self.bbox.cy
|
|
138
|
-
|
|
139
|
-
@cy.setter
|
|
140
|
-
def cy(self, value: float):
|
|
141
|
-
"""Set the y coordinate of the center of the bounding box.
|
|
142
|
-
|
|
143
|
-
:param value: the new y coordinate of the center of the bounding box
|
|
144
|
-
"""
|
|
145
|
-
self.bbox.cy = value
|
|
146
|
-
self._update_elem()
|
|
147
|
-
|
|
148
|
-
@property
|
|
149
|
-
def width(self) -> float:
|
|
150
|
-
"""The width of the bounding box.
|
|
151
|
-
|
|
152
|
-
:return: the width of the bounding box
|
|
153
|
-
"""
|
|
154
|
-
return self.bbox.width
|
|
155
|
-
|
|
156
|
-
@width.setter
|
|
157
|
-
def width(self, value: float):
|
|
158
|
-
"""Set the width of the bounding box.
|
|
159
|
-
|
|
160
|
-
:param value: the new width of the bounding box
|
|
161
|
-
"""
|
|
162
|
-
self.bbox.width = value
|
|
163
|
-
self._update_elem()
|
|
164
|
-
|
|
165
|
-
@property
|
|
166
|
-
def height(self) -> float:
|
|
167
|
-
"""The height of the bounding box.
|
|
168
|
-
|
|
169
|
-
:return: the height of the bounding box
|
|
170
|
-
"""
|
|
171
|
-
return self.bbox.height
|
|
172
|
-
|
|
173
|
-
@height.setter
|
|
174
|
-
def height(self, value: float):
|
|
175
|
-
"""Set the height of the bounding box.
|
|
176
|
-
|
|
177
|
-
:param value: the new height of the bounding box
|
|
178
|
-
"""
|
|
179
|
-
self.bbox.height = value
|
|
180
|
-
self._update_elem()
|
|
181
|
-
|
|
182
|
-
@property
|
|
183
|
-
def scale(self) -> float:
|
|
184
|
-
"""The scale of the bounding box.
|
|
185
|
-
|
|
186
|
-
:return: the scale of the bounding box
|
|
187
|
-
"""
|
|
188
|
-
return self.bbox.scale
|
|
189
|
-
|
|
190
|
-
@scale.setter
|
|
191
|
-
def scale(self, value: float):
|
|
192
|
-
"""Set the scale of the bounding box.
|
|
193
|
-
|
|
194
|
-
:param value: the scale of the bounding box
|
|
195
|
-
"""
|
|
196
|
-
self.bbox.scale = value
|
|
49
|
+
def transform(
|
|
50
|
+
self,
|
|
51
|
+
transformation: _Matrix | None = None,
|
|
52
|
+
*,
|
|
53
|
+
scale: float | None = None,
|
|
54
|
+
dx: float | None = None,
|
|
55
|
+
dy: float | None = None,
|
|
56
|
+
):
|
|
57
|
+
"""Transform the element and bounding box.
|
|
58
|
+
|
|
59
|
+
:param transformation: a 6-tuple transformation matrix
|
|
60
|
+
:param scale: a scaling factor
|
|
61
|
+
:param dx: the x translation
|
|
62
|
+
:param dy: the y translation
|
|
63
|
+
"""
|
|
64
|
+
tmat = new_transformation_matrix(transformation, scale=scale, dx=dx, dy=dy)
|
|
65
|
+
self.bbox.transform(tmat)
|
|
66
|
+
_ = transform_element(self.elem, tmat)
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import dataclasses
|
|
10
10
|
|
|
11
11
|
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
12
12
|
from svg_ultralight.string_conversion import format_number
|
|
13
|
+
from svg_ultralight.transformations import mat_apply, mat_dot, new_transformation_matrix
|
|
13
14
|
|
|
15
|
+
_Matrix = tuple[float, float, float, float, float, float]
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
@dataclasses.dataclass
|
|
16
19
|
class BoundingBox(SupportsBounds):
|
|
17
20
|
"""Mutable bounding box object for svg_ultralight.
|
|
18
21
|
|
|
@@ -21,14 +24,12 @@ class BoundingBox(SupportsBounds):
|
|
|
21
24
|
:param width: width of the bounding box
|
|
22
25
|
:param height: height of the bounding box
|
|
23
26
|
|
|
24
|
-
The below optional
|
|
25
|
-
the entire state of a BoundingBox instance.
|
|
27
|
+
The below optional parameter, in addition to the required parameters, captures
|
|
28
|
+
the entire state of a BoundingBox instance. It could be used to make a copy or
|
|
26
29
|
to initialize a transformed box with the same transform_string as another box.
|
|
27
|
-
Under most circumstances,
|
|
30
|
+
Under most circumstances, it will not be used.
|
|
28
31
|
|
|
29
|
-
:param
|
|
30
|
-
:param translation_x: x translation of the bounding box
|
|
31
|
-
:param translation_y: y translation of the bounding box
|
|
32
|
+
:param _transformation: transformation matrix
|
|
32
33
|
|
|
33
34
|
Functions that return a bounding box will return a BoundingBox instance. This
|
|
34
35
|
instance can be transformed (uniform scale and translate only). Transformations
|
|
@@ -37,10 +38,10 @@ class BoundingBox(SupportsBounds):
|
|
|
37
38
|
Define the bbox with x=, y=, width=, height=
|
|
38
39
|
|
|
39
40
|
Transform the BoundingBox by setting these variables. Each time you set x, cx,
|
|
40
|
-
x2, y, cy, y2, width, or height, private transformation
|
|
41
|
-
|
|
41
|
+
x2, y, cy, y2, width, or height, private transformation value _transformation
|
|
42
|
+
will be updated.
|
|
42
43
|
|
|
43
|
-
The ultimate transformation can be accessed through ``.
|
|
44
|
+
The ultimate transformation can be accessed through ``.transform_string``.
|
|
44
45
|
So the workflow will look like :
|
|
45
46
|
|
|
46
47
|
1. Get the bounding box of an svg element
|
|
@@ -78,13 +79,44 @@ class BoundingBox(SupportsBounds):
|
|
|
78
79
|
_y: float
|
|
79
80
|
_width: float
|
|
80
81
|
_height: float
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
_transformation: _Matrix = (1, 0, 0, 1, 0, 0)
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def transformation(self) -> _Matrix:
|
|
86
|
+
"""Return transformation matrix.
|
|
87
|
+
|
|
88
|
+
:return: transformation matrix
|
|
89
|
+
"""
|
|
90
|
+
return self._transformation
|
|
91
|
+
|
|
92
|
+
def transform(
|
|
93
|
+
self,
|
|
94
|
+
transformation: _Matrix | None = None,
|
|
95
|
+
*,
|
|
96
|
+
scale: float | None = None,
|
|
97
|
+
dx: float | None = None,
|
|
98
|
+
dy: float | None = None,
|
|
99
|
+
):
|
|
100
|
+
"""Transform the bounding box by updating the transformation attribute.
|
|
101
|
+
|
|
102
|
+
:param transformation: 2D transformation matrix
|
|
103
|
+
:param scale: scale factor
|
|
104
|
+
:param dx: x translation
|
|
105
|
+
:param dy: y translation
|
|
106
|
+
|
|
107
|
+
All parameters are optional. Scale, dx, and dy are optional and applied after
|
|
108
|
+
the transformation matrix if both are given. This shouldn't be necessary in
|
|
109
|
+
most cases, the four parameters are there to allow transformation arguments
|
|
110
|
+
to be passed in a variety of ways. Scale, dx, and dy are the sensible values
|
|
111
|
+
to pass "by hand". The transformation matrix is the sensible argument to pass
|
|
112
|
+
when applying a transformation from another bounding box instance.
|
|
113
|
+
"""
|
|
114
|
+
tmat = new_transformation_matrix(transformation, scale=scale, dx=dx, dy=dy)
|
|
115
|
+
self._transformation = mat_dot(tmat, self.transformation)
|
|
84
116
|
|
|
85
117
|
@property
|
|
86
118
|
def scale(self) -> float:
|
|
87
|
-
"""
|
|
119
|
+
"""Get scale of the bounding box.
|
|
88
120
|
|
|
89
121
|
:return: uniform scale of the bounding box
|
|
90
122
|
|
|
@@ -95,7 +127,7 @@ class BoundingBox(SupportsBounds):
|
|
|
95
127
|
width*scale, height => height*scale, scale => scale*scale. This matches how
|
|
96
128
|
scale works in almost every other context.
|
|
97
129
|
"""
|
|
98
|
-
return self.
|
|
130
|
+
return self.transformation[0]
|
|
99
131
|
|
|
100
132
|
@scale.setter
|
|
101
133
|
def scale(self, value: float) -> None:
|
|
@@ -110,7 +142,7 @@ class BoundingBox(SupportsBounds):
|
|
|
110
142
|
`scale = 2` -> ignore whatever scale was previously defined and set scale to 2
|
|
111
143
|
`scale *= 2` -> make it twice as big as it was.
|
|
112
144
|
"""
|
|
113
|
-
self.
|
|
145
|
+
self.transform(scale=value / self.scale)
|
|
114
146
|
|
|
115
147
|
@property
|
|
116
148
|
def x(self) -> float:
|
|
@@ -118,15 +150,15 @@ class BoundingBox(SupportsBounds):
|
|
|
118
150
|
|
|
119
151
|
:return: internal _x value transformed by scale and translation
|
|
120
152
|
"""
|
|
121
|
-
return (self.
|
|
153
|
+
return mat_apply(self.transformation, (self._x, 0))[0]
|
|
122
154
|
|
|
123
155
|
@x.setter
|
|
124
156
|
def x(self, value: float) -> None:
|
|
125
|
-
"""Update
|
|
157
|
+
"""Update transformation values (do not alter self._x).
|
|
126
158
|
|
|
127
159
|
:param value: new x value after transformation
|
|
128
160
|
"""
|
|
129
|
-
self.
|
|
161
|
+
self.transform(dx=value - self.x)
|
|
130
162
|
|
|
131
163
|
@property
|
|
132
164
|
def cx(self) -> float:
|
|
@@ -142,7 +174,7 @@ class BoundingBox(SupportsBounds):
|
|
|
142
174
|
|
|
143
175
|
:param value: new center x value after transformation
|
|
144
176
|
"""
|
|
145
|
-
self.
|
|
177
|
+
self.x += value - self.cx
|
|
146
178
|
|
|
147
179
|
@property
|
|
148
180
|
def x2(self) -> float:
|
|
@@ -154,11 +186,11 @@ class BoundingBox(SupportsBounds):
|
|
|
154
186
|
|
|
155
187
|
@x2.setter
|
|
156
188
|
def x2(self, value: float) -> None:
|
|
157
|
-
"""Update
|
|
189
|
+
"""Update transformation values (do not alter self._x2).
|
|
158
190
|
|
|
159
191
|
:param value: new x2 value after transformation
|
|
160
192
|
"""
|
|
161
|
-
self.
|
|
193
|
+
self.x += value - self.x2
|
|
162
194
|
|
|
163
195
|
@property
|
|
164
196
|
def y(self) -> float:
|
|
@@ -166,15 +198,15 @@ class BoundingBox(SupportsBounds):
|
|
|
166
198
|
|
|
167
199
|
:return: internal _y value transformed by scale and translation
|
|
168
200
|
"""
|
|
169
|
-
return (self.
|
|
201
|
+
return mat_apply(self.transformation, (0, self._y))[1]
|
|
170
202
|
|
|
171
203
|
@y.setter
|
|
172
204
|
def y(self, value: float) -> None:
|
|
173
|
-
"""Update
|
|
205
|
+
"""Update transformation values (do not alter self._y).
|
|
174
206
|
|
|
175
207
|
:param value: new y value after transformation
|
|
176
208
|
"""
|
|
177
|
-
self.
|
|
209
|
+
self.transform(dy=value - self.y)
|
|
178
210
|
|
|
179
211
|
@property
|
|
180
212
|
def cy(self) -> float:
|
|
@@ -190,7 +222,7 @@ class BoundingBox(SupportsBounds):
|
|
|
190
222
|
|
|
191
223
|
:param value: new center y value after transformation
|
|
192
224
|
"""
|
|
193
|
-
self.
|
|
225
|
+
self.y += value - self.cy
|
|
194
226
|
|
|
195
227
|
@property
|
|
196
228
|
def y2(self) -> float:
|
|
@@ -202,11 +234,11 @@ class BoundingBox(SupportsBounds):
|
|
|
202
234
|
|
|
203
235
|
@y2.setter
|
|
204
236
|
def y2(self, value: float) -> None:
|
|
205
|
-
"""Update
|
|
237
|
+
"""Update transformation values (do not alter self._y).
|
|
206
238
|
|
|
207
239
|
:param value: new y2 value after transformation
|
|
208
240
|
"""
|
|
209
|
-
self.y
|
|
241
|
+
self.y += value - self.y2
|
|
210
242
|
|
|
211
243
|
@property
|
|
212
244
|
def width(self) -> float:
|
|
@@ -214,11 +246,11 @@ class BoundingBox(SupportsBounds):
|
|
|
214
246
|
|
|
215
247
|
:return: internal _width value transformed by scale
|
|
216
248
|
"""
|
|
217
|
-
return self._width * self.
|
|
249
|
+
return self._width * self.scale
|
|
218
250
|
|
|
219
251
|
@width.setter
|
|
220
252
|
def width(self, value: float) -> None:
|
|
221
|
-
"""Update
|
|
253
|
+
"""Update transformation values, Do not alter self._width.
|
|
222
254
|
|
|
223
255
|
:param value: new width value after transformation
|
|
224
256
|
|
|
@@ -227,7 +259,7 @@ class BoundingBox(SupportsBounds):
|
|
|
227
259
|
"""
|
|
228
260
|
current_x = self.x
|
|
229
261
|
current_y = self.y
|
|
230
|
-
self.
|
|
262
|
+
self.scale *= value / self.width
|
|
231
263
|
self.x = current_x
|
|
232
264
|
self.y = current_y
|
|
233
265
|
|
|
@@ -237,11 +269,11 @@ class BoundingBox(SupportsBounds):
|
|
|
237
269
|
|
|
238
270
|
:return: internal _height value transformed by scale
|
|
239
271
|
"""
|
|
240
|
-
return self._height * self.
|
|
272
|
+
return self._height * self.scale
|
|
241
273
|
|
|
242
274
|
@height.setter
|
|
243
275
|
def height(self, value: float) -> None:
|
|
244
|
-
"""Update
|
|
276
|
+
"""Update transformation values, Do not alter self._height.
|
|
245
277
|
|
|
246
278
|
:param value: new height value after transformation
|
|
247
279
|
|
|
@@ -250,33 +282,16 @@ class BoundingBox(SupportsBounds):
|
|
|
250
282
|
"""
|
|
251
283
|
self.width = value * self.width / self.height
|
|
252
284
|
|
|
253
|
-
def _add_transform(self, scale: float, translation_x: float, translation_y: float):
|
|
254
|
-
"""Transform the bounding box by updating the transformation attributes.
|
|
255
|
-
|
|
256
|
-
:param scale: scale factor
|
|
257
|
-
:param translation_x: x translation
|
|
258
|
-
:param translation_y: y translation
|
|
259
|
-
|
|
260
|
-
Transformation attributes are _translation_x, _translation_y, and _scale
|
|
261
|
-
"""
|
|
262
|
-
self._translation_x += translation_x / self._scale
|
|
263
|
-
self._translation_y += translation_y / self._scale
|
|
264
|
-
self._scale *= scale
|
|
265
|
-
|
|
266
285
|
@property
|
|
267
286
|
def transform_string(self) -> str:
|
|
268
287
|
"""Transformation property string value for svg element.
|
|
269
288
|
|
|
270
|
-
:return: string value for an svg
|
|
289
|
+
:return: string value for an svg transformation attribute.
|
|
271
290
|
|
|
272
291
|
Use with
|
|
273
292
|
``update_element(elem, transform=bbox.transform_string)``
|
|
274
293
|
"""
|
|
275
|
-
|
|
276
|
-
format_number(x)
|
|
277
|
-
for x in (self._scale, self._translation_x, self._translation_y)
|
|
278
|
-
)
|
|
279
|
-
return f"scale({scale}) translate({trans_x} {trans_y})"
|
|
294
|
+
return f"matrix({' '.join(map(format_number, self.transformation))})"
|
|
280
295
|
|
|
281
296
|
def merge(self, *others: BoundingBox) -> BoundingBox:
|
|
282
297
|
"""Create a bounding box around all other bounding boxes.
|
|
@@ -306,3 +321,181 @@ class BoundingBox(SupportsBounds):
|
|
|
306
321
|
min_y = min(x.y for x in bboxes)
|
|
307
322
|
max_y = max(x.y + x.height for x in bboxes)
|
|
308
323
|
return BoundingBox(min_x, min_y, max_x - min_x, max_y - min_y)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class HasBoundingBox(SupportsBounds):
|
|
327
|
+
"""A parent class for BoundElement and others that have a bbox attribute."""
|
|
328
|
+
|
|
329
|
+
def __init__(self, bbox: BoundingBox) -> None:
|
|
330
|
+
"""Initialize the HasBoundingBox instance."""
|
|
331
|
+
self.bbox = bbox
|
|
332
|
+
|
|
333
|
+
@property
|
|
334
|
+
def transformation(self) -> _Matrix:
|
|
335
|
+
"""The transformation matrix of the bounding box."""
|
|
336
|
+
return self.bbox.transformation
|
|
337
|
+
|
|
338
|
+
def transform(
|
|
339
|
+
self,
|
|
340
|
+
transformation: _Matrix | None = None,
|
|
341
|
+
*,
|
|
342
|
+
scale: float | None = None,
|
|
343
|
+
dx: float | None = None,
|
|
344
|
+
dy: float | None = None,
|
|
345
|
+
):
|
|
346
|
+
"""Transform the element and bounding box.
|
|
347
|
+
|
|
348
|
+
:param transformation: a 6-tuple transformation matrix
|
|
349
|
+
:param scale: a scaling factor
|
|
350
|
+
:param dx: the x translation
|
|
351
|
+
:param dy: the y translation
|
|
352
|
+
"""
|
|
353
|
+
self.bbox.transform(transformation, scale=scale, dx=dx, dy=dy)
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def scale(self) -> float:
|
|
357
|
+
"""The scale of the bounding box.
|
|
358
|
+
|
|
359
|
+
:return: the scale of the bounding box
|
|
360
|
+
"""
|
|
361
|
+
return self.transformation[0]
|
|
362
|
+
|
|
363
|
+
@scale.setter
|
|
364
|
+
def scale(self, value: float):
|
|
365
|
+
"""Set the scale of the bounding box.
|
|
366
|
+
|
|
367
|
+
:param value: the scale of the bounding box
|
|
368
|
+
"""
|
|
369
|
+
self.transform(scale=value / self.scale)
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def x(self) -> float:
|
|
373
|
+
"""The x coordinate of the left edge of the bounding box.
|
|
374
|
+
|
|
375
|
+
:return: the x coordinate of the left edge of the bounding box
|
|
376
|
+
"""
|
|
377
|
+
return self.bbox.x
|
|
378
|
+
|
|
379
|
+
@x.setter
|
|
380
|
+
def x(self, value: float):
|
|
381
|
+
"""Set the x coordinate of the left edge of the bounding box.
|
|
382
|
+
|
|
383
|
+
:param value: the new x coordinate of the left edge of the bounding box
|
|
384
|
+
"""
|
|
385
|
+
self.transform(dx=value - self.x)
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def x2(self) -> float:
|
|
389
|
+
"""The x coordinate of the right edge of the bounding box.
|
|
390
|
+
|
|
391
|
+
:return: the x coordinate of the right edge of the bounding box
|
|
392
|
+
"""
|
|
393
|
+
return self.bbox.x2
|
|
394
|
+
|
|
395
|
+
@x2.setter
|
|
396
|
+
def x2(self, value: float):
|
|
397
|
+
"""Set the x coordinate of the right edge of the bounding box.
|
|
398
|
+
|
|
399
|
+
:param value: the new x coordinate of the right edge of the bounding box
|
|
400
|
+
"""
|
|
401
|
+
self.x += value - self.x2
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def cx(self) -> float:
|
|
405
|
+
"""The x coordinate of the center of the bounding box.
|
|
406
|
+
|
|
407
|
+
:return: the x coordinate of the center of the bounding box
|
|
408
|
+
"""
|
|
409
|
+
return self.bbox.cx
|
|
410
|
+
|
|
411
|
+
@cx.setter
|
|
412
|
+
def cx(self, value: float):
|
|
413
|
+
"""Set the x coordinate of the center of the bounding box.
|
|
414
|
+
|
|
415
|
+
:param value: the new x coordinate of the center of the bounding box
|
|
416
|
+
"""
|
|
417
|
+
self.x += value - self.cx
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def y(self) -> float:
|
|
421
|
+
"""The y coordinate of the top edge of the bounding box.
|
|
422
|
+
|
|
423
|
+
:return: the y coordinate of the top edge of the bounding box
|
|
424
|
+
"""
|
|
425
|
+
return self.bbox.y
|
|
426
|
+
|
|
427
|
+
@y.setter
|
|
428
|
+
def y(self, value: float):
|
|
429
|
+
"""Set the y coordinate of the top edge of the bounding box.
|
|
430
|
+
|
|
431
|
+
:param value: the new y coordinate of the top edge of the bounding box
|
|
432
|
+
"""
|
|
433
|
+
self.transform(dy=value - self.y)
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def y2(self) -> float:
|
|
437
|
+
"""The y coordinate of the bottom edge of the bounding box.
|
|
438
|
+
|
|
439
|
+
:return: the y coordinate of the bottom edge of the bounding box
|
|
440
|
+
"""
|
|
441
|
+
return self.bbox.y2
|
|
442
|
+
|
|
443
|
+
@y2.setter
|
|
444
|
+
def y2(self, value: float):
|
|
445
|
+
"""Set the y coordinate of the bottom edge of the bounding box.
|
|
446
|
+
|
|
447
|
+
:param value: the new y coordinate of the bottom edge of the bounding box
|
|
448
|
+
"""
|
|
449
|
+
self.y += value - self.y2
|
|
450
|
+
|
|
451
|
+
@property
|
|
452
|
+
def cy(self) -> float:
|
|
453
|
+
"""The y coordinate of the center of the bounding box.
|
|
454
|
+
|
|
455
|
+
:return: the y coordinate of the center of the bounding box
|
|
456
|
+
"""
|
|
457
|
+
return self.bbox.cy
|
|
458
|
+
|
|
459
|
+
@cy.setter
|
|
460
|
+
def cy(self, value: float):
|
|
461
|
+
"""Set the y coordinate of the center of the bounding box.
|
|
462
|
+
|
|
463
|
+
:param value: the new y coordinate of the center of the bounding box
|
|
464
|
+
"""
|
|
465
|
+
self.y += value - self.cy
|
|
466
|
+
|
|
467
|
+
@property
|
|
468
|
+
def width(self) -> float:
|
|
469
|
+
"""The width of the bounding box.
|
|
470
|
+
|
|
471
|
+
:return: the width of the bounding box
|
|
472
|
+
"""
|
|
473
|
+
return self.bbox.width
|
|
474
|
+
|
|
475
|
+
@width.setter
|
|
476
|
+
def width(self, value: float):
|
|
477
|
+
"""Set the width of the bounding box.
|
|
478
|
+
|
|
479
|
+
:param value: the new width of the bounding box
|
|
480
|
+
"""
|
|
481
|
+
current_x = self.x
|
|
482
|
+
current_y = self.y
|
|
483
|
+
self.scale *= value / self.width
|
|
484
|
+
self.x = current_x
|
|
485
|
+
self.y = current_y
|
|
486
|
+
|
|
487
|
+
@property
|
|
488
|
+
def height(self) -> float:
|
|
489
|
+
"""The height of the bounding box.
|
|
490
|
+
|
|
491
|
+
:return: the height of the bounding box
|
|
492
|
+
"""
|
|
493
|
+
return self.bbox.height
|
|
494
|
+
|
|
495
|
+
@height.setter
|
|
496
|
+
def height(self, value: float):
|
|
497
|
+
"""Set the height of the bounding box.
|
|
498
|
+
|
|
499
|
+
:param value: the new height of the bounding box
|
|
500
|
+
"""
|
|
501
|
+
self.width *= value / self.height
|
|
@@ -72,6 +72,8 @@ from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
|
72
72
|
if TYPE_CHECKING:
|
|
73
73
|
from lxml.etree import _Element as EtreeElement # type: ignore
|
|
74
74
|
|
|
75
|
+
_Matrix = tuple[float, float, float, float, float, float]
|
|
76
|
+
|
|
75
77
|
|
|
76
78
|
class PaddedText(SupportsBounds):
|
|
77
79
|
"""A line of text with a bounding box and padding."""
|
|
@@ -116,11 +118,32 @@ class PaddedText(SupportsBounds):
|
|
|
116
118
|
self.lmargin, self.capline, self.padded_width, self.padded_height
|
|
117
119
|
)
|
|
118
120
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
@property
|
|
122
|
+
def transformation(self) -> _Matrix:
|
|
123
|
+
"""The transformation matrix of the bounding box."""
|
|
124
|
+
return self.bbox.transformation
|
|
125
|
+
|
|
126
|
+
def _update_elem(self):
|
|
122
127
|
self.elem.attrib["transform"] = self.bbox.transform_string
|
|
123
128
|
|
|
129
|
+
def transform(
|
|
130
|
+
self,
|
|
131
|
+
transformation: _Matrix | None = None,
|
|
132
|
+
*,
|
|
133
|
+
scale: float | None = None,
|
|
134
|
+
dx: float | None = None,
|
|
135
|
+
dy: float | None = None,
|
|
136
|
+
):
|
|
137
|
+
"""Transform the element and bounding box.
|
|
138
|
+
|
|
139
|
+
:param transformation: a 6-tuple transformation matrix
|
|
140
|
+
:param scale: a scaling factor
|
|
141
|
+
:param dx: the x translation
|
|
142
|
+
:param dy: the y translation
|
|
143
|
+
"""
|
|
144
|
+
self.bbox.transform(transformation, scale=scale, dx=dx, dy=dy)
|
|
145
|
+
self._update_elem()
|
|
146
|
+
|
|
124
147
|
@property
|
|
125
148
|
def tpad(self) -> float:
|
|
126
149
|
"""The top padding of this line of text.
|
|
@@ -167,7 +190,7 @@ class PaddedText(SupportsBounds):
|
|
|
167
190
|
|
|
168
191
|
:param value: The left margin of this line of text.
|
|
169
192
|
"""
|
|
170
|
-
self.
|
|
193
|
+
self.transform(dx=value + self.lpad - self.bbox.x)
|
|
171
194
|
|
|
172
195
|
@property
|
|
173
196
|
def rmargin(self) -> float:
|
|
@@ -183,7 +206,7 @@ class PaddedText(SupportsBounds):
|
|
|
183
206
|
|
|
184
207
|
:param value: The right margin of this line of text.
|
|
185
208
|
"""
|
|
186
|
-
self.
|
|
209
|
+
self.transform(dx=value - self.rpad - self.bbox.x2)
|
|
187
210
|
|
|
188
211
|
@property
|
|
189
212
|
def capline(self) -> float:
|
|
@@ -199,7 +222,7 @@ class PaddedText(SupportsBounds):
|
|
|
199
222
|
|
|
200
223
|
:param value: The top of this line of text.
|
|
201
224
|
"""
|
|
202
|
-
self.
|
|
225
|
+
self.transform(dy=value + self.tpad - self.bbox.y)
|
|
203
226
|
|
|
204
227
|
@property
|
|
205
228
|
def baseline(self) -> float:
|
|
@@ -215,7 +238,7 @@ class PaddedText(SupportsBounds):
|
|
|
215
238
|
|
|
216
239
|
:param value: The bottom of this line of text.
|
|
217
240
|
"""
|
|
218
|
-
self.
|
|
241
|
+
self.transform(dy=value - self.bpad - self.bbox.y2)
|
|
219
242
|
|
|
220
243
|
@property
|
|
221
244
|
def padded_width(self) -> float:
|
|
@@ -239,8 +262,9 @@ class PaddedText(SupportsBounds):
|
|
|
239
262
|
*and* y2) when scaling.
|
|
240
263
|
"""
|
|
241
264
|
baseline = self.baseline
|
|
242
|
-
self.
|
|
265
|
+
self.bbox.width = width - self.lpad - self.rpad
|
|
243
266
|
self.baseline = baseline
|
|
267
|
+
self._update_elem()
|
|
244
268
|
|
|
245
269
|
@property
|
|
246
270
|
def padded_height(self) -> float:
|
svg_ultralight/query.py
CHANGED
|
@@ -118,7 +118,8 @@ def map_ids_to_bounding_boxes(
|
|
|
118
118
|
|
|
119
119
|
id2bbox: dict[str, BoundingBox] = {}
|
|
120
120
|
for id_, *bounds in (x.split(",") for x in bb_strings):
|
|
121
|
-
|
|
121
|
+
x, y, width, height = (float(x) for x in bounds)
|
|
122
|
+
id2bbox[id_] = BoundingBox(x, y, width, height)
|
|
122
123
|
return id2bbox
|
|
123
124
|
|
|
124
125
|
|
|
@@ -0,0 +1,108 @@
|
|
|
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 re
|
|
10
|
+
from contextlib import suppress
|
|
11
|
+
from typing import TYPE_CHECKING, cast
|
|
12
|
+
|
|
13
|
+
from svg_ultralight.string_conversion import format_number
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from lxml.etree import _Element as EtreeElement # type: ignore
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_RE_MATRIX = re.compile(r"matrix\(([^)]+)\)")
|
|
20
|
+
|
|
21
|
+
_Matrix = tuple[float, float, float, float, float, float]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def mat_dot(mat1: _Matrix, mat2: _Matrix) -> _Matrix:
|
|
25
|
+
"""Matrix multiplication for svg-style matrices.
|
|
26
|
+
|
|
27
|
+
:param mat1: transformation matrix (sx, 0, 0, sy, tx, ty)
|
|
28
|
+
:param mat2: transformation matrix (sx, 0, 0, sy, tx, ty)
|
|
29
|
+
|
|
30
|
+
Svg uses an unusual matrix format. For 3x3 transformation matrix
|
|
31
|
+
|
|
32
|
+
[[00, 01, 02],
|
|
33
|
+
[10, 11, 12],
|
|
34
|
+
[20, 21, 22]]
|
|
35
|
+
|
|
36
|
+
The svg matrix is
|
|
37
|
+
(00, 10, 01, 11, 02, 12)
|
|
38
|
+
|
|
39
|
+
Values 10 and 01 are only used for skewing, which is not supported by a bounding
|
|
40
|
+
box. Values 00 and 11 will always be identical for symmetric scaling, which is
|
|
41
|
+
the only scaling implemented in my BoundingBox classes. However, all six values
|
|
42
|
+
are implemented in case this function is used in other contexts.
|
|
43
|
+
"""
|
|
44
|
+
aa = sum(mat1[x] * mat2[y] for x, y in ((0, 0), (2, 1)))
|
|
45
|
+
bb = sum(mat1[x] * mat2[y] for x, y in ((1, 0), (3, 1)))
|
|
46
|
+
cc = sum(mat1[x] * mat2[y] for x, y in ((0, 2), (2, 3)))
|
|
47
|
+
dd = sum(mat1[x] * mat2[y] for x, y in ((1, 2), (3, 3)))
|
|
48
|
+
ee = sum(mat1[x] * mat2[y] for x, y in ((0, 4), (2, 5))) + mat1[4]
|
|
49
|
+
ff = sum(mat1[x] * mat2[y] for x, y in ((1, 4), (3, 5))) + mat1[5]
|
|
50
|
+
return (aa, bb, cc, dd, ee, ff)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def mat_apply(mat1: _Matrix, mat2: tuple[float, float]) -> tuple[float, float]:
|
|
54
|
+
"""Apply an svg-style transformation matrix to a point.
|
|
55
|
+
|
|
56
|
+
:param mat1: transformation matrix (sx, 0, 0, sy, tx, ty)
|
|
57
|
+
:param mat2: point (x, y)
|
|
58
|
+
"""
|
|
59
|
+
return mat1[0] * mat2[0] + mat1[4], mat1[3] * mat2[1] + mat1[5]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_transform_matrix(elem: EtreeElement) -> _Matrix:
|
|
63
|
+
"""Get the transformation matrix from an svg element.
|
|
64
|
+
|
|
65
|
+
:param element: svg element
|
|
66
|
+
"""
|
|
67
|
+
transform = elem.attrib.get("transform")
|
|
68
|
+
if not transform:
|
|
69
|
+
return (1, 0, 0, 1, 0, 0)
|
|
70
|
+
values_str = ""
|
|
71
|
+
with suppress(AttributeError):
|
|
72
|
+
values_str = cast(re.Match[str], _RE_MATRIX.match(transform)).group(1)
|
|
73
|
+
with suppress(ValueError):
|
|
74
|
+
aa, bb, cc, dd, ee, ff = (float(val) for val in values_str.split())
|
|
75
|
+
return (aa, bb, cc, dd, ee, ff)
|
|
76
|
+
msg = f"Could not parse transformation matrix from {transform}"
|
|
77
|
+
raise ValueError(msg)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def new_transformation_matrix(
|
|
81
|
+
transformation: _Matrix | None = None,
|
|
82
|
+
*,
|
|
83
|
+
scale: float | None = None,
|
|
84
|
+
dx: float | None = None,
|
|
85
|
+
dy: float | None = None,
|
|
86
|
+
) -> _Matrix:
|
|
87
|
+
"""Create a new transformation matrix.
|
|
88
|
+
|
|
89
|
+
This takes the standard arguments in the BoundingBox classes and returns an
|
|
90
|
+
svg-style transformation matrix.
|
|
91
|
+
"""
|
|
92
|
+
transformation = transformation or (1, 0, 0, 1, 0, 0)
|
|
93
|
+
scale = scale or 1
|
|
94
|
+
dx = dx or 0
|
|
95
|
+
dy = dy or 0
|
|
96
|
+
return mat_dot((scale, 0, 0, scale, dx, dy), transformation)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def transform_element(elem: EtreeElement, matrix: _Matrix) -> EtreeElement:
|
|
100
|
+
"""Apply a transformation matrix to an svg element.
|
|
101
|
+
|
|
102
|
+
:param elem: svg element
|
|
103
|
+
:param matrix: transformation matrix
|
|
104
|
+
"""
|
|
105
|
+
current = get_transform_matrix(elem)
|
|
106
|
+
updated = map(format_number, mat_dot(matrix, current))
|
|
107
|
+
elem.attrib["transform"] = f"matrix({' '.join(updated)})"
|
|
108
|
+
return elem
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
svg_ultralight/__init__.py,sha256=
|
|
2
|
-
svg_ultralight/animate.py,sha256=
|
|
1
|
+
svg_ultralight/__init__.py,sha256=b0cB7zUGr7o3XaKatJeVyHpJLfF26aXsSYZJlVnvBFQ,2079
|
|
2
|
+
svg_ultralight/animate.py,sha256=fE-zRU_uFrZIL9W78fcGz7qmrts8fz5UoWEy7b4xb44,1144
|
|
3
3
|
svg_ultralight/inkscape.py,sha256=M8yTxXOu4NlXnhsMycvEJiIDpnDeiZ_bZakJBM38ZoU,9152
|
|
4
4
|
svg_ultralight/layout.py,sha256=TTETT_8WLBXnQxDGXdAeczCFN5pFo5kKY3Q6zv4FPX4,12238
|
|
5
5
|
svg_ultralight/main.py,sha256=6oNkZfD27UMdP-oYqp5agS_IGcYb8NkUZwM9Zdyb3SA,7287
|
|
6
6
|
svg_ultralight/metadata.py,sha256=Mxgxrxe1Ar4kp2wTT29aadxgHNFaaNLABo29jStiWDg,4201
|
|
7
7
|
svg_ultralight/nsmap.py,sha256=y63upO78Rr-JJT56RWWZuyrsILh6HPoY4GhbYnK1A0g,1244
|
|
8
8
|
svg_ultralight/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
svg_ultralight/query.py,sha256=
|
|
9
|
+
svg_ultralight/query.py,sha256=_KQuk4IwhVVDgTT0GZm_gbqNUGp7lMEDM32b_CTTSUA,7320
|
|
10
10
|
svg_ultralight/root_elements.py,sha256=pt9J6mPrnoTAZVF6vKTZoM_o947I8UCj6MbGcD2JUCk,2869
|
|
11
11
|
svg_ultralight/string_conversion.py,sha256=WEmpf75RJmJ2lfJluagAz2wPsz6wM8XvTEwkq4U0vEc,7353
|
|
12
|
+
svg_ultralight/transformations.py,sha256=CT43zaWSmEoWf-rQpFgP_1KX9wL6AzpR0cF8E1fIhAU,3625
|
|
12
13
|
svg_ultralight/unit_conversion.py,sha256=g07nhzXdjPvGcJmkhLdFbeDLrSmbI8uFoVgPo7G62Bg,9258
|
|
13
14
|
svg_ultralight/bounding_boxes/__init__.py,sha256=qUEn3r4s-1QNHaguhWhhaNfdP4tl_B6YEqxtiTFuzhQ,78
|
|
14
|
-
svg_ultralight/bounding_boxes/bound_helpers.py,sha256=
|
|
15
|
-
svg_ultralight/bounding_boxes/supports_bounds.py,sha256=
|
|
16
|
-
svg_ultralight/bounding_boxes/
|
|
17
|
-
svg_ultralight/bounding_boxes/
|
|
18
|
-
svg_ultralight/bounding_boxes/
|
|
15
|
+
svg_ultralight/bounding_boxes/bound_helpers.py,sha256=YMClhdekeYbzD_ijXDAer-H3moWKN3lUNnZs1UNFFKc,3458
|
|
16
|
+
svg_ultralight/bounding_boxes/supports_bounds.py,sha256=fbHV6mGdeIVV3lS15vBKSrHiHKR7DMg4K5X__4LLmCE,4569
|
|
17
|
+
svg_ultralight/bounding_boxes/type_bound_confederation.py,sha256=PAUa8LSfyw0v2eq8EU2B_V5dlsl7ddrrPIsD-5fWU8U,2602
|
|
18
|
+
svg_ultralight/bounding_boxes/type_bound_element.py,sha256=9RdxH8osOlAvPdWR0Ww9NsasHLPYFDs-MbydoV48x4E,2239
|
|
19
|
+
svg_ultralight/bounding_boxes/type_bounding_box.py,sha256=cDrMp6uwaA--KJIQS2puG10qh8n3TBmiscg-cfk1f3w,16149
|
|
20
|
+
svg_ultralight/bounding_boxes/type_padded_text.py,sha256=QA6PfeO_sQYc5pEXuyfyQ3lRUcZAc4B2BthWXpdt3qQ,14848
|
|
19
21
|
svg_ultralight/constructors/__init__.py,sha256=YcnO0iBQc19aL8Iemw0Y452MBMBIT2AN5nZCnoGxpn0,327
|
|
20
22
|
svg_ultralight/constructors/new_element.py,sha256=VtMz9sPn9rMk6rui5Poysy3vezlOaS-tGIcGbu-SXmY,3406
|
|
21
23
|
svg_ultralight/strings/__init__.py,sha256=Zalrf-ThFz7b7xKELx5lb2gOlBgV-6jk_k_EeSdVCVk,295
|
|
22
24
|
svg_ultralight/strings/svg_strings.py,sha256=RYKMxOHq9abbZyGcFqsElBGLrBX-EjjNxln3s_ibi30,1296
|
|
23
|
-
svg_ultralight-0.
|
|
24
|
-
svg_ultralight-0.
|
|
25
|
-
svg_ultralight-0.
|
|
26
|
-
svg_ultralight-0.
|
|
25
|
+
svg_ultralight-0.29.0.dist-info/METADATA,sha256=hLjRc-xXrwqasclTCthb-BuYGgjPRxMtXwQ-zSAh7qY,8871
|
|
26
|
+
svg_ultralight-0.29.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
27
|
+
svg_ultralight-0.29.0.dist-info/top_level.txt,sha256=se-6yqM_0Yg5orJKvKWdjQZ4iR4G_EjhL7oRgju-fdY,15
|
|
28
|
+
svg_ultralight-0.29.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|