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" {
91 acro_form
92 .and_then(|af| af.field_for_widget(id).map(|field| (af, field)))
93 .filter(|(af, _)| af.need_appearances || appearance.is_none())
94 .and_then(|(af, field)| {
95 crate::forms::generate_widget_appearance(field, rect, af.dr_fonts.as_ref())
96 })
97 } else {
98 None
99 };
100
101 Some(Annotation {
102 subtype,
103 rect,
104 flags,
105 appearance,
106 generated,
107 oc,
108 })
109}
110
111fn select_appearance(file: &PdfFile, annot: &zpdf_core::PdfDict) -> Option<ObjectId> {
114 let ap = match annot.get("AP")? {
115 PdfObject::Dict(d) => d.clone(),
116 PdfObject::Ref(r) => file.resolve(*r).ok()?.as_dict().ok()?.clone(),
117 _ => return None,
118 };
119 let n = ap.get("N")?;
120
121 if let PdfObject::Ref(r) = n {
123 match file.resolve(*r).ok()? {
124 PdfObject::Stream(_) => return Some(*r),
125 PdfObject::Dict(states) => return select_state(file, &states, annot),
126 _ => return None,
127 }
128 }
129 if let PdfObject::Dict(states) = n {
131 return select_state(file, states, annot);
132 }
133 None
134}
135
136fn select_state(
137 file: &PdfFile,
138 states: &zpdf_core::PdfDict,
139 annot: &zpdf_core::PdfDict,
140) -> Option<ObjectId> {
141 let state = annot.get_name("AS").ok().or_else(|| match annot.get("V") {
144 Some(PdfObject::Name(n)) => Some(n.as_str()),
145 _ => None,
146 });
147 if let Some(state) = state {
148 if let Some(PdfObject::Ref(r)) = states.get(state) {
149 return Some(*r);
150 }
151 }
152 if states.0.len() == 1 {
154 if let Some(PdfObject::Ref(r)) = states.0.values().next() {
155 return Some(*r);
156 }
157 }
158 let _ = file;
159 None
160}