1extern crate alloc;
10
11use crate::appearance::AppearanceDict;
12use crate::types::*;
13use pdf_syntax::object::dict::keys::*;
14use pdf_syntax::object::{Array, Dict, Name, Rect, Stream};
15use pdf_syntax::page::Page;
16
17#[derive(Debug, Clone)]
19pub struct Annotation<'a> {
20 dict: Dict<'a>,
21}
22
23impl<'a> Annotation<'a> {
24 pub fn from_page(page: &Page<'a>) -> Vec<Self> {
26 page.annots()
27 .into_iter()
28 .map(|dict| Self { dict })
29 .collect()
30 }
31
32 pub fn from_dict(dict: Dict<'a>) -> Self {
34 Self { dict }
35 }
36
37 pub fn dict(&self) -> &Dict<'a> {
39 &self.dict
40 }
41
42 pub fn annotation_type(&self) -> AnnotationType {
44 self.dict
45 .get::<Name>(SUBTYPE)
46 .map(|n| AnnotationType::from_name(n.as_ref()))
47 .unwrap_or(AnnotationType::Unknown)
48 }
49
50 pub fn rect(&self) -> Option<Rect> {
52 self.dict.get::<Rect>(RECT)
53 }
54
55 pub fn contents(&self) -> Option<alloc::string::String> {
57 self.dict
58 .get::<pdf_syntax::object::String>(CONTENTS)
59 .map(|s| pdf_string_to_string(&s))
60 }
61
62 pub fn flags(&self) -> AnnotationFlags {
64 AnnotationFlags(self.dict.get::<u32>(F).unwrap_or(0))
65 }
66
67 pub fn is_hidden(&self) -> bool {
69 self.flags().hidden()
70 }
71
72 pub fn is_printable(&self) -> bool {
74 self.flags().print()
75 }
76
77 pub fn name(&self) -> Option<alloc::string::String> {
79 self.dict
80 .get::<pdf_syntax::object::String>(NM)
81 .map(|s| pdf_string_to_string(&s))
82 }
83
84 pub fn modified(&self) -> Option<alloc::string::String> {
86 self.dict
87 .get::<pdf_syntax::object::String>(M)
88 .map(|s| pdf_string_to_string(&s))
89 }
90
91 pub fn author(&self) -> Option<alloc::string::String> {
93 self.dict
94 .get::<pdf_syntax::object::String>(T)
95 .map(|s| pdf_string_to_string(&s))
96 }
97
98 pub fn subject(&self) -> Option<alloc::string::String> {
100 self.dict
101 .get::<pdf_syntax::object::String>(SUBJ)
102 .map(|s| pdf_string_to_string(&s))
103 }
104
105 pub fn color(&self) -> Option<Color> {
107 self.dict
108 .get::<Array<'_>>(C)
109 .map(|arr| Color::from_array(&arr))
110 }
111
112 pub fn border_style(&self) -> Option<BorderStyle> {
114 self.dict
115 .get::<Dict<'_>>(BS)
116 .map(|d| BorderStyle::from_dict(&d))
117 }
118
119 pub fn border_array(&self) -> Option<[f32; 3]> {
121 let arr = self.dict.get::<Array<'_>>(BORDER)?;
122 let mut iter = arr.iter::<f32>();
123 let h_radius = iter.next()?;
124 let v_radius = iter.next()?;
125 let width = iter.next()?;
126 Some([h_radius, v_radius, width])
127 }
128
129 pub fn border_effect(&self) -> Option<BorderEffect> {
131 self.dict
132 .get::<Dict<'_>>(BE)
133 .map(|d| BorderEffect::from_dict(&d))
134 }
135
136 pub fn interior_color(&self) -> Option<Color> {
138 self.dict
139 .get::<Array<'_>>(IC)
140 .map(|arr| Color::from_array(&arr))
141 }
142
143 pub fn appearance(&self) -> Option<AppearanceDict<'a>> {
145 AppearanceDict::from_annot(&self.dict)
146 }
147
148 pub fn normal_appearance(&self) -> Option<Stream<'a>> {
150 self.appearance()?.normal(&self.dict)
151 }
152
153 pub fn creation_date(&self) -> Option<alloc::string::String> {
155 self.dict
156 .get::<pdf_syntax::object::String>(CREATION_DATE)
157 .map(|s| pdf_string_to_string(&s))
158 }
159
160 pub fn opacity(&self) -> Option<f32> {
162 self.dict.get::<f32>(CA)
163 }
164
165 pub fn irt(&self) -> Option<Annotation<'a>> {
167 self.dict
168 .get::<Dict<'_>>(IRT)
169 .map(|d| Annotation { dict: d })
170 }
171
172 pub fn reply_type(&self) -> Option<alloc::string::String> {
174 self.dict
175 .get::<Name>(RT)
176 .map(|n| alloc::string::String::from(n.as_str()))
177 }
178
179 pub fn state(&self) -> Option<alloc::string::String> {
181 self.dict
182 .get::<Name>(STATE)
183 .map(|n| alloc::string::String::from(n.as_str()))
184 }
185
186 pub fn state_model(&self) -> Option<alloc::string::String> {
188 self.dict
189 .get::<Name>(STATE_MODEL)
190 .map(|n| alloc::string::String::from(n.as_str()))
191 }
192
193 pub fn popup(&self) -> Option<Annotation<'a>> {
195 self.dict
196 .get::<Dict<'_>>(POPUP)
197 .map(|d| Annotation { dict: d })
198 }
199
200 pub fn quad_points(&self) -> Option<QuadPoints> {
202 self.dict
203 .get::<Array<'_>>(QUADPOINTS)
204 .map(|arr| QuadPoints::from_array(&arr))
205 }
206}
207
208pub fn pdf_string_to_string(s: &pdf_syntax::object::String) -> alloc::string::String {
210 let bytes = s.as_bytes();
211 if bytes.len() >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF {
212 let utf16: Vec<u16> = bytes[2..]
213 .chunks_exact(2)
214 .map(|c| u16::from_be_bytes([c[0], c[1]]))
215 .collect();
216 alloc::string::String::from_utf16_lossy(&utf16)
217 } else if bytes.len() >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF {
218 alloc::string::String::from_utf8_lossy(&bytes[3..]).into_owned()
220 } else {
221 let mut s = alloc::string::String::with_capacity(bytes.len());
223 for &b in bytes {
224 s.push(pdfdoc_byte_to_char(b));
225 }
226 s
227 }
228}
229
230fn pdfdoc_byte_to_char(b: u8) -> char {
235 #[rustfmt::skip]
236 static HIGH: [char; 46] = [
237 '\u{2022}', '\u{2020}', '\u{2021}', '\u{2026}', '\u{2014}', '\u{2013}', '\u{0192}', '\u{2044}', '\u{2039}', '\u{203A}', '\u{2212}', '\u{2030}', '\u{201E}', '\u{201C}', '\u{201D}', '\u{2018}', '\u{2019}', '\u{201A}', '\u{2122}', '\u{FB01}', '\u{FB02}', '\u{0141}', '\u{0152}', '\u{0160}', '\u{0178}', '\u{017D}', '\u{0131}', '\u{0142}', '\u{0153}', '\u{0161}', '\u{017E}', '\u{FFFD}', '\u{20AC}', '\u{00A1}', '\u{00A2}', '\u{00A3}', '\u{00A4}', '\u{00A5}', '\u{00A6}', '\u{00A7}', '\u{00A8}', '\u{00A9}', '\u{00AA}', '\u{00AB}', '\u{00AC}', '\u{00AD}', ];
250 match b {
251 0x00..=0x7F => b as char,
252 0x80..=0xAD => HIGH[(b - 0x80) as usize],
253 0xAE..=0xFF => char::from(b), }
255}