.ai import: opacity mask art in pattern and brush art takes document ids

A pattern's or a brush's art was copied into the document with new ids,
but the art of an opacity mask inside it kept the ids of the reader that
read the definition. Those ids could repeat ids of objects in the
layers. The mask art now takes document ids, as a symbol's art already
does.
This commit is contained in:
dotCooCoo
2026-11-08 14:06:24 -08:00
parent d9eb644bc6
commit 1940d3e529
2 changed files with 79 additions and 3 deletions
+19 -3
View File
@@ -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<Arc<Node>> = frame.children.iter().map(|n| Arc::new(self.doc.reid(n))).collect();
let art: Vec<Arc<Node>> = 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<Arc<Node>> = frame.children.iter().map(|n| Arc::new(self.doc.reid(n))).collect();
let mut art: Vec<Arc<Node>> = 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)),
+60
View File
@@ -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<vectorcraft_doc::NodeId>) {
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<vectorcraft_doc::NodeId>) {
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"]);