svg-ultralight 0.43.1__py3-none-any.whl → 0.45.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.

Potentially problematic release.


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

@@ -0,0 +1,14 @@
1
+ """Type hints for pass-through arguments to lxml constructors.
2
+
3
+ :author: Shay Hill
4
+ :created: 2025-07-09
5
+ """
6
+
7
+ from collections.abc import Mapping
8
+ from typing import Union
9
+
10
+ # Types svg_ultralight can format to pass through to lxml constructors.
11
+ ElemAttrib = Union[str, float, None]
12
+
13
+ # Type for an optional dictionary of element attributes.
14
+ OptionalElemAttribMapping = Union[Mapping[str, ElemAttrib], None]
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  from typing import TYPE_CHECKING
10
10
 
11
+ from lxml import etree
11
12
  from lxml.etree import _Element as EtreeElement # pyright: ignore[reportPrivateUsage]
12
13
 
13
14
  from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
@@ -19,14 +20,14 @@ from svg_ultralight.constructors import new_element
19
20
  if TYPE_CHECKING:
20
21
  import os
21
22
 
23
+ from svg_ultralight.attrib_hints import ElemAttrib
22
24
  from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
23
- from lxml import etree
24
25
 
25
26
  _Matrix = tuple[float, float, float, float, float, float]
26
27
 
27
28
 
28
29
  def new_element_union(
29
- *elems: EtreeElement | SupportsBounds, **attributes: float | str
30
+ *elems: EtreeElement | SupportsBounds, **attributes: ElemAttrib
30
31
  ) -> EtreeElement:
31
32
  """Get the union of any elements found in the given arguments.
32
33
 
@@ -186,13 +187,13 @@ def _get_view_box(elem: EtreeElement) -> tuple[float, float, float, float]:
186
187
  return x, y, width, height
187
188
 
188
189
 
189
- def parse_bound_element(svg_fil: str | os.PathLike[str]) -> BoundElement:
190
+ def parse_bound_element(svg_file: str | os.PathLike[str]) -> BoundElement:
190
191
  """Import an element as a BoundElement.
191
192
 
192
193
  :param elem: the element to import.
193
194
  :return: a BoundElement instance.
194
195
  """
195
- tree = etree.parse(svg_fil)
196
+ tree = etree.parse(svg_file)
196
197
  root = tree.getroot()
197
198
  elem = new_element("g")
198
199
  elem.extend(list(root))
@@ -27,10 +27,7 @@ from svg_ultralight.font_tools.font_info import (
27
27
  )
28
28
  from svg_ultralight.font_tools.globs import DEFAULT_FONT_SIZE
29
29
  from svg_ultralight.query import get_bounding_boxes
30
- from svg_ultralight.string_conversion import (
31
- format_attr_dict,
32
- format_number,
33
- )
30
+ from svg_ultralight.string_conversion import format_attr_dict, format_number
34
31
 
35
32
  if TYPE_CHECKING:
36
33
  import os
@@ -39,6 +36,8 @@ if TYPE_CHECKING:
39
36
  _Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
40
37
  )
41
38
 
39
+ from svg_ultralight.attrib_hints import ElemAttrib, OptionalElemAttribMapping
40
+
42
41
  DEFAULT_Y_BOUNDS_REFERENCE = "{[|gjpqyf"
43
42
 
44
43
 
@@ -99,7 +98,8 @@ def pad_text_ft(
99
98
  descent: float | None = None,
100
99
  *,
101
100
  y_bounds_reference: str | None = None,
102
- **attributes: str | float,
101
+ attrib: OptionalElemAttribMapping = None,
102
+ **attributes: ElemAttrib,
103
103
  ) -> PaddedText:
104
104
  """Create a new PaddedText instance using fontTools.
105
105
 
