mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 11:17:10 -04:00
This commit is contained in:
1 parent
6a948fcc22
commit
da9cefa33c
2 files changed
+90
-1
No files matched your search
@@ -300,9 +300,10 @@ fn import_pages_mapped(
|
||||
ocgs: Vec::new(),
|
||||
});
|
||||
let src_dict = src.get(page).as_dict().cloned().unwrap_or_default();
|
||||
let resources = resources_with_ancestors(src, &src_dict);
|
||||
let mut d = Dict::new();
|
||||
for (k, v) in src_dict.iter() {
|
||||
if SKIP_ON_PAGE.contains(&k.as_slice()) {
|
||||
if SKIP_ON_PAGE.contains(&k.as_slice()) || (k.as_slice() == b"Resources" && resources.is_some()) {
|
||||
continue;
|
||||
}
|
||||
if k.as_slice() == b"Annots" {
|
||||
@@ -364,6 +365,10 @@ fn import_pages_mapped(
|
||||
}
|
||||
d.set(k.clone(), copier.copy_value(dst, v, None));
|
||||
}
|
||||
if let Some(r) = resources {
|
||||
let r = copier.copy_value(dst, &r, None);
|
||||
d.set(b"Resources".to_vec(), r);
|
||||
}
|
||||
for k in INHERITABLE {
|
||||
if !d.contains(k)
|
||||
&& let Some(v) = inherited.get(k)
|
||||
@@ -398,6 +403,56 @@ fn import_pages_mapped(
|
||||
Ok(Imported { pages: new_pages, page_map: copier.pages, objects: copier.map, renamed_fields })
|
||||
}
|
||||
|
||||
/// A page's `/Resources` with the names that only its page-tree ancestors list, or `None` when
|
||||
/// they add nothing to the dictionary it would have anyway (its own, or else its nearest
|
||||
/// ancestor's). Readers (ours included) look a resource name up the page and then up the tree,
|
||||
/// so a page that lists its font but inherits its images still shows them. A copy hangs from
|
||||
/// another tree, so it must list them itself or it draws nothing of them (#1628). The page's
|
||||
/// entry wins over an ancestor's, and a nearer ancestor's over a farther one's.
|
||||
fn resources_with_ancestors(src: &Document, page: &Dict) -> Option<Object> {
|
||||
let mut merged = page.get(b"Resources").map(|r| src.dict(r).unwrap_or_default());
|
||||
let mut added = false;
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
let mut parent = page.reference(b"Parent");
|
||||
// Bounded: a damaged tree's `/Parent` links can loop or run deep.
|
||||
while let Some(r) = parent
|
||||
&& seen.insert(r)
|
||||
&& seen.len() <= 256
|
||||
{
|
||||
let node = src.get(r);
|
||||
let Some(node) = node.as_dict() else { break };
|
||||
parent = node.reference(b"Parent");
|
||||
let Some(theirs) = node.get(b"Resources").and_then(|r| src.dict(r)) else { continue };
|
||||
let Some(mine) = merged.as_mut() else {
|
||||
merged = Some(theirs);
|
||||
continue;
|
||||
};
|
||||
for (category, entries) in theirs.iter() {
|
||||
match (mine.get(category).map(|c| src.dict(c)), src.dict(entries)) {
|
||||
(None, _) => {
|
||||
mine.set(category.clone(), entries.clone());
|
||||
added = true;
|
||||
}
|
||||
(Some(Some(mut names)), Some(entries)) => {
|
||||
let before = names.len();
|
||||
for (name, value) in entries.iter() {
|
||||
if !names.contains(name) {
|
||||
names.set(name.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
if names.len() > before {
|
||||
mine.set(category.clone(), Object::Dict(names));
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
// `/ProcSet` and the like: the page's own value stands.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
merged.filter(|_| added).map(Object::Dict)
|
||||
}
|
||||
|
||||
/// Look up a named destination in the source (`/Dests` dictionary or `/Names /Dests` tree).
|
||||
fn named_dest(src: &Document, name: &[u8]) -> Option<Object> {
|
||||
let catalog = src.get(src.root()?).as_dict().cloned()?;
|
||||
|
||||
@@ -476,6 +476,40 @@ fn inherited_attributes_travel_with_copied_pages() {
|
||||
assert_eq!(hay.pages().first().unwrap().render_dimensions(), (250.0, 500.0), "rotated 90°");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resources_split_between_page_and_tree_travel_together() {
|
||||
// The page names its own font but draws a form that only the page-tree node lists. Readers
|
||||
// (ours included) look a name up the whole chain, so the page shows; a copy that kept only
|
||||
// the page's own dictionary drew nothing (#1628).
|
||||
let b: Vec<String> = vec![
|
||||
"<< /Type /Catalog /Pages 2 0 R >>".into(),
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 200 200] /Resources << /XObject << /X0 5 0 R >> /Font << /F1 6 0 R /F2 6 0 R >> >> >>"
|
||||
.into(),
|
||||
"<< /Type /Page /Parent 2 0 R /Contents 4 0 R /Resources << /Font << /F2 7 0 R >> >> >>".into(),
|
||||
{
|
||||
let s = "q 100 0 0 100 50 50 cm /X0 Do Q BT /F2 12 Tf (x) Tj ET";
|
||||
format!("<< /Length {} >>\nstream\n{s}\nendstream", s.len())
|
||||
},
|
||||
{
|
||||
let s = "0 0 1 rg 0 0 1 1 re f";
|
||||
format!("<< /Type /XObject /Subtype /Form /BBox [0 0 1 1] /Length {} >>\nstream\n{s}\nendstream", s.len())
|
||||
},
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>".into(),
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Courier >>".into(),
|
||||
];
|
||||
let src = open(build(&b, "/Root 1 0 R"));
|
||||
let out = full_roundtrip(&combine(&[("S", &src), ("S again", &src)]).unwrap());
|
||||
for i in 0..2 {
|
||||
let res = out.dict(page_dict(&out, i).get(b"Resources").unwrap()).unwrap();
|
||||
let entry = |cat: &[u8], name: &[u8]| res.get(cat).and_then(|c| out.dict(c)).and_then(|d| d.get(name).map(|v| out.resolve(v)));
|
||||
let form = entry(b"XObject", b"X0").expect("the tree's form comes along");
|
||||
assert_eq!(form.as_dict().and_then(|d| d.name(b"Subtype")), Some(&b"Form"[..]));
|
||||
let font = |name: &[u8]| entry(b"Font", name).and_then(|f| f.as_dict().and_then(|d| d.name(b"BaseFont").map(<[u8]>::to_vec)));
|
||||
assert_eq!(font(b"F1").as_deref(), Some(&b"Helvetica"[..]), "a name only the tree has is added");
|
||||
assert_eq!(font(b"F2").as_deref(), Some(&b"Courier"[..]), "the page's own entry wins over the tree's");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn links_are_rewired_to_copied_pages_or_dropped() {
|
||||
let a = doc_a();
|
||||
|
||||
Reference in new issue
Block a user