svg-ultralight 0.34.0__tar.gz → 0.36.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.34.0 → svg_ultralight-0.36.0}/.pre-commit-config.yaml +13 -25
- {svg_ultralight-0.34.0/src/svg_ultralight.egg-info → svg_ultralight-0.36.0}/PKG-INFO +3 -1
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/pyproject.toml +7 -2
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/__init__.py +7 -5
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/bounding_boxes/bound_helpers.py +38 -2
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/constructors/__init__.py +1 -1
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/constructors/new_element.py +2 -1
- svg_ultralight-0.36.0/src/svg_ultralight/image_ops.py +134 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/query.py +0 -1
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/strings/__init__.py +1 -1
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0/src/svg_ultralight.egg-info}/PKG-INFO +3 -1
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight.egg-info/SOURCES.txt +3 -1
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight.egg-info/requires.txt +3 -0
- svg_ultralight-0.36.0/tests/conftest.py +16 -0
- svg_ultralight-0.36.0/tests/resources/arrow.svg +3 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_bounding.py +8 -0
- svg_ultralight-0.34.0/tests/conftest.py +0 -11
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/.gitignore +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/README.md +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/setup.cfg +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/animate.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/bounding_boxes/__init__.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/bounding_boxes/supports_bounds.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/bounding_boxes/type_bound_collection.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/bounding_boxes/type_bound_element.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/bounding_boxes/type_bounding_box.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/bounding_boxes/type_padded_text.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/inkscape.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/layout.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/main.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/metadata.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/nsmap.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/py.typed +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/root_elements.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/string_conversion.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/strings/svg_strings.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/transformations.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/unit_conversion.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight.egg-info/dependency_links.txt +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight.egg-info/top_level.txt +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/__init__.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_inkscape.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_layout.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_matrices.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_metadata.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_new_element.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_queries.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_root_elements.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_string_conversion.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tests/test_svg_ultralight.py +0 -0
- {svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/tox.ini +0 -0
|
@@ -42,12 +42,12 @@ 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.14.0
|
|
46
46
|
hooks:
|
|
47
47
|
- id: mypy
|
|
48
48
|
name: mypy
|
|
49
49
|
language: python
|
|
50
|
-
language_version: python 3.
|
|
50
|
+
language_version: python 3.11
|
|
51
51
|
types: [python]
|
|
52
52
|
require_serial: true
|
|
53
53
|
verbose: true
|
|
@@ -57,21 +57,8 @@ repos:
|
|
|
57
57
|
# - --ignore-missing-imports
|
|
58
58
|
# files: ^(src/|tests/)
|
|
59
59
|
|
|
60
|
-
- repo: https://github.com/PyCQA/isort
|
|
61
|
-
rev: 5.13.2
|
|
62
|
-
hooks:
|
|
63
|
-
- id: isort
|
|
64
|
-
args: ["--profile", "black", "--filter-files", "--combine-as"]
|
|
65
|
-
|
|
66
|
-
- repo: https://github.com/psf/black
|
|
67
|
-
rev: 24.10.0
|
|
68
|
-
hooks:
|
|
69
|
-
- id: black
|
|
70
|
-
language_version: python3.9
|
|
71
|
-
args: ["--skip-magic-trailing-comma"]
|
|
72
|
-
|
|
73
60
|
- repo: https://github.com/asottile/pyupgrade
|
|
74
|
-
rev: v3.19.
|
|
61
|
+
rev: v3.19.1
|
|
75
62
|
hooks:
|
|
76
63
|
- args:
|
|
77
64
|
- --py39-plus
|
|
@@ -84,8 +71,6 @@ repos:
|
|
|
84
71
|
|
|
85
72
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
|
86
73
|
# ignores
|
|
87
|
-
# ANN101 Missing type annotation for self in method
|
|
88
|
-
# ANN102 Missing type annotation for cls in classmethod
|
|
89
74
|
# ANN201 Missing return type annotation for public function
|
|
90
75
|
# ANN202 Missing return type annotation for private function (wants -> None everywhere)
|
|
91
76
|
# B905 zip() without an explicit strict= parameter
|
|
@@ -103,25 +88,28 @@ repos:
|
|
|
103
88
|
# S101 Use of `assert` detected
|
|
104
89
|
# S603 `subprocess` call: check for execution of untrusted input
|
|
105
90
|
# PLR2004 Magic value used in comparison
|
|
106
|
-
# D413 [*] Missing blank line after last section ("Attributes")
|
|
107
|
-
# D407 [*] Missing dashed underline after section ("Attributes")
|
|
108
|
-
# D406 [*] Section name should end with a newline ("Attributes")
|
|
109
91
|
# S320 Using `lxml` to parse untrusted data is known to be vulnerable to XML attacks
|
|
110
92
|
# S301 don't use pickle
|
|
111
93
|
# B028 wants explicit stacklevel on warn
|
|
112
94
|
# BLE001 Use of `except Exception:` detected
|
|
113
|
-
rev: 'v0.
|
|
95
|
+
rev: 'v0.8.4'
|
|
114
96
|
hooks:
|
|
115
97
|
- id: ruff
|
|
98
|
+
name: "ruff-lint"
|
|
116
99
|
exclude: "tests"
|
|
117
100
|
args:
|
|
118
101
|
- --target-version=py39
|
|
119
102
|
- --select=ALL
|
|
120
|
-
- --ignore=
|
|
121
|
-
|
|
103
|
+
- --ignore=ANN201,ANN202,B905,COM812,D203,D213,I001,ISC003,N802,N806,PGH003,PLR0913,PTH108,S101,S603,PLR2004,S320,S301,B028,BLE001
|
|
104
|
+
- --fix
|
|
105
|
+
- --fixable=RUF022
|
|
106
|
+
- id: ruff
|
|
107
|
+
args: ["check", "--select", "I", "--fix"]
|
|
108
|
+
- id: ruff-format
|
|
109
|
+
name: "ruff-format"
|
|
122
110
|
|
|
123
111
|
# reads pyproject.toml for additional config
|
|
124
112
|
- repo: https://github.com/RobertCraigie/pyright-python
|
|
125
|
-
rev: v1.1.
|
|
113
|
+
rev: v1.1.391
|
|
126
114
|
hooks:
|
|
127
115
|
- id: pyright
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: svg-ultralight
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.36.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
|
|
@@ -15,6 +15,8 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
15
15
|
Requires-Dist: commitizen; extra == "dev"
|
|
16
16
|
Requires-Dist: pre-commit; extra == "dev"
|
|
17
17
|
Requires-Dist: tox; extra == "dev"
|
|
18
|
+
Provides-Extra: images
|
|
19
|
+
Requires-Dist: pillow; extra == "images"
|
|
18
20
|
|
|
19
21
|
# svg_ultralight
|
|
20
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "svg-ultralight"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.36.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" }
|
|
@@ -10,6 +10,7 @@ dependencies = ["lxml", "pillow", "paragraphs", "types-lxml"]
|
|
|
10
10
|
|
|
11
11
|
[project.optional-dependencies]
|
|
12
12
|
dev = ["pytest", "commitizen", "pre-commit", "tox"]
|
|
13
|
+
images = ["pillow"]
|
|
13
14
|
|
|
14
15
|
[build-system]
|
|
15
16
|
requires = ["setuptools", "setuptools-scm"]
|
|
@@ -18,6 +19,8 @@ build-backend = "setuptools.build_meta"
|
|
|
18
19
|
|
|
19
20
|
[tool.pytest.ini_options]
|
|
20
21
|
addopts = "--doctest-modules"
|
|
22
|
+
pythonpath = ["tests"]
|
|
23
|
+
log_cli = 1
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
[tool.isort]
|
|
@@ -34,10 +37,12 @@ legacy_tox_ini = """
|
|
|
34
37
|
commands = pytest tests
|
|
35
38
|
"""
|
|
36
39
|
|
|
40
|
+
[tool.ruff.lint.pydocstyle]
|
|
41
|
+
convention = "pep257"
|
|
37
42
|
|
|
38
43
|
[tool.commitizen]
|
|
39
44
|
name = "cz_conventional_commits"
|
|
40
|
-
version = "0.
|
|
45
|
+
version = "0.36.0"
|
|
41
46
|
tag_format = "$version"
|
|
42
47
|
version_files = ["pyproject.toml:^version"]
|
|
43
48
|
annotated_tag = true
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from svg_ultralight.bounding_boxes.bound_helpers import (
|
|
8
|
+
bbox_dict,
|
|
9
|
+
cut_bbox,
|
|
10
|
+
new_bbox_rect,
|
|
8
11
|
new_bbox_union,
|
|
9
12
|
new_bound_union,
|
|
10
13
|
new_element_union,
|
|
11
|
-
cut_bbox,
|
|
12
14
|
pad_bbox,
|
|
13
|
-
|
|
14
|
-
new_bbox_rect,
|
|
15
|
+
parse_bound_element,
|
|
15
16
|
)
|
|
16
17
|
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
17
18
|
from svg_ultralight.bounding_boxes.type_bound_collection import BoundCollection
|
|
@@ -35,10 +36,10 @@ from svg_ultralight.main import new_svg_root, write_svg
|
|
|
35
36
|
from svg_ultralight.metadata import new_metadata
|
|
36
37
|
from svg_ultralight.nsmap import NSMAP, new_qname
|
|
37
38
|
from svg_ultralight.query import (
|
|
39
|
+
clear_svg_ultralight_cache,
|
|
38
40
|
get_bounding_box,
|
|
39
41
|
get_bounding_boxes,
|
|
40
42
|
pad_text,
|
|
41
|
-
clear_svg_ultralight_cache,
|
|
42
43
|
)
|
|
43
44
|
from svg_ultralight.root_elements import new_svg_root_around_bounds
|
|
44
45
|
from svg_ultralight.string_conversion import (
|
|
@@ -55,10 +56,10 @@ from svg_ultralight.transformations import (
|
|
|
55
56
|
)
|
|
56
57
|
|
|
57
58
|
__all__ = [
|
|
59
|
+
"NSMAP",
|
|
58
60
|
"BoundCollection",
|
|
59
61
|
"BoundElement",
|
|
60
62
|
"BoundingBox",
|
|
61
|
-
"NSMAP",
|
|
62
63
|
"PaddedText",
|
|
63
64
|
"SupportsBounds",
|
|
64
65
|
"bbox_dict",
|
|
@@ -86,6 +87,7 @@ __all__ = [
|
|
|
86
87
|
"new_svg_root_around_bounds",
|
|
87
88
|
"pad_bbox",
|
|
88
89
|
"pad_text",
|
|
90
|
+
"parse_bound_element",
|
|
89
91
|
"transform_element",
|
|
90
92
|
"update_element",
|
|
91
93
|
"write_pdf",
|
{svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/bounding_boxes/bound_helpers.py
RENAMED
|
@@ -16,7 +16,10 @@ from svg_ultralight.bounding_boxes.type_padded_text import PaddedText
|
|
|
16
16
|
from svg_ultralight.constructors import new_element
|
|
17
17
|
|
|
18
18
|
if TYPE_CHECKING:
|
|
19
|
+
import os
|
|
20
|
+
|
|
19
21
|
from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
|
|
22
|
+
from lxml import etree
|
|
20
23
|
|
|
21
24
|
_Matrix = tuple[float, float, float, float, float, float]
|
|
22
25
|
|
|
@@ -149,8 +152,10 @@ def pad_bbox(bbox: SupportsBounds, pad: float | tuple[float, ...]) -> BoundingBo
|
|
|
149
152
|
len = 4 : 0, 1, 2, 3
|
|
150
153
|
:return: a new bounding box with padding applied.
|
|
151
154
|
"""
|
|
152
|
-
|
|
153
|
-
return cut_bbox(
|
|
155
|
+
top, right, bottom, left = _expand_pad(pad)
|
|
156
|
+
return cut_bbox(
|
|
157
|
+
bbox, x=bbox.x - left, y=bbox.y - top, x2=bbox.x2 + right, y2=bbox.y2 + bottom
|
|
158
|
+
)
|
|
154
159
|
|
|
155
160
|
|
|
156
161
|
def bbox_dict(bbox: SupportsBounds) -> dict[str, float]:
|
|
@@ -169,3 +174,34 @@ def new_bbox_rect(bbox: BoundingBox, **kwargs: float | str) -> EtreeElement:
|
|
|
169
174
|
:param kwargs: additional attributes for the rect element.
|
|
170
175
|
"""
|
|
171
176
|
return new_element("rect", **bbox_dict(bbox), **kwargs)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _get_view_box(elem: EtreeElement) -> tuple[float, float, float, float]:
|
|
180
|
+
"""Return the view box of an element as a tuple of floats.
|
|
181
|
+
|
|
182
|
+
:param elem: the element from which to extract the view box.
|
|
183
|
+
:return: a tuple of floats representing the view box.
|
|
184
|
+
|
|
185
|
+
This will work on svg files created by this library and some others. Not all svg
|
|
186
|
+
files have a viewBox attribute.
|
|
187
|
+
"""
|
|
188
|
+
view_box = elem.get("viewBox")
|
|
189
|
+
if view_box is None:
|
|
190
|
+
msg = "Element does not have a viewBox attribute."
|
|
191
|
+
raise ValueError(msg)
|
|
192
|
+
x, y, width, height = map(float, view_box.split())
|
|
193
|
+
return x, y, width, height
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def parse_bound_element(svg_fil: str | os.PathLike[str]) -> BoundElement:
|
|
197
|
+
"""Import an element as a BoundElement.
|
|
198
|
+
|
|
199
|
+
:param elem: the element to import.
|
|
200
|
+
:return: a BoundElement instance.
|
|
201
|
+
"""
|
|
202
|
+
tree = etree.parse(svg_fil)
|
|
203
|
+
root = tree.getroot()
|
|
204
|
+
elem = new_element("g")
|
|
205
|
+
elem.extend(list(root))
|
|
206
|
+
bbox = BoundingBox(*_get_view_box(root))
|
|
207
|
+
return BoundElement(elem, bbox)
|
{svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/constructors/new_element.py
RENAMED
|
@@ -20,7 +20,8 @@ from lxml import etree
|
|
|
20
20
|
from svg_ultralight.string_conversion import set_attributes
|
|
21
21
|
|
|
22
22
|
if TYPE_CHECKING:
|
|
23
|
-
from lxml.etree import QName
|
|
23
|
+
from lxml.etree import QName
|
|
24
|
+
from lxml.etree import _Element as EtreeElement # type: ignore
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def new_element(tag: str | QName, **attributes: str | float) -> EtreeElement:
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Crop an image before converting to binary and including in the svg file.
|
|
2
|
+
|
|
3
|
+
This optional module requires the Pillow library. Create an svg image element with a
|
|
4
|
+
rasterized image positioned inside a bounding box.
|
|
5
|
+
|
|
6
|
+
:author: Shay Hill
|
|
7
|
+
:created: 2024-11-20
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from paragraphs import par
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from PIL import Image
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from PIL.Image import Image as ImageType
|
|
21
|
+
except ImportError as err:
|
|
22
|
+
msg = par(
|
|
23
|
+
"""PIL is not installed. Install it using 'pip install Pillow' to use
|
|
24
|
+
svg_ultralight.image_ops module."""
|
|
25
|
+
)
|
|
26
|
+
raise ImportError(msg) from err
|
|
27
|
+
|
|
28
|
+
import base64
|
|
29
|
+
import io
|
|
30
|
+
|
|
31
|
+
from lxml import etree
|
|
32
|
+
|
|
33
|
+
from svg_ultralight import NSMAP
|
|
34
|
+
from svg_ultralight.bounding_boxes.bound_helpers import bbox_dict
|
|
35
|
+
from svg_ultralight.constructors import new_element
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
from lxml.etree import (
|
|
41
|
+
_Element as EtreeElement, # pyright: ignore [reportPrivateUsage]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _symmetric_crop(
|
|
48
|
+
image: ImageType, center: tuple[float, float] | None = None
|
|
49
|
+
) -> ImageType:
|
|
50
|
+
"""Crop an image symmetrically around a center point.
|
|
51
|
+
|
|
52
|
+
:param image: PIL.Image instance
|
|
53
|
+
:param center: optional center point for cropping. Proportions of image with and
|
|
54
|
+
image height, so the default value, (0.5, 0.5), is the true center of the
|
|
55
|
+
image. (0.4, 0.5) would crop 20% off the right side of the image.
|
|
56
|
+
:return: PIL.Image instance
|
|
57
|
+
"""
|
|
58
|
+
if center is None:
|
|
59
|
+
return image
|
|
60
|
+
|
|
61
|
+
if not all(0 < x < 1 for x in center):
|
|
62
|
+
msg = "Center must be between (0, 0) and (1, 1)"
|
|
63
|
+
raise ValueError(msg)
|
|
64
|
+
|
|
65
|
+
xd, yd = (min(x, 1 - x) for x in center)
|
|
66
|
+
left, right = sorted(x * image.width for x in (center[0] - xd, center[0] + xd))
|
|
67
|
+
top, bottom = sorted(x * image.height for x in (center[1] - yd, center[1] + yd))
|
|
68
|
+
|
|
69
|
+
return image.crop((left, top, right, bottom))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _crop_image_to_bbox_ratio(
|
|
73
|
+
image: ImageType, bbox: BoundingBox, center: tuple[float, float] | None = None
|
|
74
|
+
) -> ImageType:
|
|
75
|
+
"""Crop an image to the ratio of a bounding box.
|
|
76
|
+
|
|
77
|
+
:param image: PIL.Image instance
|
|
78
|
+
:param bbox: BoundingBox instance
|
|
79
|
+
:param center: optional center point for cropping. Proportions of image with and
|
|
80
|
+
image height, so the default value, (0.5, 0.5), is the true center of the
|
|
81
|
+
image. (0.4, 0.5) would crop 20% off the right side of the image.
|
|
82
|
+
:return: PIL.Image instance
|
|
83
|
+
|
|
84
|
+
This crops the image to the specified ratio. It's not a resize, so it will cut
|
|
85
|
+
off the top and bottom or the sides of the image to fit the ratio.
|
|
86
|
+
"""
|
|
87
|
+
image = _symmetric_crop(image, center)
|
|
88
|
+
width, height = image.size
|
|
89
|
+
|
|
90
|
+
ratio = bbox.width / bbox.height
|
|
91
|
+
if width / height > ratio:
|
|
92
|
+
new_width = height * ratio
|
|
93
|
+
left = (width - new_width) / 2
|
|
94
|
+
right = width - left
|
|
95
|
+
return image.crop((left, 0, right, height))
|
|
96
|
+
new_height = width / ratio
|
|
97
|
+
top = (height - new_height) / 2
|
|
98
|
+
bottom = height - top
|
|
99
|
+
return image.crop((0, top, width, bottom))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _get_svg_embedded_image_str(image: ImageType) -> str:
|
|
103
|
+
"""Return the string you'll need to embed an image in an svg.
|
|
104
|
+
|
|
105
|
+
:param image: PIL.Image instance
|
|
106
|
+
:return: argument for xlink:href
|
|
107
|
+
"""
|
|
108
|
+
in_mem_file = io.BytesIO()
|
|
109
|
+
image.save(in_mem_file, format="PNG")
|
|
110
|
+
_ = in_mem_file.seek(0)
|
|
111
|
+
img_bytes = in_mem_file.read()
|
|
112
|
+
base64_encoded_result_bytes = base64.b64encode(img_bytes)
|
|
113
|
+
base64_encoded_result_str = base64_encoded_result_bytes.decode("ascii")
|
|
114
|
+
return "data:image/png;base64," + base64_encoded_result_str
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def new_image_elem_in_bbox(
|
|
118
|
+
filename: Path | str, bbox: BoundingBox, center: tuple[float, float] | None
|
|
119
|
+
) -> EtreeElement:
|
|
120
|
+
"""Create a new svg image element inside a bounding box.
|
|
121
|
+
|
|
122
|
+
:param filename: filename of source image
|
|
123
|
+
:param bbox: bounding box for the image
|
|
124
|
+
:param center: center point for cropping. Proportions of image width and image
|
|
125
|
+
height, so the default value, (0.5, 0.5), is the true center of the image.
|
|
126
|
+
(0.4, 0.5) would crop 20% off the right side of the image.
|
|
127
|
+
:return: an etree image element with the cropped image embedded
|
|
128
|
+
"""
|
|
129
|
+
image = _crop_image_to_bbox_ratio(Image.open(filename), bbox, center)
|
|
130
|
+
svg_image = new_element("image", **bbox_dict(bbox))
|
|
131
|
+
svg_image.set(
|
|
132
|
+
etree.QName(NSMAP["xlink"], "href"), _get_svg_embedded_image_str(image)
|
|
133
|
+
)
|
|
134
|
+
return svg_image
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: svg-ultralight
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.36.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
|
|
@@ -15,6 +15,8 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
15
15
|
Requires-Dist: commitizen; extra == "dev"
|
|
16
16
|
Requires-Dist: pre-commit; extra == "dev"
|
|
17
17
|
Requires-Dist: tox; extra == "dev"
|
|
18
|
+
Provides-Extra: images
|
|
19
|
+
Requires-Dist: pillow; extra == "images"
|
|
18
20
|
|
|
19
21
|
# svg_ultralight
|
|
20
22
|
|
|
@@ -5,6 +5,7 @@ pyproject.toml
|
|
|
5
5
|
tox.ini
|
|
6
6
|
src/svg_ultralight/__init__.py
|
|
7
7
|
src/svg_ultralight/animate.py
|
|
8
|
+
src/svg_ultralight/image_ops.py
|
|
8
9
|
src/svg_ultralight/inkscape.py
|
|
9
10
|
src/svg_ultralight/layout.py
|
|
10
11
|
src/svg_ultralight/main.py
|
|
@@ -43,4 +44,5 @@ tests/test_new_element.py
|
|
|
43
44
|
tests/test_queries.py
|
|
44
45
|
tests/test_root_elements.py
|
|
45
46
|
tests/test_string_conversion.py
|
|
46
|
-
tests/test_svg_ultralight.py
|
|
47
|
+
tests/test_svg_ultralight.py
|
|
48
|
+
tests/resources/arrow.svg
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Test configuration for pytest.
|
|
2
|
+
|
|
3
|
+
:author: Shay Hill
|
|
4
|
+
:created: 7/2/2019
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
def pytest_assertrepr_compare(config: Any, op: str, left: str, right: str):
|
|
11
|
+
"""See full error diffs"""
|
|
12
|
+
if op in ("==", "!="):
|
|
13
|
+
return ["{0} {1} {2}".format(left, op, right)]
|
|
14
|
+
|
|
15
|
+
TEST_RESOURCES = Path(__file__).parent / "resources"
|
|
16
|
+
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" viewBox="0 0 10 10">
|
|
2
|
+
<rect x="0" y="0" width="10" height="10"/>
|
|
3
|
+
</svg>
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
import math
|
|
9
|
+
from conftest import TEST_RESOURCES
|
|
9
10
|
from svg_ultralight.bounding_boxes.type_bound_element import BoundElement
|
|
10
11
|
from svg_ultralight.bounding_boxes.type_padded_text import PaddedText
|
|
12
|
+
from lxml import etree
|
|
11
13
|
from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
|
|
12
14
|
from svg_ultralight.bounding_boxes.type_bound_collection import BoundCollection
|
|
13
15
|
from svg_ultralight.bounding_boxes.bound_helpers import (
|
|
14
16
|
pad_bbox,
|
|
15
17
|
cut_bbox,
|
|
18
|
+
parse_bound_element,
|
|
16
19
|
bbox_dict,
|
|
17
20
|
new_bbox_rect,
|
|
18
21
|
)
|
|
@@ -245,3 +248,8 @@ class TestBoundHelpers:
|
|
|
245
248
|
elem = new_bbox_rect(bbox)
|
|
246
249
|
assert elem.attrib == {"x": "0", "y": "1", "width": "2", "height": "3"}
|
|
247
250
|
|
|
251
|
+
def test_import_bound_element():
|
|
252
|
+
blem = parse_bound_element(TEST_RESOURCES / "arrow.svg")
|
|
253
|
+
assert blem.bbox == BoundingBox(_x=0, _y=0, _width=10, _height=10, _transformation=(1, 0, 0, 1, 0, 0))
|
|
254
|
+
assert etree.tostring(blem.elem) == b'<g><ns0:rect xmlns:ns0="http://www.w3.org/2000/svg"' + b' x="0" y="0" width="10" height="10"/>\n</g>'
|
|
255
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{svg_ultralight-0.34.0 → svg_ultralight-0.36.0}/src/svg_ultralight/bounding_boxes/__init__.py
RENAMED
|
File without changes
|
{svg_ultralight-0.34.0 → svg_ultralight-0.36.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{svg_ultralight-0.34.0 → svg_ultralight-0.36.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
|
|
File without changes
|