Ship output

Read a compiled diagram back out

A text digest of the geometry, and recovering declarations from the SVG itself.

sheet
10 / 15
rev
v0.6
sections
3
compiled
3

01 / DigestReview geometry as text, not as an image

Pixel goldens answer "does this render correctly?" — a question only a browser can settle. They answer "did anything move?" badly: a shifted vertex arrives as a red blob, and we cannot see what moved without opening two images side by side.

snapshotSchematic answers the second question in text:

import { snapshotSchematic } from '@schemd/core/snapshot';

const digest = snapshotSchematic(document, fence);
schemd-snapshot 1
bounds 900x520
component R1 resistor rect=(280.000,124.000,100.000,52.000) at=(330.000,150.000) color=token:amber
component C1 capacitor rect=(536.000,296.000,48.000,48.000) at=(560.000,320.000) orient=down color=token:cyan
trace VIN.positive->R1.in curve=line markers=none/none color=token:blue net=$1 vertices=[(152.000,150.000),(280.000,150.000)]

Every rectangle and every vertex, at the three decimals the SVG writer uses, in source order. Commit it as a fixture and a routing change reviews as the handful of coordinates that actually moved.

Two things worth knowing. The leading schemd-snapshot 1 is a format version — a committed fixture outlives the code that wrote it, and without that line a future format change would read as a diff in every snapshot at once. And an orientation appears only when the declaration stated one, because a part with no orientation is a different declaration from one that names its canonical direction.

This does not replace a renderer. Nothing in a digest can tell us an arrowhead went missing or a label collided, which is why the compiler keeps its Chromium goldens for exactly those cases.

source:VIN "AC" at (110, 150) #blue [type=voltage-ac]
resistor:R1 "1 kΩ" at (330, 150) #amber
junction:VOUT "V_{out}" at (560, 150) #cyan
capacitor:C1 "100 nF" at (560, 320) #cyan [orientation=down]
ground:GND "0 V" at (330, 430) #slate

VIN.positive -> R1.in #blue [line]
R1.out -> VOUT.node #amber [line]
VOUT.node -> C1.in #cyan [ortho]
C1.out -> GND.in #slate [ortho]
compiled by @schemd/core → shown in the rail

02 / RecoveryThe output is readable back

full mode already stamps a great deal on the markup: each node group carries its id, kind, label, source line and orientation, sits at a translate, and is painted with a colour token; each wire group carries its endpoints, net, signal domain and bus width. That is most of a declaration, sitting unread.

import { parseSchematicSvg } from '@schemd/core/decompile';

const { components, connections, source, lost } = parseSchematicSvg(svg);

It is a bounded scanner over the attribute set the renderer writes, not an XML parser — the only inputs that need to work are diagrams this compiler produced. Give it default or embedded-css output and it refuses, because that markup carries no hooks at all.

What survives: every component's id, kind, label, coordinates, orientation and colour; every connection's endpoints, curve, colour, markers, bus width and signal domain. The curve comes from the path that was drawn — bezier is the only one that emits C, ortho walks in H/V steps — so it is read rather than assumed.

port:IN "D" at (150, 200) #blue
and:G1 "AND" right-of IN by 200 #purple
port:OUT "Q" right-of G1 by 200 #emerald

IN.out -> G1.in1 #blue [ortho]
G1.out -> OUT.in #purple [ortho marker-end=arrow]
compiled by @schemd/core → shown in the rail

03 / HonestyWhat does not come back, named

Recovery is partial, and it says which parts. Family options — type=, variant= — are not stamped anywhere a scanner can read; they survive only as prose inside each group's aria-label. A recovered resistor is a plain resistor even if it was declared as a thermistor.

recovery.lost;
// [{ code: 'component-variant', detail: 'Family options such as type= …' }]

There is deliberately no fidelity: 'exact' alongside that list. Any document with a component loses something, so a flag that can only ever hold one value would tell us nothing; the list tells us what.

The same principle governs malformed input. A group missing anything full mode always writes is skipped rather than guessed at — inventing a label, a source line, or a straight-line curve for markup that was edited after the fact would put a fabricated declaration into recovered source, which is worse than admitting the group could not be read.

What we get back is therefore a faithful account of topology, placement and paint. It is not a promise that recompiling reproduces the original bytes, and the compiler's own suite pins the honest version of that claim: recovered source recompiles to the same topology, the same placement, and is itself a fixed point.

resistor:R1 "fixed" at (250, 180) #amber
resistor:R2 "thermistor" right-of R1 by 200 #amber [type=thermistor]

R1.out -> R2.in #amber [line]
compiled by @schemd/core → shown in the rail