PDF import: leave art past the page unclipped in a PDF-compatible .ai

The PDF part of an .ai stands in for the editable document, whose art crossing
the artboard edge isn't clipped, so it keeps opening whole and editable (#646).
Plain PDFs still get the clip to the crop box. path_count is back to counting
every path: no clipping paths appear in the .ai cases it counts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
fastmikey99andClaude Opus 5.5 authored and bflatastic committed 2026-10-10 08:26:48 +07:00
1 parent 1d0305fc2a
commit c2339fca81
2 files changed
+24 -4

No files matched your search

+4 -2
View File
@@ -235,11 +235,13 @@ pub fn import_with_report(bytes: &[u8], opts: &ImportOptions) -> Result<ImportRe
// dropped as redundant (by hayro and by `push_clip_path`), so restore the box here:
// each object reaching past it keeps its own clip, which Fit to Artwork Bounds and
// `CropTo::Bounding` then respect.
// The crop box is what viewers show, whichever box the artboard takes.
// The crop box is what viewers show, whichever box the artboard takes. Not in a
// PDF-compatible `.ai`: its PDF part stands in for the editable document, whose art
// crossing the artboard edge isn't clipped, so it opens whole (#646).
let shown = xf.transform_rect_bbox(crate::pages::page_box(page, CropTo::Crop));
let page_box = shown.inflate(0.01, 0.01);
let mut clipped = false;
for (_, art) in &mut parts {
for (_, art) in parts.iter_mut().filter(|_| !ai) {
for n in art.iter_mut() {
if n.visual_bounds().is_some_and(|r| !contains(page_box, r)) {
let clip = Arc::new(b.clip_node(&shown.to_path(0.1), FillRule::NonZero));
+20 -2
View File
@@ -165,10 +165,9 @@ fn an_ai_file_notes_that_its_art_off_the_artboards_is_not_read() {
}
}
/// Paths that are art (not clipping paths: art reaching past the page keeps a clip to it).
fn path_count(d: &Document) -> usize {
let mut n = 0;
d.walk(|c| n += usize::from(matches!(c.kind, NodeKind::Path { clipping: false, .. })));
d.walk(|c| n += usize::from(matches!(c.kind, NodeKind::Path { .. })));
n
}
@@ -368,6 +367,25 @@ fn art_past_the_scan_limit_of_shown_layers_stays_shown() {
assert!(r.warnings.iter().any(|w| w.contains("went to its page's layer")), "{:?}", r.warnings);
}
/// Art reaching past the page keeps a clip to it in a plain PDF (as viewers show it), but not in
/// a PDF-compatible `.ai`, whose art crossing the artboard edge opens whole and editable (#646).
#[test]
fn art_past_the_page_is_clipped_in_a_pdf_but_not_in_an_ai_file() {
for ai in [false, true] {
let page = PdfPage {
entries: if ai { "/PieceInfo << /Illustrator << /Private << /AIPrivateData1 7 /NumBlock 1 >> >> >>".into() } else { String::new() },
..PdfPage::new(100.0, 100.0, "0 0 100 100 re W n 1 0 0 rg 50 50 200 200 re f")
};
let r = import_with_report(&pdf_with_catalog(&[page], &[], "", None), &ImportOptions::default()).unwrap();
let mut clips = 0;
r.document.walk(|n| clips += usize::from(matches!(n.kind, NodeKind::Group { clip: true, .. })));
let past = r.warnings.iter().any(|w| w == crate::import::PAST_PAGE_NOTE);
assert_eq!((clips, past), if ai { (0, false) } else { (1, true) }, "ai: {ai}: {:?}", r.warnings);
let art = r.document.art_bounds().unwrap();
assert_eq!(art.x1 > 100.5, ai, "ai: {ai}: {art:?}");
}
}
/// The editor's private data of a `.ai` is its `AIPrivateData` streams, joined in order.
#[test]
fn an_ai_files_private_data_is_its_streams_joined() {