mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-14 06:42:20 +00:00
fix(optimize): a destination name given by reference is copied once however many dictionaries name it (#7035)
* fix(optimize): a destination name given by reference is copied once however many dictionaries name it * fix(optimize): aliases of one destination-name object are copied once
This commit is contained in:
1 parent
0c15e3de71
commit
b0607f5cd3
2 files changed
+50
-2
No files matched your search
@@ -2,6 +2,7 @@
|
||||
//! refers to.
|
||||
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::sync::Arc;
|
||||
|
||||
use pdfcraft_cos::{Dict, Document, ObjRef, Object, PdfString};
|
||||
|
||||
@@ -164,6 +165,8 @@ pub(crate) fn remove_invalid(doc: &mut Document, pages: &[ObjRef]) -> Result<(us
|
||||
fn referenced_names(doc: &Document) -> HashSet<Vec<u8>> {
|
||||
let mut out = HashSet::new();
|
||||
let mut seen = HashSet::new();
|
||||
let mut named_refs = HashSet::new();
|
||||
let mut named_held: std::collections::HashMap<*const Object, Arc<Object>> = std::collections::HashMap::new();
|
||||
let mut stack = vec![Object::Dict(doc.trailer().clone())];
|
||||
if let Some(r) = doc.root() {
|
||||
stack.push(Object::Ref(r));
|
||||
@@ -180,11 +183,26 @@ fn referenced_names(doc: &Document) -> HashSet<Vec<u8>> {
|
||||
// string, and an unresolved ref has no name, so the destination it uses would
|
||||
// look unreferenced and be removed. A GoToR or GoToE /D names a destination in
|
||||
// another document, so it does not count as a use of this one's.
|
||||
if let Some(n) = d.get(b"Dest").and_then(|x| dest_name(doc.resolve(x).as_ref())) {
|
||||
// A name given by reference is copied once, however many dictionaries (or aliases
|
||||
// of the object) name it. The objects are kept so an address is never reused.
|
||||
let mut named = |x: Option<&Object>| {
|
||||
let x = x?;
|
||||
if let Object::Ref(r) = x
|
||||
&& !named_refs.insert(*r)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let held = doc.resolve(x);
|
||||
if matches!(x, Object::Ref(_)) && named_held.insert(Arc::as_ptr(&held), held.clone()).is_some() {
|
||||
return None;
|
||||
}
|
||||
dest_name(held.as_ref())
|
||||
};
|
||||
if let Some(n) = named(d.get(b"Dest")) {
|
||||
out.insert(n);
|
||||
}
|
||||
if d.name(b"S") == Some(b"GoTo")
|
||||
&& let Some(n) = d.get(b"D").and_then(|x| dest_name(doc.resolve(x).as_ref()))
|
||||
&& let Some(n) = named(d.get(b"D"))
|
||||
{
|
||||
out.insert(n);
|
||||
}
|
||||
|
||||
@@ -4439,6 +4439,36 @@ fn indirectly_referenced_destination_is_kept() {
|
||||
assert!(!left.contains(&b"unused".to_vec()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_links_naming_one_destination_string_by_reference_keep_it() {
|
||||
// The name string is copied once however many links name it by reference; it is still a use.
|
||||
let mut doc = Document::new_empty();
|
||||
let p = page(&mut doc, &[], "BT ET");
|
||||
let s = |t: &str| Object::String(pdfcraft_cos::PdfString::literal(t.as_bytes().to_vec()));
|
||||
let name_ref = Object::Ref(doc.add(s("target")));
|
||||
let links: Vec<Object> = (0..50)
|
||||
.map(|_| {
|
||||
let mut link = Dict::new();
|
||||
link.set(b"Subtype".to_vec(), Object::name("Link"));
|
||||
link.set(b"Rect".to_vec(), Object::Array(vec![0.into(), 0.into(), 10.into(), 10.into()]));
|
||||
link.set(b"Dest".to_vec(), name_ref.clone());
|
||||
Object::Ref(doc.add(Object::Dict(link)))
|
||||
})
|
||||
.collect();
|
||||
doc.update_dict(p, |d| d.set(b"Annots".to_vec(), Object::Array(links))).unwrap();
|
||||
let fit = Object::Array(vec![Object::Ref(p), Object::name("Fit")]);
|
||||
let mut tree = Dict::new();
|
||||
tree.set(b"Names".to_vec(), Object::Array(vec![s("target"), fit.clone(), s("unused"), fit]));
|
||||
let tree = doc.add(Object::Dict(tree));
|
||||
let mut names = Dict::new();
|
||||
names.set(b"Dests".to_vec(), Object::Ref(tree));
|
||||
let root = doc.root().unwrap();
|
||||
doc.update_dict(root, |c| c.set(b"Names".to_vec(), Object::Dict(names))).unwrap();
|
||||
let report = optimize(&mut doc, &Settings::default()).unwrap();
|
||||
assert_eq!(report.unreferenced_dests, 1);
|
||||
assert_eq!(named_dest_keys(&doc), vec![b"target".to_vec()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_destination_named_only_by_a_remote_or_embedded_go_to_is_removed() {
|
||||
// The /D of a GoToR or GoToE action names a destination in another document. A local
|
||||
|
||||
Reference in new issue
Block a user