diff --git a/crates/eps/src/import/ai/read.rs b/crates/eps/src/import/ai/read.rs index 45808b2cb..f6ea9e7ac 100644 --- a/crates/eps/src/import/ai/read.rs +++ b/crates/eps/src/import/ai/read.rs @@ -870,7 +870,9 @@ impl<'a> Reader<'a> { let mut painted = None; let node = match kind { // A compound path holding a group (its drawn look) is that look: its paths paint apart. - Kind::Compound if kids.iter().any(|k| self.looks.contains(&k.id)) => { + // Some files mark every group so, the plain one holding the subpaths too (#980): one + // paint for all its paths keeps it one compound path, its holes cut. + Kind::Compound if kids.iter().any(|k| self.looks.contains(&k.id) && paints_apart(k)) => { let (children, clip) = self.clip_first(kids, &clips); self.new_node(NodeKind::Group { children, clip }, &gs) } @@ -2203,6 +2205,19 @@ fn ungrouped(kids: Vec) -> Vec { out } +/// Whether `look`'s paths don't all paint the same. +fn paints_apart(look: &Node) -> bool { + fn leaves<'a>(n: &'a Node, out: &mut Vec<&'a Node>) { + match &n.kind { + NodeKind::Group { children, .. } => children.iter().for_each(|c| leaves(c, out)), + _ => out.push(n), + } + } + let mut paths = vec![]; + leaves(look, &mut paths); + paths.windows(2).any(|w| matches!(w, [a, b] if a.appearance != b.appearance)) +} + fn path_rule(n: &Node) -> FillRule { match &n.kind { NodeKind::Path { rule, .. } | NodeKind::Compound { rule, .. } => *rule, diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index e8c901bad..0e5bbe3bb 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -446,6 +446,21 @@ fn a_compound_path_holding_its_drawn_look_paints_its_paths_apart() { assert_eq!(fills, [Some(Color::rgb(1.0, 0.0, 0.0)), Some(Color::rgb(0.0, 0.0, 1.0))], "{a:#?}"); } +#[test] +fn a_compound_path_of_a_plain_group_stays_one_compound_path() { + // GUARDS: #980. Files write `9 () XW` after every group, the one a compound path keeps its + // subpaths in too: read as a drawn look, the compound became a group of filled paths, its + // holes filled in. Here a ring (outer path, inner one reversed, one paint) and a path beside it. + let ring = format!("{RED}1 D\n{}f\n0 D\n{}f\n", rect_path(0.0, 0.0, 20.0, 20.0), "5 5 m\n15 5 L\n15 15 L\n5 15 L\n5 5 L\n"); + let art_ = format!("0 Ae\nu\n0 Ae\n*u\n0 Ae\nu\n{ring}U\n9 () XW\n*U\n{}f\nU\n9 () XW\n", rect_path(8.0, 8.0, 12.0, 12.0)); + let a = art(&read_ok(&layer(&art_)).doc); + let NodeKind::Group { children, .. } = &a[0].kind else { panic!("{a:#?}") }; + let NodeKind::Compound { children: subpaths, .. } = &children[0].kind else { panic!("a compound path: {:#?}", children[0]) }; + assert_eq!(subpaths.len(), 2, "{subpaths:#?}"); + assert!(subpaths.iter().all(|p| matches!(p.kind, NodeKind::Path { .. }))); + assert_eq!(fill(&children[0]).color(), Some(Color::rgb(1.0, 0.0, 0.0))); +} + #[test] fn gradients_through_a_skewed_matrix() { // GUARDS: a sheared `Xm` leaves the matrix's columns oblique. A linear ramp runs along the