From 589fd863c46d902c24c0ce403b04e82c4db9c2fe Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 6 Nov 2026 19:11:35 -0800 Subject: [PATCH] .ai import: a compound path's plain subpath keeps its fill In a compound clipping path whose subpaths paint differently, the fill of a subpath that does not clip was read apart with the clipping subpaths' fills. Only the clipping subpaths' fills are read apart now, and a subpath that does not clip keeps its fill, as before. --- crates/eps/src/import/ai/read/containers.rs | 8 ++++++-- crates/eps/src/import/ai/tests.rs | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/crates/eps/src/import/ai/read/containers.rs b/crates/eps/src/import/ai/read/containers.rs index e2fc2ac8b..0f5df81bd 100644 --- a/crates/eps/src/import/ai/read/containers.rs +++ b/crates/eps/src/import/ai/read/containers.rs @@ -150,10 +150,14 @@ impl<'a> Reader<'a> { // A group in a compound path (`*u u … U *U`) only holds its paths. let mut kids = ungrouped(kids); // A compound clipping path keeps its subpaths' fill when they all paint alike. When they paint - // differently, the fills of the filled ones are a compound path of their own, under what it clips. + // differently, the fills of the filled clipping ones are a compound path of their own, under what it clips. let look = |n: &Node| (n.appearance.clone(), n.opacity, n.blend); if clipping && kids.windows(2).any(|w| look(&w[0]) != look(&w[1])) { - let fills: Vec = kids.iter_mut().filter_map(|k| self.fill_apart(k)).collect(); + let fills: Vec = kids + .iter_mut() + .filter(|k| matches!(k.kind, NodeKind::Path { clipping: true, .. })) + .filter_map(|k| self.fill_apart(k)) + .collect(); painted = self.compound(fills, &gs); } let Some(n) = self.compound(kids, &gs) else { diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index f85b67e0c..579f7967a 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -1293,6 +1293,21 @@ fn compound_paths_and_clipping() { assert!(filled.iter().all(|p| matches!(p.kind, NodeKind::Path { clipping: false, .. })), "{filled:?}"); assert_eq!((fill(&children[1]).color(), children[1].appearance.stroke().is_none()), (blue, true)); assert_eq!(fill(&children[2]).color(), Some(Color::rgb(1.0, 0.0, 0.0))); + // A subpath that doesn't clip keeps its fill; only the clipping subpath's fill is read apart. + let green = Some(Color::rgb(0.0, 1.0, 0.0)); + let plain = format!( + "0 Ae\nq\n{RED}{}f\n*u\n0 0 1 0 0 0 1 Xa\n{}f\n0 1 0 0 0 1 0 Xa\n{}h\nW\nf\n*U\nQ\n", + rect_path(0.0, 0.0, 30.0, 30.0), + rect_path(5.0, 5.0, 25.0, 25.0), + rect_path(10.0, 10.0, 20.0, 20.0) + ); + let doc = read_ok(&layer(&plain)).doc; + let NodeKind::Group { children, clip: true } = &art(&doc)[0].kind else { panic!("a clipping group") }; + let NodeKind::Compound { children: subpaths, .. } = &children[0].kind else { panic!("{:?}", children[0].kind) }; + let kinds: Vec<_> = subpaths.iter().map(|c| (matches!(c.kind, NodeKind::Path { clipping: true, .. }), fill(c).color())).collect(); + assert_eq!(kinds, [(false, blue), (true, None)]); + let NodeKind::Compound { children: apart, .. } = &children[1].kind else { panic!("{:?}", children[1].kind) }; + assert_eq!((apart.len(), fill(&children[1]).color()), (1, green), "only the clipping subpath's fill: {apart:?}"); // A tag after the compound clipping path is its own. let doc = read_ok(&layer(&mixed.replace("*U\n", "*U\n/edge (clip) XT\n"))).doc; let NodeKind::Group { children, .. } = &art(&doc)[0].kind else { panic!("a clipping group") };