diff --git a/crates/eps/src/import/ai/mod.rs b/crates/eps/src/import/ai/mod.rs index 449eb73c0..d74fba703 100644 --- a/crates/eps/src/import/ai/mod.rs +++ b/crates/eps/src/import/ai/mod.rs @@ -38,8 +38,10 @@ pub(crate) use read::{slot_frame, slot_of}; #[cfg(test)] mod tests; -/// Largest editing data read, decompressed (bytes). -pub const MAX_DATA: u64 = 256 << 20; +/// Largest editing data read, decompressed (bytes). Files with embedded rasters reach 340 MB (a +/// 30 MB poker box template unpacks to 322 MiB), so 64-bit builds read twice the old 256 MiB; a +/// 32-bit address space (wasm) keeps it. +pub const MAX_DATA: u64 = if cfg!(target_pointer_width = "64") { 512 << 20 } else { 256 << 20 }; /// Largest Zstandard window accepted (a frame asking for more is damaged data). const MAX_ZSTD_WINDOW: u64 = 64 << 20; /// Most Zstandard frames read (the app writes one). diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 3d90a4323..6a0d914c5 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -1193,6 +1193,22 @@ fn a_linked_images_colour_space_operator_is_ignored() { assert_eq!(art(&read_ok(&layer(&art_)).doc).len(), 1); } +/// Editing data past the old 256 MiB limit still opens on a 64-bit build (#3510): a card file with +/// embedded rasters unpacks to over 300 MiB. +#[cfg(target_pointer_width = "64")] +#[test] +fn editing_data_past_256_mib_is_read() { + let mut body = stream(&layer("")).into_bytes(); + // Comment lines the reader skips, so the data stays valid while it grows past the old limit. + let filler = vec![b'%'; (257 << 20) - body.len()]; + body.extend_from_slice(&filler); + let raw = [&b"%AI12_CompressedData"[..], &crate::ps::deflate(&body)].concat(); + assert!(raw.len() < 1 << 20, "zeros compress"); + let data = decode_private(&raw).unwrap(); + assert_eq!(data.len(), body.len()); + assert!(too_large().contains("512 MB")); +} + #[test] fn many_zstandard_frames_are_read_in_one_pass_and_capped() { let frame = zstd(b" "); diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index f2eea9f27..91f785528 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -130,6 +130,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri | Date | Change | Summary | |---|---|---| +| 2026-10-25 | minor | `.ai` import: the editing data is read up to 512 MiB on 64-bit builds (the limit was 256 MiB), so a file whose embedded rasters unpack to 300 MiB or more keeps its layers, groups and text instead of opening as its PDF part (part of #3510); 32-bit and web builds keep 256 MiB. Own-fixture test `import::ai::tests::editing_data_past_256_mib_is_read`; scores and hours unchanged | | 2026-10-25 | minor | `.ai` import: the `--` placeholder of a radial gradient's highlight (`-- -- angle length Bh`, a gradient without one) and the `XC` operator after a linked image's file name no longer send the whole file to its PDF part, so files with them keep their layers, groups and live text (#4176). A `--` anywhere else is still an operator the reader doesn't know. Own-fixture tests in `import::ai::tests`; scores and hours unchanged | | 2026-10-24 | minor | EPS, EMF/WMF and legacy `.ai`: type with ruby (ふりがな) is written with the reading's glyphs where the canvas draws them (they were dropped); legacy `.ai` writes such type as outlines, since a text object can't hold ruby. Tests `eps::tests::ruby_is_written_with_its_type`, `tests_legacy_ai::type_with_ruby_is_outlines_with_the_reading`, `metafile::tests::ruby_is_written_with_its_type`; scores and hours unchanged | | 2026-10-24 | minor | `.ai` import: an image larger than the editing data's reader takes (2^25 pixels, a halftone texture at print resolution) no longer leaves its layer blank: the file comes in as its PDF part, which reads larger images, with a note saying why (#1479). Own-fixture test; scores and hours unchanged |