svg-ultralight 0.50.2__tar.gz → 0.52.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 (34) hide show
  1. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/PKG-INFO +1 -1
  2. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/pyproject.toml +2 -2
  3. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/bounding_boxes/padded_text_initializers.py +61 -9
  4. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/bounding_boxes/supports_bounds.py +1 -0
  5. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/bounding_boxes/type_bound_collection.py +6 -3
  6. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/bounding_boxes/type_bound_element.py +5 -2
  7. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/bounding_boxes/type_bounding_box.py +7 -1
  8. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/bounding_boxes/type_padded_text.py +15 -2
  9. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/font_tools/font_info.py +20 -5
  10. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/transformations.py +15 -3
  11. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/README.md +0 -0
  12. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/__init__.py +0 -0
  13. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/animate.py +0 -0
  14. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/attrib_hints.py +0 -0
  15. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/bounding_boxes/__init__.py +0 -0
  16. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/bounding_boxes/bound_helpers.py +0 -0
  17. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/constructors/__init__.py +0 -0
  18. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/constructors/new_element.py +0 -0
  19. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/font_tools/__init__.py +0 -0
  20. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/font_tools/comp_results.py +0 -0
  21. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/image_ops.py +0 -0
  22. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/inkscape.py +0 -0
  23. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/layout.py +0 -0
  24. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/main.py +0 -0
  25. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/metadata.py +0 -0
  26. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/nsmap.py +0 -0
  27. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/py.typed +0 -0
  28. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/query.py +0 -0
  29. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/read_svg.py +0 -0
  30. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/root_elements.py +0 -0
  31. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/string_conversion.py +0 -0
  32. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/strings/__init__.py +0 -0
  33. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/strings/svg_strings.py +0 -0
  34. {svg_ultralight-0.50.2 → svg_ultralight-0.52.0}/src/svg_ultralight/unit_conversion.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: svg-ultralight
3
- Version: 0.50.2
3
+ Version: 0.52.0
4
4
  Summary: a sensible way to create svg files with Python
5
5
  Author: Shay Hill
6
6
  Author-email: Shay Hill <shay_public@hotmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "svg-ultralight"
