Minimal Rust + WASM library for Figma‑style vector networks (undirected graphs) targeting the browser.
It exposes a small API to JavaScript for creating a graph, connecting nodes, listing neighbors, and computing shortest paths (BFS).
# 1) Prereqs
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
# 2) Build the WASM bundle (from contour-wasm/)
# outputs to ../pkg so the web demo can import it
cd contour-wasm && wasm-pack build --target web --out-dir ../pkg && cd -
# 3) Serve statically (from repo root)
python3 -m http.server
# 4) Open the demo in your browser
# visit http://localhost:8000/web/index.htmlThis produces a pkg/ folder with JS/WASM bindings that the demo imports.
- Rust toolchain
- Target
wasm32-unknown-unknown:rustup target add wasm32-unknown-unknown wasm-pack(recommended):cargo install wasm-pack
From the workspace contour-wasm/ crate:
wasm-pack build --target web --out-dir ../pkgThis creates a pkg/ folder with JS/WASM bindings.
WASM unit tests are written with wasm-bindgen-test under tests/.
Run in a headless browser (requires Chrome or Firefox installed). Run from the contour-wasm/ crate directory:
# Chrome (from contour-wasm/)
cd contour-wasm && wasm-pack test --headless --chrome && cd -
# or Firefox
cd contour-wasm && wasm-pack test --headless --firefox && cd -You can also run them in a non-headless browser by omitting --headless.
For production, prefer the strict *_res methods which validate inputs and never panic. They return a Result-like object:
{ ok: true, value: T }on success{ ok: false, error: { code, message, data? } }on failure
See docs/errors.md for error codes and invariants. A minimal TypeScript declaration is provided at contour-wasm/types.d.ts.
Serve the folder via any static file server (so the browser can fetch the WASM file). For example, from contour/:
python3 -m http.server
# or use any other static serverThen open http://localhost:8000/web/index.html.
Demo controls:
- Import SVG: click "Import SVG" or drag-and-drop a
.svgonto the canvas. The demo parses all<path d>commands (M/L/C/Z) and imports them into the graph. - Save/Load: persists the current graph to
localStorage. - Clear: removes all nodes and edges.
- Bucket (F): toggles region fills.
- Bend (B): drag directly on a curve to bend.
new Graph()graph.add_node() -> numbergraph.add_edge(a: number, b: number) -> booleangraph.node_count() -> numbergraph.edge_count() -> numbergraph.neighbors(id: number) -> number[] | nullgraph.shortest_path(start: number, goal: number) -> number[] | nullgraph.add_svg_path(d: string) -> number(append path data; supports M/L/C/Z)graph.to_svg_paths() -> string[](export independent path fragments)graph.get_regions() -> [{ key, area, filled, color?: [r,g,b,a], points[] }]graph.toggle_region(key: number) -> booleangraph.set_region_fill(key: number, filled: boolean)graph.set_region_color(key: number, r: number, g: number, b: number, a: number)
Strict variants (examples):
graph.add_node_res(x, y) -> { ok|error }graph.move_node_res(id, x, y) -> { ok|error }graph.add_edge_res(a, b) -> { ok|error }graph.set_handle_pos_res(id, end, x, y) -> { ok|error }graph.bend_edge_to_res(id, t, tx, ty, stiffness) -> { ok|error }graph.pick_res(x, y, tol) -> { ok: true, value: null | Pick }graph.add_svg_path_res(d) -> { ok|error }- Full list in
contour-wasm/types.d.ts.
graph.add_freehand(points: Float32Array, close: boolean) -> Uint32Array- Fits a smooth cubic Bezier chain (Catmull–Rom with corner detection) through sampled points.
- Intended for the Pen → Free Draw mode in the demo.
- The crate is built as a
cdylibfor WebAssembly and useswasm-bindgenfor bindings. - The simple web demo imports from
./pkg/contour_wasm.js, which is created by building thecontour-wasmcrate. - JSON
to_jsonnow includesversionandfillsarrays;from_jsonreads them. - Robustness: editing operations clamp parameters and treat degenerate inputs as no-ops. Internals avoid panics under zero-length edges, coincident endpoints, and tiny faces. See
docs/epsilons.mdfor the centralized tolerance policy.