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(crate) field_parent: Option<ObjectReference>,
235 pub properties: Dictionary,
237}
238
239impl Annotation {
240 pub fn new(annotation_type: AnnotationType, rect: Rectangle) -> Self {
242 Self {
243 annotation_type,
244 rect,
245 contents: None,
246 subject: None,
247 name: None,
248 modified: None,
249 flags: AnnotationFlags {
250 print: true,
251 ..Default::default()
252 },
253 border: None,
254 color: None,
255 page: None,
256 field_parent: None,
257 properties: Dictionary::new(),
258 }
259 }
260
261 pub fn with_contents(mut self, contents: impl Into<String>) -> Self {
263 self.contents = Some(contents.into());
264 self
265 }
266
267 pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
269 self.subject = Some(subject.into());
270 self
271 }
272
273 pub fn with_name(mut self, name: impl Into<String>) -> Self {
275 self.name = Some(name.into());
276 self
277 }
278
279 pub fn with_color(mut self, color: Color) -> Self {
281 self.color = Some(color);
282 self
283 }
284
285 pub fn with_border(mut self, border: BorderStyle) -> Self {
287 self.border = Some(border);
288 self
289 }
290
291 pub fn with_flags(mut self, flags: AnnotationFlags) -> Self {
293 self.flags = flags;
294 self
295 }
296
297 pub fn set_field_dict(&mut self, field_dict: Dictionary) {
299 for (key, value) in field_dict.iter() {
301 self.properties.set(key, value.clone());
302 }
303 }
304
305 pub fn set_field_parent(&mut self, parent: ObjectReference) {
328 debug_assert!(
329 matches!(self.annotation_type, AnnotationType::Widget),
330 "Annotation::set_field_parent should only be called on Widget annotations, got {:?}",
331 self.annotation_type
332 );
333 self.field_parent = Some(parent);
334 }
335
336 pub fn to_dict(&self) -> Dictionary {
338 let mut dict = Dictionary::new();
339
340 dict.set("Type", Object::Name("Annot".to_string()));
342 dict.set(
343 "Subtype",
344 Object::Name(self.annotation_type.pdf_name().to_string()),
345 );
346
347 let rect_array = vec![
349 Object::Real(self.rect.lower_left.x),
350 Object::Real(self.rect.lower_left.y),
351 Object::Real(self.rect.upper_right.x),
352 Object::Real(self.rect.upper_right.y),
353 ];
354 dict.set("Rect", Object::Array(rect_array));
355
356 if let Some(ref contents) = self.contents {
358 dict.set("Contents", Object::String(contents.clone()));
359 }
360
361 if let Some(ref subject) = self.subject {
362 dict.set("Subj", Object::String(subject.clone()));
363 }
364
365 if let Some(ref name) = self.name {
366 dict.set("NM", Object::String(name.clone()));
367 }
368
369 if let Some(ref modified) = self.modified {
370 dict.set("M", Object::String(modified.clone()));
371 }
372
373 let flags = self.flags.to_flags();
375 if flags != 0 {
376 dict.set("F", Object::Integer(flags as i64));
377 }
378
379 if let Some(ref border) = self.border {
381 let mut bs_dict = Dictionary::new();
382 bs_dict.set("W", Object::Real(border.width));
383 bs_dict.set("S", Object::Name(border.style.pdf_name().to_string()));
384
385 if let Some(ref dash) = border.dash_pattern {
386 let dash_array: Vec<Object> = dash.iter().map(|&d| Object::Real(d)).collect();
387 bs_dict.set("D", Object::Array(dash_array));
388 }
389
390 dict.set("BS", Object::Dictionary(bs_dict));
391 }
392
393 if let Some(ref color) = self.color {
395 let c = match color {
396 Color::Rgb(r, g, b) => vec![Object::Real(*r), Object::Real(*g), Object::Real(*b)],
397 Color::Gray(g) => vec![Object::Real(*g)],
398 Color::Cmyk(c, m, y, k) => vec![
399 Object::Real(*c),
400 Object::Real(*m),
401 Object::Real(*y),
402 Object::Real(*k),
403 ],
404 };
405 dict.set("C", Object::Array(c));
406 }
407
408 if let Some(page) = self.page {
410 dict.set("P", Object::Reference(page));
411 }
412
413 if matches!(self.annotation_type, AnnotationType::Widget) {
424 if let Some(parent_ref) = self.field_parent {
425 dict.set("Parent", Object::Reference(parent_ref));
426 }
427 }
428
429 for (key, value) in self.properties.iter() {
431 dict.set(key, value.clone());
432 }
433
434 dict
435 }
436}
437
438#[derive(Debug)]
440pub struct AnnotationManager {
441 annotations: HashMap<ObjectReference, Vec<Annotation>>,
443 next_id: u32,
445}
446
447impl AnnotationManager {
448 pub fn new() -> Self {
450 Self {
451 annotations: HashMap::new(),
452 next_id: 1,
453 }
454 }
455
456 pub fn add_annotation(
458 &mut self,
459 page_ref: ObjectReference,
460 mut annotation: Annotation,
461 ) -> ObjectReference {
462 annotation.page = Some(page_ref);
463
464 let annot_ref = ObjectReference::new(self.next_id, 0);
465 self.next_id += 1;
466
467 self.annotations
468 .entry(page_ref)
469 .or_default()
470 .push(annotation);
471
472 annot_ref
473 }
474
475 pub fn get_page_annotations(&self, page_ref: &ObjectReference) -> Option<&Vec<Annotation>> {
477 self.annotations.get(page_ref)
478 }
479
480 pub fn all_annotations(&self) -> &HashMap<ObjectReference, Vec<Annotation>> {
482 &self.annotations
483 }
484}
485
486impl Default for AnnotationManager {
487 fn default() -> Self {
488 Self::new()
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495 use crate::geometry::Point;
496
497 #[test]
498 fn test_annotation_type() {
499 assert_eq!(AnnotationType::Text.pdf_name(), "Text");
500 assert_eq!(AnnotationType::Link.pdf_name(), "Link");
501 assert_eq!(AnnotationType::Highlight.pdf_name(), "Highlight");
502 }
503
504 #[test]
505 fn test_annotation_flags() {
506 let flags = AnnotationFlags {
507 print: true,
508 read_only: true,
509 ..Default::default()
510 };
511
512 assert_eq!(flags.to_flags(), 68); }
514
515 #[test]
516 fn test_border_style() {
517 let border = BorderStyle {
518 width: 2.0,
519 style: BorderStyleType::Dashed,
520 dash_pattern: Some(vec![3.0, 1.0]),
521 };
522
523 assert_eq!(border.width, 2.0);
524 assert_eq!(border.style.pdf_name(), "D");
525 }
526
527 #[test]
528 fn test_annotation_creation() {
529 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 200.0));
530
531 let annotation = Annotation::new(AnnotationType::Text, rect)
532 .with_contents("Test annotation")
533 .with_color(Color::Rgb(1.0, 0.0, 0.0));
534
535 assert_eq!(annotation.annotation_type, AnnotationType::Text);
536 assert_eq!(annotation.contents, Some("Test annotation".to_string()));
537 assert!(annotation.color.is_some());
538 }
539
540 #[test]
541 fn test_annotation_to_dict() {
542 let rect = Rectangle::new(Point::new(50.0, 50.0), Point::new(150.0, 150.0));
543
544 let annotation =
545 Annotation::new(AnnotationType::Square, rect).with_contents("Square annotation");
546
547 let dict = annotation.to_dict();
548 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
549 assert_eq!(
550 dict.get("Subtype"),
551 Some(&Object::Name("Square".to_string()))
552 );
553 assert!(dict.get("Rect").is_some());
554 assert_eq!(
555 dict.get("Contents"),
556 Some(&Object::String("Square annotation".to_string()))
557 );
558 }
559
560 #[test]
561 fn test_annotation_manager() {
562 let mut manager = AnnotationManager::new();
563 let page_ref = ObjectReference::new(1, 0);
564
565 let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 200.0));
566
567 let annotation = Annotation::new(AnnotationType::Text, rect);
568 let annot_ref = manager.add_annotation(page_ref, annotation);
569
570 assert_eq!(annot_ref.number(), 1);
571 assert!(manager.get_page_annotations(&page_ref).is_some());
572 assert_eq!(manager.get_page_annotations(&page_ref).unwrap().len(), 1);
573 }
574
575 #[test]
576 fn test_all_annotation_types() {
577 let types = [
578 AnnotationType::Text,
579 AnnotationType::Link,
580 AnnotationType::FreeText,
581 AnnotationType::Line,
582 AnnotationType::Square,
583 AnnotationType::Circle,
584 AnnotationType::Polygon,
585 AnnotationType::PolyLine,
586 AnnotationType::Highlight,
587 AnnotationType::Underline,
588 AnnotationType::Squiggly,
589 AnnotationType::StrikeOut,
590 AnnotationType::Stamp,
591 AnnotationType::Caret,
592 AnnotationType::Ink,
593 AnnotationType::Popup,
594 AnnotationType::FileAttachment,
595 AnnotationType::Sound,
596 AnnotationType::Movie,
597 AnnotationType::Widget,
598 AnnotationType::Screen,
599 AnnotationType::PrinterMark,
600 AnnotationType::TrapNet,
601 AnnotationType::Watermark,
602 ];
603
604 let expected_names = [
605 "Text",
606 "Link",
607 "FreeText",
608 "Line",
609 "Square",
610 "Circle",
611 "Polygon",
612 "PolyLine",
613 "Highlight",
614 "Underline",
615 "Squiggly",
616 "StrikeOut",
617 "Stamp",
618 "Caret",
619 "Ink",
620 "Popup",
621 "FileAttachment",
622 "Sound",
623 "Movie",
624 "Widget",
625 "Screen",
626 "PrinterMark",
627 "TrapNet",
628 "Watermark",
629 ];
630
631 for (annotation_type, expected_name) in types.iter().zip(expected_names.iter()) {
632 assert_eq!(annotation_type.pdf_name(), *expected_name);
633 }
634 }
635
636 #[test]
637 fn test_annotation_type_debug_clone_partial_eq() {
638 let annotation_type = AnnotationType::Highlight;
639 let debug_str = format!("{annotation_type:?}");
640 assert!(debug_str.contains("Highlight"));
641
642 let cloned = annotation_type;
643 assert_eq!(annotation_type, cloned);
644
645 assert_eq!(AnnotationType::Text, AnnotationType::Text);
646 assert_ne!(AnnotationType::Text, AnnotationType::Link);
647 }
648
649 #[test]
650 fn test_annotation_flags_comprehensive() {
651 let default_flags = AnnotationFlags::default();
653 assert_eq!(default_flags.to_flags(), 0);
654
655 let invisible_flag = AnnotationFlags {
657 invisible: true,
658 ..Default::default()
659 };
660 assert_eq!(invisible_flag.to_flags(), 1); let hidden_flag = AnnotationFlags {
663 hidden: true,
664 ..Default::default()
665 };
666 assert_eq!(hidden_flag.to_flags(), 2); let print_flag = AnnotationFlags {
669 print: true,
670 ..Default::default()
671 };
672 assert_eq!(print_flag.to_flags(), 4); let no_zoom_flag = AnnotationFlags {
675 no_zoom: true,
676 ..Default::default()
677 };
678 assert_eq!(no_zoom_flag.to_flags(), 8); let no_rotate_flag = AnnotationFlags {
681 no_rotate: true,
682 ..Default::default()
683 };
684 assert_eq!(no_rotate_flag.to_flags(), 16); let no_view_flag = AnnotationFlags {
687 no_view: true,
688 ..Default::default()
689 };
690 assert_eq!(no_view_flag.to_flags(), 32); let read_only_flag = AnnotationFlags {
693 read_only: true,
694 ..Default::default()
695 };
696 assert_eq!(read_only_flag.to_flags(), 64); let locked_flag = AnnotationFlags {
699 locked: true,
700 ..Default::default()
701 };
702 assert_eq!(locked_flag.to_flags(), 128); let locked_contents_flag = AnnotationFlags {
705 locked_contents: true,
706 ..Default::default()
707 };
708 assert_eq!(locked_contents_flag.to_flags(), 512); }
710
711 #[test]
712 fn test_annotation_flags_combined() {
713 let combined_flags = AnnotationFlags {
714 print: true,
715 read_only: true,
716 locked: true,
717 ..Default::default()
718 };
719 assert_eq!(combined_flags.to_flags(), 4 + 64 + 128); let all_flags = AnnotationFlags {
723 invisible: true,
724 hidden: true,
725 print: true,
726 no_zoom: true,
727 no_rotate: true,
728 no_view: true,
729 read_only: true,
730 locked: true,
731 locked_contents: true,
732 };
733 assert_eq!(
734 all_flags.to_flags(),
735 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 512
736 );
737 }
738
739 #[test]
740 fn test_annotation_flags_debug_clone() {
741 let flags = AnnotationFlags {
742 print: true,
743 read_only: true,
744 ..Default::default()
745 };
746 let debug_str = format!("{flags:?}");
747 assert!(debug_str.contains("AnnotationFlags"));
748
749 let cloned = flags;
750 assert_eq!(flags.print, cloned.print);
751 assert_eq!(flags.read_only, cloned.read_only);
752 assert_eq!(flags.to_flags(), cloned.to_flags());
753 }
754
755 #[test]
756 fn test_border_style_types() {
757 assert_eq!(BorderStyleType::Solid.pdf_name(), "S");
758 assert_eq!(BorderStyleType::Dashed.pdf_name(), "D");
759 assert_eq!(BorderStyleType::Beveled.pdf_name(), "B");
760 assert_eq!(BorderStyleType::Inset.pdf_name(), "I");
761 assert_eq!(BorderStyleType::Underline.pdf_name(), "U");
762 }
763
764 #[test]
765 fn test_border_style_debug_clone() {
766 let style = BorderStyleType::Dashed;
767 let debug_str = format!("{style:?}");
768 assert!(debug_str.contains("Dashed"));
769
770 let cloned = style;
771 assert_eq!(style.pdf_name(), cloned.pdf_name());
772 }
773
774 #[test]
775 fn test_border_style_default() {
776 let default_border = BorderStyle::default();
777 assert_eq!(default_border.width, 1.0);
778 assert_eq!(default_border.style.pdf_name(), "S");
779 assert!(default_border.dash_pattern.is_none());
780 }
781
782 #[test]
783 fn test_border_style_with_dash_pattern() {
784 let dashed_border = BorderStyle {
785 width: 1.5,
786 style: BorderStyleType::Dashed,
787 dash_pattern: Some(vec![5.0, 2.0, 3.0, 2.0]),
788 };
789
790 assert_eq!(dashed_border.width, 1.5);
791 assert_eq!(dashed_border.style.pdf_name(), "D");
792 assert_eq!(dashed_border.dash_pattern.as_ref().unwrap().len(), 4);
793 }
794
795 #[test]
796 fn test_border_style_debug_clone_comprehensive() {
797 let border = BorderStyle {
798 width: 2.5,
799 style: BorderStyleType::Beveled,
800 dash_pattern: Some(vec![1.0, 2.0]),
801 };
802
803 let debug_str = format!("{border:?}");
804 assert!(debug_str.contains("BorderStyle"));
805 assert!(debug_str.contains("2.5"));
806
807 let cloned = border.clone();
808 assert_eq!(border.width, cloned.width);
809 assert_eq!(border.style.pdf_name(), cloned.style.pdf_name());
810 assert_eq!(border.dash_pattern, cloned.dash_pattern);
811 }
812
813 #[test]
814 fn test_annotation_creation_comprehensive() {
815 let rect = Rectangle::new(Point::new(10.0, 20.0), Point::new(110.0, 120.0));
816
817 let annotation = Annotation::new(AnnotationType::Circle, rect);
819 assert_eq!(annotation.annotation_type, AnnotationType::Circle);
820 assert!(annotation.flags.print); assert!(annotation.contents.is_none());
822 assert!(annotation.name.is_none());
823 assert!(annotation.color.is_none());
824 assert!(annotation.border.is_none());
825 assert!(annotation.page.is_none());
826 }
827
828 #[test]
829 fn test_annotation_builder_pattern() {
830 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
831 let border = BorderStyle {
832 width: 3.0,
833 style: BorderStyleType::Inset,
834 dash_pattern: None,
835 };
836 let flags = AnnotationFlags {
837 print: true,
838 no_zoom: true,
839 ..Default::default()
840 };
841
842 let annotation = Annotation::new(AnnotationType::FreeText, rect)
843 .with_contents("Free text annotation")
844 .with_name("annotation_1")
845 .with_color(Color::Rgb(0.0, 1.0, 0.0))
846 .with_border(border)
847 .with_flags(flags);
848
849 assert_eq!(
850 annotation.contents,
851 Some("Free text annotation".to_string())
852 );
853 assert_eq!(annotation.name, Some("annotation_1".to_string()));
854 assert!(matches!(annotation.color, Some(Color::Rgb(0.0, 1.0, 0.0))));
855 assert!(annotation.border.is_some());
856 assert_eq!(annotation.border.unwrap().width, 3.0);
857 assert!(annotation.flags.print);
858 assert!(annotation.flags.no_zoom);
859 }
860
861 #[test]
862 fn test_annotation_debug_clone() {
863 let rect = Rectangle::new(Point::new(50.0, 50.0), Point::new(150.0, 100.0));
864 let annotation =
865 Annotation::new(AnnotationType::Stamp, rect).with_contents("Stamp annotation");
866
867 let debug_str = format!("{annotation:?}");
868 assert!(debug_str.contains("Annotation"));
869 assert!(debug_str.contains("Stamp"));
870
871 let cloned = annotation.clone();
872 assert_eq!(annotation.annotation_type, cloned.annotation_type);
873 assert_eq!(annotation.contents, cloned.contents);
874 assert_eq!(annotation.rect.lower_left.x, cloned.rect.lower_left.x);
875 }
876
877 #[test]
878 fn test_annotation_to_dict_comprehensive() {
879 let rect = Rectangle::new(Point::new(25.0, 25.0), Point::new(125.0, 75.0));
880 let border = BorderStyle {
881 width: 2.0,
882 style: BorderStyleType::Dashed,
883 dash_pattern: Some(vec![4.0, 2.0]),
884 };
885 let flags = AnnotationFlags {
886 print: true,
887 read_only: true,
888 ..Default::default()
889 };
890 let page_ref = ObjectReference::new(5, 0);
891
892 let mut annotation = Annotation::new(AnnotationType::Underline, rect)
893 .with_contents("Underline annotation")
894 .with_name("underline_1")
895 .with_color(Color::Cmyk(0.1, 0.2, 0.3, 0.4))
896 .with_border(border)
897 .with_flags(flags);
898 annotation.page = Some(page_ref);
899 annotation.modified = Some("D:20230101120000Z".to_string());
900
901 let dict = annotation.to_dict();
902
903 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
905 assert_eq!(
906 dict.get("Subtype"),
907 Some(&Object::Name("Underline".to_string()))
908 );
909
910 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
912 assert_eq!(rect_array.len(), 4);
913 assert_eq!(rect_array[0], Object::Real(25.0));
914 assert_eq!(rect_array[1], Object::Real(25.0));
915 assert_eq!(rect_array[2], Object::Real(125.0));
916 assert_eq!(rect_array[3], Object::Real(75.0));
917 } else {
918 panic!("Rect should be an array");
919 }
920
921 assert_eq!(
923 dict.get("Contents"),
924 Some(&Object::String("Underline annotation".to_string()))
925 );
926 assert_eq!(
927 dict.get("NM"),
928 Some(&Object::String("underline_1".to_string()))
929 );
930 assert_eq!(
931 dict.get("M"),
932 Some(&Object::String("D:20230101120000Z".to_string()))
933 );
934 assert_eq!(dict.get("P"), Some(&Object::Reference(page_ref)));
935
936 assert_eq!(dict.get("F"), Some(&Object::Integer(68))); if let Some(Object::Dictionary(bs_dict)) = dict.get("BS") {
941 assert_eq!(bs_dict.get("W"), Some(&Object::Real(2.0)));
942 assert_eq!(bs_dict.get("S"), Some(&Object::Name("D".to_string())));
943 if let Some(Object::Array(dash_array)) = bs_dict.get("D") {
944 assert_eq!(dash_array.len(), 2);
945 assert_eq!(dash_array[0], Object::Real(4.0));
946 assert_eq!(dash_array[1], Object::Real(2.0));
947 }
948 } else {
949 panic!("BS should be a dictionary");
950 }
951
952 if let Some(Object::Array(color_array)) = dict.get("C") {
954 assert_eq!(color_array.len(), 4);
955 assert_eq!(color_array[0], Object::Real(0.1));
956 assert_eq!(color_array[1], Object::Real(0.2));
957 assert_eq!(color_array[2], Object::Real(0.3));
958 assert_eq!(color_array[3], Object::Real(0.4));
959 } else {
960 panic!("C should be an array");
961 }
962 }
963
964 #[test]
965 fn test_annotation_color_variants() {
966 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 50.0));
967
968 let rgb_annotation =
970 Annotation::new(AnnotationType::Square, rect).with_color(Color::Rgb(1.0, 0.5, 0.0));
971 let rgb_dict = rgb_annotation.to_dict();
972 if let Some(Object::Array(color)) = rgb_dict.get("C") {
973 assert_eq!(color.len(), 3);
974 assert_eq!(color[0], Object::Real(1.0));
975 assert_eq!(color[1], Object::Real(0.5));
976 assert_eq!(color[2], Object::Real(0.0));
977 }
978
979 let gray_annotation =
981 Annotation::new(AnnotationType::Circle, rect).with_color(Color::Gray(0.7));
982 let gray_dict = gray_annotation.to_dict();
983 if let Some(Object::Array(color)) = gray_dict.get("C") {
984 assert_eq!(color.len(), 1);
985 assert_eq!(color[0], Object::Real(0.7));
986 }
987
988 let cmyk_annotation = Annotation::new(AnnotationType::Polygon, rect)
990 .with_color(Color::Cmyk(0.2, 0.4, 0.6, 0.1));
991 let cmyk_dict = cmyk_annotation.to_dict();
992 if let Some(Object::Array(color)) = cmyk_dict.get("C") {
993 assert_eq!(color.len(), 4);
994 assert_eq!(color[0], Object::Real(0.2));
995 assert_eq!(color[1], Object::Real(0.4));
996 assert_eq!(color[2], Object::Real(0.6));
997 assert_eq!(color[3], Object::Real(0.1));
998 }
999 }
1000
1001 #[test]
1002 fn test_annotation_without_optional_fields() {
1003 let rect = Rectangle::new(Point::new(10.0, 10.0), Point::new(60.0, 40.0));
1004 let annotation = Annotation::new(AnnotationType::Line, rect);
1005
1006 let dict = annotation.to_dict();
1007
1008 assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
1010 assert_eq!(dict.get("Subtype"), Some(&Object::Name("Line".to_string())));
1011 assert!(dict.get("Rect").is_some());
1012
1013 assert!(dict.get("Contents").is_none());
1015 assert!(dict.get("NM").is_none());
1016 assert!(dict.get("M").is_none());
1017 assert!(dict.get("P").is_none());
1018 assert!(dict.get("BS").is_none());
1019 assert!(dict.get("C").is_none());
1020
1021 assert_eq!(dict.get("F"), Some(&Object::Integer(4))); }
1025
1026 #[test]
1027 fn test_annotation_manager_comprehensive() {
1028 let mut manager = AnnotationManager::new();
1029 let page1_ref = ObjectReference::new(10, 0);
1030 let page2_ref = ObjectReference::new(20, 0);
1031
1032 let rect1 = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 50.0));
1033 let rect2 = Rectangle::new(Point::new(100.0, 100.0), Point::new(150.0, 150.0));
1034 let rect3 = Rectangle::new(Point::new(200.0, 200.0), Point::new(250.0, 250.0));
1035
1036 let annotation1 = Annotation::new(AnnotationType::Text, rect1).with_contents("Text 1");
1037 let annotation2 = Annotation::new(AnnotationType::Link, rect2).with_contents("Link 1");
1038 let annotation3 =
1039 Annotation::new(AnnotationType::Highlight, rect3).with_contents("Highlight 1");
1040
1041 let annot1_ref = manager.add_annotation(page1_ref, annotation1);
1043 let annot2_ref = manager.add_annotation(page1_ref, annotation2);
1044 let annot3_ref = manager.add_annotation(page2_ref, annotation3);
1045
1046 assert_eq!(annot1_ref.number(), 1);
1048 assert_eq!(annot2_ref.number(), 2);
1049 assert_eq!(annot3_ref.number(), 3);
1050
1051 let page1_annotations = manager.get_page_annotations(&page1_ref).unwrap();
1053 assert_eq!(page1_annotations.len(), 2);
1054 assert_eq!(page1_annotations[0].annotation_type, AnnotationType::Text);
1055 assert_eq!(page1_annotations[1].annotation_type, AnnotationType::Link);
1056 assert_eq!(page1_annotations[0].page, Some(page1_ref));
1057 assert_eq!(page1_annotations[1].page, Some(page1_ref));
1058
1059 let page2_annotations = manager.get_page_annotations(&page2_ref).unwrap();
1061 assert_eq!(page2_annotations.len(), 1);
1062 assert_eq!(
1063 page2_annotations[0].annotation_type,
1064 AnnotationType::Highlight
1065 );
1066 assert_eq!(page2_annotations[0].page, Some(page2_ref));
1067
1068 let page3_ref = ObjectReference::new(30, 0);
1070 assert!(manager.get_page_annotations(&page3_ref).is_none());
1071
1072 let all_annotations = manager.all_annotations();
1074 assert_eq!(all_annotations.len(), 2); assert!(all_annotations.contains_key(&page1_ref));
1076 assert!(all_annotations.contains_key(&page2_ref));
1077 }
1078
1079 #[test]
1080 fn test_annotation_manager_debug_default() {
1081 let manager = AnnotationManager::new();
1082 let debug_str = format!("{manager:?}");
1083 assert!(debug_str.contains("AnnotationManager"));
1084
1085 let default_manager = AnnotationManager::default();
1086 assert_eq!(default_manager.next_id, 1);
1087 assert!(default_manager.annotations.is_empty());
1088 }
1089
1090 #[test]
1091 fn test_annotation_properties_dictionary() {
1092 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 100.0));
1093 let mut annotation = Annotation::new(AnnotationType::Widget, rect);
1094
1095 annotation
1097 .properties
1098 .set("CustomProp1", Object::String("Value1".to_string()));
1099 annotation
1100 .properties
1101 .set("CustomProp2", Object::Integer(42));
1102 annotation
1103 .properties
1104 .set("CustomProp3", Object::Boolean(true));
1105
1106 let dict = annotation.to_dict();
1107
1108 assert_eq!(
1110 dict.get("CustomProp1"),
1111 Some(&Object::String("Value1".to_string()))
1112 );
1113 assert_eq!(dict.get("CustomProp2"), Some(&Object::Integer(42)));
1114 assert_eq!(dict.get("CustomProp3"), Some(&Object::Boolean(true)));
1115 }
1116
1117 #[test]
1118 fn test_annotation_edge_cases() {
1119 let rect = Rectangle::new(Point::new(-10.0, -20.0), Point::new(10.0, 20.0));
1120
1121 let annotation = Annotation::new(AnnotationType::Ink, rect).with_contents("");
1123 let dict = annotation.to_dict();
1124 assert_eq!(dict.get("Contents"), Some(&Object::String("".to_string())));
1125
1126 let long_content = "a".repeat(1000);
1128 let annotation =
1129 Annotation::new(AnnotationType::Sound, rect).with_contents(long_content.clone());
1130 let dict = annotation.to_dict();
1131 assert_eq!(dict.get("Contents"), Some(&Object::String(long_content)));
1132
1133 let annotation = Annotation::new(AnnotationType::Movie, rect)
1135 .with_name("test@#$%^&*()_+-=[]{}|;':\",./<>?");
1136 let dict = annotation.to_dict();
1137 assert_eq!(
1138 dict.get("NM"),
1139 Some(&Object::String(
1140 "test@#$%^&*()_+-=[]{}|;':\",./<>?".to_string()
1141 ))
1142 );
1143 }
1144
1145 #[test]
1146 fn test_annotation_manager_empty() {
1147 let manager = AnnotationManager::new();
1148
1149 assert!(manager.all_annotations().is_empty());
1151
1152 let page_ref = ObjectReference::new(999, 0);
1154 assert!(manager.get_page_annotations(&page_ref).is_none());
1155 }
1156
1157 #[test]
1158 fn test_annotation_manager_large_scale() {
1159 let mut manager = AnnotationManager::new();
1160 let num_pages = 100;
1161 let annotations_per_page = 50;
1162
1163 for page_num in 1..=num_pages {
1165 let page_ref = ObjectReference::new(page_num, 0);
1166
1167 for annot_num in 0..annotations_per_page {
1168 let rect = Rectangle::new(
1169 Point::new(annot_num as f64 * 10.0, page_num as f64 * 10.0),
1170 Point::new((annot_num + 1) as f64 * 10.0, (page_num + 1) as f64 * 10.0),
1171 );
1172 let annotation = Annotation::new(AnnotationType::Text, rect);
1173 manager.add_annotation(page_ref, annotation);
1174 }
1175 }
1176
1177 assert_eq!(manager.all_annotations().len(), num_pages as usize);
1179
1180 for page_num in 1..=num_pages {
1181 let page_ref = ObjectReference::new(page_num, 0);
1182 let annotations = manager.get_page_annotations(&page_ref).unwrap();
1183 assert_eq!(annotations.len(), annotations_per_page);
1184 }
1185 }
1186
1187 #[test]
1188 fn test_annotation_to_dict_minimal() {
1189 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(1.0, 1.0));
1190 let annotation = Annotation::new(AnnotationType::Circle, rect);
1191
1192 let dict = annotation.to_dict();
1193
1194 assert!(dict.contains_key("Type"));
1196 assert!(dict.contains_key("Subtype"));
1197 assert!(dict.contains_key("Rect"));
1198 assert!(dict.contains_key("F")); assert!(!dict.contains_key("Contents"));
1202 assert!(!dict.contains_key("NM"));
1203 assert!(!dict.contains_key("M"));
1204 assert!(!dict.contains_key("BS"));
1205 assert!(!dict.contains_key("C"));
1206 assert!(!dict.contains_key("P"));
1207 }
1208
1209 #[test]
1210 fn test_annotation_with_all_fields() {
1211 let rect = Rectangle::new(Point::new(10.0, 20.0), Point::new(110.0, 70.0));
1212 let border = BorderStyle {
1213 width: 2.5,
1214 style: BorderStyleType::Inset,
1215 dash_pattern: Some(vec![6.0, 3.0, 2.0, 3.0]),
1216 };
1217 let flags = AnnotationFlags {
1218 invisible: false,
1219 hidden: false,
1220 print: true,
1221 no_zoom: true,
1222 no_rotate: false,
1223 no_view: false,
1224 read_only: true,
1225 locked: true,
1226 locked_contents: false,
1227 };
1228
1229 let mut annotation = Annotation::new(AnnotationType::Polygon, rect)
1230 .with_contents("Polygon annotation with all fields")
1231 .with_name("polygon_001")
1232 .with_color(Color::Cmyk(0.1, 0.2, 0.3, 0.0))
1233 .with_border(border)
1234 .with_flags(flags);
1235
1236 annotation.modified = Some("D:20240101120000Z".to_string());
1237 annotation.page = Some(ObjectReference::new(7, 0));
1238 annotation.properties.set(
1239 "Vertices",
1240 Object::Array(vec![
1241 Object::Real(10.0),
1242 Object::Real(20.0),
1243 Object::Real(60.0),
1244 Object::Real(20.0),
1245 Object::Real(110.0),
1246 Object::Real(45.0),
1247 Object::Real(60.0),
1248 Object::Real(70.0),
1249 Object::Real(10.0),
1250 Object::Real(70.0),
1251 ]),
1252 );
1253
1254 let dict = annotation.to_dict();
1255
1256 assert!(dict.contains_key("Type"));
1258 assert!(dict.contains_key("Subtype"));
1259 assert!(dict.contains_key("Rect"));
1260 assert!(dict.contains_key("Contents"));
1261 assert!(dict.contains_key("NM"));
1262 assert!(dict.contains_key("M"));
1263 assert!(dict.contains_key("F"));
1264 assert!(dict.contains_key("BS"));
1265 assert!(dict.contains_key("C"));
1266 assert!(dict.contains_key("P"));
1267 assert!(dict.contains_key("Vertices"));
1268 }
1269
1270 #[test]
1271 fn test_annotation_rectangle_edge_cases() {
1272 let zero_rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(100.0, 100.0));
1274 let zero_annotation = Annotation::new(AnnotationType::Text, zero_rect);
1275 let dict = zero_annotation.to_dict();
1276
1277 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
1278 assert_eq!(rect_array[0], Object::Real(100.0));
1279 assert_eq!(rect_array[1], Object::Real(100.0));
1280 assert_eq!(rect_array[2], Object::Real(100.0));
1281 assert_eq!(rect_array[3], Object::Real(100.0));
1282 }
1283
1284 let neg_rect = Rectangle::new(Point::new(-50.0, -100.0), Point::new(-10.0, -20.0));
1286 let neg_annotation = Annotation::new(AnnotationType::Square, neg_rect);
1287 let dict = neg_annotation.to_dict();
1288
1289 if let Some(Object::Array(rect_array)) = dict.get("Rect") {
1290 assert_eq!(rect_array[0], Object::Real(-50.0));
1291 assert_eq!(rect_array[1], Object::Real(-100.0));
1292 assert_eq!(rect_array[2], Object::Real(-10.0));
1293 assert_eq!(rect_array[3], Object::Real(-20.0));
1294 }
1295
1296 let large_rect = Rectangle::new(Point::new(1e10, 1e10), Point::new(1e11, 1e11));
1298 let large_annotation = Annotation::new(AnnotationType::Circle, large_rect);
1299 let dict = large_annotation.to_dict();
1300
1301 assert!(dict.contains_key("Rect"));
1302 }
1303
1304 #[test]
1305 fn test_border_style_edge_cases() {
1306 let zero_border = BorderStyle {
1308 width: 0.0,
1309 style: BorderStyleType::Solid,
1310 dash_pattern: None,
1311 };
1312 assert_eq!(zero_border.width, 0.0);
1313
1314 let large_border = BorderStyle {
1316 width: 1000.0,
1317 style: BorderStyleType::Dashed,
1318 dash_pattern: Some(vec![100.0, 50.0]),
1319 };
1320 assert_eq!(large_border.width, 1000.0);
1321
1322 let empty_dash = BorderStyle {
1324 width: 1.0,
1325 style: BorderStyleType::Dashed,
1326 dash_pattern: Some(vec![]),
1327 };
1328 assert!(empty_dash.dash_pattern.as_ref().unwrap().is_empty());
1329
1330 let single_dash = BorderStyle {
1332 width: 1.0,
1333 style: BorderStyleType::Dashed,
1334 dash_pattern: Some(vec![5.0]),
1335 };
1336 assert_eq!(single_dash.dash_pattern.as_ref().unwrap().len(), 1);
1337 }
1338
1339 #[test]
1340 fn test_annotation_contents_edge_cases() {
1341 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
1342
1343 let long_string = "a".repeat(10000);
1345 let long_annotation =
1346 Annotation::new(AnnotationType::FreeText, rect).with_contents(long_string.clone());
1347 assert_eq!(long_annotation.contents, Some(long_string));
1348
1349 let unicode_contents = "Hello 世界 🌍 مرحبا мир";
1351 let unicode_annotation =
1352 Annotation::new(AnnotationType::Text, rect).with_contents(unicode_contents);
1353 assert_eq!(
1354 unicode_annotation.contents,
1355 Some(unicode_contents.to_string())
1356 );
1357
1358 let control_contents = "Line1\nLine2\tTabbed\rCarriage\0Null";
1360 let control_annotation =
1361 Annotation::new(AnnotationType::Text, rect).with_contents(control_contents);
1362 assert_eq!(
1363 control_annotation.contents,
1364 Some(control_contents.to_string())
1365 );
1366 }
1367
1368 #[test]
1369 fn test_annotation_manager_references() {
1370 let mut manager = AnnotationManager::new();
1371 let page1 = ObjectReference::new(10, 0);
1372 let page2 = ObjectReference::new(10, 1); let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 100.0));
1375
1376 let annot1 = Annotation::new(AnnotationType::Text, rect);
1378 let annot2 = Annotation::new(AnnotationType::Link, rect);
1379
1380 manager.add_annotation(page1, annot1);
1381 manager.add_annotation(page2, annot2);
1382
1383 let page1_annotations = manager.get_page_annotations(&page1).unwrap();
1385 let page2_annotations = manager.get_page_annotations(&page2).unwrap();
1386
1387 assert_eq!(page1_annotations.len(), 1);
1388 assert_eq!(page2_annotations.len(), 1);
1389 assert_eq!(page1_annotations[0].annotation_type, AnnotationType::Text);
1390 assert_eq!(page2_annotations[0].annotation_type, AnnotationType::Link);
1391 }
1392
1393 #[test]
1394 fn test_annotation_type_exhaustive() {
1395 let type_name_pairs = vec![
1397 (AnnotationType::Text, "Text"),
1398 (AnnotationType::Link, "Link"),
1399 (AnnotationType::FreeText, "FreeText"),
1400 (AnnotationType::Line, "Line"),
1401 (AnnotationType::Square, "Square"),
1402 (AnnotationType::Circle, "Circle"),
1403 (AnnotationType::Polygon, "Polygon"),
1404 (AnnotationType::PolyLine, "PolyLine"),
1405 (AnnotationType::Highlight, "Highlight"),
1406 (AnnotationType::Underline, "Underline"),
1407 (AnnotationType::Squiggly, "Squiggly"),
1408 (AnnotationType::StrikeOut, "StrikeOut"),
1409 (AnnotationType::Stamp, "Stamp"),
1410 (AnnotationType::Caret, "Caret"),
1411 (AnnotationType::Ink, "Ink"),
1412 (AnnotationType::Popup, "Popup"),
1413 (AnnotationType::FileAttachment, "FileAttachment"),
1414 (AnnotationType::Sound, "Sound"),
1415 (AnnotationType::Movie, "Movie"),
1416 (AnnotationType::Widget, "Widget"),
1417 (AnnotationType::Screen, "Screen"),
1418 (AnnotationType::PrinterMark, "PrinterMark"),
1419 (AnnotationType::TrapNet, "TrapNet"),
1420 (AnnotationType::Watermark, "Watermark"),
1421 ];
1422
1423 for (annotation_type, expected_name) in type_name_pairs {
1424 assert_eq!(annotation_type.pdf_name(), expected_name);
1425
1426 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(10.0, 10.0));
1428 let annotation = Annotation::new(annotation_type, rect);
1429 let dict = annotation.to_dict();
1430
1431 assert_eq!(
1432 dict.get("Subtype"),
1433 Some(&Object::Name(expected_name.to_string()))
1434 );
1435 }
1436 }
1437
1438 #[test]
1439 fn test_annotation_flags_bit_positions() {
1440 let flag_bit_tests = vec![
1442 (
1443 AnnotationFlags {
1444 invisible: true,
1445 ..Default::default()
1446 },
1447 0,
1448 ),
1449 (
1450 AnnotationFlags {
1451 hidden: true,
1452 ..Default::default()
1453 },
1454 1,
1455 ),
1456 (
1457 AnnotationFlags {
1458 print: true,
1459 ..Default::default()
1460 },
1461 2,
1462 ),
1463 (
1464 AnnotationFlags {
1465 no_zoom: true,
1466 ..Default::default()
1467 },
1468 3,
1469 ),
1470 (
1471 AnnotationFlags {
1472 no_rotate: true,
1473 ..Default::default()
1474 },
1475 4,
1476 ),
1477 (
1478 AnnotationFlags {
1479 no_view: true,
1480 ..Default::default()
1481 },
1482 5,
1483 ),
1484 (
1485 AnnotationFlags {
1486 read_only: true,
1487 ..Default::default()
1488 },
1489 6,
1490 ),
1491 (
1492 AnnotationFlags {
1493 locked: true,
1494 ..Default::default()
1495 },
1496 7,
1497 ),
1498 (
1499 AnnotationFlags {
1500 locked_contents: true,
1501 ..Default::default()
1502 },
1503 9,
1504 ),
1505 ];
1506
1507 for (flags, expected_bit) in flag_bit_tests {
1508 let value = flags.to_flags();
1509 assert_eq!(value, 1u32 << expected_bit);
1510 }
1511 }
1512
1513 #[test]
1514 fn test_annotation_manager_concurrent_additions() {
1515 let mut manager = AnnotationManager::new();
1516 let page_ref = ObjectReference::new(1, 0);
1517
1518 let mut refs = Vec::new();
1520 for i in 0..100 {
1521 let rect = Rectangle::new(
1522 Point::new(i as f64, i as f64),
1523 Point::new((i + 10) as f64, (i + 10) as f64),
1524 );
1525 let annotation = Annotation::new(AnnotationType::Text, rect)
1526 .with_contents(format!("Annotation {i}"));
1527 let annot_ref = manager.add_annotation(page_ref, annotation);
1528 refs.push(annot_ref);
1529 }
1530
1531 for (i, annot_ref) in refs.iter().enumerate() {
1533 assert_eq!(annot_ref.number(), (i + 1) as u32);
1534 assert_eq!(annot_ref.generation(), 0);
1535 }
1536
1537 let annotations = manager.get_page_annotations(&page_ref).unwrap();
1539 assert_eq!(annotations.len(), 100);
1540 }
1541
1542 #[test]
1543 fn test_annotation_builder_pattern_comprehensive() {
1544 let rect = Rectangle::new(Point::new(50.0, 100.0), Point::new(250.0, 200.0));
1545
1546 let annotation = Annotation::new(AnnotationType::FileAttachment, rect)
1548 .with_contents("Attached document")
1549 .with_name("attachment_001")
1550 .with_color(Color::Rgb(0.8, 0.2, 0.2))
1551 .with_border(BorderStyle {
1552 width: 1.5,
1553 style: BorderStyleType::Solid,
1554 dash_pattern: None,
1555 })
1556 .with_flags(AnnotationFlags {
1557 print: true,
1558 read_only: true,
1559 ..Default::default()
1560 });
1561
1562 assert_eq!(annotation.contents, Some("Attached document".to_string()));
1564 assert_eq!(annotation.name, Some("attachment_001".to_string()));
1565 assert!(matches!(annotation.color, Some(Color::Rgb(0.8, 0.2, 0.2))));
1566 assert!(annotation.border.is_some());
1567 assert!(annotation.flags.print);
1568 assert!(annotation.flags.read_only);
1569 }
1570
1571 #[test]
1572 fn test_annotation_dict_color_precision() {
1573 let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 50.0));
1574
1575 let colors = vec![
1577 Color::Gray(0.123456789),
1578 Color::Rgb(0.111111111, 0.222222222, 0.333333333),
1579 Color::Cmyk(0.1234, 0.2345, 0.3456, 0.4567),
1580 ];
1581
1582 for color in colors {
1583 let annotation = Annotation::new(AnnotationType::Square, rect).with_color(color);
1584 let dict = annotation.to_dict();
1585
1586 if let Some(Object::Array(color_array)) = dict.get("C") {
1587 for component in color_array {
1589 assert!(matches!(component, Object::Real(_)));
1590 }
1591 }
1592 }
1593 }
1594}