svg-ultralight 0.48.0__py3-none-any.whl → 0.50.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of svg-ultralight might be problematic. Click here for more details.

Files changed (37) hide show
  1. svg_ultralight/__init__.py +108 -105
  2. svg_ultralight/animate.py +40 -40
  3. svg_ultralight/attrib_hints.py +13 -14
  4. svg_ultralight/bounding_boxes/__init__.py +5 -5
  5. svg_ultralight/bounding_boxes/bound_helpers.py +189 -189
  6. svg_ultralight/bounding_boxes/padded_text_initializers.py +207 -207
  7. svg_ultralight/bounding_boxes/supports_bounds.py +166 -166
  8. svg_ultralight/bounding_boxes/type_bound_collection.py +71 -71
  9. svg_ultralight/bounding_boxes/type_bound_element.py +65 -65
  10. svg_ultralight/bounding_boxes/type_bounding_box.py +396 -396
  11. svg_ultralight/bounding_boxes/type_padded_text.py +411 -411
  12. svg_ultralight/constructors/__init__.py +14 -14
  13. svg_ultralight/constructors/new_element.py +115 -115
  14. svg_ultralight/font_tools/__init__.py +5 -5
  15. svg_ultralight/font_tools/comp_results.py +295 -293
  16. svg_ultralight/font_tools/font_info.py +793 -792
  17. svg_ultralight/image_ops.py +156 -156
  18. svg_ultralight/inkscape.py +261 -261
  19. svg_ultralight/layout.py +290 -291
  20. svg_ultralight/main.py +183 -198
  21. svg_ultralight/metadata.py +122 -122
  22. svg_ultralight/nsmap.py +36 -36
  23. svg_ultralight/py.typed +5 -0
  24. svg_ultralight/query.py +254 -249
  25. svg_ultralight/read_svg.py +58 -0
  26. svg_ultralight/root_elements.py +87 -87
  27. svg_ultralight/string_conversion.py +244 -244
  28. svg_ultralight/strings/__init__.py +21 -13
  29. svg_ultralight/strings/svg_strings.py +106 -67
  30. svg_ultralight/transformations.py +140 -141
  31. svg_ultralight/unit_conversion.py +247 -248
  32. {svg_ultralight-0.48.0.dist-info → svg_ultralight-0.50.1.dist-info}/METADATA +208 -214
  33. svg_ultralight-0.50.1.dist-info/RECORD +34 -0
  34. svg_ultralight-0.50.1.dist-info/WHEEL +4 -0
  35. svg_ultralight-0.48.0.dist-info/RECORD +0 -34
  36. svg_ultralight-0.48.0.dist-info/WHEEL +0 -5
  37. svg_ultralight-0.48.0.dist-info/top_level.txt +0 -1
@@ -1,156 +1,156 @@
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.bounding_boxes.type_bound_element import BoundElement
36
- from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
37
- from svg_ultralight.constructors import new_element
38
-
39
- if TYPE_CHECKING:
40
- import os
41
-
42
- from lxml.etree import (
43
- _Element as EtreeElement, # pyright: ignore [reportPrivateUsage]
44
- )
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
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_blem(
118
- filename: str | os.PathLike[str],
119
- bbox: BoundingBox | None = None,
120
- center: tuple[float, float] | None = None,
121
- ) -> BoundElement:
122
- """Create a new svg image element inside a bounding box.
123
-
124
- :param filename: filename of source image
125
- :param bbox: bounding box for the image
126
- :param center: center point for cropping. Proportions of image width and image
127
- height, so the default value, (0.5, 0.5), is the true center of the image.
128
- (0.4, 0.5) would crop 20% off the right side of the image.
129
- :return: a BoundElement element with the cropped image embedded
130
- """
131
- image = Image.open(filename)
132
- if bbox is None:
133
- bbox = BoundingBox(0, 0, image.width, image.height)
134
- image = _crop_image_to_bbox_ratio(Image.open(filename), bbox, center)
135
- svg_image = new_element("image", **bbox_dict(bbox))
136
- svg_image.set(
137
- etree.QName(NSMAP["xlink"], "href"), _get_svg_embedded_image_str(image)
138
- )
139
- return BoundElement(svg_image, bbox)
140
-
141
-
142
- def new_image_elem_in_bbox(
143
- filename: str | os.PathLike[str],
144
- bbox: BoundingBox | None = None,
145
- center: tuple[float, float] | None = None,
146
- ) -> EtreeElement:
147
- """Create a new svg image element inside a bounding box.
148
-
149
- :param filename: filename of source image
150
- :param bbox: bounding box for the image
151
- :param center: center point for cropping. Proportions of image width and image
152
- height, so the default value, (0.5, 0.5), is the true center of the image.
153
- (0.4, 0.5) would crop 20% off the right side of the image.
154
- :return: an etree image element with the cropped image embedded
155
- """
156
- return new_image_blem(filename, bbox, center).elem
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.bounding_boxes.type_bound_element import BoundElement
36
+ from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
37
+ from svg_ultralight.constructors import new_element
38
+
39
+ if TYPE_CHECKING:
40
+ import os
41
+
42
+ from lxml.etree import (
43
+ _Element as EtreeElement, # pyright: ignore [reportPrivateUsage]
44
+ )
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
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_blem(
118
+ filename: str | os.PathLike[str],
119
+ bbox: BoundingBox | None = None,
120
+ center: tuple[float, float] | None = None,
121
+ ) -> BoundElement:
122
+ """Create a new svg image element inside a bounding box.
123
+
124
+ :param filename: filename of source image
125
+ :param bbox: bounding box for the image
126
+ :param center: center point for cropping. Proportions of image width and image
127
+ height, so the default value, (0.5, 0.5), is the true center of the image.
128
+ (0.4, 0.5) would crop 20% off the right side of the image.
129
+ :return: a BoundElement element with the cropped image embedded
130
+ """
131
+ image = Image.open(filename)
132
+ if bbox is None:
133
+ bbox = BoundingBox(0, 0, image.width, image.height)
134
+ image = _crop_image_to_bbox_ratio(Image.open(filename), bbox, center)
135
+ svg_image = new_element("image", **bbox_dict(bbox))
136
+ svg_image.set(
137
+ etree.QName(NSMAP["xlink"], "href"), _get_svg_embedded_image_str(image)
138
+ )
139
+ return BoundElement(svg_image, bbox)
140
+
141
+
142
+ def new_image_elem_in_bbox(
143
+ filename: str | os.PathLike[str],
144
+ bbox: BoundingBox | None = None,
145
+ center: tuple[float, float] | None = None,
146
+ ) -> EtreeElement:
147
+ """Create a new svg image element inside a bounding box.
148
+
149
+ :param filename: filename of source image
150
+ :param bbox: bounding box for the image
151
+ :param center: center point for cropping. Proportions of image width and image
152
+ height, so the default value, (0.5, 0.5), is the true center of the image.
153
+ (0.4, 0.5) would crop 20% off the right side of the image.
154
+ :return: an etree image element with the cropped image embedded
155
+ """
156
+ return new_image_blem(filename, bbox, center).elem