From 48d5bd1aeec47303356e34c5fd21141f068f1643 Mon Sep 17 00:00:00 2001 From: EricKuck Date: Mon, 12 Oct 2026 18:27:47 -0400 Subject: [PATCH] AI import: gradient stops listed upwards aren't reversed, so a midpoint stays on the stop that starts its part of the ramp Co-Authored-By: Claude Opus 5.5 --- crates/eps/src/import/ai/paint.rs | 7 +++++-- crates/eps/src/import/ai/tests.rs | 13 +++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/eps/src/import/ai/paint.rs b/crates/eps/src/import/ai/paint.rs index 99e66150..038299ea 100644 --- a/crates/eps/src/import/ai/paint.rs +++ b/crates/eps/src/import/ai/paint.rs @@ -158,8 +158,11 @@ impl GradientDef { if self.gradient.stops.is_empty() { return None; } - // Stops come last first: reversed, stops sharing a ramp point keep their order. - self.gradient.stops.reverse(); + // Stops are listed along the ramp from either end: listed downwards they're reversed, so + // stops sharing a ramp point keep their order (and each its midpoint, to the next stop). + if self.gradient.stops.first().zip(self.gradient.stops.last()).is_some_and(|(first, last)| first.offset > last.offset) { + self.gradient.stops.reverse(); + } self.gradient.stops.sort_by(|a, b| a.offset.total_cmp(&b.offset)); Some((self.name, self.gradient)) } diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 18756d35..2f9552b5 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -410,6 +410,19 @@ fn stops_sharing_a_ramp_point_keep_their_order() { assert_eq!(colors, [Color::rgb(0.0, 0.0, 1.0), Color::rgb(0.0, 1.0, 0.0), Color::rgb(1.0, 0.0, 0.0)]); } +#[test] +fn stops_listed_upwards_keep_the_midpoint_of_the_one_that_starts_the_ramp() { + // GUARDS: reversing a list that runs upwards swaps two stops at 0, and the ramp to 100 takes the + // midpoint of the zero-length part between them. + let defs = "%AI5_BeginGradient: (U)\n(U) 0 3 Bd\n[\n<00FF>\n0\n%_0 0 0 0 1 1 1 2 1 6 50 0 Bs\n\ + %_0 0 0 0 1 1 1 2 1 6 87 0 Bs\n%_0 0 0 0 0 0 0 2 1 6 50 100 Bs\nBD\n%AI5_EndGradient\n"; + let art_ = format!("{}Bb\n1 (U) 0 0 0 1 1 0 0 1 0 0 1 Bg\n10 0 0 10 100 50 Xm\nf\n0 BB\n", rect_path(0.0, 0.0, 200.0, 100.0)); + let doc = read_ok(&format!("{defs}{}", layer(&art_))).doc; + let Paint::Gradient(g) = fill(&art(&doc)[0]) else { panic!() }; + let midpoints: Vec<_> = g.gradient.stops.iter().map(|s| s.midpoint).collect(); + assert_eq!(midpoints, [0.5, 0.87, 0.5]); +} + #[test] fn per_anchor_data_reads() { let art_ = format!("{RED}{}f\n(00800080) Xt 0 XD\n%_(00004000) Xt 1 XD\n", rect_path(0.0, 0.0, 10.0, 10.0));