svg-ultralight 0.27.0__tar.gz → 0.28.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 (44) hide show
  1. svg_ultralight-0.28.0/.gitignore +3 -0
  2. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/PKG-INFO +1 -1
  3. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/pyproject.toml +2 -2
  4. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/bounding_boxes/type_bounding_box.py +73 -38
  5. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/query.py +2 -1
  6. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight.egg-info/PKG-INFO +1 -1
  7. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight.egg-info/SOURCES.txt +1 -0
  8. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/test_queries.py +11 -15
  9. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/.pre-commit-config.yaml +0 -0
  10. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/README.md +0 -0
  11. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/setup.cfg +0 -0
  12. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/__init__.py +0 -0
  13. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/animate.py +0 -0
  14. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/bounding_boxes/__init__.py +0 -0
  15. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/bounding_boxes/bound_helpers.py +0 -0
  16. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/bounding_boxes/supports_bounds.py +0 -0
  17. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/bounding_boxes/type_bound_element.py +0 -0
  18. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/bounding_boxes/type_padded_text.py +0 -0
  19. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/constructors/__init__.py +0 -0
  20. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/constructors/new_element.py +0 -0
  21. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/inkscape.py +0 -0
  22. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/layout.py +0 -0
  23. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/main.py +0 -0
  24. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/metadata.py +0 -0
  25. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/nsmap.py +0 -0
  26. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/py.typed +0 -0
  27. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/root_elements.py +0 -0
  28. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/string_conversion.py +0 -0
  29. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/strings/__init__.py +0 -0
  30. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/strings/svg_strings.py +0 -0
  31. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight/unit_conversion.py +0 -0
  32. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight.egg-info/dependency_links.txt +0 -0
  33. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight.egg-info/requires.txt +0 -0
  34. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/src/svg_ultralight.egg-info/top_level.txt +0 -0
  35. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/__init__.py +0 -0
  36. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/conftest.py +0 -0
  37. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/test_inkscape.py +0 -0
  38. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/test_layout.py +0 -0
  39. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/test_metadata.py +0 -0
  40. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/test_new_element.py +0 -0
  41. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/test_root_elements.py +0 -0
  42. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/test_string_conversion.py +0 -0
  43. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tests/test_svg_ultralight.py +0 -0
  44. {svg_ultralight-0.27.0 → svg_ultralight-0.28.0}/tox.ini +0 -0
@@ -0,0 +1,3 @@
1
+ Update-PythonVenv.ps1
2
+ requirements.txt
3
+ dev_requirements.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: svg-ultralight
3
- Version: 0.27.0
3
+ Version: 0.28.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "svg-ultralight"
3
- version = "0.27.0"
3
+ version = "0.28.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" }
@@ -37,7 +37,7 @@ legacy_tox_ini = """
37
37
 
38
38
  [tool.commitizen]
39
39
  name = "cz_conventional_commits"
40
- version = "0.27.0"
40
+ version = "0.28.0"
41
41
  tag_format = "$version"
42
42
  version_files = ["pyproject.toml:^version"]
43
43
  annotated_tag = true
@@ -6,13 +6,51 @@
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- from dataclasses import dataclass
9
+ import dataclasses
10
10
 
11
11
  from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
12
12
  from svg_ultralight.string_conversion import format_number
13
13
 
14
+ Matrix = tuple[float, float, float, float, float, float]
14
15
 
15
- @dataclass
16
+
17
+ def mat_dot(mat1: Matrix, mat2: Matrix) -> Matrix:
18
+ """Matrix multiplication for svg-style matrices.
19
+
20
+ :param mat1: transformation matrix (sx, 0, 0, sy, tx, ty)
21
+ :param mat2: transformation matrix (sx, 0, 0, sy, tx, ty)
22
+
23
+ Svg uses an unusual matrix format. For 3x3 transformation matrix
24
+
25
+ [[00, 01, 02],
26
+ [10, 11, 12],
27
+ [20, 21, 22]]
28
+
29
+ The svg matrix is
30
+ (00, 10, 01, 11, 02, 12)
31
+
32
+ Values 10 and 01 are only used for skewing, which is not supported by a bounding
33
+ box, but they're here if this function is used in other ways.
34
+ """
35
+ aa = sum(mat1[x] * mat2[y] for x, y in ((0, 0), (2, 1)))
36
+ bb = sum(mat1[x] * mat2[y] for x, y in ((1, 0), (3, 1)))
37
+ cc = sum(mat1[x] * mat2[y] for x, y in ((0, 2), (2, 3)))
38
+ dd = sum(mat1[x] * mat2[y] for x, y in ((1, 2), (3, 3)))
39
+ ee = sum(mat1[x] * mat2[y] for x, y in ((0, 4), (2, 5))) + mat1[4]
40
+ ff = sum(mat1[x] * mat2[y] for x, y in ((1, 4), (3, 5))) + mat1[5]
41
+ return (aa, bb, cc, dd, ee, ff)
42
+
43
+
44
+ def mat_apply(mat1: Matrix, mat2: tuple[float, float]) -> tuple[float, float]:
45
+ """Apply an svg-style transformation matrix to a point.
46
+
47
+ :param mat1: transformation matrix (sx, 0, 0, sy, tx, ty)
48
+ :param mat2: point (x, y)
49
+ """
50
+ return mat1[0] * mat2[0] + mat1[4], mat1[3] * mat2[1] + mat1[5]
51
+
52
+
53
+ @dataclasses.dataclass
16
54
  class BoundingBox(SupportsBounds):
17
55
  """Mutable bounding box object for svg_ultralight.
