Fix #1387: a .ai whose PDF part is only text opens from it

This commit is contained in:
bflatastic
2026-10-12 19:50:53 +07:00
parent bfb173e967
commit 7f5d8a3db1
8 changed files with 88 additions and 18 deletions
+6 -1
View File
@@ -222,7 +222,12 @@ pub fn load_with(name: &str, bytes: &[u8], opts: &LoadOptions) -> Result<Loaded>
.flatten()
.map(|e| (e.intact, move || Some(e.data)));
restore_or_import(editing, || match super::pdfimport::import(bytes, opts) {
Ok((doc, warnings)) => Ok(through_editing_data(bytes, opts, doc, warnings)),
Ok(r) => {
// Pages of text alone: the editing copy alone where it reads, as for a file
// saved without its PDF part (#1387: else the page, which is the art).
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)))
}
// Saved without its PDF part: the editing copy is all the file has.
Err(e) if e.to_string() == vectorcraft_pdf::PdfError::PlaceholderOnly.to_string() => {
from_editing_data_alone(bytes, opts).unwrap_or(Err(e))
+3 -4
View File
@@ -130,10 +130,9 @@ fn choice<T: Choice + Default>(cmd: &str, key: &str, id: Option<&str>) -> Result
.ok_or_else(|| bad(cmd, format!("{key} must be one of {}", T::IDS.join(", "))))
}
/// Import the pages `o` pick → the document and import notes.
pub(super) fn import(bytes: &[u8], o: &LoadOptions) -> Result<(Document, Vec<String>)> {
let r = vectorcraft_pdf::import_with_report(bytes, &o.import_options(bytes)?).map_err(err)?;
Ok((r.document, r.warnings))
/// Import the pages `o` pick → the document, import notes and what else the import tells.
pub(super) fn import(bytes: &[u8], o: &LoadOptions) -> Result<vectorcraft_pdf::ImportReport> {
vectorcraft_pdf::import_with_report(bytes, &o.import_options(bytes)?).map_err(err)
}
/// Page `page` (0-based) of a PDF as a document of one artboard (its `o.crop` box) and one
@@ -154,6 +154,52 @@ fn an_ai_file_saved_without_its_pdf_part_opens_from_its_editing_data() {
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";
/// The kinds of what the first layer holds: type (or its outlines, without the font) and others.
fn text_and_others(s: &Session) -> (usize, usize) {
let art = s.doc().unwrap().doc.layers[0].children().unwrap();
let text = art.iter().filter(|n| matches!(n.kind, NodeKind::Text(_)) || n.name.as_deref() == Some("<Text Outlines>")).count();
(text, art.len() - text)
}
#[test]
fn an_ai_file_whose_pdf_part_is_only_text_opens_from_it_when_its_editing_data_cant_be_read() {
// #1387. The page states where its art is (an art, trim or bleed box), which the notice
// page of a file saved without its PDF part has no art to do.
let unreadable = b"%AI24_ZStandard_Data\0\0\0\0\0\0\0\0";
for boxed in ["/ArtBox [0 0 200 100]", "/TrimBox [0 0 200 100]", "/BleedBox [0 0 200 100]"] {
let mut s = Session::new();
let r = open(&mut s, "text.ai", &ai::ai_with(unreadable, TEXT_ART, boxed), json!({})).unwrap();
// Then a note on the font, where the machine lacks it.
assert_eq!(r["warnings"][0], json!(vectorcraft_pdf::OFF_ARTBOARD_NOTE), "{boxed}: {r}");
assert_eq!(text_and_others(&s), (1, 0), "{boxed}");
// The PDF part alone, as asked.
open(&mut s, "text.ai", &ai::ai_with(unreadable, TEXT_ART, boxed), json!({"editingData": false})).unwrap();
assert_eq!(text_and_others(&s), (1, 0), "{boxed}");
}
// The same page without a box is the notice: the file still says why it can't open.
let mut s = Session::new();
let e = open(&mut s, "text.ai", &ai::ai(unreadable, TEXT_ART), json!({})).unwrap_err().to_string();
assert!(e.contains("without PDF compatibility") && e.contains("can't be read"), "{e}");
let e = open(&mut s, "text.ai", &ai::ai(unreadable, TEXT_ART), json!({"editingData": false})).unwrap_err().to_string();
assert!(e.contains("placeholder"), "{e}");
// Placed, it is read as its page too.
let r = s.execute("file.new", &json!({})).and_then(|_| {
let data = vectorcraft_format::base64_encode(&ai::ai_with(unreadable, TEXT_ART, "/ArtBox [0 0 200 100]"));
s.execute("file.place", &json!({"name": "text.ai", "dataBase64": data, "link": false}))
});
assert!(r.is_ok(), "{r:?}");
}
#[test]
fn an_ai_file_whose_pdf_part_is_only_text_still_opens_from_editing_data_that_reads() {
let mut s = Session::new();
open(&mut s, "art.ai", &ai::ai_with(&compressed(&ai::sample_data()), TEXT_ART, "/ArtBox [0 0 200 100]"), json!({})).unwrap();
check_sample(&s);
}
#[test]
fn editing_data_false_opens_only_what_the_page_prints() {
let mut s = Session::new();