.ai import: drawn looks in pattern and brush art count in the note

An object read as its drawn look counted in the import's "came in as
their drawn look" note when it was on the page or in a kept symbol, but
not when it was in a pattern's art or a brush's art. A file whose only
such objects were in a pattern or a brush opened without the note.

- A kept pattern's drawn looks count, once for each pattern name: a
  definition that a later one of the same name replaces no longer counts.
- A brush's art counts once a brush that is kept uses it; art no kept
  brush uses adds nothing.
This commit is contained in:
dotCooCoo
2026-11-07 09:00:23 +07:00
committed by bflatastic
parent ed00a8870e
commit 0d91749c74
4 changed files with 57 additions and 1 deletions
+8
View File
@@ -475,6 +475,12 @@ struct Reader<'a> {
warnings: Vec<String>,
/// 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<String, usize>,
/// 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<String, usize>,
/// Nodes read from `%_` lines.
hidden_ids: HashSet<NodeId>,
/// 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,
@@ -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<Arc<Node>> = 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),
+9 -1
View File
@@ -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<vectorcraft_brush::Brush>, 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<NodeId> = 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::<usize>();
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",
+38
View File
@@ -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<u8> {