Preserve action chains when discarding external references

This commit is contained in:
dotCooCoo committed 2026-11-02 20:15:58 -08:00
1 parent c3749e6a98
commit 3ef82b55eb
2 files changed
+47 -5

No files matched your search

+8 -5
View File
@@ -219,7 +219,7 @@ impl Walk {
match rest {
Some(Kept::Now(v)) => {
let mut d = d.clone();
set_next(&mut d, v);
set_next(doc, &mut d, v);
Kept::Now(vec![Object::Dict(d)])
}
_ => Kept::Same,
@@ -230,13 +230,16 @@ impl Walk {
}
}
fn set_next(d: &mut Dict, mut v: Vec<Object>) {
fn set_next(doc: &mut Document, d: &mut Dict, mut v: Vec<Object>) {
match v.len() {
0 => {
d.remove(b"Next");
}
1 => d.set(b"Next".to_vec(), v.remove(0)),
_ => d.set(b"Next".to_vec(), Object::Array(v)),
_ => {
let next = v.into_iter().map(|a| if matches!(a, Object::Dict(_) | Object::Array(_)) { Object::Ref(doc.add(a)) } else { a }).collect();
d.set(b"Next".to_vec(), Object::Array(next));
}
}
}
@@ -278,8 +281,8 @@ fn strip_action(doc: &mut Document, a: &Object, owner: ObjRef) -> Result<Option<
let Some(mut d) = doc.resolve(&first).as_dict().cloned() else { return Ok(Some(None)) };
let mut next = d.get(b"Next").map(|n| listed(doc, n)).unwrap_or_default();
next.extend(v);
set_next(&mut d, next);
Ok(Some(Some(Object::Dict(d))))
set_next(doc, &mut d, next);
Ok(Some(Some(Object::Ref(doc.add(Object::Dict(d))))))
}
/// The outline walk: bookmarks stored as their own objects wait on `stack`; those stored inside
+39
View File
@@ -2569,6 +2569,20 @@ fn links_and_bookmarks_to_other_documents_are_discarded_and_links_within_stay()
assert_eq!(chain(&doc, bookmark(&doc, items[1]).get(b"A").unwrap()), ["Named"]);
}
#[test]
fn discarding_a_remote_action_keeps_the_links_local_destination() {
let (mut doc, p, _) =
linked_doc(|_, p| vec![link_with(Some(action("GoToR", None)), Some(Object::Array(vec![Object::Ref(p), Object::name("Fit")])))], &[]);
let report = optimize(&mut doc, &Settings { discard_external_refs: true, ..Settings::default() }).unwrap();
assert_eq!(report.external_links, 1);
let bytes = write_full(&doc, &SaveOptions::default()).unwrap();
let reopened = Document::open(Arc::new(bytes)).unwrap();
let links = annots_of(&reopened, p);
assert_eq!(links.len(), 1);
assert!(links[0].get(b"A").is_none());
assert_eq!(links[0].get(b"Dest"), Some(&Object::Array(vec![Object::Ref(p), Object::name("Fit")])));
}
#[test]
fn repeated_indirect_remote_link_is_removed_from_one_page() {
let (mut doc, p, _) = linked_doc(
@@ -2709,6 +2723,31 @@ fn a_long_chain_of_indirect_actions_stays_indirect_and_reads_back_after_saving()
assert_eq!(chain(&reopened, &a), vec!["URI"; 130], "every web link reads back");
}
#[test]
fn promoted_action_chains_read_back_after_saving() {
let (doc, p, _) = linked_doc(
|doc, _| {
let tail = (0..126).fold(action("Named", None), |next, _| action("Named", Some(next)));
let first = doc.add(tail);
let next = Object::Array(vec![Object::Ref(first), action("URI", None)]);
let link = doc.add(link_with(Some(action("GoToR", Some(next))), None));
vec![Object::Ref(link)]
},
&[],
);
let bytes = write_full(&doc, &SaveOptions::default()).unwrap();
let mut doc = Document::open(Arc::new(bytes)).unwrap();
let original = annots_of(&doc, p)[0].get(b"A").cloned().unwrap();
assert_eq!(chain(&doc, &original).len(), 129);
let report = optimize(&mut doc, &Settings { discard_external_refs: true, ..Settings::default() }).unwrap();
assert_eq!(report.external_links, 1);
let bytes = write_full(&doc, &SaveOptions::default()).unwrap();
let reopened = Document::open(Arc::new(bytes)).unwrap();
let a = annots_of(&reopened, p)[0].get(b"A").cloned().unwrap();
let expected: Vec<_> = std::iter::repeat_n("Named".to_string(), 127).chain(["URI".to_string()]).collect();
assert_eq!(chain(&reopened, &a), expected);
}
#[test]
fn shared_actions_that_multiply_stop_the_optimization_when_one_opens_another_document() {
use pdfcraft_optimize::OptimizeError;