svg-ultralight 0.37.0__tar.gz → 0.38.0__tar.gz
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-0.37.0 → svg_ultralight-0.38.0}/.pre-commit-config.yaml +3 -3
- {svg_ultralight-0.37.0/src/svg_ultralight.egg-info → svg_ultralight-0.38.0}/PKG-INFO +5 -4
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/README.md +2 -2
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/pyproject.toml +3 -3
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/bound_helpers.py +1 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/type_bound_collection.py +1 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/type_bound_element.py +3 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/type_padded_text.py +3 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/constructors/new_element.py +3 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/inkscape.py +3 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/main.py +12 -4
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/metadata.py +1 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/query.py +47 -25
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/root_elements.py +3 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/string_conversion.py +7 -3
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/transformations.py +5 -3
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0/src/svg_ultralight.egg-info}/PKG-INFO +5 -4
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight.egg-info/requires.txt +1 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_inkscape.py +1 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_queries.py +15 -14
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_root_elements.py +1 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_svg_ultralight.py +1 -1
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/.gitignore +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/setup.cfg +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/__init__.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/animate.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/__init__.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/supports_bounds.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/type_bounding_box.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/constructors/__init__.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/image_ops.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/layout.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/nsmap.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/py.typed +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/strings/__init__.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/strings/svg_strings.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/unit_conversion.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight.egg-info/SOURCES.txt +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight.egg-info/dependency_links.txt +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight.egg-info/top_level.txt +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/__init__.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/conftest.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/resources/arrow.svg +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_bounding.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_layout.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_matrices.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_metadata.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_new_element.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_string_conversion.py +0 -0
- {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tox.ini +0 -0
|
@@ -42,7 +42,7 @@ repos:
|
|
|
42
42
|
# files: .pre-commit-config.yaml
|
|
43
43
|
|
|
44
44
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
45
|
-
rev: v1.
|
|
45
|
+
rev: v1.15.0
|
|
46
46
|
hooks:
|
|
47
47
|
- id: mypy
|
|
48
48
|
name: mypy
|
|
@@ -92,7 +92,7 @@ repos:
|
|
|
92
92
|
# S301 don't use pickle
|
|
93
93
|
# B028 wants explicit stacklevel on warn
|
|
94
94
|
# BLE001 Use of `except Exception:` detected
|
|
95
|
-
rev: 'v0.
|
|
95
|
+
rev: 'v0.11.4'
|
|
96
96
|
hooks:
|
|
97
97
|
- id: ruff
|
|
98
98
|
name: "ruff-lint"
|
|
@@ -110,6 +110,6 @@ repos:
|
|
|
110
110
|
|
|
111
111
|
# reads pyproject.toml for additional config
|
|
112
112
|
- repo: https://github.com/RobertCraigie/pyright-python
|
|
113
|
-
rev: v1.1.
|
|
113
|
+
rev: v1.1.398
|
|
114
114
|
hooks:
|
|
115
115
|
- id: pyright
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: svg-ultralight
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.38.0
|
|
4
4
|
Summary: a sensible way to create svg files with Python
|
|
5
5
|
Author-email: Shay Hill <shay_public@hotmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -10,6 +10,7 @@ Requires-Dist: lxml
|
|
|
10
10
|
Requires-Dist: pillow
|
|
11
11
|
Requires-Dist: paragraphs
|
|
12
12
|
Requires-Dist: types-lxml
|
|
13
|
+
Requires-Dist: typing-extensions
|
|
13
14
|
Provides-Extra: dev
|
|
14
15
|
Requires-Dist: pytest; extra == "dev"
|
|
15
16
|
Requires-Dist: commitizen; extra == "dev"
|
|
@@ -187,7 +188,7 @@ Another way to add params through the new_element name / float translator. Again
|
|
|
187
188
|
|
|
188
189
|
## Extras:
|
|
189
190
|
|
|
190
|
-
### query.
|
|
191
|
+
### query.map_elems_to_bounding_boxes
|
|
191
192
|
|
|
192
193
|
Python cannot parse an svg file. Python can *create* an svg file, and Inkscape can parse (and inspect) it. Inkscape has a command-line interface capable of reading an svg file and returning some limited information. This is the only way I know for a Python program to:
|
|
193
194
|
|
|
@@ -197,7 +198,7 @@ Python cannot parse an svg file. Python can *create* an svg file, and Inkscape c
|
|
|
197
198
|
|
|
198
199
|
This would be necessary for, e.g., algorithmically fitting text in a box.
|
|
199
200
|
|
|
200
|
-
from svg_ultralight.queries import
|
|
201
|
+
from svg_ultralight.queries import map_elems_to_bounding_boxes
|
|
201
202
|
|
|
202
203
|
You can get a tiny bit more sophisticated with Inkscape bounding-box queries, but not much. This will give you pretty much all you can get out of it.
|
|
203
204
|
|
|
@@ -167,7 +167,7 @@ Another way to add params through the new_element name / float translator. Again
|
|
|
167
167
|
|
|
168
168
|
## Extras:
|
|
169
169
|
|
|
170
|
-
### query.
|
|
170
|
+
### query.map_elems_to_bounding_boxes
|
|
171
171
|
|
|
172
172
|
Python cannot parse an svg file. Python can *create* an svg file, and Inkscape can parse (and inspect) it. Inkscape has a command-line interface capable of reading an svg file and returning some limited information. This is the only way I know for a Python program to:
|
|
173
173
|
|
|
@@ -177,7 +177,7 @@ Python cannot parse an svg file. Python can *create* an svg file, and Inkscape c
|
|
|
177
177
|
|
|
178
178
|
This would be necessary for, e.g., algorithmically fitting text in a box.
|
|
179
179
|
|
|
180
|
-
from svg_ultralight.queries import
|
|
180
|
+
from svg_ultralight.queries import map_elems_to_bounding_boxes
|
|
181
181
|
|
|
182
182
|
You can get a tiny bit more sophisticated with Inkscape bounding-box queries, but not much. This will give you pretty much all you can get out of it.
|
|
183
183
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "svg-ultralight"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.38.0"
|
|
4
4
|
description = "a sensible way to create svg files with Python"
|
|
5
5
|
authors = [{ name = "Shay Hill", email = "shay_public@hotmail.com" }]
|
|
6
6
|
license = { text = "MIT" }
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python = ">=3.9"
|
|
9
|
-
dependencies = ["lxml", "pillow", "paragraphs", "types-lxml"]
|
|
9
|
+
dependencies = ["lxml", "pillow", "paragraphs", "types-lxml", "typing-extensions"]
|
|
10
10
|
|
|
11
11
|
[project.optional-dependencies]
|
|
12
12
|
dev = ["pytest", "commitizen", "pre-commit", "tox"]
|
|
@@ -42,7 +42,7 @@ convention = "pep257"
|
|
|
42
42
|
|
|
43
43
|
[tool.commitizen]
|
|
44
44
|
name = "cz_conventional_commits"
|
|
45
|
-
version = "0.
|
|
45
|
+
version = "0.38.0"
|
|
46
46
|
tag_format = "$version"
|
|
47
47
|
version_files = ["pyproject.toml:^version"]
|
|
48
48
|
annotated_tag = true
|
{svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/bound_helpers.py
RENAMED
|
@@ -8,7 +8,7 @@ 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
13
|
from svg_ultralight.bounding_boxes.type_bound_element import BoundElement
|
|
14
14
|
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -70,7 +70,9 @@ from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
|
70
70
|
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
71
71
|
|
|
72
72
|
if TYPE_CHECKING:
|
|
73
|
-
from lxml.etree import
|
|
73
|
+
from lxml.etree import (
|
|
74
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
75
|
+
)
|
|
74
76
|
|
|
75
77
|
_Matrix = tuple[float, float, float, float, float, float]
|
|
76
78
|
|
{svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/constructors/new_element.py
RENAMED
|
@@ -21,7 +21,9 @@ from svg_ultralight.string_conversion import set_attributes
|
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from lxml.etree import QName
|
|
24
|
-
from lxml.etree import
|
|
24
|
+
from lxml.etree import (
|
|
25
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
26
|
+
)
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def new_element(tag: str | QName, **attributes: str | float) -> EtreeElement:
|
|
@@ -30,7 +30,9 @@ from lxml import etree
|
|
|
30
30
|
from svg_ultralight import main
|
|
31
31
|
|
|
32
32
|
if TYPE_CHECKING:
|
|
33
|
-
from lxml.etree import
|
|
33
|
+
from lxml.etree import (
|
|
34
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
35
|
+
)
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
def write_png_from_svg(
|
|
@@ -13,6 +13,7 @@ should work with all Inkscape versions. Please report any issues.
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
import sys
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
from typing import IO, TYPE_CHECKING
|
|
18
19
|
|
|
@@ -23,8 +24,15 @@ from svg_ultralight.layout import pad_and_scale
|
|
|
23
24
|
from svg_ultralight.nsmap import NSMAP
|
|
24
25
|
from svg_ultralight.string_conversion import get_viewBox_str, svg_tostring
|
|
25
26
|
|
|
27
|
+
if sys.version_info >= (3, 10):
|
|
28
|
+
from typing import TypeGuard
|
|
29
|
+
else:
|
|
30
|
+
from typing_extensions import TypeGuard
|
|
31
|
+
|
|
26
32
|
if TYPE_CHECKING:
|
|
27
|
-
from lxml.etree import
|
|
33
|
+
from lxml.etree import (
|
|
34
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
35
|
+
)
|
|
28
36
|
|
|
29
37
|
from svg_ultralight.layout import PadArg
|
|
30
38
|
|
|
@@ -38,7 +46,7 @@ def _is_four_floats(objs: tuple[object, ...]) -> bool:
|
|
|
38
46
|
return len(objs) == 4 and all(isinstance(x, (float, int)) for x in objs)
|
|
39
47
|
|
|
40
48
|
|
|
41
|
-
def _is_io_bytes(obj: object) ->
|
|
49
|
+
def _is_io_bytes(obj: object) -> TypeGuard[IO[bytes]]:
|
|
42
50
|
"""Determine if an object is file-like.
|
|
43
51
|
|
|
44
52
|
:param obj: object
|
|
@@ -172,8 +180,8 @@ def write_svg(
|
|
|
172
180
|
svg_contents = svg_tostring(root, **tostring_kwargs)
|
|
173
181
|
|
|
174
182
|
if _is_io_bytes(svg):
|
|
175
|
-
_ = svg.write(svg_contents)
|
|
176
|
-
return svg.name
|
|
183
|
+
_ = svg.write(svg_contents)
|
|
184
|
+
return svg.name
|
|
177
185
|
if isinstance(svg, (Path, str)):
|
|
178
186
|
with Path(svg).open("wb") as svg_file:
|
|
179
187
|
_ = svg_file.write(svg_contents)
|
|
@@ -9,7 +9,7 @@ fields.
|
|
|
9
9
|
|
|
10
10
|
import warnings
|
|
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.constructors.new_element import new_element, new_sub_element
|
|
15
15
|
from svg_ultralight.nsmap import new_qname
|
|
@@ -21,7 +21,7 @@ from copy import deepcopy
|
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from subprocess import PIPE, Popen
|
|
23
23
|
from tempfile import NamedTemporaryFile, TemporaryFile
|
|
24
|
-
from typing import TYPE_CHECKING
|
|
24
|
+
from typing import TYPE_CHECKING, Literal
|
|
25
25
|
from warnings import warn
|
|
26
26
|
|
|
27
27
|
from lxml import etree
|
|
@@ -31,7 +31,11 @@ from svg_ultralight.bounding_boxes.type_padded_text import PaddedText
|
|
|
31
31
|
from svg_ultralight.main import new_svg_root, write_svg
|
|
32
32
|
|
|
33
33
|
if TYPE_CHECKING:
|
|
34
|
-
from
|
|
34
|
+
from collections.abc import Iterator
|
|
35
|
+
|
|
36
|
+
from lxml.etree import (
|
|
37
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
38
|
+
)
|
|
35
39
|
|
|
36
40
|
|
|
37
41
|
with TemporaryFile() as f:
|
|
@@ -39,20 +43,23 @@ with TemporaryFile() as f:
|
|
|
39
43
|
|
|
40
44
|
_CACHE_DIR.mkdir(exist_ok=True)
|
|
41
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
|
+
|
|
42
54
|
|
|
43
55
|
def _fill_ids(*elem_args: EtreeElement) -> None:
|
|
44
56
|
"""Set the id attribute of an element and all its children. Keep existing ids.
|
|
45
57
|
|
|
46
58
|
:param elem: an etree element, accepts multiple arguments
|
|
47
59
|
"""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
for child in elem:
|
|
52
|
-
_fill_ids(child)
|
|
53
|
-
if elem.get("id") is None:
|
|
54
|
-
elem.set("id", f"svg_ul-{uuid.uuid4()}")
|
|
55
|
-
_fill_ids(*elem_args[1:])
|
|
60
|
+
for elem in _iter_elems(*elem_args):
|
|
61
|
+
if elem.get("id") is None:
|
|
62
|
+
elem.set("id", f"{_TEMP_ID_PREFIX}-{uuid.uuid4()}")
|
|
56
63
|
|
|
57
64
|
|
|
58
65
|
def _normalize_views(elem: EtreeElement) -> None:
|
|
@@ -83,19 +90,30 @@ def _envelop_copies(*elem_args: EtreeElement) -> EtreeElement:
|
|
|
83
90
|
return envelope
|
|
84
91
|
|
|
85
92
|
|
|
86
|
-
def
|
|
93
|
+
def _split_bb_string(bb_string: str) -> tuple[str, BoundingBox]:
|
|
94
|
+
"""Split a bounding box string into id and BoundingBox instance.
|
|
95
|
+
|
|
96
|
+
:param bb_string: "id,x,y,width,height"
|
|
97
|
+
:return: (id, BoundingBox(x, y, width, height))
|
|
98
|
+
"""
|
|
99
|
+
id_, *bounds = bb_string.split(",")
|
|
100
|
+
x, y, width, height = (float(x) for x in bounds)
|
|
101
|
+
return id_, BoundingBox(x, y, width, height)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def map_elems_to_bounding_boxes(
|
|
87
105
|
inkscape: str | Path, *elem_args: EtreeElement
|
|
88
|
-
) -> dict[
|
|
106
|
+
) -> dict[EtreeElement | Literal["svg"], BoundingBox]:
|
|
89
107
|
r"""Query an svg file for bounding-box dimensions.
|
|
90
108
|
|
|
91
109
|
:param inkscape: path to an inkscape executable on your local file system
|
|
92
110
|
IMPORTANT: path cannot end with ``.exe``.
|
|
93
111
|
Use something like ``"C:\\Program Files\\Inkscape\\inkscape"``
|
|
94
112
|
:param elem_args: xml element (written to a temporary file then queried)
|
|
95
|
-
:return: svg
|
|
96
|
-
mapped to (x, y, width, height)
|
|
97
|
-
:effects: adds an id attribute
|
|
98
|
-
|
|
113
|
+
:return: svg elements (and a bounding box for the entire svg file as ``svg``)
|
|
114
|
+
mapped to BoundingBox(x, y, width, height)
|
|
115
|
+
:effects: temporarily adds an id attribute if any ids are missing. Non-unique ids
|
|
116
|
+
will break this function.
|
|
99
117
|
|
|
100
118
|
Bounding boxes are relative to svg viewbox. If viewbox x == -10,
|
|
101
119
|
all bounding-box x values will be offset -10. So, everything is wrapped in a root
|
|
@@ -126,14 +144,18 @@ def map_ids_to_bounding_boxes(
|
|
|
126
144
|
svg = write_svg(svg_file, envelope)
|
|
127
145
|
with Popen(f'"{inkscape}" --query-all {svg}', stdout=PIPE) as bb_process:
|
|
128
146
|
bb_data = str(bb_process.communicate()[0])[2:-1]
|
|
129
|
-
bb_strings = re.split(r"[\\r]*\\n", bb_data)[:-1]
|
|
130
147
|
os.unlink(svg_file.name)
|
|
148
|
+
bb_strings = re.split(r"[\\r]*\\n", bb_data)[:-1]
|
|
149
|
+
id2bbox = dict(map(_split_bb_string, bb_strings))
|
|
131
150
|
|
|
132
|
-
|
|
133
|
-
for
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
151
|
+
elem2bbox: dict[EtreeElement | Literal["svg"], BoundingBox] = {}
|
|
152
|
+
for elem in _iter_elems(*elem_args):
|
|
153
|
+
elem2bbox[elem] = id2bbox.pop(elem.attrib["id"])
|
|
154
|
+
if elem.attrib["id"].startswith(_TEMP_ID_PREFIX):
|
|
155
|
+
del elem.attrib["id"]
|
|
156
|
+
((_, scene_bbox),) = id2bbox.items()
|
|
157
|
+
elem2bbox["svg"] = scene_bbox
|
|
158
|
+
return elem2bbox
|
|
137
159
|
|
|
138
160
|
|
|
139
161
|
def _hash_elem(elem: EtreeElement) -> str:
|
|
@@ -178,7 +200,7 @@ def get_bounding_boxes(
|
|
|
178
200
|
|
|
179
201
|
This will work most of the time, but if you're missing an nsmap, you'll need to
|
|
180
202
|
create an entire xml file with a custom nsmap (using
|
|
181
|
-
`svg_ultralight.new_svg_root`) then call `
|
|
203
|
+
`svg_ultralight.new_svg_root`) then call `map_elems_to_bounding_boxes` directly.
|
|
182
204
|
"""
|
|
183
205
|
elem2hash = {elem: _hash_elem(elem) for elem in elem_args}
|
|
184
206
|
cached = [_try_bbox_cache(h) for h in elem2hash.values()]
|
|
@@ -187,10 +209,10 @@ def get_bounding_boxes(
|
|
|
187
209
|
|
|
188
210
|
hash2bbox = {h: c for h, c in zip(elem2hash.values(), cached) if c is not None}
|
|
189
211
|
remainder = [e for e, c in zip(elem_args, cached) if c is None]
|
|
190
|
-
id2bbox =
|
|
212
|
+
id2bbox = map_elems_to_bounding_boxes(inkscape, *remainder)
|
|
191
213
|
for elem in remainder:
|
|
192
214
|
hash_ = elem2hash[elem]
|
|
193
|
-
hash2bbox[hash_] = id2bbox[elem
|
|
215
|
+
hash2bbox[hash_] = id2bbox[elem]
|
|
194
216
|
with (_CACHE_DIR / hash_).open("wb") as f:
|
|
195
217
|
pickle.dump(hash2bbox[hash_], f)
|
|
196
218
|
return tuple(hash2bbox[h] for h in elem2hash.values())
|
|
@@ -13,7 +13,9 @@ from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
|
13
13
|
from svg_ultralight.main import new_svg_root
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
|
-
from lxml.etree import
|
|
16
|
+
from lxml.etree import (
|
|
17
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
18
|
+
)
|
|
17
19
|
|
|
18
20
|
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
19
21
|
from svg_ultralight.layout import PadArg
|
|
@@ -22,7 +22,9 @@ from svg_ultralight.nsmap import NSMAP
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
23
|
from collections.abc import Iterable
|
|
24
24
|
|
|
25
|
-
from lxml.etree import
|
|
25
|
+
from lxml.etree import (
|
|
26
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
27
|
+
)
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
def format_number(num: float | str) -> str:
|
|
@@ -120,7 +122,9 @@ def _fix_key_and_format_val(key: str, val: str | float) -> tuple[str, str]:
|
|
|
120
122
|
popular one will be 'class') can be passed with a trailing underscore (e.g.,
|
|
121
123
|
class_='body_text').
|
|
122
124
|
"""
|
|
123
|
-
if ":" in key:
|
|
125
|
+
if "http:" in key or "https:" in key:
|
|
126
|
+
key_ = key
|
|
127
|
+
elif ":" in key:
|
|
124
128
|
namespace, tag = key.split(":")
|
|
125
129
|
key_ = str(etree.QName(NSMAP[namespace], tag))
|
|
126
130
|
else:
|
|
@@ -184,7 +188,7 @@ def svg_tostring(xml: EtreeElement, **tostring_kwargs: str | bool | None) -> byt
|
|
|
184
188
|
value = tostring_kwargs.get(arg_name, default.value)
|
|
185
189
|
tostring_kwargs[arg_name] = value
|
|
186
190
|
as_bytes = etree.tostring(etree.ElementTree(xml), **tostring_kwargs) # type: ignore
|
|
187
|
-
return cast(bytes, as_bytes)
|
|
191
|
+
return cast("bytes", as_bytes)
|
|
188
192
|
|
|
189
193
|
|
|
190
194
|
def get_viewBox_str(
|
|
@@ -13,10 +13,12 @@ from typing import TYPE_CHECKING, cast
|
|
|
13
13
|
from svg_ultralight.string_conversion import format_number
|
|
14
14
|
|
|
15
15
|
if TYPE_CHECKING:
|
|
16
|
-
from lxml.etree import
|
|
16
|
+
from lxml.etree import (
|
|
17
|
+
_Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
|
|
18
|
+
)
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
RE_MATRIX = re.compile(r"matrix\(([^)]+)\)")
|
|
20
22
|
|
|
21
23
|
_Matrix = tuple[float, float, float, float, float, float]
|
|
22
24
|
|
|
@@ -86,7 +88,7 @@ def get_transform_matrix(elem: EtreeElement) -> _Matrix:
|
|
|
86
88
|
return (1, 0, 0, 1, 0, 0)
|
|
87
89
|
values_str = ""
|
|
88
90
|
with suppress(AttributeError):
|
|
89
|
-
values_str = cast(re.Match[str],
|
|
91
|
+
values_str = cast("re.Match[str]", RE_MATRIX.match(transform)).group(1)
|
|
90
92
|
with suppress(ValueError):
|
|
91
93
|
aa, bb, cc, dd, ee, ff = (float(val) for val in values_str.split())
|
|
92
94
|
return (aa, bb, cc, dd, ee, ff)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: svg-ultralight
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.38.0
|
|
4
4
|
Summary: a sensible way to create svg files with Python
|
|
5
5
|
Author-email: Shay Hill <shay_public@hotmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -10,6 +10,7 @@ Requires-Dist: lxml
|
|
|
10
10
|
Requires-Dist: pillow
|
|
11
11
|
Requires-Dist: paragraphs
|
|
12
12
|
Requires-Dist: types-lxml
|
|
13
|
+
Requires-Dist: typing-extensions
|
|
13
14
|
Provides-Extra: dev
|
|
14
15
|
Requires-Dist: pytest; extra == "dev"
|
|
15
16
|
Requires-Dist: commitizen; extra == "dev"
|
|
@@ -187,7 +188,7 @@ Another way to add params through the new_element name / float translator. Again
|
|
|
187
188
|
|
|
188
189
|
## Extras:
|
|
189
190
|
|
|
190
|
-
### query.
|
|
191
|
+
### query.map_elems_to_bounding_boxes
|
|
191
192
|
|
|
192
193
|
Python cannot parse an svg file. Python can *create* an svg file, and Inkscape can parse (and inspect) it. Inkscape has a command-line interface capable of reading an svg file and returning some limited information. This is the only way I know for a Python program to:
|
|
193
194
|
|
|
@@ -197,7 +198,7 @@ Python cannot parse an svg file. Python can *create* an svg file, and Inkscape c
|
|
|
197
198
|
|
|
198
199
|
This would be necessary for, e.g., algorithmically fitting text in a box.
|
|
199
200
|
|
|
200
|
-
from svg_ultralight.queries import
|
|
201
|
+
from svg_ultralight.queries import map_elems_to_bounding_boxes
|
|
201
202
|
|
|
202
203
|
You can get a tiny bit more sophisticated with Inkscape bounding-box queries, but not much. This will give you pretty much all you can get out of it.
|
|
203
204
|
|
|
@@ -10,7 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
|
|
11
11
|
import pytest
|
|
12
12
|
from lxml import etree
|
|
13
|
-
from lxml.etree import _Element as EtreeElement #
|
|
13
|
+
from lxml.etree import _Element as EtreeElement # pyright: ignore[reportPrivateUsage]
|
|
14
14
|
|
|
15
15
|
from svg_ultralight.constructors import new_sub_element
|
|
16
16
|
from svg_ultralight.inkscape import convert_text_to_path
|
|
@@ -14,7 +14,7 @@ import pytest
|
|
|
14
14
|
|
|
15
15
|
from svg_ultralight import BoundingBox, new_svg_root
|
|
16
16
|
from svg_ultralight.constructors import new_sub_element
|
|
17
|
-
from svg_ultralight.query import
|
|
17
|
+
from svg_ultralight.query import map_elems_to_bounding_boxes, get_bounding_boxes, get_bounding_box
|
|
18
18
|
|
|
19
19
|
INKSCAPE = Path(r"C:\Program Files\Inkscape\bin\inkscape")
|
|
20
20
|
|
|
@@ -153,23 +153,24 @@ class TestMapIdsToBoundingBoxes:
|
|
|
153
153
|
def test_gets_bboxes(self) -> None:
|
|
154
154
|
"""Run with a temporary file."""
|
|
155
155
|
xml = new_svg_root(10, 20, 160, 19, id="svg1")
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
result =
|
|
159
|
-
assert result[
|
|
160
|
-
assert result[
|
|
161
|
-
|
|
162
|
-
def
|
|
163
|
-
"""
|
|
156
|
+
rect1 = new_sub_element(xml, "rect", id="rect1", x=0, y=0, width=16, height=9)
|
|
157
|
+
rect2 = new_sub_element(xml, "rect", id="rect2", x=0, y=0, width=8, height=32)
|
|
158
|
+
result = map_elems_to_bounding_boxes(INKSCAPE, xml)
|
|
159
|
+
assert result[rect1] == BoundingBox(0.0, 0.0, 16.0, 9.0)
|
|
160
|
+
assert result[rect2] == BoundingBox(0.0, 0.0, 8.0, 32.0)
|
|
161
|
+
|
|
162
|
+
def test_removes_temp_ids(self) -> None:
|
|
163
|
+
"""Removes temporary IDs created during bounding box mapping."""
|
|
164
164
|
xml = new_svg_root(10, 20, 160, 19, id="svg1")
|
|
165
165
|
rect1 = new_sub_element(xml, "rect", x=0, y=0, width=16, height=9)
|
|
166
166
|
rect2 = new_sub_element(xml, "rect", x=0, y=0, width=8, height=32)
|
|
167
167
|
rect3 = new_sub_element(xml, "rect", x=0, y=0, width=12, height=18)
|
|
168
|
-
result =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
assert
|
|
172
|
-
|
|
168
|
+
result = map_elems_to_bounding_boxes(INKSCAPE, xml)
|
|
169
|
+
for elem in (xml, rect1, rect2, rect3):
|
|
170
|
+
assert elem in result
|
|
171
|
+
assert xml.attrib.get("id") == "svg1"
|
|
172
|
+
for elem in (rect1, rect2, rect3):
|
|
173
|
+
assert "id" not in elem.attrib
|
|
173
174
|
|
|
174
175
|
def test_get_bboxes_explicit(self) -> None:
|
|
175
176
|
"""Returns a dict with an entry for each element plus an envelope entry."""
|
|
@@ -12,7 +12,7 @@ from svg_ultralight.bounding_boxes.type_padded_text import PaddedText
|
|
|
12
12
|
from svg_ultralight.constructors import new_element
|
|
13
13
|
from svg_ultralight.root_elements import new_svg_root_around_bounds
|
|
14
14
|
from svg_ultralight.bounding_boxes.bound_helpers import new_bound_union
|
|
15
|
-
from lxml.etree import _Element as EtreeElement #
|
|
15
|
+
from lxml.etree import _Element as EtreeElement # pyright: ignore[reportPrivateUsage]
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class TestNewSvgRootAroundBounds:
|
|
@@ -14,7 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
|
|
15
15
|
import pytest
|
|
16
16
|
from lxml import etree
|
|
17
|
-
from lxml.etree import _Element as EtreeElement #
|
|
17
|
+
from lxml.etree import _Element as EtreeElement # pyright: ignore[reportPrivateUsage]
|
|
18
18
|
|
|
19
19
|
from svg_ultralight import NSMAP
|
|
20
20
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/__init__.py
RENAMED
|
File without changes
|
{svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/supports_bounds.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|