Merge pull request #6259 from dotCooCoo/fix/ai-pattern-mask-ids

.ai import: opacity mask art in pattern and brush art takes document ids
This commit is contained in:
BFlat
2026-11-09 07:46:40 +08:00
committed by GitHub
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"]);