18
56
 
@@ -21,14 +59,12 @@ class BoundingBox(SupportsBounds):
21
59
  :param width: width of the bounding box
22
60
  :param height: height of the bounding box
23
61
 
24
- The below optional parameters, in addition to the required parameters, capture
25
- the entire state of a BoundingBox instance. They could be used to make a copy or
62
+ The below optional parameter, in addition to the required parameters, captures
63
+ the entire state of a BoundingBox instance. It could be used to make a copy or
26
64
  to initialize a transformed box with the same transform_string as another box.
27
- Under most circumstances, they will not be used.
65
+ Under most circumstances, it will not be used.
28
66
 
29
- :param scale: scale of the bounding box
30
- :param translation_x: x translation of the bounding box
31
- :param translation_y: y translation of the bounding box
67
+ :param _transform: transformation matrix
32
68
 
33
69
  Functions that return a bounding box will return a BoundingBox instance. This
34
70
  instance can be transformed (uniform scale and translate only). Transformations
@@ -37,8 +73,8 @@ class BoundingBox(SupportsBounds):
37
73
  Define the bbox with x=, y=, width=, height=
38
74
 
39
75
  Transform the BoundingBox by setting these variables. Each time you set x, cx,
40
- x2, y, cy, y2, width, or height, private transformation values (_scale,
41
- _transform_x, and _transform_y) will be updated.
76
+ x2, y, cy, y2, width, or height, private transformation value _transform will be
77
+ updated.
42
78
 
43
79
  The ultimate transformation can be accessed through ``.transformation_string``.
44
80
  So the workflow will look like :
@@ -78,13 +114,19 @@ class BoundingBox(SupportsBounds):
78
114
  _y: float
79
115
  _width: float
80
116
  _height: float
81
- _scale: float = 1.0
82
- _translation_x: float = 0.0
83
- _translation_y: float = 0.0
117
+ _transform: Matrix = (1, 0, 0, 1, 0, 0)
118
+
119
+ @property
120
+ def transform(self) -> Matrix:
121
+ """Get read only tranformation matrix.
122
+
123
+ :return: transformation matrix of the bounding box
124
+ """
125
+ return self._transform
84
126
 
85
127
  @property
86
128
  def scale(self) -> float:
87
- """Read-only scale.
129
+ """Get scale of the bounding box.
88
130
 
89
131
  :return: uniform scale of the bounding box
90
132
 
@@ -95,7 +137,7 @@ class BoundingBox(SupportsBounds):
95
137
  width*scale, height => height*scale, scale => scale*scale. This matches how
96
138
  scale works in almost every other context.
97
139
  """
98
- return self._scale
140
+ return self._transform[0]
99
141
 
100
142
  @scale.setter
101
143
  def scale(self, value: float) -> None:
@@ -110,7 +152,7 @@ class BoundingBox(SupportsBounds):
110
152
  `scale = 2` -> ignore whatever scale was previously defined and set scale to 2
111
153
  `scale *= 2` -> make it twice as big as it was.
112
154
  """
113
- self._scale = value
155
+ self._add_transform(value / self.scale, 0, 0)
114
156
 
115
157
  @property
116
158
  def x(self) -> float:
@@ -118,7 +160,7 @@ class BoundingBox(SupportsBounds):
118
160
 
119
161
  :return: internal _x value transformed by scale and translation
120
162
  """
121
- return (self._translation_x + self._x) * self._scale
163
+ return mat_apply(self._transform, (self._x, 0))[0]
122
164
 
123
165
  @x.setter
124
166
  def x(self, value: float) -> None:
@@ -142,7 +184,7 @@ class BoundingBox(SupportsBounds):
142
184
 
143
185
  :param value: new center x value after transformation
144
186
  """
