mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
This commit is contained in:
@@ -975,12 +975,26 @@ fn register_fields(dst: &mut Document, fields: &[ObjRef]) -> Result<(), Organize
|
||||
|
||||
/// A new document containing copies of `pages` from `src` (Extract Pages / Split).
|
||||
/// Document information (title, author…) is carried over, and so is the print standard the
|
||||
/// source declares (PDF/X: its output intents and identification, #263).
|
||||
/// source declares (PDF/X: its output intents and identification, #263), and the bookmarks that
|
||||
/// go to the copied pages.
|
||||
pub fn extract_pages(src: &Document, pages: &[usize]) -> Result<Document, OrganizeError> {
|
||||
let mut out = Document::new_empty();
|
||||
// One source: nothing to deduplicate. Its fields keep their names (a source with two
|
||||
// same-named field roots stays one field, as it was), so Extract and Split never rename.
|
||||
let imported = import_pages_mapped(&mut out, src, pages, 0, FieldConflicts::Merge)?;
|
||||
// The bookmarks of these pages come along, mapped to the copies (#4020).
|
||||
let outline = kept_bookmarks(source_outline(src, imported.page_map));
|
||||
if !outline.is_empty() {
|
||||
let root = crate::outline::container(&mut out, &[])?;
|
||||
if let Some((first, last, _)) = write_outline_level(&mut out, root, &outline)? {
|
||||
out.update_dict(root, |d| {
|
||||
d.set(b"First".to_vec(), Object::Ref(first));
|
||||
d.set(b"Last".to_vec(), Object::Ref(last));
|
||||
})?;
|
||||
let top = crate::outline::children(&out, root);
|
||||
crate::outline::relink_and_recount(&mut out, root, &top)?;
|
||||
}
|
||||
}
|
||||
for key in crate::INFO_KEYS {
|
||||
if let Some(v) = crate::info(src, key) {
|
||||
crate::set_info(&mut out, key, &v)?;
|
||||
@@ -1352,6 +1366,18 @@ fn source_outline(src: &Document, pages: HashMap<ObjRef, ObjRef>) -> Vec<Outline
|
||||
outline_level(&mapper, first, 0, &mut budget)
|
||||
}
|
||||
|
||||
/// `nodes` without the bookmarks whose page was not copied (no destination left), unless a bookmark
|
||||
/// under them stays. [`outline_level`] already bounds the depth.
|
||||
fn kept_bookmarks(nodes: Vec<OutlineNode>) -> Vec<OutlineNode> {
|
||||
nodes
|
||||
.into_iter()
|
||||
.filter_map(|mut n| {
|
||||
n.children = kept_bookmarks(n.children);
|
||||
(n.dest.is_some() || !n.children.is_empty()).then_some(n)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// One level of bookmarks from `first` (its siblings), each with its children. Cycle- and size-safe:
|
||||
/// at most `budget` bookmarks are read in all, and nesting stops at 32 levels.
|
||||
fn outline_level(m: &Copier<'_>, first: Option<ObjRef>, depth: u8, budget: &mut usize) -> Vec<OutlineNode> {
|
||||
|
||||
@@ -1082,6 +1082,28 @@ fn form_fields_come_along_and_are_registered() {
|
||||
assert_eq!(out.get(kid).as_dict().unwrap().reference(b"P"), Some(pages(&out).unwrap()[0].obj));
|
||||
}
|
||||
|
||||
/// #4020: Extract keeps the bookmarks that go to the pages it copies, mapped to the copies. One whose
|
||||
/// page was left out goes, unless a bookmark under it stays.
|
||||
#[test]
|
||||
fn extract_keeps_the_bookmarks_of_the_pages_it_copies() {
|
||||
let mut src = doc_a();
|
||||
crate::add_bookmark(&mut src, &[], 0, "One", 0).unwrap();
|
||||
crate::add_bookmark(&mut src, &[], 1, "Two", 1).unwrap();
|
||||
crate::add_bookmark(&mut src, &[1], 0, "Two.a", 2).unwrap();
|
||||
crate::add_bookmark(&mut src, &[], 2, "Three", 2).unwrap();
|
||||
let marks = |d: &Document| -> Vec<(String, usize, Option<usize>)> { bookmarks(d).into_iter().map(|b| (b.title, b.path.len(), b.page)).collect() };
|
||||
let s = |t: &str| t.to_string();
|
||||
|
||||
let all = full_roundtrip(&extract_pages(&src, &[0, 1, 2]).unwrap());
|
||||
assert_eq!(marks(&all), [(s("One"), 1, Some(0)), (s("Two"), 1, Some(1)), (s("Two.a"), 2, Some(2)), (s("Three"), 1, Some(2))]);
|
||||
|
||||
let last = full_roundtrip(&extract_pages(&src, &[2]).unwrap());
|
||||
assert_eq!(marks(&last), [(s("Two"), 1, None), (s("Two.a"), 2, Some(0)), (s("Three"), 1, Some(0))], "One's page was left out");
|
||||
|
||||
let none = full_roundtrip(&extract_pages(&doc_a(), &[0]).unwrap());
|
||||
assert!(!none.get(none.root().unwrap()).as_dict().unwrap().contains(b"Outlines"), "no bookmarks, no outline");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn popup_cycles_copy_once_and_structure_links_are_removed() {
|
||||
let out = full_roundtrip(&extract_pages(&doc_a(), &[2]).unwrap());
|
||||
|
||||
Reference in New Issue
Block a user