Rename DrawCraft → VectorCraft: crates (vectorcraft-*), apps, binaries, types, env vars, docs, examples (.vectorcraft); old .drawcraft files and preferences still load. Fix: text bounds exact after opening a file; selection no longer marks a document modified (dirty = content differs from the saved document). Screenshots and renders regenerated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-10-01 14:30:09 -04:00
1 parent deb9df299c
commit 4878f1c5dd
279 files changed
+2526 -2410

No files matched your search

+9 -9
View File
@@ -1,22 +1,22 @@
[package]
name = "drawcraft-testkit"
name = "vectorcraft-testkit"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "DrawCraft test helpers: fixtures, generators, render comparison, geometry assertions"
description = "VectorCraft test helpers: fixtures, generators, render comparison, geometry assertions"
publish = false
[lints]
workspace = true
[dependencies]
drawcraft-geom = { workspace = true }
drawcraft-color = { workspace = true }
drawcraft-doc = { workspace = true }
drawcraft-engine = { workspace = true }
drawcraft-render = { workspace = true }
drawcraft-format = { workspace = true }
drawcraft-svg = { workspace = true }
vectorcraft-geom = { workspace = true }
vectorcraft-color = { workspace = true }
vectorcraft-doc = { workspace = true }
vectorcraft-engine = { workspace = true }
vectorcraft-render = { workspace = true }
vectorcraft-format = { workspace = true }
vectorcraft-svg = { workspace = true }
kurbo = { workspace = true }
serde_json = { workspace = true }
proptest = { workspace = true }
+5 -5
View File
@@ -2,11 +2,11 @@
use std::sync::Arc;
use drawcraft_color::{Color, Paint};
use drawcraft_doc::{Appearance, Document, Node, NodeId, NodeKind};
use drawcraft_engine::Session;
use drawcraft_geom::{PathData, Rect, shapes};
use serde_json::{Value, json};
use vectorcraft_color::{Color, Paint};
use vectorcraft_doc::{Appearance, Document, Node, NodeId, NodeKind};
use vectorcraft_engine::Session;
use vectorcraft_geom::{PathData, Rect, shapes};
/// Execute a command and panic with context on error.
#[track_caller]
@@ -108,7 +108,7 @@ pub fn rich_session() -> Session {
exec(&mut s, "effect.apply", json!({"effect": "distort.roughen", "params": {"size": 3, "detail": 5, "seed": 7}}));
let l = id_of(&exec(&mut s, "shape.line", json!({"x1": 20, "y1": 300, "x2": 200, "y2": 350})));
exec(&mut s, "stroke.set", json!({"weight": 3, "endArrow": "Arrow"}));
let _ = id_of(&exec(&mut s, "text.create", json!({"x": 250, "y": 300, "text": "DrawCraft", "size": 30})));
let _ = id_of(&exec(&mut s, "text.create", json!({"x": 250, "y": 300, "text": "VectorCraft", "size": 30})));
exec(&mut s, "layer.new", json!({"name": "Top"}));
let d = rect(&mut s, 350.0, 200.0, 80.0, 80.0);
let e = ellipse(&mut s, 380.0, 230.0, 80.0, 80.0);
+1 -1
View File
@@ -1,7 +1,7 @@
//! Geometry assertions.
use drawcraft_geom::{BezPath, ParamCurve, PathData, Point, Rect};
use kurbo::{ParamCurveNearest, Shape};
use vectorcraft_geom::{BezPath, ParamCurve, PathData, Point, Rect};
pub fn approx(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() <= eps
+10 -10
View File
@@ -2,9 +2,9 @@
use std::collections::HashSet;
use drawcraft_doc::{Document, NodeKind};
use drawcraft_engine::Session;
use serde_json::{Value, json};
use vectorcraft_doc::{Document, NodeKind};
use vectorcraft_engine::Session;
/// Serialize a document to JSON (the canonical form for equality checks).
pub fn doc_json(doc: &Document) -> Value {
@@ -88,11 +88,11 @@ pub fn check_session(s: &Session) -> Result<(), String> {
Ok(())
}
/// `.drawcraft` save → load must reproduce the document (numbers compared to 1e-12 relative —
/// `.vectorcraft` save → load must reproduce the document (numbers compared to 1e-12 relative —
/// see [`check_native_roundtrip_exact`] for the bit-exact version).
pub fn check_native_roundtrip(doc: &Document) -> Result<(), String> {
let bytes = drawcraft_format::save(doc, false);
let back = drawcraft_format::load(&bytes).map_err(|e| format!("load: {e}"))?;
let bytes = vectorcraft_format::save(doc, false);
let back = vectorcraft_format::load(&bytes).map_err(|e| format!("load: {e}"))?;
let (a, b) = (doc_json(doc), doc_json(&back));
if !json_approx_eq(&a, &b, 1e-12) {
return Err(format!("native round trip differs:\n{}", first_diff(&a, &b, "$")));
@@ -100,10 +100,10 @@ pub fn check_native_roundtrip(doc: &Document) -> Result<(), String> {
Ok(())
}
/// Bit-exact `.drawcraft` round trip (every f64 must survive).
/// Bit-exact `.vectorcraft` round trip (every f64 must survive).
pub fn check_native_roundtrip_exact(doc: &Document) -> Result<(), String> {
let bytes = drawcraft_format::save(doc, false);
let back = drawcraft_format::load(&bytes).map_err(|e| format!("load: {e}"))?;
let bytes = vectorcraft_format::save(doc, false);
let back = vectorcraft_format::load(&bytes).map_err(|e| format!("load: {e}"))?;
let (a, b) = (doc_json(doc), doc_json(&back));
if a != b {
return Err(format!("native round trip differs:\n{}", first_diff(&a, &b, "$")));
@@ -126,8 +126,8 @@ pub fn json_approx_eq(a: &Value, b: &Value, rel: f64) -> bool {
/// SVG export must not panic and must re-import.
pub fn check_svg_roundtrip(doc: &Document) -> Result<Document, String> {
let svg = drawcraft_svg::export(doc, &drawcraft_svg::ExportOptions::default());
drawcraft_svg::import(&svg).map_err(|e| format!("svg re-import: {e}\n{svg}"))
let svg = vectorcraft_svg::export(doc, &vectorcraft_svg::ExportOptions::default());
vectorcraft_svg::import(&svg).map_err(|e| format!("svg re-import: {e}\n{svg}"))
}
/// `document.inspect` must succeed and serialize.
+5 -5
View File
@@ -1,4 +1,4 @@
//! DrawCraft test helpers.
//! VectorCraft test helpers.
//!
//! - [`fixtures`]: sessions and documents in known states (empty, single/multi selection, a "rich"
//! document touching most node kinds), plus a [`fixtures::DocBuilder`] for building documents
@@ -6,7 +6,7 @@
//! - [`strategies`]: proptest strategies for geometry and for engine command sequences ([`strategies::Op`]), and
//! junk-parameter generators for fuzzing the command registry.
//! - [`invariants`]: structural checks for documents and sessions, and round-trip checks
//! (`.drawcraft`, SVG, `document.inspect`).
//! (`.vectorcraft`, SVG, `document.inspect`).
//! - [`raster`]: rendering helpers and image comparison with a perceptual tolerance.
//! - [`geom`]: geometry assertions (approximate equality, curve sampling, Hausdorff distance).
//!
@@ -19,14 +19,14 @@ pub mod invariants;
pub mod raster;
pub mod strategies;
pub use drawcraft_engine::{Session, doc::NodeId};
pub use serde_json::{Value, json};
pub use vectorcraft_engine::{Session, doc::NodeId};
/// Re-exports so dependents without direct dependencies (e.g. app test crates) can use them.
pub use {drawcraft_doc as doc, drawcraft_format as format, drawcraft_render as render, drawcraft_svg as svg};
pub use {vectorcraft_doc as doc, vectorcraft_format as format, vectorcraft_render as render, vectorcraft_svg as svg};
/// A per-process temporary directory for test output (created on first use).
pub fn temp_dir(tag: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("drawcraft-testkit-{tag}-{}", std::process::id()));
let dir = std::env::temp_dir().join(format!("vectorcraft-testkit-{tag}-{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("create temp dir");
dir
}
+3 -3
View File
@@ -1,8 +1,8 @@
//! Rendering helpers and image comparison with a perceptual tolerance.
use drawcraft_doc::Document;
use drawcraft_geom::{Affine, Rect};
use drawcraft_render::{RenderOptions, Rendered, Renderer};
use vectorcraft_doc::Document;
use vectorcraft_geom::{Affine, Rect};
use vectorcraft_render::{RenderOptions, Rendered, Renderer};
/// A straight-alpha RGBA8 image.
#[derive(Clone, Debug, PartialEq)]
+8 -8
View File
@@ -1,9 +1,9 @@
//! Proptest strategies: geometry, engine command sequences, junk parameters.
use drawcraft_engine::{Session, command_specs};
use drawcraft_geom::{Anchor, PathData, Point, Rect, SubPath};
use proptest::prelude::*;
use serde_json::{Value, json};
use vectorcraft_engine::{Session, command_specs};
use vectorcraft_geom::{Anchor, PathData, Point, Rect, SubPath};
use crate::fixtures::{art_nodes, top_level_ids};
@@ -54,12 +54,12 @@ pub fn polygon_path(pts: &[Point]) -> PathData {
/// An axis-aligned ellipse as a path.
pub fn arb_ellipse() -> impl Strategy<Value = PathData> {
arb_rect().prop_map(drawcraft_geom::shapes::ellipse)
arb_rect().prop_map(vectorcraft_geom::shapes::ellipse)
}
/// Rectangles, ellipses and star-shaped polygons.
pub fn arb_closed_shape() -> impl Strategy<Value = PathData> {
prop_oneof![arb_rect().prop_map(drawcraft_geom::shapes::rectangle), arb_ellipse(), arb_star_polygon().prop_map(|p| polygon_path(&p)),]
prop_oneof![arb_rect().prop_map(vectorcraft_geom::shapes::rectangle), arb_ellipse(), arb_star_polygon().prop_map(|p| polygon_path(&p)),]
}
/// A random cubic Bézier path (open or closed, possibly several subpaths, arbitrary handles).
@@ -259,7 +259,7 @@ pub fn arb_ops(len: std::ops::Range<usize>) -> impl Strategy<Value = Vec<Op>> {
prop::collection::vec(arb_op(), len)
}
fn pick(ids: &[drawcraft_doc::NodeId], i: usize) -> Option<u64> {
fn pick(ids: &[vectorcraft_doc::NodeId], i: usize) -> Option<u64> {
if ids.is_empty() { None } else { Some(ids[i % ids.len()].0) }
}
@@ -275,7 +275,7 @@ impl Op {
art_nodes(d)
.iter()
.filter(|n| {
!d.parent_of(n.id).and_then(|p| d.node(p)).is_some_and(|p| matches!(p.kind, drawcraft_doc::NodeKind::Compound { .. }))
!d.parent_of(n.id).and_then(|p| d.node(p)).is_some_and(|p| matches!(p.kind, vectorcraft_doc::NodeKind::Compound { .. }))
})
.map(|n| n.id)
.collect()
@@ -401,7 +401,7 @@ impl Op {
}
/// Apply this op to the session.
pub fn apply(&self, s: &mut Session) -> drawcraft_engine::Result<Value> {
pub fn apply(&self, s: &mut Session) -> vectorcraft_engine::Result<Value> {
let (id, p) = self.command(s);
s.execute(&id, &p)
}
@@ -409,7 +409,7 @@ impl Op {
/// Keep the unused-import lint quiet when only some helpers are used.
#[doc(hidden)]
pub fn _top_level(s: &Session) -> Vec<drawcraft_doc::NodeId> {
pub fn _top_level(s: &Session) -> Vec<vectorcraft_doc::NodeId> {
s.active().map(|d| top_level_ids(&d.doc)).unwrap_or_default()
}