1use crate::annotations::{Annotation, AnnotationType};
7use crate::error::Result;
8use crate::geometry::{Point, Rectangle};
9use crate::graphics::Color;
10use crate::objects::{Object, ObjectId};
11
12#[derive(Debug, Clone)]
14pub struct PopupAnnotation {
15 pub rect: Rectangle,
17 pub parent: Option<ObjectId>,
19 pub open: bool,
21 pub contents: Option<String>,
23 pub color: Option<Color>,
25 pub flags: PopupFlags,
27}
28
29#[derive(Debug, Clone, Copy, Default)]
31pub struct PopupFlags {
32 pub no_rotate: bool,
34 pub no_zoom: bool,
36}
37
38impl Default for PopupAnnotation {
39 fn default() -> Self {
40 Self {
41 rect: Rectangle::new(Point::new(100.0, 100.0), Point::new(300.0, 200.0)),
42 parent: None,
43 open: false,
44 contents: None,
45 color: Some(Color::rgb(1.0, 1.0, 0.9)), flags: PopupFlags::default(),
47 }
48 }
49}
50
51impl PopupAnnotation {
52 pub fn new(rect: Rectangle) -> Self {
54 Self {
55 rect,
56 ..Default::default()
57 }
58 }
59
60 pub fn with_parent(mut self, parent: ObjectId) -> Self {
62 self.parent = Some(parent);
63 self
64 }
65
66 pub fn with_open(mut self, open: bool) -> Self {
68 self.open = open;
69 self
70 }
71
72 pub fn with_contents(mut self, contents: impl Into<String>) -> Self {
74 self.contents = Some(contents.into());
75 self
76 }
77
78 pub fn with_color(mut self, color: Option<Color>) -> Self {
80 self.color = color;
81 self
82 }
83
84 pub fn with_no_rotate(mut self, no_rotate: bool) -> Self {
86 self.flags.no_rotate = no_rotate;
87 self
88 }
89
90 pub fn with_no_zoom(mut self, no_zoom: bool) -> Self {
92 self.flags.no_zoom = no_zoom;
93 self
94 }
95
96 pub fn with_flags(mut self, flags: PopupFlags) -> Self {
98 self.flags = flags;
99 self
100 }
101
102 pub fn to_annotation(&self) -> Result<Annotation> {
104 let mut annotation = Annotation::new(AnnotationType::Popup, self.rect);
105
106 if let Some(parent_ref) = &self.parent {
108 annotation
109 .properties
110 .set("Parent", Object::Reference(*parent_ref));
111 }
112
113 annotation
115 .properties
116 .set("Open", Object::Boolean(self.open));
117
118 if let Some(contents) = &self.contents {
120 annotation
121 .properties
122 .set("Contents", Object::String(contents.clone()));
123 }
124
125 if let Some(color) = &self.color {
127 annotation.properties.set(
128 "C",
129 Object::Array(vec![
130 Object::Real(color.r()),
131 Object::Real(color.g()),
132 Object::Real(color.b()),
133 ]),
134 );
135 }
136
137 let mut flags = 0;
139 if self.flags.no_rotate {
140 flags |= 1 << 4; }
142 if self.flags.no_zoom {
143 flags |= 1 << 3; }
145
146 if flags != 0 {
147 annotation
148 .properties
149 .set("F", Object::Integer(flags as i64));
150 }
151
152 Ok(annotation)
153 }
154}
155
156pub fn create_text_popup(
158 parent: ObjectId,
159 rect: Rectangle,
160 contents: impl Into<String>,
161) -> Result<Annotation> {
162 PopupAnnotation::new(rect)
163 .with_parent(parent)
164 .with_contents(contents)
165 .with_open(false)
166 .to_annotation()
167}
168
169pub fn create_markup_popup(
171 parent: ObjectId,
172 position: Point,
173 width: f64,
174 height: f64,
175 contents: impl Into<String>,
176) -> Result<Annotation> {
177 let rect = Rectangle::new(
178 position,
179 Point::new(position.x + width, position.y + height),
180 );
181
182 PopupAnnotation::new(rect)
183 .with_parent(parent)
184 .with_contents(contents)
185 .with_open(false)
186 .with_color(Some(Color::rgb(1.0, 1.0, 0.8))) .to_annotation()
188}
189
190pub fn create_open_popup(
192 parent: ObjectId,
193 rect: Rectangle,
194 contents: impl Into<String>,
195) -> Result<Annotation> {
196 PopupAnnotation::new(rect)
197 .with_parent(parent)
198 .with_contents(contents)
199 .with_open(true)
200 .to_annotation()
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn test_popup_creation() {
209 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(300.0, 200.0));
210
211 let popup = PopupAnnotation::new(rect);
212 assert_eq!(popup.rect, rect);
213 assert!(!popup.open);
214 assert!(popup.parent.is_none());
215 }
216
217 #[test]
218 fn test_popup_with_parent() {
219 let parent_ref = ObjectId::new(10, 0);
220 let rect = Rectangle::new(Point::new(200.0, 200.0), Point::new(400.0, 300.0));
221
222 let popup = PopupAnnotation::new(rect).with_parent(parent_ref);
223
224 assert_eq!(popup.parent, Some(parent_ref));
225 }
226
227 #[test]
228 fn test_popup_with_contents() {
229 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(200.0, 100.0));
230
231 let popup = PopupAnnotation::new(rect)
232 .with_contents("This is a popup annotation")
233 .with_open(true);
234
235 assert_eq!(
236 popup.contents,
237 Some("This is a popup annotation".to_string())
238 );
239 assert!(popup.open);
240 }
241
242 #[test]
243 fn test_popup_with_color() {
244 let popup = PopupAnnotation::default().with_color(Some(Color::rgb(0.9, 0.9, 1.0)));
245
246 assert_eq!(popup.color, Some(Color::rgb(0.9, 0.9, 1.0)));
247 }
248
249 #[test]
250 fn test_popup_flags() {
251 let flags = PopupFlags {
252 no_rotate: true,
253 no_zoom: true,
254 };
255
256 let popup = PopupAnnotation::default().with_flags(flags);
257
258 assert!(popup.flags.no_rotate);
259 assert!(popup.flags.no_zoom);
260 }
261
262 #[test]
263 fn test_popup_individual_flags() {
264 let popup = PopupAnnotation::default()
265 .with_no_rotate(true)
266 .with_no_zoom(false);
267
268 assert!(popup.flags.no_rotate);
269 assert!(!popup.flags.no_zoom);
270 }
271
272 #[test]
273 fn test_popup_to_annotation() {
274 let parent_ref = ObjectId::new(5, 0);
275 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(300.0, 200.0));
276
277 let popup = PopupAnnotation::new(rect)
278 .with_parent(parent_ref)
279 .with_contents("Test popup")
280 .with_open(true)
281 .with_color(Some(Color::rgb(1.0, 1.0, 0.0)));
282
283 let annotation = popup.to_annotation();
284 assert!(annotation.is_ok());
285 }
286
287 #[test]
288 fn test_create_text_popup() {
289 let parent = ObjectId::new(1, 0);
290 let rect = Rectangle::new(Point::new(50.0, 50.0), Point::new(250.0, 150.0));
291
292 let popup = create_text_popup(parent, rect, "Text annotation popup");
293 assert!(popup.is_ok());
294 }
295
296 #[test]
297 fn test_create_markup_popup() {
298 let parent = ObjectId::new(2, 0);
299 let position = Point::new(100.0, 200.0);
300
301 let popup = create_markup_popup(parent, position, 200.0, 100.0, "Markup comment");
302 assert!(popup.is_ok());
303 }
304
305 #[test]
306 fn test_create_open_popup() {
307 let parent = ObjectId::new(3, 0);
308 let rect = Rectangle::new(Point::new(150.0, 150.0), Point::new(350.0, 250.0));
309
310 let popup = create_open_popup(parent, rect, "Initially open popup");
311 assert!(popup.is_ok());
312 }
313
314 #[test]
315 fn test_popup_default() {
316 let popup = PopupAnnotation::default();
317
318 assert!(!popup.open);
319 assert!(popup.parent.is_none());
320 assert!(popup.contents.is_none());
321 assert_eq!(popup.color, Some(Color::rgb(1.0, 1.0, 0.9)));
322 assert!(!popup.flags.no_rotate);
323 assert!(!popup.flags.no_zoom);
324 }
325
326 #[test]
327 fn test_popup_flags_default() {
328 let flags = PopupFlags::default();
329
330 assert!(!flags.no_rotate);
331 assert!(!flags.no_zoom);
332 }
333
334 #[test]
335 fn test_popup_complex() {
336 let parent = ObjectId::new(42, 1);
337 let rect = Rectangle::new(Point::new(200.0, 300.0), Point::new(400.0, 450.0));
338
339 let popup = PopupAnnotation::new(rect)
340 .with_parent(parent)
341 .with_contents("Complex popup with all features")
342 .with_open(true)
343 .with_color(Some(Color::rgb(0.8, 0.9, 1.0)))
344 .with_no_rotate(true)
345 .with_no_zoom(true);
346
347 assert_eq!(popup.rect, rect);
348 assert_eq!(popup.parent, Some(parent));
349 assert_eq!(
350 popup.contents,
351 Some("Complex popup with all features".to_string())
352 );
353 assert!(popup.open);
354 assert_eq!(popup.color, Some(Color::rgb(0.8, 0.9, 1.0)));
355 assert!(popup.flags.no_rotate);
356 assert!(popup.flags.no_zoom);
357
358 let annotation = popup.to_annotation();
359 assert!(annotation.is_ok());
360 }
361
362 #[test]
363 fn test_popup_without_parent() {
364 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
366
367 let popup = PopupAnnotation::new(rect).with_contents("Standalone popup");
368
369 assert!(popup.parent.is_none());
370
371 let annotation = popup.to_annotation();
372 assert!(annotation.is_ok());
373 }
374}