How it works
An SVG says "a rectangle here, some text there, a path with an arrowhead". draw.io wants "this box, labelled so, with an arrow from it to that box". These are the stages that get from one to the other.
The five stages
Recognise. A source that names its own parts is read by those names rather than from its picture, which is always more exact. That is what a profile does. An SVG that already carries a draw.io diagram skips everything below: the diagram inside it is returned as it is.
Parse. Everything a profile did not claim is read as an SVG. The style
cascade is resolved in full - presentation attributes, then stylesheet rules,
then style="", with inheritance and custom properties through var() - along
with 2D transforms, viewBox, nested <svg>, <use> and <symbol>, and
lengths in any unit. Rectangles stay rectangles and arrowheads stay arrowheads;
a renderer would have turned both into paths by now, which is exactly the
information a diagram needs.
Scene. The result is a scene: shapes, text, connectors, frames and groups in page coordinates, with no draw.io anywhere in it. It has invariants of its own, and every sample in the test corpus is checked against them.
Layout. What the source did not say is worked out here - and only here, so it can be tested on its own. It fills gaps and never overwrites what a reader already knew: where a profile said which two shapes an arrow joins, that stands.
Emit. The scene becomes an mxGraphModel. Cell ids are stable and numbers
are formatted one way, so converting the same file twice gives the same bytes
and a diff means something.
What the layout infers
Labels. Text drawn inside a box, an ellipse or an outline that closes becomes that shape's label, placed where the SVG had it - so moving the shape in draw.io moves its words. A box drawn in outline, with no paint inside it, is a box like any other. A stroke that never closes - an underline, a bracket, a flourish - carries nothing, because its bounding box would swallow whatever words happened to lie across it.
Borders. Several elements often draw one visual thing: a filled body with a separate unpainted outline over it. The outline is the body's border. It is never given the words, never given an arrow's glue, and never made the frame around the thing it outlines.
Connectors. A line, an unfilled polyline or an open path is an edge, and its markers become arrows with their tips at its ends. A triangle drawn right after it, in its colour, at one of its ends, is an arrow too - many exporters draw them that way. Each end is glued to the shape it reaches, within a few stroke widths, preferring a box to the frame around it.
Frames. A shape that holds other shapes is a container. Every shape, free text and line lying inside it becomes its child, written relative to its corner, so dragging the frame takes everything in it along: a subnet its boxes, a layer its services, a legend its keys. A line belongs to a frame only if the shapes it joins do too. A frame's own title stays free text rather than becoming its label.
Every threshold above scales with font size or stroke width. None is a fixed number of pixels, because a diagram exported at twice the scale is the same diagram.
What is reported instead of drawn
Some things draw.io cannot express. Each becomes an image cell holding an SVG of that element alone, placed where it draws, and each is listed as a note returned with the diagram:
- masks, filters, patterns, text along a path, and HTML in a
<foreignObject>; - a gradient that is not two colours across or out from the shape - it is drawn as its first colour, with a note;
- a skewed or mirrored picture, which has no equivalent in a cell's rotation;
- a clip path, which is drawn without and reported: most clip nothing a diagram needs;
- a reference that cannot be resolved - to nothing, to another file, or back to itself - and one that nests too deep or draws too much.
Nothing is dropped without a word. Every element that draws ends as a cell, as part of one, or as a note; one that does neither is reported as unaccounted, which is a bug report rather than a description.
Why the output is checked by looking at it
A style string that reads correctly can render wrongly, and several defects found in this project were invisible in the XML and obvious in a picture. So every change is judged by converting the whole corpus and putting each source SVG next to the exported diagram, not by reading the markup.
draw.io has behaviours worth knowing about if you write .drawio files
yourself:
- a
<b>tag in an HTML label silently kills the label'sfontColor; a wholly bold label usesfontStyle=1instead; - an edge bound to a shape without fixed connection points is rerouted to that
shape's perimeter and its real geometry discarded, so every bound edge here
carries
exitX/exitYandentryX/entryY; - labels centre both ways unless
alignandverticalAlignsay otherwise, and carry two units of spacing on every side unlessspacing=0says otherwise; - a duplicate cell id makes draw.io refuse the whole document.