mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 22:28:09 +00:00
Merge pull request #6214 from el-j/fix/ai-import-registration-style-6205
Fix #6205: support Registration and named swatches in AI art styles
This commit is contained in:
@@ -67,20 +67,29 @@ pub(super) fn color_op(op: &str, vals: &[V]) -> Option<(Paint, Option<Named>)> {
|
||||
"x" => named(vals, Some(false)),
|
||||
"xx" => named(vals, None),
|
||||
"xk" => named(vals, None).map(|(p, n)| (p, n.map(|n| Named { spot: false, ..n }))),
|
||||
"xs" | "xz" => registration(vals),
|
||||
"xs" | "xz" => registration(op, vals),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_registration(name: &str) -> bool {
|
||||
matches!(name, vectorcraft_color::swatch::REGISTRATION | "Registration" | "/All" | "All" | "/[Registration]")
|
||||
}
|
||||
|
||||
/// `c m y k (name) tint x` or `c m y k [r g b | L a b] (name) tint type Xx` (type 1: RGB, 2: Lab);
|
||||
/// `rgb` forces RGB or CMYK.
|
||||
fn named(vals: &[V], rgb: Option<bool>) -> Option<(Paint, Option<Named>)> {
|
||||
let at = vals.iter().rposition(|v| matches!(v, V::Str(_)))?;
|
||||
let at = vals.iter().rposition(|v| matches!(v, V::Str(_) | V::Name(_)))?;
|
||||
let name = vals.get(at)?.text()?;
|
||||
let before: Vec<f32> = vals.get(..at)?.iter().filter_map(V::num).map(|v| v as f32).collect();
|
||||
let after: Vec<f32> = vals.get(at + 1..)?.iter().filter_map(V::num).map(|v| v as f32).collect();
|
||||
let unit = |v: f32| v.clamp(0.0, 1.0);
|
||||
let tint = 1.0 - unit(*after.first()?);
|
||||
if is_registration(&name) {
|
||||
let full = Color::cmyk(1.0, 1.0, 1.0, 1.0);
|
||||
let paint = Paint::Solid { color: full.tinted(tint), swatch: Some(vectorcraft_color::swatch::REGISTRATION.into()), tint };
|
||||
return Some((paint, None));
|
||||
}
|
||||
let n = before.len();
|
||||
let kind = after.get(1).copied();
|
||||
let color = if rgb.unwrap_or(kind == Some(1.0)) {
|
||||
@@ -102,9 +111,20 @@ fn named(vals: &[V], rgb: Option<bool>) -> Option<(Paint, Option<Named>)> {
|
||||
|
||||
/// `… (name) tint Xs` / `… (name) tint type Xz`: the built-in Registration swatch at the tint (its
|
||||
/// own colour, whatever values the operands give).
|
||||
fn registration(vals: &[V]) -> Option<(Paint, Option<Named>)> {
|
||||
let at = vals.iter().rposition(|v| matches!(v, V::Str(_)))?;
|
||||
let tint = 1.0 - vals.get(at + 1).and_then(V::num).unwrap_or(0.0).clamp(0.0, 1.0) as f32;
|
||||
fn registration(op: &str, vals: &[V]) -> Option<(Paint, Option<Named>)> {
|
||||
let at = vals.iter().rposition(|v| matches!(v, V::Str(_) | V::Name(_)));
|
||||
let tint = if let Some(i) = at {
|
||||
1.0 - vals.get(i + 1).and_then(V::num).unwrap_or(0.0).clamp(0.0, 1.0) as f32
|
||||
} else {
|
||||
let mut nums = vals.iter().rev().filter_map(V::num);
|
||||
let t = if op.eq_ignore_ascii_case("xz") {
|
||||
let _type = nums.next();
|
||||
nums.next().unwrap_or(0.0)
|
||||
} else {
|
||||
nums.next().unwrap_or(0.0)
|
||||
};
|
||||
1.0 - t.clamp(0.0, 1.0) as f32
|
||||
};
|
||||
let full = Color::cmyk(1.0, 1.0, 1.0, 1.0);
|
||||
Some((Paint::Solid { color: full.tinted(tint), swatch: Some(vectorcraft_color::swatch::REGISTRATION.into()), tint }, None))
|
||||
}
|
||||
@@ -512,6 +532,29 @@ mod tests {
|
||||
let mut v = nums(&[1.0, 1.0, 1.0, 1.0]);
|
||||
v.extend([V::Str(b"[Registration]".to_vec()), V::Num(0.0)]);
|
||||
assert!(matches!(color_op("Xs", &v), Some((Paint::Solid { tint, .. }, None)) if tint == 1.0));
|
||||
let mut v = nums(&[1.0, 1.0, 1.0, 1.0]);
|
||||
v.extend([V::Str(b"[Registration]".to_vec()), V::Num(1.0)]);
|
||||
assert!(
|
||||
matches!(color_op("Xs", &v), Some((Paint::Solid { tint, color, .. }, None)) if tint == 0.0 && color == Color::cmyk(1.0, 1.0, 1.0, 1.0).tinted(0.0))
|
||||
);
|
||||
let mut v = nums(&[1.0, 1.0, 1.0, 1.0]);
|
||||
v.extend([V::Name("All".into()), V::Num(1.0)]);
|
||||
assert!(matches!(color_op("Xs", &v), Some((Paint::Solid { tint, .. }, None)) if tint == 0.0));
|
||||
let mut v = nums(&[1.0, 1.0, 1.0, 1.0]);
|
||||
v.extend([V::Str(b"[Registration]".to_vec()), V::Num(0.2), V::Num(0.0)]);
|
||||
let (p, named) = color_op("Xx", &v).unwrap();
|
||||
assert!(named.is_none());
|
||||
assert!(matches!(p, Paint::Solid { swatch: Some(s), tint, .. } if s == "[Registration]" && (tint - 0.8).abs() < 1e-6));
|
||||
let mut v = nums(&[1.0, 1.0, 1.0, 1.0]);
|
||||
v.extend([V::Name("All".into()), V::Num(0.5), V::Num(0.0)]);
|
||||
let (p, named) = color_op("Xx", &v).unwrap();
|
||||
assert!(named.is_none());
|
||||
assert!(matches!(p, Paint::Solid { swatch: Some(s), tint, .. } if s == "[Registration]" && (tint - 0.5).abs() < 1e-6));
|
||||
// Name-free Xs and Xz operands
|
||||
let v = nums(&[1.0, 1.0, 1.0, 1.0, 0.3]);
|
||||
assert!(matches!(color_op("Xs", &v), Some((Paint::Solid { tint, .. }, None)) if (tint - 0.7).abs() < 1e-6));
|
||||
let v = nums(&[1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.2, 1.0]);
|
||||
assert!(matches!(color_op("Xz", &v), Some((Paint::Solid { tint, .. }, None)) if (tint - 0.8).abs() < 1e-6));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -528,6 +571,14 @@ mod tests {
|
||||
assert_eq!(named.name, "MySpot");
|
||||
assert!((named.tint - 0.6).abs() < 1e-6);
|
||||
assert!(matches!(p, Paint::Solid { swatch: Some(_), .. }));
|
||||
let mut v_name = nums(&[0.0, 0.5, 1.0, 0.0]);
|
||||
v_name.push(V::Name("MySpot".into()));
|
||||
v_name.push(V::Num(0.4));
|
||||
let (p, named) = color_op("x", &v_name).unwrap();
|
||||
let named = named.unwrap();
|
||||
assert_eq!(named.name, "MySpot");
|
||||
assert!((named.tint - 0.6).abs() < 1e-6);
|
||||
assert!(matches!(p, Paint::Solid { swatch: Some(_), .. }));
|
||||
assert!(color_op("k", &nums(&[1.0])).is_none());
|
||||
// A global process colour: CMYK (type 0) or RGB (type 1), not an ink.
|
||||
let mut v = nums(&[0.0, 0.0, 0.0, 1.0]);
|
||||
|
||||
@@ -523,7 +523,7 @@ impl<'a> Reader<'a> {
|
||||
fn color(&mut self, w: &str, vals: &[V]) {
|
||||
let Some((p, named)) = paint::color_op(w, vals) else { return };
|
||||
// Registration (`Xs`, `Xz`) says nothing of the document's colour model.
|
||||
let registration = matches!(w, "Xs" | "XS" | "Xz" | "XZ");
|
||||
let registration = matches!(w, "Xs" | "XS" | "Xz" | "XZ") || p.is_registration();
|
||||
match p.color().filter(|_| !registration) {
|
||||
Some(vectorcraft_color::Color::Rgb { .. }) => self.rgb_colors += 1,
|
||||
Some(vectorcraft_color::Color::Cmyk { .. }) => self.cmyk_colors += 1,
|
||||
|
||||
@@ -59,7 +59,7 @@ use vectorcraft_effects::is_pathfinder;
|
||||
|
||||
use super::brush::PathBrush;
|
||||
use super::lex::{Lexer, Tok};
|
||||
use super::obj::V;
|
||||
use super::obj::{self, V};
|
||||
use super::paint;
|
||||
|
||||
const GLOW: &str = "Adobe Outer Glow"; // brand-ok: the filter's name in the files
|
||||
@@ -432,6 +432,7 @@ pub(super) fn art_styles(data: &[u8]) -> HashMap<String, ArtStyle> {
|
||||
b"FillStyle" => style_entry = Some(Kind::Fill),
|
||||
b"StrokeStyle" | b"BlendStyle" => style_entry = Some(Kind::Other),
|
||||
b"Name" if name.is_none() && filters.is_empty() => name = operands.iter().rev().find_map(V::text),
|
||||
b"All" | b"[Registration]" | b"Registration" => operands.push(V::Name(obj::text(n))),
|
||||
b"CompoundFilter" => pending = Some(true),
|
||||
b"BasicFilter" => pending = Some(false),
|
||||
b"Part" => in_basic = false,
|
||||
@@ -605,7 +606,8 @@ fn block_data(lex: &mut Lexer, encoded: &[u8]) -> Option<Vec<u8>> {
|
||||
fn paint_op(f: &mut Filter, op: &str, vals: &[V]) {
|
||||
let last = vals.iter().rev().find_map(V::num);
|
||||
match op {
|
||||
"Xa" | "k" | "g" | "x" | "Xk" | "XA" | "K" | "G" | "X" | "XK" => {
|
||||
"Xa" | "k" | "g" | "x" | "Xx" | "Xk" | "Xs" | "Xz" | "XA" | "K" | "G" | "X" | "XX" | "XK" | "XS" | "XZ" | "xa" | "xx" | "xk" | "xs"
|
||||
| "xz" => {
|
||||
let Some((p, _)) = paint::color_op(op, vals) else { return };
|
||||
if op.ends_with(|c: char| c.is_ascii_lowercase()) {
|
||||
f.fill = Some(p);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use vectorcraft_color::Color;
|
||||
|
||||
#[test]
|
||||
fn a_variable_width_stroke_keeps_its_profile() {
|
||||
@@ -1393,3 +1394,52 @@ fn a_style_is_read_as_a_writer_checks_it() {
|
||||
let _ = read(junk, Some(&own));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn art_styles_read_registration_and_named_colors() {
|
||||
let reg_0 = fill("1 1 1 1 ([Registration]) 1 Xs");
|
||||
let reg_100 = fill("1 1 1 1 ([Registration]) 0 Xs");
|
||||
let reg_name = fill("1 1 1 1 /All 1 Xs");
|
||||
let named_spot = fill("0 0.5 1 0 (Shemen) 0 0 Xx");
|
||||
let stroke_reg = basic(
|
||||
"Stroke Style Filter",
|
||||
"/StrokeStyle : 0 R\n1 1 1 1 ([Registration]) 1 XS\n0 1 0 0 0 Xy\n0 J 0 j 1 w 10 M []0 d\n0 XR\n1 0 Xd\n/Def ;\n (StrokeStyle) ,\n",
|
||||
);
|
||||
let data = format!(
|
||||
"/KnownStyle :\n(Reg0) /Name ,\n/ActiveStyle :\n{}\n\
|
||||
/KnownStyle :\n(Reg100) /Name ,\n/ActiveStyle :\n{}\n\
|
||||
/KnownStyle :\n(RegName) /Name ,\n/ActiveStyle :\n{}\n\
|
||||
/KnownStyle :\n(Shemen) /Name ,\n/ActiveStyle :\n{}\n\
|
||||
/KnownStyle :\n(StrokeReg) /Name ,\n/ActiveStyle :\n{}\n",
|
||||
compound("Chain Style Filter", &[compound("Stack Style Filter", &[reg_0])]),
|
||||
compound("Chain Style Filter", &[compound("Stack Style Filter", &[reg_100])]),
|
||||
compound("Chain Style Filter", &[compound("Stack Style Filter", &[reg_name])]),
|
||||
compound("Chain Style Filter", &[compound("Stack Style Filter", &[named_spot])]),
|
||||
compound("Chain Style Filter", &[compound("Stack Style Filter", &[stroke_reg])]),
|
||||
);
|
||||
let styles = art_styles(data.as_bytes());
|
||||
let st0 = &styles["Reg0"];
|
||||
assert!(st0.plain);
|
||||
let [StyleItem::Fill(Paint::Solid { swatch, tint, color })] = st0.items.as_slice() else { panic!("{st0:?}") };
|
||||
assert_eq!((swatch.as_deref(), *tint, *color), (Some(vectorcraft_color::swatch::REGISTRATION), 0.0, Color::cmyk(1.0, 1.0, 1.0, 1.0).tinted(0.0)));
|
||||
|
||||
let st100 = &styles["Reg100"];
|
||||
assert!(st100.plain);
|
||||
let [StyleItem::Fill(Paint::Solid { swatch, tint, color })] = st100.items.as_slice() else { panic!("{st100:?}") };
|
||||
assert_eq!((swatch.as_deref(), *tint, *color), (Some(vectorcraft_color::swatch::REGISTRATION), 1.0, Color::cmyk(1.0, 1.0, 1.0, 1.0)));
|
||||
|
||||
let st_name = &styles["RegName"];
|
||||
assert!(st_name.plain);
|
||||
let [StyleItem::Fill(Paint::Solid { swatch, tint, .. })] = st_name.items.as_slice() else { panic!("{st_name:?}") };
|
||||
assert_eq!((swatch.as_deref(), *tint), (Some(vectorcraft_color::swatch::REGISTRATION), 0.0));
|
||||
|
||||
let st_spot = &styles["Shemen"];
|
||||
assert!(st_spot.plain);
|
||||
let [StyleItem::Fill(Paint::Solid { swatch, tint, color })] = st_spot.items.as_slice() else { panic!("{st_spot:?}") };
|
||||
assert_eq!((swatch.as_deref(), *tint, *color), (Some("Shemen"), 1.0, Color::cmyk(0.0, 0.5, 1.0, 0.0)));
|
||||
|
||||
let st_stroke = &styles["StrokeReg"];
|
||||
assert!(st_stroke.plain);
|
||||
let [StyleItem::Stroke { paint: Paint::Solid { swatch, tint, .. }, .. }] = st_stroke.items.as_slice() else { panic!("{st_stroke:?}") };
|
||||
assert_eq!((swatch.as_deref(), *tint), (Some(vectorcraft_color::swatch::REGISTRATION), 0.0));
|
||||
}
|
||||
|
||||
@@ -6069,6 +6069,30 @@ fn an_object_whose_transparency_differs_from_its_listed_style_is_not_linked() {
|
||||
assert_eq!(a[0].extras().graphic_style, None, "the style has full opacity");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_import_registration_style_does_not_carry_over_previous_color_6205() {
|
||||
// Regression test for #6205:
|
||||
// A background shape sets a spot colour ("Shemen").
|
||||
// A following object has an art style with [Registration] at 0% tint.
|
||||
// The object must resolve the Registration style ([Registration] 0% / #ffffff)
|
||||
// rather than falling back to the prior spot color.
|
||||
let defs = [fill_style("RegWhite", "1 1 1 1 ([Registration]) 1 Xs\n")];
|
||||
let background = format!("0 0.67 0.73 0.26 (Shemen) 0 0 Xx\n{}f\n", rect_path(0.0, 0.0, 100.0, 100.0));
|
||||
let styled_obj = styled_square("RegWhite");
|
||||
let content = format!("{background}{styled_obj}");
|
||||
let r = read_ok(&format!("{}{}", styles_and_list(&defs, &["RegWhite"]), layer(&content)));
|
||||
let a = art(&r.doc);
|
||||
assert_eq!(a.len(), 2, "{a:#?}");
|
||||
// First object is background in Shemen spot
|
||||
let Paint::Solid { swatch: s0, .. } = fill(&a[0]) else { panic!("{:#?}", a[0]) };
|
||||
assert_eq!(s0.as_deref(), Some("Shemen"));
|
||||
// Second object is styled in RegWhite: [Registration] at 0% tint (#ffffff)
|
||||
let Paint::Solid { swatch, tint, color } = fill(&a[1]) else { panic!("{:#?}", a[1]) };
|
||||
assert_eq!(swatch.as_deref(), Some(vectorcraft_color::swatch::REGISTRATION));
|
||||
assert_eq!(tint, 0.0);
|
||||
assert_eq!(color, Color::cmyk(1.0, 1.0, 1.0, 1.0).tinted(0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_listed_style_keeps_its_transparency_and_links_objects_that_have_it() {
|
||||
let blend = "/BasicFilter :\n(Blend Style Filter) 0 0 /Filter ,\n1 /Visible ,\n/Dictionary : /NotRecorded ,\n\
|
||||
|
||||
Reference in New Issue
Block a user