From ba32ef5b099288fc944b9cd80b5f55a6a01068fc Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 17 Oct 2026 16:13:37 -0700 Subject: [PATCH] .ai import: the editing data reader warns about each image it can't read An image in a .ai's editing data whose operands had no matrix, fewer than 11 numbers, or a matrix of other than six numbers was left out with no warning. So was one whose matrix maps it onto a line or a point. The reader's warnings did not mention such an image, while the reader warned "an image VectorCraft can't read was left out" for an image whose size or bit depth it can't use. Each of those exits now adds the same warning, as does the one where the decoded pixels don't make an image. --- crates/eps/src/import/ai/read.rs | 21 +++++++++++++++---- crates/eps/src/import/ai/tests.rs | 34 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/crates/eps/src/import/ai/read.rs b/crates/eps/src/import/ai/read.rs index 161407d03..432a3dd23 100644 --- a/crates/eps/src/import/ai/read.rs +++ b/crates/eps/src/import/ai/read.rs @@ -2012,9 +2012,15 @@ impl<'a> Reader<'a> { /// An image of operands `vals` and samples `data`. fn image_from(&mut self, vals: Vec, data: &[u8]) -> Result<(), String> { let space = self.raster.as_ref().and_then(|r| r.space.clone()).unwrap_or_default(); - let Some(m) = vals.iter().find_map(|v| if let V::Arr(a) = v { Some(Self::nums(a)) } else { None }) else { return Ok(()) }; + let Some(m) = vals.iter().find_map(|v| if let V::Arr(a) = v { Some(Self::nums(a)) } else { None }) else { + self.warn("an image VectorCraft can't read was left out"); + return Ok(()); + }; let n = Self::nums(&vals); - let [_, _, _, _, w, h, bits, kind, alpha, _, binary, ..] = n.as_slice() else { return Ok(()) }; + let [_, _, _, _, w, h, bits, kind, alpha, _, binary, ..] = n.as_slice() else { + self.warn("an image VectorCraft can't read was left out"); + return Ok(()); + }; let (w, h) = (*w as u64, *h as u64); if w == 0 || h == 0 || w.saturating_mul(h) > MAX_PIXELS || ![1.0, 8.0].contains(bits) { self.warn("an image VectorCraft can't read was left out"); @@ -2080,7 +2086,10 @@ impl<'a> Reader<'a> { rgba.extend_from_slice(&[px[0], px[1], px[2], a]); } } - let Some(img) = image::RgbaImage::from_raw(w as u32, h as u32, rgba) else { return Ok(()) }; + let Some(img) = image::RgbaImage::from_raw(w as u32, h as u32, rgba) else { + self.warn("an image VectorCraft can't read was left out"); + return Ok(()); + }; let mut png = vec![]; if img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).is_err() { self.warn("an image VectorCraft can't read was left out"); @@ -2096,10 +2105,14 @@ impl<'a> Reader<'a> { // The matrix is an art-space matrix (y up) applied to the pixels with their rows running // downwards, (u, -v): pixels → (u, -v) → art space. Flipping the matrix's result instead // gives the same place only when the image isn't rotated (b = c = 0). - let [a, b, c, d, tx, ty] = m else { return Ok(()) }; + let [a, b, c, d, tx, ty] = m else { + self.warn("an image VectorCraft can't read was left out"); + return Ok(()); + }; let to_doc = self.space(); let xf = to_doc * Affine::new([*a, *b, *c, *d, *tx, *ty]) * FLIP; if !xf.as_coeffs().iter().all(|v| v.is_finite()) || xf.determinant().abs() < 1e-12 { + self.warn("an image VectorCraft can't read was left out"); return Ok(()); } let mask = alpha.and_then(|a| self.alpha_mask(a, w, h, xf)); diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 72d625077..cb700fbfc 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -677,6 +677,40 @@ fn images_with_alpha_channels() { assert!(art(&r.doc).is_empty() && r.warnings.iter().any(|w| w.contains("cut short"))); } +/// Editing data `data` opens without its one image and with the warning "an image VectorCraft can't read was left out" once. +fn image_left_out_with_a_warning(data: &[u8]) { + let r = read(data).unwrap(); + assert!(art(&r.doc).is_empty(), "{:#?}", art(&r.doc)); + let left_out = r.warnings.iter().filter(|w| *w == "an image VectorCraft can't read was left out").count(); + assert_eq!(left_out, 1, "{:?}", r.warnings); +} + +#[test] +fn an_image_without_a_matrix_is_left_out_with_a_warning() { + image_left_out_with_a_warning(&image("", (1, 1), ("DeviceRGB", 3, 0), &[255, 0, 0], 3)); +} + +#[test] +fn an_image_with_too_few_operands_is_left_out_with_a_warning() { + // The operands hold 10 numbers after the matrix, one short of the binary flag. + let text = String::from_utf8(image("[ 1 0 0 1 0 100 ]", (1, 1), ("DeviceRGB", 3, 0), b"abc", 3)).unwrap(); + let data = text.replacen("] 0 0 1 1 1 1 8 3 0 0 1 0 4 4 0 0\n", "] 0 0 1 1 1 1 8 3 0 0\n", 1); + assert_ne!(data, text); + image_left_out_with_a_warning(data.as_bytes()); +} + +#[test] +fn an_image_whose_matrix_isnt_six_numbers_is_left_out_with_a_warning() { + image_left_out_with_a_warning(&image("[ 1 0 0 1 ]", (1, 1), ("DeviceRGB", 3, 0), &[255, 0, 0], 3)); +} + +#[test] +fn an_image_whose_matrix_has_no_area_is_left_out_with_a_warning() { + image_left_out_with_a_warning(&image("[ 0 0 0 0 0 0 ]", (1, 1), ("DeviceRGB", 3, 0), &[255, 0, 0], 3)); + // This matrix maps the image onto a line. + image_left_out_with_a_warning(&image("[ 1 2 2 4 0 100 ]", (1, 1), ("DeviceRGB", 3, 0), &[255, 0, 0], 3)); +} + /// The raster matrix `[a b c d tx ty]` is an art-space matrix (y up) applied to the image's pixels /// with their rows running downwards, `(u, -v)`: art x = a·u − c·v + tx, art y = b·u − d·v + ty. /// Without rotation (b = c = 0) that is the same as flipping the matrix's result; a rotated image