Import PDF: linked .ai pages open on their trim boxes with the bleed

With linkPages and cropTo: crop, a PDF-compatible .ai opened on its crop
boxes, while opening it as art uses its trim boxes. It now uses the trim
boxes too. A page on its trim box shows its bleed box round the
artboard, and the pages' bleed (the widest on each side) becomes the
document's bleed, as the importer sets it.
This commit is contained in:
dotCooCoo
2026-11-14 16:42:06 -08:00
parent cc7d081198
commit e6bfe40d30
5 changed files with 58 additions and 7 deletions
File diff suppressed because one or more lines are too long
+19 -5
View File
@@ -217,8 +217,11 @@ fn page_options(page: usize, o: &LoadOptions) -> ImportOptions {
/// `document.open` with `linkPages`: the pages `o` picks of the PDF (or PDF-compatible `.ai` or
/// `.ait`) `name`, read from `path`, as a document holding one placed document per page, linked to
/// that page of the file as File › Place with Link places one ([`document::source`]). Each page
/// gets an artboard of its `o.crop` box (its art's bounds for `bounding`, a blank page's crop box), in a row from the left
/// with the gap a PDF's artboards open with, and a layer named after the page. The document opens
/// gets an artboard of its `o.crop` box (its art's bounds for `bounding`, a blank page's crop box;
/// a PDF-compatible `.ai` asked for its crop box gets its trim box, as it opens), in a row from the
/// left with the gap a PDF's artboards open with, and a layer named after the page. On its trim box,
/// a page shows its bleed box round the artboard, and the document's bleed is the pages' bleed (the
/// widest on each side). The document opens
/// in CMYK when most of the pages read in CMYK, unless `o.color_mode` names a mode. Refused without a
/// path, for any other file, with a password (a linked page is read again without one), with
/// `textAs: outlines` (a linked page shows the file's own type) and for a file over
@@ -247,6 +250,8 @@ pub(super) fn linked_pages(name: &str, bytes: &[u8], path: Option<&str>, o: &Loa
Some(range) => parse_range(range, count).map_err(|e| err(format!("pages: {e}")))?,
};
let bounding = o.crop == CropTo::Bounding;
// A PDF-compatible `.ai` asked for its crop box opens on its trim boxes ([`LoadOptions::import_options`]).
let crop = if o.crop == CropTo::Crop && opened.has_illustrator_data() { CropTo::Trim } else { o.crop };
let mut doc = Document::new(1.0, 1.0);
let board = doc.artboards.first().cloned().ok_or_else(|| err("a new document has no artboard"))?;
doc.artboards.clear();
@@ -261,7 +266,11 @@ pub(super) fn linked_pages(name: &str, bytes: &[u8], path: Option<&str>, o: &Loa
for (i, &index) in picked.iter().enumerate() {
let page = index as u32 + 1;
let file_once = document::SharedFile { bytes: &shared, hash: &hash, opened: &opened };
let read = |bounding| document::shared_source(name, file_once, Some(path), page, bounding, document::page_box(o.crop), C);
// On its trim box, a page shows its bleed box, the artboard on the trim box inside it, so
// the art in the bleed shows and prints as bleed.
let bleed = if crop == CropTo::Trim { opened.page_bleed(index) } else { None };
let page_box = if bleed.is_some() { vectorcraft_doc::PageBox::Bleed } else { document::page_box(crop) };
let read = |bounding| document::shared_source(name, file_once, Some(path), page, bounding, page_box, C);
// A blank page has no art's bounds: it keeps its crop box, as opening it as art does.
let (s, bounding) = match read(bounding) {
Err(e) if bounding && e.to_string().contains("has no art to place") => (read(false)?, false),
@@ -273,9 +282,14 @@ pub(super) fn linked_pages(name: &str, bytes: &[u8], path: Option<&str>, o: &Loa
return Err(bad(C, format!("linkPages: `{file}` isn't a PDF or a PDF-compatible .ai file")));
}
cmyk_pages += usize::from(s.color_mode == ColorMode::Cmyk);
// The document's bleed is the widest of the pages' on each side.
let [top, bottom, left, right] = bleed.unwrap_or_default();
for (side, b) in doc.setup.bleed.iter_mut().zip([top, bottom, left, right]) {
*side = side.max(b.min(vectorcraft_doc::setup::MAX_BLEED));
}
let mut node = document::node(&mut doc, &s, LinkInfo { page: Some(page), ..link.clone() }, bounding);
if let NodeKind::PlacedDocument(p) = &mut node.kind {
p.xf = Affine::translate((x, 0.0));
p.xf = Affine::translate((x - left, -top));
}
node.name = Some(file.clone());
let mut layer = Node::layer(doc.alloc_id(), &format!("Page {page}"), LayerColor::Preset((i % 27) as u8));
@@ -283,7 +297,7 @@ pub(super) fn linked_pages(name: &str, bytes: &[u8], path: Option<&str>, o: &Loa
children.push(Arc::new(node));
}
doc.layers.push(Arc::new(layer));
let rect = Rect::new(x, 0.0, x + s.frame.width(), s.frame.height());
let rect = Rect::new(x, 0.0, x + s.frame.width() - left - right, s.frame.height() - top - bottom);
doc.artboards.push(Artboard { id: i as u32 + 1, name: format!("Artboard {}", i + 1), rect, ..board.clone() });
x = rect.x1 + gap;
for w in s.warnings {
@@ -1181,6 +1181,30 @@ fn link_pages_refuses_an_ai_file_saved_without_its_pdf_part() {
let _ = std::fs::remove_dir_all(&d);
}
/// `document.open` with `linkPages` opens a PDF-compatible `.ai` asked for its crop box on its trim
/// box, as it opens as art, showing its bleed box round the artboard, which is the document's bleed.
#[test]
fn link_pages_open_an_ai_file_on_its_trim_box_with_its_bleed() {
let d = std::env::temp_dir().join(format!("vc-ailinkpagestrim-{}", std::process::id()));
std::fs::create_dir_all(&d).unwrap();
let path = d.join("trimmed.ai");
let boxes = "/TrimBox [10 10 190 90] /BleedBox [4 2 196 98]";
std::fs::write(&path, ai::ai_with(&compressed(&ai::sample_data()), "0 g 10 10 40 40 re f", boxes)).unwrap();
let path = path.to_string_lossy().to_string();
let mut s = Session::new();
s.execute("document.open", &json!({"path": path, "linkPages": true})).unwrap();
let doc = &s.doc().unwrap().doc;
let board = doc.artboards[0].rect;
assert_eq!((board.width(), board.height()), (180.0, 80.0), "the trim box");
assert_eq!(doc.setup.bleed, [8.0, 8.0, 6.0, 6.0], "top, bottom, left, right");
// The page shows its bleed box, so the art in the bleed is kept.
let placed = doc.layers[0].children().unwrap()[0].clone();
let NodeKind::PlacedDocument(p) = &placed.kind else { panic!("{:?}", placed.kind) };
assert_eq!((p.width, p.height, p.page_box), (192.0, 96.0, vectorcraft_doc::PageBox::Bleed));
assert_eq!(p.xf.translation(), vectorcraft_geom::Vec2::new(-6.0, -8.0), "the trim box on the artboard");
let _ = std::fs::remove_dir_all(&d);
}
/// A page of text alone, as text-only art has in its PDF part: no path is painted.
const TEXT_ART: &str = "BT /F1 36 Tf 10 40 Td (Text only) Tj ET";
+11
View File
@@ -297,6 +297,17 @@ impl Opened {
bleed,
}
}
/// How far page `index`'s (0-based) bleed box reaches past its trim box, `[top, bottom, left,
/// right]` in points, as the page shows (rotated); `None` past the last page, and when the bleed
/// box doesn't hold the trim box.
pub fn page_bleed(&self, index: usize) -> Option<[f64; 4]> {
let page = self.pdf.pages().get(index)?;
let init = Affine::new(page.initial_transform(true).as_coeffs());
let (trim, bleed) = (init.transform_rect_bbox(page_box(page, CropTo::Trim)), init.transform_rect_bbox(page_box(page, CropTo::Bleed)));
let sides = [trim.y0 - bleed.y0, bleed.y1 - trim.y1, trim.x0 - bleed.x0, bleed.x1 - trim.x1];
sides.iter().all(|b| b.is_finite() && *b >= 0.0).then_some(sides)
}
}
/// A PDF's pages as an import on their trim boxes sizes them ([`Opened::trim_pages`]).
+3 -1
View File
@@ -1280,7 +1280,9 @@ such a page opens from that data at the page's size.
`linkPages: true` (File › Open's Import PDF dialog: Import PDF pages as links) opens each page `pages` picks as a
placed document linked to that page of the file, as `file.place` with `link` places one, instead of as editable art.
Each page gets an artboard of its `cropTo` box (`bounding`: its art's bounds; a blank page gets its crop box), in a row from the left 36 pt apart, and
a layer named after the page (`Page 2`). The document opens in CMYK when most of those pages are painted mostly in
a layer named after the page (`Page 2`). A PDF-compatible `.ai` with `cropTo: "crop"` gets its trim boxes, as it does
when it opens as art. A page on its trim box shows its bleed box round the artboard, and the pages give the document
their bleed. The document opens in CMYK when most of those pages are painted mostly in
CMYK, unless `colorMode` names a mode. All the pages' objects share one copy of the file. The Links panel lists each
page, and `links.embed` makes a page editable art.
`document.open` refuses `linkPages` for a file that is not a PDF or a PDF-compatible `.ai`, for a file given as