@@ -115,11 +115,15 @@ def pad_text_ft(
115
115
  extents of the capline reference. This argument is provided to mimic the
116
116
  behavior of the query module's `pad_text` function. `pad_text` does no
117
117
  inspect font files and relies on Inkscape to measure reference characters.
118
+ :param attrib: optionally pass additional attributes as a mapping instead of as
119
+ anonymous kwargs. This is useful for pleasing the linter when unpacking a
120
+ dictionary into a function call.
118
121
  :param attributes: additional attributes to set on the text element. There is a
119
122
  chance these will cause the font element to exceed the BoundingBox of the
120
123
  PaddedText instance.
121
124
  :return: a PaddedText instance with a line_gap defined.
122
125
  """
126
+ attributes.update(attrib or {})
123
127
  attributes_ = format_attr_dict(**attributes)
124
128
  attributes_.update(get_svg_font_attributes(font))
125
129
 
@@ -145,7 +149,8 @@ def pad_text_mix(
145
149
  descent: float | None = None,
146
150
  *,
147
151
  y_bounds_reference: str | None = None,
148
- **attributes: str | float,
152
+ attrib: OptionalElemAttribMapping = None,
153
+ **attributes: ElemAttrib,
149
154
  ) -> PaddedText:
150
155
  """Use Inkscape text bounds and fill missing with fontTools.
151
156
 
@@ -161,11 +166,15 @@ def pad_text_mix(
161
166
  extents of the capline reference. This argument is provided to mimic the
162
167
  behavior of the query module's `pad_text` function. `pad_text` does no
163
168
  inspect font files and relies on Inkscape to measure reference characters.
169
+ :param attrib: optionally pass additional attributes as a mapping instead of as
170
+ anonymous kwargs. This is useful for pleasing the linter when unpacking a
171
+ dictionary into a function call.
164
172
  :param attributes: additional attributes to set on the text element. There is a
165
173
  chance these will cause the font element to exceed the BoundingBox of the
166
174
  PaddedText instance.
167
175
  :return: a PaddedText instance with a line_gap defined.
168
176
  """
177
+ attributes.update(attrib or {})
169
178
  elem = new_element("text", text=text, **attributes)
170
179
  padded_inkscape = pad_text(inkscape, elem, y_bounds_reference, font=font)
171
180
  padded_fonttools = pad_text_ft(
@@ -175,7 +184,7 @@ def pad_text_mix(
175
184
  ascent,
176
185
  descent,
177
186
  y_bounds_reference=y_bounds_reference,
178
- **attributes,
187
+ attrib=attributes,
179
188
  )
180
189
  bbox = padded_inkscape.unpadded_bbox
181
190
  rpad = padded_inkscape.rpad
@@ -25,8 +25,10 @@ if TYPE_CHECKING:
25
25
  _Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
26
26
  )
27
27
 
28
+ from svg_ultralight.attrib_hints import ElemAttrib
28
29
 
29
- def new_element(tag: str | QName, **attributes: str | float) -> EtreeElement:
30
+
31
+ def new_element(tag: str | QName, **attributes: ElemAttrib) -> EtreeElement:
30
32
  """Create an etree.Element, make every kwarg value a string.
31
33
 
32
34
  :param tag: element tag
@@ -62,7 +64,7 @@ def new_element(tag: str | QName, **attributes: str | float) -> EtreeElement:
62
64
 
63
65
 
64
66
  def new_sub_element(
65
- parent: EtreeElement, tag: str | QName, **attributes: str | float
67
+ parent: EtreeElement, tag: str | QName, **attributes: ElemAttrib
66
68
  ) -> EtreeElement:
67
69
  """Create an etree.SubElement, make every kwarg value a string.
68
70
 
@@ -81,7 +83,7 @@ def new_sub_element(
81
83
  return elem
82
84
 
83
85
 
84
- def update_element(elem: EtreeElement, **attributes: str | float) -> EtreeElement:
86
+ def update_element(elem: EtreeElement, **attributes: ElemAttrib) -> EtreeElement:
85
87
  """Update an existing etree.Element with additional params.
86
88
 
87
89
  :param elem: at etree element
@@ -94,7 +96,7 @@ def update_element(elem: EtreeElement, **attributes: str | float) -> EtreeElemen
94
96
  return elem
95
97
 
96
98
 
97
- def deepcopy_element(elem: EtreeElement, **attributes: str | float) -> EtreeElement:
99
+ def deepcopy_element(elem: EtreeElement, **attributes: ElemAttrib) -> EtreeElement:
98
100
  """Create a deepcopy of an element. Optionally pass additional params.
99
101
 
100
102
  :param elem: at etree element or list of elements
@@ -31,8 +31,8 @@ from svg_ultralight.bounding_boxes.padded_text_initializers import (
31
31
  pad_text_ft,
32
32
  )
33
33
  from svg_ultralight.constructors import new_element
34
- from svg_ultralight.font_tools.font_info import get_svg_font_attributes
35
- from svg_ultralight.main import write_svg
34
+ from svg_ultralight.font_tools.font_info import FTFontInfo, get_svg_font_attributes
35
+ from svg_ultralight.inkscape import write_root
36
36
  from svg_ultralight.root_elements import new_svg_root_around_bounds
37
37
 
38
38
  if TYPE_CHECKING:
@@ -201,7 +201,15 @@ def draw_comparison(
201
201
  text = Path(font).stem
202
202
  font_size = 12
203
203
  font_attributes = get_svg_font_attributes(font)
204
- text_elem = new_element("text", text=text, **font_attributes, font_size=font_size)
204
+ text_elem = new_element(
205
+ "text",
206
+ text=text,
207
+ **font_attributes,
208
+ font_size=font_size,
209
+ fill="none",
210
+ stroke="green",
211
+ stroke_width=0.1,
212
+ )
205
213
  padded_pt = pad_text(inkscape, text_elem)
206
214
  padded_ft = pad_text_ft(
207
215
  font,
@@ -210,10 +218,10 @@ def draw_comparison(
210
218
  y_bounds_reference=DEFAULT_Y_BOUNDS_REFERENCE,
211
219
  fill="none",
212
220
  stroke="orange",
213
- stroke_width=0.05,
221
+ stroke_width=0.1,
214
222
  )
215
223
 
216
- root = new_svg_root_around_bounds(pad_bbox(padded_pt.bbox, 10))
224
+ root = new_svg_root_around_bounds(pad_bbox(padded_pt.bbox, 1))
217
225
  root.append(
218
226
  new_bbox_rect(
219
227
  padded_pt.unpadded_bbox, fill="none", stroke_width=0.07, stroke="red"
@@ -227,7 +235,7 @@ def draw_comparison(
227
235
  root.append(padded_pt.elem)
228
236
  root.append(padded_ft.elem)
229
237
  _ = sys.stdout.write(f"{Path(font).stem} comparison drawn at {output}.\n")
230
- _ = write_svg(Path(output), root)
238
+ _ = write_root(inkscape, Path(output), root)
231
239
 
232
240
 
233
241
  def _iter_fonts(*fonts_dirs: Path) -> Iterator[Path]:
@@ -280,4 +288,6 @@ if __name__ == "__main__":
280
288
  _test_every_font_on_my_system(_INKSCAPE, _FONT_DIRS)
281
289
 
282
290
  font = Path(r"C:\Windows\Fonts\arial.ttf")
283
- draw_comparison(_INKSCAPE, "temp.svg", font)
291
+ font = Path("C:/Windows/Fonts/Aptos-Display-Bold.ttf")
292
+ info = FTFontInfo(font)
293
+ draw_comparison(_INKSCAPE, "temp.svg", font, "AApple")
@@ -105,6 +105,7 @@ from typing import TYPE_CHECKING, Any, cast
105
105
  from fontTools.pens.basePen import BasePen
106
106
  from fontTools.pens.boundsPen import BoundsPen
107
107
  from fontTools.ttLib import TTFont
108
+ from paragraphs import par
108
109
  from svg_path_data import format_svgd_shortest, get_cpts_from_svgd, get_svgd_from_cpts
109
110
 
110
111
  from svg_ultralight.bounding_boxes.type_bounding_box import BoundingBox
@@ -118,12 +119,68 @@ if TYPE_CHECKING:
118
119
 
119
120
  from lxml.etree import _Element as EtreeElement
120
121
 
122
+ from svg_ultralight.attrib_hints import ElemAttrib
123
+
121
124
  logging.getLogger("fontTools").setLevel(logging.ERROR)
122
125
 
123
126
 
124
- def _split_into_quadratic(
125
- *pts: tuple[float, float],
126
- ) -> Iterator[tuple[tuple[float, float], tuple[float, float]]]:
127
+ # extract_gpos_kerning is an unfinished attempt to extract kerning from the GPOS
128
+ # table.
129
+ def get_gpos_kerning(font: TTFont) -> dict[tuple[str, str], int]:
130
+ """Extract kerning pairs from the GPOS table of a font.
131
+
132
+ :param font: A fontTools TTFont object.
133
+ :return: A dictionary mapping glyph pairs to their kerning values.
134
+ :raises ValueError: If the font does not have a GPOS table.
135
+
136
+ This is the more elaborate kerning that is used in OTF fonts and some TTF fonts.
137
+ It has several flavors, I'm only implementing glyph-pair kerning (Format 1),
138
+ because I don't have fonts to test anything else.
139
+ """
140
+ if "GPOS" not in font:
141
+ msg = "Font does not have a GPOS table."
142
+ raise ValueError(msg)
143
+
144
+ gpos = font["GPOS"].table
145
+ kern_table: dict[tuple[str, str], int] = {}
146
+
147
+ type2_lookups = (x for x in gpos.LookupList.Lookup if x.LookupType == 2)
148
+ subtables = list(it.chain(*(x.SubTable for x in type2_lookups)))
149
+ for subtable in (x for x in subtables if x.Format == 1): # glyph-pair kerning
150
+ for pair_set, glyph1 in zip(subtable.PairSet, subtable.Coverage.glyphs):
151
+ for pair_value in pair_set.PairValueRecord:
152
+ glyph2 = pair_value.SecondGlyph
153
+ value1 = pair_value.Value1
154
+ xadv = getattr(value1, "XAdvance", None)
155
+ xpla = getattr(value1, "XPlacement", None)
156
+ value = xadv or xpla or 0
157
+ if value != 0: # only record non-zero kerning values
158
+ kern_table[(glyph1, glyph2)] = value
159
+
160
+ for subtable in (x for x in subtables if x.Format == 2): # class-based kerning
161
+ defs1 = subtable.ClassDef1.classDefs
162
+ defs2 = subtable.ClassDef2.classDefs
163
+ record1 = subtable.Class1Record
164
+ defs1 = {k: v for k, v in defs1.items() if v < len(record1)}
165
+ for (glyph1, class1), (glyph2, class2) in it.product(
166
+ defs1.items(), defs2.items()
167
+ ):
168
+ class1_record = record1[class1]
169
+ if class2 < len(class1_record.Class2Record):
170
+ value1 = class1_record.Class2Record[class2].Value1
171
+ xadv = getattr(value1, "XAdvance", None)
172
+ xpla = getattr(value1, "XPlacement", None)
173
+ value = xadv or xpla or 0
174
+ if value != 0:
175
+ kern_table[(glyph1, glyph2)] = value
176
+
177
+ return kern_table
178
+
179
+
180
+ _XYTuple = tuple[float, float]
181
+
182
+
183
+ def _split_into_quadratic(*pts: _XYTuple) -> Iterator[tuple[_XYTuple, _XYTuple]]:
127
184
  """Connect a series of points with quadratic bezier segments.
128
185
 
129
186
  :param points: a series of at least two (x, y) coordinates.
@@ -183,9 +240,16 @@ class PathPen(BasePen):
183
240
 
184
241
  def curveTo(self, *pts: tuple[float, float]) -> None:
185
242
  """Add a series of cubic bezier segments to the path."""
186
- msg = "Cubic Bezier curves not implemented for getting svg path data."
187
- raise NotImplementedError(msg)
188
- self._cmds.extend(("Q", *map(str, it.chain(*pts))))
243
+ if len(pts) > 3:
244
+ msg = par(
245
+ """I'm uncertain how to decompose these points into cubics (if the
246
+ goal is to match font rendering in Inkscape and elsewhere. There is
247
+ function, decomposeSuperBezierSegment, in fontTools, but I cannot
248
+ find a reference for the algorithm. I'm hoping to run into one in a
249
+ font file so I have a test case."""
250
+ )
251
+ raise NotImplementedError(msg)
252
+ self._cmds.extend(("C", *map(str, it.chain(*pts))))
189
253
 
190
254
  def qCurveTo(self, *pts: tuple[float, float]) -> None:
191
255
  """Add a series of quadratic bezier segments to the path."""
@@ -252,13 +316,18 @@ class FTFontInfo:
252
316
  method would give precedence to the first occurrence. That behavior is copied
253
317
  from examples found online.
254
318
  """
255
- with suppress(KeyError, AttributeError):
319
+ try:
256
320
  kern_tables = cast(
257
321
  "list[dict[tuple[str, str], int]]",
258
322
  [x.kernTable for x in self.font["kern"].kernTables],
259
323
  )
260
- return dict(x for d in reversed(kern_tables) for x in d.items())
261
- return {}
324
+ kern = dict(x for d in reversed(kern_tables) for x in d.items())
325
+ except (KeyError, AttributeError):
326
+ kern = {}
327
+ with suppress(Exception):
328
+ kern.update(get_gpos_kerning(self.font))
329
+
330
+ return kern
262
331
 
263
332
  @ft.cached_property
264
333
  def hhea(self) -> Any:
@@ -453,7 +522,7 @@ class FTTextInfo:
453
522
  """
454
523
  return self.font_size / self.font.units_per_em
455
524
 
456
- def new_element(self, **attributes: str | float) -> EtreeElement:
525
+ def new_element(self, **attributes: ElemAttrib) -> EtreeElement:
457
526
  """Return an svg text element with the appropriate font attributes."""
458
527
  matrix_vals = (self.scale, 0, 0, -self.scale, 0, 0)
459
528
  matrix = f"matrix({' '.join(format_numbers(matrix_vals))})"
svg_ultralight/main.py CHANGED
@@ -36,6 +36,7 @@ if TYPE_CHECKING:
36
36
  _Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
37
37
  )
38
38
 
39
+ from svg_ultralight.attrib_hints import ElemAttrib, OptionalElemAttribMapping
39
40
  from svg_ultralight.layout import PadArg
40
41
 
41
42
 
@@ -68,7 +69,8 @@ def new_svg_root(
68
69
  print_height_: float | str | None = None,
69
70
  dpu_: float = 1,
70
71
  nsmap: dict[str | None, str] | None = None,
71
- **attributes: float | str,
72
+ attrib: OptionalElemAttribMapping | None = None,
73
+ **attributes: ElemAttrib,
72
74
  ) -> EtreeElement:
73
75
  """Create an svg root element from viewBox style parameters.
74
76
 
@@ -87,6 +89,9 @@ def new_svg_root(
87
89
  different from print_width_ and print_height_ in that dpu_ scales the
88
90
  *padded* output.
89
91
  :param nsmap: optionally pass a namespace map of your choosing
92
+ :param attrib: optionally pass additional attributes as a mapping instead of as
93
+ anonymous kwargs. This is useful for pleasing the linter when unpacking a
94
+ dictionary into a function call.
90
95
  :param attributes: element attribute names and values
91
96
  :return: root svg element
92
97
 
@@ -97,10 +102,11 @@ def new_svg_root(
97
102
  will be passed to ``etree.Element`` as element parameters. These will
98
103
  supercede any parameters inferred from the trailing underscore parameters.
99
104
  """
105
+ attributes.update(attrib or {})
100
106
  if nsmap is None:
101
107
  nsmap = NSMAP
102
108
 
103
- inferred_attribs: dict[str, float | str] = {}
109
+ inferred_attribs: dict[str, ElemAttrib] = {}
104
110
  view_box_args = (x_, y_, width_, height_)
105
111
  if _is_four_floats(view_box_args):
106
112
  assert isinstance(x_, (float, int))
@@ -17,6 +17,7 @@ if TYPE_CHECKING:
17
17
  _Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
18
18
  )
