zpdf_document/
annotation.rs1use zpdf_core::{ObjectId, PdfObject, Rect};
8use zpdf_parser::PdfFile;
9
10use crate::page::PdfPage;
11
12pub const ANNOT_FLAG_HIDDEN: i64 = 1 << 1;
14pub const ANNOT_FLAG_NOVIEW: i64 = 1 << 5;
15
16#[derive(Debug, Clone)]
17pub struct Annotation {
18 pub subtype: String,
19 pub rect: Rect,
21 pub flags: i64,
23 pub appearance: Option<ObjectId>,
26 pub oc: Option<PdfObject>,
29}
30
31impl Annotation {
32 pub fn is_viewable(&self) -> bool {
35 self.flags & (ANNOT_FLAG_HIDDEN | ANNOT_FLAG_NOVIEW) == 0
36 && self.subtype != "Popup"
38 && self.appearance.is_some()
39 && self.rect.width() > 0.0
40 && self.rect.height() > 0.0
41 }
42}
43
44pub fn parse_annotations(file: &PdfFile, page: &PdfPage) -> Vec<Annotation> {
48 page.annots
49 .iter()
50 .filter_map(|&id| parse_annotation(file, id))
51 .collect()
52}
53
54fn parse_annotation(file: &PdfFile, id: ObjectId) -> Option<Annotation> {
55 let obj = file.resolve(id).ok()?;
56 let dict = obj.as_dict().ok()?;
57
58 let subtype = dict.get_name("Subtype").unwrap_or("").to_string();
59 let rect = crate::page::resolve_rect(file, dict, "Rect")?;
60 let flags = match dict.get("F") {
61 Some(PdfObject::Integer(n)) => *n,
62 Some(PdfObject::Ref(r)) => file
63 .resolve(*r)
64 .ok()
65 .and_then(|o| o.as_i64().ok())
66 .unwrap_or(0),
67 _ => 0,
68 };
69
70 let appearance = select_appearance(file, dict);
71 let oc = dict.get("OC").cloned();
72
73 Some(Annotation {
74 subtype,
75 rect,
76 flags,
77 appearance,
78 oc,
79 })
80}
81
82fn select_appearance(file: &PdfFile, annot: &zpdf_core::PdfDict) -> Option<ObjectId> {
85 let ap = match annot.get("AP")? {
86 PdfObject::Dict(d) => d.clone(),
87 PdfObject::Ref(r) => file.resolve(*r).ok()?.as_dict().ok()?.clone(),
88 _ => return None,
89 };
90 let n = ap.get("N")?;
91
92 if let PdfObject::Ref(r) = n {
94 match file.resolve(*r).ok()? {
95 PdfObject::Stream(_) => return Some(*r),
96 PdfObject::Dict(states) => return select_state(file, &states, annot),
97 _ => return None,
98 }
99 }
100 if let PdfObject::Dict(states) = n {
102 return select_state(file, states, annot);
103 }
104 None
105}
106
107fn select_state(
108 file: &PdfFile,
109 states: &zpdf_core::PdfDict,
110 annot: &zpdf_core::PdfDict,
111) -> Option<ObjectId> {
112 let as_name = annot.get_name("AS").ok();
113 if let Some(state) = as_name {
114 if let Some(PdfObject::Ref(r)) = states.get(state) {
115 return Some(*r);
116 }
117 }
118 if states.0.len() == 1 {
120 if let Some(PdfObject::Ref(r)) = states.0.values().next() {
121 return Some(*r);
122 }
123 }
124 let _ = file;
125 None
126}