svg-ultralight 0.3.1__tar.gz → 0.54.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 (51) hide show
  1. svg_ultralight-0.54.0/PKG-INFO +208 -0
  2. {svg_ultralight-0.3.1 → svg_ultralight-0.54.0}/README.md +190 -148
  3. svg_ultralight-0.54.0/pyproject.toml +75 -0
  4. svg_ultralight-0.54.0/src/svg_ultralight/__init__.py +108 -0
  5. svg_ultralight-0.54.0/src/svg_ultralight/animate.py +40 -0
  6. svg_ultralight-0.54.0/src/svg_ultralight/attrib_hints.py +14 -0
  7. svg_ultralight-0.54.0/src/svg_ultralight/bounding_boxes/__init__.py +5 -0
  8. svg_ultralight-0.54.0/src/svg_ultralight/bounding_boxes/bound_helpers.py +189 -0
  9. svg_ultralight-0.54.0/src/svg_ultralight/bounding_boxes/padded_text_initializers.py +343 -0
  10. svg_ultralight-0.54.0/src/svg_ultralight/bounding_boxes/supports_bounds.py +167 -0
  11. svg_ultralight-0.54.0/src/svg_ultralight/bounding_boxes/type_bound_collection.py +74 -0
  12. svg_ultralight-0.54.0/src/svg_ultralight/bounding_boxes/type_bound_element.py +68 -0
  13. svg_ultralight-0.54.0/src/svg_ultralight/bounding_boxes/type_bounding_box.py +402 -0
  14. svg_ultralight-0.54.0/src/svg_ultralight/bounding_boxes/type_padded_text.py +424 -0
  15. svg_ultralight-0.54.0/src/svg_ultralight/constructors/__init__.py +14 -0
  16. svg_ultralight-0.54.0/src/svg_ultralight/constructors/new_element.py +120 -0
  17. svg_ultralight-0.54.0/src/svg_ultralight/font_tools/__init__.py +5 -0
  18. svg_ultralight-0.54.0/src/svg_ultralight/font_tools/comp_results.py +295 -0
  19. svg_ultralight-0.54.0/src/svg_ultralight/font_tools/font_info.py +820 -0
  20. svg_ultralight-0.54.0/src/svg_ultralight/image_ops.py +156 -0
  21. svg_ultralight-0.54.0/src/svg_ultralight/inkscape.py +261 -0
  22. svg_ultralight-0.54.0/src/svg_ultralight/layout.py +291 -0
  23. svg_ultralight-0.54.0/src/svg_ultralight/main.py +183 -0
  24. svg_ultralight-0.54.0/src/svg_ultralight/metadata.py +122 -0
  25. svg_ultralight-0.54.0/src/svg_ultralight/nsmap.py +36 -0
  26. svg_ultralight-0.54.0/src/svg_ultralight/py.typed +5 -0
  27. svg_ultralight-0.54.0/src/svg_ultralight/query.py +254 -0
  28. svg_ultralight-0.54.0/src/svg_ultralight/read_svg.py +58 -0
  29. svg_ultralight-0.54.0/src/svg_ultralight/root_elements.py +87 -0
  30. svg_ultralight-0.54.0/src/svg_ultralight/string_conversion.py +244 -0
  31. svg_ultralight-0.54.0/src/svg_ultralight/strings/__init__.py +21 -0
  32. svg_ultralight-0.54.0/src/svg_ultralight/strings/svg_strings.py +106 -0
  33. svg_ultralight-0.54.0/src/svg_ultralight/transformations.py +152 -0
  34. svg_ultralight-0.54.0/src/svg_ultralight/unit_conversion.py +247 -0
  35. svg_ultralight-0.3.1/LICENSE.txt +0 -22
  36. svg_ultralight-0.3.1/MANIFEST.in +0 -1
  37. svg_ultralight-0.3.1/PKG-INFO +0 -163
  38. svg_ultralight-0.3.1/constructors/__init__.py +0 -9
  39. svg_ultralight-0.3.1/constructors/new_element.py +0 -95
  40. svg_ultralight-0.3.1/setup.cfg +0 -4
  41. svg_ultralight-0.3.1/setup.py +0 -25
  42. svg_ultralight-0.3.1/svg_ultralight/__init__.py +0 -8
  43. svg_ultralight-0.3.1/svg_ultralight/py.typed +0 -0
  44. svg_ultralight-0.3.1/svg_ultralight/svg_ultralight.py +0 -192
  45. svg_ultralight-0.3.1/svg_ultralight.egg-info/PKG-INFO +0 -163
  46. svg_ultralight-0.3.1/svg_ultralight.egg-info/SOURCES.txt +0 -16
  47. svg_ultralight-0.3.1/svg_ultralight.egg-info/dependency_links.txt +0 -1
  48. svg_ultralight-0.3.1/svg_ultralight.egg-info/requires.txt +0 -1
  49. svg_ultralight-0.3.1/svg_ultralight.egg-info/top_level.txt +0 -2
  50. svg_ultralight-0.3.1/test/test_new_element.py +0 -39
  51. svg_ultralight-0.3.1/test/test_svg_ultralight.py +0 -144
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.4
2
+ Name: svg-ultralight
3
+ Version: 0.54.0
4
+ Summary: a sensible way to create svg files with Python
5
+ Author: Shay Hill
6
+ Author-email: Shay Hill <shay_public@hotmail.com>
7
+ License-Expression: MIT
8
+ Requires-Dist: cssutils>=2.11.1
9
+ Requires-Dist: fonttools>=4.60.1
10
+ Requires-Dist: lxml>=6.0.2
11
+ Requires-Dist: paragraphs>=1.0.1
12
+ Requires-Dist: pillow>=11.3.0
13
+ Requires-Dist: svg-path-data>=0.5.3
14
+ Requires-Dist: types-lxml>=2025.8.25
15
+ Requires-Dist: typing-extensions>=4.15.0
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+
19
+ # svg_ultralight
20
+
21
+ The most straightforward way to create SVG files with Python.
22
+
23
+ ## Four principal functions:
24
+
25
+ from svg_ultralight import new_svg_root, write_svg, write_png_from_svg, write_png
26
+
27
+ ## One convenience:
28
+
29
+ from svg_ultralight import NSMAP
30
+
31
+ ### new_svg_root
32
+ x_: Optional[float],
33
+ y_: Optional[float],
34
+ width_: Optional[float],
35
+ height_: Optional[float],
36
+ pad_: float = 0
37
+ dpu_: float = 1
38
+ nsmap: Optional[Dict[str, str]] = None (svg_ultralight.NSMAP if None)
39
+ **attributes: Union[float, str],
40
+ -> etree.Element
41
+
42
+ Create an svg root element from viewBox style arguments and provide the necessary svg-specific attributes and namespaces. This is your window onto the scene.
43
+
44
+ Three ways to call:
45
+
46
+ 1. The trailing-underscore arguments are the same you'd use to create a `rect` element (plus `pad_` and `dpu_`). `new_svg_root` will infer `viewBox`, `width`, and `height` svg attributes from these values.
47
+ 2. Use the svg attributes you already know: `viewBox`, `width`, `height`, etc. These will be written to the xml file.
48
+ 3. Of course, you can combine 1. and 2. if you know what you're doing.
49
+
50
+ See `namespaces` below.
51
+
52
+ * `x_`: x value in upper-left corner
53
+ * `y_`: y value in upper-left corner
54
+ * `width_`: width of viewBox
55
+ * `height_`: height of viewBox
56
+ * `pad_`: the one small convenience I've provided. Optionally increase viewBox by `pad` in all directions.
57
+ * `dpu_`: pixels per viewBox unit for output png images.
58
+ * `nsmap`: namespaces. (defaults to svg_ultralight.NSMAP). Available as an argument should you wish to add additional namespaces. To do this, add items to NSMAP then call with `nsmap=NSMAP`.
59
+ * `**attributes`: the trailing-underscore arguments are an *optional* shortcut for creating a scene. The entire svg interface is available to you through kwargs. See `A few helpers` below for details on attribute-name translation between Python and xml (the short version: `this_name` becomes `this-name` and `this_` becomes `this`)
60
+
61
+ ### namespaces (svg_ultralight.NSMAP)
62
+
63
+ `new_svg_root` will create a root with several available namespaces.
64
+
65
+ * `"dc": "http://purl.org/dc/elements/1.1/"`
66
+ * `"cc": "http://creativecommons.org/ns#"`
67
+ * `"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"`
68
+ * `"svg": "http://www.w3.org/2000/svg"`
69
+ * `"xlink": "http://www.w3.org/1999/xlink"`
70
+ * `"sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"`
71
+ * `"inkscape": "http://www.inkscape.org/namespaces/inkscape"`
72
+
73
+ I have made these available to you as `svg_ultralight.NSMAP`
74
+
75
+ ### write_svg
76
+ svg: str,
77
+ xml: etree.Element,
78
+ stylesheet: Optional[str] = None,
79
+ do_link_css: bool = False,
80
+ **tostring_kwargs,
81
+ -> str:
82
+
83
+ Write an xml element as an svg file. This will link or inline your css code and insert the necessary declaration, doctype, and processing instructions.
84
+
85
+ * `svg`: path to output file (include extension .svg)
86
+ * `param xml`: root node of your svg geometry (created by `new_svg_root`)
87
+ * `stylesheet`: optional path to a css stylesheet
88
+ * `do_link_css`: link to stylesheet, else (default) write contents of stylesheet into svg (ignored if `stylesheet` is None). If you have a stylesheet somewhere, the default action is to dump the entire contents into your svg file. Linking to the stylesheet is more elegant, but inlining *always* works.
89
+ * `**tostring_kwargs`: optional kwarg arguments for `lxml.etree.tostring`. Passing `xml_declaration=True` by itself will create an xml declaration with encoding set to UTF-8 and an svg DOCTYPE. These defaults can be overridden with keyword arguments `encoding` and `doctype`. If you don't know what this is, you can probably get away without it.
90
+ * `returns`: for convenience, returns svg filename (`svg`)
91
+ * `effects`: creates svg file at `svg`
92
+
93
+ ### write_png_from_svg
94
+
95
+ inkscape_exe: str,
96
+ svg: str
97
+ png: Optional[str]
98
+ -> str
99
+
100
+ Convert an svg file to a png. Python does not have a library for this. That has an upside, as any library would be one more set of svg implementation idiosyncrasies we'd have to deal with. Inkscape will convert the file. This function provides the necessary command-line arguments.
101
+
102
+ * `inkscape_exe`: path to inkscape.exe
103
+ * `svg`: path to svg file
104
+ * `png`: optional path to png output (if not given, png name will be inferred from `svg`: `'name.svg'` becomes `'name.png'`)
105
+ * `return`: png filename
106
+ * `effects`: creates png file at `png` (or infers png path and filename from `svg`)
107
+
108
+ ### write_png
109
+
110
+ inkscape_exe: str,
111
+ png: str,
112
+ xml: etree.Element,
113
+ stylesheet: Optional[str] = None
114
+ -> str
115
+
116
+ Create a png without writing an initial svg to your filesystem. This is not faster (it may be slightly slower), but it may be important when writing many images (animation frames) to your filesystem.
117
+
118
+ * `inkscape_exe`: path to inkscape.exe
119
+ * `png`: path to output file (include extension .png)
120
+ * `param xml`: root node of your svg geometry (created by `new_svg_root`)
121
+ * `stylesheet`: optional path to a css stylesheet
122
+ * `returns`: for convenience, returns png filename (`png`)
123
+ * `effects`: creates png file at `png`
124
+
125
+ ## A few helpers:
126
+
127
+ from svg_ultralight.constructors import new_element, new_sub_element
128
+
129
+ I do want to keep this ultralight and avoid creating some pseudo scripting language between Python and lxml, but here are two very simple, very optional functions to save your having to `str()` every argument to `etree.Element`.
130
+
131
+ ### constructors.new_element
132
+
133
+ tag: str
134
+ **params: Union[str, float]
135
+ -> etree.Element
136
+
137
+ Python allows underscores in variable names; xml uses dashes.
138
+
139
+ Python understands numbers; xml wants strings.
140
+
141
+ This is a convenience function to swap `"_"` for `"-"` and `10.2` for `"10.2"` before creating an xml element.
142
+
143
+ Translates numbers to strings
144
+
145
+ >>> elem = new_element('line', x1=0, y1=0, x2=5, y2=5)
146
+ >>> etree.tostring(elem)
147
+ b'<line x1="0" y1="0" x2="5" y2="5"/>'
148
+
149
+ Translates underscores to hyphens
150
+
151
+ >>> elem = new_element('line', stroke_width=1)
152
+ >>> etree.tostring(elem)
153
+ b'<line stroke-width="1"/>'
154
+
155
+ Removes trailing underscores. You'll almost certainly want to use reserved names like ``class`` as svg parameters. This
156
+ can be done by passing the name with a trailing underscore.
157
+
158
+ >>> elem = new_element('line', class_='thick_line')
159
+ >>> etree.tostring(elem)
160
+ b'<line class="thick_line"/>'
161
+
162
+ Special handling for a 'text' argument. Places value between element tags.
163
+
164
+ >>> elem = new_element('text', text='please star my project')
165
+ >>> etree.tostring(elem)
166
+ b'<text>please star my project</text>'
167
+
168
+ ### constructors.new_sub_element
169
+
170
+ parent: etree.Element
171
+ tag: str
172
+ **params: Union[str, float]
173
+ -> etree.Element
174
+
175
+ As above, but creates a subelement.
176
+
177
+ >>> parent = etree.Element('g')
178
+ >>> _ = new_sub_element('rect')
179
+ >>> etree.tostring(parent)
180
+ b'<g><rect/></g>'
181
+
182
+ ### update_element
183
+
184
+ Another way to add params through the new_element name / float translator. Again unnecessary, but potentially helpful. Easily understood from the code or docstrings.
185
+
186
+ ## Extras:
187
+
188
+ ### query.map_elems_to_bounding_boxes
189
+
190
+ Python cannot parse an svg file. Python can *create* an svg file, and Inkscape can parse (and inspect) it. Inkscape has a command-line interface capable of reading an svg file and returning some limited information. This is the only way I know for a Python program to:
191
+
192
+ 1. create an svg file (optionally without writing to filesystem)
193
+ 2. query the svg file for bounding-box information
194
+ 3. create an adjusted svg file.
195
+
196
+ This would be necessary for, e.g., algorithmically fitting text in a box.
197
+
198
+ from svg_ultralight.queries import map_elems_to_bounding_boxes
199
+
200
+ You can get a tiny bit more sophisticated with Inkscape bounding-box queries, but not much. This will give you pretty much all you can get out of it.
201
+
202
+ ### animate.write_gif
203
+
204
+ Create an animated gif from a sequence of png filenames. This is a Pillow one-liner, but it's convenient for me to have it, so it might be convenient for you. Requires pillow, which is not a project dependency.
205
+
206
+ from svg_ultralight.animate import write_gif
207
+
208
+ [Full Documentation and Tutorial](https://shayallenhill.com/svg-with-css-in-python/)
@@ -1,148 +1,190 @@
1
- # svg_ultralight
2
-
3
- The most straightforward way to create SVG files with Python.
4
-
5
- ## Four principal functions:
6
-
7
- from svg_ultralight import new_svg_root, write_svg, write_png_from_svg, write_png
8
-
9
- ## One convenience:
10
-
11
- from svg_ultralight import NSMAP
12
-
13
- ### new_svg_root
14
- x: float,
15
- y: float,
16
- width: float,
17
- height: float,
18
- pad: float = 0
19
- nsmap: Optional[Dict[str, str]] = None (svg_ultralight.NSMAP if None)
20
- -> etree.Element
21
-
22
- Create an svg root element from viewBox style arguments and provide the necessary svg-specific attributes and namespaces. This is your window onto the scene. The arguments are the same you'd use to create a `rect` element (plus `pad`):
23
-
24
- See `namespaces` below.
25
-
26
- * `x`: x value in upper-left corner
27
- * `y`: y value in upper-left corner
28
- * `width`: width of viewBox
29
- * `height`: height of viewBox
30
- * `pad`: The one small convenience I've provided. Optionally increase viewBox by `pad` in all directions.
31
- * `nsmap`: namespaces. (defaults to svg_ultralight.NSMAP). Available as an argument should you wish to add additional namespaces. To do this, add items to NSMAP then call with `nsmap=NSMAP`.
32
-
33
- ### namespaces (svg_ultralight.NSMAP)
34
-
35
- `new_svg_root` will create a root with several available namespaces.
36
-
37
- * `"dc": "http://purl.org/dc/elements/1.1/"`
38
- * `"cc": "http://creativecommons.org/ns#"`
39
- * `"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"`
40
- * `"svg": "http://www.w3.org/2000/svg"`
41
- * `"xlink": "http://www.w3.org/1999/xlink"`
42
- * `"sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"`
43
- * `"inkscape": "http://www.inkscape.org/namespaces/inkscape"`
44
-
45
- I have made these available to you as `svg_ultralight.NSMAP`
46
-
47
- ### write_svg
48
- svg: str,
49
- xml: etree.Element,
50
- stylesheet: Optional[str] = None,
51
- do_link_css: bool = False,
52
- **tostring_kwargs,
53
- -> str:
54
-
55
- Write an xml element as an svg file. This will link or inline your css code and insert the necessary declaration, doctype, and processing instructions.
56
-
57
- * `svg`: path to output file (include extension .svg)
58
- * `param xml`: root node of your svg geometry (created by `new_svg_root`)
59
- * `stylesheet`: optional path to a css stylesheet
60
- * `do_link_css`: link to stylesheet, else (default) write contents of stylesheet into svg (ignored if `stylesheet` is None). If you have a stylesheet somewhere, the default action is to dump the entire contents into your svg file. Linking to the stylesheet is more elegant, but inlining *always* works.
61
- * `**tostring_kwargs`: optional kwarg arguments for `lxml.etree.tostring`. Passing `xml_declaration=True` by itself will create an xml declaration with encoding set to UTF-8 and an svg DOCTYPE. These defaults can be overridden with keyword arguments `encoding` and `doctype`. If you don't know what this is, you can probably get away without it.
62
- * `returns`: for convenience, returns svg filename (`svg`)
63
- * `effects`: creates svg file at `svg`
64
-
65
- ### write_png_from_svg
66
-
67
- inkscape_exe: str,
68
- svg: str
69
- png: Optional[str]
70
- -> str
71
-
72
- Convert an svg file to a png. Python does not have a library for this. That has an upside, as any library would be one more set of svg implementation idiosyncrasies we'd have to deal with. Inkscape will convert the file. This function provides the necessary command-line arguments.
73
-
74
- * `inkscape_exe`: path to inkscape.exe
75
- * `svg`: path to svg file
76
- * `png`: optional path to png output (if not given, png name will be inferred from `svg`: `'name.svg'` becomes `'name.png'`)
77
- * `return`: png filename
78
- * `effects`: creates png file at `png` (or infers png path and filename from `svg`)
79
-
80
- ### write_png
81
-
82
- inkscape_exe: str,
83
- png: str,
84
- xml: etree.Element,
85
- stylesheet: Optional[str] = None
86
- -> str
87
-
88
- Create a png without writing an initial svg to your filesystem. This is not faster (it may be slightly slower), but it may be important when writing many images (animation frames) to your filesystem.
89
-
90
- * `inkscape_exe`: path to inkscape.exe
91
- * `png`: path to output file (include extension .png)
92
- * `param xml`: root node of your svg geometry (created by `new_svg_root`)
93
- * `stylesheet`: optional path to a css stylesheet
94
- * `returns`: for convenience, returns png filename (`png`)
95
- * `effects`: creates png file at `png`
96
-
97
- ## Two helpers:
98
-
99
- from svg_ultralight.constructors import new_element, new_sub_element
100
-
101
- I do want to keep this ultralight and avoid creating some pseudo scripting language between Python and lxml, but here are two very simple, very optional functions to save your having to `str()` every argument to `etree.Element`.
102
-
103
- ### constructors.new_element
104
-
105
- tag: str
106
- **params: Union[str, float]
107
- -> etree.Element
108
-
109
- Python allows underscores in variable names; xml uses dashes.
110
-
111
- Python understands numbers; xml wants strings.
112
-
113
- This is a convenience function to swap `"_"` for `"-"` and `10.2` for `"10.2"` before creating an xml element.
114
-
115
- Translates numbers to strings
116
-
117
- >>> elem = new_element('line', x1=0, y1=0, x2=5, y2=5)
118
- >>> etree.tostring(elem)
119
- b'<line x1="0" y1="0" x2="5" y2="5"/>'
120
-
121
- Translates underscores to hyphens
122
-
123
- >>> elem = new_element('line', stroke_width=1)
124
- >>> etree.tostring(elem)
125
- b'<line stroke-width="1"/>'
126
-
127
- Special handling for a 'text' argument. Places value between element tags.
128
-
129
- >>> elem = new_element('text', text='please star my project')
130
- >>> etree.tostring(elem)
131
- b'<text>please star my project</text>'
132
-
133
- ### constructors.new_sub_element
134
-
135
- parent: etree.Element
136
- tag: str
137
- **params: Union[str, float]
138
- -> etree.Element
139
-
140
- As above, but creates a subelement.
141
-
142
- >>> parent = etree.Element('g')
143
- >>> _ = new_sub_element('rect')
144
- >>> etree.tostring(parent)
145
- b'<g><rect/></g>'
146
-
147
- [Full Documentation and Tutorial](https://shayallenhill.com/svg-with-css-in-python/)
148
-
1
+ # svg_ultralight
2
+
3
+ The most straightforward way to create SVG files with Python.
4
+
5
+ ## Four principal functions:
6
+
7
+ from svg_ultralight import new_svg_root, write_svg, write_png_from_svg, write_png
8
+
9
+ ## One convenience:
10
+
11
+ from svg_ultralight import NSMAP
12
+
13
+ ### new_svg_root
14
+ x_: Optional[float],
15
+ y_: Optional[float],
16
+ width_: Optional[float],
17
+ height_: Optional[float],
18
+ pad_: float = 0
19
+ dpu_: float = 1
20
+ nsmap: Optional[Dict[str, str]] = None (svg_ultralight.NSMAP if None)
21
+ **attributes: Union[float, str],
22
+ -> etree.Element
23
+
24
+ Create an svg root element from viewBox style arguments and provide the necessary svg-specific attributes and namespaces. This is your window onto the scene.
25
+
26
+ Three ways to call:
27
+
28
+ 1. The trailing-underscore arguments are the same you'd use to create a `rect` element (plus `pad_` and `dpu_`). `new_svg_root` will infer `viewBox`, `width`, and `height` svg attributes from these values.
29
+ 2. Use the svg attributes you already know: `viewBox`, `width`, `height`, etc. These will be written to the xml file.
30
+ 3. Of course, you can combine 1. and 2. if you know what you're doing.
31
+
32
+ See `namespaces` below.
33
+
34
+ * `x_`: x value in upper-left corner
35
+ * `y_`: y value in upper-left corner
36
+ * `width_`: width of viewBox
37
+ * `height_`: height of viewBox
38
+ * `pad_`: the one small convenience I've provided. Optionally increase viewBox by `pad` in all directions.
39
+ * `dpu_`: pixels per viewBox unit for output png images.
40
+ * `nsmap`: namespaces. (defaults to svg_ultralight.NSMAP). Available as an argument should you wish to add additional namespaces. To do this, add items to NSMAP then call with `nsmap=NSMAP`.
41
+ * `**attributes`: the trailing-underscore arguments are an *optional* shortcut for creating a scene. The entire svg interface is available to you through kwargs. See `A few helpers` below for details on attribute-name translation between Python and xml (the short version: `this_name` becomes `this-name` and `this_` becomes `this`)
42
+
43
+ ### namespaces (svg_ultralight.NSMAP)
44
+
45
+ `new_svg_root` will create a root with several available namespaces.
46
+
47
+ * `"dc": "http://purl.org/dc/elements/1.1/"`
48
+ * `"cc": "http://creativecommons.org/ns#"`
49
+ * `"rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#"`
50
+ * `"svg": "http://www.w3.org/2000/svg"`
51
+ * `"xlink": "http://www.w3.org/1999/xlink"`
52
+ * `"sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"`
53
+ * `"inkscape": "http://www.inkscape.org/namespaces/inkscape"`
54
+
55
+ I have made these available to you as `svg_ultralight.NSMAP`
56
+
57
+ ### write_svg
58
+ svg: str,
59
+ xml: etree.Element,
60
+ stylesheet: Optional[str] = None,
61
+ do_link_css: bool = False,
62
+ **tostring_kwargs,
63
+ -> str:
64
+
65
+ Write an xml element as an svg file. This will link or inline your css code and insert the necessary declaration, doctype, and processing instructions.
66
+
67
+ * `svg`: path to output file (include extension .svg)
68
+ * `param xml`: root node of your svg geometry (created by `new_svg_root`)
69
+ * `stylesheet`: optional path to a css stylesheet
70
+ * `do_link_css`: link to stylesheet, else (default) write contents of stylesheet into svg (ignored if `stylesheet` is None). If you have a stylesheet somewhere, the default action is to dump the entire contents into your svg file. Linking to the stylesheet is more elegant, but inlining *always* works.
71
+ * `**tostring_kwargs`: optional kwarg arguments for `lxml.etree.tostring`. Passing `xml_declaration=True` by itself will create an xml declaration with encoding set to UTF-8 and an svg DOCTYPE. These defaults can be overridden with keyword arguments `encoding` and `doctype`. If you don't know what this is, you can probably get away without it.
72
+ * `returns`: for convenience, returns svg filename (`svg`)
73
+ * `effects`: creates svg file at `svg`
74
+
75
+ ### write_png_from_svg
76
+
77
+ inkscape_exe: str,
78
+ svg: str
79
+ png: Optional[str]
80
+ -> str
81
+
82
+ Convert an svg file to a png. Python does not have a library for this. That has an upside, as any library would be one more set of svg implementation idiosyncrasies we'd have to deal with. Inkscape will convert the file. This function provides the necessary command-line arguments.
83
+
84
+ * `inkscape_exe`: path to inkscape.exe
85
+ * `svg`: path to svg file
86
+ * `png`: optional path to png output (if not given, png name will be inferred from `svg`: `'name.svg'` becomes `'name.png'`)
87
+ * `return`: png filename
88
+ * `effects`: creates png file at `png` (or infers png path and filename from `svg`)
89
+
90
+ ### write_png
91
+
92
+ inkscape_exe: str,
93
+ png: str,
94
+ xml: etree.Element,
95
+ stylesheet: Optional[str] = None
96
+ -> str
97
+
98
+ Create a png without writing an initial svg to your filesystem. This is not faster (it may be slightly slower), but it may be important when writing many images (animation frames) to your filesystem.
99
+
100
+ * `inkscape_exe`: path to inkscape.exe
101
+ * `png`: path to output file (include extension .png)
102
+ * `param xml`: root node of your svg geometry (created by `new_svg_root`)
103
+ * `stylesheet`: optional path to a css stylesheet
104
+ * `returns`: for convenience, returns png filename (`png`)
105
+ * `effects`: creates png file at `png`
106
+
107
+ ## A few helpers:
108
+
109
+ from svg_ultralight.constructors import new_element, new_sub_element
110
+
111
+ I do want to keep this ultralight and avoid creating some pseudo scripting language between Python and lxml, but here are two very simple, very optional functions to save your having to `str()` every argument to `etree.Element`.
112
+
113
+ ### constructors.new_element
114
+
115
+ tag: str
116
+ **params: Union[str, float]
117
+ -> etree.Element
118
+
119
+ Python allows underscores in variable names; xml uses dashes.
120
+
121
+ Python understands numbers; xml wants strings.
122
+
123
+ This is a convenience function to swap `"_"` for `"-"` and `10.2` for `"10.2"` before creating an xml element.
124
+
125
+ Translates numbers to strings
126
+
127
+ >>> elem = new_element('line', x1=0, y1=0, x2=5, y2=5)
128
+ >>> etree.tostring(elem)
129
+ b'<line x1="0" y1="0" x2="5" y2="5"/>'
130
+
131
+ Translates underscores to hyphens
132
+
133
+ >>> elem = new_element('line', stroke_width=1)
134
+ >>> etree.tostring(elem)
135
+ b'<line stroke-width="1"/>'
136
+
137
+ Removes trailing underscores. You'll almost certainly want to use reserved names like ``class`` as svg parameters. This
138
+ can be done by passing the name with a trailing underscore.
139
+
140
+ >>> elem = new_element('line', class_='thick_line')
141
+ >>> etree.tostring(elem)
142
+ b'<line class="thick_line"/>'
143
+
144
+ Special handling for a 'text' argument. Places value between element tags.
145
+
146
+ >>> elem = new_element('text', text='please star my project')
147
+ >>> etree.tostring(elem)
148
+ b'<text>please star my project</text>'
149
+
150
+ ### constructors.new_sub_element
151
+
152
+ parent: etree.Element
153
+ tag: str
154
+ **params: Union[str, float]
155
+ -> etree.Element
156
+
157
+ As above, but creates a subelement.
158
+
159
+ >>> parent = etree.Element('g')
160
+ >>> _ = new_sub_element('rect')
161
+ >>> etree.tostring(parent)
162
+ b'<g><rect/></g>'
163
+
164
+ ### update_element
165
+
166
+ Another way to add params through the new_element name / float translator. Again unnecessary, but potentially helpful. Easily understood from the code or docstrings.
167
+
168
+ ## Extras:
169
+
170
+ ### query.map_elems_to_bounding_boxes
171
+
172
+ Python cannot parse an svg file. Python can *create* an svg file, and Inkscape can parse (and inspect) it. Inkscape has a command-line interface capable of reading an svg file and returning some limited information. This is the only way I know for a Python program to:
173
+
174
+ 1. create an svg file (optionally without writing to filesystem)
175
+ 2. query the svg file for bounding-box information
176
+ 3. create an adjusted svg file.
177
+
178
+ This would be necessary for, e.g., algorithmically fitting text in a box.
179
+
180
+ from svg_ultralight.queries import map_elems_to_bounding_boxes
181
+
182
+ You can get a tiny bit more sophisticated with Inkscape bounding-box queries, but not much. This will give you pretty much all you can get out of it.
183
+
184
+ ### animate.write_gif
185
+
186
+ Create an animated gif from a sequence of png filenames. This is a Pillow one-liner, but it's convenient for me to have it, so it might be convenient for you. Requires pillow, which is not a project dependency.
187
+
188
+ from svg_ultralight.animate import write_gif
189
+
190
+ [Full Documentation and Tutorial](https://shayallenhill.com/svg-with-css-in-python/)
@@ -0,0 +1,75 @@
1
+ [project]
2
+ name = "svg-ultralight"
3
+ version = "0.54.0"
4
+ description = "a sensible way to create svg files with Python"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ authors = [
8
+ { name = "Shay Hill", email = "shay_public@hotmail.com" }
9
+ ]
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "cssutils>=2.11.1",
13
+ "fonttools>=4.60.1",
14
+ "lxml>=6.0.2",
15
+ "paragraphs>=1.0.1",
16
+ "pillow>=11.3.0",
17
+ "svg-path-data>=0.5.3",
18
+ "types-lxml>=2025.8.25",
19
+ "typing-extensions>=4.15.0",
20
+ ]
21
+
22
+ [build-system]
23
+ requires = ["uv_build>=0.8.22,<0.9.0"]
24
+ build-backend = "uv_build"
25
+
26
+ [dependency-groups]
27
+ dev = [
28
+ "commitizen",
29
+ "pre-commit",
30
+ "pytest",
31
+ ]
32
+
33
+
34
+ [tool.commitizen]
35
+ name = "cz_conventional_commits"
36
+ version = "0.54.0"
37
+ tag_format = "$version"
38
+ major-version-zero = true
39
+ version_files = ["pyproject.toml:^version"]
40
+
41
+
42
+ [tool.isort]
43
+ profile = "black"
44
+
45
+
46
+ [tool.pytest.ini_options]
47
+ addopts = "--doctest-modules"
48
+ pythonpath = ["tests"]
49
+ log_cli = 1
50
+
51
+ [tool.ruff.lint.pydocstyle]
52
+ convention = "pep257"
53
+
54
+ [tool.ruff.lint.per-file-ignores]
55
+ "tests/*.py" = ["S101", "D", "F401"] # Ignore assertions, docstrings, unused imports in test files
56
+
57
+ [tool.pyright]
58
+ include = ["src"]
59
+ exclude = ["**/__pycache__.py"]
60
+
61
+ pythonVersion = "3.10"
62
+ pythonPlatform = "Any"
63
+
64
+ typeCheckingMode = "strict"
65
+ reportShadowedImports = true
66
+ reportCallInDefaultInitializer = true
67
+ reportImplicitStringConcatenation = true
68
+ # reportMissingSuperCall = true
69
+ reportPropertyTypeMismatch = true
70
+ reportUninitializedInstanceVariable = true
71
+ reportUnnecessaryTypeIgnoreComment = true
72
+ reportUnusedCallResult = true
73
+
74
+ venvPath = "."
75
+ venv = "./.venv"