Skip to main content

oxidize_pdf/annotations/
annotation.rs

1//! Base annotation types and management
2
3use crate::geometry::Rectangle;
4use crate::graphics::Color;
5use crate::objects::{Dictionary, Object, ObjectReference};
6use std::collections::HashMap;
7
8/// Annotation types according to ISO 32000-1 Table 169
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum AnnotationType {
11    /// Text annotation (sticky note)
12    Text,
13    /// Link annotation
14    Link,
15    /// Free text annotation
16    FreeText,
17    /// Line annotation
18    Line,
19    /// Square annotation
20    Square,
21    /// Circle annotation
22    Circle,
23    /// Polygon annotation
24    Polygon,
25    /// Polyline annotation
26    PolyLine,
27    /// Highlight annotation
28    Highlight,
29    /// Underline annotation
30    Underline,
31    /// Squiggly underline annotation
32    Squiggly,
33    /// Strikeout annotation
34    StrikeOut,
35    /// Rubber stamp annotation
36    Stamp,
37    /// Caret annotation
38    Caret,
39    /// Ink annotation
40    Ink,
41    /// Popup annotation
42    Popup,
43    /// File attachment annotation
44    FileAttachment,
45    /// Sound annotation
46    Sound,
47    /// Movie annotation
48    Movie,
49    /// Widget annotation (form field)
50    Widget,
51    /// Screen annotation
52    Screen,
53    /// Printer mark annotation
54    PrinterMark,
55    /// Trap network annotation
56    TrapNet,
57    /// Watermark annotation
58    Watermark,
59}
60
61impl AnnotationType {
62    /// Get PDF subtype name
63    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/// Annotation flags according to ISO 32000-1 Section 12.5.3
94#[derive(Debug, Clone, Copy, Default)]
95pub struct AnnotationFlags {
96    /// Annotation is invisible
97    pub invisible: bool,
98    /// Annotation is hidden
99    pub hidden: bool,
100    /// Annotation should be printed
101    pub print: bool,
102    /// Annotation should not zoom
103    pub no_zoom: bool,
104    /// Annotation should not rotate
105    pub no_rotate: bool,
106    /// Annotation should not be viewed
107    pub no_view: bool,
108    /// Annotation is read-only
109    pub read_only: bool,
110    /// Annotation is locked
111    pub locked: bool,
112    /// Annotation content is locked
113    pub locked_contents: bool,
114}
115
116impl AnnotationFlags {
117    /// Convert to PDF flags integer
118    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/// Border style for annotations
152#[derive(Debug, Clone)]
153pub struct BorderStyle {
154    /// Width in points
155    pub width: f64,
156    /// Style: S (solid), D (dashed), B (beveled), I (inset), U (underline)
157    pub style: BorderStyleType,
158    /// Dash pattern for dashed borders
159    pub dash_pattern: Option<Vec<f64>>,
160}
161
162/// Border style type
163#[derive(Debug, Clone, Copy)]
164pub enum BorderStyleType {
165    /// Solid border
166    Solid,
167    /// Dashed border
168    Dashed,
169    /// Beveled border
170    Beveled,
171    /// Inset border
172    Inset,
173    /// Underline only
174    Underline,
175}
176
177impl BorderStyleType {
178    /// Get PDF name
179    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/// Base annotation structure
201#[derive(Debug, Clone)]
202pub struct Annotation {
203    /// Annotation type
204    pub annotation_type: AnnotationType,
205    /// Rectangle defining annotation position
206    pub rect: Rectangle,
207    /// Optional content text
208    pub contents: Option<String>,
209    /// Optional subject
210    pub subject: Option<String>,
211    /// Optional annotation name
212    pub name: Option<String>,
213    /// Modification date
214    pub modified: Option<String>,
215    /// Flags
216    pub flags: AnnotationFlags,
217    /// Border style
218    pub border: Option<BorderStyle>,
219    /// Color
220    pub color: Option<Color>,
221    /// Page reference (set by manager)
222    pub page: Option<ObjectReference>,
223    /// Additional properties specific to annotation type
224    pub properties: Dictionary,
225}
226
227impl Annotation {
228    /// Create a new annotation
229    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    /// Set contents
249    pub fn with_contents(mut self, contents: impl Into<String>) -> Self {
250        self.contents = Some(contents.into());
251        self
252    }
253
254    /// Set subject
255    pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
256        self.subject = Some(subject.into());
257        self
258    }
259
260    /// Set name
261    pub fn with_name(mut self, name: impl Into<String>) -> Self {
262        self.name = Some(name.into());
263        self
264    }
265
266    /// Set color
267    pub fn with_color(mut self, color: Color) -> Self {
268        self.color = Some(color);
269        self
270    }
271
272    /// Set border
273    pub fn with_border(mut self, border: BorderStyle) -> Self {
274        self.border = Some(border);
275        self
276    }
277
278    /// Set flags
279    pub fn with_flags(mut self, flags: AnnotationFlags) -> Self {
280        self.flags = flags;
281        self
282    }
283
284    /// Set field dictionary properties (for widget annotations)
285    pub fn set_field_dict(&mut self, field_dict: Dictionary) {
286        // Merge field dictionary into properties
287        for (key, value) in field_dict.iter() {
288            self.properties.set(key, value.clone());
289        }
290    }
291
292    /// Convert to PDF dictionary
293    pub fn to_dict(&self) -> Dictionary {
294        let mut dict = Dictionary::new();
295
296        // Required fields
297        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        // Rectangle
304        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        // Optional fields
313        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        // Flags
330        let flags = self.flags.to_flags();
331        if flags != 0 {
332            dict.set("F", Object::Integer(flags as i64));
333        }
334
335        // Border
336        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        // Color
350        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        // Page reference
365        if let Some(page) = self.page {
366            dict.set("P", Object::Reference(page));
367        }
368
369        // Merge additional properties
370        for (key, value) in self.properties.iter() {
371            dict.set(key, value.clone());
372        }
373
374        dict
375    }
376}
377
378/// Annotation manager
379#[derive(Debug)]
380pub struct AnnotationManager {
381    /// Annotations by page
382    annotations: HashMap<ObjectReference, Vec<Annotation>>,
383    /// Next annotation ID
384    next_id: u32,
385}
386
387impl AnnotationManager {
388    /// Create a new annotation manager
389    pub fn new() -> Self {
390        Self {
391            annotations: HashMap::new(),
392            next_id: 1,
393        }
394    }
395
396    /// Add an annotation to a page
397    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    /// Get annotations for a page
416    pub fn get_page_annotations(&self, page_ref: &ObjectReference) -> Option<&Vec<Annotation>> {
417        self.annotations.get(page_ref)
418    }
419
420    /// Get all annotations
421    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); // bits 2 and 6 set
453    }
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        // Test default flags
592        let default_flags = AnnotationFlags::default();
593        assert_eq!(default_flags.to_flags(), 0);
594
595        // Test individual flags
596        let invisible_flag = AnnotationFlags {
597            invisible: true,
598            ..Default::default()
599        };
600        assert_eq!(invisible_flag.to_flags(), 1); // bit 0
601
602        let hidden_flag = AnnotationFlags {
603            hidden: true,
604            ..Default::default()
605        };
606        assert_eq!(hidden_flag.to_flags(), 2); // bit 1
607
608        let print_flag = AnnotationFlags {
609            print: true,
610            ..Default::default()
611        };
612        assert_eq!(print_flag.to_flags(), 4); // bit 2
613
614        let no_zoom_flag = AnnotationFlags {
615            no_zoom: true,
616            ..Default::default()
617        };
618        assert_eq!(no_zoom_flag.to_flags(), 8); // bit 3
619
620        let no_rotate_flag = AnnotationFlags {
621            no_rotate: true,
622            ..Default::default()
623        };
624        assert_eq!(no_rotate_flag.to_flags(), 16); // bit 4
625
626        let no_view_flag = AnnotationFlags {
627            no_view: true,
628            ..Default::default()
629        };
630        assert_eq!(no_view_flag.to_flags(), 32); // bit 5
631
632        let read_only_flag = AnnotationFlags {
633            read_only: true,
634            ..Default::default()
635        };
636        assert_eq!(read_only_flag.to_flags(), 64); // bit 6
637
638        let locked_flag = AnnotationFlags {
639            locked: true,
640            ..Default::default()
641        };
642        assert_eq!(locked_flag.to_flags(), 128); // bit 7
643
644        let locked_contents_flag = AnnotationFlags {
645            locked_contents: true,
646            ..Default::default()
647        };
648        assert_eq!(locked_contents_flag.to_flags(), 512); // bit 9
649    }
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); // bits 2, 6, 7
660
661        // Test all flags set
662        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        // Test basic creation
758        let annotation = Annotation::new(AnnotationType::Circle, rect);
759        assert_eq!(annotation.annotation_type, AnnotationType::Circle);
760        assert!(annotation.flags.print); // Default should have print enabled
761        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        // Check required fields
844        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        // Check rectangle
851        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        // Check optional fields
862        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        // Check flags
877        assert_eq!(dict.get("F"), Some(&Object::Integer(68))); // bits 2 and 6
878
879        // Check border
880        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        // Check color
893        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        // Test RGB color
909        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        // Test Gray color
920        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        // Test CMYK color
929        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        // Should have required fields
949        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        // Should not have optional fields when not set
954        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        // F should not be present when flags are 0 (except default print flag)
962        // Actually, print is set by default, so F should be present
963        assert_eq!(dict.get("F"), Some(&Object::Integer(4))); // bit 2 for print
964    }
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        // Add annotations to different pages
982        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        // Check annotation references are sequential
987        assert_eq!(annot1_ref.number(), 1);
988        assert_eq!(annot2_ref.number(), 2);
989        assert_eq!(annot3_ref.number(), 3);
990
991        // Check page 1 has 2 annotations
992        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        // Check page 2 has 1 annotation
1000        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        // Check non-existent page
1009        let page3_ref = ObjectReference::new(30, 0);
1010        assert!(manager.get_page_annotations(&page3_ref).is_none());
1011
1012        // Check all annotations
1013        let all_annotations = manager.all_annotations();
1014        assert_eq!(all_annotations.len(), 2); // 2 pages with annotations
1015        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        // Add custom properties
1036        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        // Check that custom properties are included
1049        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        // Test with empty contents
1062        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        // Test with very long contents
1067        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        // Test with special characters in name
1074        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        // Test empty manager
1090        assert!(manager.all_annotations().is_empty());
1091
1092        // Test non-existent page
1093        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        // Add many annotations
1104        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        // Verify counts
1118        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        // Only required fields should be present
1135        assert!(dict.contains_key("Type"));
1136        assert!(dict.contains_key("Subtype"));
1137        assert!(dict.contains_key("Rect"));
1138        assert!(dict.contains_key("F")); // Default print flag
1139
1140        // Optional fields should not be present
1141        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        // Verify all fields are present
1197        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        // Test with zero-size rectangle
1213        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        // Test with negative coordinates
1225        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        // Test with very large coordinates
1237        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        // Test with zero width
1247        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        // Test with very large width
1255        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        // Test with empty dash pattern
1263        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        // Test with single value dash pattern
1271        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        // Test with very long contents
1284        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        // Test with unicode contents
1290        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        // Test with control characters
1299        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); // Same number, different generation
1313
1314        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 100.0));
1315
1316        // Add annotations to pages with same number but different generation
1317        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        // Verify they are stored separately
1324        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        // Ensure all annotation types have correct PDF names
1336        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            // Also test that it round-trips through annotation creation
1367            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        // Test each flag individually to ensure correct bit position
1381        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        // Simulate concurrent-like additions
1459        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        // Verify all references are unique and sequential
1472        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        // Verify all annotations are stored
1478        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        // Test builder pattern with all methods
1487        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        // Verify all properties were set
1503        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        // Test with precise color values
1516        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                // Verify all values are Real objects
1528                for component in color_array {
1529                    assert!(matches!(component, Object::Real(_)));
1530                }
1531            }
1532        }
1533    }
1534}