From cec31c5f4abf034c3732b1f887b140955ef73b88 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 18 Oct 2026 09:14:05 -0700 Subject: [PATCH] PDF/.ai import: a .ai saved without PDF compatibility opens when pages names every page A .ai saved without PDF compatibility has only a placeholder page in its PDF part. document.open reads such a file from its editing data, but with `pages` given it failed with "the file holds only a placeholder page", even for "1" on a file of one page. When `pages` names every page of the file, document.open now reads the editing data as it does without `pages`. Docs: docs/mcp.md. --- crates/engine/src/cmd/fileio/load.rs | 12 ++++++++++-- crates/engine/src/cmd/fileio/tests_aiimport.rs | 13 +++++++++++++ docs/mcp.md | 3 ++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/cmd/fileio/load.rs b/crates/engine/src/cmd/fileio/load.rs index 58ee8ee39..1b35be869 100644 --- a/crates/engine/src/cmd/fileio/load.rs +++ b/crates/engine/src/cmd/fileio/load.rs @@ -194,6 +194,12 @@ fn from_editing_data_alone(bytes: &[u8], opts: &LoadOptions) -> Option bool { + let Some(range) = &opts.pages else { return true }; + vectorcraft_pdf::info(bytes, opts.password.as_deref()).is_ok_and(|i| parse_range(range, i.pages.len()).is_ok_and(|p| p.len() == i.pages.len())) +} + /// [`load`] with `document.open` options (the PDF pages, box and password; the DXF options). pub fn load_with(name: &str, bytes: &[u8], opts: &LoadOptions) -> Result { let format = detect(name, bytes).ok_or_else(|| match super::unsupported(&super::extension(name)) { @@ -239,9 +245,11 @@ pub fn load_with(name: &str, bytes: &[u8], opts: &LoadOptions) -> Result let alone = r.only_text.then(|| from_editing_data_alone(bytes, opts)).flatten().and_then(|read| read.ok()); Ok(alone.unwrap_or_else(|| through_editing_data(bytes, opts, r.document, r.warnings, r.user_unit))) } - // Saved without its PDF part: the editing copy is all the file has. + // Saved without its PDF part: the editing copy is all the file has (`pages` that + // name every page read it as the whole file). Err(e) if e.to_string() == vectorcraft_pdf::PdfError::PlaceholderOnly.to_string() => { - from_editing_data_alone(bytes, opts).unwrap_or(Err(e)) + let whole = LoadOptions { pages: None, ..opts.clone() }; + from_editing_data_alone(bytes, if every_page(bytes, opts) { &whole } else { opts }).unwrap_or(Err(e)) } Err(e) => Err(e), })? diff --git a/crates/engine/src/cmd/fileio/tests_aiimport.rs b/crates/engine/src/cmd/fileio/tests_aiimport.rs index 119986796..d66f766d4 100644 --- a/crates/engine/src/cmd/fileio/tests_aiimport.rs +++ b/crates/engine/src/cmd/fileio/tests_aiimport.rs @@ -229,6 +229,19 @@ fn an_ai_file_saved_without_its_pdf_part_opens_from_its_editing_data() { assert!(e.contains("placeholder"), "{e}"); } +#[test] +fn an_ai_file_saved_without_its_pdf_part_opens_with_pages_naming_every_page() { + let file = ai::ai(&compressed(&ai::sample_data()), PLACEHOLDER); + for pages in [json!("1"), json!(1), json!("1-")] { + let mut s = Session::new(); + open(&mut s, "art.ai", &file, json!({"pages": pages})).unwrap(); + check_sample(&s); + } + // Without editing data, the placeholder still isn't opened as the art. + let e = open(&mut Session::new(), "plain.ai", &ai::ai(b"", PLACEHOLDER), json!({"pages": "1"})).unwrap_err().to_string(); + assert!(e.contains("placeholder"), "{e}"); +} + /// 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"; diff --git a/docs/mcp.md b/docs/mcp.md index a3359d6de..48bc08c22 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -666,7 +666,8 @@ page) opens from its editing data alone: its type is made from the file's text d area type and type on a path), and what can't be is left out with a warning. Non-native art (the content of a placed PDF, which Illustrator shows but doesn't edit and keeps as a PDF inside the editing data) is drawn by the PDF importer, as a group named "Non-native art" fitted to the object's box; one whose PDF can't be read is left out with a warning. -Without editing data it says it can't be opened. +Without editing data it says it can't be opened. `pages` that name every page of such a file also open it from its +editing data. What a PDF holds comes in as editable art: soft masks become opacity masks (an alpha mask as a white copy of its art; the backdrop colour gives Clip, an inverting transfer function Invert), transparency groups keep isolation and knockout,