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,248 +1,247 @@
|
|
|
1
|
-
"""Convert between absolute units.
|
|
2
|
-
|
|
3
|
-
Model everything in user units, then use "width" and "height" in the svg root element
|
|
4
|
-
to scale these units to the desired size.
|
|
5
|
-
|
|
6
|
-
I borrowed test values and and conventions from Inkscape's `inkek.units.py`.
|
|
7
|
-
|
|
8
|
-
:author: Shay Hill
|
|
9
|
-
:created: 2023-02-12
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import dataclasses
|
|
15
|
-
import enum
|
|
16
|
-
import re
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
str,
|
|
55
|
-
tuple[
|
|
56
|
-
tuple[
|
|
57
|
-
tuple[
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
:
|
|
75
|
-
:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
| str | "55.
|
|
89
|
-
| str | "
|
|
90
|
-
| str
|
|
91
|
-
| (
|
|
92
|
-
| (
|
|
93
|
-
| (
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
""
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
value
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
value
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
value_as_str
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
result =
|
|
211
|
-
result
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
result =
|
|
221
|
-
result
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
:
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
result =
|
|
231
|
-
result
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return self.__mul__(1.0 / scalar)
|
|
1
|
+
"""Convert between absolute units.
|
|
2
|
+
|
|
3
|
+
Model everything in user units, then use "width" and "height" in the svg root element
|
|
4
|
+
to scale these units to the desired size.
|
|
5
|
+
|
|
6
|
+
I borrowed test values and and conventions from Inkscape's `inkek.units.py`.
|
|
7
|
+
|
|
8
|
+
:author: Shay Hill
|
|
9
|
+
:created: 2023-02-12
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import dataclasses
|
|
15
|
+
import enum
|
|
16
|
+
import re
|
|
17
|
+
|
|
18
|
+
from svg_ultralight.string_conversion import format_number
|
|
19
|
+
|
|
20
|
+
# units per inch
|
|
21
|
+
_UPI = 96
|
|
22
|
+
|
|
23
|
+
# units per centimeter
|
|
24
|
+
_UPC = 96 / 2.54
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Unit(enum.Enum):
|
|
28
|
+
"""SVG Units of measurement.
|
|
29
|
+
|
|
30
|
+
Value is (unit conversion, unit specifier)
|
|
31
|
+
|
|
32
|
+
The unit specifier string are how various units are identified in SVG.
|
|
33
|
+
e.g., "44in"
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
IN = "in", _UPI # inches
|
|
37
|
+
PT = "pt", 4 / 3 # points
|
|
38
|
+
PX = "px", 1 # pixels
|
|
39
|
+
MM = "mm", _UPC / 10 # millimeters
|
|
40
|
+
CM = "cm", _UPC # centimeters
|
|
41
|
+
M = "m", _UPC * 100 # meters
|
|
42
|
+
KM = "km", _UPC * 100000 # kilometers
|
|
43
|
+
Q = "Q", _UPC / 40 # quarter-millimeters
|
|
44
|
+
PC = "pc", _UPI / 6 # picas
|
|
45
|
+
YD = "yd", _UPI * 36 # yards
|
|
46
|
+
FT = "ft", _UPI * 12 # feet
|
|
47
|
+
USER = "", 1 # "user units" without a unit specifier
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# the arguments this module will attempt to interpret as a string with a unit specifier
|
|
51
|
+
MeasurementArg = (
|
|
52
|
+
float
|
|
53
|
+
| str
|
|
54
|
+
| tuple[str, str]
|
|
55
|
+
| tuple[float, str]
|
|
56
|
+
| tuple[str, Unit]
|
|
57
|
+
| tuple[float, Unit]
|
|
58
|
+
| Unit
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
_UNIT_SPECIFIER2UNIT = {x.value[0]: x for x in Unit}
|
|
62
|
+
|
|
63
|
+
_UNIT_SPECIFIERS = [x.value[0] for x in Unit]
|
|
64
|
+
_NUMBER = r"([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?"
|
|
65
|
+
_UNIT_RE = re.compile(rf"(?P<unit>{'|'.join(_UNIT_SPECIFIERS)})")
|
|
66
|
+
_NUMBER_RE = re.compile(rf"(?P<number>{_NUMBER})")
|
|
67
|
+
_NUMBER_AND_UNIT = re.compile(rf"^{_NUMBER_RE.pattern}{_UNIT_RE.pattern}$")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _parse_unit(measurement_arg: MeasurementArg) -> tuple[float, Unit]:
|
|
71
|
+
"""Split the value and unit from a string.
|
|
72
|
+
|
|
73
|
+
:param measurement_arg: The value to parse (e.g. "55.32px")
|
|
74
|
+
:return: A tuple of the value and Unit
|
|
75
|
+
:raise ValueError: If the value cannot be parsed
|
|
76
|
+
|
|
77
|
+
Take a value such as "55.32px" and return (55.32, Unit.PX). Preserves non-units,
|
|
78
|
+
so "55.32" returns (55.32, Unit.USER) These are actually pixels, but you don't
|
|
79
|
+
want "px" in your viewbox calls. It is best to work in non-specified "user units"
|
|
80
|
+
and then set the svg width and height to an specified unit.
|
|
81
|
+
|
|
82
|
+
Can handle a lot of args:
|
|
83
|
+
|
|
84
|
+
| arg type | example | result |
|
|
85
|
+
| ------------- | ------------------ | ------------------ |
|
|
86
|
+
| float | 55.32 | (55.32, Unit.USER) |
|
|
87
|
+
| str | "55.32px" | (55.32, Unit.PX) |
|
|
88
|
+
| str | "55.32" | (55.32, Unit.USER) |
|
|
89
|
+
| str | "px" | (0.0, Unit.PX) |
|
|
90
|
+
| (str, str) | ("55.32", "px") | (55.32, Unit.PX) |
|
|
91
|
+
| (float, str) | (55.32, "px") | (55.32, Unit.PX) |
|
|
92
|
+
| (str, Unit) | ("55.32", Unit.PX) | (55.32, Unit.PX) |
|
|
93
|
+
| (float, Unit) | (55.32, Unit.PX) | (55.32, Unit.PX) |
|
|
94
|
+
| Unit | Unit.PX | (0.0, Unit.PX) |
|
|
95
|
+
| Measurement | Measurement("3in") | (3.0, Unit.IN) |
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
failure_msg = f"Cannot parse value and unit from {measurement_arg}"
|
|
99
|
+
unit: str | Unit
|
|
100
|
+
try:
|
|
101
|
+
if isinstance(measurement_arg, tuple):
|
|
102
|
+
number, unit = float(measurement_arg[0]), measurement_arg[1]
|
|
103
|
+
if isinstance(unit, Unit):
|
|
104
|
+
return number, unit
|
|
105
|
+
return number, _UNIT_SPECIFIER2UNIT[unit]
|
|
106
|
+
|
|
107
|
+
if isinstance(measurement_arg, (int, float)):
|
|
108
|
+
return _parse_unit((measurement_arg, ""))
|
|
109
|
+
|
|
110
|
+
if isinstance(measurement_arg, Unit):
|
|
111
|
+
return _parse_unit((0, measurement_arg))
|
|
112
|
+
|
|
113
|
+
if number_unit := _NUMBER_AND_UNIT.match(str(measurement_arg)):
|
|
114
|
+
return _parse_unit((number_unit["number"], number_unit["unit"]))
|
|
115
|
+
|
|
116
|
+
if unit_only := _UNIT_RE.match(str(measurement_arg)):
|
|
117
|
+
return _parse_unit((0, unit_only["unit"]))
|
|
118
|
+
|
|
119
|
+
except (ValueError, KeyError) as e:
|
|
120
|
+
raise ValueError(failure_msg) from e
|
|
121
|
+
|
|
122
|
+
raise ValueError(failure_msg)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclasses.dataclass
|
|
126
|
+
class Measurement:
|
|
127
|
+
"""Measurement with unit of measurement.
|
|
128
|
+
|
|
129
|
+
Converts to and stores the value in user units. Also retains the input units so
|
|
130
|
+
you can update the value then convert back.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
value: float
|
|
134
|
+
native_unit: Unit
|
|
135
|
+
|
|
136
|
+
def __init__(self, measurement_arg: MeasurementArg) -> None:
|
|
137
|
+
"""Create a measurement from a string or float.
|
|
138
|
+
|
|
139
|
+
:param measurement_arg: a float (user units) or string with unit specifier.
|
|
140
|
+
:raises ValueError: if the input units cannot be identified
|
|
141
|
+
"""
|
|
142
|
+
value, self.native_unit = _parse_unit(measurement_arg)
|
|
143
|
+
self.value = value * self.native_unit.value[1]
|
|
144
|
+
|
|
145
|
+
def get_value(self, unit: Unit | None = None) -> float:
|
|
146
|
+
"""Get the measurement in the specified unit.
|
|
147
|
+
|
|
148
|
+
:param unit: optional unit to convert to
|
|
149
|
+
:return: value in specified units
|
|
150
|
+
|
|
151
|
+
It's best to do all math with self.value, but this is here for conversion
|
|
152
|
+
with less precision loss.
|
|
153
|
+
"""
|
|
154
|
+
if unit is None:
|
|
155
|
+
return self.value
|
|
156
|
+
if isinstance(unit, str):
|
|
157
|
+
unit = _UNIT_SPECIFIER2UNIT[unit]
|
|
158
|
+
return self.value / unit.value[1]
|
|
159
|
+
|
|
160
|
+
def get_tuple(self, unit: Unit | None = None) -> tuple[float, Unit]:
|
|
161
|
+
"""Get the measurement as a tuple of value and unit.
|
|
162
|
+
|
|
163
|
+
:param unit: optional unit to convert to
|
|
164
|
+
:return: value in specified as a tuple
|
|
165
|
+
"""
|
|
166
|
+
return self.get_value(unit), unit or Unit.USER
|
|
167
|
+
|
|
168
|
+
def get_str(self, unit: Unit | None = None) -> str:
|
|
169
|
+
"""Get the measurement in the specified unit as a string.
|
|
170
|
+
|
|
171
|
+
:param optional unit: the unit to convert to
|
|
172
|
+
:return: the measurement in the specified unit as a string
|
|
173
|
+
|
|
174
|
+
The input arguments for groups of measurements are less flexible than for
|
|
175
|
+
single measurements. Single measurements can be defined by something like
|
|
176
|
+
`(1, "in")`, but groups can be passed as single or tuples, so there is no way
|
|
177
|
+
to differentiate between (1, "in") and "1in" or (1, "in") as ("1", "0in").
|
|
178
|
+
That is a limitation, but doint it that way preserved the flexibility (and
|
|
179
|
+
backwards compatibility) of being able to define padding as "1in" everywhere
|
|
180
|
+
or (1, 2, 3, 4) for top, right, bottom, left.
|
|
181
|
+
|
|
182
|
+
The string from this method is different from the string in the `get_svg`
|
|
183
|
+
method, because this string will print a full printable precision, while the
|
|
184
|
+
svg string will print a reduced precision. So this string can be used to as
|
|
185
|
+
an argument to pad or print_width without losing precision.
|
|
186
|
+
"""
|
|
187
|
+
value, unit = self.get_tuple(unit)
|
|
188
|
+
return f"{value}{unit.value[0]}"
|
|
189
|
+
|
|
190
|
+
def get_svg(self, unit: Unit | None = None) -> str:
|
|
191
|
+
"""Get the measurement in the specified unit as it would be written in svg.
|
|
192
|
+
|
|
193
|
+
:param optional unit: the unit to convert to
|
|
194
|
+
:return: the measurement in the specified unit, always as a string
|
|
195
|
+
|
|
196
|
+
Rounds values to 6 decimal places as recommended by svg guidance online.
|
|
197
|
+
Higher precision just changes file size without imroving quality.
|
|
198
|
+
"""
|
|
199
|
+
_, unit = self.get_tuple(unit)
|
|
200
|
+
value_as_str = format_number(self.get_value(unit))
|
|
201
|
+
return f"{value_as_str}{unit.value[0]}"
|
|
202
|
+
|
|
203
|
+
def __add__(self, other: Measurement) -> Measurement:
|
|
204
|
+
"""Add two measurements.
|
|
205
|
+
|
|
206
|
+
:param other: the other measurement
|
|
207
|
+
:return: the sum of the two measurements in self native unit
|
|
208
|
+
"""
|
|
209
|
+
result = Measurement(self.native_unit)
|
|
210
|
+
result.value = self.value + other.value
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
def __sub__(self, other: Measurement) -> Measurement:
|
|
214
|
+
"""Subtract two measurements.
|
|
215
|
+
|
|
216
|
+
:param other: the other measurement
|
|
217
|
+
:return: the difference of the two measurements in self native unit
|
|
218
|
+
"""
|
|
219
|
+
result = Measurement(self.native_unit)
|
|
220
|
+
result.value = self.value - other.value
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
def __mul__(self, scalar: float) -> Measurement:
|
|
224
|
+
"""Multiply a measurement by a scalar.
|
|
225
|
+
|
|
226
|
+
:param scalar: the scalar to multiply by
|
|
227
|
+
:return: the measurement multiplied by the scalar in self native unit
|
|
228
|
+
"""
|
|
229
|
+
result = Measurement(self.native_unit)
|
|
230
|
+
result.value = self.value * scalar
|
|
231
|
+
return result
|
|
232
|
+
|
|
233
|
+
def __rmul__(self, scalar: float) -> Measurement:
|
|
234
|
+
"""Multiply a measurement by a scalar.
|
|
235
|
+
|
|
236
|
+
:param scalar: the scalar to multiply by
|
|
237
|
+
:return: the measurement multiplied by the scalar in self native unit
|
|
238
|
+
"""
|
|
239
|
+
return self.__mul__(scalar)
|
|
240
|
+
|
|
241
|
+
def __truediv__(self, scalar: float) -> Measurement:
|
|
242
|
+
"""Divide a measurement by a scalar.
|
|
243
|
+
|
|
244
|
+
:param scalar: the scalar to divide by
|
|
245
|
+
:return: the measurement divided by the scalar in self native unit
|
|
246
|
+
"""
|
|
247
|
+
return self.__mul__(1.0 / scalar)
|