diff --git a/crates/automation/src/redact.rs b/crates/automation/src/redact.rs index e662bcac19..8ffb5a0075 100644 --- a/crates/automation/src/redact.rs +++ b/crates/automation/src/redact.rs @@ -212,6 +212,12 @@ impl Automation { if which.is_empty() { return Err(ToolError::InvalidArgs("categories is empty".into())); } + // The scan counts what removal takes, an AcroForm with no fields as 1, so none + // means the removal would refuse, with an error about redaction marks. + let info = self.doc(a)?.hidden_info(); + if which.iter().all(|h| info.iter().find(|c| c.0 == *h).is_none_or(|c| c.1 == 0)) { + return Err(failed("nothing to remove: the listed categories hold nothing (see doc_hidden_info)")); + } Edit::RemoveHidden { which } } }; diff --git a/crates/automation/src/tools.rs b/crates/automation/src/tools.rs index 866138899d..924aae34c1 100644 --- a/crates/automation/src/tools.rs +++ b/crates/automation/src/tools.rs @@ -562,7 +562,7 @@ pub fn tools() -> Vec { t( "doc_remove_hidden", "Remove hidden information / Sanitize", - "Remove the listed categories (see doc_hidden_info), or every category when none are listed (Sanitize Document). Form fields are flattened so their values stay visible. The next save rewrites the whole file. Undoable until saved.", + "Remove the listed categories (see doc_hidden_info), or every category when none are listed (Sanitize Document). Fails when the listed categories hold nothing. Form fields are flattened so their values stay visible. The next save rewrites the whole file. Undoable until saved.", ) .destructive() .with(schema( diff --git a/crates/automation/tests/it/automation.rs b/crates/automation/tests/it/automation.rs index 0d73dbb900..3ba6bbb1bb 100644 --- a/crates/automation/tests/it/automation.rs +++ b/crates/automation/tests/it/automation.rs @@ -4670,6 +4670,11 @@ fn removing_hidden_information_through_tools() { assert_eq!(r["undo"], "Remove hidden information"); assert_eq!(ok(&mut a, "comment_list", json!({ "doc": doc }))["count"], 0); assert!(count(&ok(&mut a, "doc_hidden_info", json!({ "doc": doc })), "metadata") >= 1, "only comments went"); + // With none left, removing them again says so, and Optimize discarding them still runs. + let again = a.call("doc_remove_hidden", &json!({ "doc": doc, "categories": ["comments"] })); + assert!(matches!(&again, Err(ToolError::Failed(m)) if m.starts_with("nothing to remove")), "{again:?}"); + let optimized = ok(&mut a, "doc_optimize", json!({ "doc": doc, "path": "optimized.pdf", "discard": ["comments", "bookmarks"] })); + assert_eq!(optimized["discarded"], json!([]), "{optimized}"); let r = ok(&mut a, "doc_remove_hidden", json!({ "doc": doc })); assert_eq!(r["undo"], "Sanitize document"); ok(&mut a, "doc_save", json!({ "doc": doc, "path": "clean.pdf" }));