mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
Viewers draw notes, FreeText, ink and stamps from their dictionary when the file stores no /AP /N. flatten only drew annotations that already had an appearance and then removed the rest, so a supported note disappeared and the saved page came back blank. Build the appearance a viewer would show for every selected, visible, non-widget annotation that has none, then draw it as usual. A subtype this build cannot render still fails to build and is handled exactly as before. The new synthetic test draws the note icon into the page and asserts it is baked in; without the fix flatten reports 0 drawn and the icon is lost. Co-authored-by: Big Pickle <noreply@opencode.ai>
This commit is contained in:
@@ -118,6 +118,23 @@ fn flatten_which(doc: &mut Document, pages: &[usize], which: Which) -> Result<us
|
||||
.unwrap_or_default();
|
||||
let mut gone: HashSet<ObjRef> = HashSet::new();
|
||||
let mut gone_inline: HashSet<usize> = HashSet::new();
|
||||
// A viewer draws many comments from their dictionary even when the file stores no
|
||||
// appearance (notes, FreeText, ink, stamps…). Build the appearance it would show, so
|
||||
// flattening keeps a comment that still has ink on screen instead of deleting it.
|
||||
for entry in &list {
|
||||
let Some(r) = entry.as_ref() else { continue };
|
||||
let d = doc.resolve(entry).as_dict().cloned().unwrap_or_default();
|
||||
let subtype = d.name(b"Subtype").unwrap_or_default();
|
||||
if subtype == b"Widget" || !selected(doc, &d, which) {
|
||||
continue;
|
||||
}
|
||||
let flags = d.get(b"F").and_then(|f| doc.resolve(f).as_int()).unwrap_or(0);
|
||||
if flags & (HIDDEN | NO_VIEW) != 0 || appearance(doc, &d).is_some() {
|
||||
continue;
|
||||
}
|
||||
// A subtype this build cannot draw only fails here; it is still removed below.
|
||||
let _ = pdfcraft_annot::set_appearance(doc, r);
|
||||
}
|
||||
for (k, entry) in list.iter().enumerate() {
|
||||
let obj = doc.resolve(entry);
|
||||
let Some(d) = obj.as_dict() else { continue };
|
||||
|
||||
@@ -175,6 +175,26 @@ fn flattening_draws_appearances_into_the_page_and_removes_the_comments() {
|
||||
assert!(marks_present(&doc).is_empty(), "flattened content is not a removable mark");
|
||||
}
|
||||
|
||||
/// #805: a supported note with no stored appearance is still drawn by viewers. Flattening must
|
||||
/// generate that appearance and bake it into the page, not delete a comment that shows an icon.
|
||||
#[test]
|
||||
fn flattening_keeps_a_note_whose_appearance_is_missing() {
|
||||
let page = stream("", " ");
|
||||
let mut doc = build(&[
|
||||
"<< /Type /Catalog /Pages 2 0 R >>",
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
|
||||
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] /Resources << >> /Contents 4 0 R /Annots [5 0 R] >>",
|
||||
&page,
|
||||
"<< /Type /Annot /Subtype /Text /Rect [10 10 30 30] /Contents (Synthetic note) /Name /Comment /Open false /F 0 >>",
|
||||
]);
|
||||
assert_eq!(flatten(&mut doc, &[0], true, false).unwrap(), 1, "the note icon is drawn");
|
||||
let doc = reopen(&doc);
|
||||
let p = &pdfcraft_model::pages(&doc)[0];
|
||||
assert!(!p.dict.contains(b"Annots"), "the note is gone once its icon is baked in");
|
||||
let flat = streams(&doc, 0).pop().unwrap();
|
||||
assert!(flat.contains("q 1 0 0 1 10 10 cm /PCFl0 Do Q"), "{flat}");
|
||||
}
|
||||
|
||||
/// Display space is in points, so on a `/UserUnit 2` page an item's box maps to half as many
|
||||
/// user-space units. Items record the `/UserUnit` they were written for; one without it was
|
||||
/// written when display space was in user-space units, and reads back scaled to points.
|
||||
|
||||
Reference in New Issue
Block a user