Optimize: a removed external action is not replaced by a null in its /Next

When an action to another document was removed and its /Next list
started with a null or a reference to something other than an action,
that entry became the head of the chain, and the actions after it, such
as a web link, were dropped with it. Leading entries that are not
action dictionaries are now skipped. The Settings::discard_external_refs
doc comment lists every action type the option removes.
This commit is contained in:
dotCooCoo committed 2026-10-24 08:01:32 -07:00
1 parent f3e2701852
commit 8de27ff9ad
3 files changed
+31 -2

No files matched your search

+3
View File
@@ -265,6 +265,9 @@ fn strip_action(doc: &mut Document, a: &Object, owner: ObjRef) -> Result<Option<
if walk.removed == 0 && !walk.cut {
return Ok(None);
}
// A null or a reference to something other than an action cannot lead the chain.
let head = v.iter().position(|o| doc.resolve(o).as_dict().is_some()).unwrap_or(v.len());
v.drain(..head);
if v.is_empty() {
return Ok(Some(None));
}
+3 -2
View File
@@ -168,8 +168,9 @@ pub struct Settings {
pub remove_invalid_links: bool,
/// Clean Up: remove named destinations nothing refers to.
pub remove_unreferenced_dests: bool,
/// Discard User Data: take `GoToR`, `GoToE` and `Launch` actions out of links and bookmarks;
/// a link left with nothing to do is removed. Links within the document and web links stay.
/// Discard User Data: take `GoToR`, `GoToE`, `Launch` and `ImportData` actions, and `Thread`
/// actions with an `/F`, out of links and bookmarks; a link left with nothing to do is
/// removed. Links within the document and web links stay.
pub discard_external_refs: bool,
}
+25
View File
@@ -623,6 +623,31 @@ fn an_action_to_another_document_hands_its_place_to_the_actions_after_it() {
);
}
#[test]
fn entries_that_are_not_actions_do_not_take_the_place_of_a_removed_action() {
let (doc, p, items) = linked_doc(
|doc, _| {
// A null, then a reference to an empty object, before the web link.
let empty = doc.add(Object::Null);
let next = Object::Array(vec![Object::Null, Object::Ref(empty), action("URI", None), Object::Null]);
vec![link_with(Some(action("GoToR", Some(next))), None)]
},
&[
action("Launch", Some(Object::Array(vec![Object::Null, action("URI", None)]))),
action("GoToR", Some(Object::Array(vec![Object::Null, Object::Null]))),
],
);
let mut doc = doc;
let report = optimize(&mut doc, &Settings { discard_external_refs: true, ..Settings::default() }).unwrap();
assert_eq!((report.external_links, report.external_bookmarks), (1, 2), "{report:?}");
let links = annots_of(&doc, p);
assert_eq!(links.len(), 1, "the link keeps its web link, so it stays on the page");
assert_eq!(chain(&doc, links[0].get(b"A").unwrap()), ["URI"]);
let bookmark = |r: ObjRef| doc.get(r).as_dict().unwrap().clone();
assert_eq!(chain(&doc, bookmark(items[0]).get(b"A").unwrap()), ["URI"]);
assert!(bookmark(items[1]).get(b"A").is_none(), "a chain of nulls alone leaves no action");
}
#[test]
fn a_chain_deeper_than_the_limit_loses_its_tail_and_one_without_a_remote_action_stays() {
let deep = |n: usize, last: &str| (0..n).fold(action(last, None), |next, _| action("URI", Some(next)));