zpdf_document/
annotation.rs1use zpdf_core::{ObjectId, PdfObject, Rect};
8use zpdf_parser::PdfFile;
9
10use crate::forms::{AcroForm, GeneratedAppearance};
11use crate::page::PdfPage;
12
13pub const ANNOT_FLAG_HIDDEN: i64 = 1 << 1;
15pub const ANNOT_FLAG_NOVIEW: i64 = 1 << 5;
16
17#[derive(Debug, Clone)]
18pub struct Annotation {
19 pub subtype: String,
20 pub rect: Rect,
22 pub flags: i64,
24 pub appearance: Option<ObjectId>,
27 pub generated: Option<GeneratedAppearance>,
31 pub oc: Option<PdfObject>,
34}
35
36impl Annotation {
37 pub fn is_viewable(&self) -> bool {
40 self.flags & (ANNOT_FLAG_HIDDEN | ANNOT_FLAG_NOVIEW) == 0
41 && self.subtype != "Popup"
43 && (self.appearance.is_some() || self.generated.is_some())
44 && self.rect.width() > 0.0
45 && self.rect.height() > 0.0
46 }
47}
48
49pub fn parse_annotations(
54 file: &PdfFile,
55 page: &PdfPage,
56 acro_form: Option<&AcroForm>,
57) -> Vec<Annotation> {
58 page.annots
59 .iter()
60 .filter_map(|&id| parse_annotation(file, id, acro_form))
61 .collect()
62}
63
64fn parse_annotation(
65 file: &PdfFile,
66 id: ObjectId,
67 acro_form: Option<&AcroForm>,
68) -> Option<Annotation> {
69 let obj = file.resolve(id).ok()?;
70 let dict = obj.as_dict().ok()?;
71
72 let subtype = dict.get_name("Subtype").unwrap_or("").to_string();
73 let rect = crate::page::resolve_rect(file, dict, "Rect")?;
74 let flags = match dict.get("F") {
75 Some(PdfObject::Integer(n)) => *n,
76 Some(PdfObject::Ref(r)) => file
77 .resolve(*r)
78 .ok()
79 .and_then(|o| o.as_i64().ok())
80 .unwrap_or(0),
81 _ => 0,
82 };
83
84 let appearance = select_appearance(file, dict);
85 let oc = dict.get("OC").cloned();
86
87 let generated = if subtype == "Widget" {
92 acro_form
93 .and_then(|af| af.field_for_widget(id).map(|field| (af, field)))
94 .filter(|(af, _)| af.need_appearances || appearance.is_none())
95 .and_then(|(af, field)| {
96 crate::forms::generate_widget_appearance(field, rect, af.dr_fonts.as_ref())
97 })
98 } else if appearance.is_none() {
99 crate::annot_appearance::generate_annotation_appearance(file, dict, &subtype, rect)
100 } else {
101 None
102 };
103
104 Some(Annotation {
105 subtype,
106 rect,
107 flags,
108 appearance,
109 generated,
110 oc,
111 })
112}
113
114fn select_appearance(file: &PdfFile, annot: &zpdf_core::PdfDict) -> Option<ObjectId> {
117 let ap = match annot.get("AP")? {
118 PdfObject::Dict(d) => d.clone(),
119 PdfObject::Ref(r) => file.resolve(*r).ok()?.as_dict().ok()?.clone(),
120 _ => return None,
121 };
122 let n = ap.get("N")?;
123
124 if let PdfObject::Ref(r) = n {
126 match file.resolve(*r).ok()? {
127 PdfObject::Stream(_) => return Some(*r),
128 PdfObject::Dict(states) => return select_state(file, &states, annot),
129 _ => return None,
130 }
131 }
132 if let PdfObject::Dict(states) = n {
134 return select_state(file, states, annot);
135 }
136 None
137}
138
139fn select_state(
140 file: &PdfFile,
141 states: &zpdf_core::PdfDict,
142 annot: &zpdf_core::PdfDict,
143) -> Option<ObjectId> {
144 let state = annot.get_name("AS").ok().or_else(|| match annot.get("V") {
147 Some(PdfObject::Name(n)) => Some(n.as_str()),
148 _ => None,
149 });
150 if let Some(state) = state {
151 if let Some(PdfObject::Ref(r)) = states.get(state) {
152 return Some(*r);
153 }
154 }
155 if states.0.len() == 1 {
157 if let Some(PdfObject::Ref(r)) = states.0.values().next() {
158 return Some(*r);
159 }
160 }
161 let _ = file;
162 None
163}