A project that starts from scratch and uses Rust to re implement Excalidraw, built on top of GPUI Kit, but removed certain features.
This is a single Rust library crate that reimplements Excalidraw — the hand-drawn style virtual whiteboard — with a runnable desktop application example. The data model, serialization format, and interaction model faithfully mirror the upstream Excalidraw project.
- Pure Rust library (lib name
gpui_excalidraw) consumable by downstream GPUI Kit applications. - 1:1 data model — element types, fill styles, stroke styles, arrowheads, bindings, and the
.excalidrawJSON schema match Excalidraw'spackages/element/src/types.tsexactly. - i18n — 11 locales (English, 简体中文, 繁體中文, 日本語, 한국어, Deutsch, Français, Español, Italiano, Português, Русский) with runtime language switching.
- Theming — light/dark theme switching mapped onto GPUI Kit's semantic design tokens.
- Hand-drawn rendering — a roughjs-style renderer (hachure / cross-hatch / zigzag / solid fills, deterministic seeded jitter) producing the signature Excalidraw "sketchy" look.
- Full editing interaction — selection (marquee + click), drawing (rectangle / diamond / ellipse / arrow / line / freedraw / text / image / frame / sticky note), move / resize / rotate handles, multi-point line & arrow authoring (click-click-Enter or double-click, draggable vertices), endpoint binding (arrows/lines auto-attach to shape outlines and re-glue when the shape moves or rotates), alt-drag duplicate, grid snapping, Shift angle / square constraints, clipboard (copy / cut / paste / select-all), space-drag pan, zoom-to-fit / zoom-to-selection, text inline editing, eraser (drag), laser pointer, pan, zoom-to-cursor, undo/redo, delete, duplicate.
- Menus — top-left file menu (new / save
.excalidraw/ load / export PNG / export SVG), a right-click canvas context menu (duplicate, copy/cut/paste, select all, bring to front, send to back), a command palette (Ctrl+/, fuzzy-filterable and localized), a find-on-canvas bar (Ctrl+F, match count + prev/next stepper), and a keyboard-shortcut dialog (?) listing every binding. - Element operations — lock/unlock, group/ungroup, align (left/center/right/top/middle/bottom), distribute (horizontal/vertical), layer reordering (bring forward/send backward/front/back).
- Properties panel — right-side floating panel to edit stroke/background color, fill style, stroke width, stroke style, opacity, roughness, roundness, arrowheads (start/end), and text properties (font family, font size, text alignment).
- Library panel — save selected elements to a reusable library and insert them back onto the canvas.
- Layers panel — element list with selection, reordering, and deletion.
- Keyboard shortcuts — tool switching (V/H/R/D/O/A/L/P/T/F/E/N/K/0), Ctrl+Z/Y undo/redo, Ctrl+C/X/V clipboard, Ctrl+A select-all, Ctrl+D duplicate, Ctrl+G group, Ctrl+S save, Ctrl+/ command palette, Ctrl+F find on canvas, ? shortcut dialog, Ctrl+Shift+E export PNG, Enter to commit a polyline, Delete/Backspace, Shift+1/2 zoom-to-fit/selection.
- Export & persistence — SVG export (hachure/cross-hatch/zigzag fills clipped to the shape outline, arrowheads, rounded corners, solid fills, multi-line text via
<tspan>, and embedded images as<image href="data:…">), real PNG export (the SVG is rasterized withresvg/usvg, so the bytes are an actual bitmap at 1x/2x scale, not an SVG renamed), and.excalidrawJSON serialization/deserialization compatible with the official format (including the top-levelfilesmap), plus round-trip load. - Tests — unit tests, integration tests (including headless GPUI UI tests), and runnable examples.
The whole package lives at the repository root:
Cargo.toml Single package: lib `gpui_excalidraw` + bin `excalidraw-example`
build.rs Embeds assets/icons/*.svg into `ui::icons`
assets/ icons/ (128 upstream glyphs) + fonts/ (Excalifont, Virgil)
src/
lib.rs Crate root: `pub mod core` / `render` / `ui` + top-level re-exports
core/ Data model, geometry, element types, scene, collision, transform
render/ Hand-drawn (roughjs-style) renderer + SVG/PNG exporters
ui/ GPUI Kit editor: toolbar, canvas, interaction, i18n, theme, export
bin/ The single-window editor application
tests/ Integration tests (core_*, render, ui_*)
examples/ verify_features, demo_scene_svg, export_demo
The layer directories are declared through #[path] in src/core/lib.rs,
src/render/lib.rs, and src/ui/lib.rs, which keeps the physical grouping
(core/, render/, ui/) while the public paths stay short — gpui_excalidraw::core::…,
::render::…, ::ui::….
# Run the full editor application (requires a desktop environment)
cargo run --bin excalidraw-example
# Run all tests (unit + integration + headless UI tests)
cargo test
# Lint
cargo clippy --all-targetsAdd a .cargo/config.toml pointing crates-io at the official sparse index if your setup needs it; a global ~/.cargo/config.toml (e.g. rsproxy) is picked up automatically.
Add to your Cargo.toml:
[dependencies]
gpui-excalidraw = { path = "path/to/gpui-excalidraw" }
gpui-kit = "0.7"use gpui_kit::*;
fn main() {
gpui_kit::application().run(|cx| {
gpui_kit::init(cx);
gpui_kit::open_window(WindowOptions::default(), cx, |_, cx| {
cx.new(|_| {
let mut editor = excalidraw::Editor::new();
editor.add_demo_scene();
editor
})
})
.expect("failed to open window");
});
}use excalidraw::core::{ElementOptions, new_rectangle, Scene, Point};
use excalidraw::{export_svg, scene_to_json};
let mut scene = Scene::new();
let blue = ElementOptions { stroke_color: "#1971c2".into(), ..Default::default() };
scene.add(new_rectangle(0.0, 0.0, 200.0, 100.0, &blue));
let svg = export_svg(&scene, "#ffffff");
let json = scene_to_json(&scene, &excalidraw::core::AppState::default()).unwrap();use excalidraw::{I18n, Language};
let i18n = I18n::new(Language::ZhCn);
assert_eq!(i18n.t("toolbar.rectangle"), "矩形");The element model replicates Excalidraw's Element union exactly, including:
rectangle,diamond,ellipse,line,arrow,freedraw,text,image,frame,magicframe,iframe,embeddable,stickynote,selectionFillStyle(hachure,cross-hatch,solid,zigzag),StrokeStyle(solid,dashed,dotted)Arrowheadvariants (including cardinality / crowfoot notation)FixedPointBinding,BoundElement,ImageCropseed/version/versionNoncefields for deterministic rendering and collaboration reconciliation
The .excalidraw JSON output uses Excalidraw's exact camelCase field names and { "type": "excalidraw", "version": 2 } envelope, so saved files open directly in the official Excalidraw app.
# End-to-end feature verification; writes verify.svg + verify.png + verify.excalidraw
cargo run --example verify_features
# Render the built-in demo scene to demo-scene.svg
cargo run --example demo_scene_svg
# Print an SVG export of a small scene and write diagram.excalidraw
cargo run --example export_demoPNG export is available both from the UI (file menu → 导出图片, or Ctrl+Shift+E) and programmatically:
let png = excalidraw::render::export::export_png(&scene, "#ffffff", 2.0)?;MIT