diff --git a/crates/eps/src/import/ai/paint.rs b/crates/eps/src/import/ai/paint.rs index 038299ea9..438a52bdb 100644 --- a/crates/eps/src/import/ai/paint.rs +++ b/crates/eps/src/import/ai/paint.rs @@ -238,6 +238,11 @@ impl Instance { } } + /// Does it carry a matrix of its own (`Xm` or `Bm`) rather than only `Bg`'s numbers? + pub fn has_matrix(&self) -> bool { + self.xm.is_some() || self.bm.is_some() + } + /// The paint, through `to_doc` (art space → the document), when the gradient is defined. pub fn paint(&self, defs: &HashMap, to_doc: Affine) -> Option { let name = self.name.as_ref()?; diff --git a/crates/eps/src/import/ai/read.rs b/crates/eps/src/import/ai/read.rs index e376c80c1..4a24389d4 100644 --- a/crates/eps/src/import/ai/read.rs +++ b/crates/eps/src/import/ai/read.rs @@ -732,6 +732,7 @@ impl<'a> Reader<'a> { // The gradients defined so far, lent (and those it defines kept). sub.gradients = std::mem::take(&mut self.gradients); sub.nodes = self.nodes; + sub.template = self.template; let read = sub.open(Kind::Group, false).and_then(|()| sub.run()); self.nodes = sub.nodes; self.gradients = std::mem::take(&mut sub.gradients); @@ -1754,7 +1755,16 @@ impl<'a> Reader<'a> { } fn set_instance(&mut self, i: &Instance) { - let to_doc = self.space(); + let mut to_doc = self.space(); + // In a pattern's definition a gradient's `Xm` and `Bm` are written y down about the canvas point of art space's + // origin, as the file's PDF part draws them. + if self.pattern.is_some() + && i.has_matrix() + && let Some(t) = self.template + { + let (cx, cy) = ((t[0] + t[2]) / 2.0, (t[1] + t[3]) / 2.0); + to_doc *= Affine::new([1.0, 0.0, 0.0, -1.0, CANVAS_CENTRE - cx, CANVAS_CENTRE + cy]); + } match i.paint(&self.gradients, to_doc) { Some(p) if i.stroke => self.pending_stroke = Some(p), Some(p) => self.pending_fill = Some(p), diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 6f773d9e5..0de6149ef 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -217,6 +217,29 @@ fn a_pattern_tiles_from_its_lower_left_wherever_its_definition_drew_it() { assert_eq!(xf * Point::ZERO, Point::new(0.0, 92.0)); } +/// A gradient in a pattern's definition is placed by an `Xm` written y down about the canvas point of +/// art space's origin (here 7941, 7941: the canvas's centre less the template box's): its ramp from +/// (10, 0) up to (10, 20) in the pattern's space is `0 -20 20 0 -7931 7941 Xm`. +#[test] +fn a_gradient_in_a_patterns_definition_is_placed_from_the_canvas() { + let defs = + "%AI5_BeginGradient: (G)\n(G) 0 2 Bd\n[\n<00FF>\n0\n%_0 0 0 0 1 0 0 2 1 6 50 0 Bs\n%_0 0 0 0 0 0 1 2 1 6 50 100 Bs\nBD\n%AI5_EndGradient\n"; + let tile = rect_path(0.0, 0.0, 20.0, 20.0).replace('\n', "\n%_"); + let pattern = format!( + "%AI3_BeginPattern: (Shade)\n(Shade) 0 0 20 20\n%_0 A\n%_0 Xw\n%_0 Ae\n%_u\n%_Bb\n%_1 (G) 0 0 90 1 1 0 0 1 0 0 1 Bg\n\ + %_0 -20 20 0 -7931 7941 Xm\n%_{tile}f\n%_0 BB\n%_U\nE\n%AI3_EndPattern\n" + ); + let file = stream(&format!("{defs}{pattern}{}", layer(&(rect_path(0.0, 0.0, 1.0, 1.0) + "f\n")))) + .replace("%%EndComments", "%AI3_TemplateBox: 250.5 -250.5 250.5 -250.5\n%%EndComments"); + let doc = read(file.as_bytes()).unwrap().doc; + let def = doc.pattern("Shade").unwrap(); + let shaded = &def.art[0].children().unwrap()[0]; + let Paint::Gradient(g) = fill(shaded) else { panic!("{:?}", fill(shaded)) }; + let geom = g.geom.unwrap(); + // The pattern's space is y down. + assert!(geom.start.distance(Point::new(10.0, 0.0)) < 1e-9 && geom.end.distance(Point::new(10.0, -20.0)) < 1e-9, "{geom:?}"); +} + /// A pattern whose definition it can't read whole isn't kept: a fill with it is something the file /// has that isn't read, where it shows. Its definition costs nothing else. #[test]