dkdc-draw 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ [workspace]
2
+ resolver = "2"
3
+ members = [
4
+ "crates/draw-core",
5
+ "crates/draw-app",
6
+ "crates/draw-webapp",
7
+ "crates/draw-cli",
8
+ "crates/draw-wasm",
9
+ ]
10
+ default-members = [
11
+ "crates/draw-core",
12
+ "crates/draw-cli",
13
+ "crates/draw-wasm",
14
+ ]
15
+ exclude = ["crates/draw-py"]
16
+
17
+ [profile.release]
18
+ lto = true
19
+ strip = true
20
+ codegen-units = 1
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dkdc.io
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,134 @@
1
+ Metadata-Version: 2.4
2
+ Name: dkdc-draw
3
+ Version: 0.1.1
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3.11
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Classifier: Programming Language :: Rust
11
+ Classifier: Topic :: Multimedia :: Graphics
12
+ License-File: LICENSE
13
+ Summary: Local-first drawing tool
14
+ Keywords: graphics,drawing,canvas,sketch
15
+ Home-Page: https://github.com/dkdc-io/draw
16
+ Author-email: Cody <cody@dkdc.io>
17
+ License-Expression: MIT
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
20
+ Project-URL: Homepage, https://github.com/dkdc-io/draw
21
+ Project-URL: Repository, https://github.com/dkdc-io/draw
22
+
23
+ # draw
24
+
25
+ Local-first, sketch-style drawing tool. Excalidraw-inspired, built on Rust with a vanilla JS frontend.
26
+
27
+ Single binary. No cloud. Your drawings live in `~/.config/draw/drawings/`.
28
+
29
+ ## Features
30
+
31
+ - **9 tools**: Select, Rectangle, Ellipse, Diamond, Line, Arrow, Pen (freedraw), Text, Eraser
32
+ - **Sketch-style fills**: hachure (diagonal lines), cross-hatch, solid, none
33
+ - **Full interactions**: drag, resize, multi-select, rubber band, pan (scroll/space+drag), zoom (ctrl+scroll)
34
+ - **Undo/redo**: all operations, including batched multi-element changes
35
+ - **Copy/paste/duplicate** with proper ID remapping
36
+ - **Z-ordering**: bring to front/back/forward/backward
37
+ - **Styles**: stroke color/width/dash, fill color/pattern/density, opacity, font
38
+ - **Export**: SVG and PNG
39
+ - **Document management**: auto-save, dirty indicator, rename, list
40
+ - **Desktop app**: native window via webview (same UI, no browser needed)
41
+ - **Python bindings**: `import dkdc_draw`
42
+ - **Keyboard-driven**: full shortcut set, `?` for help overlay
43
+
44
+ ## Install
45
+
46
+ ### From source (Rust)
47
+
48
+ ```bash
49
+ cargo install dkdc-draw --features webapp
50
+ ```
51
+
52
+ ### From source (Python)
53
+
54
+ ```bash
55
+ uv tool install dkdc-draw
56
+ ```
57
+
58
+ ### Development
59
+
60
+ ```bash
61
+ bin/setup # install dependencies
62
+ bin/install # build and install CLI
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ ```bash
68
+ draw --webapp # launch web UI
69
+ draw --app # launch desktop app
70
+ draw new [name] # create new drawing
71
+ draw open <file> # open .draw.json file
72
+ draw list # list saved drawings
73
+ draw export-svg <file> [-o output.svg]
74
+ draw export-png <file> [-o output.png] [--scale 2.0]
75
+ ```
76
+
77
+ ### Python
78
+
79
+ ```python
80
+ import dkdc_draw
81
+
82
+ doc = dkdc_draw.new_document("sketch")
83
+ dkdc_draw.save_document(doc, "sketch.draw.json")
84
+ svg = dkdc_draw.export_svg(doc)
85
+ ```
86
+
87
+ ### Keyboard shortcuts
88
+
89
+ | Key | Action |
90
+ |-----|--------|
91
+ | V | Select |
92
+ | R | Rectangle |
93
+ | O | Ellipse |
94
+ | D | Diamond |
95
+ | L | Line |
96
+ | A | Arrow |
97
+ | P | Pen (freedraw) |
98
+ | T | Text |
99
+ | E | Eraser |
100
+ | G | Toggle grid snap |
101
+ | ? | Help overlay |
102
+ | Ctrl+Z | Undo |
103
+ | Ctrl+Shift+Z | Redo |
104
+ | Ctrl+S | Save |
105
+ | Ctrl+A | Select all |
106
+ | Ctrl+D | Duplicate |
107
+ | Delete | Delete selected |
108
+ | ] / [ | Bring to front / Send to back |
109
+
110
+ ## Architecture
111
+
112
+ ```
113
+ crates/
114
+ draw-core/ Document model, serialization, SVG/PNG export, tiny-skia renderer
115
+ draw-cli/ CLI binary (clap)
116
+ draw-webapp/ Axum web server (port 1213) + embedded vanilla JS frontend
117
+ draw-app/ Desktop app (wry webview)
118
+ draw-wasm/ WASM bindings for the renderer
119
+ draw-py/ PyO3 Python bindings
120
+ ```
121
+
122
+ ## Development
123
+
124
+ ```bash
125
+ bin/build # build all (Rust + Python)
126
+ bin/check # run all checks (format, lint, test)
127
+ bin/format # format all code
128
+ bin/test # run all tests
129
+ ```
130
+
131
+ ## License
132
+
133
+ MIT
134
+
@@ -0,0 +1,111 @@
1
+ # draw
2
+
3
+ Local-first, sketch-style drawing tool. Excalidraw-inspired, built on Rust with a vanilla JS frontend.
4
+
5
+ Single binary. No cloud. Your drawings live in `~/.config/draw/drawings/`.
6
+
7
+ ## Features
8
+
9
+ - **9 tools**: Select, Rectangle, Ellipse, Diamond, Line, Arrow, Pen (freedraw), Text, Eraser
10
+ - **Sketch-style fills**: hachure (diagonal lines), cross-hatch, solid, none
11
+ - **Full interactions**: drag, resize, multi-select, rubber band, pan (scroll/space+drag), zoom (ctrl+scroll)
12
+ - **Undo/redo**: all operations, including batched multi-element changes
13
+ - **Copy/paste/duplicate** with proper ID remapping
14
+ - **Z-ordering**: bring to front/back/forward/backward
15
+ - **Styles**: stroke color/width/dash, fill color/pattern/density, opacity, font
16
+ - **Export**: SVG and PNG
17
+ - **Document management**: auto-save, dirty indicator, rename, list
18
+ - **Desktop app**: native window via webview (same UI, no browser needed)
19
+ - **Python bindings**: `import dkdc_draw`
20
+ - **Keyboard-driven**: full shortcut set, `?` for help overlay
21
+
22
+ ## Install
23
+
24
+ ### From source (Rust)
25
+
26
+ ```bash
27
+ cargo install dkdc-draw --features webapp
28
+ ```
29
+
30
+ ### From source (Python)
31
+
32
+ ```bash
33
+ uv tool install dkdc-draw
34
+ ```
35
+
36
+ ### Development
37
+
38
+ ```bash
39
+ bin/setup # install dependencies
40
+ bin/install # build and install CLI
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ```bash
46
+ draw --webapp # launch web UI
47
+ draw --app # launch desktop app
48
+ draw new [name] # create new drawing
49
+ draw open <file> # open .draw.json file
50
+ draw list # list saved drawings
51
+ draw export-svg <file> [-o output.svg]
52
+ draw export-png <file> [-o output.png] [--scale 2.0]
53
+ ```
54
+
55
+ ### Python
56
+
57
+ ```python
58
+ import dkdc_draw
59
+
60
+ doc = dkdc_draw.new_document("sketch")
61
+ dkdc_draw.save_document(doc, "sketch.draw.json")
62
+ svg = dkdc_draw.export_svg(doc)
63
+ ```
64
+
65
+ ### Keyboard shortcuts
66
+
67
+ | Key | Action |
68
+ |-----|--------|
69
+ | V | Select |
70
+ | R | Rectangle |
71
+ | O | Ellipse |
72
+ | D | Diamond |
73
+ | L | Line |
74
+ | A | Arrow |
75
+ | P | Pen (freedraw) |
76
+ | T | Text |
77
+ | E | Eraser |
78
+ | G | Toggle grid snap |
79
+ | ? | Help overlay |
80
+ | Ctrl+Z | Undo |
81
+ | Ctrl+Shift+Z | Redo |
82
+ | Ctrl+S | Save |
83
+ | Ctrl+A | Select all |
84
+ | Ctrl+D | Duplicate |
85
+ | Delete | Delete selected |
86
+ | ] / [ | Bring to front / Send to back |
87
+
88
+ ## Architecture
89
+
90
+ ```
91
+ crates/
92
+ draw-core/ Document model, serialization, SVG/PNG export, tiny-skia renderer
93
+ draw-cli/ CLI binary (clap)
94
+ draw-webapp/ Axum web server (port 1213) + embedded vanilla JS frontend
95
+ draw-app/ Desktop app (wry webview)
96
+ draw-wasm/ WASM bindings for the renderer
97
+ draw-py/ PyO3 Python bindings
98
+ ```
99
+
100
+ ## Development
101
+
102
+ ```bash
103
+ bin/build # build all (Rust + Python)
104
+ bin/check # run all checks (format, lint, test)
105
+ bin/format # format all code
106
+ bin/test # run all tests
107
+ ```
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,31 @@
1
+ [package]
2
+ name = "dkdc-draw"
3
+ version = "0.1.1"
4
+ edition = "2024"
5
+ authors = ["Cody <cody@dkdc.io>"]
6
+ description = "Local-first drawing tool"
7
+ repository = "https://github.com/dkdc-io/draw"
8
+ homepage = "https://github.com/dkdc-io/draw"
9
+ license = "MIT"
10
+ readme = "README.md"
11
+ keywords = ["graphics", "drawing", "cli", "sketch"]
12
+ categories = ["graphics", "command-line-utilities"]
13
+
14
+ [lib]
15
+ name = "draw"
16
+ path = "src/lib.rs"
17
+
18
+ [[bin]]
19
+ name = "draw"
20
+ path = "src/main.rs"
21
+
22
+ [features]
23
+ app = ["dep:dkdc-draw-app"]
24
+ webapp = ["dep:dkdc-draw-webapp"]
25
+
26
+ [dependencies]
27
+ anyhow = "1"
28
+ dkdc-draw-core = { version = "0.1.1", path = "../draw-core" }
29
+ dkdc-draw-app = { version = "0.1.1", path = "../draw-app", optional = true }
30
+ dkdc-draw-webapp = { version = "0.1.1", path = "../draw-webapp", optional = true }
31
+ clap = { version = "4.5", features = ["derive"] }
@@ -0,0 +1,111 @@
1
+ # draw
2
+
3
+ Local-first, sketch-style drawing tool. Excalidraw-inspired, built on Rust with a vanilla JS frontend.
4
+
5
+ Single binary. No cloud. Your drawings live in `~/.config/draw/drawings/`.
6
+
7
+ ## Features
8
+
9
+ - **9 tools**: Select, Rectangle, Ellipse, Diamond, Line, Arrow, Pen (freedraw), Text, Eraser
10
+ - **Sketch-style fills**: hachure (diagonal lines), cross-hatch, solid, none
11
+ - **Full interactions**: drag, resize, multi-select, rubber band, pan (scroll/space+drag), zoom (ctrl+scroll)
12
+ - **Undo/redo**: all operations, including batched multi-element changes
13
+ - **Copy/paste/duplicate** with proper ID remapping
14
+ - **Z-ordering**: bring to front/back/forward/backward
15
+ - **Styles**: stroke color/width/dash, fill color/pattern/density, opacity, font
16
+ - **Export**: SVG and PNG
17
+ - **Document management**: auto-save, dirty indicator, rename, list
18
+ - **Desktop app**: native window via webview (same UI, no browser needed)
19
+ - **Python bindings**: `import dkdc_draw`
20
+ - **Keyboard-driven**: full shortcut set, `?` for help overlay
21
+
22
+ ## Install
23
+
24
+ ### From source (Rust)
25
+
26
+ ```bash
27
+ cargo install dkdc-draw --features webapp
28
+ ```
29
+
30
+ ### From source (Python)
31
+
32
+ ```bash
33
+ uv tool install dkdc-draw
34
+ ```
35
+
36
+ ### Development
37
+
38
+ ```bash
39
+ bin/setup # install dependencies
40
+ bin/install # build and install CLI
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ```bash
46
+ draw --webapp # launch web UI
47
+ draw --app # launch desktop app
48
+ draw new [name] # create new drawing
49
+ draw open <file> # open .draw.json file
50
+ draw list # list saved drawings
51
+ draw export-svg <file> [-o output.svg]
52
+ draw export-png <file> [-o output.png] [--scale 2.0]
53
+ ```
54
+
55
+ ### Python
56
+
57
+ ```python
58
+ import dkdc_draw
59
+
60
+ doc = dkdc_draw.new_document("sketch")
61
+ dkdc_draw.save_document(doc, "sketch.draw.json")
62
+ svg = dkdc_draw.export_svg(doc)
63
+ ```
64
+
65
+ ### Keyboard shortcuts
66
+
67
+ | Key | Action |
68
+ |-----|--------|
69
+ | V | Select |
70
+ | R | Rectangle |
71
+ | O | Ellipse |
72
+ | D | Diamond |
73
+ | L | Line |
74
+ | A | Arrow |
75
+ | P | Pen (freedraw) |
76
+ | T | Text |
77
+ | E | Eraser |
78
+ | G | Toggle grid snap |
79
+ | ? | Help overlay |
80
+ | Ctrl+Z | Undo |
81
+ | Ctrl+Shift+Z | Redo |
82
+ | Ctrl+S | Save |
83
+ | Ctrl+A | Select all |
84
+ | Ctrl+D | Duplicate |
85
+ | Delete | Delete selected |
86
+ | ] / [ | Bring to front / Send to back |
87
+
88
+ ## Architecture
89
+
90
+ ```
91
+ crates/
92
+ draw-core/ Document model, serialization, SVG/PNG export, tiny-skia renderer
93
+ draw-cli/ CLI binary (clap)
94
+ draw-webapp/ Axum web server (port 1213) + embedded vanilla JS frontend
95
+ draw-app/ Desktop app (wry webview)
96
+ draw-wasm/ WASM bindings for the renderer
97
+ draw-py/ PyO3 Python bindings
98
+ ```
99
+
100
+ ## Development
101
+
102
+ ```bash
103
+ bin/build # build all (Rust + Python)
104
+ bin/check # run all checks (format, lint, test)
105
+ bin/format # format all code
106
+ bin/test # run all tests
107
+ ```
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,147 @@
1
+ use anyhow::Result;
2
+ use clap::Parser;
3
+ use std::path::PathBuf;
4
+
5
+ use draw_core::document::Document;
6
+
7
+ #[derive(Parser, Debug)]
8
+ #[command(name = "draw")]
9
+ #[command(about = "Local-first drawing tool")]
10
+ #[command(version)]
11
+ pub struct Args {
12
+ /// Open the desktop app
13
+ #[cfg(feature = "app")]
14
+ #[arg(short = 'a', long)]
15
+ pub app: bool,
16
+
17
+ /// Open the webapp
18
+ #[cfg(feature = "webapp")]
19
+ #[arg(short = 'w', long)]
20
+ pub webapp: bool,
21
+
22
+ #[command(subcommand)]
23
+ pub command: Option<Command>,
24
+ }
25
+
26
+ #[derive(clap::Subcommand, Debug)]
27
+ pub enum Command {
28
+ /// Create a new drawing
29
+ New {
30
+ /// Drawing name
31
+ #[arg(default_value = "untitled")]
32
+ name: String,
33
+ },
34
+ /// Open an existing drawing
35
+ Open {
36
+ /// Path to .draw.json file
37
+ file: PathBuf,
38
+ },
39
+ /// List saved drawings
40
+ List,
41
+ /// Export drawing to SVG
42
+ ExportSvg {
43
+ /// Path to .draw.json file
44
+ file: PathBuf,
45
+ /// Output SVG path (defaults to same name with .svg extension)
46
+ #[arg(short, long)]
47
+ output: Option<PathBuf>,
48
+ },
49
+ /// Export drawing to PNG
50
+ ExportPng {
51
+ /// Path to .draw.json file
52
+ file: PathBuf,
53
+ /// Output PNG path (defaults to same name with .png extension)
54
+ #[arg(short, long)]
55
+ output: Option<PathBuf>,
56
+ /// Scale factor (default: 2x for retina)
57
+ #[arg(short, long, default_value_t = 2.0)]
58
+ scale: f32,
59
+ },
60
+ }
61
+
62
+ pub fn run_cli(argv: impl IntoIterator<Item = impl Into<String>>) -> Result<()> {
63
+ let argv: Vec<String> = argv.into_iter().map(Into::into).collect();
64
+
65
+ // No args = help
66
+ if argv.len() <= 1 {
67
+ Args::parse_from(["draw", "--help"]);
68
+ }
69
+
70
+ let args = Args::parse_from(&argv);
71
+
72
+ #[cfg(feature = "app")]
73
+ if args.app {
74
+ return draw_app::run_app(None);
75
+ }
76
+
77
+ #[cfg(feature = "webapp")]
78
+ if args.webapp {
79
+ return draw_webapp::run_webapp(None);
80
+ }
81
+
82
+ match args.command {
83
+ Some(Command::New { name }) => {
84
+ let doc = Document::new(name);
85
+ let path = draw_core::storage::save_to_storage(&doc)?;
86
+ println!("Created: {} ({})", doc.name, path.display());
87
+
88
+ #[cfg(feature = "webapp")]
89
+ return draw_webapp::run_webapp(Some(doc.id));
90
+
91
+ #[cfg(not(feature = "webapp"))]
92
+ {
93
+ println!("Run with --webapp to open in browser");
94
+ Ok(())
95
+ }
96
+ }
97
+ Some(Command::Open { file }) => {
98
+ let doc = draw_core::storage::load(&file)?;
99
+ println!("Loaded: {} ({} elements)", doc.name, doc.elements.len());
100
+
101
+ #[cfg(feature = "webapp")]
102
+ return draw_webapp::run_webapp(Some(doc.id));
103
+
104
+ #[cfg(not(feature = "webapp"))]
105
+ {
106
+ println!("Run with --webapp to open in browser");
107
+ Ok(())
108
+ }
109
+ }
110
+ Some(Command::List) => {
111
+ let drawings = draw_core::storage::list_drawings()?;
112
+ if drawings.is_empty() {
113
+ println!("No saved drawings.");
114
+ } else {
115
+ for (name, path) in drawings {
116
+ println!(" {} ({})", name, path.display());
117
+ }
118
+ }
119
+ Ok(())
120
+ }
121
+ Some(Command::ExportSvg { file, output }) => {
122
+ let doc = draw_core::storage::load(&file)?;
123
+ let svg = draw_core::export_svg(&doc);
124
+ let out_path = output.unwrap_or_else(|| file.with_extension("svg"));
125
+ std::fs::write(&out_path, &svg)?;
126
+ println!("Exported SVG to {}", out_path.display());
127
+ Ok(())
128
+ }
129
+ Some(Command::ExportPng {
130
+ file,
131
+ output,
132
+ scale,
133
+ }) => {
134
+ let doc = draw_core::storage::load(&file)?;
135
+ let png = draw_core::export_png_with_scale(&doc, scale)?;
136
+ let out_path = output.unwrap_or_else(|| file.with_extension("png"));
137
+ std::fs::write(&out_path, &png)?;
138
+ println!("Exported PNG to {}", out_path.display());
139
+ Ok(())
140
+ }
141
+ None => {
142
+ // No subcommand but also no flags = help
143
+ Args::parse_from(["draw", "--help"]);
144
+ unreachable!()
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,16 @@
1
+ pub mod cli;
2
+
3
+ pub use cli::run_cli;
4
+ pub use draw_core::{
5
+ document::Document,
6
+ element::{Element, FreeDrawElement, LineElement, ShapeElement, TextElement},
7
+ export_png, export_png_with_scale, export_svg,
8
+ point::{Bounds, Point, ViewState},
9
+ render::{HandlePosition, RenderConfig, Renderer},
10
+ storage,
11
+ style::{
12
+ Arrowhead, DEFAULT_FILL_COLOR, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE,
13
+ DEFAULT_HACHURE_ANGLE, DEFAULT_HACHURE_GAP, DEFAULT_STROKE_COLOR, DEFAULT_STROKE_WIDTH,
14
+ FillStyle, FillType, FontStyle, StrokeStyle, TextAlign,
15
+ },
16
+ };
@@ -0,0 +1,3 @@
1
+ fn main() -> anyhow::Result<()> {
2
+ draw::run_cli(std::env::args())
3
+ }
@@ -0,0 +1,25 @@
1
+ [package]
2
+ name = "dkdc-draw-core"
3
+ version = "0.1.1"
4
+ edition = "2024"
5
+ authors = ["Cody <cody@dkdc.io>"]
6
+ description = "Core library for draw"
7
+ repository = "https://github.com/dkdc-io/draw"
8
+ license = "MIT"
9
+
10
+ [lib]
11
+ name = "draw_core"
12
+ path = "src/lib.rs"
13
+
14
+ [dependencies]
15
+ anyhow = "1"
16
+ chrono = { version = "0.4", features = ["serde"] }
17
+ dirs = "6"
18
+ resvg = "0.47"
19
+ serde = { version = "1", features = ["derive"] }
20
+ serde_json = "1"
21
+ tiny-skia = "0.12"
22
+ uuid = { version = "1", features = ["v4"] }
23
+
24
+ [dev-dependencies]
25
+ tempfile = "3"