diff --git a/crates/eps/src/import/ai/read.rs b/crates/eps/src/import/ai/read.rs index 79febd5f1..025ccc480 100644 --- a/crates/eps/src/import/ai/read.rs +++ b/crates/eps/src/import/ai/read.rs @@ -475,6 +475,12 @@ struct Reader<'a> { warnings: Vec, /// Objects read as their drawn look (several fills or strokes, effects, brushes). drawn_looks: usize, + /// The objects read as their drawn look in the art of [`Reader::brush_art`], by name: counted in + /// [`Reader::drawn_looks`] once a brush that is kept uses the art. + brush_art_looks: HashMap, + /// The objects read as their drawn look in the art of the patterns kept, by the name the document + /// has the pattern by: a pattern defined again counts only its last definition. + pattern_looks: HashMap, /// Nodes read from `%_` lines. hidden_ids: HashSet, /// The blend part each object's dictionary names (`ShapeBlendPathType`, `ShapeBlendPathID`). @@ -679,6 +685,8 @@ impl<'a> Reader<'a> { color_model: None, warnings: Vec::new(), drawn_looks: 0, + brush_art_looks: HashMap::new(), + pattern_looks: HashMap::new(), hidden_ids: HashSet::new(), blend_roles: HashMap::new(), blend_record: None, diff --git a/crates/eps/src/import/ai/read/directives.rs b/crates/eps/src/import/ai/read/directives.rs index a5445bfbf..33af13372 100644 --- a/crates/eps/src/import/ai/read/directives.rs +++ b/crates/eps/src/import/ai/read/directives.rs @@ -507,6 +507,7 @@ impl<'a> Reader<'a> { } else { self.v7_patterns.remove(&head.name); } + self.pattern_looks.insert(def.name.clone(), sub.drawn_looks); self.doc.patterns.push(def); for (key, blob) in sub.doc.images { self.doc.images.entry(key).or_insert(blob); @@ -752,6 +753,7 @@ impl<'a> Reader<'a> { && sub.frames.len() == 1 && 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 node = match art.len() { 1 => art.pop().map(Arc::unwrap_or_clone), diff --git a/crates/eps/src/import/ai/read/document.rs b/crates/eps/src/import/ai/read/document.rs index 2fd918fe6..65d949f0f 100644 --- a/crates/eps/src/import/ai/read/document.rs +++ b/crates/eps/src/import/ai/read/document.rs @@ -914,13 +914,20 @@ impl<'a> Reader<'a> { } /// The brushes read ([`brush::finish_brushes`]) and how many weren't; `None` when the file has - /// no brush objects (the document keeps the default brushes). + /// no brush objects (the document keeps the default brushes). The objects read as their drawn + /// look in the art the brushes use count toward the note on drawn looks, once for each art. fn brush_library(&mut self) -> Option<(Vec, usize)> { if !self.brush_objects { return None; } let drafts = std::mem::take(&mut self.brush_drafts); let (lib, left_out) = brush::finish_brushes(drafts, &self.brush_art, self.brush_order.as_deref()); + let used: std::collections::HashSet = lib.iter().flat_map(|b| b.art()).map(|a| a.id).collect(); + for (name, art) in &self.brush_art { + if used.contains(&art.id) { + self.drawn_looks += self.brush_art_looks.get(name).copied().unwrap_or(0); + } + } Some((lib, left_out + self.brushes_unread)) } @@ -1087,6 +1094,7 @@ impl<'a> Reader<'a> { warnings.push(brush::left_out_note(unread)); } } + self.drawn_looks += self.pattern_looks.values().sum::(); if self.drawn_looks > 0 { warnings.push(format!( "objects with several fills or strokes, effects or brushes came in as their drawn look ({}): their appearance isn't editable yet", diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 08b69f23e..f5eadb931 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -2396,6 +2396,44 @@ fn a_drawn_look_stands_for_its_object() { assert!(r.warnings.iter().any(|w| w.contains("drawn look (1)")), "{:?}", r.warnings); } +/// An object kept as its drawn look: the look (a group), then the object on `%_` lines and its style. +fn drawn_look_art() -> String { + let hidden_rect = rect_path(0.0, 0.0, 10.0, 10.0).replace('\n', "\n%_"); + format!( + "0 Ae\nu\n{RED}{}f\n{}S\nU\n%_{hidden_rect}n\n%_/ArtDictionary :\n%_/XMLUID : (Look) ; (AI10_ArtUID) ,\n%_;\n1 (Look style) XW\n", + rect_path(0.0, 0.0, 10.0, 10.0), + rect_path(0.0, 0.0, 10.0, 10.0), + ) +} + +/// A pattern's art holding an object kept as its drawn look counts in the note, as a symbol's does; +/// a definition the same name replaces later counts no longer. +#[test] +fn a_drawn_look_in_a_patterns_art_is_noted() { + let pattern = |art: &str| format!("%AI3_BeginPattern: (P)\n(P) 0 0 10 10\n0 A\n0 Xw\n{art}E\n%AI3_EndPattern\n"); + let looked = pattern(&drawn_look_art()); + let r = read_ok(&format!("{looked}{}", layer(""))); + assert_eq!(r.doc.patterns.len(), 1, "{:?}", r.warnings); + assert!(r.warnings.iter().any(|w| w.contains("drawn look (1)")), "{:?}", r.warnings); + let plain = pattern(&format!("{RED}{}f\n", rect_path(0.0, 0.0, 10.0, 10.0))); + let r = read_ok(&format!("{looked}{plain}{}", layer(""))); + assert_eq!(r.doc.patterns.len(), 1, "{:?}", r.warnings); + assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{:?}", r.warnings); +} + +/// A brush's art holding an object kept as its drawn look counts in the note once a brush that is +/// kept uses the art; art no brush uses brings no note. +#[test] +fn a_drawn_look_in_a_brushs_art_is_noted_when_a_brush_uses_it() { + let section = format!("%AI8_BeginBrushPattern\n(Dot art)\n0 A\n{}%AI8_EndBrushPattern\n", drawn_look_art()); + let r = read_ok(&format!("{section}{}{}", plugin_object(SCATTER, "Spray", SPRAY), layer(""))); + let lib = vectorcraft_brush::library(&r.doc); + assert!(matches!(brush_named(&lib, "Spray"), vectorcraft_brush::BrushKind::Scatter(_)), "{:?}", r.warnings); + assert!(r.warnings.iter().any(|w| w.contains("drawn look (1)")), "{:?}", r.warnings); + let r = read_ok(&format!("{section}{}{}", plugin_object(CALLIGRAPHIC, "Nib", NIB), layer(""))); + assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{:?}", r.warnings); +} + /// Editing data with one layer holding an image: `matrix` (`[a b c d tx ty]`), its size, colour /// space, kind and alpha channels, its samples, and the size the data comment declares. fn image(matrix: &str, (w, h): (u32, u32), (space, kind, alpha): (&str, u32, u32), data: &[u8], declared: usize) -> Vec {