organize: deleting the opening page drops the catalog OpenAction (#821) (#855)

This commit is contained in:
MarekSpodBiedry authored and GitHub committed 2026-10-10 15:31:51 -04:00
1 parent eb75e5a9d9
commit 6fd053b918
2 files changed
+52 -1

No files matched your search

+13 -1
View File
@@ -195,7 +195,7 @@ pub fn delete_pages(doc: &mut Document, indices: &[usize]) -> Result<(), Organiz
}
/// Bookmarks, and links on the `keep` pages, that go to one of the `gone` pages lose that
/// destination (/Dest, or a GoTo /A), rather than pointing at nothing.
/// destination (/Dest, or a GoTo /A), rather than pointing at nothing; so does the catalog's /OpenAction.
fn drop_destinations_to(doc: &mut Document, gone: &[ObjRef], keep: &[(ObjRef, Dict)]) -> Result<(), OrganizeError> {
let mut holders = outline::items(doc);
for (p, _) in keep {
@@ -222,6 +222,18 @@ fn drop_destinations_to(doc: &mut Document, gone: &[ObjRef], keep: &[(ObjRef, Di
dead.push((h, b"A"));
}
}
// The catalog's /OpenAction is either a destination array or a GoTo action; one that opens a
// deleted page is dropped, as a bookmark's would be.
if let Some(root) = doc.root() {
let open = doc.get(root).as_dict().and_then(|c| c.get(b"OpenAction").map(|o| doc.resolve(o)));
let opens_gone = match open.as_deref() {
Some(Object::Dict(a)) => a.name(b"S") == Some(b"GoTo") && to_gone(a.get(b"D")),
other => to_gone(other),
};
if opens_gone {
dead.push((root, b"OpenAction"));
}
}
for (h, key) in dead {
doc.update_dict(h, |d| {
d.remove(key);
+39
View File
@@ -170,6 +170,45 @@ fn deleted_pages_are_not_kept_by_what_points_at_them() {
assert_eq!(form.as_dict().and_then(|f| f.get(b"Fields")).and_then(Object::as_array).map(Vec::len), Some(0), "the field went with its page");
}
#[test]
fn deleting_the_opening_page_drops_the_open_action() {
let open_action = |doc: &Document| doc.get(doc.root().unwrap()).as_dict().unwrap().get(b"OpenAction").cloned();
let set_open = |doc: &mut Document, value: Object| {
let root = doc.root().unwrap();
doc.update_dict(root, |c| c.set(b"OpenAction".to_vec(), value)).unwrap();
};
let page = |doc: &Document, i: usize| pages(doc).unwrap()[i].obj;
for as_action in [false, true] {
let target = |doc: &Document, i: usize| {
let dest = Object::Array(vec![Object::Ref(page(doc, i)), Object::Name(b"Fit".to_vec())]);
if as_action {
let mut a = Dict::new();
a.set(b"S".to_vec(), Object::Name(b"GoTo".to_vec()));
a.set(b"D".to_vec(), dest);
Object::Dict(a)
} else {
dest
}
};
// The opening page goes: no OpenAction is left to point at nothing.
let mut doc = doc_a();
let open = target(&doc, 0);
set_open(&mut doc, open);
delete_pages(&mut doc, &[0]).unwrap();
let out = full_roundtrip(&doc);
assert_eq!(labels(&out), ["A2", "A3"]);
assert_eq!(open_action(&out), None);
// Another page goes: the opening page keeps its OpenAction.
let mut doc = doc_a();
let open = target(&doc, 0);
set_open(&mut doc, open);
delete_pages(&mut doc, &[2]).unwrap();
let out = full_roundtrip(&doc);
assert_eq!(labels(&out), ["A1", "A2"]);
assert!(open_action(&out).is_some());
}
}
#[test]
fn cannot_delete_every_page_or_missing_pages() {
let mut doc = open(fixture());