19
19
 
20
+ from svg_ultralight.attrib_hints import ElemAttrib, OptionalElemAttribMapping
20
21
  from svg_ultralight.bounding_boxes.supports_bounds import SupportsBounds
21
22
  from svg_ultralight.layout import PadArg
22
23
 
@@ -43,7 +44,8 @@ def new_svg_root_around_bounds(
43
44
  print_height_: float | str | None = None,
44
45
  dpu_: float = 1,
45
46
  nsmap: dict[str | None, str] | None = None,
46
- **attributes: float | str,
47
+ attrib: OptionalElemAttribMapping = None,
48
+ **attributes: ElemAttrib,
47
49
  ) -> EtreeElement:
48
50
  """Create svg root around BoundElements.
49
51
 
@@ -61,10 +63,14 @@ def new_svg_root_around_bounds(
61
63
  different from print_width_ and print_height_ in that dpu_ scales the
62
64
  *padded* output.
63
65
  :param nsmap: optionally pass a namespace map of your choosing
66
+ :param attrib: optionally pass additional attributes as a mapping instead of as
67
+ anonymous kwargs. This is useful for pleasing the linter when unpacking a
68
+ dictionary into a function call.
64
69
  :param attributes: element attribute names and values
65
70
  :return: root svg element
66
71
  :raise ValueError: if no bounding boxes are found in bounded
67
72
  """
