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.

Files changed (50) hide show
  1. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/.pre-commit-config.yaml +3 -3
  2. {svg_ultralight-0.37.0/src/svg_ultralight.egg-info → svg_ultralight-0.38.0}/PKG-INFO +5 -4
  3. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/README.md +2 -2
  4. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/pyproject.toml +3 -3
  5. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/bound_helpers.py +1 -1
  6. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/type_bound_collection.py +1 -1
  7. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/type_bound_element.py +3 -1
  8. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/type_padded_text.py +3 -1
  9. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/constructors/new_element.py +3 -1
  10. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/inkscape.py +3 -1
  11. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/main.py +12 -4
  12. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/metadata.py +1 -1
  13. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/query.py +47 -25
  14. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/root_elements.py +3 -1
  15. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/string_conversion.py +7 -3
  16. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/transformations.py +5 -3
  17. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0/src/svg_ultralight.egg-info}/PKG-INFO +5 -4
  18. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight.egg-info/requires.txt +1 -0
  19. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_inkscape.py +1 -1
  20. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_queries.py +15 -14
  21. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_root_elements.py +1 -1
  22. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_svg_ultralight.py +1 -1
  23. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/.gitignore +0 -0
  24. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/setup.cfg +0 -0
  25. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/__init__.py +0 -0
  26. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/animate.py +0 -0
  27. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/__init__.py +0 -0
  28. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/supports_bounds.py +0 -0
  29. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/bounding_boxes/type_bounding_box.py +0 -0
  30. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/constructors/__init__.py +0 -0
  31. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/image_ops.py +0 -0
  32. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/layout.py +0 -0
  33. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/nsmap.py +0 -0
  34. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/py.typed +0 -0
  35. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/strings/__init__.py +0 -0
  36. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/strings/svg_strings.py +0 -0
  37. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight/unit_conversion.py +0 -0
  38. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight.egg-info/SOURCES.txt +0 -0
  39. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight.egg-info/dependency_links.txt +0 -0
  40. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/src/svg_ultralight.egg-info/top_level.txt +0 -0
  41. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/__init__.py +0 -0
  42. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/conftest.py +0 -0
  43. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/resources/arrow.svg +0 -0
  44. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_bounding.py +0 -0
  45. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_layout.py +0 -0
  46. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_matrices.py +0 -0
  47. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_metadata.py +0 -0
  48. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_new_element.py +0 -0
  49. {svg_ultralight-0.37.0 → svg_ultralight-0.38.0}/tests/test_string_conversion.py +0 -0
  50. {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.14.0
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.8.4'
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.391
113
+ rev: v1.1.398
114
114
  hooks:
115
115
  - id: pyright
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: svg-ultralight
3
- Version: 0.37.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.map_ids_to_bounding_boxes
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 map_ids_to_bounding_boxes
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.map_ids_to_bounding_boxes
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 map_ids_to_bounding_boxes
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.37.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.37.0"
45
+ version = "0.38.0"
46
46
  tag_format = "$version"
47
47
  version_files = ["pyproject.toml:^version"]
48
48
  annotated_tag = true
@@ -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 # type: ignore
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 # type: ignore
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 _Element as EtreeElement # type: ignore
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 _Element as EtreeElement # type: ignore
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
 
@@ -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 _Element as EtreeElement # type: ignore
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 _Element as EtreeElement # type: ignore
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 _Element as EtreeElement # type: ignore
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) -> bool:
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) # type: ignore
176
- return svg.name # type: ignore
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 # type: ignore
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 lxml.etree import _Element as EtreeElement # type: ignore
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
- if not elem_args:
49
- return
50
- elem = elem_args[0]
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 map_ids_to_bounding_boxes(
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[str, BoundingBox]:
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 element ids (and a bounding box for the entire svg file as ``svg``)
96
- mapped to (x, y, width, height)
97
- :effects: adds an id attribute to any element without one. These will all have
98
- the prefix svg_ul-, so you can find and remove them later if desired.
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
- id2bbox: dict[str, BoundingBox] = {}
133
- for id_, *bounds in (x.split(",") for x in bb_strings):
134
- x, y, width, height = (float(x) for x in bounds)
135
- id2bbox[id_] = BoundingBox(x, y, width, height)
136
- return id2bbox
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 `map_ids_to_bounding_boxes` directly.
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 = map_ids_to_bounding_boxes(inkscape, *remainder)
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.attrib["id"]]
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 _Element as EtreeElement # type: ignore
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 _Element as EtreeElement # type: ignore
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 _Element as EtreeElement # type: ignore
16
+ from lxml.etree import (
17
+ _Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
18
+ )
17
19
 
18
20
 
19
- _RE_MATRIX = re.compile(r"matrix\(([^)]+)\)")
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], _RE_MATRIX.match(transform)).group(1)
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
1
+ Metadata-Version: 2.4
2
2
  Name: svg-ultralight
3
- Version: 0.37.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.map_ids_to_bounding_boxes
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 map_ids_to_bounding_boxes
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
 
@@ -2,6 +2,7 @@ lxml
2
2
  pillow
3
3
  paragraphs
4
4
  types-lxml
5
+ typing-extensions
5
6
 
6
7
  [dev]
7
8
  pytest
@@ -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 # type: ignore
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 map_ids_to_bounding_boxes, get_bounding_boxes, get_bounding_box
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
- _ = new_sub_element(xml, "rect", id="rect1", x=0, y=0, width=16, height=9)
157
- _ = new_sub_element(xml, "rect", id="rect2", x=0, y=0, width=8, height=32)
158
- result = map_ids_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_adds_missing_ids(self) -> None:
163
- """Returns a dict with an entry for each element plus an envelope entry."""
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 = map_ids_to_bounding_boxes(INKSCAPE, xml)
169
- assert xml.get("id") in result
170
- assert rect1.get("id") in result
171
- assert rect2.get("id") in result
172
- assert rect3.get("id") in result
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 # type: ignore
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 # type: ignore
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