mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
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:
1 parent
deb9df299c
commit
4878f1c5dd
279 files changed
+2526
-2410
No files matched your search
@@ -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 }
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user