73
+ attributes.update(attrib or {})
68
74
  bbox = bound.new_bbox_union(*bounded)
69
75
  viewbox = _viewbox_args_from_bboxes(bbox)
70
76
  return new_svg_root(
@@ -77,5 +83,5 @@ def new_svg_root_around_bounds(
77
83
  print_height_=print_height_,
78
84
  dpu_=dpu_,
79
85
  nsmap=nsmap,
80
- **attributes,
86
+ attrib=attributes,
81
87
  )
@@ -26,6 +26,8 @@ if TYPE_CHECKING:
26
26
  _Element as EtreeElement, # pyright: ignore[reportPrivateUsage]
27
27
  )
28
28
 
29
+ from svg_ultralight.attrib_hints import ElemAttrib
30
+
29
31
 
30
32
  def format_number(num: float | str, resolution: int | None = 6) -> str:
31
33
  """Format a number into an svg-readable float string with resolution = 6.
@@ -50,7 +52,7 @@ def format_numbers(
50
52
  return [format_number(num) for num in nums]
51
53
 
52
54
 
53
- def _fix_key_and_format_val(key: str, val: str | float) -> tuple[str, str]:
55
+ def _fix_key_and_format_val(key: str, val: ElemAttrib) -> tuple[str, str]:
54
56
  """Format one key, value pair for an svg element.
