diff --git a/crates/eps/src/import/ai/brush.rs b/crates/eps/src/import/ai/brush.rs index f7e1f8893..3904e6aab 100644 --- a/crates/eps/src/import/ai/brush.rs +++ b/crates/eps/src/import/ai/brush.rs @@ -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)> { 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(¶ms))) + let mut strings = strings.into_iter(); + let kind = strings.next()?; + Some((kind, strings.next(), obj::text(¶ms))) } /// The brush manager's order (`params`: its parameters): the kind and name of each brush. Pairs diff --git a/crates/eps/src/import/ai/read/directives.rs b/crates/eps/src/import/ai/read/directives.rs index e2c9df048..b1c9db39d 100644 --- a/crates/eps/src/import/ai/read/directives.rs +++ b/crates/eps/src/import/ai/read/directives.rs @@ -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(¶ms); + if name.is_some() { + self.brush_objects = true; + self.brush_order = brush::parse_brush_manager(¶ms); + } 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; diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index eb46288c4..6e97b279d 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -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::>(), ["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() {