145
- self._add_transform(1, value - self.cx, 0)
187
+ self.x += value - self.cx
146
188
 
147
189
  @property
148
190
  def x2(self) -> float:
@@ -158,7 +200,7 @@ class BoundingBox(SupportsBounds):
158
200
 
159
201
  :param value: new x2 value after transformation
160
202
  """
161
- self._add_transform(1, value - self.x2, 0)
203
+ self.x += value - self.x2
162
204
 
163
205
  @property
164
206
  def y(self) -> float:
@@ -166,7 +208,7 @@ class BoundingBox(SupportsBounds):
166
208
 
167
209
  :return: internal _y value transformed by scale and translation
168
210
  """
169
- return (self._translation_y + self._y) * self._scale
211
+ return mat_apply(self._transform, (0, self._y))[1]
170
212
 
171
213
  @y.setter
172
214
  def y(self, value: float) -> None:
@@ -190,7 +232,7 @@ class BoundingBox(SupportsBounds):
190
232
 
191
233
  :param value: new center y value after transformation
192
234
  """
193
- self._add_transform(1, 0, value - self.cy)
235
+ self.y += value - self.cy
194
236
 
195
237
  @property
196
238
  def y2(self) -> float:
@@ -206,7 +248,7 @@ class BoundingBox(SupportsBounds):
206
248
 
207
249
  :param value: new y2 value after transformation
208
250
  """
209
- self.y = value - self.height
251
+ self.y += value - self.y2
210
252
 
211
253
  @property
212
254
  def width(self) -> float:
@@ -214,7 +256,7 @@ class BoundingBox(SupportsBounds):
214
256
 
215
257
  :return: internal _width value transformed by scale
216
258
  """
217
- return self._width * self._scale
259
+ return self._width * self.scale
218
260
 
219
261
  @width.setter
220
262
  def width(self, value: float) -> None:
@@ -227,7 +269,7 @@ class BoundingBox(SupportsBounds):
227
269
  """
228
270
  current_x = self.x
229
271
  current_y = self.y
230
- self._scale *= value / self.width
272
+ self.scale *= value / self.width
231
273
  self.x = current_x
232
274
  self.y = current_y
233
275
 
@@ -237,7 +279,7 @@ class BoundingBox(SupportsBounds):
237
279
 
238
280
  :return: internal _height value transformed by scale
239
281
  """
240
- return self._height * self._scale
282
+ return self._height * self.scale
241
283
 
242
284
  @height.setter
243
285
  def height(self, value: float) -> None:
@@ -250,18 +292,15 @@ class BoundingBox(SupportsBounds):
250
292
  """
251
293
  self.width = value * self.width / self.height
252
294
 
253
- def _add_transform(self, scale: float, translation_x: float, translation_y: float):
295
+ def _add_transform(self, scale: float, dx: float, dy: float):
254
296
  """Transform the bounding box by updating the transformation attributes.
255
297
 
256
298
  :param scale: scale factor
257
- :param translation_x: x translation
258
- :param translation_y: y translation
259
-
260
- Transformation attributes are _translation_x, _translation_y, and _scale
299
+ :param dx: x translation
300
+ :param dy: y translation
261
301
  """
262
- self._translation_x += translation_x / self._scale
263
- self._translation_y += translation_y / self._scale
264
- self._scale *= scale
302
+ tmat = (scale, 0, 0, scale, dx, dy)
303
+ self._transform = mat_dot(tmat, self._transform)
265
304
 
266
305
  @property
267
306
  def transform_string(self) -> str:
@@ -272,11 +311,7 @@ class BoundingBox(SupportsBounds):
272
311
  Use with
273
312
  ``update_element(elem, transform=bbox.transform_string)``
274
313
  """
275
- scale, trans_x, trans_y = (
276
- format_number(x)
277
- for x in (self._scale, self._translation_x, self._translation_y)
278
- )
279
- return f"scale({scale}) translate({trans_x} {trans_y})"
314
+ return f"matrix({' '.join(map(format_number, self._transform))})"
280
315
 
281
316
  def merge(self, *others: BoundingBox) -> BoundingBox:
282
317
  """Create a bounding box around all other bounding boxes.
