mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-10 04:51:37 +00:00
fix(xfdf): FDF comment export excludes source pages by identity, not by /Type (#6036)
This commit is contained in:
1 parent
ce1cd80477
commit
3a1a2272fe
2 files changed
+51
-2
No files matched your search
+14
-2
@@ -42,13 +42,23 @@ struct FdfCopy<'a> {
|
||||
objects: Vec<Object>,
|
||||
numbers: HashMap<ObjRef, u32>,
|
||||
pending: Vec<(ObjRef, usize)>,
|
||||
/// The leaf pages of the source document, by identity: whatever their `/Type` says (or does not).
|
||||
source_pages: HashSet<ObjRef>,
|
||||
nodes: usize,
|
||||
bytes: usize,
|
||||
}
|
||||
|
||||
impl<'a> FdfCopy<'a> {
|
||||
fn new(doc: &'a Document) -> Self {
|
||||
Self { doc, objects: Vec::new(), numbers: HashMap::new(), pending: Vec::new(), nodes: MAX_FDF_OBJECTS, bytes: MAX_FDF_BYTES }
|
||||
Self {
|
||||
doc,
|
||||
objects: Vec::new(),
|
||||
numbers: HashMap::new(),
|
||||
pending: Vec::new(),
|
||||
source_pages: HashSet::new(),
|
||||
nodes: MAX_FDF_OBJECTS,
|
||||
bytes: MAX_FDF_BYTES,
|
||||
}
|
||||
}
|
||||
|
||||
fn charge(&mut self, bytes: usize) -> Result<(), DataError> {
|
||||
@@ -181,7 +191,7 @@ impl<'a> FdfCopy<'a> {
|
||||
while let Some((source, index)) = self.pending.get(cursor).copied() {
|
||||
// ISO 32000-2 §7.3.10: an absent or stale reference denotes null. Keep reader
|
||||
// errors distinct so damaged objects cannot be silently lost during export.
|
||||
let value = if doc.generation(source.num) != source.generation {
|
||||
let value = if doc.generation(source.num) != source.generation || self.source_pages.contains(&source) {
|
||||
std::sync::Arc::new(Object::Null)
|
||||
} else {
|
||||
match doc.try_get(source.num) {
|
||||
@@ -250,6 +260,8 @@ pub fn export_fdf(doc: &Document, comments: bool, fields: bool, file: &str) -> R
|
||||
// Keep the page/array owners once. Handles retain positions, never annotation
|
||||
// bodies: the document cache can evict and reparse even a repeated source reference.
|
||||
let pages = pdfcraft_model::pages(doc);
|
||||
// Page discovery accepts a leaf with no /Type: the export keeps these out by reference too.
|
||||
copy.source_pages = pages.iter().map(|p| p.obj).collect();
|
||||
let mut arrays = Vec::new();
|
||||
let mut picked: Vec<(Option<ObjRef>, usize, usize)> = Vec::new();
|
||||
for (pi, page) in pages.iter().enumerate() {
|
||||
|
||||
@@ -1706,6 +1706,43 @@ fn xml_form_import_preserves_text_around_comments_and_instructions() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Page discovery accepts a leaf without /Type, so a comment's nested reference to one must not
|
||||
/// carry the page or its content into the export.
|
||||
#[test]
|
||||
fn fdf_export_nulls_a_nested_link_to_a_page_without_a_type() {
|
||||
let source = {
|
||||
let mut doc = Document::new_empty();
|
||||
let root = doc.root().unwrap();
|
||||
let tree = doc.get(root).as_dict().unwrap().reference(b"Pages").unwrap();
|
||||
let contents = doc.add(pdfcraft_cos::Stream::from_raw(Dict::new(), b"% private-page-content\n".repeat(4_096)));
|
||||
let mut page = Dict::new();
|
||||
page.set(b"Parent".to_vec(), Object::Ref(tree));
|
||||
page.set(b"MediaBox".to_vec(), Object::Array(vec![0.into(), 0.into(), 600.into(), 800.into()]));
|
||||
page.set(b"Contents".to_vec(), Object::Ref(contents));
|
||||
let page = doc.add(page);
|
||||
doc.update_dict(tree, |d| {
|
||||
d.set(b"Type".to_vec(), Object::name("Pages"));
|
||||
d.set(b"Count".to_vec(), Object::Int(1));
|
||||
d.set(b"Kids".to_vec(), Object::Array(vec![Object::Ref(page)]));
|
||||
})
|
||||
.unwrap();
|
||||
let mut target = Dict::new();
|
||||
target.set(b"Label".to_vec(), PdfString::text("kept"));
|
||||
target.set(b"Target".to_vec(), Object::Ref(page));
|
||||
fdf_note_with_data(&mut doc, Object::Array(vec![Object::Dict(target)]));
|
||||
write_full(&doc, &SaveOptions::default()).unwrap()
|
||||
};
|
||||
let source = Document::open(Arc::new(source)).unwrap();
|
||||
assert_eq!(pdfcraft_model::pages(&source).len(), 1);
|
||||
let bytes = export_fdf(&source, true, false, "source.pdf").unwrap();
|
||||
assert!(bytes.len() < 4_096, "the untyped page's content must stay in the PDF ({} bytes)", bytes.len());
|
||||
let fdf = Document::open(Arc::new(bytes)).unwrap();
|
||||
let data = exported_note_data(&fdf);
|
||||
let target = data.as_array().unwrap()[0].as_dict().unwrap();
|
||||
assert_eq!(target.get(b"Label").unwrap().as_string().unwrap().to_text(), "kept");
|
||||
assert_eq!(*fdf.resolve(target.get(b"Target").unwrap()), Object::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fdf_export_nulls_nested_page_links_without_copying_the_source_page_tree() {
|
||||
for indirect_type in [false, true] {
|
||||
|
||||
Reference in new issue
Block a user