55
57
 
56
58
  :param key: element attribute name
@@ -82,7 +84,9 @@ def _fix_key_and_format_val(key: str, val: str | float) -> tuple[str, str]:
82
84
  else:
83
85
  key_ = key.rstrip("_").replace("_", "-")
84
86
 
85
- if isinstance(val, (int, float)):
87
+ if val is None:
88
+ val_ = "none"
89
+ elif isinstance(val, (int, float)):
86
90
  val_ = format_number(val)
87
91
  else:
88
92
  val_ = val
@@ -90,7 +94,7 @@ def _fix_key_and_format_val(key: str, val: str | float) -> tuple[str, str]:
90
94
  return key_, val_
91
95
 
92
96
 
93
- def format_attr_dict(**attributes: str | float) -> dict[str, str]:
97
+ def format_attr_dict(**attributes: ElemAttrib) -> dict[str, str]:
94
98
  """Use svg_ultralight key / value fixer to create a dict of attributes.
95
99
 
96
100
  :param attributes: element attribute names and values.
@@ -99,7 +103,7 @@ def format_attr_dict(**attributes: str | float) -> dict[str, str]:
99
103
  return dict(_fix_key_and_format_val(key, val) for key, val in attributes.items())
100
104
 
101
105
 
102
- def set_attributes(elem: EtreeElement, **attributes: str | float) -> None:
106
+ def set_attributes(elem: EtreeElement, **attributes: ElemAttrib) -> None:
103
107
  """Set name: value items as element attributes. Make every value a string.
