SVG import
The goal of SVG import is to convert SVG into a Path/Layer/Doc representation "as close as possible" to the original data, within the constraints of our chose data model.
In the scope of vpype2, this includes:
- sort paths into a Document/Layer/Path structure—this includes correctly interpreting inkscape layers
- curved path elements should be preserved (#1)
- as much metadata as possible should be extracted (#4)
usvg offers most of that, but doesn't provide access to original attributes: https://github.com/RazrFalcon/resvg/issues/588
Possible avenues:
- fork usvg and add raw attribute to nodes
- fork usvg and add ref to roxml element to nodes
- migrate to
svg(a lot more things would have to be done manually) - use
svgin parallel tousvgjust to extract top-level groups attributes 4b) useroxmltreein parallel to/beforeusvgjust to extract top-level group attributes - what else?
Of these, (4) appears to be the most immediate hack, though it's not exactly clean. Option (3) would provide the most flexibility in the long term.
For (3), kurbo::BezPath::from_svg() implements SVG path parsing, simplify arcs into cubic bezier. It does keep quad bezier, but they would easily be converted to cubic and/or accepted in the data model.
Full migration to svg (option 3) feels daunting. This basically is the output of that lib for resources/fixtures/multilayer.svg:
Instruction: "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
Tag svg: Start {"xmlns:sodipodi": Value("http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"), "xmlns:ev": Value("http://www.w3.org/2001/xml-events"), "version": Value("1.2"), "baseProfile": Value("tiny"), "xmlns:inkscape": Value("http://www.inkscape.org/namespaces/inkscape"), "width": Value("15.875cm"), "xmlns:rdf": Value("http://www.w3.org/1999/02/22-rdf-syntax-ns#"), "xmlns:cc": Value("http://creativecommons.org/ns#"), "height": Value("15.875cm"), "xmlns:xlink": Value("http://www.w3.org/1999/xlink"), "viewBox": Value("0 0 600.0 600.0"), "xmlns:dc": Value("http://purl.org/dc/elements/1.1/"), "xmlns": Value("http://www.w3.org/2000/svg")}
Tag line: Empty {"x2": Value("420"), "y1": Value("20"), "x1": Value("300"), "stroke": Value("#ccc"), "y2": Value("35")}
Tag g: Start {"style": Value("display:inline"), "fill": Value("none"), "inkscape:label": Value("3"), "id": Value("layer1"), "inkscape:groupmode": Value("layer"), "stroke": Value("#00f")}
Tag polygon: Empty {"points": Value("10.0,10.0 10.0,210.0 210.0,210.0 210.0,10.0")}
Tag g: End {}
Tag g: Start {"fill": Value("none"), "id": Value("layer2"), "stroke": Value("#080"), "style": Value("display:inline"), "inkscape:groupmode": Value("layer"), "inkscape:label": Value("2")}
Tag polygon: Empty {"points": Value("400.0,400.0 400.0,700.0 600.0,700.0 600.0,400.0")}
Tag g: Start {"stroke": Value("#906"), "transform": Value("translate(-50, -50) rotate(35, 400, 200)")}
Tag polygon: Empty {"points": Value("400.0,400.0 400.0,700.0 600.0,700.0 600.0,400.0")}
Tag g: End {}
Tag g: End {}
Tag svg: End {}
All the basic stuff needed to be done: unit, interpreting transforms, default attribute values, etc. And that's not counting advanced stuff (use, nested svg, CSS, reference, etc.). 100% doesn't sound like something I want to undertake.
The problem with option (4) is that it requires double parsing (once with svg, once with roxlmtree (behind usvg). A better approach might be to use roxmltree instead of svg to extract the inkscape:* attributes, as a usvg::Tree can be built from a roxmltree::Document. Hence option (4b).
Option (4) implemented in 3f13cd7190b44fdee0d74e1bf2634745fc6a16d5
Discussions in the resvg issue linked above indicate that there is no path forward were usvg would properly support Inkscape layers. A permanent fork would be needed to pursue something along the lines of (1) or (2).
Btw, both the discussions and my experience so far indicate that (2) is not actually feasible. usvg does way too many tree-level manipulation for this to be viable. Even some top-level path sometime end up in "phantom" groups (which would have to be marked as such).
For the time being, the way forward is to for usvg and rosvgtree to support Inkscape layers. Initial implementation is here: https://github.com/abey79/resvg/pull/1. I'll maintain the vsvg branch on my resvg fork as the dependency for this project.
Proper implementation in edd1d796118d26d26018d9392baa6ef2ab8d7648 :tada:
As I want to publish vsvg & friends to crates.io, I can no longer use a fork of usvg and rosvgtree. So I need an alternate way to:
- propagate
inkscape:xxxattributes throughusvgandrosvgtree - identify "spurious" top-level groups produced by
usvgaround some drawable
My solution consists of rewriting the input SVG to encode all needed information into the group's id attribute before feeding the SVG to usvg.
If any of the inkscape:groupmode, or inkscape:label attribute exists, they are all (together with id) stored in a struct of Option, serialised to JSON, and base64 encoded after a __vsvg_encoded__ prefix. If only id exists, it's left as is. Otherwise, the id attribute is set to __vsvg_empty__XXX, where XXX is some random characters to preserve unique ID.
This way, after processing with usvg, the id attributes are starting either by __vsvg_encoded__ or __vsvg_empty__, or can be considered "spurious".
After experimenting with the great quick_xml crate, the preprocessing step appears to be <<100ms for SVGs up to 100MB.
I am both ashamed and proud of this solution. SVG does that to you...