svg-ultralight 0.64.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.
- svg_ultralight/__init__.py +112 -0
- svg_ultralight/animate.py +40 -0
- svg_ultralight/attrib_hints.py +14 -0
- svg_ultralight/bounding_boxes/__init__.py +5 -0
- svg_ultralight/bounding_boxes/bound_helpers.py +200 -0
- svg_ultralight/bounding_boxes/padded_text_initializers.py +442 -0
- svg_ultralight/bounding_boxes/supports_bounds.py +167 -0
- svg_ultralight/bounding_boxes/type_bound_collection.py +74 -0
- svg_ultralight/bounding_boxes/type_bound_element.py +68 -0
- svg_ultralight/bounding_boxes/type_bounding_box.py +432 -0
- svg_ultralight/bounding_boxes/type_padded_list.py +208 -0
- svg_ultralight/bounding_boxes/type_padded_text.py +502 -0
- svg_ultralight/constructors/__init__.py +14 -0
- svg_ultralight/constructors/new_element.py +117 -0
- svg_ultralight/font_tools/__init__.py +5 -0
- svg_ultralight/font_tools/comp_results.py +291 -0
- svg_ultralight/font_tools/font_info.py +849 -0
- svg_ultralight/image_ops.py +156 -0
- svg_ultralight/inkscape.py +261 -0
- svg_ultralight/layout.py +291 -0
- svg_ultralight/main.py +183 -0
- svg_ultralight/metadata.py +122 -0
- svg_ultralight/nsmap.py +36 -0
- svg_ultralight/py.typed +5 -0
- svg_ultralight/query.py +254 -0
- svg_ultralight/read_svg.py +58 -0
- svg_ultralight/root_elements.py +96 -0
- svg_ultralight/string_conversion.py +244 -0
- svg_ultralight/strings/__init__.py +21 -0
- svg_ultralight/strings/svg_strings.py +106 -0
- svg_ultralight/transformations.py +152 -0
- svg_ultralight/unit_conversion.py +247 -0
- svg_ultralight-0.64.0.dist-info/METADATA +208 -0
- svg_ultralight-0.64.0.dist-info/RECORD +35 -0
- svg_ultralight-0.64.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""A list of padded text elements that transform as one.
|
|
2
|
+
|
|
3
|
+
This class simplifies working with multiple PaddedText elements as a group.
|
|
4
|
+
|
|
5
|
+
In addition to the usual PaddedText getters and setters (x, cx, x2, y, cy, y2, width,
|
|
6
|
+
height), it also provides tight versions of those (tx, tcx, tx2, ty, tcy, ty2,
|
|
7
|
+
twidth, theight) that operate on the unpadded bounding box. Get and set these
|
|
8
|
+
dimension attributes with the get_dim and set_dim methods.
|
|
9
|
+
|
|
10
|
+
Will additionally stack and align the elements. Stack (any method with a `name`
|
|
11
|
+
argument) will expect a one of x, cx, x2, y, cy, or y2 (or their tight equivalents
|
|
12
|
+
tx, tcx, tx2, ty, tcy, ty2).
|
|
13
|
+
|
|
14
|
+
A PaddedList instance contains no information except pointers to the PaddedText
|
|
15
|
+
elements, so you could, for instance, create a jagged arrangements of text elements
|
|
16
|
+
with
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
padded_list.align('x')
|
|
20
|
+
padded_list.stack()
|
|
21
|
+
padded_list[::2].transform(dx=10)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
:author: Shay Hill
|
|
25
|
+
:created: 2025-11-17
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import itertools as it
|
|
29
|
+
from typing import cast, overload
|
|
30
|
+
|
|
31
|
+
from svg_ultralight.attrib_hints import ElemAttrib
|
|
32
|
+
from svg_ultralight.bounding_boxes.bound_helpers import new_bound_union
|
|
33
|
+
from svg_ultralight.bounding_boxes.type_bound_element import BoundElement
|
|
34
|
+
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
35
|
+
from svg_ultralight.bounding_boxes.type_padded_text import PaddedText
|
|
36
|
+
from svg_ultralight.constructors import update_element
|
|
37
|
+
from svg_ultralight.transformations import new_transformation_matrix
|
|
38
|
+
|
|
39
|
+
_Matrix = tuple[float, float, float, float, float, float]
|
|
40
|
+
|
|
41
|
+
# fmt: off
|
|
42
|
+
_BBOX_DIMS = {
|
|
43
|
+
"x", "cx", "x2", "y", "cy", "y2", "width", "height",
|
|
44
|
+
"tx", "tcx", "tx2", "ty", "tcy", "ty2", "twidth", "theight",
|
|
45
|
+
}
|
|
46
|
+
# fmt: on
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PaddedList:
|
|
50
|
+
"""A list of padded text elements that transform as one."""
|
|
51
|
+
|
|
52
|
+
def __init__(self, *plems: PaddedText) -> None:
|
|
53
|
+
"""Initialize with a list of padded text elements."""
|
|
54
|
+
self.plems = list(plems)
|
|
55
|
+
|
|
56
|
+
@overload
|
|
57
|
+
def __getitem__(self, idx: slice) -> "PaddedList": ...
|
|
58
|
+
|
|
59
|
+
@overload
|
|
60
|
+
def __getitem__(self, idx: int) -> PaddedText: ...
|
|
61
|
+
|
|
62
|
+
def __getitem__(self, idx: slice | int) -> "PaddedList | PaddedText":
|
|
63
|
+
"""Get one or more padded text elements."""
|
|
64
|
+
if isinstance(idx, int):
|
|
65
|
+
return self.plems[idx]
|
|
66
|
+
return PaddedList(*self.plems[idx])
|
|
67
|
+
|
|
68
|
+
def append(self, ptext: PaddedText) -> None:
|
|
69
|
+
"""Append a padded text element to the list."""
|
|
70
|
+
self.plems.append(ptext)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def bbox(self) -> BoundingBox:
|
|
74
|
+
"""The bounding box of the padded text elements."""
|
|
75
|
+
return BoundingBox.union(*(x.bbox for x in self.plems))
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def tbox(self) -> BoundingBox:
|
|
79
|
+
"""The unpadded bounding box of the padded text elements.
|
|
80
|
+
|
|
81
|
+
t for true or tight.
|
|
82
|
+
"""
|
|
83
|
+
return BoundingBox.union(*(x.tbox for x in self.plems))
|
|
84
|
+
|
|
85
|
+
def transform(
|
|
86
|
+
self,
|
|
87
|
+
transformation: _Matrix | None = None,
|
|
88
|
+
*,
|
|
89
|
+
scale: tuple[float, float] | float | None = None,
|
|
90
|
+
dx: float | None = None,
|
|
91
|
+
dy: float | None = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
"""Apply a transformation to all the padded text elements."""
|
|
94
|
+
for p in self.plems:
|
|
95
|
+
p.transform(transformation, scale=scale, dx=dx, dy=dy)
|
|
96
|
+
|
|
97
|
+
def transform_preserve_sidebearings(
|
|
98
|
+
self,
|
|
99
|
+
transformation: _Matrix | None = None,
|
|
100
|
+
*,
|
|
101
|
+
scale: tuple[float, float] | float | None = None,
|
|
102
|
+
dx: float | None = None,
|
|
103
|
+
dy: float | None = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Apply a transformation to all the padded text elements."""
|
|
106
|
+
for p in self.plems:
|
|
107
|
+
p.transform_preserve_sidebearings(transformation, scale=scale, dx=dx, dy=dy)
|
|
108
|
+
|
|
109
|
+
def union(self, **attribs: ElemAttrib) -> BoundElement:
|
|
110
|
+
"""Return a single bound element containing all the padded text elements."""
|
|
111
|
+
union = new_bound_union(*self.plems)
|
|
112
|
+
_ = update_element(union.elem, **attribs)
|
|
113
|
+
return union
|
|
114
|
+
|
|
115
|
+
def tunion(self, **attribs: ElemAttrib) -> BoundElement:
|
|
116
|
+
"""Return a single bound element containing all the unpadded text elements.
|
|
117
|
+
|
|
118
|
+
This version uses the unpadded bounding boxes of the padded text elements.
|
|
119
|
+
"""
|
|
120
|
+
union = self.union(**attribs)
|
|
121
|
+
union.bbox = self.tbox
|
|
122
|
+
return union
|
|
123
|
+
|
|
124
|
+
def padded_union(self, **attribs: ElemAttrib) -> PaddedText:
|
|
125
|
+
"""Return a PaddedText inst where the elem is a `g` of all the padded text."""
|
|
126
|
+
union = self.tunion(**attribs)
|
|
127
|
+
tpad = self.tbox.y - self.bbox.y
|
|
128
|
+
rpad = self.bbox.x2 - self.tbox.x2
|
|
129
|
+
bpad = self.bbox.y2 - self.tbox.y2
|
|
130
|
+
lpad = self.tbox.x - self.bbox.x
|
|
131
|
+
return PaddedText(union.elem, union.bbox, tpad, rpad, bpad, lpad)
|
|
132
|
+
|
|
133
|
+
def get_dim(self, dim: str) -> float:
|
|
134
|
+
"""Get a dimension from bbox or tbox."""
|
|
135
|
+
if dim not in _BBOX_DIMS:
|
|
136
|
+
msg = "Invalid bbox dimension '{dim}'"
|
|
137
|
+
raise ValueError(msg)
|
|
138
|
+
box = self.tbox if dim.startswith("t") else self.bbox
|
|
139
|
+
dim = dim.removeprefix("t")
|
|
140
|
+
if dim not in ("x", "cx", "x2", "y", "cy", "y2", "width", "height"):
|
|
141
|
+
msg = f"Cannot get dimension '{dim}'"
|
|
142
|
+
raise AttributeError(msg)
|
|
143
|
+
return cast("float", getattr(box, dim))
|
|
144
|
+
|
|
145
|
+
def new_tmat(self, dim: str, value: float) -> _Matrix:
|
|
146
|
+
"""Create a transformation matrix to set a bbox dimension to a value.
|
|
147
|
+
|
|
148
|
+
:param dim: One of 'x', 'cx', 'x2', 'y', 'cy', 'y2', 'width', or 'height'
|
|
149
|
+
or any of the same prefixed with 't'.
|
|
150
|
+
"""
|
|
151
|
+
if dim not in _BBOX_DIMS:
|
|
152
|
+
msg = "Invalid bbox dimension '{dim}'"
|
|
153
|
+
raise ValueError(msg)
|
|
154
|
+
current_value = self.get_dim(dim)
|
|
155
|
+
dim = dim.removeprefix("t")
|
|
156
|
+
if dim in ("x", "cx", "x2"):
|
|
157
|
+
return new_transformation_matrix(dx=value - current_value)
|
|
158
|
+
if dim in ("y", "cy", "y2"):
|
|
159
|
+
return new_transformation_matrix(dy=value - current_value)
|
|
160
|
+
if dim in ("width", "height"):
|
|
161
|
+
return new_transformation_matrix(scale=value / current_value)
|
|
162
|
+
msg = f"Cannot set dimension '{dim}'"
|
|
163
|
+
raise AttributeError(msg)
|
|
164
|
+
|
|
165
|
+
def set_dim(self, **dims: float) -> None:
|
|
166
|
+
"""Set a dimension on bbox or tbox."""
|
|
167
|
+
for dim, value in dims.items():
|
|
168
|
+
if dim not in _BBOX_DIMS:
|
|
169
|
+
msg = "Invalid bbox dim '{dim}'"
|
|
170
|
+
raise ValueError(msg)
|
|
171
|
+
tmat = self.new_tmat(dim, value)
|
|
172
|
+
self.transform(transformation=tmat)
|
|
173
|
+
|
|
174
|
+
def set(self, **attribs: ElemAttrib) -> None:
|
|
175
|
+
"""Set an attribute on all padded text elements."""
|
|
176
|
+
for p in self.plems:
|
|
177
|
+
_ = update_element(p.elem, **attribs)
|
|
178
|
+
|
|
179
|
+
def align(self, dimension: str, value: float | None = None) -> None:
|
|
180
|
+
"""Align the specified edges or centers of the padded text elements.
|
|
181
|
+
|
|
182
|
+
:param dimension: One of 'x', 'cx', 'x2', 'y', 'cy', or 'y2' or any of the
|
|
183
|
+
same prefixed with 't'.
|
|
184
|
+
:param value: If provided, align to this value. Otherwise, align to the
|
|
185
|
+
corresponding edge or center of the bounding box of all the padded
|
|
186
|
+
text elements.
|
|
187
|
+
"""
|
|
188
|
+
if value is None:
|
|
189
|
+
value = self.get_dim(dimension)
|
|
190
|
+
for i, plem in enumerate(self.plems):
|
|
191
|
+
tmat = self[i : i + 1].new_tmat(dimension, value)
|
|
192
|
+
plem.transform(transformation=tmat)
|
|
193
|
+
|
|
194
|
+
def stack(self, scale: float = 1, gap: float | None = None) -> None:
|
|
195
|
+
"""Stack the gapded text elements vertically with a gap.
|
|
196
|
+
|
|
197
|
+
:param scale: If provided, scale the native line height (ascent + descent)
|
|
198
|
+
of the text elements by this factor.
|
|
199
|
+
:param gap: If provided, add this much space between the text elements. This
|
|
200
|
+
is an alternate strategy for when you are using fonts of different sizes.
|
|
201
|
+
If the gap parameter is passed, the scale parameter is ignored.
|
|
202
|
+
"""
|
|
203
|
+
if gap is not None:
|
|
204
|
+
for above, below in it.pairwise(self.plems):
|
|
205
|
+
below.y = above.y2 + gap
|
|
206
|
+
return
|
|
207
|
+
for above, below in it.pairwise(self.plems):
|
|
208
|
+
below.y = above.y + above.height * scale
|