104
108
 
105
109
  :param elem: element to receive element.set(keyword, str(value)) calls
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: svg-ultralight
3
- Version: 0.43.1
3
+ Version: 0.45.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,34 +1,35 @@
1
1
  svg_ultralight/__init__.py,sha256=3YdTZfa0xUDrPCbOVyx39D9ENks-JEtsi9DFZzvaTf0,2755
2
2
  svg_ultralight/animate.py,sha256=SMcQkeWAP9dD08Iyzy9qGG8Qk1p-14WfrB7WSN8Pj_4,1133
3
+ svg_ultralight/attrib_hints.py,sha256=oCF20eG06vg2Tuapk1A14ovN7ZqZoZ43d-um_Zh0BPs,418
3
4
  svg_ultralight/image_ops.py,sha256=6V9YkxnhZCBw_hbVyY0uxDj0pZgiEWBSGRuPuOYKY4w,4694
4
5
  svg_ultralight/inkscape.py,sha256=ySCxWnQwEt1sosa1b4mEkR-ugpfoAeg9gs1OLPS69iI,9597
5
6
  svg_ultralight/layout.py,sha256=7LV2I3u4EhqSc6ASvgwDtTZyV-Y1qt2wtvRtH2uKVAE,12799
6
- svg_ultralight/main.py,sha256=_zY6F8zc1tAGSe8isBOK5ajpDB8d4I7x7zD2fjsP4SE,7475
7
+ svg_ultralight/main.py,sha256=zaqWc5y9tsJoyNhpX2gIHKj1ZGEcfJebxNCcRQyoziU,7857
7
8
  svg_ultralight/metadata.py,sha256=xaIfqhKu52Dl4JOrRlpUsWkkE7Umw8j5Z4waFTli-kI,4234
