svg-ultralight 0.64.0__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.
Files changed (35) hide show
  1. svg_ultralight/__init__.py +112 -0
  2. svg_ultralight/animate.py +40 -0
  3. svg_ultralight/attrib_hints.py +14 -0
  4. svg_ultralight/bounding_boxes/__init__.py +5 -0
  5. svg_ultralight/bounding_boxes/bound_helpers.py +200 -0
  6. svg_ultralight/bounding_boxes/padded_text_initializers.py +442 -0
  7. svg_ultralight/bounding_boxes/supports_bounds.py +167 -0
  8. svg_ultralight/bounding_boxes/type_bound_collection.py +74 -0
  9. svg_ultralight/bounding_boxes/type_bound_element.py +68 -0
  10. svg_ultralight/bounding_boxes/type_bounding_box.py +432 -0
  11. svg_ultralight/bounding_boxes/type_padded_list.py +208 -0
  12. svg_ultralight/bounding_boxes/type_padded_text.py +502 -0
  13. svg_ultralight/constructors/__init__.py +14 -0
  14. svg_ultralight/constructors/new_element.py +117 -0
  15. svg_ultralight/font_tools/__init__.py +5 -0
  16. svg_ultralight/font_tools/comp_results.py +291 -0
  17. svg_ultralight/font_tools/font_info.py +849 -0
  18. svg_ultralight/image_ops.py +156 -0
  19. svg_ultralight/inkscape.py +261 -0
  20. svg_ultralight/layout.py +291 -0
  21. svg_ultralight/main.py +183 -0
  22. svg_ultralight/metadata.py +122 -0
  23. svg_ultralight/nsmap.py +36 -0
  24. svg_ultralight/py.typed +5 -0
  25. svg_ultralight/query.py +254 -0
  26. svg_ultralight/read_svg.py +58 -0
  27. svg_ultralight/root_elements.py +96 -0
  28. svg_ultralight/string_conversion.py +244 -0
  29. svg_ultralight/strings/__init__.py +21 -0
  30. svg_ultralight/strings/svg_strings.py +106 -0
  31. svg_ultralight/transformations.py +152 -0
  32. svg_ultralight/unit_conversion.py +247 -0
  33. svg_ultralight-0.64.0.dist-info/METADATA +208 -0
  34. svg_ultralight-0.64.0.dist-info/RECORD +35 -0
  35. svg_ultralight-0.64.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,502 @@
