Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/automation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,7 @@ impl Automation {
("discard_alternate_images", &mut settings.discard_alternate_images),
("discard_tags", &mut settings.discard_tags),
("discard_print_settings", &mut settings.discard_print_settings),
("discard_form_actions", &mut settings.discard_form_actions),
("flate_unencoded", &mut settings.flate_unencoded),
("remove_invalid_links", &mut settings.remove_invalid_links),
("remove_unreferenced_dests", &mut settings.remove_unreferenced_dests),
Expand Down Expand Up @@ -1293,6 +1294,8 @@ impl Automation {
"thumbnails": o.thumbnails,
"alternate_images": o.alternate_images,
"tags_removed": o.tags_removed,
"print_settings": o.print_settings,
"form_actions": o.form_actions,
"streams_compressed": o.streams_compressed,
"invalid_links": o.invalid_links,
"invalid_bookmarks": o.invalid_bookmarks,
Expand Down
3 changes: 2 additions & 1 deletion crates/automation/src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1375,7 +1375,7 @@ pub fn tools() -> Vec<ToolDef> {
.with(schema(json!({ "doc": doc() }), &["doc"])),
t("doc_open_revision", "Open a revision", "Open saved revision `revision` (1 = the oldest) of a document as a new, unsaved document, to see the file as it was then.")
.with(schema(json!({ "doc": doc(), "revision": { "type": "integer", "minimum": 1 } }), &["doc", "revision"])),
t("doc_optimize", "Optimize PDF", "Write an optimized copy to `path` (Acrobat's PDF Optimizer). preset: standard (default; Reduce File Size's choices) or mobile (images to 96 ppi above 144, JPEG high quality); the options below change it. merge_fragments (default true) first puts a page picture drawn as many stacked image strips (as some PDF printers write it) back together into one image, so it can be resampled (merged_images, fragments_merged). color / gray: { downsample, ppi, above_ppi, compression: jpeg|zip|retain, quality 1–100 } (defaults: downsample to 150 ppi above 225, JPEG 60). Images are measured where pages draw them and replaced only when smaller. discard_*: thumbnails (default true), alternate_images (true), tags, print_settings; flate_unencoded (true); remove_invalid_links and remove_unreferenced_dests (true); images and forms the pages list but never draw are always dropped (unused_xobjects). discard: Remove Hidden Information categories (metadata, attachments, comments, form-fields, hidden-text, hidden-layers, bookmarks, links-actions-scripts, private-data). Signed documents are refused. The open document is unchanged.")
t("doc_optimize", "Optimize PDF", "Write an optimized copy to `path` (Acrobat's PDF Optimizer). preset: standard (default; Reduce File Size's choices) or mobile (images to 96 ppi above 144, JPEG high quality); the options below change it. merge_fragments (default true) first puts a page picture drawn as many stacked image strips (as some PDF printers write it) back together into one image, so it can be resampled (merged_images, fragments_merged). color / gray: { downsample, ppi, above_ppi, compression: jpeg|zip|retain, quality 1–100 } (defaults: downsample to 150 ppi above 225, JPEG 60). Images are measured where pages draw them and replaced only when smaller. discard_*: thumbnails (default true), alternate_images (true), tags, print_settings (entries removed: print_settings), form_actions (submit, import and reset actions removed: form_actions; the fields and links stay, and the actions chained after one still run); flate_unencoded (true); remove_invalid_links and remove_unreferenced_dests (true); images and forms the pages list but never draw are always dropped (unused_xobjects). discard: Remove Hidden Information categories (metadata, attachments, comments, form-fields, hidden-text, hidden-layers, bookmarks, links-actions-scripts, private-data). Signed documents are refused. The open document is unchanged.")
.cmd("optimize.advanced")
.with(schema(
json!({
Expand All @@ -1389,6 +1389,7 @@ pub fn tools() -> Vec<ToolDef> {
"discard_alternate_images": { "type": "boolean" },
"discard_tags": { "type": "boolean" },
"discard_print_settings": { "type": "boolean" },
"discard_form_actions": { "type": "boolean" },
"flate_unencoded": { "type": "boolean" },
"remove_invalid_links": { "type": "boolean" },
"remove_unreferenced_dests": { "type": "boolean" },
Expand Down
26 changes: 26 additions & 0 deletions crates/automation/tests/it/automation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5725,6 +5725,32 @@ fn optimizing_through_tools() {
assert!(matches!(a.call("doc_optimize", &json!({ "doc": doc, "path": "x.pdf", "discard": ["everything"] })), Err(ToolError::InvalidArgs(_))));
}

#[test]
fn optimizing_discards_form_actions_through_tools() {
let dir = workdir("optimize-form-actions");
let mut a = auto(&dir);
let doc = ok(&mut a, "doc_open", json!({ "path": "a.pdf" }))["doc"].as_u64().unwrap();
ok(&mut a, "form_add_field", json!({ "doc": doc, "page": 1, "type": "button", "rect": [20, 20, 120, 42], "name": "Send" }));
ok(
&mut a,
"form_set_actions",
json!({ "doc": doc, "field": "Send", "actions": [
{ "trigger": "mouse_up", "submit": "https://example.com/submit" },
{ "trigger": "mouse_down", "reset": [] },
{ "trigger": "mouse_enter", "javascript": "app.alert(1);" }
] }),
);
let kept = ok(&mut a, "doc_optimize", json!({ "doc": doc, "path": "kept.pdf" }));
assert_eq!(kept["form_actions"], 0, "{kept}");
let r = ok(&mut a, "doc_optimize", json!({ "doc": doc, "path": "small.pdf", "discard_form_actions": true }));
assert_eq!(r["form_actions"], 2, "{r}");
let small = ok(&mut a, "doc_open", json!({ "path": "small.pdf" }))["doc"].as_u64().unwrap();
let fields = ok(&mut a, "form_fields", json!({ "doc": small }));
assert!(fields.to_string().contains("Send"), "the button stays: {fields}");
let actions = ok(&mut a, "form_actions", json!({ "doc": small, "field": "Send" }));
assert_eq!(actions["actions"], json!([{ "trigger": "mouse_enter", "javascript": "app.alert(1);" }]), "{actions}");
}

/// A one-page PDF whose page picture is drawn as `rows` grey images one pixel row high, 600 px
/// wide on a 72 pt square (600 ppi), as some PDF printers write it.
fn printed_in_strips(rows: usize) -> Vec<u8> {
Expand Down
5 changes: 4 additions & 1 deletion crates/optimize/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ let report = optimize_with_progress(&mut doc, &settings, &mut |stage| keep_going
stored as JPEG (Acrobat's quality steps) or ZIP; soft masks are resampled with their image.
An image is replaced only if the result is smaller. Left alone: CMYK and other colour spaces,
image masks, decode arrays, more than 8 bits, JPEG 2000, JBIG2 and CCITT.
- **Discard objects:** thumbnails, alternate images, document tags, print settings.
- **Discard objects:** thumbnails, alternate images, document tags, print settings, and form
submission, import and reset actions (`/SubmitForm`, `/ImportData`, `/ResetForm`, off by
default). The fields, links, bookmarks and pages that ran a removed action keep their other
actions, and the actions chained after it through `/Next` still run in order.
- **Clean up:** Flate for streams with no filter; for streams with an outer ASCII85 or ASCIIHex
layer, that layer is removed when the result is smaller (the inner filters keep their encoded
bytes). Streams with `/DecodeParms`, images and object streams are left alone.
Expand Down
Loading