diff --git a/crates/eps/src/import/ai/read/directives.rs b/crates/eps/src/import/ai/read/directives.rs index 68f981751..ef27e5f74 100644 --- a/crates/eps/src/import/ai/read/directives.rs +++ b/crates/eps/src/import/ai/read/directives.rs @@ -503,7 +503,15 @@ impl<'a> Reader<'a> { let Some(tile) = head.tile.filter(|t| whole && sane(*t) && t.width() > 0.0 && t.height() > 0.0 && !frame.children.is_empty()) else { return; }; - let art: Vec> = frame.children.iter().map(|n| Arc::new(self.doc.reid(n))).collect(); + let art: Vec> = frame + .children + .iter() + .map(|n| { + let mut kept = self.doc.reid(n); + self.reid_masks(&mut kept, 0); + Arc::new(kept) + }) + .collect(); // The objects of the art linked to graphic styles, by their ids in the document. if !sub.style_links.is_empty() { let mut ids = HashMap::new(); @@ -671,7 +679,7 @@ impl<'a> Reader<'a> { named } - /// 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 + /// Give the art of the opacity masks in `n` (a copy of a symbol's, a pattern's or a brush'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. pub(super) fn reid_masks(&mut self, n: &mut Node, depth: usize) { @@ -783,7 +791,15 @@ impl<'a> Reader<'a> { && sub.doc.images.is_empty(); let Some(frame) = sub.frames.pop().filter(|f| whole && !f.children.is_empty()) else { return }; self.brush_art_looks.insert(name.clone(), sub.drawn_looks); - let mut art: Vec> = frame.children.iter().map(|n| Arc::new(self.doc.reid(n))).collect(); + let mut art: Vec> = frame + .children + .iter() + .map(|n| { + let mut kept = self.doc.reid(n); + self.reid_masks(&mut kept, 0); + Arc::new(kept) + }) + .collect(); let node = match art.len() { 1 => art.pop().map(Arc::unwrap_or_clone), _ => Some(Node::group(self.doc.alloc_id(), art)), diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 07236c2c6..5125adae0 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -5974,6 +5974,37 @@ fn an_object_in_a_brushs_art_with_a_style_this_reads_comes_in_as_itself() { assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{:?}", r.warnings); } +/// The art of an opacity mask in a brush's art takes ids of the document, as the rest of the art +/// does: no object of the brush's art shares an id with an object of the layers. +#[test] +fn a_brushs_opacity_mask_art_takes_ids_of_the_document() { + fn ids(n: &Node, out: &mut Vec) { + out.push(n.id); + if let Some(m) = n.extras().mask.as_ref() { + ids(&m.art, out); + } + for c in n.children().into_iter().flatten() { + ids(c, out); + } + } + let square = rect_path(0.0, 0.0, 10.0, 10.0); + let mask_art: String = format!("1 g\n{square}f\n").lines().map(|l| format!("%_{l}\n")).collect(); + let mask = format!("%_/ArtDictionary :\n%_/Mask :\n%_X=\n{mask_art}%_X+\n%_(Art) ,\n%_1 /Bool (Clipping) ,\n%_; (AI9_Mask) ,\n%_;\n"); + let section = format!("%AI8_BeginBrushPattern\n(Dot art)\n0 A\n{RED}{square}f\n{mask}%AI8_EndBrushPattern\n"); + let shapes: String = (0..12).map(|i| format!("{RED}{}f\n", rect_path(f64::from(i) * 20.0, 0.0, f64::from(i) * 20.0 + 10.0, 10.0))).collect(); + let r = read_ok(&format!("{section}{}{}", plugin_object(SCATTER, "Spray", SPRAY), layer(&shapes))); + let lib = vectorcraft_brush::library(&r.doc); + let spray = lib.iter().find(|b| b.name == "Spray").unwrap_or_else(|| panic!("{:?}", r.warnings)); + let art = spray.art(); + assert!(art.iter().any(|n| n.extras().mask.is_some()), "the mask is read: {art:#?}"); + let mut all = Vec::new(); + for n in r.doc.layers.iter().map(|l| &**l).chain(art) { + ids(n, &mut all); + } + let unique: std::collections::HashSet<_> = all.iter().copied().collect(); + assert_eq!(unique.len(), all.len(), "{all:?}"); +} + /// An art styles section inside a symbol's, a pattern's or a brush's art is skipped: the file's own /// art styles still style the layers' objects read after the definition. #[test] @@ -6196,6 +6227,35 @@ fn an_object_in_a_patterns_art_with_a_listed_style_is_linked() { assert_eq!(square.extras().graphic_style, Some(1), "{square:#?}"); } +/// The art of an opacity mask in a pattern's art takes ids of the document, as the rest of the art +/// does: no object of the pattern's art shares an id with an object of the layers. +#[test] +fn a_patterns_opacity_mask_art_takes_ids_of_the_document() { + fn ids(n: &Node, out: &mut Vec) { + out.push(n.id); + if let Some(m) = n.extras().mask.as_ref() { + ids(&m.art, out); + } + for c in n.children().into_iter().flatten() { + ids(c, out); + } + } + let square = rect_path(0.0, 0.0, 10.0, 10.0); + let mask_art: String = format!("1 g\n{square}f\n").lines().map(|l| format!("%_{l}\n")).collect(); + let mask = format!("%_/ArtDictionary :\n%_/Mask :\n%_X=\n{mask_art}%_X+\n%_(Art) ,\n%_1 /Bool (Clipping) ,\n%_; (AI9_Mask) ,\n%_;\n"); + let pattern = format!("%AI3_BeginPattern: (P)\n(P) 0 0 10 10\n0 A\n0 Xw\n{RED}{square}f\n{mask}E\n%AI3_EndPattern\n"); + let shapes: String = (0..12).map(|i| format!("{RED}{}f\n", rect_path(f64::from(i) * 20.0, 0.0, f64::from(i) * 20.0 + 10.0, 10.0))).collect(); + let r = read_ok(&format!("{pattern}{}", layer(&shapes))); + let def = r.doc.pattern("P").unwrap_or_else(|| panic!("{:?}", r.warnings)); + assert!(def.art.iter().any(|n| n.extras().mask.is_some()), "the mask is read: {:#?}", def.art); + let mut all = Vec::new(); + for n in r.doc.layers.iter().chain(&def.art) { + ids(n, &mut all); + } + let unique: std::collections::HashSet<_> = all.iter().copied().collect(); + assert_eq!(unique.len(), all.len(), "{all:?}"); +} + #[test] fn an_object_in_a_version_7_patterns_tile_with_a_listed_style_is_linked() { let defs = styles_and_list(&[fill_style("Blue", "0 0 0 0 0 0 1 Xa\n")], &["Blue"]);