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.

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