From 650c5a00a642ee4178a2c15fce3b7fc2e54b573f Mon Sep 17 00:00:00 2001 From: japbcoelho Date: Thu, 8 Oct 2026 21:23:53 -0300 Subject: [PATCH] Color params: one scale per CMYK colour, not per component paint.setFill {"color": {"c": 18.16, "m": 13.39, "y": 13.9, "k": 0.79}} stored K 79 %: recolor.colors listed the colour as "cmyk 18.16 13.39 13.9 79" and the exported PDF printed it as a dark grey. color_value chose the scale for each component on its own (above 1: percent, else a fraction), so a percentage of 1 or less beside larger ones became a fraction. A {c, m, y, k} object now takes percent for every component as soon as one is above 1, and fractions only when all four are within 0..1, as before. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/mod.rs | 10 +++++++--- crates/engine/src/tests_recolor.rs | 13 +++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index 64d57147..81c878c9 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -363,7 +363,7 @@ pub fn matrix_param(p: &Value, key: &str) -> Option { Some(Affine::new(c)) } /// A colour from `"#rrggbb"`, a colour key (`"cmyk 0 100 100 0"`, `"lab 55 60 40"`), `[r,g,b]` (0..1), -/// `{"c":..,"m":..,"y":..,"k":..}`, `{"gray":..}` or `{"l":..,"a":..,"b":..}` (CIE Lab: L 0..100, a and b about +/// `{"c":..,"m":..,"y":..,"k":..}` (percent when any component is above 1, else 0..1), `{"gray":..}` or `{"l":..,"a":..,"b":..}` (CIE Lab: L 0..100, a and b about /// −128..127). pub fn color_value(v: &Value) -> Option { match v { @@ -372,8 +372,12 @@ pub fn color_value(v: &Value) -> Option { Value::Array(a) if a.len() >= 3 => Some(Color::rgb(a[0].as_f64()? as f32, a[1].as_f64()? as f32, a[2].as_f64()? as f32)), Value::Object(o) => { if let (Some(c), Some(m), Some(y), Some(k)) = (o.get("c"), o.get("m"), o.get("y"), o.get("k")) { - let f = |v: &Value| v.as_f64().map(|x| if x > 1.0 { x / 100.0 } else { x } as f32); - return Some(Color::cmyk(f(c)?, f(m)?, f(y)?, f(k)?)); + // One scale for the whole colour: percent when any component is above 1, else 0..1. + // Deciding per component read {c: 18, k: 0.79} as K 79 %. + let v = [c.as_f64()?, m.as_f64()?, y.as_f64()?, k.as_f64()?]; + let scale = if v.iter().any(|x| *x > 1.0) { 100.0 } else { 1.0 }; + let f = |x: f64| (x / scale) as f32; + return Some(Color::cmyk(f(v[0]), f(v[1]), f(v[2]), f(v[3]))); } if let Some(g) = o.get("gray") { let g = g.as_f64()?; diff --git a/crates/engine/src/tests_recolor.rs b/crates/engine/src/tests_recolor.rs index a8de2898..a1a3676d 100644 --- a/crates/engine/src/tests_recolor.rs +++ b/crates/engine/src/tests_recolor.rs @@ -303,3 +303,16 @@ fn edit_group_refuses_to_change_built_in_swatches() { let ok = run(&mut s, "swatch.editGroup", json!({"group": "Marks", "colors": [key, "#0000ff"]})); assert_eq!(ok["swatches"][0], vectorcraft_color::swatch::REGISTRATION, "kept as it is"); } + +/// A `{c, m, y, k}` object takes one scale for the whole colour: percent as soon as any component is +/// above 1, else fractions. Reading each component on its own turned K 0.79 % into 79 %. +#[test] +fn cmyk_object_reads_one_scale_for_every_component() { + let k = |v: Value| color_value(&v).unwrap().to_cmyk(); + let close = |a: [f32; 4], b: [f32; 4]| a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-5); + assert!(close(k(json!({"c": 18.16, "m": 13.39, "y": 13.9, "k": 0.79})), [0.1816, 0.1339, 0.139, 0.0079])); + assert!(close(k(json!({"c": 100, "m": 0.5, "y": 0, "k": 1})), [1.0, 0.005, 0.0, 0.01])); + // All components 0..1: fractions, as before. + assert!(close(k(json!({"c": 0.1, "m": 0.5, "y": 0.9, "k": 0.2})), [0.1, 0.5, 0.9, 0.2])); + assert!(close(k(json!({"c": 0, "m": 1, "y": 1, "k": 0})), [0.0, 1.0, 1.0, 0.0])); +}