1
+ """A padded bounding box around a line of text.
2
+
3
+ A text element (presumably), an svg_ultralight BoundingBox around that element, and
4
+ padding on each side of that box. This is to simplify treating scaling and moving a
5
+ text element as if it were written on a ruled sheet of paper.
6
+
7
+ Padding represents the space between the direction-most point of the text and the
8
+ left margin, right margin, descent, and ascent of the text. Top and bottom padding
9
+ may be less than zero if the constructor used a `y_bounds_reference` argument, as
10
+ descenders and ascenders may extend below and above the bounds of that reference
11
+ character.
12
+
13
+ There is a getter and setter for each of the four padding values. These *do not* move
14
+ the text element. For instance, if you decrease the left padding, the left margin
15
+ will move, *not* the text element.
16
+
17
+ There is a getter and setter for each of x, cx, x2, y, cy, and y2. These *do* move
18
+ the element, but do not scale it. For instance, if you move the left margin (x value)
19
+ to the left, the right margin (and the text element with it) will move to the left.
20
+
21
+ There are getters and setters for width, height, and scale. These scale the text and
22
+ the padding values.
23
+
24
+ `set_width_preserve_sidebearings()`, `set_height_preserve_sidebearings(), and
25
+ `transform_preserve_sidebearings()` methods scale the text and the top and bottom
26
+ padding, but not the left or right padding. These also keep the text element anchored
27
+ on `x` and `y2`. These methods are useful for aligning text of different sizes on,
28
+ for instance, a business card so that Ls or Hs of different sizes line up vertically.
29
+
30
+ Building an honest instance of this class is fairly involved:
31
+
32
+ 1. Create a left-aligned text element.
33
+
34
+ 2. Create a BoundingBox around the left-aligned text element. The difference between
35
+ 0 and that BoundingBox's left edge is the left padding.
36
+
37
+ 3. Create a right-aligned copy of the text element.
38
+
39
+ 4. Create a BoundingBox around the right-aligned text element. The difference between
40
+ the BoundingBox's right edge 0 is the right padding.
41
+
42
+ 5. Use a BoundingBox around a "normal" capital (e.g. "M") to infer the baseline and
43
+ capline and then calculate the top and bottom margins.
44
+
45
+ The padded text initializers in bounding_boxes.padded_text_initializers create
46
+ PaddedText instances with sensible defaults.
47
+
48
+ :author: Shay Hill
49
+ :created: 2021-11-28
50
+ """
51
+
52
+ from __future__ import annotations
53
+
54
+ import math
55
+ from typing import TYPE_CHECKING
56
+
57
+ from paragraphs import par
58
+
59
+ from svg_ultralight.bounding_boxes.type_bound_element import BoundElement
60
+ from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
61
+ from svg_ultralight.transformations import new_transformation_matrix, transform_element
62
+
63
+ if TYPE_CHECKING:
64
+ from lxml.etree import (
65
+ _Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
66
+ )
67
+
68
+ _Matrix = tuple[float, float, float, float, float, float]
69
+
70
+ _no_line_gap_msg = par(
71
+ """No line_gap defined. Line gap is an inherent font attribute defined within a
72
+ font file. If this PaddedText instance was created with `pad_text` from reference
73
+ elements, a line_gap was not defined. Reading line_gap from the font file
74
+ requires creating a PaddedText instance with `pad_text_ft` or `pad_text_mixed`.
75
+ You can set an arbitrary line_gap after init with `instance.line_gap = value`."""
76
+ )
77
+
78
+ _no_font_size_msg = par(
79
+ """No font_size defined. Font size is an inherent font attribute defined within a
80
+ font file or an argument passed to `pad_text`. Any instance created with a padded
81
+ text initializer should have this property."""
82
+ )
83
+
84
+
85
+ class PaddedText(BoundElement):
86
+ """A line of text with a bounding box and padding."""
87
+
88
+ def __init__(
89
+ self,
90
+ elem: EtreeElement,
91
+ bbox: BoundingBox,
92
+ tpad: float,
93
+ rpad: float,
94
+ bpad: float,
95
+ lpad: float,
96
+ line_gap: float | None = None,
97
+ font_size: float | None = None,
98
+ ) -> None:
99
+ """Initialize a PaddedText instance.
100
+
101
+ :param elem: The text element.
102
+ :param bbox: The bounding box around text element.
103
+ :param tpad: Top padding.
104
+ :param rpad: Right padding.
105
+ :param bpad: Bottom padding.
106
+ :param lpad: Left padding.
107
+ :param line_gap: The line gap between this line of text and the next. This is
108
+ an inherent font attribute sometimes defined within a font file.
109
+ """
110
+ self.elem = elem
111
+ self.unpadded_bbox = bbox
112
+ self.base_tpad = tpad
113
+ self.rpad = rpad
114
+ self.base_bpad = bpad
115
+ self.lpad = lpad
116
+ self._line_gap = line_gap
117
+ self._font_size = font_size
118
+
119
+ @property
120
+ def tbox(self) -> BoundingBox:
121
+ """Return the unpadded BoundingBox around the text element.
122
+
123
+ Tight bbox or True bbox. An alias for unpadded_bbox.
124
+
125
+ :return: The unpadded BoundingBox around the text element.
126
+ """
127
+ return self.unpadded_bbox
128
+
129
+ @tbox.setter
130
+ def tbox(self, value: BoundingBox) -> None:
131
+ """Set the unpadded BoundingBox around the text element.
132
+
133
+ :param value: The new unpadded BoundingBox.
134
+ """
135
+ self.unpadded_bbox = value
136
+
137
+ @property
138
+ def bbox(self) -> BoundingBox:
139
+ """Return a BoundingBox around the margins and cap/baseline.
140
+
141
+ :return: A BoundingBox around the margins and cap/baseline.
142
+
143
+ This is useful for creating a merged bounding box with
144
+ `svg_ultralight.BoundingBox.merged`. The merged bbox and merged_bbox
145
+ attributes of multiple bounding boxes can be used to create a PaddedText
146
+ instance around multiple text elements (a <g> elem).
147
+ """
148
+ return BoundingBox(
149
+ self.x,
150
+ self.y,
151
+ self.width,
152
+ self.height,
153
+ )
154
+
155
+ @bbox.setter
156
+ def bbox(self, value: BoundingBox) -> None:
157
+ """Set the bounding box of this PaddedText.
158
+
159
+ :param value: The new bounding box.
160
+ :effects: The text element is transformed to fit the new bounding box.
161
+ """
162
+ msg = "Cannot set bbox of PaddedText, use transform() instead."
163
+ raise NotImplementedError(msg)
164
+
165
+ def transform(
166
+ self,
167
+ transformation: _Matrix | None = None,
168
+ *,
169
+ scale: tuple[float, float] | float | None = None,
170
+ dx: float | None = None,
171
+ dy: float | None = None,
172
+ reverse: bool = False,
173
+ ) -> None:
174
+ """Transform the element and bounding box.
175
+
176
+ :param transformation: a 6-tuple transformation matrix
177
+ :param scale: a scaling factor
178
+ :param dx: the x translation
179
+ :param dy: the y translation
180
+ :param reverse: Transform the element as if it were in a <g> element
181
+ transformed by tmat.
182
+ """
183
+ tmat = new_transformation_matrix(transformation, scale=scale, dx=dx, dy=dy)
184
+ self.unpadded_bbox.transform(tmat, reverse=reverse)
185
+ _ = transform_element(self.elem, tmat, reverse=reverse)
186
+ x_norm = pow(tmat[0] ** 2 + tmat[1] ** 2, 1 / 2)
187
+ self.lpad *= x_norm
188
+ self.rpad *= x_norm
189
+ if self._line_gap or self._font_size:
190
+ y_norm = pow(tmat[2] ** 2 + tmat[3] ** 2, 1 / 2)
191
+ if self._line_gap:
192
+ self._line_gap *= y_norm
193
+ if self._font_size:
194
+ self._font_size *= y_norm
195
+
196
+ def transform_preserve_sidebearings(
197
+ self,
198
+ transformation: _Matrix | None = None,
199
+ *,
200
+ scale: tuple[float, float] | float | None = None,
201
+ dx: float | None = None,
202
+ dy: float | None = None,
203
+ reverse: bool = False,
204
+ ) -> None:
205
+ """Transform the element and bounding box preserving sidebearings.
206
+
207
+ :param transformation: a 6-tuple transformation matrix
208
+ :param scale: a scaling factor
209
+ :param dx: the x translation
210
+ :param dy: the y translation
211
+ :param reverse: Transform the element as if it were in a <g> element
212
+ transformed by tmat.
213
+ """
214
+ lpad = self.lpad
215
+ rpad = self.rpad
216
+ x = self.x
217
+ y2 = self.y2
218
+ self.transform(transformation, scale=scale, dx=dx, dy=dy, reverse=reverse)
219
+ self.lpad = lpad
220
+ self.rpad = rpad
221
+ self.x = x
222
+ self.y2 = y2
223
+
224
+ @property
225
+ def line_gap(self) -> float:
226
+ """The line gap between this line of text and the next.
227
+
228
+ :return: The line gap between this line of text and the next.
229
+ """
230
+ if self._line_gap is None:
231
+ raise AttributeError(_no_line_gap_msg)
232
+ return self._line_gap
233
+
234
+ @line_gap.setter
235
+ def line_gap(self, value: float) -> None:
236
+ """Set the line gap between this line of text and the next.
237
+
238
+ :param value: The new line gap.
239
+ """
240
+ self._line_gap = value
241
+
242
+ @property
243
+ def font_size(self) -> float:
244
+ """The font size of this line of text.
245
+
246
+ :return: The font size of this line of text.
247
+ """
248
+ if self._font_size is None:
249
+ raise AttributeError(_no_font_size_msg)
250
+ return self._font_size
251
+
252
+ @font_size.setter
253
+ def font_size(self, value: float) -> None:
254
+ """Set the font size of this line of text.
255
+
256
+ :param value: The new font size.
257
+ """
258
+ self.transform(scale=value / self.font_size)
259
+
260
+ @property
261
+ def leading(self) -> float:
262
+ """The leading of this line of text.
263
+
264
+ :return: The line gap plus the height of this line of text.
265
+ """
266
+ return self.height + self.line_gap
267
+
268
+ @property
269
+ def tpad(self) -> float:
270
+ """The top padding of this line of text.
271
+
272
+ :return: The scaled top padding of this line of text.
273
+ """
274
+ return self.base_tpad * self.tbox.scale[1]
275
+
276
+ @tpad.setter
277
+ def tpad(self, value: float) -> None:
278
+ """Set the top padding of this line of text.
279
+
280
+ :param value: The new top padding.
281
+ """
282
+ self.base_tpad = value / self.tbox.scale[1]
283
+
284
+ @property
285
+ def bpad(self) -> float:
286
+ """The bottom padding of this line of text.
287
+
288
+ :return: The scaled bottom padding of this line of text.
289
+ """
290
+ return self.base_bpad * self.tbox.scale[1]
291
+
292
+ @bpad.setter
293
+ def bpad(self, value: float) -> None:
294
+ """Set the bottom padding of this line of text.
295
+
296
+ :param value: The new bottom padding.
297
+ """
298
+ self.base_bpad = value / self.tbox.scale[1]
299
+
300
+ @property
301
+ def scale(self) -> tuple[float, float]:
302
+ """Get scale of the bounding box.
303
+
304
+ :return: uniform scale of the bounding box
305
+
306
+ Use caution, the scale attribute can cause errors in intuition. Changing
307
+ width or height will change the scale attribute, but not the x or y values.
308
+ The scale setter, on the other hand, will work in the tradational manner.
309
+ I.e., x => x*scale, y => y*scale, x2 => x*scale, y2 => y*scale, width =>
310
+ width*scale, height => height*scale, scale => scale*scale. This matches how
311
+ scale works in almost every other context.
312
+ """
313
+ xx, xy, yx, yy, *_ = self.tbox.transformation
314
+ return math.sqrt(xx * xx + xy * xy), math.sqrt(yx * yx + yy * yy)
315
+
316
+ @scale.setter
317
+ def scale(self, value: tuple[float, float]) -> None:
318
+ """Scale the bounding box by a uniform factor.
319
+
320
+ :param value: new scale value
321
+
322
+ Don't miss this! You are setting the scale, not scaling the scale! If you
323
+ have a previously defined scale other than 1, this is probably not what you
324
+ want. Most of the time, you will want to use the *= operator.
325
+
326
+ `scale = 2` -> ignore whatever scale was previously defined and set scale to 2
327
+ `scale *= 2` -> make it twice as big as it was.
328
+ """
329
+ new_scale = (
330
+ value[0] / self.tbox.scale[0],
331
+ value[1] / self.tbox.scale[1],
332
+ )
333
+ self.transform(scale=new_scale)
334
+
335
+ @property
336
+ def uniform_scale(self) -> float:
337
+ """Get uniform scale of the bounding box.
338
+
339
+ :return: uniform scale of the bounding box
340
+ :raises ValueError: if the scale is non-uniform.
341
+ """
342
+ scale = self.scale
343
+ if math.isclose(scale[0], scale[1]):
344
+ return scale[0]
345
+ msg = f"Non-uniform scale detected: sx={scale[0]}, sy={scale[1]}"
346
+ raise ValueError(msg)
347
+
348
+ @property
349
+ def width(self) -> float:
350
+ """The width of this line of text with padding.
351
+
352
+ :return: The scaled width of this line of text with padding.
353
+ """
354
+ return self.tbox.width + self.lpad + self.rpad
355
+
356
+ @width.setter
357
+ def width(self, value: float) -> None:
358
+ """Scale to padded_width = width without scaling padding.
359
+
360
+ :param width: The new width of this line of text.
361
+ :effects: the text_element bounding box is scaled to width - lpad - rpad.
362
+
363
+ Svg_Ultralight BoundingBoxes preserve x and y when scaling. This is
364
+ consistent with how rectangles, viewboxes, and anything else defined by x, y,
365
+ width, height behaves in SVG. This is unintuitive for text, because the
366
+ baseline is near y2 (y + height) not y. So, we preserve baseline (alter y
367
+ *and* y2) when scaling.
368
+ """
369
+ self.transform(scale=value / self.width)
370
+
371
+ def set_width_preserve_sidebearings(self, value: float) -> None:
372
+ """Set the width of this line of text without scaling sidebearings.
373
+
374
+ :param value: The new width of this line of text.
375
+ :effects: the text_element bounding box is scaled to width - lpad - rpad.
376
+ """
377
+ no_margins_old = self.tbox.width
378
+ no_margins_new = value - self.lpad - self.rpad
379
+ scale = no_margins_new / no_margins_old
380
+ self.transform_preserve_sidebearings(scale=scale)
381
+
382
+ @property
383
+ def height(self) -> float:
384
+ """The height of this line of text with padding.
385
+
386
+ :return: The scaled height of this line of text with padding.
387
+ """
388
+ return self.tbox.height + self.tpad + self.bpad
389
+
390
+ @height.setter
391
+ def height(self, value: float) -> None:
392
+ """Scale to height without scaling padding.
393
+
394
+ :param height: The new height of this line of text.
395
+ :effects: the text_element bounding box is scaled to height - tpad - bpad.
396
+ """
397
+ scale = value / self.height
398
+ self.transform(scale=scale)
399
+
400
+ def set_height_preserve_sidebearings(self, value: float) -> None:
401
+ """Set the height of this line of text without scaling sidebearings.
402
+
403
+ :param value: The new height of this line of text.
404
+ :effects: the text_element bounding box is scaled to height - tpad - bpad.
405
+ """
406
+ self.transform_preserve_sidebearings(scale=value / self.height)
407
+
408
+ @property
409
+ def x(self) -> float:
410
+ """The left margin of this line of text.
411
+
412
+ :return: The left margin of this line of text.
413
+ """
414
+ return self.tbox.x - self.lpad
415
+
416
+ @x.setter
417
+ def x(self, value: float) -> None:
418
+ """Set the left margin of this line of text.
419
+
420
+ :param value: The left margin of this line of text.
421
+ """
422
+ self.transform(dx=value + self.lpad - self.tbox.x)
423
+
424
+ @property
425
+ def cx(self) -> float:
426
+ """The horizontal center of this line of text.
427
+
428
+ :return: The horizontal center of this line of text.
429
+ """
430
+ return self.x + self.width / 2
431
+
432
+ @cx.setter
433
+ def cx(self, value: float) -> None:
434
+ """Set the horizontal center of this line of text.
435
+
436
+ :param value: The horizontal center of this line of text.
437
+ """
438
+ self.x += value - self.cx
439
+
440
+ @property
441
+ def x2(self) -> float:
442
+ """The right margin of this line of text.
443
+
444
+ :return: The right margin of this line of text.
445
+ """
446
+ return self.tbox.x2 + self.rpad
447
+
448
+ @x2.setter
449
+ def x2(self, value: float) -> None:
450
+ """Set the right margin of this line of text.
451
+
452
+ :param value: The right margin of this line of text.
453
+ """
454
+ self.transform(dx=value - self.rpad - self.tbox.x2)
455
+
456
+ @property
457
+ def y(self) -> float:
458
+ """The top of this line of text.
459
+
460
+ :return: The top of this line of text.
461
+ """
462
+ return self.tbox.y - self.tpad
463
+
464
+ @y.setter
465
+ def y(self, value: float) -> None:
466
+ """Set the top of this line of text.
467
+
468
+ :param value: The top of this line of text.
469
+ """
470
+ self.transform(dy=value + self.tpad - self.tbox.y)
471
+
472
+ @property
473
+ def cy(self) -> float:
474
+ """The horizontal center of this line of text.
475
+
476
+ :return: The horizontal center of this line of text.
477
+ """
478
+ return self.y + self.height / 2
479
+
480
+ @cy.setter
481
+ def cy(self, value: float) -> None:
482
+ """Set the horizontal center of this line of text.
483
+
484
+ :param value: The horizontal center of this line of text.
485
+ """
486
+ self.y += value - self.cy
487
+
488
+ @property
489
+ def y2(self) -> float:
490
+ """The bottom of this line of text.
491
+
492
+ :return: The bottom of this line of text.
493
+ """
494
+ return self.tbox.y2 + self.bpad
495
+
496
+ @y2.setter
497
+ def y2(self, value: float) -> None:
498
+ """Set the bottom of this line of text.
499
+
500
+ :param value: The bottom of this line of text.
501
+ """
502
+ self.transform(dy=value - self.bpad - self.tbox.y2)
@@ -0,0 +1,14 @@
1
+ """Raise the level of the constructors module.
2
+
3
+ :author: Shay Hill
4
+ created: 12/22/2019.
5
+ """
6
+
7
+ from svg_ultralight.constructors.new_element import (
8
+ deepcopy_element,
9
+ new_element,
10
+ new_sub_element,
11
+ update_element,
12
+ )
13
+
14
+ __all__ = ["deepcopy_element", "new_element", "new_sub_element", "update_element"]
@@ -0,0 +1,117 @@
1
+ """SVG Element constructors. Create an svg element from a dictionary.
2
+
3
+ :author: Shay Hill
4
+ :created: 1/31/2020
5
+
6
+ This is principally to allow passing values, rather than strings, as svg element
7
+ parameters.
8
+
9
+ Will translate ``stroke_width=10`` to ``stroke-width="10"``
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import copy
15
+ import warnings
16
+ from typing import TYPE_CHECKING
17
+
18
+ from lxml import etree
19
+
20
+ from svg_ultralight.string_conversion import set_attributes
21
+
22
+ if TYPE_CHECKING:
23
+ from lxml.etree import (
24
+ QName,
25
+ )
26
+ from lxml.etree import (
27
+ _Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
28
+ )
29
+
30
+ from svg_ultralight.attrib_hints import ElemAttrib
31
+
32
+
33
+ def new_element(tag: str | QName, **attributes: ElemAttrib) -> EtreeElement:
34
+ """Create an etree.Element, make every kwarg value a string.
35
+
36
+ :param tag: element tag
37
+ :param attributes: element attribute names and values
38
+ :returns: new ``tag`` element
39
+
40
+ >>> elem = new_element('line', x1=0, y1=0, x2=5, y2=5)
41
+ >>> etree.tostring(elem)
42
+ b'<line x1="0" y1="0" x2="5" y2="5"/>'
43
+
44
+ Strips trailing underscores
45
+
46
+ >>> elem = new_element('line', in_="SourceAlpha")
47
+ >>> etree.tostring(elem)
48
+ b'<line in="SourceAlpha"/>'
49
+
50
+ Translates other underscores to hyphens
51
+
52
+ >>> elem = new_element('line', stroke_width=1)
53
+ >>> etree.tostring(elem)
54
+ b'<line stroke-width="1"/>'
55
+
56
+ Special handling for a 'text' argument. Places value between element tags.
57
+
58
+ >>> elem = new_element('text', text='please star my project')
59
+ >>> etree.tostring(elem)
60
+ b'<text>please star my project</text>'
61
+
62
+ """
63
+ elem = etree.Element(tag)
64
+ set_attributes(elem, **attributes)
65
+ return elem
66
+
67
+
68
+ def new_sub_element(
69
+ parent: EtreeElement, tag: str | QName, **attributes: ElemAttrib
70
+ ) -> EtreeElement:
71
+ """Create an etree.SubElement, make every kwarg value a string.
72
+
73
+ :param parent: parent element
74
+ :param tag: element tag
75
+ :param attributes: element attribute names and values
76
+ :returns: new ``tag`` element
77
+
78
+ >>> parent = etree.Element('g')
79
+ >>> _ = new_sub_element(parent, 'rect')
80
+ >>> etree.tostring(parent)
81
+ b'<g><rect/></g>'
82
+ """
83
+ elem = etree.SubElement(parent, tag)
84
+ set_attributes(elem, **attributes)
85
+ return elem
86
+
87
+
88
+ def update_element(elem: EtreeElement, **attributes: ElemAttrib) -> EtreeElement:
89
+ """Update an existing etree.Element with additional params.
90
+
91
+ :param elem: at etree element
92
+ :param attributes: element attribute names and values
93
+ :returns: the element with updated attributes
94
+
95
+ This is to take advantage of the argument conversion in ``new_element``.
96
+ """
97
+ set_attributes(elem, **attributes)
98
+ return elem
99
+
100
+
101
+ def deepcopy_element(elem: EtreeElement, **attributes: ElemAttrib) -> EtreeElement:
102
+ """Create a deepcopy of an element. Optionally pass additional params.
103
+
104
+ :param elem: at etree element or list of elements
105
+ :param attributes: element attribute names and values
106
+ :returns: a deepcopy of the element with updated attributes
107
+ :raises DeprecationWarning:
108
+ """
109
+ warnings.warn(
110
+ "deepcopy_element is deprecated. "
111
+ + "Use copy.deepcopy from the standard library instead.",
112
+ category=DeprecationWarning,
113
+ stacklevel=1,
114
+ )
115
+ elem = copy.deepcopy(elem)
116
+ _ = update_element(elem, **attributes)
117
+ return elem
@@ -0,0 +1,5 @@
1
+ """Mark font_tools as a package.
2
+
3
+ :author: Shay Hill
4
+ :created: 2025-06-04
5
+ """