mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 02:58:32 -06:00
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the PrintCraft settings and crash-recovery folders to the new name, and both apps read settings saved under the old storage key, so upgrades keep recent files, preferences and unsaved work. The signing test fixtures keep their PrintCraft subject names (they are binary). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
136 lines
5.8 KiB
Rust
136 lines
5.8 KiB
Rust
//! Acrobat JavaScript in the shell (egui_kittest): the console, Document JavaScripts, a button
|
|
//! script and Preferences ▸ JavaScript.
|
|
|
|
use egui_kittest::Harness;
|
|
use egui_kittest::kittest::Queryable;
|
|
use pdfcraft_ui_egui::{Dialog, PdfCraftApp};
|
|
|
|
/// A form with a text field and a push button whose Mouse Up script fills it in.
|
|
fn form() -> Vec<u8> {
|
|
let objs = [
|
|
"<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [4 0 R 5 0 R] /DA (/Helv 0 Tf 0 g) /DR << /Font << /Helv 6 0 R >> >> >> >>",
|
|
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 300 300] >>",
|
|
"<< /Type /Page /Parent 2 0 R /Annots [4 0 R 5 0 R] >>",
|
|
"<< /Type /Annot /Subtype /Widget /FT /Tx /T (greeting) /Rect [20 250 280 270] /P 3 0 R /F 4 >>",
|
|
"<< /Type /Annot /Subtype /Widget /FT /Btn /Ff 65536 /T (hello) /Rect [20 200 120 230] /P 3 0 R /F 4 /A << /S /JavaScript /JS (getField\\('greeting'\\).value = 'Hello from JavaScript'; app.alert\\('Filled in'\\);) >> >>",
|
|
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>",
|
|
];
|
|
let mut out = b"%PDF-1.7\n".to_vec();
|
|
let mut offsets = Vec::new();
|
|
for (i, o) in objs.iter().enumerate() {
|
|
offsets.push(out.len());
|
|
out.extend_from_slice(format!("{} 0 obj\n{o}\nendobj\n", i + 1).as_bytes());
|
|
}
|
|
let xref = out.len();
|
|
out.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes());
|
|
for o in offsets {
|
|
out.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes());
|
|
}
|
|
out.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objs.len() + 1).as_bytes());
|
|
out
|
|
}
|
|
|
|
fn harness() -> Harness<'static, PdfCraftApp> {
|
|
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_cc| {
|
|
let mut app = PdfCraftApp::new();
|
|
app.open_bytes("form.pdf", None, form()).unwrap();
|
|
app
|
|
});
|
|
h.run_steps(4);
|
|
h
|
|
}
|
|
|
|
fn value(h: &Harness<'static, PdfCraftApp>, name: &str) -> Vec<String> {
|
|
let app = h.state();
|
|
let id = app.active_ids().unwrap().1;
|
|
app.session.get(id).unwrap().form.iter().find(|f| f.name == name).unwrap().value.clone()
|
|
}
|
|
|
|
#[test]
|
|
fn console_runs_scripts_against_the_document() {
|
|
let mut h = harness();
|
|
assert!(h.state_mut().execute("tools.js_console"));
|
|
h.run_steps(2);
|
|
h.get_by_label("JavaScript Console");
|
|
h.state_mut().js_console.input = "getField('greeting').value = 'typed'; console.println(numFields); 6 * 7".into();
|
|
h.get_by_label("Run").click();
|
|
h.run_steps(2);
|
|
assert_eq!(value(&h, "greeting"), ["typed"]);
|
|
let log = h.state().js_console.log.clone();
|
|
assert!(log.contains(&"2".to_string()) && log.contains(&"42".to_string()), "{log:?}");
|
|
h.state_mut().js_console.input = "throw new Error('boom')".into();
|
|
h.run_steps(4);
|
|
h.get_by_label("Run").click();
|
|
h.run_steps(2);
|
|
assert!(h.state().js_console.log.last().unwrap().contains("boom"));
|
|
h.run_steps(4);
|
|
h.get_by_label("Clear").click();
|
|
h.run_steps(2);
|
|
assert!(h.state().js_console.log.is_empty(), "{:?} {:?}", h.state().js_console.log, h.state().dialog);
|
|
}
|
|
|
|
#[test]
|
|
fn button_scripts_run_and_alert() {
|
|
let mut h = harness();
|
|
let id = h.state().active_ids().unwrap().1;
|
|
h.state_mut().run_button_script(id, "hello", "getField('greeting').value = 'Hello from JavaScript'; app.alert('Filled in');");
|
|
h.run_steps(2);
|
|
assert_eq!(value(&h, "greeting"), ["Hello from JavaScript"]);
|
|
assert_eq!(h.state().toast.clone().unwrap().0, "Filled in");
|
|
}
|
|
|
|
#[test]
|
|
fn document_scripts_and_preferences() {
|
|
let mut h = harness();
|
|
assert!(h.state_mut().execute("tools.document_js"));
|
|
h.run_steps(2);
|
|
h.get_by_label("Document JavaScripts");
|
|
h.state_mut().doc_js.name = "init".into();
|
|
h.state_mut().doc_js.script = "function hi() { return 'hi'; }".into();
|
|
h.get_by_label("Save").click();
|
|
h.run_steps(2);
|
|
let id = h.state().active_ids().unwrap().1;
|
|
assert_eq!(h.state().session.get(id).unwrap().document_scripts()[0].0, "init");
|
|
h.state_mut().dialog = Some(Dialog::Preferences);
|
|
h.run_steps(2);
|
|
h.get_by_label("Enable Acrobat JavaScript").click();
|
|
h.run_steps(2);
|
|
assert!(!h.state().session.javascript());
|
|
let saved = h.state().persist();
|
|
let mut fresh = PdfCraftApp::new();
|
|
fresh.restore(&saved);
|
|
assert!(!fresh.session.javascript(), "the preference is remembered");
|
|
}
|
|
|
|
#[test]
|
|
fn merge_data_files_into_a_spreadsheet() {
|
|
let dir = std::env::temp_dir().join(format!("pdfcraft-merge-ui-{}", std::process::id()));
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
let out = dir.join("report.csv");
|
|
let mut app = PdfCraftApp::new();
|
|
app.save_override = Some(out.to_string_lossy().into_owned());
|
|
app.merge_data_files(vec![("form.pdf".into(), form())]);
|
|
assert_eq!(std::fs::read_to_string(&out).unwrap(), "greeting\n\n");
|
|
assert!(app.toast.clone().unwrap().0.starts_with("Merged 1 file"));
|
|
app.merge_data_files(vec![("x.fdf".into(), b"junk".to_vec())]);
|
|
assert!(app.toast.clone().unwrap().0.starts_with("x.fdf:"));
|
|
}
|
|
|
|
#[test]
|
|
fn prepare_a_form_detects_fields_on_a_paper_form() {
|
|
let paper = pdfcraft_engine::Session::new().create_from_text("t", "Name: ____________________\n\nPhone: ____________________").unwrap().to_vec();
|
|
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| {
|
|
let mut app = PdfCraftApp::new();
|
|
app.open_bytes("paper.pdf", None, paper.clone()).unwrap();
|
|
app
|
|
});
|
|
h.run_steps(4);
|
|
assert!(h.state_mut().execute("form.prepare"));
|
|
h.run_steps(3);
|
|
let app = h.state();
|
|
let id = app.active_ids().unwrap().1;
|
|
let names: Vec<String> = app.session.get(id).unwrap().form.iter().map(|f| f.name.clone()).collect();
|
|
assert_eq!(names, ["Name", "Phone"]);
|
|
assert_eq!(app.toast.clone().unwrap().0, "Detected 2 form fields");
|
|
}
|