Files
vectorcraft/crates/engine/src/tests/stylelib.rs
T

422 lines
25 KiB
Rust

//! Graphic style libraries: the built-in libraries, adding to the document (deduped, one undo step,
//! applied, patterns brought along), saving and loading `.vcstyles` and user libraries.
use std::sync::Arc;
use serde_json::{Value, json};
use vectorcraft_color::{Paint, Swatch};
use vectorcraft_doc::{GraphicStyle, Node, NodeId, PatternDef};
use vectorcraft_geom::{Point, Rect, shapes};
use crate::*;
fn session() -> Session {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
s
}
fn run(s: &mut Session, id: &str, p: Value) -> Value {
s.execute(id, &p).unwrap_or_else(|e| panic!("{id} {p}: {e}"))
}
fn doc(s: &Session) -> &vectorcraft_doc::Document {
&s.doc().unwrap().doc
}
fn undo_len(s: &Session) -> usize {
s.doc().unwrap().history.undo.len()
}
fn names(v: &Value) -> Vec<&str> {
v.as_array().unwrap().iter().map(|n| n.as_str().unwrap()).collect()
}
fn rect(s: &mut Session, x: f64, y: f64, w: f64, h: f64) -> NodeId {
NodeId(run(s, "shape.rectangle", json!({"x": x, "y": y, "width": w, "height": h}))["id"].as_u64().unwrap())
}
#[test]
fn builtin_libraries_are_listed_with_original_styles_using_catalogued_effects() {
let mut s = session();
let list = run(&mut s, "graphicStyle.libraries", json!({}));
let libs = list["libraries"].as_array().unwrap();
assert!(libs.len() >= 8 && libs.iter().all(|l| l["category"] == "builtIn"), "{libs:?}");
assert!(libs.iter().any(|l| l["id"] == "game-hud" && l["name"] == "Game HUD"), "{libs:?}");
assert!(libs.iter().any(|l| l["id"] == "status-effects" && l["name"] == "Status Effects"), "{libs:?}");
assert!(libs.iter().any(|l| l["id"] == "textures" && l["name"] == "Textures" && l["count"] == 8), "{libs:?}");
for l in libs {
let id = l["id"].as_str().unwrap();
let g = run(&mut s, "graphicStyle.library", json!({"library": id}));
let styles = g["styles"].as_array().unwrap();
assert!(!styles.is_empty() && l["count"].as_u64() == Some(styles.len() as u64), "{id}");
let (_, lib) = cmd::stylelib::library(&s, id).unwrap();
for st in &lib.styles {
let ap = &st.appearance;
let item_fx = ap.items.iter().flat_map(|i| i.effects());
for e in ap.effects.iter().chain(item_fx) {
assert!(vectorcraft_render::effects::effect_info(&e.id).is_some(), "{id} / {}: unknown effect {}", st.name, e.id);
}
}
}
let game = run(&mut s, "graphicStyle.library", json!({"library": "game-hud"}));
let game_names: Vec<&str> = game["styles"].as_array().unwrap().iter().map(|x| x["name"].as_str().unwrap()).collect();
assert_eq!(game_names, ["Health Fill", "Mana Glow", "Damage Flash", "Quest Outline", "Coin Shine", "Boss Alert"]);
let status = run(&mut s, "graphicStyle.library", json!({"library": "status-effects"}));
let status_names: Vec<&str> = status["styles"].as_array().unwrap().iter().map(|x| x["name"].as_str().unwrap()).collect();
assert_eq!(status_names, ["Poison Tint", "Freeze Edge", "Burn Aura", "Stun Pulse", "Stealth Fade", "Shield Buff"]);
let textures = run(&mut s, "graphicStyle.library", json!({"library": "textures"}));
let texture_names: Vec<&str> = textures["styles"].as_array().unwrap().iter().map(|x| x["name"].as_str().unwrap()).collect();
let expected = ["Fine Grain", "Canvas Weave", "Burlap Sack", "Cracked Glaze", "Mosaic Floor", "Quilted Squares", "Leaded Glass", "Damp Paper"];
assert_eq!(texture_names, expected);
let id = rect(&mut s, 10.0, 20.0, 40.0, 30.0);
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "game-hud", "name": "Mana Glow", "apply": true, "ids": [id.0]}));
assert_eq!((names(&r["added"]), r["applied"].as_str()), (vec!["Mana Glow"], Some("Mana Glow")));
let node = doc(&s).node(id).unwrap();
assert_eq!(node.appearance.fill_paint(), Paint::solid(vectorcraft_color::Color::from_hex("#3498db").unwrap()));
assert_eq!(node.appearance.stroke_paint(), Paint::None);
assert_eq!(node.appearance.effects.iter().map(|e| e.id.as_str()).collect::<Vec<_>>(), ["stylize.outerGlow"]);
let id2 = rect(&mut s, 60.0, 20.0, 40.0, 30.0);
let r2 = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "status-effects", "name": "Poison Tint", "apply": true, "ids": [id2.0]}));
assert_eq!((names(&r2["added"]), r2["applied"].as_str()), (vec!["Poison Tint"], Some("Poison Tint")));
let node2 = doc(&s).node(id2).unwrap();
assert_eq!(node2.appearance.fill_paint(), Paint::solid(vectorcraft_color::Color::from_hex("#2ecc71").unwrap()));
assert_eq!(node2.appearance.effects.iter().map(|e| e.id.as_str()).collect::<Vec<_>>(), ["stylize.outerGlow"]);
// By name in any case.
assert_eq!(run(&mut s, "graphicStyle.library", json!({"library": "shadows and glows"}))["id"], "shadows-glows");
assert!(s.execute("graphicStyle.library", &json!({"library": "nope"})).is_err());
}
#[test]
fn painted_and_sketched_styles_set_every_effect_setting_and_reopen_applied() {
let mut s = session();
let list = run(&mut s, "graphicStyle.libraries", json!({}));
let libs = list["libraries"].as_array().unwrap();
let at = libs.iter().position(|l| l["id"] == "painted-sketched").expect("listed");
assert_eq!((libs[at]["name"].as_str(), libs[at]["count"].as_u64()), (Some("Painted and Sketched"), Some(7)));
assert_eq!(libs[at - 1]["id"], "textures");
let g = run(&mut s, "graphicStyle.library", json!({"library": "Painted and Sketched"}));
let listed: Vec<(&str, &str, Vec<&str>)> =
g["styles"].as_array().unwrap().iter().map(|x| (x["name"].as_str().unwrap(), x["fill"].as_str().unwrap(), names(&x["effects"]))).collect();
let want = [
("Washed Blue", "#8ab9c9", "artistic.watercolor"),
("Bristle Ochre", "#c4975a", "artistic.dryBrush"),
("Pastel Heather", "#a78ca5", "artistic.roughPastels"),
("Charcoal Shade", "#666666", "sketch.charcoal"),
("Ink Contour", "#d9e2df", "brushStrokes.inkOutlines"),
("Wet Ink", "#637b85", "brushStrokes.sumiE"),
("Fine Pen", "#d8d4c6", "sketch.graphicPen"),
];
assert_eq!(listed, want.iter().map(|&(n, f, e)| (n, f, vec![e])).collect::<Vec<_>>());
// Every setting of each effect is given, with the catalog's key and kind of value.
let (_, lib) = cmd::stylelib::library(&s, "painted-sketched").unwrap();
for st in &lib.styles {
let e = &st.appearance.effects[0];
let defaults = vectorcraft_render::effects::effect_info(&e.id).unwrap().defaults;
let (set, all) = (e.params.as_object().unwrap(), defaults.as_object().unwrap());
let keys = |m: &serde_json::Map<String, Value>| m.keys().cloned().collect::<Vec<_>>();
assert_eq!(keys(set), keys(all), "{}", st.name);
for (k, v) in set {
let kind = |v: &Value| (v.is_number(), v.is_string(), v.is_boolean());
assert_eq!(kind(v), kind(&all[k]), "{} / {k}", st.name);
}
}
let strokes: Vec<(&str, f64)> =
lib.styles.iter().filter(|g| g.appearance.stroke_paint() != Paint::None).map(|g| (g.name.as_str(), g.appearance.stroke_width())).collect();
assert_eq!(strokes, [("Bristle Ochre", 1.0), ("Ink Contour", 1.0)]);
// Each one adds and applies to a rectangle in one undo step.
let mut ids = vec![];
for (i, (name, fill, effect)) in want.iter().enumerate() {
let id = rect(&mut s, 10.0 + 25.0 * i as f64, 20.0, 20.0, 30.0);
let before = undo_len(&s);
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "painted-sketched", "name": name, "apply": true, "ids": [id.0]}));
assert_eq!((names(&r["added"]), r["applied"].as_str()), (vec![*name], Some(*name)));
assert_eq!(undo_len(&s), before + 1, "{name}");
let node = doc(&s).node(id).unwrap();
assert_eq!(node.appearance.fill_paint(), Paint::solid(vectorcraft_color::Color::from_hex(fill).unwrap()), "{name}");
assert_eq!(node.appearance.effects.iter().map(|e| e.id.as_str()).collect::<Vec<_>>(), [*effect]);
assert_eq!(node.appearance.effects[0].params, lib.style(name).unwrap().appearance.effects[0].params, "{name}");
ids.push((id, *name));
}
// Adding one again reuses it: no new style, no undo step.
let before = (undo_len(&s), doc(&s).graphic_styles.len());
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "painted-sketched", "name": "Wet Ink"}));
assert_eq!((names(&r["added"]), names(&r["existing"])), (vec![], vec!["Wet Ink"]));
assert_eq!((undo_len(&s), doc(&s).graphic_styles.len()), before);
// An unknown style or library changes nothing.
assert!(s.execute("graphicStyle.addFromLibrary", &json!({"library": "painted-sketched", "names": ["Wet Ink", "Nope"]})).is_err());
assert!(s.execute("graphicStyle.addFromLibrary", &json!({"library": "painted-and-sketched", "name": "Wet Ink"})).is_err());
assert_eq!((undo_len(&s), doc(&s).graphic_styles.len()), before);
// Saved and opened again, the styles keep their effects and the rectangles stay linked to them.
let b64 = run(&mut s, "document.serialize", json!({}))["dataBase64"].as_str().unwrap().to_string();
run(&mut s, "document.open", json!({"name": "painted.vectorcraft", "dataBase64": b64}));
for (id, name) in &ids {
let d = doc(&s);
let style = d.graphic_style(name).unwrap();
assert!(style.same_look(lib.style(name).unwrap()), "{name}");
let node = d.node(*id).unwrap();
assert_eq!((node.extras().graphic_style, &node.appearance.effects), (Some(style.id), &style.appearance.effects), "{name}");
}
// With nothing selected, `apply` only adds; empty `names` adds the whole library in order.
run(&mut s, "file.new", json!({"width": 100, "height": 100}));
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "painted-sketched", "name": "Fine Pen", "apply": true}));
assert!(r.get("applied").is_none() && doc(&s).graphic_style("Fine Pen").is_some());
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "painted-sketched", "names": []}));
assert_eq!((names(&r["added"]), names(&r["existing"])), (want[..6].iter().map(|w| w.0).collect::<Vec<_>>(), vec!["Fine Pen"]));
}
#[test]
fn add_dedupes_names_as_one_undo_step() {
let mut s = session();
let before = (undo_len(&s), doc(&s).graphic_styles.len());
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "shadows-glows", "names": ["Halo", "Ember", "Halo"]}));
assert_eq!((names(&r["added"]), names(&r["existing"])), (vec!["Halo", "Ember"], vec![]));
assert_eq!(undo_len(&s), before.0 + 1, "one undo step");
let d = doc(&s);
assert_eq!(d.graphic_styles.len(), before.1 + 2);
let halo = d.graphic_style("Halo").unwrap();
assert!(halo.id != 0 && d.graphic_styles.iter().filter(|g| g.id == halo.id).count() == 1, "a fresh id");
// Adding again finds them and changes nothing (no undo step).
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "shadows-glows", "name": "Halo"}));
assert_eq!((names(&r["added"]), names(&r["existing"])), (vec![], vec!["Halo"]));
assert_eq!(undo_len(&s), before.0 + 1);
// A name taken by another look gets a number.
let id = rect(&mut s, 0.0, 0.0, 20.0, 20.0);
run(&mut s, "graphicStyle.new", json!({"name": "Feathered", "id": id.0}));
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "shadows-glows", "names": ["Feathered"]}));
assert_eq!(names(&r["added"]), ["Feathered 2"]);
assert_eq!(doc(&s).graphic_style("Feathered 2").unwrap().appearance.effects[0].id, "stylize.feather");
// The whole library; undo removes a whole add.
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "hand-drawn"}));
let all = cmd::stylelib::library(&s, "hand-drawn").unwrap().1.len();
assert_eq!(r["added"].as_array().unwrap().len(), all);
run(&mut s, "edit.undo", json!({}));
assert!(doc(&s).graphic_style("Sketchy Line").is_none() && doc(&s).graphic_style("Feathered 2").is_some());
assert!(s.execute("graphicStyle.addFromLibrary", &json!({"library": "hand-drawn", "name": "Nope"})).is_err());
}
#[test]
fn add_with_apply_styles_the_selection_in_the_same_undo_step() {
let mut s = session();
let id = rect(&mut s, 10.0, 20.0, 100.0, 50.0);
let before = undo_len(&s);
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "gradient-finishes", "name": "Spotlight", "apply": true}));
assert_eq!(r["applied"], "Spotlight");
assert_eq!(undo_len(&s), before + 1, "added and applied as one step");
let (g, linked) = s.selection_graphic_style().unwrap();
assert_eq!((g.name.as_str(), linked), ("Spotlight", true));
// The unit-box gradient lands at the same place relative to the object's bounds.
let Paint::Gradient(gp) = doc(&s).node(id).unwrap().appearance.fill_paint() else { panic!("a gradient") };
let geom = gp.geom.unwrap();
assert!((geom.start - Point::new(10.0 + 0.35 * 100.0, 20.0 + 0.3 * 50.0)).hypot() < 1e-6, "{geom:?}");
run(&mut s, "edit.undo", json!({}));
assert!(doc(&s).graphic_style("Spotlight").is_none());
assert_eq!(doc(&s).node(id).unwrap().extras().graphic_style, None);
// Without a selection it only adds; `ids` targets objects; an existing style is applied as is.
run(&mut s, "select.none", json!({}));
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "blends-transparency", "name": "Ghosted", "apply": true}));
assert!(r.get("applied").is_none() && doc(&s).graphic_style("Ghosted").is_some());
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "blends-transparency", "name": "Ghosted", "apply": true, "ids": [id.0]}));
assert_eq!((names(&r["existing"]), r["applied"].as_str()), (vec!["Ghosted"], Some("Ghosted")));
assert_eq!(doc(&s).node(id).unwrap().opacity, 0.4);
}
#[test]
fn add_with_null_ids_and_no_selection_only_adds_style() {
let mut omitted = session();
let mut null_ids = session();
let params = json!({"library": "shadows-glows", "name": "Halo", "apply": true});
let expected = run(&mut omitted, "graphicStyle.addFromLibrary", params.clone());
let mut with_null = params;
with_null["ids"] = Value::Null;
let actual = run(&mut null_ids, "graphicStyle.addFromLibrary", with_null);
assert_eq!(actual, expected);
assert_eq!(names(&actual["added"]), ["Halo"]);
assert!(actual.get("applied").is_none());
assert!(doc(&null_ids).graphic_style("Halo").is_some());
assert_eq!(undo_len(&null_ids), undo_len(&omitted));
}
#[test]
fn add_with_null_ids_applies_to_selection() {
let mut s = session();
let id = rect(&mut s, 10.0, 20.0, 100.0, 50.0);
let before = undo_len(&s);
let result = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "shadows-glows", "name": "Halo", "apply": true, "ids": null}));
assert_eq!(result["applied"], "Halo");
assert_eq!(undo_len(&s), before + 1);
let (style, linked) = s.selection_graphic_style().unwrap();
assert_eq!((style.name.as_str(), linked), ("Halo", true));
assert_eq!(doc(&s).node(id).unwrap().extras().graphic_style, Some(style.id));
}
#[test]
fn textures_styles_apply_their_texture_effects_and_keep_their_params_through_save_and_open() {
let mut s = session();
let (_, lib) = cmd::stylelib::library(&s, "textures").unwrap();
let mut ids = vec![];
for (i, g) in lib.styles.iter().enumerate() {
let id = rect(&mut s, (i % 4) as f64 * 50.0, (i / 4) as f64 * 50.0, 40.0, 40.0);
let before = undo_len(&s);
let r = run(&mut s, "graphicStyle.addFromLibrary", json!({"library": "textures", "name": g.name, "apply": true, "ids": [id.0]}));
assert_eq!((names(&r["added"]), r["applied"].as_str()), (vec![g.name.as_str()], Some(g.name.as_str())));
assert_eq!(undo_len(&s), before + 1, "{}: added and applied as one step", g.name);
ids.push(id);
}
let effects = |s: &Session| -> Vec<Vec<(String, Value)>> {
ids.iter().map(|id| doc(s).node(*id).unwrap().appearance.effects.iter().map(|e| (e.id.clone(), e.params.clone())).collect()).collect()
};
let applied = effects(&s);
let effect_ids: Vec<Vec<&str>> = applied.iter().map(|fx| fx.iter().map(|(id, _)| id.as_str()).collect()).collect();
assert_eq!(
effect_ids,
[
["texture.grain"],
["texture.texturizer"],
["texture.texturizer"],
["texture.craquelure"],
["texture.mosaicTiles"],
["texture.patchwork"],
["texture.stainedGlass"],
["sketch.waterPaper"],
]
);
assert_eq!(applied[1][0].1["texture"], "canvas");
assert_eq!(applied[3][0].1["crackSpacing"], 12.0);
for (fx, g) in applied.iter().zip(&lib.styles) {
let want: Vec<(String, Value)> = g.appearance.effects.iter().map(|e| (e.id.clone(), e.params.clone())).collect();
assert_eq!(fx, &want, "{} applies the library's params", g.name);
}
let b64 = run(&mut s, "document.serialize", json!({"format": "vectorcraft"}))["dataBase64"].as_str().unwrap().to_string();
run(&mut s, "document.open", json!({"name": "textures.vectorcraft", "dataBase64": b64}));
assert_eq!(effects(&s), applied, "the params survive a save and open");
for g in &lib.styles {
let saved = doc(&s).graphic_style(&g.name).unwrap();
assert!(saved.same_look(g), "{} keeps its look", g.name);
assert!(ids.iter().any(|id| doc(&s).node(*id).unwrap().extras().graphic_style == Some(saved.id)), "{} stays linked", g.name);
}
}
/// A document with pattern `name` (a dot) and its swatch.
fn add_pattern(s: &mut Session, name: &str, radius: f64) {
s.edit("Pattern", |d, _| {
let dot = Node::path(d.alloc_id(), shapes::ellipse(Rect::new(0.0, 0.0, radius, radius)), vectorcraft_doc::Appearance::default_art());
d.patterns.push(PatternDef { tile: Rect::new(0.0, 0.0, 10.0, 10.0), ..PatternDef::new(name, vec![Arc::new(dot)]) });
d.swatches.push(Swatch { name: name.into(), paint: pattern(name), global: false, spot: false });
Ok(())
})
.unwrap();
}
fn pattern(name: &str) -> Paint {
Paint::Pattern { pattern: name.into(), xf: Default::default() }
}
#[test]
fn save_and_load_round_trip_with_patterns_and_unlinked_swatches() {
let mut s = session();
add_pattern(&mut s, "Dots", 4.0);
run(&mut s, "swatch.edit", json!({"name": "Red", "global": true}));
let id = rect(&mut s, 0.0, 0.0, 40.0, 40.0);
run(&mut s, "paint.setFill", json!({"swatch": "Dots"}));
run(&mut s, "paint.setStroke", json!({"swatch": "Red"}));
run(&mut s, "transparency.set", json!({"opacity": 50, "blend": "Multiply"}));
run(&mut s, "graphicStyle.new", json!({"name": "Dotted", "id": id.0}));
let r = run(&mut s, "graphicStyle.saveLibrary", json!({"names": ["Dotted", "Sunshine"], "name": "Kit"}));
assert_eq!(r["count"], 2);
let data = r["data"].as_str().unwrap().to_string();
assert!(data.contains("\"vcstyles\""));
let r = run(&mut s, "graphicStyle.loadLibrary", json!({"data": data, "name": "kit.vcstyles"}));
assert_eq!((r["name"].as_str(), r["count"].as_u64()), (Some("Kit"), Some(2)));
let lib_id = r["library"].as_str().unwrap().to_string();
let (_, lib) = cmd::stylelib::library(&s, &lib_id).unwrap();
let dotted = lib.style("Dotted").unwrap();
let mine = doc(&s).graphic_style("Dotted").unwrap();
assert!(dotted.same_look(&mine.standalone()) && (dotted.opacity, dotted.id) == (0.5, 0));
let red = doc(&s).swatch("Red").and_then(|w| w.paint.color()).unwrap();
assert_eq!(mine.appearance.stroke_paint(), Paint::Solid { color: red, swatch: Some("Red".into()), tint: 1.0 });
assert_eq!(dotted.appearance.stroke_paint(), Paint::solid(red), "unlinked from the swatch");
assert_eq!(lib.patterns.len(), 1);
let list = run(&mut s, "graphicStyle.libraries", json!({}));
assert!(list["libraries"].as_array().unwrap().iter().any(|l| l["id"] == lib_id.as_str() && l["category"] == "loaded"));
// Into another document: the pattern comes along (with its swatch), once.
run(&mut s, "file.new", json!({"width": 100, "height": 100}));
run(&mut s, "graphicStyle.addFromLibrary", json!({"library": lib_id, "name": "Dotted"}));
let d = doc(&s);
assert_eq!(d.pattern("Dots"), lib.patterns.first());
assert!(d.swatch("Dots").is_some());
assert_eq!(d.graphic_style("Dotted").unwrap().appearance.fill_paint(), pattern("Dots"));
// A document whose "Dots" is another pattern gets the library's under a free name.
run(&mut s, "file.new", json!({"width": 100, "height": 100}));
add_pattern(&mut s, "Dots", 8.0);
run(&mut s, "graphicStyle.addFromLibrary", json!({"library": lib_id}));
let d = doc(&s);
assert_eq!(d.graphic_style("Dotted").unwrap().appearance.fill_paint(), pattern("Dots 2"));
assert_eq!(d.patterns.len(), 2);
assert!(s.execute("graphicStyle.saveLibrary", &json!({"names": ["Nope"]})).is_err());
assert!(s.execute("graphicStyle.saveLibrary", &json!({"user": true})).is_err(), "no user folder in a headless session");
}
#[test]
fn user_libraries_and_other_documents_styles() {
let dir = std::env::temp_dir().join(format!("vc-stylelib-user-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let mut s = session();
s.style_libraries.set_user_dir(Some(dir.to_string_lossy().to_string()));
let r = run(&mut s, "graphicStyle.saveLibrary", json!({"user": true, "name": "My: Looks"}));
assert_eq!(r["library"], "user/My- Looks.vcstyles");
let path = r["path"].as_str().unwrap().to_string();
let list = run(&mut s, "graphicStyle.libraries", json!({}));
let user: Vec<&Value> = list["libraries"].as_array().unwrap().iter().filter(|l| l["category"] == "user").collect();
assert_eq!(user.len(), 1);
assert_eq!((user[0]["name"].as_str(), user[0]["count"].as_u64()), (Some("My: Looks"), Some(doc(&s).graphic_styles.len() as u64)));
assert_eq!(list["userFolder"].as_str(), Some(dir.to_string_lossy().as_ref()));
// Opening a file of the user folder gives its User Defined library, and so does a file with the
// same bytes in another folder.
assert_eq!(run(&mut s, "graphicStyle.loadLibrary", json!({ "path": path }))["library"], "user/My- Looks.vcstyles");
let elsewhere = dir.with_file_name(format!("vc-stylelib-elsewhere-{}", std::process::id()));
std::fs::create_dir_all(&elsewhere).unwrap();
let copy = elsewhere.join("Looks.vcstyles");
std::fs::copy(&path, &copy).unwrap();
assert_eq!(run(&mut s, "graphicStyle.loadLibrary", json!({ "path": copy.to_string_lossy() }))["library"], "user/My- Looks.vcstyles");
let _ = std::fs::remove_dir_all(elsewhere);
let _ = std::fs::remove_dir_all(dir);
// Another document's graphic styles load as a library.
let id = rect(&mut s, 0.0, 0.0, 10.0, 10.0);
run(&mut s, "graphicStyle.new", json!({"name": "Poster Look", "id": id.0}));
let b64 = run(&mut s, "document.serialize", json!({}))["dataBase64"].as_str().unwrap().to_string();
let r = run(&mut s, "graphicStyle.loadLibrary", json!({"dataBase64": b64, "name": "Poster.vectorcraft"}));
assert_eq!(r["name"], "Poster");
let (_, lib) = cmd::stylelib::library(&s, r["library"].as_str().unwrap()).unwrap();
assert!(lib.style("Poster Look").is_some_and(|g: &GraphicStyle| g.id == 0));
assert!(s.execute("graphicStyle.loadLibrary", &json!({"data": "hello", "name": "x.txt"})).is_err());
}
#[test]
fn every_builtin_style_paints_visibly_on_a_rectangle() {
let mut s = session();
let mut renderer = vectorcraft_render::Renderer::new();
for b in vectorcraft_doc::style_libs::STYLE_LIBRARIES {
let (_, lib) = cmd::stylelib::library(&s, b.id).unwrap();
for g in &lib.styles {
run(&mut s, "file.new", json!({"width": 100, "height": 100}));
// Art under the rectangle: a blend mode shows only against art, not the bare page.
let under = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
run(&mut s, "paint.setFill", json!({"color": "#336699", "ids": [under.0]}));
run(&mut s, "paint.setStroke", json!({"none": true, "ids": [under.0]}));
let id = rect(&mut s, 25.0, 30.0, 50.0, 40.0);
run(&mut s, "graphicStyle.addFromLibrary", json!({"library": b.id, "name": g.name, "apply": true}));
assert_eq!(doc(&s).node(id).unwrap().extras().graphic_style, doc(&s).graphic_style(&g.name).map(|x| x.id), "{}", g.name);
let img = renderer.render_region(doc(&s), Rect::new(0.0, 0.0, 100.0, 100.0), 0.5, true);
let bare = img.pixel(0, 0);
let painted = (0..img.height).flat_map(|y| (0..img.width).map(move |x| (x, y))).filter(|&(x, y)| img.pixel(x, y) != bare).count();
assert!(painted > 40, "{} / {} paints {painted} pixels", b.id, g.name);
}
}
}