From ecb994540c75aa2e76d5317fba9090e77332f106 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 12 Nov 2026 21:36:18 -0800 Subject: [PATCH] .ai import: a one-paint style's gradient paints the side its flag names A gradient instance in a one-paint style marked both the fill and the stroke as not read, so a style with a solid fill and a stroke gradient was left out even when its `Xd` turns on only the fill. The instance's flag (`1 Bb` for the stroke) now says which side the gradient paints, as the object reader reads it; a gradient outside an instance still marks both. --- crates/eps/src/import/ai/style.rs | 13 +++++++++++-- crates/eps/src/import/ai/style/tests.rs | 5 ++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/eps/src/import/ai/style.rs b/crates/eps/src/import/ai/style.rs index 27657f570b..482c172386 100644 --- a/crates/eps/src/import/ai/style.rs +++ b/crates/eps/src/import/ai/style.rs @@ -780,6 +780,8 @@ struct OnePaint { fill_unread: bool, /// Its last stroke paint is a pattern (`P`) or may be a gradient (`Bg`). stroke_unread: bool, + /// Inside a gradient instance (`flag Bb` … `BB`): whether it paints the stroke (flag 1). + instance: Option, } impl OnePaint { @@ -788,8 +790,15 @@ impl OnePaint { "Xd" => self.xd |= vals.iter().filter_map(V::num).count() >= 2, "p" => self.fill_unread = true, "P" => self.stroke_unread = true, - // Read as either one's, since its operands don't say which it paints. - "Bg" => (self.fill_unread, self.stroke_unread) = (true, true), + // The flag of the instance it is in says which one it paints, as an object's does + // (`Instance::begin` in `paint.rs`); outside one it is read as either one's. + "Bb" => self.instance = Some(vals.iter().filter_map(V::num).next_back() == Some(1.0)), + "BB" => self.instance = None, + "Bg" => match self.instance { + Some(true) => self.stroke_unread = true, + Some(false) => self.fill_unread = true, + None => (self.fill_unread, self.stroke_unread) = (true, true), + }, // A color replaces what came before it; one that isn't read leaves none. "Xa" | "k" | "g" | "x" | "Xx" | "Xk" | "Xs" | "Xz" | "xa" | "xx" | "xk" | "xs" | "xz" => { (self.fill_unread, self.paint.fill) = (false, None); diff --git a/crates/eps/src/import/ai/style/tests.rs b/crates/eps/src/import/ai/style/tests.rs index 29a6e996bb..ea8428809c 100644 --- a/crates/eps/src/import/ai/style/tests.rs +++ b/crates/eps/src/import/ai/style/tests.rs @@ -1541,6 +1541,9 @@ fn a_style_of_one_paint_reads_as_the_fill_and_stroke_its_xd_turns_on() { } let solid_stroke = one("(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] P\n0 1 1 0 K\n1 0 Xd\n"); assert!(matches!(solid_stroke, Some(Ok(i)) if matches!(i.as_slice(), [StyleItem::Stroke { .. }]))); + // A gradient instance's flag says which one it paints: a stroke gradient leaves a solid fill read. + let fill_and_stroke_gradient = one("0 0 1 0 k\n1 Bb\n2 (Ramp) 0 0 90 1 1 0 0 1 0 0 1 Bg\n0 BB\n0 1 Xd\n"); + assert!(matches!(fill_and_stroke_gradient, Some(Ok(i)) if matches!(i.as_slice(), [StyleItem::Fill(_)]))); // Not read: no `Xd`, a gradient or pattern fill, a pattern stroke, a gradient stroke after a solid one, a paint // turned on with no color. for paint in [ @@ -1548,7 +1551,7 @@ fn a_style_of_one_paint_reads_as_the_fill_and_stroke_its_xd_turns_on() { "0 Bb\n2 (Sky) 0 0 0 100 1 0 0 1 0 0 Bg\n0 BB\n0 1 Xd\n".to_string(), "(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] p\n0 1 Xd\n".to_string(), "(Dots) 0 0 1 1 0 0 0 0 0 [1 0 0 1 0 0] P\n1 0 Xd\n".to_string(), - "0 R\n0 1 1 0 K\nBb\n0 0 0 0 Bh\n2 (Ramp) 0 0 90 1 1 0 0 1 0 0 1 Bg\n0 BB\n2 w\n1 0 Xd\n".to_string(), + "0 R\n0 1 1 0 K\n1 Bb\n0 0 0 0 Bh\n2 (Ramp) 0 0 90 1 1 0 0 1 0 0 1 Bg\n0 BB\n2 w\n1 0 Xd\n".to_string(), "0 0 1 0 k\n1 1 Xd\n".to_string(), ] { assert_eq!(one(&paint), Some(Err(())), "{paint}");