1use crate::geometry::Rectangle;
4use crate::graphics::Color;
5use crate::objects::{Dictionary, Object, ObjectReference};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum AnnotationType {
11 Text,
13 Link,
15 FreeText,
17 Line,
19 Square,
21 Circle,
23 Polygon,
25 PolyLine,
27 Highlight,
29 Underline,
31 Squiggly,
33 StrikeOut,
35 Stamp,
37 Caret,
39 Ink,
41 Popup,
43 FileAttachment,
45 Sound,
47 Movie,
49 Widget,
51 Screen,
53 PrinterMark,
55 TrapNet,
57 Watermark,
59}
60
61impl AnnotationType {
62 pub fn pdf_name(&self) -> &'static str {
64 match self {
65 AnnotationType::Text => "Text",
66 AnnotationType::Link => "Link",
67 AnnotationType::FreeText => "FreeText",
68 AnnotationType::Line => "Line",
69 AnnotationType::Square => "Square",
70 AnnotationType::Circle => "Circle",
71 AnnotationType::Polygon => "Polygon",
72 AnnotationType::PolyLine => "PolyLine",
73 AnnotationType::Highlight => "Highlight",
74 AnnotationType::Underline => "Underline",
75 AnnotationType::Squiggly => "Squiggly",
76 AnnotationType::StrikeOut => "StrikeOut",
77 AnnotationType::Stamp => "Stamp",
78 AnnotationType::Caret => "Caret",
79 AnnotationType::Ink => "Ink",
80 AnnotationType::Popup => "Popup",
81 AnnotationType::FileAttachment => "FileAttachment",
82 AnnotationType::Sound => "Sound",
83 AnnotationType::Movie => "Movie",
84 AnnotationType::Widget => "Widget",
85 AnnotationType::Screen => "Screen",
86 AnnotationType::PrinterMark => "PrinterMark",
87 AnnotationType::TrapNet => "TrapNet",
88 AnnotationType::Watermark => "Watermark",
89 }
90 }
91}
92
93#[derive(Debug, Clone, Copy, Default)]
95pub struct AnnotationFlags {
96 pub invisible: bool,
98 pub hidden: bool,
100 pub print: bool,
102 pub no_zoom: bool,
104 pub no_rotate: bool,
106 pub no_view: bool,
108 pub read_only: bool,
110 pub locked: bool,
112 pub locked_contents: bool,
114}
115
116impl AnnotationFlags {
117 pub fn to_flags(&self) -> u32 {
119 let mut flags = 0u32;
120 if self.invisible {
121 flags |= 1 << 0;
122 }
123 if self.hidden {
124 flags |= 1 << 1;
125 }
126 if self.print {
127 flags |= 1 << 2;
128 }
129 if self.no_zoom {
130 flags |= 1 << 3;
131 }
132 if self.no_rotate {
133 flags |= 1 << 4;
134 }
135 if self.no_view {
136 flags |= 1 << 5;
137 }
138 if self.read_only {
139 flags |= 1 << 6;
140 }
141 if self.locked {
142 flags |= 1 << 7;
143 }
144 if self.locked_contents {
145 flags |= 1 << 9;
146 }
147 flags
148 }
149}
150
151#[derive(Debug, Clone)]
153pub struct BorderStyle {
154 pub width: f64,
156 pub style: BorderStyleType,
158 pub dash_pattern: Option<Vec<f64>>,
160}
161
162#[derive(Debug, Clone, Copy)]
164pub enum BorderStyleType {
165 Solid,
167 Dashed,
169 Beveled,
171 Inset,
173 Underline,
175}
176
177impl BorderStyleType {
178 pub fn pdf_name(&self) -> &'static str {
180 match self {
181 BorderStyleType::Solid => "S",
182 BorderStyleType::Dashed => "D",
183 BorderStyleType::Beveled => "B",
184 BorderStyleType::Inset => "I",
185 BorderStyleType::Underline => "U",
186 }
187 }
188}
189
190impl Default for BorderStyle {
191 fn default() -> Self {
192 Self {
193 width: 1.0,
194 style: BorderStyleType::Solid,
195 dash_pattern: None,
196 }
197 }
198}
199
200#[derive(Debug, Clone)]
202pub struct Annotation {
203 pub annotation_type: AnnotationType,
205 pub rect: Rectangle,
207 pub contents: Option<String>,
209 pub subject: Option<String>,
211 pub name: Option<String>,
213 pub modified: Option<String>,
215 pub flags: AnnotationFlags,
217 pub border: Option<BorderStyle>,
219 pub color: Option<Color>,
221 pub page: Option<ObjectReference>,
223 pub properties: Dictionary,
225}
226
227impl Annotation {
228 pub fn new(annotation_type: AnnotationType, rect: Rectangle) -> Self {
230 Self {
231 annotation_type,
232 rect,
233 contents: None,
234 subject: None,
235 name: None,
236 modified: None,
237 flags: AnnotationFlags {
238 print: true,
239 ..Default::default()
240 },
241 border: None,
242 color: None,
243 page: None,
244 properties: Dictionary::new(),
245 }
246 }
247
248 pub fn with_contents(mut self, contents: impl Into<String>) -> Self {
250 self.contents = Some(contents.into());
251 self
252 }
253
254 pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
256 self.subject = Some(subject.into());
257 self
258 }
259
260 pub fn with_name(mut self, name: impl Into<String>) -> Self {
262 self.name = Some(name.into());
263 self
264 }
265
266 pub fn with_color(mut self, color: Color) -> Self {
268 self.color = Some(color);
269 self
270 }
271
272 pub fn with_border(mut self, border: BorderStyle) -> Self {
274 self.border = Some(border);
275 self
276 }
277
278 pub fn with_flags(mut self, flags: AnnotationFlags) -> Self {
280 self.flags = flags;
281 self
282 }
283
284 pub fn set_field_dict(&mut self, field_dict: Dictionary) {
286 for (key, value) in field_dict.iter() {
288 self.properties.set(key, value.clone());
289 }
290 }
291
292 pub fn to_dict(&self) -> Dictionary {
294 let mut dict = Dictionary::new();
295
296 dict.set("Type", Object::Name("Annot".to_string()));
298 dict.set(
299 "Subtype",
300 Object::Name(self.annotation_type.pdf_name().to_string()),
301 );
302
303 let rect_array = vec![
305 Object::Real(self.rect.lower_left.x),
306 Object::Real(self.rect.lower_left.y),
307 Object::Real(self.rect.upper_right.x),
308 Object::Real(self.rect.upper_right.y),
309 ];
310 dict.set("Rect", Object::Array(rect_array));
311
312 if let Some(ref contents) = self.contents {
314 dict.set("Contents", Object::String(contents.clone()));
315 }
316
317 if let Some(ref subject) = self.subject {
318 dict.set("Subj", Object::String(subject.clone()));
319 }
320
321 if let Some(ref name) = self.name {
322 dict.set("NM", Object::String(name.clone()));
323 }
324
325 if let Some(ref modified) = self.modified {
326 dict.set("M", Object::String(modified.clone()));
327 }
328
329 let flags = self.flags.to_flags();
331 if flags != 0 {
332 dict.set("F", Object::Integer(flags as i64));
333 }
334
335 if let Some(ref border) = self.border {
337 let mut bs_dict = Dictionary::new();
338 bs_dict.set("W", Object::Real(border.width));
339 bs_dict.set("S", Object::Name(border.style.pdf_name().to_string()));
340
341 if let Some(ref dash) = border.dash_pattern {
342 let dash_array: Vec<Object> = dash.iter().map(|&d| Object::Real(d)).collect();
343 bs_dict.set("D", Object::Array(dash_array));
344 }
345
346 dict.set("BS", Object::Dictionary(bs_dict));
347 }
348
349 if let Some(ref color) = self.color {
351 let c = match color {
352 Color::Rgb(r, g, b) => vec![Object::Real(*r), Object::Real(*g), Object::Real(*b)],
353 Color::Gray(g) => vec![Object::Real(*g)],
354 Color::Cmyk(c, m, y, k) => vec![
355 Object::Real(*c),
356 Object::Real(*m),
357 Object::Real(*y),
358 Object::Real(*k),
359 ],
360 };
361 dict.set("C", Object::Array(c));
362 }
363
364 if let Some(page) = self.page {
366 dict.set("P", Object::Reference(page));
367 }
368
369 for (key, value) in self.properties.iter() {
371 dict.set(key, value.clone());
372 }
373
374 dict
375 }
376}
377
378#[derive(Debug)]
380pub struct AnnotationManager {
381 annotations: HashMap<ObjectReference, Vec<Annotation>>,
383 next_id: u32,
385}
386
387impl AnnotationManager {
388 pub fn new() -> Self {
390 Self {
391 annotations: HashMap::new(),
392 next_id: 1,
393 }
394 }
395
396 pub fn add_annotation(
398 &mut self,
399 page_ref: ObjectReference,
400 mut annotation: Annotation,
401 ) -> ObjectReference {
402 annotation.page = Some(page_ref);
403
404 let annot_ref = ObjectReference::new(self.next_id, 0);
405 self.next_id += 1;
406
407 self.annotations
408 .entry(page_ref)
409 .or_default()
410 .push(annotation);
411
412 annot_ref
413 }
414
415 pub fn get_page_annotations(&self, page_ref: &ObjectReference) -> Option<&Vec<Annotation>> {
417 self.annotations.get(page_ref)
418 }
419
420 pub fn all_annotations(&self) -> &HashMap<ObjectReference, Vec<Annotation>> {
422 &self.annotations
423 }
424}
425
426impl Default for AnnotationManager {
427 fn default() -> Self {
428 Self::new()
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use crate::geometry::Point;
436
437 #[test]
438 fn test_annotation_type() {
439 assert_eq!(AnnotationType::Text.pdf_name(), "Text");
440 assert_eq!(AnnotationType::Link.pdf_name(), "Link");
441 assert_eq!(AnnotationType::Highlight.pdf_name(), "Highlight");
442 }
443
444 #[test]
445 fn test_annotation_flags() {
446 let flags = AnnotationFlags {
447 print: true,
448 read_only: true,
449 ..Default::default()
450 };
451
452 assert_eq!(flags.to_flags(), 68); }
454
455 #[test]
456 fn test_border_style() {
457 let border = BorderStyle {
458 width: 2.0,
459 style: BorderStyleType::Dashed,
460 dash_pattern: Some(vec![3.0, 1.0]),
461 };
462
463 assert_eq!(border.width, 2.0);
464 assert_eq!(border.style.pdf_name(), "D");
465 }
466
467 #[test]
468 fn test_annotation_creation() {
469 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 200.0));
470
471 let annotation = Annotation::new(AnnotationType::Text, rect)
472 .with_contents("Test annotation")
473 .with_color(Color::Rgb(1.0, 0.0, 0.0));
474
475 assert_eq!(annotation.annotation_type, AnnotationType::Text);
476 assert_eq!(annotation.contents, Some("Test annotation".to_string()));
477 assert!(annotation.color.is_some());
478 }
479
480 #[test]
481 fn test_annotation_to_dict() {
482 let rect = Rectangle::new(Point::new(50.0, 50.0), Point::new(150.0, 150.0));
483
484 let annotation =
485 Annotation::new(AnnotationType::Square, rect).with_contents("Square annotation");
486
487 let dict = annotation.to_dict();
488 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
489 assert_eq!(
490 dict.get("Subtype"),
491 Some(&Object::Name("Square".to_string()))
492 );
493 assert!(dict.get("Rect").is_some());
494 assert_eq!(
495 dict.get("Contents"),
496 Some(&Object::String("Square annotation".to_string()))
497 );
498 }
499
500 #[test]
501 fn test_annotation_manager() {
502 let mut manager = AnnotationManager::new();
503 let page_ref = ObjectReference::new(1, 0);
504
505 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 200.0));
506
507 let annotation = Annotation::new(AnnotationType::Text, rect);
508 let annot_ref = manager.add_annotation(page_ref, annotation);
509
510 assert_eq!(annot_ref.number(), 1);
511 assert!(manager.get_page_annotations(&page_ref).is_some());
512 assert_eq!(manager.get_page_annotations(&page_ref).unwrap().len(), 1);
513 }
514
515 #[test]
516 fn test_all_annotation_types() {
517 let types = [
518 AnnotationType::Text,
519 AnnotationType::Link,
520 AnnotationType::FreeText,
521 AnnotationType::Line,
522 AnnotationType::Square,
523 AnnotationType::Circle,
524 AnnotationType::Polygon,
525 AnnotationType::PolyLine,
526 AnnotationType::Highlight,
527 AnnotationType::Underline,
528 AnnotationType::Squiggly,
529 AnnotationType::StrikeOut,
530 AnnotationType::Stamp,
531 AnnotationType::Caret,
532 AnnotationType::Ink,
533 AnnotationType::Popup,
534 AnnotationType::FileAttachment,
535 AnnotationType::Sound,
536 AnnotationType::Movie,
537 AnnotationType::Widget,
538 AnnotationType::Screen,
539 AnnotationType::PrinterMark,
540 AnnotationType::TrapNet,
541 AnnotationType::Watermark,
542 ];
543
544 let expected_names = [
545 "Text",
546 "Link",
547 "FreeText",
548 "Line",
549 "Square",
550 "Circle",
551 "Polygon",
552 "PolyLine",
553 "Highlight",
554 "Underline",
555 "Squiggly",
556 "StrikeOut",
557 "Stamp",
558 "Caret",
559 "Ink",
560 "Popup",
561 "FileAttachment",
562 "Sound",
563 "Movie",
564 "Widget",
565 "Screen",
566 "PrinterMark",
567 "TrapNet",
568 "Watermark",
569 ];
570
571 for (annotation_type, expected_name) in types.iter().zip(expected_names.iter()) {
572 assert_eq!(annotation_type.pdf_name(), *expected_name);
573 }
574 }
575
576 #[test]
577 fn test_annotation_type_debug_clone_partial_eq() {
578 let annotation_type = AnnotationType::Highlight;
579 let debug_str = format!("{annotation_type:?}");
580 assert!(debug_str.contains("Highlight"));
581
582 let cloned = annotation_type;
583 assert_eq!(annotation_type, cloned);
584
585 assert_eq!(AnnotationType::Text, AnnotationType::Text);
586 assert_ne!(AnnotationType::Text, AnnotationType::Link);
587 }
588
589 #[test]
590 fn test_annotation_flags_comprehensive() {
591 let default_flags = AnnotationFlags::default();
593 assert_eq!(default_flags.to_flags(), 0);
594
595 let invisible_flag = AnnotationFlags {
597 invisible: true,
598 ..Default::default()
599 };
600 assert_eq!(invisible_flag.to_flags(), 1); let hidden_flag = AnnotationFlags {
603 hidden: true,
604 ..Default::default()
605 };
606 assert_eq!(hidden_flag.to_flags(), 2); let print_flag = AnnotationFlags {
609 print: true,
610 ..Default::default()
611 };
612 assert_eq!(print_flag.to_flags(), 4); let no_zoom_flag = AnnotationFlags {
615 no_zoom: true,
616 ..Default::default()
617 };
618 assert_eq!(no_zoom_flag.to_flags(), 8); let no_rotate_flag = AnnotationFlags {
621 no_rotate: true,
622 ..Default::default()
623 };
624 assert_eq!(no_rotate_flag.to_flags(), 16); let no_view_flag = AnnotationFlags {
627 no_view: true,
628 ..Default::default()
629 };
630 assert_eq!(no_view_flag.to_flags(), 32); let read_only_flag = AnnotationFlags {
633 read_only: true,
634 ..Default::default()
635 };
636 assert_eq!(read_only_flag.to_flags(), 64); let locked_flag = AnnotationFlags {
639 locked: true,
640 ..Default::default()
641 };
642 assert_eq!(locked_flag.to_flags(), 128); let locked_contents_flag = AnnotationFlags {
645 locked_contents: true,
646 ..Default::default()
647 };
648 assert_eq!(locked_contents_flag.to_flags(), 512); }
650
651 #[test]
652 fn test_annotation_flags_combined() {
653 let combined_flags = AnnotationFlags {
654 print: true,
655 read_only: true,
656 locked: true,
657 ..Default::default()
658 };
659 assert_eq!(combined_flags.to_flags(), 4 + 64 + 128); let all_flags = AnnotationFlags {
663 invisible: true,
664 hidden: true,
665 print: true,
666 no_zoom: true,
667 no_rotate: true,
668 no_view: true,
669 read_only: true,
670 locked: true,
671 locked_contents: true,
672 };
673 assert_eq!(
674 all_flags.to_flags(),
675 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 512
676 );
677 }
678
679 #[test]
680 fn test_annotation_flags_debug_clone() {
681 let flags = AnnotationFlags {
682 print: true,
683 read_only: true,
684 ..Default::default()
685 };
686 let debug_str = format!("{flags:?}");
687 assert!(debug_str.contains("AnnotationFlags"));
688
689 let cloned = flags;
690 assert_eq!(flags.print, cloned.print);
691 assert_eq!(flags.read_only, cloned.read_only);
692 assert_eq!(flags.to_flags(), cloned.to_flags());
693 }
694
695 #[test]
696 fn test_border_style_types() {
697 assert_eq!(BorderStyleType::Solid.pdf_name(), "S");
698 assert_eq!(BorderStyleType::Dashed.pdf_name(), "D");
699 assert_eq!(BorderStyleType::Beveled.pdf_name(), "B");
700 assert_eq!(BorderStyleType::Inset.pdf_name(), "I");
701 assert_eq!(BorderStyleType::Underline.pdf_name(), "U");
702 }
703
704 #[test]
705 fn test_border_style_debug_clone() {
706 let style = BorderStyleType::Dashed;
707 let debug_str = format!("{style:?}");
708 assert!(debug_str.contains("Dashed"));
709
710 let cloned = style;
711 assert_eq!(style.pdf_name(), cloned.pdf_name());
712 }
713
714 #[test]
715 fn test_border_style_default() {
716 let default_border = BorderStyle::default();
717 assert_eq!(default_border.width, 1.0);
718 assert_eq!(default_border.style.pdf_name(), "S");
719 assert!(default_border.dash_pattern.is_none());
720 }
721
722 #[test]
723 fn test_border_style_with_dash_pattern() {
724 let dashed_border = BorderStyle {
725 width: 1.5,
726 style: BorderStyleType::Dashed,
727 dash_pattern: Some(vec![5.0, 2.0, 3.0, 2.0]),
728 };
729
730 assert_eq!(dashed_border.width, 1.5);
731 assert_eq!(dashed_border.style.pdf_name(), "D");
732 assert_eq!(dashed_border.dash_pattern.as_ref().unwrap().len(), 4);
733 }
734
735 #[test]
736 fn test_border_style_debug_clone_comprehensive() {
737 let border = BorderStyle {
738 width: 2.5,
739 style: BorderStyleType::Beveled,
740 dash_pattern: Some(vec![1.0, 2.0]),
741 };
742
743 let debug_str = format!("{border:?}");
744 assert!(debug_str.contains("BorderStyle"));
745 assert!(debug_str.contains("2.5"));
746
747 let cloned = border.clone();
748 assert_eq!(border.width, cloned.width);
749 assert_eq!(border.style.pdf_name(), cloned.style.pdf_name());
750 assert_eq!(border.dash_pattern, cloned.dash_pattern);
751 }
752
753 #[test]
754 fn test_annotation_creation_comprehensive() {
755 let rect = Rectangle::new(Point::new(10.0, 20.0), Point::new(110.0, 120.0));
756
757 let annotation = Annotation::new(AnnotationType::Circle, rect);
759 assert_eq!(annotation.annotation_type, AnnotationType::Circle);
760 assert!(annotation.flags.print); assert!(annotation.contents.is_none());
762 assert!(annotation.name.is_none());
763 assert!(annotation.color.is_none());
764 assert!(annotation.border.is_none());
765 assert!(annotation.page.is_none());
766 }
767
768 #[test]
769 fn test_annotation_builder_pattern() {
770 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
771 let border = BorderStyle {
772 width: 3.0,
773 style: BorderStyleType::Inset,
774 dash_pattern: None,
775 };
776 let flags = AnnotationFlags {
777 print: true,
778 no_zoom: true,
779 ..Default::default()
780 };
781
782 let annotation = Annotation::new(AnnotationType::FreeText, rect)
783 .with_contents("Free text annotation")
784 .with_name("annotation_1")
785 .with_color(Color::Rgb(0.0, 1.0, 0.0))
786 .with_border(border)
787 .with_flags(flags);
788
789 assert_eq!(
790 annotation.contents,
791 Some("Free text annotation".to_string())
792 );
793 assert_eq!(annotation.name, Some("annotation_1".to_string()));
794 assert!(matches!(annotation.color, Some(Color::Rgb(0.0, 1.0, 0.0))));
795 assert!(annotation.border.is_some());
796 assert_eq!(annotation.border.unwrap().width, 3.0);
797 assert!(annotation.flags.print);
798 assert!(annotation.flags.no_zoom);
799 }
800
801 #[test]
802 fn test_annotation_debug_clone() {
803 let rect = Rectangle::new(Point::new(50.0, 50.0), Point::new(150.0, 100.0));
804 let annotation =
805 Annotation::new(AnnotationType::Stamp, rect).with_contents("Stamp annotation");
806
807 let debug_str = format!("{annotation:?}");
808 assert!(debug_str.contains("Annotation"));
809 assert!(debug_str.contains("Stamp"));
810
811 let cloned = annotation.clone();
812 assert_eq!(annotation.annotation_type, cloned.annotation_type);
813 assert_eq!(annotation.contents, cloned.contents);
814 assert_eq!(annotation.rect.lower_left.x, cloned.rect.lower_left.x);
815 }
816
817 #[test]
818 fn test_annotation_to_dict_comprehensive() {
819 let rect = Rectangle::new(Point::new(25.0, 25.0), Point::new(125.0, 75.0));
820 let border = BorderStyle {
821 width: 2.0,
822 style: BorderStyleType::Dashed,
823 dash_pattern: Some(vec![4.0, 2.0]),
824 };
825 let flags = AnnotationFlags {
826 print: true,
827 read_only: true,
828 ..Default::default()
829 };
830 let page_ref = ObjectReference::new(5, 0);
831
832 let mut annotation = Annotation::new(AnnotationType::Underline, rect)
833 .with_contents("Underline annotation")
834 .with_name("underline_1")
835 .with_color(Color::Cmyk(0.1, 0.2, 0.3, 0.4))
836 .with_border(border)
837 .with_flags(flags);
838 annotation.page = Some(page_ref);
839 annotation.modified = Some("D:20230101120000Z".to_string());
840
841 let dict = annotation.to_dict();
842
843 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
845 assert_eq!(
846 dict.get("Subtype"),
847 Some(&Object::Name("Underline".to_string()))
848 );
849
850 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
852 assert_eq!(rect_array.len(), 4);
853 assert_eq!(rect_array[0], Object::Real(25.0));
854 assert_eq!(rect_array[1], Object::Real(25.0));
855 assert_eq!(rect_array[2], Object::Real(125.0));
856 assert_eq!(rect_array[3], Object::Real(75.0));
857 } else {
858 panic!("Rect should be an array");
859 }
860
861 assert_eq!(
863 dict.get("Contents"),
864 Some(&Object::String("Underline annotation".to_string()))
865 );
866 assert_eq!(
867 dict.get("NM"),
868 Some(&Object::String("underline_1".to_string()))
869 );
870 assert_eq!(
871 dict.get("M"),
872 Some(&Object::String("D:20230101120000Z".to_string()))
873 );
874 assert_eq!(dict.get("P"), Some(&Object::Reference(page_ref)));
875
876 assert_eq!(dict.get("F"), Some(&Object::Integer(68))); if let Some(Object::Dictionary(bs_dict)) = dict.get("BS") {
881 assert_eq!(bs_dict.get("W"), Some(&Object::Real(2.0)));
882 assert_eq!(bs_dict.get("S"), Some(&Object::Name("D".to_string())));
883 if let Some(Object::Array(dash_array)) = bs_dict.get("D") {
884 assert_eq!(dash_array.len(), 2);
885 assert_eq!(dash_array[0], Object::Real(4.0));
886 assert_eq!(dash_array[1], Object::Real(2.0));
887 }
888 } else {
889 panic!("BS should be a dictionary");
890 }
891
892 if let Some(Object::Array(color_array)) = dict.get("C") {
894 assert_eq!(color_array.len(), 4);
895 assert_eq!(color_array[0], Object::Real(0.1));
896 assert_eq!(color_array[1], Object::Real(0.2));
897 assert_eq!(color_array[2], Object::Real(0.3));
898 assert_eq!(color_array[3], Object::Real(0.4));
899 } else {
900 panic!("C should be an array");
901 }
902 }
903
904 #[test]
905 fn test_annotation_color_variants() {
906 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 50.0));
907
908 let rgb_annotation =
910 Annotation::new(AnnotationType::Square, rect).with_color(Color::Rgb(1.0, 0.5, 0.0));
911 let rgb_dict = rgb_annotation.to_dict();
912 if let Some(Object::Array(color)) = rgb_dict.get("C") {
913 assert_eq!(color.len(), 3);
914 assert_eq!(color[0], Object::Real(1.0));
915 assert_eq!(color[1], Object::Real(0.5));
916 assert_eq!(color[2], Object::Real(0.0));
917 }
918
919 let gray_annotation =
921 Annotation::new(AnnotationType::Circle, rect).with_color(Color::Gray(0.7));
922 let gray_dict = gray_annotation.to_dict();
923 if let Some(Object::Array(color)) = gray_dict.get("C") {
924 assert_eq!(color.len(), 1);
925 assert_eq!(color[0], Object::Real(0.7));
926 }
927
928 let cmyk_annotation = Annotation::new(AnnotationType::Polygon, rect)
930 .with_color(Color::Cmyk(0.2, 0.4, 0.6, 0.1));
931 let cmyk_dict = cmyk_annotation.to_dict();
932 if let Some(Object::Array(color)) = cmyk_dict.get("C") {
933 assert_eq!(color.len(), 4);
934 assert_eq!(color[0], Object::Real(0.2));
935 assert_eq!(color[1], Object::Real(0.4));
936 assert_eq!(color[2], Object::Real(0.6));
937 assert_eq!(color[3], Object::Real(0.1));
938 }
939 }
940
941 #[test]
942 fn test_annotation_without_optional_fields() {
943 let rect = Rectangle::new(Point::new(10.0, 10.0), Point::new(60.0, 40.0));
944 let annotation = Annotation::new(AnnotationType::Line, rect);
945
946 let dict = annotation.to_dict();
947
948 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
950 assert_eq!(dict.get("Subtype"), Some(&Object::Name("Line".to_string())));
951 assert!(dict.get("Rect").is_some());
952
953 assert!(dict.get("Contents").is_none());
955 assert!(dict.get("NM").is_none());
956 assert!(dict.get("M").is_none());
957 assert!(dict.get("P").is_none());
958 assert!(dict.get("BS").is_none());
959 assert!(dict.get("C").is_none());
960
961 assert_eq!(dict.get("F"), Some(&Object::Integer(4))); }
965
966 #[test]
967 fn test_annotation_manager_comprehensive() {
968 let mut manager = AnnotationManager::new();
969 let page1_ref = ObjectReference::new(10, 0);
970 let page2_ref = ObjectReference::new(20, 0);
971
972 let rect1 = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 50.0));
973 let rect2 = Rectangle::new(Point::new(100.0, 100.0), Point::new(150.0, 150.0));
974 let rect3 = Rectangle::new(Point::new(200.0, 200.0), Point::new(250.0, 250.0));
975
976 let annotation1 = Annotation::new(AnnotationType::Text, rect1).with_contents("Text 1");
977 let annotation2 = Annotation::new(AnnotationType::Link, rect2).with_contents("Link 1");
978 let annotation3 =
979 Annotation::new(AnnotationType::Highlight, rect3).with_contents("Highlight 1");
980
981 let annot1_ref = manager.add_annotation(page1_ref, annotation1);
983 let annot2_ref = manager.add_annotation(page1_ref, annotation2);
984 let annot3_ref = manager.add_annotation(page2_ref, annotation3);
985
986 assert_eq!(annot1_ref.number(), 1);
988 assert_eq!(annot2_ref.number(), 2);
989 assert_eq!(annot3_ref.number(), 3);
990
991 let page1_annotations = manager.get_page_annotations(&page1_ref).unwrap();
993 assert_eq!(page1_annotations.len(), 2);
994 assert_eq!(page1_annotations[0].annotation_type, AnnotationType::Text);
995 assert_eq!(page1_annotations[1].annotation_type, AnnotationType::Link);
996 assert_eq!(page1_annotations[0].page, Some(page1_ref));
997 assert_eq!(page1_annotations[1].page, Some(page1_ref));
998
999 let page2_annotations = manager.get_page_annotations(&page2_ref).unwrap();
1001 assert_eq!(page2_annotations.len(), 1);
1002 assert_eq!(
1003 page2_annotations[0].annotation_type,
1004 AnnotationType::Highlight
1005 );
1006 assert_eq!(page2_annotations[0].page, Some(page2_ref));
1007
1008 let page3_ref = ObjectReference::new(30, 0);
1010 assert!(manager.get_page_annotations(&page3_ref).is_none());
1011
1012 let all_annotations = manager.all_annotations();
1014 assert_eq!(all_annotations.len(), 2); assert!(all_annotations.contains_key(&page1_ref));
1016 assert!(all_annotations.contains_key(&page2_ref));
1017 }
1018
1019 #[test]
1020 fn test_annotation_manager_debug_default() {
1021 let manager = AnnotationManager::new();
1022 let debug_str = format!("{manager:?}");
1023 assert!(debug_str.contains("AnnotationManager"));
1024
1025 let default_manager = AnnotationManager::default();
1026 assert_eq!(default_manager.next_id, 1);
1027 assert!(default_manager.annotations.is_empty());
1028 }
1029
1030 #[test]
1031 fn test_annotation_properties_dictionary() {
1032 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 100.0));
1033 let mut annotation = Annotation::new(AnnotationType::Widget, rect);
1034
1035 annotation
1037 .properties
1038 .set("CustomProp1", Object::String("Value1".to_string()));
1039 annotation
1040 .properties
1041 .set("CustomProp2", Object::Integer(42));
1042 annotation
1043 .properties
1044 .set("CustomProp3", Object::Boolean(true));
1045
1046 let dict = annotation.to_dict();
1047
1048 assert_eq!(
1050 dict.get("CustomProp1"),
1051 Some(&Object::String("Value1".to_string()))
1052 );
1053 assert_eq!(dict.get("CustomProp2"), Some(&Object::Integer(42)));
1054 assert_eq!(dict.get("CustomProp3"), Some(&Object::Boolean(true)));
1055 }
1056
1057 #[test]
1058 fn test_annotation_edge_cases() {
1059 let rect = Rectangle::new(Point::new(-10.0, -20.0), Point::new(10.0, 20.0));
1060
1061 let annotation = Annotation::new(AnnotationType::Ink, rect).with_contents("");
1063 let dict = annotation.to_dict();
1064 assert_eq!(dict.get("Contents"), Some(&Object::String("".to_string())));
1065
1066 let long_content = "a".repeat(1000);
1068 let annotation =
1069 Annotation::new(AnnotationType::Sound, rect).with_contents(long_content.clone());
1070 let dict = annotation.to_dict();
1071 assert_eq!(dict.get("Contents"), Some(&Object::String(long_content)));
1072
1073 let annotation = Annotation::new(AnnotationType::Movie, rect)
1075 .with_name("test@#$%^&*()_+-=[]{}|;':\",./<>?");
1076 let dict = annotation.to_dict();
1077 assert_eq!(
1078 dict.get("NM"),
1079 Some(&Object::String(
1080 "test@#$%^&*()_+-=[]{}|;':\",./<>?".to_string()
1081 ))
1082 );
1083 }
1084
1085 #[test]
1086 fn test_annotation_manager_empty() {
1087 let manager = AnnotationManager::new();
1088
1089 assert!(manager.all_annotations().is_empty());
1091
1092 let page_ref = ObjectReference::new(999, 0);
1094 assert!(manager.get_page_annotations(&page_ref).is_none());
1095 }
1096
1097 #[test]
1098 fn test_annotation_manager_large_scale() {
1099 let mut manager = AnnotationManager::new();
1100 let num_pages = 100;
1101 let annotations_per_page = 50;
1102
1103 for page_num in 1..=num_pages {
1105 let page_ref = ObjectReference::new(page_num, 0);
1106
1107 for annot_num in 0..annotations_per_page {
1108 let rect = Rectangle::new(
1109 Point::new(annot_num as f64 * 10.0, page_num as f64 * 10.0),
1110 Point::new((annot_num + 1) as f64 * 10.0, (page_num + 1) as f64 * 10.0),
1111 );
1112 let annotation = Annotation::new(AnnotationType::Text, rect);
1113 manager.add_annotation(page_ref, annotation);
1114 }
1115 }
1116
1117 assert_eq!(manager.all_annotations().len(), num_pages as usize);
1119
1120 for page_num in 1..=num_pages {
1121 let page_ref = ObjectReference::new(page_num, 0);
1122 let annotations = manager.get_page_annotations(&page_ref).unwrap();
1123 assert_eq!(annotations.len(), annotations_per_page);
1124 }
1125 }
1126
1127 #[test]
1128 fn test_annotation_to_dict_minimal() {
1129 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(1.0, 1.0));
1130 let annotation = Annotation::new(AnnotationType::Circle, rect);
1131
1132 let dict = annotation.to_dict();
1133
1134 assert!(dict.contains_key("Type"));
1136 assert!(dict.contains_key("Subtype"));
1137 assert!(dict.contains_key("Rect"));
1138 assert!(dict.contains_key("F")); assert!(!dict.contains_key("Contents"));
1142 assert!(!dict.contains_key("NM"));
1143 assert!(!dict.contains_key("M"));
1144 assert!(!dict.contains_key("BS"));
1145 assert!(!dict.contains_key("C"));
1146 assert!(!dict.contains_key("P"));
1147 }
1148
1149 #[test]
1150 fn test_annotation_with_all_fields() {
1151 let rect = Rectangle::new(Point::new(10.0, 20.0), Point::new(110.0, 70.0));
1152 let border = BorderStyle {
1153 width: 2.5,
1154 style: BorderStyleType::Inset,
1155 dash_pattern: Some(vec![6.0, 3.0, 2.0, 3.0]),
1156 };
1157 let flags = AnnotationFlags {
1158 invisible: false,
1159 hidden: false,
1160 print: true,
1161 no_zoom: true,
1162 no_rotate: false,
1163 no_view: false,
1164 read_only: true,
1165 locked: true,
1166 locked_contents: false,
1167 };
1168
1169 let mut annotation = Annotation::new(AnnotationType::Polygon, rect)
1170 .with_contents("Polygon annotation with all fields")
1171 .with_name("polygon_001")
1172 .with_color(Color::Cmyk(0.1, 0.2, 0.3, 0.0))
1173 .with_border(border)
1174 .with_flags(flags);
1175
1176 annotation.modified = Some("D:20240101120000Z".to_string());
1177 annotation.page = Some(ObjectReference::new(7, 0));
1178 annotation.properties.set(
1179 "Vertices",
1180 Object::Array(vec![
1181 Object::Real(10.0),
1182 Object::Real(20.0),
1183 Object::Real(60.0),
1184 Object::Real(20.0),
1185 Object::Real(110.0),
1186 Object::Real(45.0),
1187 Object::Real(60.0),
1188 Object::Real(70.0),
1189 Object::Real(10.0),
1190 Object::Real(70.0),
1191 ]),
1192 );
1193
1194 let dict = annotation.to_dict();
1195
1196 assert!(dict.contains_key("Type"));
1198 assert!(dict.contains_key("Subtype"));
1199 assert!(dict.contains_key("Rect"));
1200 assert!(dict.contains_key("Contents"));
1201 assert!(dict.contains_key("NM"));
1202 assert!(dict.contains_key("M"));
1203 assert!(dict.contains_key("F"));
1204 assert!(dict.contains_key("BS"));
1205 assert!(dict.contains_key("C"));
1206 assert!(dict.contains_key("P"));
1207 assert!(dict.contains_key("Vertices"));
1208 }
1209
1210 #[test]
1211 fn test_annotation_rectangle_edge_cases() {
1212 let zero_rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(100.0, 100.0));
1214 let zero_annotation = Annotation::new(AnnotationType::Text, zero_rect);
1215 let dict = zero_annotation.to_dict();
1216
1217 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
1218 assert_eq!(rect_array[0], Object::Real(100.0));
1219 assert_eq!(rect_array[1], Object::Real(100.0));
1220 assert_eq!(rect_array[2], Object::Real(100.0));
1221 assert_eq!(rect_array[3], Object::Real(100.0));
1222 }
1223
1224 let neg_rect = Rectangle::new(Point::new(-50.0, -100.0), Point::new(-10.0, -20.0));
1226 let neg_annotation = Annotation::new(AnnotationType::Square, neg_rect);
1227 let dict = neg_annotation.to_dict();
1228
1229 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
1230 assert_eq!(rect_array[0], Object::Real(-50.0));
1231 assert_eq!(rect_array[1], Object::Real(-100.0));
1232 assert_eq!(rect_array[2], Object::Real(-10.0));
1233 assert_eq!(rect_array[3], Object::Real(-20.0));
1234 }
1235
1236 let large_rect = Rectangle::new(Point::new(1e10, 1e10), Point::new(1e11, 1e11));
1238 let large_annotation = Annotation::new(AnnotationType::Circle, large_rect);
1239 let dict = large_annotation.to_dict();
1240
1241 assert!(dict.contains_key("Rect"));
1242 }
1243
1244 #[test]
1245 fn test_border_style_edge_cases() {
1246 let zero_border = BorderStyle {
1248 width: 0.0,
1249 style: BorderStyleType::Solid,
1250 dash_pattern: None,
1251 };
1252 assert_eq!(zero_border.width, 0.0);
1253
1254 let large_border = BorderStyle {
1256 width: 1000.0,
1257 style: BorderStyleType::Dashed,
1258 dash_pattern: Some(vec![100.0, 50.0]),
1259 };
1260 assert_eq!(large_border.width, 1000.0);
1261
1262 let empty_dash = BorderStyle {
1264 width: 1.0,
1265 style: BorderStyleType::Dashed,
1266 dash_pattern: Some(vec![]),
1267 };
1268 assert!(empty_dash.dash_pattern.as_ref().unwrap().is_empty());
1269
1270 let single_dash = BorderStyle {
1272 width: 1.0,
1273 style: BorderStyleType::Dashed,
1274 dash_pattern: Some(vec![5.0]),
1275 };
1276 assert_eq!(single_dash.dash_pattern.as_ref().unwrap().len(), 1);
1277 }
1278
1279 #[test]
1280 fn test_annotation_contents_edge_cases() {
1281 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
1282
1283 let long_string = "a".repeat(10000);
1285 let long_annotation =
1286 Annotation::new(AnnotationType::FreeText, rect).with_contents(long_string.clone());
1287 assert_eq!(long_annotation.contents, Some(long_string));
1288
1289 let unicode_contents = "Hello 世界 🌍 مرحبا мир";
1291 let unicode_annotation =
1292 Annotation::new(AnnotationType::Text, rect).with_contents(unicode_contents);
1293 assert_eq!(
1294 unicode_annotation.contents,
1295 Some(unicode_contents.to_string())
1296 );
1297
1298 let control_contents = "Line1\nLine2\tTabbed\rCarriage\0Null";
1300 let control_annotation =
1301 Annotation::new(AnnotationType::Text, rect).with_contents(control_contents);
1302 assert_eq!(
1303 control_annotation.contents,
1304 Some(control_contents.to_string())
1305 );
1306 }
1307
1308 #[test]
1309 fn test_annotation_manager_references() {
1310 let mut manager = AnnotationManager::new();
1311 let page1 = ObjectReference::new(10, 0);
1312 let page2 = ObjectReference::new(10, 1); let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 100.0));
1315
1316 let annot1 = Annotation::new(AnnotationType::Text, rect);
1318 let annot2 = Annotation::new(AnnotationType::Link, rect);
1319
1320 manager.add_annotation(page1, annot1);
1321 manager.add_annotation(page2, annot2);
1322
1323 let page1_annotations = manager.get_page_annotations(&page1).unwrap();
1325 let page2_annotations = manager.get_page_annotations(&page2).unwrap();
1326
1327 assert_eq!(page1_annotations.len(), 1);
1328 assert_eq!(page2_annotations.len(), 1);
1329 assert_eq!(page1_annotations[0].annotation_type, AnnotationType::Text);
1330 assert_eq!(page2_annotations[0].annotation_type, AnnotationType::Link);
1331 }
1332
1333 #[test]
1334 fn test_annotation_type_exhaustive() {
1335 let type_name_pairs = vec![
1337 (AnnotationType::Text, "Text"),
1338 (AnnotationType::Link, "Link"),
1339 (AnnotationType::FreeText, "FreeText"),
1340 (AnnotationType::Line, "Line"),
1341 (AnnotationType::Square, "Square"),
1342 (AnnotationType::Circle, "Circle"),
1343 (AnnotationType::Polygon, "Polygon"),
1344 (AnnotationType::PolyLine, "PolyLine"),
1345 (AnnotationType::Highlight, "Highlight"),
1346 (AnnotationType::Underline, "Underline"),
1347 (AnnotationType::Squiggly, "Squiggly"),
1348 (AnnotationType::StrikeOut, "StrikeOut"),
1349 (AnnotationType::Stamp, "Stamp"),
1350 (AnnotationType::Caret, "Caret"),
1351 (AnnotationType::Ink, "Ink"),
1352 (AnnotationType::Popup, "Popup"),
1353 (AnnotationType::FileAttachment, "FileAttachment"),
1354 (AnnotationType::Sound, "Sound"),
1355 (AnnotationType::Movie, "Movie"),
1356 (AnnotationType::Widget, "Widget"),
1357 (AnnotationType::Screen, "Screen"),
1358 (AnnotationType::PrinterMark, "PrinterMark"),
1359 (AnnotationType::TrapNet, "TrapNet"),
1360 (AnnotationType::Watermark, "Watermark"),
1361 ];
1362
1363 for (annotation_type, expected_name) in type_name_pairs {
1364 assert_eq!(annotation_type.pdf_name(), expected_name);
1365
1366 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(10.0, 10.0));
1368 let annotation = Annotation::new(annotation_type, rect);
1369 let dict = annotation.to_dict();
1370
1371 assert_eq!(
1372 dict.get("Subtype"),
1373 Some(&Object::Name(expected_name.to_string()))
1374 );
1375 }
1376 }
1377
1378 #[test]
1379 fn test_annotation_flags_bit_positions() {
1380 let flag_bit_tests = vec![
1382 (
1383 AnnotationFlags {
1384 invisible: true,
1385 ..Default::default()
1386 },
1387 0,
1388 ),
1389 (
1390 AnnotationFlags {
1391 hidden: true,
1392 ..Default::default()
1393 },
1394 1,
1395 ),
1396 (
1397 AnnotationFlags {
1398 print: true,
1399 ..Default::default()
1400 },
1401 2,
1402 ),
1403 (
1404 AnnotationFlags {
1405 no_zoom: true,
1406 ..Default::default()
1407 },
1408 3,
1409 ),
1410 (
1411 AnnotationFlags {
1412 no_rotate: true,
1413 ..Default::default()
1414 },
1415 4,
1416 ),
1417 (
1418 AnnotationFlags {
1419 no_view: true,
1420 ..Default::default()
1421 },
1422 5,
1423 ),
1424 (
1425 AnnotationFlags {
1426 read_only: true,
1427 ..Default::default()
1428 },
1429 6,
1430 ),
1431 (
1432 AnnotationFlags {
1433 locked: true,
1434 ..Default::default()
1435 },
1436 7,
1437 ),
1438 (
1439 AnnotationFlags {
1440 locked_contents: true,
1441 ..Default::default()
1442 },
1443 9,
1444 ),
1445 ];
1446
1447 for (flags, expected_bit) in flag_bit_tests {
1448 let value = flags.to_flags();
1449 assert_eq!(value, 1u32 << expected_bit);
1450 }
1451 }
1452
1453 #[test]
1454 fn test_annotation_manager_concurrent_additions() {
1455 let mut manager = AnnotationManager::new();
1456 let page_ref = ObjectReference::new(1, 0);
1457
1458 let mut refs = Vec::new();
1460 for i in 0..100 {
1461 let rect = Rectangle::new(
1462 Point::new(i as f64, i as f64),
1463 Point::new((i + 10) as f64, (i + 10) as f64),
1464 );
1465 let annotation = Annotation::new(AnnotationType::Text, rect)
1466 .with_contents(format!("Annotation {i}"));
1467 let annot_ref = manager.add_annotation(page_ref, annotation);
1468 refs.push(annot_ref);
1469 }
1470
1471 for (i, annot_ref) in refs.iter().enumerate() {
1473 assert_eq!(annot_ref.number(), (i + 1) as u32);
1474 assert_eq!(annot_ref.generation(), 0);
1475 }
1476
1477 let annotations = manager.get_page_annotations(&page_ref).unwrap();
1479 assert_eq!(annotations.len(), 100);
1480 }
1481
1482 #[test]
1483 fn test_annotation_builder_pattern_comprehensive() {
1484 let rect = Rectangle::new(Point::new(50.0, 100.0), Point::new(250.0, 200.0));
1485
1486 let annotation = Annotation::new(AnnotationType::FileAttachment, rect)
1488 .with_contents("Attached document")
1489 .with_name("attachment_001")
1490 .with_color(Color::Rgb(0.8, 0.2, 0.2))
1491 .with_border(BorderStyle {
1492 width: 1.5,
1493 style: BorderStyleType::Solid,
1494 dash_pattern: None,
1495 })
1496 .with_flags(AnnotationFlags {
1497 print: true,
1498 read_only: true,
1499 ..Default::default()
1500 });
1501
1502 assert_eq!(annotation.contents, Some("Attached document".to_string()));
1504 assert_eq!(annotation.name, Some("attachment_001".to_string()));
1505 assert!(matches!(annotation.color, Some(Color::Rgb(0.8, 0.2, 0.2))));
1506 assert!(annotation.border.is_some());
1507 assert!(annotation.flags.print);
1508 assert!(annotation.flags.read_only);
1509 }
1510
1511 #[test]
1512 fn test_annotation_dict_color_precision() {
1513 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 50.0));
1514
1515 let colors = vec![
1517 Color::Gray(0.123456789),
1518 Color::Rgb(0.111111111, 0.222222222, 0.333333333),
1519 Color::Cmyk(0.1234, 0.2345, 0.3456, 0.4567),
1520 ];
1521
1522 for color in colors {
1523 let annotation = Annotation::new(AnnotationType::Square, rect).with_color(color);
1524 let dict = annotation.to_dict();
1525
1526 if let Some(Object::Array(color_array)) = dict.get("C") {
1527 for component in color_array {
1529 assert!(matches!(component, Object::Real(_)));
1530 }
1531 }
1532 }
1533 }
1534}