.ai import: a brush object without a name counts as a brush left out

A plug-in object that names a brush kind but ends before the brush's
name was dropped without a trace. When it was the file's only brush
object, the document kept the default brushes and showed no note. It is
now counted with the other brushes that can't be read, so the file gets
the Brushes panel note and no default brushes.
This commit is contained in:
dotCooCoo
2026-11-04 18:28:40 +07:00
committed by bflatastic
parent 67cc383d77
commit 55dc5bb4a5
3 changed files with 30 additions and 7 deletions
+6 -4
View File
@@ -126,8 +126,9 @@ pub(super) enum BrushError {
}
/// The strings of a plug-in object's section (`section`: what follows `%AI8_BeginPluginObject`):
/// its plug-in name, its name and its parameters, joined. `None` with fewer than two strings.
pub(super) fn plugin_strings(section: &[u8]) -> Option<(String, String, String)> {
/// its plug-in name, its name and its parameters, joined. The name is `None` with one string, and
/// the whole is `None` with none.
pub(super) fn plugin_strings(section: &[u8]) -> Option<(String, Option<String>, String)> {
let mut lex = Lexer::new(section);
let mut strings = vec![];
let mut params = vec![];
@@ -142,8 +143,9 @@ pub(super) fn plugin_strings(section: &[u8]) -> Option<(String, String, String)>
}
}
}
let [kind, name]: [String; 2] = strings.try_into().ok()?;
Some((kind, name, obj::text(&params)))
let mut strings = strings.into_iter();
let kind = strings.next()?;
Some((kind, strings.next(), obj::text(&params)))
}
/// The brush manager's order (`params`: its parameters): the kind and name of each brush. Pairs
+10 -3
View File
@@ -518,16 +518,23 @@ impl<'a> Reader<'a> {
}
/// A plug-in object (`section`: what follows `%AI8_BeginPluginObject`): the brush manager's
/// order or a brush ([`brush`]); other objects are left alone.
/// order or a brush ([`brush`]); other objects are left alone. A brush without a name is counted
/// as not read.
fn plugin_object(&mut self, section: &[u8]) {
let Some((kind, name, params)) = brush::plugin_strings(section) else { return };
if kind == brush::MANAGER {
self.brush_objects = true;
self.brush_order = brush::parse_brush_manager(&params);
if name.is_some() {
self.brush_objects = true;
self.brush_order = brush::parse_brush_manager(&params);
}
return;
}
let Some(tool) = brush::tool(&kind) else { return };
self.brush_objects = true;
let Some(name) = name else {
self.brushes_unread += 1;
return;
};
if self.brush_drafts.len() >= brush::MAX_BRUSHES {
self.brushes_unread += 1;
return;
+14
View File
@@ -5224,6 +5224,20 @@ fn a_file_whose_brushes_are_all_left_out_has_none() {
assert!(vectorcraft_brush::library(&r.doc).is_empty());
}
/// A brush object that ends after its plug-in name is counted as a brush left out, and the file
/// gets no default brushes.
#[test]
fn a_brush_object_without_a_name_is_counted_as_left_out() {
let nameless = format!("%AI5_Begin_NonPrinting\nNp\n%AI8_BeginPluginObject\n({CALLIGRAPHIC})\n%AI8_EndPluginObject\n%AI5_End_NonPrinting--\n");
let r = read_ok(&format!("{nameless}{}", layer("")));
assert!(vectorcraft_brush::library(&r.doc).is_empty(), "{:?}", r.warnings);
assert!(r.warnings.iter().any(|w| w.ends_with("Brushes panel (1)")), "{:?}", r.warnings);
let r = read_ok(&format!("{}{nameless}{}", plugin_object(CALLIGRAPHIC, "Nib", NIB), layer("")));
let lib = vectorcraft_brush::library(&r.doc);
assert_eq!(lib.iter().map(|b| b.name.as_str()).collect::<Vec<_>>(), ["Nib"], "{:?}", r.warnings);
assert!(r.warnings.iter().any(|w| w.ends_with("Brushes panel (1)")), "{:?}", r.warnings);
}
/// A gradient's `Br` (a stop's ramp) isn't a brush: the gradient keeps its stops.
#[test]
fn a_gradients_br_is_not_a_brush() {