@@ -118,7 +118,8 @@ def map_ids_to_bounding_boxes(
118
118
 
119
119
  id2bbox: dict[str, BoundingBox] = {}
120
120
  for id_, *bounds in (x.split(",") for x in bb_strings):
121
- id2bbox[id_] = BoundingBox(*(float(x) for x in bounds))
121
+ x, y, width, height = (float(x) for x in bounds)
122
+ id2bbox[id_] = BoundingBox(x, y, width, height)
122
123
  return id2bbox
123
124
 
124
125
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: svg-ultralight
3
- Version: 0.27.0
3
+ Version: 0.28.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
@@ -1,3 +1,4 @@
1
+ .gitignore
1
2
  .pre-commit-config.yaml
2
3
  README.md
3
4
  pyproject.toml
@@ -7,15 +7,14 @@ A quick test. Won't be able to run it till you change INKSCAPE to the correct pa
7
7
  your system.
8
8
  """
9
9
 
10
- import math
11
10
  from dataclasses import dataclass
12
11
  from pathlib import Path
13
12
 
14
13
  import pytest
15
14
 
16
- from svg_ultralight import new_svg_root
15
+ from svg_ultralight import BoundingBox, new_svg_root
17
16
  from svg_ultralight.constructors import new_sub_element
18
- from svg_ultralight.query import BoundingBox, map_ids_to_bounding_boxes
17
+ from svg_ultralight.query import map_ids_to_bounding_boxes
19
18
 
20
19
  INKSCAPE = Path(r"C:\Program Files\Inkscape\bin\inkscape")
21
20
 
@@ -25,7 +24,6 @@ if not INKSCAPE.with_suffix(".exe").exists():
25
24
 
26
25
 
27
26
  class TestMergeBoundingBoxes:
28
-
29
27
  def test_new_merged_bbox(self):
30
28
  bbox_a = BoundingBox(-2, -4, 10, 20)
31
29
  bbox_b = BoundingBox(0, 0, 10, 10)
@@ -72,45 +70,45 @@ class TestBoundingBox:
72
70
  assert bbox.width == 9000.0
73
71
  assert bbox.height == 12000.0
74
72
 
75
- def test_x(self, bounding_box):
73
+ def test_x(self, bounding_box: BoundingBox):
76
74
  assert bounding_box.x == 0.0
77
75
  bounding_box.x = 50.0
78
76
  assert bounding_box.x == 50.0
79
77
  assert bounding_box.cx == 100.0
80
78
 
81
- def test_x2(self, bounding_box):
79
+ def test_x2(self, bounding_box: BoundingBox):
82
80
  assert bounding_box.x2 == 100.0
83
81
  bounding_box.x2 = 150.0
84
82
  assert bounding_box.x2 == 150.0
85
83
  assert bounding_box.cx == 100.0
86
84
 
87
- def test_y(self, bounding_box):
85
+ def test_y(self, bounding_box: BoundingBox):
88
86
  assert bounding_box.y == 0.0
89
87
  bounding_box.y = 50.0
90
88
  assert bounding_box.y == 50.0
91
89
  assert bounding_box.cy == 150.0
92
90
 
93
- def test_y2(self, bounding_box):
91
+ def test_y2(self, bounding_box: BoundingBox):
94
92
  assert bounding_box.y2 == 200.0
95
93
  bounding_box.y2 = 250.0
96
94
  assert bounding_box.y2 == 250.0
97
95
  assert bounding_box.cy == 150.0
98
96
 
99
- def test_width(self, bounding_box):
97
+ def test_width(self, bounding_box: BoundingBox):
100
98
  assert bounding_box.width == 100.0
101
99
  bounding_box.width = 150.0
102
100
  assert bounding_box.width == 150.0
103
101
  assert bounding_box.x2 == 150.0
104
102
 
105
- def test_height(self, bounding_box):
103
+ def test_height(self, bounding_box: BoundingBox):
106
104
  assert bounding_box.height == 200.0
107
105
  bounding_box.height = 250.0
108
106
  assert bounding_box.height == 250.0
109
107
  assert bounding_box.y2 == 250.0
110
108
 
111
- def test_transform_string(self, bounding_box):
109
+ def test_transform_string(self, bounding_box: BoundingBox):
112
110
  transform_string = bounding_box.transform_string
113
- assert transform_string == "scale(1) translate(0 0)"
111
+ assert transform_string == "matrix(1 0 0 1 0 0)"
114
112
 
115
113
  def test_merge(self):
116
114
  bbox1 = MockSupportsBounds(0, 0, 100, 200)
@@ -182,6 +180,4 @@ class TestAlterBoundingBox:
182
180
  bbox.y = 200
183
181
  bbox.height = 200
184
182
  bbox.height = 40
185
- assert math.isclose(bbox.scale, 1)
186
- assert math.isclose(bbox._translation_x, 90) # type: ignore
187
- assert math.isclose(bbox._translation_y, 180) # type: ignore
183
+ assert bbox.transform == (1, 0, 0, 1, 90, 180)
File without changes