Merge pull request #5569 from dotCooCoo/fix/ai-incomplete-brush-object-counted

.ai import: a brush object without a name counts as a brush left out
This commit is contained in:
BFlat
2026-11-04 18:51:04 +07:00
committed by GitHub
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() {