From b48a261cb810bbf6726280ae97bebd8c1ea4c4b1 Mon Sep 17 00:00:00 2001 From: Cody Date: Mon, 13 Apr 2026 01:06:57 -0400 Subject: [PATCH] docs(examples): add gallery of 5 canonical drawings Adds a sample gallery under examples/gallery/ showing every element type, fill pattern, stroke style, and freedraw curves. Each of the 5 drawings is committed as .draw.json + .svg + .png so reviewers see exactly how rendering changes over time via the diff on the exported assets. Regenerated by a single runnable example: cargo run --example gallery -p dkdc-draw-core The example pins Document id and timestamps, making regeneration byte-idempotent (verified). Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 8 + crates/draw-core/examples/gallery.rs | 655 +++++++++++++++++++++++++++ examples/gallery/README.md | 47 ++ examples/gallery/flowchart.draw.json | 316 +++++++++++++ examples/gallery/flowchart.png | Bin 0 -> 20428 bytes examples/gallery/flowchart.svg | 169 +++++++ examples/gallery/patterns.draw.json | 190 ++++++++ examples/gallery/patterns.png | Bin 0 -> 28707 bytes examples/gallery/patterns.svg | 172 +++++++ examples/gallery/sketch.draw.json | 612 +++++++++++++++++++++++++ examples/gallery/sketch.png | Bin 0 -> 16136 bytes examples/gallery/sketch.svg | 7 + examples/gallery/sticky.draw.json | 234 ++++++++++ examples/gallery/sticky.png | Bin 0 -> 5944 bytes examples/gallery/sticky.svg | 22 + examples/gallery/wireframe.draw.json | 318 +++++++++++++ examples/gallery/wireframe.png | Bin 0 -> 12135 bytes examples/gallery/wireframe.svg | 15 + 18 files changed, 2765 insertions(+) create mode 100644 crates/draw-core/examples/gallery.rs create mode 100644 examples/gallery/README.md create mode 100644 examples/gallery/flowchart.draw.json create mode 100644 examples/gallery/flowchart.png create mode 100644 examples/gallery/flowchart.svg create mode 100644 examples/gallery/patterns.draw.json create mode 100644 examples/gallery/patterns.png create mode 100644 examples/gallery/patterns.svg create mode 100644 examples/gallery/sketch.draw.json create mode 100644 examples/gallery/sketch.png create mode 100644 examples/gallery/sketch.svg create mode 100644 examples/gallery/sticky.draw.json create mode 100644 examples/gallery/sticky.png create mode 100644 examples/gallery/sticky.svg create mode 100644 examples/gallery/wireframe.draw.json create mode 100644 examples/gallery/wireframe.png create mode 100644 examples/gallery/wireframe.svg diff --git a/README.md b/README.md index 493b5b5..0c655de 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,14 @@ uv run python examples/hello.py # Python: same, plus hello.draw.js Both build a small document programmatically and export it. Source: `crates/draw-core/examples/hello.rs`, `examples/hello.py`. +### Sample gallery + +[examples/gallery/](examples/gallery/) contains five canonical drawings — flowchart, sticky-notes, wireframe, sketch, patterns — each committed as `.draw.json` + `.svg` + `.png`. All five are produced by a single Rust example that exercises the full API surface (every element type, every fill pattern, stroke/dash styles, freedraw curves, arrows with heads): + +```bash +cargo run --example gallery -p dkdc-draw-core +``` + ### Keyboard shortcuts | Key | Action | diff --git a/crates/draw-core/examples/gallery.rs b/crates/draw-core/examples/gallery.rs new file mode 100644 index 0000000..ef00f9c --- /dev/null +++ b/crates/draw-core/examples/gallery.rs @@ -0,0 +1,655 @@ +//! Sample gallery: builds 5 canonical drawings demonstrating the full +//! `draw-core` API surface and exports each as `.draw.json`, `.svg`, and +//! `.png` under `examples/gallery/`. +//! +//! Run with: `cargo run --example gallery -p dkdc-draw-core` +//! +//! Each drawing here targets a different feature slice: +//! - flowchart: rectangles, diamond, arrows with arrowheads, text +//! - sticky: solid-fill rectangles with text labels (no hachure) +//! - wireframe: ellipses, dashed strokes, bordered layout +//! - sketch: freedraw curves + text annotations +//! - patterns: every FillType side by side (Solid, Hachure, CrossHatch, None) + +use std::fs; +use std::path::{Path, PathBuf}; + +use draw_core::style::{Arrowhead, FillStyle, FillType, FontStyle, StrokeStyle, TextAlign}; +use draw_core::{ + Document, Element, FreeDrawElement, LineElement, Point, ShapeElement, TextElement, + export_png_with_scale, export_svg, storage, +}; + +fn main() -> anyhow::Result<()> { + let out_dir: PathBuf = std::env::var("DRAW_GALLERY_OUT") + .map(PathBuf::from) + .unwrap_or_else(|_| { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join("examples") + .join("gallery") + }); + fs::create_dir_all(&out_dir)?; + + let drawings = [ + ("flowchart", flowchart()), + ("sticky", sticky_notes()), + ("wireframe", wireframe()), + ("sketch", sketch()), + ("patterns", patterns()), + ]; + + for (slug, mut doc) in drawings.iter().cloned() { + // Pin id and timestamps so regenerated artifacts are byte-identical to + // the committed ones — reviewers see a clean no-op diff unless content + // actually changed. + pin_metadata(&mut doc, slug); + write_drawing(&out_dir, slug, &doc)?; + } + + println!("wrote {} drawings to {}", drawings.len(), out_dir.display()); + Ok(()) +} + +fn pin_metadata(doc: &mut Document, slug: &str) { + doc.id = format!("gallery-{slug}"); + let pinned = "2026-01-01T00:00:00+00:00".to_string(); + doc.created_at = pinned.clone(); + doc.modified_at = pinned; +} + +fn write_drawing(out_dir: &Path, slug: &str, doc: &Document) -> anyhow::Result<()> { + let json_path = out_dir.join(format!("{slug}.draw.json")); + storage::save(doc, &json_path)?; + + let svg_path = out_dir.join(format!("{slug}.svg")); + fs::write(&svg_path, export_svg(doc))?; + + // 2x scale for retina-quality gallery assets. + let png_path = out_dir.join(format!("{slug}.png")); + fs::write(&png_path, export_png_with_scale(doc, 2.0)?)?; + + println!("wrote {slug}: json + svg + png"); + Ok(()) +} + +// ── Drawing builders ────────────────────────────────────────────────── + +fn flowchart() -> Document { + let mut doc = Document::new("flowchart".into()); + + // Start ellipse + doc.add_element(Element::Ellipse(shape_with_fill( + "start", + 40.0, + 20.0, + 140.0, + 60.0, + FillType::Hachure, + "#10b981", + ))); + doc.add_element(Element::Text(centered_label( + "start-label", + 70.0, + 42.0, + "start", + 18.0, + "#064e3b", + ))); + + // Decision diamond + doc.add_element(Element::Diamond(shape_with_fill( + "decide", + 40.0, + 140.0, + 140.0, + 100.0, + FillType::Hachure, + "#f59e0b", + ))); + doc.add_element(Element::Text(centered_label( + "decide-label", + 75.0, + 180.0, + "valid?", + 16.0, + "#78350f", + ))); + + // Accept rectangle + doc.add_element(Element::Rectangle(shape_with_fill( + "accept", + 240.0, + 150.0, + 140.0, + 80.0, + FillType::Hachure, + "#3b82f6", + ))); + doc.add_element(Element::Text(centered_label( + "accept-label", + 270.0, + 180.0, + "process", + 16.0, + "#1e3a8a", + ))); + + // Reject rectangle + doc.add_element(Element::Rectangle(shape_with_fill( + "reject", + 40.0, + 300.0, + 140.0, + 80.0, + FillType::Hachure, + "#ef4444", + ))); + doc.add_element(Element::Text(centered_label( + "reject-label", + 80.0, + 330.0, + "reject", + 16.0, + "#7f1d1d", + ))); + + // Arrows + doc.add_element(Element::Arrow(arrow_with_head( + "a1", + 110.0, + 80.0, + vec![Point::new(0.0, 0.0), Point::new(0.0, 60.0)], + ))); + doc.add_element(Element::Arrow(arrow_with_head( + "a2", + 180.0, + 190.0, + vec![Point::new(0.0, 0.0), Point::new(60.0, 0.0)], + ))); + doc.add_element(Element::Arrow(arrow_with_head( + "a3", + 110.0, + 240.0, + vec![Point::new(0.0, 0.0), Point::new(0.0, 60.0)], + ))); + + // Edge labels + doc.add_element(Element::Text(centered_label( + "yes-label", + 190.0, + 170.0, + "yes", + 14.0, + "#52525b", + ))); + doc.add_element(Element::Text(centered_label( + "no-label", 120.0, 260.0, "no", 14.0, "#52525b", + ))); + + doc +} + +fn sticky_notes() -> Document { + let mut doc = Document::new("sticky-notes".into()); + + let notes = [ + ( + "note1", + 20.0, + 20.0, + "#fef3c7", + "#78350f", + "shopping\n bread\n butter", + ), + ( + "note2", + 200.0, + 20.0, + "#dbeafe", + "#1e3a8a", + "read list\n Rust book\n SICP", + ), + ( + "note3", + 380.0, + 20.0, + "#fce7f3", + "#831843", + "weekend\n hike\n cook", + ), + ( + "note4", + 110.0, + 220.0, + "#d1fae5", + "#064e3b", + "ideas\n draw gallery\n lint pass", + ), + ( + "note5", + 290.0, + 220.0, + "#fee2e2", + "#7f1d1d", + "blockers\n pixmap panic\n hachure opacity", + ), + ]; + + for (i, (slug, x, y, fill_hex, text_hex, text)) in notes.iter().enumerate() { + doc.add_element(Element::Rectangle(ShapeElement { + id: format!("{slug}-bg"), + x: *x, + y: *y, + width: 160.0, + height: 160.0, + angle: if i % 2 == 0 { -0.03 } else { 0.03 }, + stroke: StrokeStyle { + color: (*text_hex).to_string(), + width: 1.5, + dash: vec![], + }, + fill: FillStyle { + color: (*fill_hex).to_string(), + style: FillType::Solid, + gap: 10.0, + angle: 0.0, + }, + opacity: 1.0, + locked: false, + group_id: None, + })); + doc.add_element(Element::Text(TextElement { + id: format!("{slug}-text"), + x: x + 12.0, + y: y + 16.0, + text: (*text).to_string(), + font: FontStyle { + family: "Inter, sans-serif".to_string(), + size: 14.0, + align: TextAlign::Left, + }, + stroke: StrokeStyle { + color: (*text_hex).to_string(), + width: 1.0, + dash: vec![], + }, + opacity: 1.0, + angle: 0.0, + locked: false, + group_id: None, + })); + } + + doc +} + +fn wireframe() -> Document { + let mut doc = Document::new("wireframe".into()); + + // Window frame + doc.add_element(Element::Rectangle(shape_with_stroke( + "frame", + 20.0, + 20.0, + 440.0, + 320.0, + "#334155", + 2.5, + vec![], + ))); + // Title bar + doc.add_element(Element::Rectangle(shape_with_stroke( + "titlebar", + 20.0, + 20.0, + 440.0, + 40.0, + "#334155", + 1.5, + vec![], + ))); + doc.add_element(Element::Text(centered_label( + "title", 34.0, 34.0, "~/draw", 14.0, "#334155", + ))); + + // Dashed content area + doc.add_element(Element::Rectangle(shape_with_stroke( + "content", + 40.0, + 80.0, + 400.0, + 240.0, + "#64748b", + 1.0, + vec![6.0, 4.0], + ))); + + // Inner buttons (ellipses) + doc.add_element(Element::Ellipse(shape_with_stroke( + "btn1", + 60.0, + 100.0, + 80.0, + 32.0, + "#0ea5e9", + 1.5, + vec![], + ))); + doc.add_element(Element::Text(centered_label( + "btn1-label", + 80.0, + 110.0, + "open", + 12.0, + "#0c4a6e", + ))); + + doc.add_element(Element::Ellipse(shape_with_stroke( + "btn2", + 160.0, + 100.0, + 80.0, + 32.0, + "#0ea5e9", + 1.5, + vec![], + ))); + doc.add_element(Element::Text(centered_label( + "btn2-label", + 180.0, + 110.0, + "save", + 12.0, + "#0c4a6e", + ))); + + doc.add_element(Element::Ellipse(shape_with_stroke( + "btn3", + 260.0, + 100.0, + 80.0, + 32.0, + "#0ea5e9", + 1.5, + vec![], + ))); + doc.add_element(Element::Text(centered_label( + "btn3-label", + 280.0, + 110.0, + "export", + 12.0, + "#0c4a6e", + ))); + + // Canvas placeholder with crossed diagonals + doc.add_element(Element::Rectangle(shape_with_stroke( + "canvas", + 60.0, + 160.0, + 380.0, + 140.0, + "#94a3b8", + 1.0, + vec![], + ))); + doc.add_element(Element::Line(LineElement::new( + "d1".into(), + 60.0, + 160.0, + vec![Point::new(0.0, 0.0), Point::new(380.0, 140.0)], + ))); + doc.add_element(Element::Line(LineElement::new( + "d2".into(), + 440.0, + 160.0, + vec![Point::new(0.0, 0.0), Point::new(-380.0, 140.0)], + ))); + + doc +} + +fn sketch() -> Document { + let mut doc = Document::new("sketch".into()); + + // A squiggly curve + let squiggle: Vec = (0..60) + .map(|i| { + let t = i as f64; + Point::new(t * 6.0, (t * 0.3).sin() * 40.0) + }) + .collect(); + doc.add_element(Element::FreeDraw(FreeDrawElement { + id: "wave".into(), + x: 30.0, + y: 80.0, + points: squiggle, + stroke: StrokeStyle { + color: "#8b5cf6".to_string(), + width: 2.5, + dash: vec![], + }, + opacity: 1.0, + locked: false, + group_id: None, + })); + + // A heart-ish freedraw + let heart: Vec = (0..64) + .map(|i| { + let t = i as f64 / 64.0 * std::f64::consts::TAU; + let x = 16.0 * t.sin().powi(3); + let y = + -(13.0 * t.cos() - 5.0 * (2.0 * t).cos() - 2.0 * (3.0 * t).cos() - (4.0 * t).cos()); + Point::new(x * 3.0, y * 3.0) + }) + .collect(); + doc.add_element(Element::FreeDraw(FreeDrawElement { + id: "heart".into(), + x: 250.0, + y: 180.0, + points: heart, + stroke: StrokeStyle { + color: "#e11d48".to_string(), + width: 3.0, + dash: vec![], + }, + opacity: 1.0, + locked: false, + group_id: None, + })); + + // Annotation arrow pointing at the heart + doc.add_element(Element::Arrow(arrow_with_head( + "ann-arrow", + 100.0, + 220.0, + vec![Point::new(0.0, 0.0), Point::new(120.0, -20.0)], + ))); + doc.add_element(Element::Text(centered_label( + "ann-text", + 20.0, + 220.0, + "freedraw!", + 16.0, + "#334155", + ))); + + doc.add_element(Element::Text(centered_label( + "wave-text", + 30.0, + 40.0, + "sin wave via FreeDraw", + 16.0, + "#334155", + ))); + + doc +} + +fn patterns() -> Document { + let mut doc = Document::new("fill-patterns".into()); + + let tiles = [ + ("solid", 20.0, "Solid", FillType::Solid, "#ec4899"), + ("hachure", 200.0, "Hachure", FillType::Hachure, "#ec4899"), + ( + "crosshatch", + 380.0, + "CrossHatch", + FillType::CrossHatch, + "#ec4899", + ), + ("none", 560.0, "None", FillType::None, "#ec4899"), + ]; + + for (slug, x, label, style, color) in tiles { + doc.add_element(Element::Rectangle(ShapeElement { + id: format!("{slug}-rect"), + x, + y: 40.0, + width: 160.0, + height: 120.0, + angle: 0.0, + stroke: StrokeStyle { + color: color.to_string(), + width: 2.0, + dash: vec![], + }, + fill: FillStyle { + color: color.to_string(), + style, + gap: 8.0, + angle: -0.785, + }, + opacity: 1.0, + locked: false, + group_id: None, + })); + doc.add_element(Element::Text(centered_label( + &format!("{slug}-label"), + x + 10.0, + 180.0, + label, + 14.0, + "#1f2937", + ))); + } + + doc +} + +// ── Small builders ──────────────────────────────────────────────────── + +fn shape_with_fill( + id: &str, + x: f64, + y: f64, + w: f64, + h: f64, + style: FillType, + color: &str, +) -> ShapeElement { + ShapeElement { + id: id.to_string(), + x, + y, + width: w, + height: h, + angle: 0.0, + stroke: StrokeStyle { + color: color.to_string(), + width: 2.0, + dash: vec![], + }, + fill: FillStyle { + color: color.to_string(), + style, + gap: 10.0, + angle: -0.785, + }, + opacity: 1.0, + locked: false, + group_id: None, + } +} + +fn shape_with_stroke( + id: &str, + x: f64, + y: f64, + w: f64, + h: f64, + color: &str, + stroke_w: f64, + dash: Vec, +) -> ShapeElement { + ShapeElement { + id: id.to_string(), + x, + y, + width: w, + height: h, + angle: 0.0, + stroke: StrokeStyle { + color: color.to_string(), + width: stroke_w, + dash, + }, + fill: FillStyle { + color: color.to_string(), + style: FillType::None, + gap: 10.0, + angle: 0.0, + }, + opacity: 1.0, + locked: false, + group_id: None, + } +} + +fn arrow_with_head(id: &str, x: f64, y: f64, points: Vec) -> LineElement { + LineElement { + id: id.to_string(), + x, + y, + points, + stroke: StrokeStyle { + color: "#334155".to_string(), + width: 2.0, + dash: vec![], + }, + start_arrowhead: None, + end_arrowhead: Some(Arrowhead::Arrow), + opacity: 1.0, + locked: false, + group_id: None, + start_binding: None, + end_binding: None, + } +} + +fn centered_label(id: &str, x: f64, y: f64, text: &str, size: f64, color: &str) -> TextElement { + TextElement { + id: id.to_string(), + x, + y, + text: text.to_string(), + font: FontStyle { + family: "Inter, sans-serif".to_string(), + size, + align: TextAlign::Left, + }, + stroke: StrokeStyle { + color: color.to_string(), + width: 1.0, + dash: vec![], + }, + opacity: 1.0, + angle: 0.0, + locked: false, + group_id: None, + } +} diff --git a/examples/gallery/README.md b/examples/gallery/README.md new file mode 100644 index 0000000..5b0a85b --- /dev/null +++ b/examples/gallery/README.md @@ -0,0 +1,47 @@ +# gallery + +Canonical sample drawings showing off the full `draw-core` API surface. Each +drawing is committed as three artifacts: + +- `.draw.json` — the document, in the native `.draw.json` format (this + is what you'd open in the app or CLI). +- `.svg` — exported via `export_svg`. +- `.png` — exported via `export_png_with_scale(&doc, 2.0)` (retina 2x). + +## index + +| drawing | features exercised | +| ----------- | ------------------------------------------------------- | +| flowchart | rectangles, diamond, ellipse, arrows with arrowheads, text labels | +| sticky | solid-fill rectangles with rotation, multiline text labels | +| wireframe | ellipses, dashed strokes, nested frames, diagonal lines | +| sketch | freedraw curves (sine wave + parametric heart), arrow annotation | +| patterns | every `FillType` side by side (Solid, Hachure, CrossHatch, None) | + +## regenerating + +All artifacts are produced by a single Rust example: + +```bash +cargo run --example gallery -p dkdc-draw-core +``` + +It writes into `examples/gallery/` relative to the workspace root. Override +the output directory with `DRAW_GALLERY_OUT=`. + +The Python bindings can round-trip any `.draw.json` in this directory: + +```bash +uv run python -c ' +import json, pathlib, dkdc_draw +doc = pathlib.Path("examples/gallery/flowchart.draw.json").read_text() +pathlib.Path("/tmp/flowchart.svg").write_text(dkdc_draw.export_svg(doc)) +' +``` + +## convention + +If you change any drawing in `crates/draw-core/examples/gallery.rs`, +regenerate the artifacts and commit them together. The committed assets are +the gallery — reviewers use the diff on the `.svg` / `.png` to see exactly +how rendering changed. diff --git a/examples/gallery/flowchart.draw.json b/examples/gallery/flowchart.draw.json new file mode 100644 index 0000000..6212751 --- /dev/null +++ b/examples/gallery/flowchart.draw.json @@ -0,0 +1,316 @@ +{ + "id": "gallery-flowchart", + "version": 1, + "name": "flowchart", + "elements": [ + { + "type": "Ellipse", + "id": "start", + "x": 40.0, + "y": 20.0, + "width": 140.0, + "height": 60.0, + "angle": 0.0, + "stroke": { + "color": "#10b981", + "width": 2.0, + "dash": [] + }, + "fill": { + "color": "#10b981", + "style": "hachure", + "gap": 10.0, + "angle": -0.785 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "start-label", + "x": 70.0, + "y": 42.0, + "text": "start", + "font": { + "family": "Inter, sans-serif", + "size": 18.0, + "align": "left" + }, + "stroke": { + "color": "#064e3b", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Diamond", + "id": "decide", + "x": 40.0, + "y": 140.0, + "width": 140.0, + "height": 100.0, + "angle": 0.0, + "stroke": { + "color": "#f59e0b", + "width": 2.0, + "dash": [] + }, + "fill": { + "color": "#f59e0b", + "style": "hachure", + "gap": 10.0, + "angle": -0.785 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "decide-label", + "x": 75.0, + "y": 180.0, + "text": "valid?", + "font": { + "family": "Inter, sans-serif", + "size": 16.0, + "align": "left" + }, + "stroke": { + "color": "#78350f", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "accept", + "x": 240.0, + "y": 150.0, + "width": 140.0, + "height": 80.0, + "angle": 0.0, + "stroke": { + "color": "#3b82f6", + "width": 2.0, + "dash": [] + }, + "fill": { + "color": "#3b82f6", + "style": "hachure", + "gap": 10.0, + "angle": -0.785 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "accept-label", + "x": 270.0, + "y": 180.0, + "text": "process", + "font": { + "family": "Inter, sans-serif", + "size": 16.0, + "align": "left" + }, + "stroke": { + "color": "#1e3a8a", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "reject", + "x": 40.0, + "y": 300.0, + "width": 140.0, + "height": 80.0, + "angle": 0.0, + "stroke": { + "color": "#ef4444", + "width": 2.0, + "dash": [] + }, + "fill": { + "color": "#ef4444", + "style": "hachure", + "gap": 10.0, + "angle": -0.785 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "reject-label", + "x": 80.0, + "y": 330.0, + "text": "reject", + "font": { + "family": "Inter, sans-serif", + "size": 16.0, + "align": "left" + }, + "stroke": { + "color": "#7f1d1d", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Arrow", + "id": "a1", + "x": 110.0, + "y": 80.0, + "points": [ + { + "x": 0.0, + "y": 0.0 + }, + { + "x": 0.0, + "y": 60.0 + } + ], + "stroke": { + "color": "#334155", + "width": 2.0, + "dash": [] + }, + "start_arrowhead": null, + "end_arrowhead": "arrow", + "opacity": 1.0, + "locked": false, + "group_id": null, + "start_binding": null, + "end_binding": null + }, + { + "type": "Arrow", + "id": "a2", + "x": 180.0, + "y": 190.0, + "points": [ + { + "x": 0.0, + "y": 0.0 + }, + { + "x": 60.0, + "y": 0.0 + } + ], + "stroke": { + "color": "#334155", + "width": 2.0, + "dash": [] + }, + "start_arrowhead": null, + "end_arrowhead": "arrow", + "opacity": 1.0, + "locked": false, + "group_id": null, + "start_binding": null, + "end_binding": null + }, + { + "type": "Arrow", + "id": "a3", + "x": 110.0, + "y": 240.0, + "points": [ + { + "x": 0.0, + "y": 0.0 + }, + { + "x": 0.0, + "y": 60.0 + } + ], + "stroke": { + "color": "#334155", + "width": 2.0, + "dash": [] + }, + "start_arrowhead": null, + "end_arrowhead": "arrow", + "opacity": 1.0, + "locked": false, + "group_id": null, + "start_binding": null, + "end_binding": null + }, + { + "type": "Text", + "id": "yes-label", + "x": 190.0, + "y": 170.0, + "text": "yes", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#52525b", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "no-label", + "x": 120.0, + "y": 260.0, + "text": "no", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#52525b", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + } + ], + "view": { + "scroll_x": 0.0, + "scroll_y": 0.0, + "zoom": 1.0 + }, + "created_at": "2026-01-01T00:00:00+00:00", + "modified_at": "2026-01-01T00:00:00+00:00" +} \ No newline at end of file diff --git a/examples/gallery/flowchart.png b/examples/gallery/flowchart.png new file mode 100644 index 0000000000000000000000000000000000000000..384aeec088c18eda0c087f018733f832dfd6eb26 GIT binary patch literal 20428 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 literal 0 HcmV?d00001 diff --git a/examples/gallery/flowchart.svg b/examples/gallery/flowchart.svg new file mode 100644 index 0000000..762ef0c --- /dev/null +++ b/examples/gallery/flowchart.svg @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + start + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + valid? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + process + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + reject + + + + yes + no + \ No newline at end of file diff --git a/examples/gallery/patterns.draw.json b/examples/gallery/patterns.draw.json new file mode 100644 index 0000000..9b1d671 --- /dev/null +++ b/examples/gallery/patterns.draw.json @@ -0,0 +1,190 @@ +{ + "id": "gallery-patterns", + "version": 1, + "name": "fill-patterns", + "elements": [ + { + "type": "Rectangle", + "id": "solid-rect", + "x": 20.0, + "y": 40.0, + "width": 160.0, + "height": 120.0, + "angle": 0.0, + "stroke": { + "color": "#ec4899", + "width": 2.0, + "dash": [] + }, + "fill": { + "color": "#ec4899", + "style": "solid", + "gap": 8.0, + "angle": -0.785 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "solid-label", + "x": 30.0, + "y": 180.0, + "text": "Solid", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#1f2937", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "hachure-rect", + "x": 200.0, + "y": 40.0, + "width": 160.0, + "height": 120.0, + "angle": 0.0, + "stroke": { + "color": "#ec4899", + "width": 2.0, + "dash": [] + }, + "fill": { + "color": "#ec4899", + "style": "hachure", + "gap": 8.0, + "angle": -0.785 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "hachure-label", + "x": 210.0, + "y": 180.0, + "text": "Hachure", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#1f2937", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "crosshatch-rect", + "x": 380.0, + "y": 40.0, + "width": 160.0, + "height": 120.0, + "angle": 0.0, + "stroke": { + "color": "#ec4899", + "width": 2.0, + "dash": [] + }, + "fill": { + "color": "#ec4899", + "style": "crosshatch", + "gap": 8.0, + "angle": -0.785 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "crosshatch-label", + "x": 390.0, + "y": 180.0, + "text": "CrossHatch", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#1f2937", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "none-rect", + "x": 560.0, + "y": 40.0, + "width": 160.0, + "height": 120.0, + "angle": 0.0, + "stroke": { + "color": "#ec4899", + "width": 2.0, + "dash": [] + }, + "fill": { + "color": "#ec4899", + "style": "none", + "gap": 8.0, + "angle": -0.785 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "none-label", + "x": 570.0, + "y": 180.0, + "text": "None", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#1f2937", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + } + ], + "view": { + "scroll_x": 0.0, + "scroll_y": 0.0, + "zoom": 1.0 + }, + "created_at": "2026-01-01T00:00:00+00:00", + "modified_at": "2026-01-01T00:00:00+00:00" +} \ No newline at end of file diff --git a/examples/gallery/patterns.png b/examples/gallery/patterns.png new file mode 100644 index 0000000000000000000000000000000000000000..3187862b9f02b0d1576b06378398040073971191 GIT binary patch literal 28707 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 W?|(B*pD@Bt<>^lUuzd6Gum1;sJ<8nx literal 0 HcmV?d00001 diff --git a/examples/gallery/patterns.svg b/examples/gallery/patterns.svg new file mode 100644 index 0000000..baef53f --- /dev/null +++ b/examples/gallery/patterns.svg @@ -0,0 +1,172 @@ + + + + + + + + + + + Solid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hachure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CrossHatch + + None + \ No newline at end of file diff --git a/examples/gallery/sketch.draw.json b/examples/gallery/sketch.draw.json new file mode 100644 index 0000000..020f9ac --- /dev/null +++ b/examples/gallery/sketch.draw.json @@ -0,0 +1,612 @@ +{ + "id": "gallery-sketch", + "version": 1, + "name": "sketch", + "elements": [ + { + "type": "FreeDraw", + "id": "wave", + "x": 30.0, + "y": 80.0, + "points": [ + { + "x": 0.0, + "y": 0.0 + }, + { + "x": 6.0, + "y": 11.820808266453582 + }, + { + "x": 12.0, + "y": 22.585698935801414 + }, + { + "x": 18.0, + "y": 31.333076385099332 + }, + { + "x": 24.0, + "y": 37.28156343868905 + }, + { + "x": 30.0, + "y": 39.89979946416218 + }, + { + "x": 36.0, + "y": 38.95390523512781 + }, + { + "x": 42.0, + "y": 34.52837466595495 + }, + { + "x": 48.0, + "y": 27.018527222046043 + }, + { + "x": 54.0, + "y": 17.095195209353207 + }, + { + "x": 60.0, + "y": 5.644800322394689 + }, + { + "x": 66.0, + "y": -6.309827765729929 + }, + { + "x": 72.0, + "y": -17.700817731794082 + }, + { + "x": 78.0, + "y": -27.51064636735895 + }, + { + "x": 84.0, + "y": -34.86303089654353 + }, + { + "x": 90.0, + "y": -39.10120470660388 + }, + { + "x": 96.0, + "y": -39.84658435343363 + }, + { + "x": 102.0, + "y": -37.0325872931093 + }, + { + "x": 108.0, + "y": -30.910579502239507 + }, + { + "x": 114.0, + "y": -22.027421703905503 + }, + { + "x": 120.0, + "y": -11.176619927957034 + }, + { + "x": 126.0, + "y": 0.6725560193739886 + }, + { + "x": 132.0, + "y": 12.461654540535115 + }, + { + "x": 138.0, + "y": 23.137590575527977 + }, + { + "x": 144.0, + "y": 31.746714553966108 + }, + { + "x": 150.0, + "y": 37.519999070989556 + }, + { + "x": 156.0, + "y": 39.9417338149842 + }, + { + "x": 162.0, + "y": 38.79559243380345 + }, + { + "x": 168.0, + "y": 34.18395632353122 + }, + { + "x": 174.0, + "y": 26.518769203287334 + }, + { + "x": 180.0, + "y": 16.484739409670265 + }, + { + "x": 186.0, + "y": 4.978176940282538 + }, + { + "x": 192.0, + "y": -6.973071248919186 + }, + { + "x": 198.0, + "y": -18.301435751012857 + }, + { + "x": 204.0, + "y": -27.994987503741694 + }, + { + "x": 210.0, + "y": -35.18783039886681 + }, + { + "x": 216.0, + "y": -39.23744920265965 + }, + { + "x": 222.0, + "y": -39.78210352815957 + }, + { + "x": 228.0, + "y": -36.77314102658703 + }, + { + "x": 234.0, + "y": -30.479343356761333 + }, + { + "x": 240.0, + "y": -21.462916720017397 + }, + { + "x": 246.0, + "y": -10.529271654632106 + }, + { + "x": 252.0, + "y": 1.3449218888454677 + }, + { + "x": 258.0, + "y": 13.09897756550772 + }, + { + "x": 264.0, + "y": 23.682940588288922 + }, + { + "x": 270.0, + "y": 32.15137706206483 + }, + { + "x": 276.0, + "y": 37.74782677776417 + }, + { + "x": 282.0, + "y": 39.972375549916705 + }, + { + "x": 288.0, + "y": 38.62631106197112 + }, + { + "x": 294.0, + "y": 33.82987324571737 + }, + { + "x": 300.0, + "y": 26.011513606284673 + }, + { + "x": 306.0, + "y": 15.869622925224547 + }, + { + "x": 312.0, + "y": 4.310146091977762 + }, + { + "x": 318.0, + "y": -7.634343254967506 + }, + { + "x": 324.0, + "y": -18.896879455938645 + }, + { + "x": 330.0, + "y": -28.471413694764923 + }, + { + "x": 336.0, + "y": -35.502681343260186 + }, + { + "x": 342.0, + "y": -39.36260020326571 + }, + { + "x": 348.0, + "y": -39.70637521882533 + }, + { + "x": 354.0, + "y": -36.50329799164738 + } + ], + "stroke": { + "color": "#8b5cf6", + "width": 2.5, + "dash": [] + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "FreeDraw", + "id": "heart", + "x": 250.0, + "y": 180.0, + "points": [ + { + "x": 0.0, + "y": -15.0 + }, + { + "x": 0.045200924810633265, + "y": -15.587144522240113 + }, + { + "x": 0.3564087963453909, + "y": -17.28229493068177 + }, + { + "x": 1.174122940807801, + "y": -19.89421890694082 + }, + { + "x": 2.690049175007791, + "y": -23.12859945595143 + }, + { + "x": 5.028065805669552, + "y": -26.621323269409714 + }, + { + "x": 8.231105023866913, + "y": -29.978925669979326 + }, + { + "x": 12.255103057710976, + "y": -32.821071869394665 + }, + { + "x": 16.970562748477136, + "y": -34.81980515339464 + }, + { + "x": 22.171615479146563, + "y": -35.73085909624809 + }, + { + "x": 27.59182217869809, + "y": -35.41352259921986 + }, + { + "x": 32.9253712004955, + "y": -33.83718488863639 + }, + { + "x": 37.8518643587874, + "y": -31.074532775104437 + }, + { + "x": 42.06257149632326, + "y": -27.283159020543362 + }, + { + "x": 45.28590544214684, + "y": -22.67881660085628 + }, + { + "x": 47.309934188985586, + "y": -17.504517144894248 + }, + { + "x": 48.0, + "y": -12.000000000000004 + }, + { + "x": 47.3099341889856, + "y": -6.375764072134954 + }, + { + "x": 45.28590544214684, + "y": -0.7949286873630488 + }, + { + "x": 42.06257149632327, + "y": 4.635171245657534 + }, + { + "x": 37.8518643587874, + "y": 9.861329339508005 + }, + { + "x": 32.92537120049551, + "y": 14.873977303857792 + }, + { + "x": 27.591822178698095, + "y": 19.690378941147863 + }, + { + "x": 22.171615479146567, + "y": 24.334872240696512 + }, + { + "x": 16.970562748477143, + "y": 28.819805153394633 + }, + { + "x": 12.255103057710976, + "y": 33.130504334810794 + }, + { + "x": 8.231105023866913, + "y": 37.21678795381274 + }, + { + "x": 5.028065805669559, + "y": 40.99232966580723 + }, + { + "x": 2.6900491750077933, + "y": 44.34180289154786 + }, + { + "x": 1.174122940807802, + "y": 47.134407870207724 + }, + { + "x": 0.35640879634539285, + "y": 49.24132159313966 + }, + { + "x": 0.04520092481063357, + "y": 50.553980129404735 + }, + { + "x": 8.816044883168516e-47, + "y": 51.0 + }, + { + "x": -0.045200924810633245, + "y": 50.55398012940475 + }, + { + "x": -0.35640879634539147, + "y": 49.24132159313965 + }, + { + "x": -1.1741229408077984, + "y": 47.13440787020773 + }, + { + "x": -2.690049175007789, + "y": 44.34180289154786 + }, + { + "x": -5.028065805669552, + "y": 40.99232966580725 + }, + { + "x": -8.231105023866903, + "y": 37.216787953812755 + }, + { + "x": -12.255103057710965, + "y": 33.1305043348108 + }, + { + "x": -16.970562748477136, + "y": 28.81980515339464 + }, + { + "x": -22.17161547914653, + "y": 24.334872240696537 + }, + { + "x": -27.59182217869809, + "y": 19.69037894114788 + }, + { + "x": -32.9253712004955, + "y": 14.873977303857803 + }, + { + "x": -37.85186435878737, + "y": 9.86132933950804 + }, + { + "x": -42.06257149632326, + "y": 4.6351712456575465 + }, + { + "x": -45.28590544214683, + "y": -0.7949286873630245 + }, + { + "x": -47.3099341889856, + "y": -6.3757640721349675 + }, + { + "x": -48.0, + "y": -11.99999999999999 + }, + { + "x": -47.3099341889856, + "y": -17.5045171448942 + }, + { + "x": -45.28590544214684, + "y": -22.678816600856273 + }, + { + "x": -42.06257149632327, + "y": -27.283159020543344 + }, + { + "x": -37.85186435878738, + "y": -31.074532775104444 + }, + { + "x": -32.92537120049551, + "y": -33.837184888636386 + }, + { + "x": -27.591822178698113, + "y": -35.41352259921985 + }, + { + "x": -22.17161547914655, + "y": -35.73085909624808 + }, + { + "x": -16.970562748477153, + "y": -34.81980515339463 + }, + { + "x": -12.255103057711002, + "y": -32.82107186939467 + }, + { + "x": -8.231105023866913, + "y": -29.978925669979333 + }, + { + "x": -5.028065805669563, + "y": -26.621323269409714 + }, + { + "x": -2.690049175007804, + "y": -23.128599455951466 + }, + { + "x": -1.174122940807803, + "y": -19.89421890694083 + }, + { + "x": -0.3564087963453934, + "y": -17.282294930681783 + }, + { + "x": -0.04520092481063313, + "y": -15.58714452224011 + } + ], + "stroke": { + "color": "#e11d48", + "width": 3.0, + "dash": [] + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Arrow", + "id": "ann-arrow", + "x": 100.0, + "y": 220.0, + "points": [ + { + "x": 0.0, + "y": 0.0 + }, + { + "x": 120.0, + "y": -20.0 + } + ], + "stroke": { + "color": "#334155", + "width": 2.0, + "dash": [] + }, + "start_arrowhead": null, + "end_arrowhead": "arrow", + "opacity": 1.0, + "locked": false, + "group_id": null, + "start_binding": null, + "end_binding": null + }, + { + "type": "Text", + "id": "ann-text", + "x": 20.0, + "y": 220.0, + "text": "freedraw!", + "font": { + "family": "Inter, sans-serif", + "size": 16.0, + "align": "left" + }, + "stroke": { + "color": "#334155", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "wave-text", + "x": 30.0, + "y": 40.0, + "text": "sin wave via FreeDraw", + "font": { + "family": "Inter, sans-serif", + "size": 16.0, + "align": "left" + }, + "stroke": { + "color": "#334155", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + } + ], + "view": { + "scroll_x": 0.0, + "scroll_y": 0.0, + "zoom": 1.0 + }, + "created_at": "2026-01-01T00:00:00+00:00", + "modified_at": "2026-01-01T00:00:00+00:00" +} \ No newline at end of file diff --git a/examples/gallery/sketch.png b/examples/gallery/sketch.png new file mode 100644 index 0000000000000000000000000000000000000000..c66e26fe48b39658712ae7bb5bfc4eaaa5a29b9a GIT binary patch literal 16136 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 + + + + freedraw! + sin wave via FreeDraw + \ No newline at end of file diff --git a/examples/gallery/sticky.draw.json b/examples/gallery/sticky.draw.json new file mode 100644 index 0000000..b068cc0 --- /dev/null +++ b/examples/gallery/sticky.draw.json @@ -0,0 +1,234 @@ +{ + "id": "gallery-sticky", + "version": 1, + "name": "sticky-notes", + "elements": [ + { + "type": "Rectangle", + "id": "note1-bg", + "x": 20.0, + "y": 20.0, + "width": 160.0, + "height": 160.0, + "angle": -0.03, + "stroke": { + "color": "#78350f", + "width": 1.5, + "dash": [] + }, + "fill": { + "color": "#fef3c7", + "style": "solid", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "note1-text", + "x": 32.0, + "y": 36.0, + "text": "shopping\n bread\n butter", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#78350f", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "note2-bg", + "x": 200.0, + "y": 20.0, + "width": 160.0, + "height": 160.0, + "angle": 0.03, + "stroke": { + "color": "#1e3a8a", + "width": 1.5, + "dash": [] + }, + "fill": { + "color": "#dbeafe", + "style": "solid", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "note2-text", + "x": 212.0, + "y": 36.0, + "text": "read list\n Rust book\n SICP", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#1e3a8a", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "note3-bg", + "x": 380.0, + "y": 20.0, + "width": 160.0, + "height": 160.0, + "angle": -0.03, + "stroke": { + "color": "#831843", + "width": 1.5, + "dash": [] + }, + "fill": { + "color": "#fce7f3", + "style": "solid", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "note3-text", + "x": 392.0, + "y": 36.0, + "text": "weekend\n hike\n cook", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#831843", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "note4-bg", + "x": 110.0, + "y": 220.0, + "width": 160.0, + "height": 160.0, + "angle": 0.03, + "stroke": { + "color": "#064e3b", + "width": 1.5, + "dash": [] + }, + "fill": { + "color": "#d1fae5", + "style": "solid", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "note4-text", + "x": 122.0, + "y": 236.0, + "text": "ideas\n draw gallery\n lint pass", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#064e3b", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "note5-bg", + "x": 290.0, + "y": 220.0, + "width": 160.0, + "height": 160.0, + "angle": -0.03, + "stroke": { + "color": "#7f1d1d", + "width": 1.5, + "dash": [] + }, + "fill": { + "color": "#fee2e2", + "style": "solid", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "note5-text", + "x": 302.0, + "y": 236.0, + "text": "blockers\n pixmap panic\n hachure opacity", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#7f1d1d", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + } + ], + "view": { + "scroll_x": 0.0, + "scroll_y": 0.0, + "zoom": 1.0 + }, + "created_at": "2026-01-01T00:00:00+00:00", + "modified_at": "2026-01-01T00:00:00+00:00" +} \ No newline at end of file diff --git a/examples/gallery/sticky.png b/examples/gallery/sticky.png new file mode 100644 index 0000000000000000000000000000000000000000..13236524b2fa57546acb2e814c4fd92db36ec3d1 GIT binary patch literal 5944 zcmeAS@N?(olHy`uVBq!ia0y~yU`b$LU{>H@1B&E)6!B(Y5c}`x;uumf=k4wNT`8$d z?GM*?bDG^Lm|U7}en+w-);vN?ocjxtj|lTY2Z4MC13^*YgVG=Pxw;N6=oDWN(5u9D z?#AU3=`3rtwl=rrYqJFQG;X(eS}FHq*SiPTpYNHIZwj=D!DB7RLIzDn5T&4Y07x<% zVUYlm4Ko;+fs}x70+3`#V&Vak4n7TxK&pe&03>EOWHs$JOM7+n|G!GxWgky4L~Och zQTKQCeDh?tb@qRbRc$(d;LzG_ziU1o<(_S*y#DXgjbFbVw`2A$|8vQIe%>*jvt|c! z9-pp?iQ8wxFfGTbs`Picaazi(WnU%Szez@H$=o&Pu3i4x;~)O;=-mA?Z+`t_E+J6R z;&@Z&3a5?ny~a{r3`?Cic94Utclk9K5?(e*HPc9q$*;e!TDh z9T^7ig7>dC7ry^?bH?sw4ujo4KD{vZ=Wj^;@nHee8Dsyy`9Lkdx9_u=!?3wm|9}0z zm5du$=I^h3t-#cy?J=|?XgDUiQ9lji5@j@UM`Cy-u&M4_jj2xOiS2a+5SL*=}fu$@^JQsO3R!3 zdgjPn;O0CKbm#Z4H?J5BhF9ZqG?U=TB(c&C@BI7i_sj#gyf<9UlKKDXq&TRZ z9kYL*-7K}=JUX|YW|jUqb@XT&s8{kWK|220?fP7O{sz|B%NB=kx1VjOetFA0y|>02 za&8OQ?yqgLSAeFXeGQGazrXj^_qFHUkpMQHfB&huWBI>KrfI+Jj|V&U*#d<=o;82m z{+98ZJ5cHEqtl)*|(x4!rD#bao**?!KPp2y$s zNi%qFxNVcLpGD%v=G)JogWAnIcN9N=#lRo}ZCw0h0=96rO3lu+l49`QcKeW_`Rc3B qGG?(ISd_cHfWb1qW#qP!ziHP#KYNw`k5dXL3_M-^T-G@yGywpWOun!H literal 0 HcmV?d00001 diff --git a/examples/gallery/sticky.svg b/examples/gallery/sticky.svg new file mode 100644 index 0000000..e44fedb --- /dev/null +++ b/examples/gallery/sticky.svg @@ -0,0 +1,22 @@ + + + shopping + bread + butter + + read list + Rust book + SICP + + weekend + hike + cook + + ideas + draw gallery + lint pass + + blockers + pixmap panic + hachure opacity + \ No newline at end of file diff --git a/examples/gallery/wireframe.draw.json b/examples/gallery/wireframe.draw.json new file mode 100644 index 0000000..4a53829 --- /dev/null +++ b/examples/gallery/wireframe.draw.json @@ -0,0 +1,318 @@ +{ + "id": "gallery-wireframe", + "version": 1, + "name": "wireframe", + "elements": [ + { + "type": "Rectangle", + "id": "frame", + "x": 20.0, + "y": 20.0, + "width": 440.0, + "height": 320.0, + "angle": 0.0, + "stroke": { + "color": "#334155", + "width": 2.5, + "dash": [] + }, + "fill": { + "color": "#334155", + "style": "none", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "titlebar", + "x": 20.0, + "y": 20.0, + "width": 440.0, + "height": 40.0, + "angle": 0.0, + "stroke": { + "color": "#334155", + "width": 1.5, + "dash": [] + }, + "fill": { + "color": "#334155", + "style": "none", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "title", + "x": 34.0, + "y": 34.0, + "text": "~/draw", + "font": { + "family": "Inter, sans-serif", + "size": 14.0, + "align": "left" + }, + "stroke": { + "color": "#334155", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "content", + "x": 40.0, + "y": 80.0, + "width": 400.0, + "height": 240.0, + "angle": 0.0, + "stroke": { + "color": "#64748b", + "width": 1.0, + "dash": [ + 6.0, + 4.0 + ] + }, + "fill": { + "color": "#64748b", + "style": "none", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Ellipse", + "id": "btn1", + "x": 60.0, + "y": 100.0, + "width": 80.0, + "height": 32.0, + "angle": 0.0, + "stroke": { + "color": "#0ea5e9", + "width": 1.5, + "dash": [] + }, + "fill": { + "color": "#0ea5e9", + "style": "none", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "btn1-label", + "x": 80.0, + "y": 110.0, + "text": "open", + "font": { + "family": "Inter, sans-serif", + "size": 12.0, + "align": "left" + }, + "stroke": { + "color": "#0c4a6e", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Ellipse", + "id": "btn2", + "x": 160.0, + "y": 100.0, + "width": 80.0, + "height": 32.0, + "angle": 0.0, + "stroke": { + "color": "#0ea5e9", + "width": 1.5, + "dash": [] + }, + "fill": { + "color": "#0ea5e9", + "style": "none", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "btn2-label", + "x": 180.0, + "y": 110.0, + "text": "save", + "font": { + "family": "Inter, sans-serif", + "size": 12.0, + "align": "left" + }, + "stroke": { + "color": "#0c4a6e", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Ellipse", + "id": "btn3", + "x": 260.0, + "y": 100.0, + "width": 80.0, + "height": 32.0, + "angle": 0.0, + "stroke": { + "color": "#0ea5e9", + "width": 1.5, + "dash": [] + }, + "fill": { + "color": "#0ea5e9", + "style": "none", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Text", + "id": "btn3-label", + "x": 280.0, + "y": 110.0, + "text": "export", + "font": { + "family": "Inter, sans-serif", + "size": 12.0, + "align": "left" + }, + "stroke": { + "color": "#0c4a6e", + "width": 1.0, + "dash": [] + }, + "opacity": 1.0, + "angle": 0.0, + "locked": false, + "group_id": null + }, + { + "type": "Rectangle", + "id": "canvas", + "x": 60.0, + "y": 160.0, + "width": 380.0, + "height": 140.0, + "angle": 0.0, + "stroke": { + "color": "#94a3b8", + "width": 1.0, + "dash": [] + }, + "fill": { + "color": "#94a3b8", + "style": "none", + "gap": 10.0, + "angle": 0.0 + }, + "opacity": 1.0, + "locked": false, + "group_id": null + }, + { + "type": "Line", + "id": "d1", + "x": 60.0, + "y": 160.0, + "points": [ + { + "x": 0.0, + "y": 0.0 + }, + { + "x": 380.0, + "y": 140.0 + } + ], + "stroke": { + "color": "#a855f7", + "width": 2.0, + "dash": [] + }, + "start_arrowhead": null, + "end_arrowhead": null, + "opacity": 1.0, + "locked": false, + "group_id": null, + "start_binding": null, + "end_binding": null + }, + { + "type": "Line", + "id": "d2", + "x": 440.0, + "y": 160.0, + "points": [ + { + "x": 0.0, + "y": 0.0 + }, + { + "x": -380.0, + "y": 140.0 + } + ], + "stroke": { + "color": "#a855f7", + "width": 2.0, + "dash": [] + }, + "start_arrowhead": null, + "end_arrowhead": null, + "opacity": 1.0, + "locked": false, + "group_id": null, + "start_binding": null, + "end_binding": null + } + ], + "view": { + "scroll_x": 0.0, + "scroll_y": 0.0, + "zoom": 1.0 + }, + "created_at": "2026-01-01T00:00:00+00:00", + "modified_at": "2026-01-01T00:00:00+00:00" +} \ No newline at end of file diff --git a/examples/gallery/wireframe.png b/examples/gallery/wireframe.png new file mode 100644 index 0000000000000000000000000000000000000000..a00ada7d4fd79e393f785e96bd770fec2a845513 GIT binary patch literal 12135 zcmdsddpwlu*Y`ar?3Admqew?`n2w0hjuaw{^JzMmq%?{gW(HI3Ql`Q#awd|-IXNZ6 z*d4STLK@21E~k-0rlG;iJnOdi{``K=^ZUK;=Xu`u{pa2P@L6-O)3vU3t@XXuecwE0 zWw!i>wLc&Tvi!(l6I%okWg>`3i^Nj+#WU!~5(LrKI%0AF7o0rabM%J$Y0}oIS+|Db z{1pxf54%=u3OGs#Ts5+GD$jk#Q;Ei3+1Fyrj`}Srk#fSE8J~8!@+LZq$&xBwH=8l& zE_X1yN|CTuyc}5efxgBVVwJ@d5)|7?0nTQkDu;lUvA!UkrhJe z6Y}O}Wx*4%77O^hl8Q(nh_QqS7KjZJO!)h=3L***uKa)hz)!Wbg(e&nAEWn$t6>l% zI?ZG9Zkk|Ly4*A|ZJr6E^L?Sush}?DU9buu|na|5{-ymM+F%V*l>_ zX8-#XsC7@5JUv+FS@2N5k22X`7?;a0cH2L)osm(IV9O7rp0=Wtarnc5vZvlsnBGI8 zD&Zp7kFL*VIHkDN*%5Nexu!M@?KYo;NS0YmO*mQSXa;G5oic-GTk#xJHm ziDh&?&g<#avygBZTwHlz@AtR2#rz3|_}v`L$|%HO$$mH5lWBprM^4{tgMh@Zo_pZ9 zEbijw*jFy4$xfpkw8-g!Yo?|6!@eyV={eWl_j3kJk1_h`f;fFvc*AXWjaN3Q#JS~L zWN#opY?w<7|D|6ds>#e|?_`sBFdwNLh5$DSB|b<0Cdn+R@9q8mjS*IQSgrmdBXO$K zXJEQzcDJ3;E0L_ZyX84sb^RtooF|WKw>8Ox`;{tOAh~j1k#=0#cbo1(wHLnQ@~xZS z3l$b-C$#;}H_n4@fW|IH+&P@AY5S`f*!ROk7~_`_`LZtr>8Y>SA`e0re{ zt)j4+E~mzi4Rh9Xuzh0tFf8hWDuaXj8_QG`gHt?$x@6fT=54hK#&!@{^j@F>YCq z$NWqnyNJK!!;B!iBcf)nQ}?&|rqZ*l4DEd7fW&UWZI``s^K}M^OOCca@7>-etx4!? zqA0zkyd)T|IBke**@*1&kJ5Che<7zaxRsmA^RPLe{rl(JDIKZL6TGD~&zFfuj>Av*>8?U9rxGs{mD93YR!)Gv(2uJB~iTi<($@|Lnrq(iZi`Mu)8iKn_4^# zsy^I7<}Np>_nEAcfB$4$dx~8?PoAGWzwpj;GW$gSwzh-;#)90galE123(Du;C>qy! z$OrJ}$yYhe(Yd!{&7Rhc-GURJ1y@cD^0jmN%=Vq!@ziZ7b6umEenM0rR@vA@WNNkQ zjNf=q2xrW(zPRR0>pQD?BhJupSobSSn{RvDc3Fpgxs%?uLwWqPVO$-hB#notuB7V4 z&2>Hq<8fUF$9rxMS^XlX>8v|(;*mx}{_isrZ=X&IDY=dYyn?f%uTL6IpZe1~#Cfl6U|+F~a6>E^SXiZ@`NF*;lvK*BBdCpTiOh&k`l zS}c$5@Sw5V^6i=)mAQhR_L@&~LGHr0%Dia;!}opH;UDV%%#{@ni9SM==m9O=Fv4IGHOxMg<4gY?Ws@w(%NjpCW6 zDbndZV*OOMVr+8t%&TlxgyPk@NY&7Gi&x=uT*YSMmSB@m&M&Z!JfYPWeWKf_LTYBs(1WRJp7rewZs? z@TI4JQ%{ajVR=5mI_x@4fm558Agz#Z!NJf1Cw|Q;4zsYdL{?;wj)xF~n?r3LF4s{% z?$Wf}DpI^dq`k{YI5Xjy-54)DREru-kXA7g@k6#xhKE&?j_d9EUS;U(JIoy}+!}wG zk?W^VWD+OykBzmYJ)R1Z*$z0cwTtM@fKDk-(D%aiyhZkMt~B!ufC ztlU$?F9Isfpbyu4dZ?e1G5hk+6+Qa%^~$}k>&p{;i?SNUX{QYFCEY`!Z<3I1ufd-A z4qtn+gVBXH*`fSr^dqJDJ>ht7#dGU8wO1eG+wuqIS{Y<8r*K86zxV=j5nk%NHSBn zP3->6p>L&=M&)<$n8dTsu3Mi}?3C+zK`Bx~*-jc#yHgGa*nM=|V60zq!F9KC$u%%@ zj>c5d73sF&PuGr~^-?cerO`6ASEov|>>3ZB0D)?s;UUwD7{_>Nfk(B|rboX^_?dAd z15Qp|h^gm_E!$ny#3eDt-|rl{>a|lD>(VtW`X&wOwl{4)YJp$P!DJ>#Bl|3oiM&-h z;>xk~;4=U`Dr3Z!*$~fi_s;gmh51S{9{N~4Y zNy;7fcFirXCAnH^o-}k?3o5;!xC+GjS8TU^w�=#vE~r6LrAXH(2qUBByp|JVf_C zcn#5Y{`#I<8{y@gS4f$namD;`L^un9!pjeQi!L>aF9SGIk_Q`>h;$z(XRlZ@xx!F; zezwbw(Fnn2EPW%guCRUe+j~=Hs19hg3NA=PR8nAV)*}Mgu>V7+$)}rW_6ttH z?dOmbbIVKb{m7{`0I%}5!(z&w;uEo*uJNyLKsI` zT@G()@^JQ1TQsm`3iWFE_GGvB%x7uM6Y7fDg`r>1l$r%<)^1??KJ0bKk=9unWlx?k zIy2Ml*yT{4%)_sR#2uT1le&^BnloG6OXBlN2NaX;spZ>~PZ{OUG*;)2#h9_WOcq-K zBfnetQ|a;^(x+=yDP;shh5KlgmOBF#L4s-bh7mE=QD{`ty1l+;?KgvoE)8NI|NU|+ z1@H4XTi;czh@`7^iK(xBy6EK(-rEda{e)E>y4srYR=nlBh@ao39^+R1XAWuCcL(Qb zWfl^xEq%JnhEH<^*u=4trOWR7C@L5jemL8gTeJ_z$%GU#csKsSP1NM%!`M?o!s?{$ z%9Lj;?I0bygkB{|ewfYn2Ep9u*|a{5$X)_z=(nhzDpg|B^0N~ZzB!lN^CkPlp8X_K za!lBV?N;YIPuA4^bANW!_SBHIlcDOh2$r=UDvka~Ik(|%B&%4XJhJP#ezV8??}?5DS$ z)4FcxSkP`q@yg&6*hmvD)%KU5YEwe#WbPN*m&A>+)(bfFnj|)3f%Srwv888Z z4P*RUFF$g=>T+tSyx?RWHU6!8#rx!<)-uEXJ)O&MH>%XuojGsLZTI;4xbP!fJuUlT>9LghTuhHl3Il($H z++OV2I4kG-YLtIAk8SteftHacr+KDy;zi!fo5}EDf$+tOkGpDw?&UeeN}JH;z(U7$ z1Hp4IZ$CEEt$YpFtV6q$heYuXV)!_M@LCXAnBQ8}5BF4`kYWc$OQB=oaGyRqgniAJ zp`t)BtT>eCpzXKbUQ;AziB(AduY@8AbMY=$iqd!Sq*opFnbOii_v?anqZI)4974lO zgQ{J=b5pwmZ@VfDeexdKsKvPew_`snCf*?=Ks(%b@TzBC$K=?4{=9Vlnx@{VtFzyg zvz)zmcig#6Ony~&L25jvP~N{OJm~3ZLsm~hU#}JRx$uBh$Gi1=xvY8C%-ah z-_K>O=dgV2i(Kw;=T8jknju5|k$;(4_oGuGs#L{n%Qq#=e}8t?B8rYO5QLeL{M?V8*VnM&uD{s-U>f)TXv$Qv zETArQik)&>HS0cU^kz`AVC3^{*ojkvnT|mnbECbkUgl|4GnuE$WLK@C>VF{$y82>g z4S7fRT^I|p-Gf!^=v~+u$~6-vPE=3jzBl69!<4F%9yCAt?fq~~QmP=^=rF%)HnDCZ zMbLDp^mX{8ws66kwc+StfJ0s63Uz6j@{?29x#^u9k&#B@Dg zdj8Xkr$Of(4hAv4teLEy8Q7%*GkUDN@r zz1tjNOp7fg`MMWA87mEB3>9?rHm#-H^BMPe^i7`YQti>UtftLhO!vklag9Ff`F5`B z)ACV|km|gdz4r6*)#RzPZS6TGl)(vNP382!$F2^$uP@{59%2wYrAh7WAmg@=Zzy3h zrqzs;%bb6jVYr=h^Y{9*%BhtJ@oE^|bbeb>a|bb;JTs9H#!geK$4P%06%`sgCm-<= zT9#(#Jo9kM0uPnWA9H{2*3mUts1!!>ue6sNC9$*eee$bQ;uSHv6-M(T^fA`+G47~> z=lFPx59zA3V1hArZOlAth%``KVQXj;_JwpDK|a(J%zAt^SFh*rr+d-(d3Lxuh{vZ2 z+S@v2n?Ia-D~4Y?FIsM2v*)VoIl@V~FA?xP` zbTq=p^gK2yfQdo_SrKKu;r$CcBP004Sz+-(7r_cJfl!+4EkF_FA5yB8o9fPzGa%jKHQWLhvnj=WbHMXi4 zUhWVoPJ2{&<2cL`AD|K;HtsLC0g;6g&Oq2eWj~0reX@0bsVmm6=Ek5rIox>7d^3U^ zg69w6`JxwlSf-6v5X-%&t(TdVH=Jduzx@JS)Cmx*l3<>)1}(+{oct4bdzXS5Q!&b+ zIc>tkTiMdZkQNUhD3g0u)Z^x0gsOGS-9$_T+ZYYLt8C4o-8h6?*RuhqN@Ccms=)OY74c24 zlo0EuF9rj<6qGYn=~`DJB$-8H||$@Dn^ii7b>5w{aJ{ zyNbF&6xdYuvP`jxBG^+V5T}n_IGh*%GxQ)UMD8|LXbl%JSG z!|xEokE{gi(b6o_qF@nhz&-HVdn*voD#g%+#zD+Vt|C&kk3rKbFh0H!Id}}@@rF40 z17t}i6(r(+VXM9YCIi&AQWOwTh?LS{APlT7$-pgi>79qRlGG89(2-y=oxt~%kU=Bs zF$(eVAYXatl1xAnVsH(8Ya)4!E@nIftWqLatQ}Z-1FSd9kflRty7^{SN}O0lz5ERJ znYoj$C9+fukADCPi?-#^sAiH(<~#5d-;R^VM95GtJAg9gLm=ulBcl0`;Z(>&v0D~M z>aWk1-B0Y=IwE?Vhw@|sPuoiBWet$N0c*x?7r{oafza%DtR*r8FQ|$krPIm4#o2_2 zra)*^Ji(wT*mw)O+Y$zZ7s$VbWNa}+30dmpmEe~Kgd<&346mXD$~f7T65bmz3KAAs zvdnYfeWw+&^f0XP(Su5fH`frUDGA^*vvU{w487`CR)aiEEFjT*GgTZ6KU+n`+&PdX zyD|YZZWqH-cfjtd!j=TYAX3;RvF672!PiRA7>@y>07PN{UhH~gEBWR!TUA9Bqkz8y z9{ECY+yE{o*m>qo5D7N^GaTDbeZz=;f^cYDVXIQ>B$>|6Y1mMIbe#$A5OlEq2_ zQMVNlExBdBxgTPVj=e=oU>EW6ATtUE6F|WZSX(^2=u$bT9-H0;o-{@vQqenN&091< zWXJ+pIsz+=hD<|fARhpy#ajo6h7CY~GH*yoqyfx`!AXxhcuB^X4A!-$L1+7-%zKr`=np}U1Q>{aki00e@-}>9;os73j^>le7T`)Q zeXuA3#)^*7@^El85ot&?{E;CWR2(^k62B2_=ln??nh@;Wa#y;$O=fNF-9MjsS4S2J_!(VjSMOOvfH3dd3| zU1|-EH79p0b370_`OjDdUQ38FmEIrlpPg|myyuluIOYxj!|>s)$9k`xIWB3+s0#a_ z_O`84`n@aHbyZ_-X0=z1fzkBpQ9u~e<`=Zxc~dO?F4LGYt5nk5$&O{B(HsBlesMeJ zs2Bx>r_c1uePEvyI!;Tsoy9Q5MOd&j$`0Fgw{9P1A5n8WJAb>j>GbrmhPKPgBQm8#?%S|=PxL=QM6=*#|;E0YB(v*Aw)P1BL8!+%l0 zIkJgmI*uCfl7Z97oqnZ?X<3^?n+BvpH3iu>iR_H?Xen2PbUJn9l1&hlN4y7YT=9d- zZR^38#$VtB<H?p?Bjq%yio#D8S4m$V;R^0*+!^3omN@0i#1{wK$&i|r=X=k9T3D1 zz|;fVjfH+4kOHOsJUF5&$1*j6vX<@&?yktHl$Ze#m`qDCPvYcjp`2hoqo*61#1>et zin0jS*bmw_3YJRJ{_2}52jQn`^ryRs)XJaPs!l7w*7#yYWvhBi1A$i5hZ9f;$Dt|U z^`M8`f%+8!J*!9;biRhJTMm}vwb9C*Lxak#{zv78s=x9sIQ#L>a143m%uz1`+7pw4 zMvR3@pY8=!!3R z`9c@j0EN{F(#_aaf@ypjM7)*JJ}b#I9)>60MkoQjOc!SC{?C;{nJ8FArXlg1R$~<2 zplT-|ijA;`r%a*INU6tCLl%LF7621yzyM5ITtFlkuo`|X5Rg@ft`?}R5W}ah0e`V- zKwMaaLRu&kQeT8Z*PyyGGhlbuLlyN$TY(q2F9moEV7^GYFHZfyITUe$wl@IJ0df6V zPT{EvSv^(-ux~tU22}ejSwL%2x4=Eb<}vQd1c0E8XrtH%{T=#(p9%zVN*DUO4n`sP z23n~h9?{Ae1xPl;4y?k0Hf#eXtTMm@2HXnJdBihSTV<>sq!dDfrr&9XTt{Fr%!|Zk!qG-TV}+`E!G3iT5{(t;;YKt~tZcsBHx zs*-TE0%dTuQb(^=&S(>EaaWIxm&e4PgP0*V%(7&kZbI68Q2UPov&jA98V3u2YaD&? z8V4v1z2?#3num`wM~SBywl^~+m>Pho5M&6-DN5Wz2{|}YFU0Uei)_BI-I(|UC=7^k z5*-(5fe;AjlA;x3%>i`%aa7!PT~sbYMJs>GvYkcmuBZj~Y+E|qUGFRst&eeRxB?zT z<4xVK9y<*$`xVed`#~;-$WqUstD%0ShEL(t-2Nt;o zkI~<+7xAl0Sy%{ai#SabI-%w?I!a>A;mxyh+r+XOb4x2^qKL?UQDfb_(5C!eIaTj7 z3Uv|Ym7}(FnDZ=r3K)(O?Af7*sdzCV7?8h;EX4Q7hb2r11lnW!4`(-QtqFNb8fmKG z6^7r3dEToz`?-f9o|MoGy{jC$P)KIY_vufO$MzV)CGU^)EB&C89e%euWJYK7Wnp+W zZ=si7_|B2FKJ&Tuf;u-h3WHE*9j~Sp-~QS_cCM(4HBPyMT^h=GeflE2 z8s-nC3D113%?;X{W8;#WOADKNF^u|cOn_1>9R5xCwOu&>C4B3at?>TutPzQ@9cEh} z*W%Pup8JjFzr>NyZvaK0CI9ri;iQGu5B|`6Iu-!%X$PO4CQU zMAl!Z7Cv@OBL9~!{ou1d<-5==@t@%1yEkUrMZ0O*X+0VZ)8hkmkr~0m|M-wrZ%}9P z3%$R=H^~3y%hTk;&$cz(_n`2;bV1rcb{4zTo9x9@Se{|b)fQ}p^oXb0rTUfiq~Is~mHz-B=xaX! literal 0 HcmV?d00001 diff --git a/examples/gallery/wireframe.svg b/examples/gallery/wireframe.svg new file mode 100644 index 0000000..3c302c0 --- /dev/null +++ b/examples/gallery/wireframe.svg @@ -0,0 +1,15 @@ + + + + ~/draw + + + open + + save + + export + + + + \ No newline at end of file