Skip to content
Open
Show file tree
Hide file tree
Changes from all 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/documents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,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),
("object_streams", &mut settings.object_streams),
("lzw_to_flate", &mut settings.lzw_to_flate),
Expand Down Expand Up @@ -416,6 +417,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,
"lzw_streams": o.lzw_streams,
"invalid_links": o.invalid_links,
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 @@ -1517,7 +1517,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. compatibility: retain (default; keeps the document's version, or 1.5 when object streams are written), 1.4, 1.5, 1.6, 1.7 or 2.0. 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). mono: { downsample, ppi, above_ppi, compression: ccitt_g4|jbig2|retain } for black-and-white images and image masks (default: averaged down to 300 ppi above 450 ppi, counted in images_resampled; stored losslessly as CCITT Group 4 or JBIG2 (or 1-bit Flate, once resampled or packed from more bits) when that is smaller, up to 16 JBIG2 images sharing one symbol dictionary (/JBIG2Globals) where that is smaller still (image masks and DeviceGray images named directly, of at most 64 Mi pixels, which redaction can store again without it), Group 3 fax images, JBIG2 images, black-and-white RGB and indexed images using only palette entries 0 and 1 among them, counted in images_recompressed; retain resamples only, a CCITT image staying CCITT, a JBIG2 image JBIG2 and any other becoming Flate). Images past what one run may decode stay as they are, counted in images_refused; streams it only stores with Flate past that stay LZW, uncounted. 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); object_streams (default true; always off for 1.4); lzw_to_flate (true) decodes LZW streams and stores them with Flate instead, leaving any it cannot decode unchanged (lzw_streams counts the streams converted); 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. compatibility: retain (default; keeps the document's version, or 1.5 when object streams are written), 1.4, 1.5, 1.6, 1.7 or 2.0. 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). mono: { downsample, ppi, above_ppi, compression: ccitt_g4|jbig2|retain } for black-and-white images and image masks (default: averaged down to 300 ppi above 450 ppi, counted in images_resampled; stored losslessly as CCITT Group 4 or JBIG2 (or 1-bit Flate, once resampled or packed from more bits) when that is smaller, up to 16 JBIG2 images sharing one symbol dictionary (/JBIG2Globals) where that is smaller still (image masks and DeviceGray images named directly, of at most 64 Mi pixels, which redaction can store again without it), Group 3 fax images, JBIG2 images, black-and-white RGB and indexed images using only palette entries 0 and 1 among them, counted in images_recompressed; retain resamples only, a CCITT image staying CCITT, a JBIG2 image JBIG2 and any other becoming Flate). Images past what one run may decode stay as they are, counted in images_refused; streams it only stores with Flate past that stay LZW, uncounted. 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); object_streams (default true; always off for 1.4); lzw_to_flate (true) decodes LZW streams and stores them with Flate instead, leaving any it cannot decode unchanged (lzw_streams counts the streams converted); 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 @@ -1533,6 +1533,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" },
"object_streams": { "type": "boolean" },
"lzw_to_flate": { "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 @@ -6771,6 +6771,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}");
}

/// #3227 review: retaining the version, a catalog `/Version` below the 1.5 an object stream
/// needs is not what viewers read: the header is raised, and the later of the two is reported.
#[test]
Expand Down
5 changes: 4 additions & 1 deletion crates/optimize/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ let report = optimize_with_progress(&mut doc, &settings, &mut |stage| keep_going
1,000 scanned pages' worth, plus more for each byte of the file, enough to decode a stream
about 8 times at Flate's best. Images past it stay as they are, and `Report::images_refused`
counts those the image step read. Streams it only stores with Flate (below) aren't counted.
- **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 keep their ASCII layer. LZW-compressed
Expand Down
Loading
Loading