meerk40t / svgelements

SVG Parsing for Elements, Paths, and other SVG Objects.
MIT License
124 stars 28 forks source link

Creating an SVG with viewbox and width/height in mm results in Error on write #228

Closed robinwersich closed 1 year ago

robinwersich commented 1 year ago

Creating a simple SVG with width and height in mm, and a viewBox, results in an SVG object that is not writable:

SVG(viewBox="0 0 10 10", width="10mm", height="10mm").string_xml()

Traceback (most recent call last):
  File "C:\Python310\lib\code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
  File "C:\...\venv\lib\site-packages\svgelements\svgelements.py", line 3432, in string_xml
    return tostring(self)
  File "C:\...\venv\lib\site-packages\svgelements\svgelements.py", line 9352, in tostring
    return tostring(_write_node(node), encoding="unicode")
  File "C:\...\venv\lib\site-packages\svgelements\svgelements.py", line 9415, in _write_node
    vt = node.viewbox_transform
  File "C:\...\venv\lib\site-packages\svgelements\svgelements.py", line 8885, in viewbox_transform
    return self.viewbox.transform(self)
  File "C:\...\venv\lib\site-packages\svgelements\svgelements.py", line 3255, in transform
    return Viewbox.viewbox_transform(
  File "C:\...\venv\lib\site-packages\svgelements\svgelements.py", line 3351, in viewbox_transform
    raise ValueError
ValueError

The error does not occur if viewBox is omitted or width/height is without unit. It does, however, occur if no height and width is given.

tatarize commented 1 year ago

It attempts to write the viewport transform which is really the mapping between 0 0 10 10 and 0 0 10mm 10mm it correctly determined the scaling was 1mm but couldn't write that out as a matrix since it expects all real-length values should be converted to real values. A fact that is no longer true since you could merely try to output these lengths without ever having rendered them to real units.

        svg = SVG(viewBox="0 0 10 10", width="10mm", height="10mm")
        svg.render(ppi=96.0)
        print(svg.string_xml())

which would give:

<svg viewBox="0 0 10 10" width="37.795296" height="37.795296" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" />

For most of svgelements life there wasn't an output and the expectation was to have successfully parsed an input. I guess the correct answer here is without having the .render() information to merely write the data back out going full circle. There might be other such errors but it seems like if you create a rectangle with actual physical units it should still write that to disk even if it doesn't know how large those units are.

I'll check the other shapes and make sure there's no other obvious crashes with un-rendered values.

Basically there's a two phase parsing, one where the shape is created with all the acceptable values like % and mm etc. Then during the parse() we call render() usually with the parsing information provided to SVG.parse() so it has real numbers that it can do actual geometric calculations with. Here, the meaning of mm is left to be ambiguous still and it should, instead, write those values to disk without knowing what they are.

tatarize commented 1 year ago

Other objects seem to work find with ambiguous lengths.