mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3927 from storytold/fix/1479-ai-large-images
.ai import: an image too large for the editing data's reader opens the file as its PDF part, not with a blank layer (#1479)
This commit is contained in:
@@ -31,7 +31,7 @@ use super::lex::find;
|
||||
use crate::ps;
|
||||
|
||||
pub(crate) use frames::text_frames;
|
||||
pub use read::{Structure, artboards, read, variables};
|
||||
pub use read::{IMAGE_TOO_LARGE, Structure, artboards, read, variables};
|
||||
pub(crate) use read::{slot_frame, slot_of};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -76,6 +76,9 @@ const MAX_NODES: usize = 4_000_000;
|
||||
const MAX_POINTS: usize = 4_000_000;
|
||||
/// Largest image read (pixels).
|
||||
const MAX_PIXELS: u64 = 1 << 25;
|
||||
/// The note for an image past [`MAX_PIXELS`]: the file then opens from its PDF part, which reads
|
||||
/// larger images (#1479).
|
||||
pub const IMAGE_TOO_LARGE: &str = "an image too large to read from the editing data was left out";
|
||||
/// Most hex digits of an image's samples read from comment lines (CMYK and alpha at the largest size).
|
||||
const MAX_HEX: usize = (MAX_PIXELS as usize) * 10;
|
||||
/// Largest side of the document and its artboards (points).
|
||||
@@ -2096,7 +2099,11 @@ impl<'a> Reader<'a> {
|
||||
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) {
|
||||
if w.saturating_mul(h) > MAX_PIXELS {
|
||||
self.warn(IMAGE_TOO_LARGE);
|
||||
return Ok(());
|
||||
}
|
||||
if w == 0 || h == 0 || ![1.0, 8.0].contains(bits) {
|
||||
self.warn("an image VectorCraft can't read was left out");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1401,20 +1401,27 @@ pub fn layered_ai(private: &[u8], visible: Document, warnings: Vec<String>, outl
|
||||
if !has_layers(&data) {
|
||||
return (visible, warnings, false);
|
||||
}
|
||||
// The PDF part, its pages placed and named as the editing data's artboards are, when the
|
||||
// layers can't be used (`why`).
|
||||
let from_pdf_part = |mut visible: Document, warnings: Vec<String>, why: &str| {
|
||||
place_pages(&ai::artboards(&data), &mut visible, unit);
|
||||
let mut all = unread(warnings, why);
|
||||
all.extend(ai::variables(&data).note());
|
||||
(visible, all, false)
|
||||
};
|
||||
match build(&data, Some(&visible), Page::Artboard, outlined, trust, unit) {
|
||||
// An image too large for the editing data's reader would be missing from the layers, while
|
||||
// the PDF part has it: the file comes in as its PDF part instead (#1479).
|
||||
Ok((_, notes)) if notes.iter().any(|n| n == ai::IMAGE_TOO_LARGE) => {
|
||||
from_pdf_part(visible, warnings, "an image is too large to read from them")
|
||||
}
|
||||
Ok((mut done, notes)) => {
|
||||
done.setup.bleed = visible.setup.bleed;
|
||||
let mut all = warnings;
|
||||
all.extend(notes.into_iter().filter(|n| !all.contains(n)).collect::<Vec<_>>());
|
||||
(done, all, true)
|
||||
}
|
||||
Err(why) => {
|
||||
let mut visible = visible;
|
||||
place_pages(&ai::artboards(&data), &mut visible, unit);
|
||||
let mut all = unread(warnings, &why);
|
||||
all.extend(ai::variables(&data).note());
|
||||
(visible, all, false)
|
||||
}
|
||||
Err(why) => from_pdf_part(visible, warnings, &why),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +243,29 @@ fn in_points(mut d: Document, k: f64) -> Document {
|
||||
d
|
||||
}
|
||||
|
||||
/// #1479: an image larger than the editing data's reader takes would be missing from the layers,
|
||||
/// while the PDF part has it (it reads larger images): the file comes in as its PDF part, with a
|
||||
/// note saying why, rather than without the image.
|
||||
#[test]
|
||||
fn an_ai_file_with_an_image_too_large_for_its_editing_data_comes_in_as_its_pdf_part() {
|
||||
let raster = |w: u32, h: u32| {
|
||||
format!(
|
||||
"%AI5_File:\n%AI5_BeginRaster\n() 1 XG\n/DeviceGray XN\n[ 1 0 0 1 0 100 ] {w} {h} 0 Xh\n\
|
||||
[ 1 0 0 1 0 100 ] 0 0 {w} {h} {w} {h} 8 1 0 0 1 0 4 4 0 0\n%%BeginData: 1\rXI\n\u{0}%%EndData\r\nXH\r\n%AI5_EndRaster\r\nN"
|
||||
)
|
||||
};
|
||||
// 6,000 × 6,000 pixels: past the reader's 2^25.
|
||||
let art = native(&layer("Texture", true, &format!("{}\n{}", square(10, 10), raster(6000, 6000))));
|
||||
let (d, warnings, read) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, true, 1.0);
|
||||
assert!(!read, "the PDF part is used: {warnings:?}");
|
||||
assert!(warnings.iter().any(|w| w.contains("an image is too large to read from them")), "{warnings:?}");
|
||||
assert_eq!(d.layers[0].children().map_or(0, |c| c.len()), 1, "the PDF part's art");
|
||||
// An image the reader takes keeps the layers.
|
||||
let art = native(&layer("Texture", true, &format!("{}\n{}", square(10, 10), raster(1, 1))));
|
||||
let (_, warnings, read) = crate::layered_ai(&private(&art), pdf_part(10.0, 86.0), vec![], false, true, 1.0);
|
||||
assert!(read, "{warnings:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_ai_file_gets_its_layers_and_the_art_outside_its_artboard() {
|
||||
// A square on the artboard, and one above it (the artboard is y 0 to 100 here).
|
||||
|
||||
@@ -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-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 |
|
||||
| 2026-10-24 | minor | PDF export: a pattern fill or stroke is written as a PDF tiling pattern (ISO 32000-1, 8.7.3.1) of one period of the tiling, as the canvas repeats it, so grid, brick and hex tilings and overlapping tiles keep their look in a few kilobytes, and PDF import reads it back as one object with a pattern swatch instead of hundreds of tile paths (#3849). Patterns nested more than four deep, or with tiles beyond a billion points, are still written as their tiles, now with a warning. Tests: the issue's hatch round trip, a brick tiling read back against the canvas, the nesting warning; scores and hours unchanged |
|
||||
| 2026-10-23 | minor | SVG: type written as outlines stacks each run's colour glyphs (bitmap images, COLR and SVG art) under the run's paint, as the canvas does, so a plain character set over a colour one stays on top; scores unchanged |
|
||||
| 2026-10-23 | minor | PDF import: a line's spacing is measured from the file's own glyph widths, so type in a font the file doesn't carry, drawn with an installed font whose advances its widths round off (Helvetica on macOS: 2048 units an em), keeps one tracking value on its letters instead of a run per character (#722 follow-up); test with built-in Inter, independent of the fonts installed; scores and hours unchanged |
|
||||
|
||||
Reference in New Issue
Block a user