mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 03:06:57 -06:00
M3.9: swatches in colour groups are first-class — Document Color Mode converts them, they can be spot colours with their own plate and PDF Separation, and Document Info counts them
Document::swatches_iter/_mut, swatch_mut, swatch_group_of, remove_swatch and a copy-on-write walk over linked solid paints (map_solid_paints) cover every colour group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
fb8c41d8e3
commit
00a0874d4d
@@ -257,7 +257,7 @@ fn convert_mode(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
n
|
||||
});
|
||||
for sw in &mut d.swatches {
|
||||
for sw in d.swatches_iter_mut() {
|
||||
match &mut sw.paint {
|
||||
Paint::Solid { color, .. } => *color = conv(color),
|
||||
Paint::Gradient(g) => {
|
||||
@@ -486,14 +486,14 @@ fn set_spot(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "swatch.setSpot";
|
||||
let name = str_param(p, "name").ok_or_else(|| bad(C, "missing `name`"))?.to_string();
|
||||
let spot = bool_or(p, "spot", true);
|
||||
let sw = s.doc()?.doc.swatches.iter().find(|w| w.name == name).ok_or_else(|| bad(C, format!("no swatch `{name}`")))?;
|
||||
let sw = s.doc()?.doc.swatch(&name).ok_or_else(|| bad(C, format!("no swatch `{name}`")))?;
|
||||
if !matches!(sw.paint, Paint::Solid { .. }) {
|
||||
return Err(bad(C, "only solid-colour swatches can be spot colours"));
|
||||
}
|
||||
if sw.spot != spot {
|
||||
let n = name.clone();
|
||||
s.edit("Swatch Options", |d, _| {
|
||||
if let Some(w) = d.swatches.iter_mut().find(|w| w.name == n) {
|
||||
if let Some(w) = d.swatch_mut(&n) {
|
||||
w.spot = spot;
|
||||
if spot {
|
||||
w.global = true;
|
||||
|
||||
@@ -113,7 +113,7 @@ fn info(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
"images": images,
|
||||
"symbols": symbols,
|
||||
"spotColors": spot,
|
||||
"swatches": d.swatches.len(),
|
||||
"swatches": d.swatches_iter().count(),
|
||||
"graphicStyles": d.graphic_styles.len(),
|
||||
"characterStyles": d.char_styles.len(),
|
||||
"paragraphStyles": d.para_styles.len(),
|
||||
|
||||
@@ -469,7 +469,7 @@ fn color_mode(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
for l in &mut d.layers {
|
||||
map_node_colors(Arc::make_mut(l), &f, true, true);
|
||||
}
|
||||
for sw in &mut d.swatches {
|
||||
for sw in d.swatches_iter_mut() {
|
||||
if let vectorcraft_color::Paint::Solid { color, .. } = &mut sw.paint {
|
||||
*color = f(*color);
|
||||
}
|
||||
|
||||
@@ -733,6 +733,8 @@ mod tests_pattern;
|
||||
#[cfg(test)]
|
||||
mod tests_prefs;
|
||||
#[cfg(test)]
|
||||
mod tests_swatches;
|
||||
#[cfg(test)]
|
||||
mod tests_textedit;
|
||||
#[cfg(test)]
|
||||
mod tests_xform;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
//! Swatches: colour groups as first-class swatch homes (colour mode, spot plates, PDF, Document
|
||||
//! Info), naming and kinds, Swatch Options edits reaching linked art, deleting and new groups.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn session() -> Session {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 100, "height": 100})).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 rect(s: &mut Session) -> NodeId {
|
||||
NodeId(run(s, "shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10}))["id"].as_u64().unwrap())
|
||||
}
|
||||
|
||||
fn doc(s: &Session) -> &vectorcraft_doc::Document {
|
||||
&s.doc().unwrap().doc
|
||||
}
|
||||
|
||||
/// Put a solid swatch into colour group `group` directly in the model (no command creates one in a
|
||||
/// group before M3.13).
|
||||
fn grouped_swatch(s: &mut Session, group: &str, name: &str, color: Color) {
|
||||
let d = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc);
|
||||
let g = d.swatch_groups.iter_mut().find(|g| g.name == group).unwrap();
|
||||
g.swatches.push(vectorcraft_color::Swatch { name: name.into(), paint: Paint::solid(color), global: true, spot: false });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_color_mode_converts_grouped_swatches() {
|
||||
let mut s = session();
|
||||
grouped_swatch(&mut s, "Grays", "Ink", Color::rgb(0.2, 0.4, 0.6));
|
||||
run(&mut s, "object.convertDocumentColorMode", json!({"mode": "cmyk"}));
|
||||
for g in &doc(&s).swatch_groups {
|
||||
for sw in &g.swatches {
|
||||
assert!(!matches!(sw.paint.color(), Some(Color::Rgb { .. })), "{} in {} is still RGB", sw.name, g.name);
|
||||
}
|
||||
}
|
||||
assert!(matches!(doc(&s).swatch("Ink").unwrap().paint.color(), Some(Color::Cmyk { .. })));
|
||||
// The File menu's mode switch converts them as well.
|
||||
let mut s = session();
|
||||
run(&mut s, "file.documentColorMode", json!({"mode": "cmyk"}));
|
||||
assert!(matches!(doc(&s).swatch("Bright Red").unwrap().paint.color(), Some(Color::Cmyk { .. })));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn grouped_spot_swatches_print_on_their_own_plate() {
|
||||
let mut s = session();
|
||||
grouped_swatch(&mut s, "Brights", "Signal Orange", Color::cmyk(0.0, 0.6, 1.0, 0.0));
|
||||
assert_eq!(run(&mut s, "swatch.setSpot", json!({"name": "Signal Orange"})), json!({"name": "Signal Orange", "spot": true}));
|
||||
assert!(doc(&s).swatch("Signal Orange").is_some_and(|w| w.spot && w.global));
|
||||
let plates = run(&mut s, "color.plates", json!({}));
|
||||
assert!(plates["plates"].as_array().unwrap().iter().any(|p| p["name"] == "Signal Orange" && p["spot"] == true), "{plates}");
|
||||
let a = rect(&mut s);
|
||||
run(&mut s, "paint.setFill", json!({"ids": [a.0], "swatch": "Signal Orange"}));
|
||||
let bytes = vectorcraft_pdf::export(doc(&s), &vectorcraft_pdf::PdfOptions { compress: false, created: Some(0), ..Default::default() }).unwrap();
|
||||
let pdf = String::from_utf8_lossy(&bytes);
|
||||
assert!(pdf.contains("/Separation") && pdf.contains("Signal#20Orange"), "a grouped spot swatch is a Separation");
|
||||
let info = run(&mut s, "document.info", json!({}));
|
||||
assert_eq!(info["spotColors"], json!(["Signal Orange"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_info_counts_grouped_swatches() {
|
||||
let mut s = session();
|
||||
let d = doc(&s);
|
||||
let all = d.swatches.len() + d.swatch_groups.iter().map(|g| g.swatches.len()).sum::<usize>();
|
||||
assert!(all > d.swatches.len());
|
||||
assert_eq!(run(&mut s, "document.info", json!({}))["swatches"], json!(all));
|
||||
}
|
||||
Reference in New Issue
Block a user