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
svg_ultralight/nsmap.py
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
"""xml namespace entries for svg files.
|
|
2
|
-
|
|
3
|
-
:author: Shay Hill
|
|
4
|
-
:created: 1/14/2021
|
|
5
|
-
|
|
6
|
-
I started by copying out entries from Inkscape output. Added more as I found them
|
|
7
|
-
necessary. This is a pretty robust list. Can be pared down as documented at
|
|
8
|
-
https://shayallenhill.com/svg-with-css-in-python/
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
from lxml.etree import QName
|
|
14
|
-
|
|
15
|
-
_SVG_NAMESPACE = "http://www.w3.org/2000/svg"
|
|
16
|
-
NSMAP = {
|
|
17
|
-
None: _SVG_NAMESPACE,
|
|
18
|
-
"dc": "http://purl.org/dc/elements/1.1/",
|
|
19
|
-
"cc": "http://creativecommons.org/ns#",
|
|
20
|
-
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
21
|
-
"svg": _SVG_NAMESPACE,
|
|
22
|
-
"xlink": "http://www.w3.org/1999/xlink",
|
|
23
|
-
"sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
|
|
24
|
-
"inkscape": "http://www.inkscape.org/namespaces/inkscape",
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def new_qname(namespace_abbreviation: str | None, tag: str) -> QName:
|
|
29
|
-
"""Create a qualified name for an svg element.
|
|
30
|
-
|
|
31
|
-
:param namespace_abbreviation: The namespace abbreviation. This
|
|
32
|
-
will have to be a key in NSMAP (e.g., "dc", "cc", "rdf").
|
|
33
|
-
:param tag: The tag name of the element.
|
|
34
|
-
:return: A qualified name for the element.
|
|
35
|
-
"""
|
|
36
|
-
return QName(NSMAP[namespace_abbreviation], tag)
|
|
1
|
+
"""xml namespace entries for svg files.
|
|
2
|
+
|
|
3
|
+
:author: Shay Hill
|
|
4
|
+
:created: 1/14/2021
|
|
5
|
+
|
|
6
|
+
I started by copying out entries from Inkscape output. Added more as I found them
|
|
7
|
+
necessary. This is a pretty robust list. Can be pared down as documented at
|
|
8
|
+
https://shayallenhill.com/svg-with-css-in-python/
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from lxml.etree import QName
|
|
14
|
+
|
|
15
|
+
_SVG_NAMESPACE = "http://www.w3.org/2000/svg"
|
|
16
|
+
NSMAP = {
|
|
17
|
+
None: _SVG_NAMESPACE,
|
|
18
|
+
"dc": "http://purl.org/dc/elements/1.1/",
|
|
19
|
+
"cc": "http://creativecommons.org/ns#",
|
|
20
|
+
"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
21
|
+
"svg": _SVG_NAMESPACE,
|
|
22
|
+
"xlink": "http://www.w3.org/1999/xlink",
|
|
23
|
+
"sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
|
|
24
|
+
"inkscape": "http://www.inkscape.org/namespaces/inkscape",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def new_qname(namespace_abbreviation: str | None, tag: str) -> QName:
|
|
29
|
+
"""Create a qualified name for an svg element.
|
|
30
|
+
|
|
31
|
+
:param namespace_abbreviation: The namespace abbreviation. This
|
|
32
|
+
will have to be a key in NSMAP (e.g., "dc", "cc", "rdf").
|
|
33
|
+
:param tag: The tag name of the element.
|
|
34
|
+
:return: A qualified name for the element.
|
|
35
|
+
"""
|
|
36
|
+
return QName(NSMAP[namespace_abbreviation], tag)
|
svg_ultralight/py.typed
CHANGED
svg_ultralight/query.py
CHANGED
|
@@ -1,249 +1,254 @@
|
|
|
1
|
-
"""Query an SVG file for bounding boxes.
|
|
2
|
-
|
|
3
|
-
:author: Shay Hill
|
|
4
|
-
:created: 7/25/2020
|
|
5
|
-
|
|
6
|
-
Bounding boxes are generated with a command-line call to Inkscape, so an Inkscape
|
|
7
|
-
installation is required for this to work. The bounding boxes are returned as
|
|
8
|
-
BoundingBox instances, which are a big help with aligning objects (e.g., text on a
|
|
9
|
-
business card). Getting bounding boxes from Inkscape is not exceptionally fast.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import hashlib
|
|
15
|
-
import os
|
|
16
|
-
import pickle
|
|
17
|
-
import re
|
|
18
|
-
import uuid
|
|
19
|
-
from contextlib import suppress
|
|
20
|
-
from copy import deepcopy
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
from subprocess import PIPE, Popen
|
|
23
|
-
from tempfile import NamedTemporaryFile, TemporaryFile
|
|
24
|
-
from typing import TYPE_CHECKING, Literal
|
|
25
|
-
from warnings import warn
|
|
26
|
-
|
|
27
|
-
from lxml import etree
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
from svg_ultralight.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
x, y, width, height
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
:param
|
|
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
|
-
Inkscape will
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
except
|
|
196
|
-
msg = f"
|
|
197
|
-
warn(msg)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
:param
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
"""
|
|
248
|
-
|
|
249
|
-
|
|
1
|
+
"""Query an SVG file for bounding boxes.
|
|
2
|
+
|
|
3
|
+
:author: Shay Hill
|
|
4
|
+
:created: 7/25/2020
|
|
5
|
+
|
|
6
|
+
Bounding boxes are generated with a command-line call to Inkscape, so an Inkscape
|
|
7
|
+
installation is required for this to work. The bounding boxes are returned as
|
|
8
|
+
BoundingBox instances, which are a big help with aligning objects (e.g., text on a
|
|
9
|
+
business card). Getting bounding boxes from Inkscape is not exceptionally fast.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import hashlib
|
|
15
|
+
import os
|
|
16
|
+
import pickle
|
|
17
|
+
import re
|
|
18
|
+
import uuid
|
|
19
|
+
from contextlib import suppress
|
|
20
|
+
from copy import deepcopy
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from subprocess import PIPE, Popen
|
|
23
|
+
from tempfile import NamedTemporaryFile, TemporaryFile
|
|
24
|
+
from typing import TYPE_CHECKING, Literal
|
|
25
|
+
from warnings import warn
|
|
26
|
+
|
|
27
|
+
from lxml import etree
|
|
28
|
+
from lxml.etree import _Comment as EtreeComment # pyright: ignore[reportPrivateUsage]
|
|
29
|
+
|
|
30
|
+
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
31
|
+
from svg_ultralight.main import new_svg_root, write_svg
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from collections.abc import Iterator
|
|
35
|
+
|
|
36
|
+
from lxml.etree import (
|
|
37
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
with TemporaryFile() as f:
|
|
42
|
+
_CACHE_DIR = Path(f.name).parent / "svg_ultralight_cache"
|
|
43
|
+
|
|
44
|
+
_CACHE_DIR.mkdir(exist_ok=True)
|
|
45
|
+
|
|
46
|
+
_TEMP_ID_PREFIX = "svg_ultralight-temp_query_module-"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _iter_elems(*elem_args: EtreeElement) -> Iterator[EtreeElement]:
|
|
50
|
+
"""Yield element and sub-elements."""
|
|
51
|
+
for elem in elem_args:
|
|
52
|
+
yield from elem.iter()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _fill_ids(*elem_args: EtreeElement) -> None:
|
|
56
|
+
"""Set the id attribute of an element and all its children. Keep existing ids.
|
|
57
|
+
|
|
58
|
+
:param elem: an etree element, accepts multiple arguments
|
|
59
|
+
"""
|
|
60
|
+
for elem in _iter_elems(*elem_args):
|
|
61
|
+
if isinstance(elem, EtreeComment):
|
|
62
|
+
continue
|
|
63
|
+
if elem.get("id") is None:
|
|
64
|
+
elem.set("id", f"{_TEMP_ID_PREFIX}-{uuid.uuid4()}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _normalize_views(elem: EtreeElement) -> None:
|
|
68
|
+
"""Create a square viewBox for any element with an svg tag.
|
|
69
|
+
|
|
70
|
+
:param elem: an etree element
|
|
71
|
+
|
|
72
|
+
This prevents the bounding boxes from being distorted. Only do this to copies,
|
|
73
|
+
because there's no way to undo it.
|
|
74
|
+
"""
|
|
75
|
+
for child in elem:
|
|
76
|
+
_normalize_views(child)
|
|
77
|
+
if str(elem.tag).endswith("svg"):
|
|
78
|
+
elem.set("viewBox", "0 0 1 1")
|
|
79
|
+
elem.set("width", "1")
|
|
80
|
+
elem.set("height", "1")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _envelop_copies(*elem_args: EtreeElement) -> EtreeElement:
|
|
84
|
+
"""Create an svg root element enveloping all elem_args.
|
|
85
|
+
|
|
86
|
+
:param elem_args: one or more etree elements
|
|
87
|
+
:return: an etree element enveloping copies of elem_args with all views normalized
|
|
88
|
+
"""
|
|
89
|
+
envelope = new_svg_root(0, 0, 1, 1)
|
|
90
|
+
envelope.extend([deepcopy(e) for e in elem_args])
|
|
91
|
+
_normalize_views(envelope)
|
|
92
|
+
return envelope
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _split_bb_string(bb_string: str) -> tuple[str, BoundingBox]:
|
|
96
|
+
"""Split a bounding box string into id and BoundingBox instance.
|
|
97
|
+
|
|
98
|
+
:param bb_string: "id,x,y,width,height"
|
|
99
|
+
:return: (id, BoundingBox(x, y, width, height))
|
|
100
|
+
"""
|
|
101
|
+
id_, *bounds = bb_string.split(",")
|
|
102
|
+
x, y, width, height = (float(x) for x in bounds)
|
|
103
|
+
return id_, BoundingBox(x, y, width, height)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def map_elems_to_bounding_boxes(
|
|
107
|
+
inkscape: str | os.PathLike[str], *elem_args: EtreeElement
|
|
108
|
+
) -> dict[EtreeElement | Literal["svg"], BoundingBox]:
|
|
109
|
+
r"""Query an svg file for bounding-box dimensions.
|
|
110
|
+
|
|
111
|
+
:param inkscape: path to an inkscape executable on your local file system
|
|
112
|
+
IMPORTANT: path cannot end with ``.exe``.
|
|
113
|
+
Use something like ``"C:\\Program Files\\Inkscape\\inkscape"``
|
|
114
|
+
:param elem_args: xml element (written to a temporary file then queried)
|
|
115
|
+
:return: input svg elements and any descendents of those elements mapped
|
|
116
|
+
`BoundingBox(x, y, width, height)`
|
|
117
|
+
So return dict keys are the input elements themselves with one exception: a
|
|
118
|
+
string key, "svg", is mapped to a bounding box around all input elements.
|
|
119
|
+
:effects: temporarily adds an id attribute if any ids are missing. These are
|
|
120
|
+
removed if the function completes. Existing, non-unique ids will break this
|
|
121
|
+
function.
|
|
122
|
+
|
|
123
|
+
Bounding boxes are relative to svg viewBox. If, for instance, viewBox x == -10,
|
|
124
|
+
all bounding-box x values will be offset -10. So, everything is wrapped in a root
|
|
125
|
+
element, `envelope` with a "normalized" viewBox, `viewBox=(0, 0, 1, 1)`. That
|
|
126
|
+
way, any child root elements ("child root elements" sounds wrong, but it works)
|
|
127
|
+
viewBoxes are normalized as well. This works even with a root element around a
|
|
128
|
+
root element, so input elem_args can be root elements or "normal" elements like
|
|
129
|
+
"rect", "circle", or "text" or a mixture of both. Bounding boxes output here will
|
|
130
|
+
work as expected in any viewBox.
|
|
131
|
+
|
|
132
|
+
The ``inkscape --query-all svg`` call will return a tuple:
|
|
133
|
+
|
|
134
|
+
(b'svg1,x,y,width,height\\r\\elem1,x,y,width,height\\r\\n', None)
|
|
135
|
+
where x, y, width, and height are strings of numbers.
|
|
136
|
+
|
|
137
|
+
This calls the command and formats the output into a dictionary. There is a
|
|
138
|
+
little extra complexity to handle cases with duplicate elements. Inkscape will
|
|
139
|
+
map bounding boxes to element ids *if* those ids are unique. If Inkscape
|
|
140
|
+
encounters a duplicate ID, Inkscape will map the bounding box of that element to
|
|
141
|
+
a string like "rect1". If you pass unequal elements with the same id, I can't
|
|
142
|
+
help you, but you might pass the same element multiple times. If you do this,
|
|
143
|
+
Inkscape will find a bounding box for each occurrence, map the first occurrence
|
|
144
|
+
to the id, then map subsequent occurrences to a string like "rect1". This
|
|
145
|
+
function will handle that.
|
|
146
|
+
"""
|
|
147
|
+
if not elem_args:
|
|
148
|
+
return {}
|
|
149
|
+
_fill_ids(*elem_args)
|
|
150
|
+
|
|
151
|
+
envelope = _envelop_copies(*elem_args)
|
|
152
|
+
with NamedTemporaryFile(mode="wb", delete=False, suffix=".svg") as svg_file:
|
|
153
|
+
svg = write_svg(svg_file, envelope)
|
|
154
|
+
with Popen(f'"{inkscape}" --query-all {svg}', stdout=PIPE) as bb_process:
|
|
155
|
+
bb_data = str(bb_process.communicate()[0])[2:-1]
|
|
156
|
+
os.unlink(svg_file.name)
|
|
157
|
+
|
|
158
|
+
bb_strings = re.split(r"[\\r]*\\n", bb_data)[:-1]
|
|
159
|
+
id2bbox = dict(map(_split_bb_string, bb_strings))
|
|
160
|
+
|
|
161
|
+
elem2bbox: dict[EtreeElement | Literal["svg"], BoundingBox] = {}
|
|
162
|
+
for elem in _iter_elems(*elem_args):
|
|
163
|
+
elem_id = elem.attrib.get("id")
|
|
164
|
+
if not (elem_id): # id removed in a previous loop
|
|
165
|
+
continue
|
|
166
|
+
with suppress(KeyError):
|
|
167
|
+
# some elems like <style> don't have a bounding box
|
|
168
|
+
elem2bbox[elem] = id2bbox[elem_id]
|
|
169
|
+
if elem_id.startswith(_TEMP_ID_PREFIX):
|
|
170
|
+
del elem.attrib["id"]
|
|
171
|
+
elem2bbox["svg"] = BoundingBox.merged(*id2bbox.values())
|
|
172
|
+
return elem2bbox
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _hash_elem(elem: EtreeElement) -> str:
|
|
176
|
+
"""Hash an EtreeElement.
|
|
177
|
+
|
|
178
|
+
Will match identical (excepting id) elements.
|
|
179
|
+
"""
|
|
180
|
+
elem_copy = deepcopy(elem)
|
|
181
|
+
with suppress(KeyError):
|
|
182
|
+
_ = elem_copy.attrib.pop("id")
|
|
183
|
+
hash_object = hashlib.sha256(etree.tostring(elem_copy))
|
|
184
|
+
return hash_object.hexdigest()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _try_bbox_cache(elem_hash: str) -> BoundingBox | None:
|
|
188
|
+
"""Try to load a cached bounding box."""
|
|
189
|
+
cache_path = _CACHE_DIR / elem_hash
|
|
190
|
+
if not cache_path.exists():
|
|
191
|
+
return None
|
|
192
|
+
try:
|
|
193
|
+
with cache_path.open("rb") as f:
|
|
194
|
+
return pickle.load(f)
|
|
195
|
+
except (EOFError, pickle.UnpicklingError) as e:
|
|
196
|
+
msg = f"Error loading cache file {cache_path}: {e}"
|
|
197
|
+
warn(msg, stacklevel=2)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
msg = f"Unexpected error loading cache file {cache_path}: {e}"
|
|
200
|
+
warn(msg, stacklevel=2)
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def get_bounding_boxes(
|
|
205
|
+
inkscape: str | os.PathLike[str], *elem_args: EtreeElement
|
|
206
|
+
) -> tuple[BoundingBox, ...]:
|
|
207
|
+
r"""Get bounding box around a single element (or multiple elements).
|
|
208
|
+
|
|
209
|
+
:param inkscape: path to an inkscape executable on your local file system
|
|
210
|
+
IMPORTANT: path cannot end with ``.exe``.
|
|
211
|
+
Use something like ``"C:\\Program Files\\Inkscape\\inkscape"``
|
|
212
|
+
:param elem_args: xml elements
|
|
213
|
+
:return: a BoundingBox instance around a each elem_arg
|
|
214
|
+
|
|
215
|
+
This will work most of the time, but if you're missing an nsmap, you'll need to
|
|
216
|
+
create an entire xml file with a custom nsmap (using
|
|
217
|
+
`svg_ultralight.new_svg_root`) then call `map_elems_to_bounding_boxes` directly.
|
|
218
|
+
"""
|
|
219
|
+
elem2hash = {elem: _hash_elem(elem) for elem in elem_args}
|
|
220
|
+
cached = [_try_bbox_cache(h) for h in elem2hash.values()]
|
|
221
|
+
if None not in cached:
|
|
222
|
+
return tuple(filter(None, cached))
|
|
223
|
+
|
|
224
|
+
hash2bbox = {
|
|
225
|
+
h: c for h, c in zip(elem2hash.values(), cached, strict=True) if c is not None
|
|
226
|
+
}
|
|
227
|
+
remainder = [e for e, c in zip(elem_args, cached, strict=True) if c is None]
|
|
228
|
+
id2bbox = map_elems_to_bounding_boxes(inkscape, *remainder)
|
|
229
|
+
for elem in remainder:
|
|
230
|
+
hash_ = elem2hash[elem]
|
|
231
|
+
hash2bbox[hash_] = id2bbox[elem]
|
|
232
|
+
with (_CACHE_DIR / hash_).open("wb") as f:
|
|
233
|
+
pickle.dump(hash2bbox[hash_], f)
|
|
234
|
+
return tuple(hash2bbox[h] for h in elem2hash.values())
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def get_bounding_box(
|
|
238
|
+
inkscape: str | os.PathLike[str], elem: EtreeElement
|
|
239
|
+
) -> BoundingBox:
|
|
240
|
+
r"""Get bounding box around a single element.
|
|
241
|
+
|
|
242
|
+
:param inkscape: path to an inkscape executable on your local file system
|
|
243
|
+
IMPORTANT: path cannot end with ``.exe``.
|
|
244
|
+
Use something like ``"C:\\Program Files\\Inkscape\\inkscape"``
|
|
245
|
+
:param elem: xml element
|
|
246
|
+
:return: a BoundingBox instance around a single elem
|
|
247
|
+
"""
|
|
248
|
+
return get_bounding_boxes(inkscape, elem)[0]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def clear_svg_ultralight_cache() -> None:
|
|
252
|
+
"""Clear all cached bounding boxes."""
|
|
253
|
+
for cache_file in _CACHE_DIR.glob("*"):
|
|
254
|
+
cache_file.unlink()
|