diff --git a/crates/eps/src/import/ai/read/containers.rs b/crates/eps/src/import/ai/read/containers.rs index abe4eaf27..e2fc2ac8b 100644 --- a/crates/eps/src/import/ai/read/containers.rs +++ b/crates/eps/src/import/ai/read/containers.rs @@ -332,17 +332,22 @@ impl<'a> Reader<'a> { *clipping = false; } let mut fill = Node::new(self.doc.alloc_id(), kind); - // A compound clipping path's subpaths: copies of their own that don't clip. + // A compound clipping path's subpaths: copies with ids of their own, linked to the same swatches, that don't clip. for c in fill.children_mut().into_iter().flatten() { - let c = Arc::make_mut(c); - self.reid(c); - if let NodeKind::Path { clipping, .. } = &mut c.kind { + let mut copy = self.doc.reid(c); + self.reid_masks(&mut copy, 0); + if let NodeKind::Path { clipping, .. } = &mut copy.kind { *clipping = false; } + *c = Arc::new(copy); } (fill.opacity, fill.blend, fill.isolate, fill.knockout, fill.knockout_shape, fill.visible, fill.locked) = (n.opacity, n.blend, n.isolate, n.knockout, n.knockout_shape, n.visible, n.locked); - if let Some(mask) = n.extras().mask.clone() { + // The opacity mask's art is a copy with ids of its own, linked to the same swatches. + if let Some(mut mask) = n.extras().mask.clone() { + let mut art = self.doc.reid(&mask.art); + self.reid_masks(&mut art, 0); + mask.art = Arc::new(art); fill.extras_mut().mask = Some(mask); } let (mut fills, mut rest) = ((*n.appearance).clone(), (*n.appearance).clone()); diff --git a/crates/eps/src/import/ai/read/directives.rs b/crates/eps/src/import/ai/read/directives.rs index aedb753d6..307105f95 100644 --- a/crates/eps/src/import/ai/read/directives.rs +++ b/crates/eps/src/import/ai/read/directives.rs @@ -648,10 +648,10 @@ impl<'a> Reader<'a> { named } - /// Give the art of the opacity masks in `n` (a copy of a symbol's art with ids of this + /// Give the art of the opacity masks in `n` (a copy of a symbol's art or of a clipping path with ids of this /// document, [`vectorcraft_doc::Document::reid`], which leaves masks as they are) ids of this /// document too. - fn reid_masks(&mut self, n: &mut Node, depth: usize) { + pub(super) fn reid_masks(&mut self, n: &mut Node, depth: usize) { if depth > MAX_DEPTH { return; } diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 0cb243692..f85b67e0c 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -1809,22 +1809,52 @@ fn a_compound_path_holding_its_drawn_look_paints_its_paths_apart() { fn clipping_paths_beside_a_compound_paths_look_keep_their_fills() { let (a, b) = (rect_path(0.0, 0.0, 20.0, 20.0), rect_path(5.0, 5.0, 25.0, 25.0)); let look = format!("0 Ae\nu\n{RED}{}f\n0 0 0 0 0 0 1 Xa\n{}f\nU\n9 () XW\n", rect_path(0.0, 0.0, 30.0, 30.0), rect_path(10.0, 10.0, 15.0, 15.0)); - let mask_art: String = format!("1 g\n{}f\n", rect_path(0.0, 0.0, 10.0, 10.0)).lines().map(|l| format!("%_{l}\n")).collect(); + let mask_art: String = + format!("0 0 0 0 (Global white) 1 0 Xk\n{}f\n", rect_path(0.0, 0.0, 10.0, 10.0)).lines().map(|l| format!("%_{l}\n")).collect(); let mask = format!("%_/ArtDictionary :\n%_/Mask :\n%_X=\n{mask_art}%_X+\n%_(Art) ,\n%_; (AI9_Mask) ,\n%_;\n"); let children = |second: &str, after_first: &str| { let body = format!("0 Ae\n*u\n{RED}{a}h\nW\nf\n{after_first}{second}{b}h\nW\nf\n{look}*U\n"); - let a = art(&read_ok(&layer(&body)).doc); + let doc = read_ok(&layer(&body)).doc; + // Every object, opacity masks' art included, has an id of its own. + fn ids(n: &Node, seen: &mut std::collections::HashSet) { + assert!(seen.insert(n.id), "{:?} appears twice", n.id); + if let Some(m) = &n.extras().mask { + ids(&m.art, seen); + } + for c in n.children().into_iter().flatten() { + ids(c, seen); + } + } + let mut seen = std::collections::HashSet::new(); + doc.layers.iter().for_each(|l| ids(l, &mut seen)); + let a = art(&doc); let NodeKind::Group { children, clip: true } = &a[0].kind else { panic!("{a:#?}") }; - children.iter().map(|c| (c.kind.clone(), fill(c).color(), c.extras().mask.is_some())).collect::>() + // An opacity mask: the swatch its art's fill is linked to. + let mask = |c: &Node| { + c.extras().mask.as_ref().map(|m| { + let mut swatch = None; + m.art.walk(&mut |p| { + if let Paint::Solid { swatch: Some(s), .. } = fill(p) { + swatch = Some(s); + } + }); + swatch + }) + }; + children.iter().map(|c| (c.kind.clone(), fill(c).color(), mask(c))).collect::>() }; let (red, blue) = (Some(Color::rgb(1.0, 0.0, 0.0)), Some(Color::rgb(0.0, 0.0, 1.0))); - let paths = |c: &[(NodeKind, Option, bool)]| { + // A child's kind, fill color and, with an opacity mask, the swatch its art is linked to. + type Child = (NodeKind, Option, Option>); + let paths = |c: &[Child]| { assert!(matches!(c[0].0, NodeKind::Compound { .. }) && c.len() == 4, "{c:?}"); - c[..3].iter().map(|(k, c, m)| (matches!(k, NodeKind::Path { clipping: false, .. }), *c, *m)).collect::>() + c[..3].iter().map(|(k, c, m)| (matches!(k, NodeKind::Path { clipping: false, .. }), *c, m.clone())).collect::>() }; - assert_eq!(paths(&children("", "")), [(false, None, false), (true, red, false), (true, red, false)]); - assert_eq!(paths(&children("0 0 0 0 0 0 1 Xa\n", "")), [(false, None, false), (true, red, false), (true, blue, false)]); - assert_eq!(paths(&children("0 0 0 0 0 0 1 Xa\n", &mask)), [(false, None, false), (true, red, true), (true, blue, false)]); + assert_eq!(paths(&children("", "")), [(false, None, None), (true, red, None), (true, red, None)]); + assert_eq!(paths(&children("0 0 0 0 0 0 1 Xa\n", "")), [(false, None, None), (true, red, None), (true, blue, None)]); + // The fill's copy of the mask stays linked to the mask art's global swatch. + let linked = Some(Some("Global white".to_string())); + assert_eq!(paths(&children("0 0 0 0 0 0 1 Xa\n", &mask)), [(false, None, None), (true, red, linked), (true, blue, None)]); } #[test]