3
- version = "0.50.2"
3
+ version = "0.52.0"
4
4
  description = "a sensible way to create svg files with Python"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -33,7 +33,7 @@ dev = [
33
33
 
34
34
  [tool.commitizen]
35
35
  name = "cz_conventional_commits"
36
- version = "0.50.2"
36
+ version = "0.52.0"
37
37
  tag_format = "$version"
38
38
  major-version-zero = true
39
39
  version_files = ["pyproject.toml:^version"]
@@ -23,11 +23,12 @@ to 16px.
23
23
  from __future__ import annotations
24
24
 
25
25
  from copy import deepcopy
26
- from typing import TYPE_CHECKING
26
+ from typing import TYPE_CHECKING, overload
27
27
 
28
28
  from svg_ultralight.bounding_boxes.type_padded_text import PaddedText
29
29
  from svg_ultralight.constructors import new_element, update_element
30
30
  from svg_ultralight.font_tools.font_info import (
31
+ FTFontInfo,
31
32
  get_padded_text_info,
32
33
  get_svg_font_attributes,
33
34
  )
@@ -99,6 +100,7 @@ def pad_text(
99
100
  return PaddedText(text_elem, bbox, tpad, rpad, bpad, lpad)
100
101
 
101
102
 
103
+ @overload
102
104
  def pad_text_ft(
103
105
  font: str | os.PathLike[str],
104
106
  text: str,
@@ -109,11 +111,38 @@ def pad_text_ft(
109
111
  y_bounds_reference: str | None = None,
110
112
  attrib: OptionalElemAttribMapping = None,
111
113
  **attributes: ElemAttrib,
112
- ) -> PaddedText:
114
+ ) -> PaddedText: ...
115
+
116
+
117
+ @overload
118
+ def pad_text_ft(
119
+ font: str | os.PathLike[str],
120
+ text: list[str],
121
+ font_size: float | None = None,
122
+ ascent: float | None = None,
123
+ descent: float | None = None,
124
+ *,
125
+ y_bounds_reference: str | None = None,
126
+ attrib: OptionalElemAttribMapping = None,
127
+ **attributes: ElemAttrib,
128
+ ) -> list[PaddedText]: ...
129
+
130
+
131
+ def pad_text_ft(
132
+ font: str | os.PathLike[str],
133
+ text: str | list[str],
134
+ font_size: float | None = None,
135
+ ascent: float | None = None,
136
+ descent: float | None = None,
137
+ *,
138
+ y_bounds_reference: str | None = None,
139
+ attrib: OptionalElemAttribMapping = None,
140
+ **attributes: ElemAttrib,
141
+ ) -> PaddedText | list[PaddedText]:
113
142
  """Create a new PaddedText instance using fontTools.
114
143
 
115
144
  :param font: path to a font file.
116
- :param text: the text of the text element.
145
+ :param text: the text of the text element or a list of text strings.
117
146
  :param font_size: the font size to use.
118
147
  :param ascent: the ascent of the font. If not provided, it will be calculated
119
148
  from the font file.
@@ -130,7 +159,8 @@ def pad_text_ft(
130
159
  :param attributes: additional attributes to set on the text element. There is a
131
160
  chance these will cause the font element to exceed the BoundingBox of the
132
161
  PaddedText instance.
133
- :return: a PaddedText instance with a line_gap defined.
162
+ :return: a PaddedText instance with a line_gap defined. If a list of strings is
163
+ given for parameter `text`, a list of PaddedText instances is returned.
134
164
  """
135
165
  attributes.update(attrib or {})
136
166
  attributes_ = format_attr_dict(**attributes)
@@ -142,11 +172,33 @@ def pad_text_ft(
142
172
  _ = attributes_.pop("font-weight", None)
143
173
  _ = attributes_.pop("font-stretch", None)
144
174
 
145
- info = get_padded_text_info(
146
- font, text, font_size, ascent, descent, y_bounds_reference=y_bounds_reference
147
- )
148
- elem = info.new_element(**attributes_)
149
- return PaddedText(elem, info.bbox, *info.padding, info.line_gap)
175
+ font_info = FTFontInfo(font)
176
+
177
+ try:
178
+ input_one_text_item = False
179
+ if isinstance(text, str):
180
+ input_one_text_item = True
181
+ text = [text]
182
+
183
+ elems: list[PaddedText] = []
184
+ for text_item in text:
185
+ text_info = get_padded_text_info(
186
+ font_info,
187
+ text_item,
188
+ font_size,
189
+ ascent,
190
+ descent,
191
+ y_bounds_reference=y_bounds_reference,
192
+ )
193
+ elem = text_info.new_element(**attributes_)
194
+ elems.append(
195
+ PaddedText(elem, text_info.bbox, *text_info.padding, text_info.line_gap)
196
+ )
197
+ finally:
198
+ font_info.font.close()
199
+ if input_one_text_item:
200
+ return elems[0]
201
+ return elems
150
202
 
151
203
 
152
204
  def pad_text_mix(
@@ -52,6 +52,7 @@ class SupportsBounds(Protocol):
52
52
  scale: tuple[float, float] | float | None = None,
53
53
  dx: float | None = None,
54
54
  dy: float | None = None,
55
+ reverse: bool = False,
55
56
  ) -> None:
56
57
  """Apply a transformation to the object."""
57
58
  ...
@@ -47,6 +47,7 @@ class BoundCollection(HasBoundingBox):
47
47
  scale: tuple[float, float] | float | None = None,
48
48
  dx: float | None = None,
49
49
  dy: float | None = None,
50
+ reverse: bool = False,
50
51
  ) -> None:
51
52
  """Transform each bound element in self.blems.
52
53
 
@@ -54,9 +55,11 @@ class BoundCollection(HasBoundingBox):
54
55
  :param scale: optional scale factor
55
56
  :param dx: optional x translation
56
57
  :param dy: optional y translation
58
+ :param reverse: Transform the element as if it were in a <g> element
59
+ transformed by tmat.
57
60
 
58
61
  Keep track of all compounding transformations in order to have a value for
59
- self.scale (required for membersh and to provide access to cumulative
62
+ self.scale (required for members and to provide access to cumulative
60
63
  transforms should this be useful for any reason. This means all
61
64
  transformations must be applied to two bounding boxes: a persistant bbox to
62
65
  keep track of the scale property and a temporary bbox to isolate each
@@ -66,6 +69,6 @@ class BoundCollection(HasBoundingBox):
66
69
  self.bbox.transform(tmat)
67
70
  for blem in self.blems:
68
71
  if isinstance(blem, EtreeElement):
69
- _ = transform_element(blem, tmat)
72
+ _ = transform_element(blem, tmat, reverse=reverse)
70
73
  else:
71
- blem.transform(tmat)
74
+ blem.transform(tmat, reverse=reverse)
@@ -52,6 +52,7 @@ class BoundElement(HasBoundingBox):
52
52
  scale: tuple[float, float] | float | None = None,
53
53
  dx: float | None = None,
54
54
  dy: float | None = None,
55
+ reverse: bool = False,
55
56
  ) -> None:
56
57
  """Transform the element and bounding box.
57
58
 
@@ -59,7 +60,9 @@ class BoundElement(HasBoundingBox):
59
60
  :param scale: a scaling factor
60
61
  :param dx: the x translation
61
62
  :param dy: the y translation
63
+ :param reverse: Transform the element as if it were in a <g> element
64
+ transformed by tmat.
62
65
  """
63
66
  tmat = new_transformation_matrix(transformation, scale=scale, dx=dx, dy=dy)
64
- self.bbox.transform(tmat)
65
- _ = transform_element(self.elem, tmat)
67
+ self.bbox.transform(tmat, reverse=reverse)
68
+ _ = transform_element(self.elem, tmat, reverse=reverse)
@@ -78,6 +78,7 @@ class HasBoundingBox(SupportsBounds):
78
78
  scale: tuple[float, float] | float | None = None,
79
79
  dx: float | None = None,
80
80
  dy: float | None = None,
81
+ reverse: bool = False,
81
82
  ) -> None:
82
83
  """Transform the bounding box by updating the transformation attribute.
83
84
 
@@ -85,6 +86,8 @@ class HasBoundingBox(SupportsBounds):
85
86
  :param scale: scale factor
86
87
  :param dx: x translation
87
88
  :param dy: y translation
89
+ :param reverse: Transform the element as if it were in a <g> element
90
+ transformed by tmat.
88
91
 
89
92
  All parameters are optional. Scale, dx, and dy are optional and applied after
90
93
  the transformation matrix if both are given. This shouldn't be necessary in
@@ -94,7 +97,10 @@ class HasBoundingBox(SupportsBounds):
94
97
  when applying a transformation from another bounding box instance.
95
98
  """
96
99
  tmat = new_transformation_matrix(transformation, scale=scale, dx=dx, dy=dy)
97
- self.bbox.transformation = mat_dot(tmat, self.bbox.transformation)
100
+ if reverse:
101
+ self.bbox.transformation = mat_dot(self.bbox.transformation, tmat)
102
+ else:
103
+ self.bbox.transformation = mat_dot(tmat, self.bbox.transformation)
98
104
 
99
105
  @property
100
106
  def scale(self) -> tuple[float, float]:
@@ -119,6 +119,16 @@ class PaddedText(BoundElement):
119
119
  self.lpad = lpad
120
120
  self._line_gap = line_gap
121
121
 
122
+ @property
123
+ def tbox(self) -> BoundingBox:
124
+ """Return the unpadded BoundingBox around the text element.
125
+
126
+ Tight bbox or True bbox. An alias for unpadded_bbox.
127
+
128
+ :return: The unpadded BoundingBox around the text element.
129
+ """
130
+ return self.unpadded_bbox
131
+
122
132
  @property
123
133
  def bbox(self) -> BoundingBox:
124
134
  """Return a BoundingBox around the margins and cap/baseline.
@@ -154,6 +164,7 @@ class PaddedText(BoundElement):
154
164
  scale: tuple[float, float] | float | None = None,
155
165
  dx: float | None = None,
156
166
  dy: float | None = None,
167
+ reverse: bool = False,
157
168
  ) -> None:
158
169
  """Transform the element and bounding box.
159
170
 
@@ -161,10 +172,12 @@ class PaddedText(BoundElement):
161
172
  :param scale: a scaling factor
162
173
  :param dx: the x translation
163
174
  :param dy: the y translation
175
+ :param reverse: Transform the element as if it were in a <g> element
176
+ transformed by tmat.
164
177
  """
165
178
  tmat = new_transformation_matrix(transformation, scale=scale, dx=dx, dy=dy)
166
- self.unpadded_bbox.transform(tmat)
167
- _ = transform_element(self.elem, tmat)
179
+ self.unpadded_bbox.transform(tmat, reverse=reverse)
180
+ _ = transform_element(self.elem, tmat, reverse=reverse)
168
181
 
169
182
  @property
170
183
  def line_gap(self) -> float:
@@ -425,7 +425,13 @@ class FTFontInfo:
425
425
  coordinates (+y is down).
426
426
  """
427
427
  min_x, min_y, max_x, max_y = self.get_char_bounds(char)
428
- return BoundingBox(min_x, -max_y, max_x - min_x, max_y - min_y)
428
+ return BoundingBox(
429
+ min_x,
430
+ min_y,
431
+ max_x - min_x,
432
+ max_y - min_y,
433
+ transformation=(1, 0, 0, -1, 0, 0),
434
+ )
429
435
 
430
436
  def get_text_bounds(self, text: str) -> tuple[int, int, int, int]:
431
437
  """Return bounds of a string as xmin, ymin, xmax, ymax.
@@ -484,7 +490,13 @@ class FTFontInfo:
484
490
  coordinates (+y is down).
485
491
  """
486
492
  min_x, min_y, max_x, max_y = self.get_text_bounds(text)
487
- return BoundingBox(min_x, -max_y, max_x - min_x, max_y - min_y)
493
+ return BoundingBox(
494
+ min_x,
495
+ min_y,
496
+ max_x - min_x,
497
+ max_y - min_y,
498
+ transformation=(1, 0, 0, -1, 0, 0),
499
+ )
488
500
 
489
501
  def get_lsb(self, char: str) -> float:
490
502
  """Return the left side bearing of a character."""
@@ -567,7 +579,7 @@ class FTTextInfo:
567
579
  """
568
580
  bbox = self.font.get_text_bbox(self.text)
569
581
  bbox.transform(scale=self.scale)
570
- return BoundingBox(*bbox.values())
582
+ return bbox
571
583
 
572
584
  @property
573
585
  def ascent(self) -> float:
@@ -644,7 +656,7 @@ def get_font_size_given_height(font: str | os.PathLike[str], height: float) -> f
644
656
 
645
657
 
646
658
  def get_padded_text_info(
647
- font: str | os.PathLike[str],
659
+ font: str | os.PathLike[str] | FTFontInfo,
648
660
  text: str,
649
661
  font_size: float | None = None,
650
662
  ascent: float | None = None,
@@ -669,7 +681,10 @@ def get_padded_text_info(
669
681
  :return: A FTTextInfo object with the information necessary to create a
670
682
  PaddedText instance: bbox, tpad, rpad, bpad, lpad.
671
683
  """
672
- font_info = FTFontInfo(font)
684
+ if isinstance(font, FTFontInfo):
685
+ font_info = font
686
+ else:
687
+ font_info = FTFontInfo(font)
673
688
  if y_bounds_reference:
674
689
  capline_info = FTTextInfo(font_info, y_bounds_reference, font_size)
675
690
  ascent = -capline_info.bbox.y
@@ -129,12 +129,24 @@ def new_transformation_matrix(
129
129
  return mat_dot((float(scale_x), 0, 0, float(scale_y), dx, dy), transformation)
130
130
 
131
131
 
132
- def transform_element(elem: EtreeElement, matrix: _Matrix) -> EtreeElement:
132
+ def transform_element(
133
+ elem: EtreeElement, matrix: _Matrix, *, reverse: bool = False
134
+ ) -> EtreeElement:
133
135
  """Apply a transformation matrix to an svg element.
134
136
 
135
137
  :param elem: svg element
136
- :param matrix: transformation matrix
138
+ :par m matrix: transformation matrix
139
+
140
+ :param reverse: If you have a transformation matrix, A, and wish to apply an
141
+ additional transform, B, the result is B @ A. This is how an element can be
142
+ cumulatively transformed in svg.
143
+
144
+ If the element is transformed by A and is a part of a GROUP transformed by B,
145
+ then the result is the reverse: A @ B.
137
146
  """
138
147
  current = get_transform_matrix(elem)
139
- elem.attrib["transform"] = svg_matrix(mat_dot(matrix, current))
148
+ if reverse:
149
+ elem.attrib["transform"] = svg_matrix(mat_dot(current, matrix))
150
+ else:
151
+ elem.attrib["transform"] = svg_matrix(mat_dot(matrix, current))
140
152
  return elem