8
9
  svg_ultralight/nsmap.py,sha256=y63upO78Rr-JJT56RWWZuyrsILh6HPoY4GhbYnK1A0g,1244
9
10
  svg_ultralight/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
11
  svg_ultralight/query.py,sha256=iFFsK78TuUT6h3iv_V8_fwCggIAdYa97K7oOPhhoPuY,9711
11
- svg_ultralight/root_elements.py,sha256=E_H7HXk0M5F3IyFVOxO8PQmhww1-sHTzJhx8hBJPZvg,2911
12
- svg_ultralight/string_conversion.py,sha256=omCmiwyu5AIDHltPiXIEYQG3KiII1twlUJN2oven0mM,8749
12
+ svg_ultralight/root_elements.py,sha256=H_J7GMO8yqoie169uDUB1LxWSpc7BgUH6H657ZPRahg,3292
13
+ svg_ultralight/string_conversion.py,sha256=NWguHAmuKWON7Fh2EkVDfMIwp-jk6k7SW8hAUh72H0o,8850
13
14
  svg_ultralight/transformations.py,sha256=T3vSxcTWOwWnwu3OF610LHMbKScUIVWICUAvru5zLnU,4488
14
15
  svg_ultralight/unit_conversion.py,sha256=g07nhzXdjPvGcJmkhLdFbeDLrSmbI8uFoVgPo7G62Bg,9258
15
16
  svg_ultralight/bounding_boxes/__init__.py,sha256=qUEn3r4s-1QNHaguhWhhaNfdP4tl_B6YEqxtiTFuzhQ,78
16
- svg_ultralight/bounding_boxes/bound_helpers.py,sha256=LFkVsdYFKYCnEL6vLvEa_5cfu8D44ZGYeEEb7_0MnC0,7146
17
- svg_ultralight/bounding_boxes/padded_text_initializers.py,sha256=tPQ5ilZnSm83HH852euoe0V77Nf-78BG7wlE7K8FqnM,7668
17
+ svg_ultralight/bounding_boxes/bound_helpers.py,sha256=iCinp_kFsxi29gJEMnDZpKs13Fj1fj8SWqbbWO94UTI,7203
18
+ svg_ultralight/bounding_boxes/padded_text_initializers.py,sha256=LytVTNwdkJvh914DXIzB4zfrbk563K30ODlk8BmuotA,8328
18
19
  svg_ultralight/bounding_boxes/supports_bounds.py,sha256=T7LGse58fDBgmlzupSC63C1ZMXjFbyzBTsTUaqD_4Sw,4513
