svg-ultralight 0.37.0__py3-none-any.whl → 0.39.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/bounding_boxes/bound_helpers.py +4 -11
- svg_ultralight/bounding_boxes/supports_bounds.py +4 -9
- svg_ultralight/bounding_boxes/type_bound_collection.py +2 -2
- svg_ultralight/bounding_boxes/type_bound_element.py +4 -5
- svg_ultralight/bounding_boxes/type_bounding_box.py +155 -262
- svg_ultralight/bounding_boxes/type_padded_text.py +65 -205
- svg_ultralight/constructors/new_element.py +3 -1
- svg_ultralight/inkscape.py +3 -1
- svg_ultralight/layout.py +22 -13
- svg_ultralight/main.py +12 -4
- svg_ultralight/metadata.py +1 -1
- svg_ultralight/query.py +72 -40
- svg_ultralight/root_elements.py +3 -1
- svg_ultralight/string_conversion.py +7 -3
- svg_ultralight/transformations.py +17 -9
- {svg_ultralight-0.37.0.dist-info → svg_ultralight-0.39.0.dist-info}/METADATA +5 -4
- svg_ultralight-0.39.0.dist-info/RECORD +29 -0
- {svg_ultralight-0.37.0.dist-info → svg_ultralight-0.39.0.dist-info}/WHEEL +1 -1
- svg_ultralight-0.37.0.dist-info/RECORD +0 -29
- {svg_ultralight-0.37.0.dist-info → svg_ultralight-0.39.0.dist-info}/top_level.txt +0 -0
|
@@ -8,10 +8,11 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
|
-
from lxml.etree import _Element as EtreeElement #
|
|
11
|
+
from lxml.etree import _Element as EtreeElement # pyright: ignore[reportPrivateUsage]
|
|
12
12
|
|
|
13
|
+
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
13
14
|
from svg_ultralight.bounding_boxes.type_bound_element import BoundElement
|
|
14
|
-
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
15
|
+
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox, HasBoundingBox
|
|
15
16
|
from svg_ultralight.bounding_boxes.type_padded_text import PaddedText
|
|
16
17
|
from svg_ultralight.constructors import new_element
|
|
17
18
|
|
|
@@ -66,15 +67,7 @@ def new_bbox_union(*blems: SupportsBounds | EtreeElement) -> BoundingBox:
|
|
|
66
67
|
|
|
67
68
|
Will used the padded_box attribute of PaddedText instances.
|
|
68
69
|
"""
|
|
69
|
-
bboxes
|
|
70
|
-
for blem in blems:
|
|
71
|
-
if isinstance(blem, BoundingBox):
|
|
72
|
-
bboxes.append(blem)
|
|
73
|
-
elif isinstance(blem, BoundElement):
|
|
74
|
-
bboxes.append(blem.bbox)
|
|
75
|
-
elif isinstance(blem, PaddedText):
|
|
76
|
-
bboxes.append(blem.padded_bbox)
|
|
77
|
-
|
|
70
|
+
bboxes = [x.bbox for x in blems if isinstance(x, HasBoundingBox)]
|
|
78
71
|
if not bboxes:
|
|
79
72
|
msg = (
|
|
80
73
|
"Cannot find any bounding boxes to union. "
|
|
@@ -37,7 +37,7 @@ class SupportsBounds(Protocol):
|
|
|
37
37
|
cy (float): The center y coordinate.
|
|
38
38
|
width (float): The width of the object.
|
|
39
39
|
height(float): The height of the object.
|
|
40
|
-
scale (float): The scale of the object.
|
|
40
|
+
scale ((float, float)): The x and yx and y scale of the object.
|
|
41
41
|
|
|
42
42
|
There is no setter for scale. Scale is a function of width and height.
|
|
43
43
|
Setting scale would be ambiguous. because the typical implementation of
|
|
@@ -45,16 +45,11 @@ class SupportsBounds(Protocol):
|
|
|
45
45
|
set width and height.
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
|
-
@property
|
|
49
|
-
def transformation(self) -> _Matrix:
|
|
50
|
-
"""Return an svg-style transformation matrix."""
|
|
51
|
-
...
|
|
52
|
-
|
|
53
48
|
def transform(
|
|
54
49
|
self,
|
|
55
50
|
transformation: _Matrix | None = None,
|
|
56
51
|
*,
|
|
57
|
-
scale: float | None = None,
|
|
52
|
+
scale: tuple[float, float] | None = None,
|
|
58
53
|
dx: float | None = None,
|
|
59
54
|
dy: float | None = None,
|
|
60
55
|
):
|
|
@@ -158,12 +153,12 @@ class SupportsBounds(Protocol):
|
|
|
158
153
|
"""
|
|
159
154
|
|
|
160
155
|
@property
|
|
161
|
-
def scale(self) -> float:
|
|
156
|
+
def scale(self) -> tuple[float, float]:
|
|
162
157
|
"""Return scale of the object."""
|
|
163
158
|
...
|
|
164
159
|
|
|
165
160
|
@scale.setter
|
|
166
|
-
def scale(self, value: float):
|
|
161
|
+
def scale(self, value: tuple[float, float]):
|
|
167
162
|
"""Return scale of the object.
|
|
168
163
|
|
|
169
164
|
:param value: The scale of the object.
|
|
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
import dataclasses
|
|
10
10
|
from typing import TYPE_CHECKING
|
|
11
11
|
|
|
12
|
-
from lxml.etree import _Element as EtreeElement #
|
|
12
|
+
from lxml.etree import _Element as EtreeElement # pyright: ignore[reportPrivateUsage]
|
|
13
13
|
|
|
14
14
|
from svg_ultralight.bounding_boxes.bound_helpers import new_bbox_union
|
|
15
15
|
from svg_ultralight.bounding_boxes.type_bounding_box import HasBoundingBox
|
|
@@ -44,7 +44,7 @@ class BoundCollection(HasBoundingBox):
|
|
|
44
44
|
self,
|
|
45
45
|
transformation: _Matrix | None = None,
|
|
46
46
|
*,
|
|
47
|
-
scale: float | None = None,
|
|
47
|
+
scale: tuple[float, float] | None = None,
|
|
48
48
|
dx: float | None = None,
|
|
49
49
|
dy: float | None = None,
|
|
50
50
|
):
|
|
@@ -19,7 +19,9 @@ from svg_ultralight.bounding_boxes.type_bounding_box import HasBoundingBox
|
|
|
19
19
|
from svg_ultralight.transformations import new_transformation_matrix, transform_element
|
|
20
20
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
|
-
from lxml.etree import
|
|
22
|
+
from lxml.etree import (
|
|
23
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
24
|
+
)
|
|
23
25
|
|
|
24
26
|
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
25
27
|
|
|
@@ -43,14 +45,11 @@ class BoundElement(HasBoundingBox):
|
|
|
43
45
|
self.elem = element
|
|
44
46
|
self.bbox = bounding_box
|
|
45
47
|
|
|
46
|
-
def _update_elem(self):
|
|
47
|
-
self.elem.attrib["transform"] = self.bbox.transform_string
|
|
48
|
-
|
|
49
48
|
def transform(
|
|
50
49
|
self,
|
|
51
50
|
transformation: _Matrix | None = None,
|
|
52
51
|
*,
|
|
53
|
-
scale: float | None = None,
|
|
52
|
+
scale: tuple[float, float] | None = None,
|
|
54
53
|
dx: float | None = None,
|
|
55
54
|
dy: float | None = None,
|
|
56
55
|
):
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import dataclasses
|
|
10
|
+
import math
|
|
10
11
|
|
|
11
12
|
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
12
13
|
from svg_ultralight.string_conversion import format_number
|
|
@@ -15,85 +16,64 @@ from svg_ultralight.transformations import mat_apply, mat_dot, new_transformatio
|
|
|
15
16
|
_Matrix = tuple[float, float, float, float, float, float]
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
class
|
|
20
|
-
"""Mutable bounding box object for svg_ultralight.
|
|
21
|
-
|
|
22
|
-
:param x: left x value
|
|
23
|
-
:param y: top y value
|
|
24
|
-
:param width: width of the bounding box
|
|
25
|
-
:param height: height of the bounding box
|
|
26
|
-
|
|
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
|
|
29
|
-
to initialize a transformed box with the same transform_string as another box.
|
|
30
|
-
Under most circumstances, it will not be used.
|
|
31
|
-
|
|
32
|
-
:param _transformation: transformation matrix
|
|
33
|
-
|
|
34
|
-
Functions that return a bounding box will return a BoundingBox instance. This
|
|
35
|
-
instance can be transformed (uniform scale and translate only). Transformations
|
|
36
|
-
will be combined and scored to be passed to new_element as a transform value.
|
|
37
|
-
|
|
38
|
-
Define the bbox with x=, y=, width=, height=
|
|
39
|
-
|
|
40
|
-
Transform the BoundingBox by setting these variables. Each time you set x, cx,
|
|
41
|
-
x2, y, cy, y2, width, or height, private transformation value _transformation
|
|
42
|
-
will be updated.
|
|
43
|
-
|
|
44
|
-
The ultimate transformation can be accessed through ``.transform_string``.
|
|
45
|
-
So the workflow will look like :
|
|
46
|
-
|
|
47
|
-
1. Get the bounding box of an svg element
|
|
48
|
-
2. Update the bounding box x, y, width, and height
|
|
49
|
-
3. Transform the original svg element with
|
|
50
|
-
update_element(elem, transform=bbox.transform_string)
|
|
51
|
-
4. The transformed element will lie in the transformed BoundingBox
|
|
52
|
-
|
|
53
|
-
In addition to x, y, width, and height, x2 and y2 can be set to establish the
|
|
54
|
-
right x value or bottom y value.
|
|
55
|
-
|
|
56
|
-
The point of all of this is to simplify stacking and aligning elements. To stack:
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
elem_a = new_element(*args)
|
|
60
|
-
bbox_a = get_bounding_box(elem_a)
|
|
61
|
-
|
|
62
|
-
elem_b = new_element(*args)
|
|
63
|
-
bbox_b = get_bounding_box(elem_b)
|
|
64
|
-
|
|
65
|
-
# align at same x
|
|
66
|
-
bbox_b.x = bbox_a.x
|
|
67
|
-
|
|
68
|
-
# make the same width
|
|
69
|
-
bbox_b.width = bbox_a.width
|
|
19
|
+
class HasBoundingBox(SupportsBounds):
|
|
20
|
+
"""A parent class for BoundElement and others that have a bbox attribute."""
|
|
70
21
|
|
|
71
|
-
|
|
72
|
-
|
|
22
|
+
def __init__(self, bbox: BoundingBox) -> None:
|
|
23
|
+
"""Initialize the HasBoundingBox instance."""
|
|
24
|
+
self.bbox = bbox
|
|
73
25
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
26
|
+
def _get_input_corners(
|
|
27
|
+
self,
|
|
28
|
+
) -> tuple[
|
|
29
|
+
tuple[float, float],
|
|
30
|
+
tuple[float, float],
|
|
31
|
+
tuple[float, float],
|
|
32
|
+
tuple[float, float],
|
|
33
|
+
]:
|
|
34
|
+
"""Get the input corners of the bounding box.
|
|
35
|
+
|
|
36
|
+
:return: four corners counter-clockwise starting at top left
|
|
37
|
+
"""
|
|
38
|
+
x = self.bbox.base_x
|
|
39
|
+
y = self.bbox.base_y
|
|
40
|
+
x2 = x + self.bbox.base_width
|
|
41
|
+
y2 = y + self.bbox.base_height
|
|
42
|
+
return (x, y), (x2, y), (x2, y2), (x, y2)
|
|
43
|
+
|
|
44
|
+
def _get_transformed_corners(
|
|
45
|
+
self,
|
|
46
|
+
) -> tuple[
|
|
47
|
+
tuple[float, float],
|
|
48
|
+
tuple[float, float],
|
|
49
|
+
tuple[float, float],
|
|
50
|
+
tuple[float, float],
|
|
51
|
+
]:
|
|
52
|
+
"""Get the transformed corners of the bounding box.
|
|
77
53
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
54
|
+
:return: four corners counter-clockwise starting at top left, transformed by
|
|
55
|
+
self.transformation
|
|
56
|
+
"""
|
|
57
|
+
c0, c1, c2, c3 = (
|
|
58
|
+
mat_apply(self.bbox.transformation, c) for c in self._get_input_corners()
|
|
59
|
+
)
|
|
60
|
+
return c0, c1, c2, c3
|
|
83
61
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"""Return transformation matrix.
|
|
62
|
+
def _scale_scale_by_uniform_scalar(self, scalar: float) -> None:
|
|
63
|
+
"""Scale the bounding box uniformly by a factor.
|
|
87
64
|
|
|
88
|
-
:
|
|
65
|
+
:param scale: scale factor
|
|
66
|
+
Unlike self.scale, this does not set the scale, but scales the scale. So if
|
|
67
|
+
the current scale is (2, 6), and you call this with a scalar of 2, the new
|
|
68
|
+
scale will be (4, 12).
|
|
89
69
|
"""
|
|
90
|
-
|
|
70
|
+
self.transform(scale=(scalar, scalar))
|
|
91
71
|
|
|
92
72
|
def transform(
|
|
93
73
|
self,
|
|
94
74
|
transformation: _Matrix | None = None,
|
|
95
75
|
*,
|
|
96
|
-
scale: float | None = None,
|
|
76
|
+
scale: tuple[float, float] | None = None,
|
|
97
77
|
dx: float | None = None,
|
|
98
78
|
dy: float | None = None,
|
|
99
79
|
):
|
|
@@ -112,10 +92,10 @@ class BoundingBox(SupportsBounds):
|
|
|
112
92
|
when applying a transformation from another bounding box instance.
|
|
113
93
|
"""
|
|
114
94
|
tmat = new_transformation_matrix(transformation, scale=scale, dx=dx, dy=dy)
|
|
115
|
-
self.
|
|
95
|
+
self.bbox.transformation = mat_dot(tmat, self.bbox.transformation)
|
|
116
96
|
|
|
117
97
|
@property
|
|
118
|
-
def scale(self) -> float:
|
|
98
|
+
def scale(self) -> tuple[float, float]:
|
|
119
99
|
"""Get scale of the bounding box.
|
|
120
100
|
|
|
121
101
|
:return: uniform scale of the bounding box
|
|
@@ -127,10 +107,11 @@ class BoundingBox(SupportsBounds):
|
|
|
127
107
|
width*scale, height => height*scale, scale => scale*scale. This matches how
|
|
128
108
|
scale works in almost every other context.
|
|
129
109
|
"""
|
|
130
|
-
|
|
110
|
+
xx, xy, yx, yy, *_ = self.bbox.transformation
|
|
111
|
+
return math.sqrt(xx * xx + xy * xy), math.sqrt(yx * yx + yy * yy)
|
|
131
112
|
|
|
132
113
|
@scale.setter
|
|
133
|
-
def scale(self, value: float) -> None:
|
|
114
|
+
def scale(self, value: tuple[float, float]) -> None:
|
|
134
115
|
"""Scale the bounding box by a uniform factor.
|
|
135
116
|
|
|
136
117
|
:param value: new scale value
|
|
@@ -142,7 +123,8 @@ class BoundingBox(SupportsBounds):
|
|
|
142
123
|
`scale = 2` -> ignore whatever scale was previously defined and set scale to 2
|
|
143
124
|
`scale *= 2` -> make it twice as big as it was.
|
|
144
125
|
"""
|
|
145
|
-
self.
|
|
126
|
+
new_scale = value[0] / self.scale[0], value[1] / self.scale[1]
|
|
127
|
+
self.transform(scale=new_scale)
|
|
146
128
|
|
|
147
129
|
@property
|
|
148
130
|
def x(self) -> float:
|
|
@@ -150,13 +132,13 @@ class BoundingBox(SupportsBounds):
|
|
|
150
132
|
|
|
151
133
|
:return: internal _x value transformed by scale and translation
|
|
152
134
|
"""
|
|
153
|
-
return
|
|
135
|
+
return min(x for x, _ in self._get_transformed_corners())
|
|
154
136
|
|
|
155
137
|
@x.setter
|
|
156
|
-
def x(self, value: float)
|
|
157
|
-
"""
|
|
138
|
+
def x(self, value: float):
|
|
139
|
+
"""Set the x coordinate of the left edge of the bounding box.
|
|
158
140
|
|
|
159
|
-
:param value: new x
|
|
141
|
+
:param value: the new x coordinate of the left edge of the bounding box
|
|
160
142
|
"""
|
|
161
143
|
self.transform(dx=value - self.x)
|
|
162
144
|
|
|
@@ -182,7 +164,7 @@ class BoundingBox(SupportsBounds):
|
|
|
182
164
|
|
|
183
165
|
:return: transformed x + transformed width
|
|
184
166
|
"""
|
|
185
|
-
return
|
|
167
|
+
return max(x for x, _ in self._get_transformed_corners())
|
|
186
168
|
|
|
187
169
|
@x2.setter
|
|
188
170
|
def x2(self, value: float) -> None:
|
|
@@ -198,7 +180,7 @@ class BoundingBox(SupportsBounds):
|
|
|
198
180
|
|
|
199
181
|
:return: internal _y value transformed by scale and translation
|
|
200
182
|
"""
|
|
201
|
-
return
|
|
183
|
+
return min(y for _, y in self._get_transformed_corners())
|
|
202
184
|
|
|
203
185
|
@y.setter
|
|
204
186
|
def y(self, value: float) -> None:
|
|
@@ -230,7 +212,7 @@ class BoundingBox(SupportsBounds):
|
|
|
230
212
|
|
|
231
213
|
:return: transformed y + transformed height
|
|
232
214
|
"""
|
|
233
|
-
return
|
|
215
|
+
return max(y for _, y in self._get_transformed_corners())
|
|
234
216
|
|
|
235
217
|
@y2.setter
|
|
236
218
|
def y2(self, value: float) -> None:
|
|
@@ -246,7 +228,7 @@ class BoundingBox(SupportsBounds):
|
|
|
246
228
|
|
|
247
229
|
:return: internal _width value transformed by scale
|
|
248
230
|
"""
|
|
249
|
-
return self.
|
|
231
|
+
return self.x2 - self.x
|
|
250
232
|
|
|
251
233
|
@width.setter
|
|
252
234
|
def width(self, value: float) -> None:
|
|
@@ -259,7 +241,7 @@ class BoundingBox(SupportsBounds):
|
|
|
259
241
|
"""
|
|
260
242
|
current_x = self.x
|
|
261
243
|
current_y = self.y
|
|
262
|
-
self.
|
|
244
|
+
self._scale_scale_by_uniform_scalar(value / self.width)
|
|
263
245
|
self.x = current_x
|
|
264
246
|
self.y = current_y
|
|
265
247
|
|
|
@@ -269,7 +251,7 @@ class BoundingBox(SupportsBounds):
|
|
|
269
251
|
|
|
270
252
|
:return: internal _height value transformed by scale
|
|
271
253
|
"""
|
|
272
|
-
return self.
|
|
254
|
+
return self.y2 - self.y
|
|
273
255
|
|
|
274
256
|
@height.setter
|
|
275
257
|
def height(self, value: float) -> None:
|
|
@@ -291,7 +273,96 @@ class BoundingBox(SupportsBounds):
|
|
|
291
273
|
Use with
|
|
292
274
|
``update_element(elem, transform=bbox.transform_string)``
|
|
293
275
|
"""
|
|
294
|
-
return f"matrix({' '.join(map(format_number, self.transformation))})"
|
|
276
|
+
return f"matrix({' '.join(map(format_number, self.bbox.transformation))})"
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@dataclasses.dataclass
|
|
280
|
+
class BoundingBox(HasBoundingBox):
|
|
281
|
+
"""Mutable bounding box object for svg_ultralight.
|
|
282
|
+
|
|
283
|
+
:param x: left x value
|
|
284
|
+
:param y: top y value
|
|
285
|
+
:param width: width of the bounding box
|
|
286
|
+
:param height: height of the bounding box
|
|
287
|
+
|
|
288
|
+
The below optional parameter, in addition to the required parameters, captures
|
|
289
|
+
the entire state of a BoundingBox instance. It could be used to make a copy or
|
|
290
|
+
to initialize a transformed box with the same transform_string as another box.
|
|
291
|
+
Under most circumstances, it will not be used.
|
|
292
|
+
|
|
293
|
+
:param transformation: transformation matrix
|
|
294
|
+
|
|
295
|
+
Functions that return a bounding box will return a BoundingBox instance. This
|
|
296
|
+
instance can be transformed (uniform scale and translate only). Transformations
|
|
297
|
+
will be combined and scored to be passed to new_element as a transform value.
|
|
298
|
+
|
|
299
|
+
Define the bbox with x=, y=, width=, height=
|
|
300
|
+
|
|
301
|
+
Transform the BoundingBox by setting these variables. Each time you set x, cx,
|
|
302
|
+
x2, y, cy, y2, width, or height, private transformation value transformation
|
|
303
|
+
will be updated.
|
|
304
|
+
|
|
305
|
+
The ultimate transformation can be accessed through ``.transform_string``.
|
|
306
|
+
So the workflow will look like :
|
|
307
|
+
|
|
308
|
+
1. Get the bounding box of an svg element
|
|
309
|
+
2. Update the bounding box x, y, width, and height
|
|
310
|
+
3. Transform the original svg element with
|
|
311
|
+
update_element(elem, transform=bbox.transform_string)
|
|
312
|
+
4. The transformed element will lie in the transformed BoundingBox
|
|
313
|
+
|
|
314
|
+
In addition to x, y, width, and height, x2 and y2 can be set to establish the
|
|
315
|
+
right x value or bottom y value.
|
|
316
|
+
|
|
317
|
+
The point of all of this is to simplify stacking and aligning elements. To stack:
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
elem_a = new_element(*args)
|
|
321
|
+
bbox_a = get_bounding_box(elem_a)
|
|
322
|
+
|
|
323
|
+
elem_b = new_element(*args)
|
|
324
|
+
bbox_b = get_bounding_box(elem_b)
|
|
325
|
+
|
|
326
|
+
# align at same x
|
|
327
|
+
bbox_b.x = bbox_a.x
|
|
328
|
+
|
|
329
|
+
# make the same width
|
|
330
|
+
bbox_b.width = bbox_a.width
|
|
331
|
+
|
|
332
|
+
# stack a on top of b
|
|
333
|
+
bbox_a.y2 = bbox_b.y
|
|
334
|
+
|
|
335
|
+
update_element(elem_a, transform=bbox_a.transform_string)
|
|
336
|
+
update_element(elem_b, transform=bbox_b.transform_string)
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
base_x: float = dataclasses.field(init=False)
|
|
340
|
+
base_y: float = dataclasses.field(init=False)
|
|
341
|
+
base_width: float = dataclasses.field(init=False)
|
|
342
|
+
base_height: float = dataclasses.field(init=False)
|
|
343
|
+
transformation: _Matrix = dataclasses.field(init=False)
|
|
344
|
+
|
|
345
|
+
def __init__(
|
|
346
|
+
self,
|
|
347
|
+
x: float,
|
|
348
|
+
y: float,
|
|
349
|
+
width: float,
|
|
350
|
+
height: float,
|
|
351
|
+
transformation: _Matrix = (1, 0, 0, 1, 0, 0),
|
|
352
|
+
) -> None:
|
|
353
|
+
"""Initialize a BoundingBox instance.
|
|
354
|
+
|
|
355
|
+
:param x: left x value
|
|
356
|
+
:param y: top y value
|
|
357
|
+
:param width: width of the bounding box
|
|
358
|
+
:param height: height of the bounding box
|
|
359
|
+
"""
|
|
360
|
+
self.base_x = x
|
|
361
|
+
self.base_y = y
|
|
362
|
+
self.base_width = width
|
|
363
|
+
self.base_height = height
|
|
364
|
+
self.transformation = transformation
|
|
365
|
+
self.bbox = self
|
|
295
366
|
|
|
296
367
|
def merge(self, *others: BoundingBox) -> BoundingBox:
|
|
297
368
|
"""Create a bounding box around all other bounding boxes.
|
|
@@ -321,181 +392,3 @@ class BoundingBox(SupportsBounds):
|
|
|
321
392
|
min_y = min(x.y for x in bboxes)
|
|
322
393
|
max_y = max(x.y + x.height for x in bboxes)
|
|
323
394
|
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
|