From 7beebfabb93bc027c46d1f7d00d289af2284b012 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Fri, 16 Oct 2026 01:19:36 +0700 Subject: [PATCH] eps: apply the Large Canvas scale to EPS files only; a .ai keeps its art space (#2031) --- crates/eps/src/import/ai/read.rs | 20 ++++++++++++-------- crates/eps/src/import/ai/tests.rs | 5 +++++ crates/eps/src/import/native.rs | 4 +++- crates/eps/src/import/tests_native.rs | 4 ++++ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/eps/src/import/ai/read.rs b/crates/eps/src/import/ai/read.rs index be9f84eb1..6956c01ab 100644 --- a/crates/eps/src/import/ai/read.rs +++ b/crates/eps/src/import/ai/read.rs @@ -114,9 +114,12 @@ pub(crate) fn slot_frame(name: Option<&str>) -> usize { name.and_then(|n| n.strip_prefix(TEXT_SLOT)).and_then(|rest| rest.split_once(':')).and_then(|(_, f)| f.parse().ok()).unwrap_or(0) } -/// Read the structure of the editing data `data`. -pub fn read(data: &[u8]) -> Result { +/// Read the structure of the editing data `data`, applying the file's `%AI24_LargeCanvasScale` only +/// when `large_canvas` is set: an EPS's page is drawn at the full size, but a `.ai`'s PDF part may +/// keep the reduced one, which the PDF import reads as it is (#2031). +pub fn read(data: &[u8], large_canvas: bool) -> Result { let mut r = Reader::new(data); + r.large_canvas = large_canvas.then_some(1.0); r.run()?; r.finish() } @@ -392,8 +395,9 @@ struct Reader<'a> { art_size: Option<(f64, f64)>, template_center: Option<(f64, f64)>, /// `%AI24_LargeCanvasScale`: a Large Canvas document's editing data is written at 1/N scale - /// (see [`Reader::ensure_space`]); 1 (no scaling) when the file doesn't declare one. - large_canvas: f64, + /// (see [`Reader::ensure_space`]); 1 (no scaling) when the file doesn't declare one, `None` + /// when the declared factor is not applied ([`read`]). + large_canvas: Option, /// This reader reads a pattern's definition (see [`Reader::define_pattern`]). pattern: Option, /// The tile's top left (y down) of each pattern read, by name. @@ -528,7 +532,7 @@ impl<'a> Reader<'a> { slot_names: HashMap::new(), art_size: None, template_center: None, - large_canvas: 1.0, + large_canvas: None, pattern: None, pattern_tiles: HashMap::new(), text: None, @@ -669,10 +673,10 @@ impl<'a> Reader<'a> { // being this; the printed page applies the same factor, so the art reaches the page's // size once it is applied to the art space ([`Self::ensure_space`]). A value outside // the range Illustrator writes is left alone. - if let Ok(n) = v.trim().parse::() + if let (Some(scale), Ok(n)) = (&mut self.large_canvas, v.trim().parse::()) && (1.0..=100.0).contains(&n) { - self.large_canvas = n; + *scale = n; } } else if let Some(v) = line.strip_prefix("AI9_ColorModel:") { self.color_model = Some(v.trim() == "2"); @@ -1881,7 +1885,7 @@ impl<'a> Reader<'a> { } let first = self.first_board().or_else(|| self.bbox_board()); let top_left = first.map_or(Point::new(0.0, 792.0), |r| Point::new(r.x0, r.y1)); - let s = self.large_canvas; + let s = self.large_canvas.unwrap_or(1.0); let m = Affine::new([s, 0.0, 0.0, -s, -s * top_left.x, s * top_left.y]); self.to_doc = Some(m); m diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 2f9552b55..5b201fd68 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -9,6 +9,11 @@ use vectorcraft_doc::{AppearanceItem, ColorMode, Document, Knockout, LayerColor, use super::*; +/// [`super::read`] as an EPS reads it. +fn read(data: &[u8]) -> Result { + super::read(data, true) +} + /// Editing data: header comments (crop marks 0 0 200 100), then `body`. fn stream(body: &str) -> String { format!( diff --git a/crates/eps/src/import/native.rs b/crates/eps/src/import/native.rs index 32a6a966e..729bb807d 100644 --- a/crates/eps/src/import/native.rs +++ b/crates/eps/src/import/native.rs @@ -872,7 +872,9 @@ struct Frame<'a> { /// nothing is compared. `outlined`: the page's type was read as outlines, so it has none to put in /// the text objects. fn build(data: &[u8], visible: Option<&Document>, page: Page, outlined: bool, trust: bool) -> Result<(Document, Vec), String> { - let Structure { mut doc, to_doc, bbox, artboard, template, hidden_unread, slot_names, warnings } = ai::read(data)?; + // An EPS (its page is its art) draws a Large Canvas page at full size; a `.ai` keeps its art + // space, as its PDF part may (#2031). + let Structure { mut doc, to_doc, bbox, artboard, template, hidden_unread, slot_names, warnings } = ai::read(data, matches!(page, Page::Art))?; let (mut shown, mut hidden) = (vec![], vec![]); let mut frame = None; if let Some(visible) = visible { diff --git a/crates/eps/src/import/tests_native.rs b/crates/eps/src/import/tests_native.rs index 51f88251a..0c5ee221d 100644 --- a/crates/eps/src/import/tests_native.rs +++ b/crates/eps/src/import/tests_native.rs @@ -269,6 +269,10 @@ fn a_large_canvas_eps_keeps_its_layers_at_the_pages_size() { let r = import(&eps(&page_square(10, 10), &art)).unwrap(); assert_eq!(names(&r.document), ["Layer 1"], "{:?}", r.warnings); assert!(r.warnings.iter().any(|w| w.contains("different page")), "{:?}", r.warnings); + // A `.ai` keeps its art space: its PDF part is read at the size it declares. + let art = native_large_canvas(100.0, 10.0, &layer("Art", true, &square(10, 10))); + let (d, w, read) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, false); + assert!(read && names(&d) == ["Art"], "{w:?}"); } /// A `.ai` whose layers can't be read comes in as its PDF part, a page per artboard in a row; its