19
20
  svg_ultralight/bounding_boxes/type_bound_collection.py,sha256=NAEpqo9H9ewhuLcOmBnMWUE0zQ1t4bvckovgvWy6hIo,2645
20
21
  svg_ultralight/bounding_boxes/type_bound_element.py,sha256=Sc-R0uXkb0aS8-OcyM5pDukKhFUka0G6KCp6LcYDcIU,2204
21
22
  svg_ultralight/bounding_boxes/type_bounding_box.py,sha256=bABFT-BwVdOmRJ-BHOLE_7LZv57MDwqABHvRDcVmvtI,13509
22
23
  svg_ultralight/bounding_boxes/type_padded_text.py,sha256=WNIoqC9pfB8g_00P6RHcBdNE6VniqlLJXw_bh-GNjFA,13098
23
24
  svg_ultralight/constructors/__init__.py,sha256=XLOInLhzMERWNnFAs-itMs-OZrBOpvQthZJ2T5duqBE,327
24
- svg_ultralight/constructors/new_element.py,sha256=hRUW2hR_BTkthEqPClYV7-IeFe9iv2zwb6ehp1k1xDk,3475
25
+ svg_ultralight/constructors/new_element.py,sha256=kGRaVsT1yugADUCKz3NjFW_14Ts1M8UQeLPTarTXtBY,3529
25
26
  svg_ultralight/font_tools/__init__.py,sha256=NX3C0vvoB-G4S-h1f0NLWePjYAMMR37D1cl_G4WBjHc,83
26
- svg_ultralight/font_tools/comp_results.py,sha256=iEDbExO3D7ffo0NZxrqf-WjtzUCJZbdHmqwjjac4laY,10444
27
- svg_ultralight/font_tools/font_info.py,sha256=CDAA5hJOkVS8yyRzAk0uVb0WMTL3fY6Jhxk9I6SEFi8,26375
27
+ svg_ultralight/font_tools/comp_results.py,sha256=gFxdqY1D5z8MGt1UyWOK8O_t50AHgg-B846uWdzoLco,10687
28
+ svg_ultralight/font_tools/font_info.py,sha256=UAOzz3-rsDl8TcbKh7baGcvwvTnpBJYuW71crjHUTE8,29400
28
29
  svg_ultralight/font_tools/globs.py,sha256=JdrrGMqDtD4WcY7YGUWV43DUW63RVev-x9vWqsQUhxU,119
29
30
  svg_ultralight/strings/__init__.py,sha256=BMGhF1pulscIgkiYvZLr6kPRR0L4lW0jUNFxkul4_EM,295
30
31
  svg_ultralight/strings/svg_strings.py,sha256=FQNxNmMkR2M-gCFo_woQKXLgCHi3ncUlRMiaRR_a9nQ,1978
31
- svg_ultralight-0.43.1.dist-info/METADATA,sha256=0lV1rScmuaMl3tHmBxIBwD2I-shfrGe__RAX7Sx2bmc,9052
32
- svg_ultralight-0.43.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
- svg_ultralight-0.43.1.dist-info/top_level.txt,sha256=se-6yqM_0Yg5orJKvKWdjQZ4iR4G_EjhL7oRgju-fdY,15
34
- svg_ultralight-0.43.1.dist-info/RECORD,,
32
+ svg_ultralight-0.45.0.dist-info/METADATA,sha256=ywfQ4Rb8uBQRYmeGa88BZcoifcpwXcLip1pHMsOpXRc,9052
33
+ svg_ultralight-0.45.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ svg_ultralight-0.45.0.dist-info/top_level.txt,sha256=se-6yqM_0Yg5orJKvKWdjQZ4iR4G_EjhL7oRgju-fdY,15
35
+ svg_ultralight-0.45.0.dist-info/RECORD,,