.ai import: a PDF part cropped to the art no longer hides the layers

A PDF-compatible .ai whose PDF part's pages are cropped to its art (each
page the box of all the art, the editing data's bounding box) rather
than to its artboards came in as that PDF part: the editing data's
artboard wasn't the page's size, so its layers were refused. With
several artboards every page held all the art, so the document had a
copy of everything per page.

When the artboard isn't the page's size but the editing data's bounding
box is, the layers are compared with the page on that box and keep
their artboards. The comparison of their art with the page still
decides whether they are used.
This commit is contained in:
Don Omondi
2026-10-30 08:15:04 +07:00
committed by bflatastic
parent 494de9dfa2
commit 0f1b70f8b1
5 changed files with 55 additions and 8 deletions
@@ -535,6 +535,27 @@ fn an_ai_file_whose_pdf_part_is_only_text_still_opens_from_editing_data_that_rea
check_sample(&s);
}
#[test]
fn an_ai_file_whose_pdf_part_is_cropped_to_its_art_opens_from_its_layers() {
// A 400 × 200 pt artboard with two red bars across (100, 50)–(300, 150). The PDF part's page
// is that art's box, 200 × 100 pt (the editing data's bounding box), not the artboard.
let red = "0 1 1 0 1 0 0 Xa\n";
let art = ai::layer("Art", &format!("{red}{}f\n{red}{}f\n", ai::rect(100.0, 50.0, 180.0, 150.0), ai::rect(220.0, 50.0, 300.0, 150.0)));
let data = ai::editing_data_in([100.0, 50.0, 300.0, 150.0], [0.0, 0.0, 400.0, 200.0], &art);
let page = "1 0 0 rg 0 0 80 100 re f 120 0 80 100 re f";
let mut s = Session::new();
let r = open(&mut s, "cropped.ai", &ai::ai(&compressed(data.as_bytes()), page), json!({})).unwrap();
assert!(!r["warnings"].to_string().contains("weren't read"), "{r}");
let doc = &s.doc().unwrap().doc;
assert_eq!(names(&doc.layers), ["Art"]);
assert_eq!(doc.artboards.len(), 1);
assert_eq!(doc.artboards[0].rect, vectorcraft_geom::Rect::new(0.0, 0.0, 400.0, 200.0), "the file's artboard, not its page");
let bars = doc.layers[0].children().unwrap();
assert_eq!(bars.len(), 2);
let b = bars[0].geometric_bounds().unwrap();
assert!((b.x0 - 100.0).abs() < 0.01 && (b.y0 - 50.0).abs() < 0.01, "where the artboard has it: {b:?}");
}
#[test]
fn editing_data_false_opens_only_what_the_page_prints() {
let mut s = Session::new();
+12 -2
View File
@@ -79,8 +79,18 @@ pub(super) fn build_from(
let page_rect = visible.artboards.first().map(|a| a.rect).ok_or("it has no page")?;
// The layers' art in the document's terms: a Large Canvas editing copy is written at 1/N
// scale, which `to_doc` carries, so its art and the page reach the same size (#2031).
let rect = to_doc.transform_rect_bbox(art_box);
if (rect.width() - page_rect.width()).abs() > 0.5 || (rect.height() - page_rect.height()).abs() > 0.5 {
let fits = |r: &Rect| (r.width() - page_rect.width()).abs() <= 0.5 && (r.height() - page_rect.height()).abs() <= 0.5;
let mut rect = to_doc.transform_rect_bbox(art_box);
// A `.ai` whose PDF part's pages are cropped to its art (each page the box of all of it,
// the editing data's bounding box) rather than to its artboards: the layers are compared
// with the page on that box, and keep their artboards.
if matches!(page, Page::Artboard)
&& !fits(&rect)
&& let Some(art) = bbox.map(|[a, b, c, d]| to_doc.transform_rect_bbox(Rect::new(a, b, c, d))).filter(fits)
{
rect = art;
}
if !fits(&rect) {
// Both sizes in points as the fields show them, and the editing data's page to the
// file's as a scale (1:10 is a tenth of its size) when its sides are in proportion and
// the scale doesn't round to 1:1.
+9 -2
View File
@@ -381,11 +381,18 @@ fn an_ai_file_that_cant_be_read_through_its_editing_data_keeps_its_pdf_part() {
let (d, w, read) = crate::layered_ai(b"nothing", pdf.clone(), vec![], false, false, 1.0);
assert_eq!(d.layers.len(), pdf.layers.len());
assert!(w.is_empty() && !read);
// Editing data that isn't for this page.
let other = native(&layer("Jig", true, &square(10, 10))).replace("%AI3_Cropmarks: 0 0 100 100", "%AI3_Cropmarks: 0 0 50 50");
// Editing data that isn't for this page: neither its artboard nor its art's box is the page.
let other = native(&layer("Jig", true, &square(10, 10))).replace(" 0 0 100 100", " 0 0 50 50");
let (d, w, _) = crate::layered_ai(&private(&other), pdf.clone(), vec![], false, false, 1.0);
assert_eq!(names(&d), names(&pdf));
assert!(w.iter().any(|w| w.contains("for a 50 × 50 pt page, the file's 100 × 100 pt page at 1:2")), "{w:?}");
// A page cropped to the art's box, not the artboard, is that art's page: the layers are read,
// with their artboard.
let cropped = native(&layer("Jig", true, &square(10, 10))).replace("%AI3_Cropmarks: 0 0 100 100", "%AI3_Cropmarks: 0 0 50 50");
let (d, w, read) = crate::layered_ai(&private(&cropped), pdf.clone(), vec![], false, false, 1.0);
assert!(read, "{w:?}");
assert_eq!(names(&d), ["Jig"]);
assert_eq!(d.artboards[0].rect.size(), vectorcraft_geom::Size::new(50.0, 50.0));
}
/// #2034: editing data for a page of another size gives both sizes in points, in an EPS's note and
+10 -2
View File
@@ -5,9 +5,17 @@
/// Editing data of a `width` × `height` page: header comments (the art's bounding box and the crop
/// marks are the page), `body` (setup and layers), the trailer.
pub fn editing_data(width: f64, height: f64, body: &str) -> String {
editing_data_in([0.0, 0.0, width, height], [0.0, 0.0, width, height], body)
}
/// [`editing_data`] whose art's bounding box (`x0 y0 x1 y1`, art space) is `art` and whose
/// artboard (the crop marks) is `board`.
pub fn editing_data_in(art: [f64; 4], board: [f64; 4], body: &str) -> String {
let [a0, a1, a2, a3] = art;
let [b0, b1, b2, b3] = board;
format!(
"{PS_HEADER}\n%%Creator: VectorCraft tests\n%%BoundingBox: 0 0 {width} {height}\n%%HiResBoundingBox: 0 0 {width} {height}\n\
%AI5_FileFormat 14.0\n%AI3_Cropmarks: 0 0 {width} {height}\n%%EndComments\n\
"{PS_HEADER}\n%%Creator: VectorCraft tests\n%%BoundingBox: {a0} {a1} {a2} {a3}\n%%HiResBoundingBox: {a0} {a1} {a2} {a3}\n\
%AI5_FileFormat 14.0\n%AI3_Cropmarks: {b0} {b1} {b2} {b3}\n%%EndComments\n\
%%BeginProlog\n%%EndProlog\n%%BeginSetup\n%%EndSetup\n{body}\n%%PageTrailer\ngsave annotatepage grestore showpage\n%%Trailer\n%%EOF\n"
)
}
File diff suppressed because one or more lines are too long