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    /// Parent form field reference for widget annotations that live as
224    /// children of an /AcroForm field (ISO 32000-1 §12.7.3.1).
225    ///
226    /// When set, serialization writes `/Parent <ref>` into the annotation
227    /// dictionary so that the widget is linked to the field it belongs to.
228    /// Only meaningful for `AnnotationType::Widget`; ignored otherwise.
229    ///
230    /// Scoped to `pub(crate)` because setting a `/Parent` on a non-Widget
231    /// annotation is meaningless and (for encrypted / PDF/A output) risky.
232    /// External callers must go through [`Annotation::set_field_parent`],
233    /// which validates the annotation type.
234    pub(crate) field_parent: Option<ObjectReference>,
235    /// Additional properties specific to annotation type
236    pub properties: Dictionary,
237}
238
239impl Annotation {
240    /// Create a new annotation
241    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    /// Set contents
262    pub fn with_contents(mut self, contents: impl Into<String>) -> Self {
263        self.contents = Some(contents.into());
264        self
265    }
266
267    /// Set subject
268    pub fn with_subject(mut self, subject: impl Into<String>) -> Self {
269        self.subject = Some(subject.into());
270        self
271    }
272
273    /// Set name
274    pub fn with_name(mut self, name: impl Into<String>) -> Self {
275        self.name = Some(name.into());
276        self
277    }
278
279    /// Set color
280    pub fn with_color(mut self, color: Color) -> Self {
281        self.color = Some(color);
282        self
283    }
284
285    /// Set border
286    pub fn with_border(mut self, border: BorderStyle) -> Self {
287        self.border = Some(border);
288        self
289    }
290
291    /// Set flags
292    pub fn with_flags(mut self, flags: AnnotationFlags) -> Self {
293        self.flags = flags;
294        self
295    }
296
297    /// Set field dictionary properties (for widget annotations)
298    pub fn set_field_dict(&mut self, field_dict: Dictionary) {
299        // Merge field dictionary into properties
300        for (key, value) in field_dict.iter() {
301            self.properties.set(key, value.clone());
302        }
303    }
304
305    /// Set the parent form-field reference for a Widget annotation.
306    ///
307    /// Widget annotations that live as children of an /AcroForm field
308    /// (ISO 32000-1 §12.7.3.1) must carry `/Parent` pointing at the
309    /// parent field dictionary. This setter records that relationship;
310    /// the writer remaps the placeholder to a real indirect-object id
311    /// at serialization time.
312    ///
313    /// Only meaningful for [`AnnotationType::Widget`].
314    ///
315    /// # Panics
316    ///
317    /// In **debug builds**, calling this on a non-Widget annotation
318    /// trips a `debug_assert!` — using `/Parent` on a non-Widget
319    /// annotation is a spec violation and almost always a programmer
320    /// error. In **release builds** the setter still stores the value
321    /// (no panic), but the defence-in-depth check in [`to_dict`] will
322    /// refuse to emit `/Parent` for non-Widget annotations, so a
323    /// mis-targeted call is degraded to a silent no-op rather than an
324    /// invalid PDF.
325    ///
326    /// [`to_dict`]: Annotation::to_dict
327    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    /// Convert to PDF dictionary
337    pub fn to_dict(&self) -> Dictionary {
338        let mut dict = Dictionary::new();
339
340        // Required fields
341        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        // Rectangle
348        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        // Optional fields
357        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        // Flags
374        let flags = self.flags.to_flags();
375        if flags != 0 {
376            dict.set("F", Object::Integer(flags as i64));
377        }
378
379        // Border
380        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        // Color
394        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        // Page reference
409        if let Some(page) = self.page {
410            dict.set("P", Object::Reference(page));
411        }
412
413        // Parent form field (widget annotations only — ISO 32000-1 §12.7.3.1).
414        // Emitted before merging additional properties so any explicit
415        // `/Parent` already in `properties` takes precedence.
416        //
417        // We gate on `annotation_type == Widget` as defence in depth even
418        // though `set_field_parent` already refuses non-Widget types: if
419        // someone flipped `annotation_type` after setting `field_parent`,
420        // emitting `/Parent` on, say, a Link would produce a malformed
421        // form hierarchy that most viewers silently tolerate but ISO
422        // 32000-1 §12.7.3.1 forbids.
423        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        // Merge additional properties
430        for (key, value) in self.properties.iter() {
431            dict.set(key, value.clone());
432        }
433
434        dict
435    }
436}
437
438/// Annotation manager
439#[derive(Debug)]
440pub struct AnnotationManager {
441    /// Annotations by page
442    annotations: HashMap<ObjectReference, Vec<Annotation>>,
443    /// Next annotation ID
444    next_id: u32,
445}
446
447impl AnnotationManager {
448    /// Create a new annotation manager
449    pub fn new() -> Self {
450        Self {
451            annotations: HashMap::new(),
452            next_id: 1,
453        }
454    }
455
456    /// Add an annotation to a page
457    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    /// Get annotations for a page
476    pub fn get_page_annotations(&self, page_ref: &ObjectReference) -> Option<&Vec<Annotation>> {
477        self.annotations.get(page_ref)
478    }
479
480    /// Get all annotations
481    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); // bits 2 and 6 set
513    }
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        // Test default flags
652        let default_flags = AnnotationFlags::default();
653        assert_eq!(default_flags.to_flags(), 0);
654
655        // Test individual flags
656        let invisible_flag = AnnotationFlags {
657            invisible: true,
658            ..Default::default()
659        };
660        assert_eq!(invisible_flag.to_flags(), 1); // bit 0
661
662        let hidden_flag = AnnotationFlags {
663            hidden: true,
664            ..Default::default()
665        };
666        assert_eq!(hidden_flag.to_flags(), 2); // bit 1
667
668        let print_flag = AnnotationFlags {
669            print: true,
670            ..Default::default()
671        };
672        assert_eq!(print_flag.to_flags(), 4); // bit 2
673
674        let no_zoom_flag = AnnotationFlags {
675            no_zoom: true,
676            ..Default::default()
677        };
678        assert_eq!(no_zoom_flag.to_flags(), 8); // bit 3
679
680        let no_rotate_flag = AnnotationFlags {
681            no_rotate: true,
682            ..Default::default()
683        };
684        assert_eq!(no_rotate_flag.to_flags(), 16); // bit 4
685
686        let no_view_flag = AnnotationFlags {
687            no_view: true,
688            ..Default::default()
689        };
690        assert_eq!(no_view_flag.to_flags(), 32); // bit 5
691
692        let read_only_flag = AnnotationFlags {
693            read_only: true,
694            ..Default::default()
695        };
696        assert_eq!(read_only_flag.to_flags(), 64); // bit 6
697
698        let locked_flag = AnnotationFlags {
699            locked: true,
700            ..Default::default()
701        };
702        assert_eq!(locked_flag.to_flags(), 128); // bit 7
703
704        let locked_contents_flag = AnnotationFlags {
705            locked_contents: true,
706            ..Default::default()
707        };
708        assert_eq!(locked_contents_flag.to_flags(), 512); // bit 9
709    }
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); // bits 2, 6, 7
720
721        // Test all flags set
722        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        // Test basic creation
818        let annotation = Annotation::new(AnnotationType::Circle, rect);
819        assert_eq!(annotation.annotation_type, AnnotationType::Circle);
820        assert!(annotation.flags.print); // Default should have print enabled
821        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        // Check required fields
904        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        // Check rectangle
911        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        // Check optional fields
922        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        // Check flags
937        assert_eq!(dict.get("F"), Some(&Object::Integer(68))); // bits 2 and 6
938
939        // Check border
940        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        // Check color
953        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        // Test RGB color
969        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        // Test Gray color
980        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        // Test CMYK color
989        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        // Should have required fields
1009        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        // Should not have optional fields when not set
1014        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        // F should not be present when flags are 0 (except default print flag)
1022        // Actually, print is set by default, so F should be present
1023        assert_eq!(dict.get("F"), Some(&Object::Integer(4))); // bit 2 for print
1024    }
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        // Add annotations to different pages
1042        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        // Check annotation references are sequential
1047        assert_eq!(annot1_ref.number(), 1);
1048        assert_eq!(annot2_ref.number(), 2);
1049        assert_eq!(annot3_ref.number(), 3);
1050
1051        // Check page 1 has 2 annotations
1052        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        // Check page 2 has 1 annotation
1060        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        // Check non-existent page
1069        let page3_ref = ObjectReference::new(30, 0);
1070        assert!(manager.get_page_annotations(&page3_ref).is_none());
1071
1072        // Check all annotations
1073        let all_annotations = manager.all_annotations();
1074        assert_eq!(all_annotations.len(), 2); // 2 pages with annotations
1075        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        // Add custom properties
1096        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        // Check that custom properties are included
1109        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        // Test with empty contents
1122        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        // Test with very long contents
1127        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        // Test with special characters in name
1134        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        // Test empty manager
1150        assert!(manager.all_annotations().is_empty());
1151
1152        // Test non-existent page
1153        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        // Add many annotations
1164        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        // Verify counts
1178        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        // Only required fields should be present
1195        assert!(dict.contains_key("Type"));
1196        assert!(dict.contains_key("Subtype"));
1197        assert!(dict.contains_key("Rect"));
1198        assert!(dict.contains_key("F")); // Default print flag
1199
1200        // Optional fields should not be present
1201        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        // Verify all fields are present
1257        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        // Test with zero-size rectangle
1273        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        // Test with negative coordinates
1285        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        // Test with very large coordinates
1297        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        // Test with zero width
1307        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        // Test with very large width
1315        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        // Test with empty dash pattern
1323        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        // Test with single value dash pattern
1331        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        // Test with very long contents
1344        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        // Test with unicode contents
1350        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        // Test with control characters
1359        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); // Same number, different generation
1373
1374        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 100.0));
1375
1376        // Add annotations to pages with same number but different generation
1377        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        // Verify they are stored separately
1384        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        // Ensure all annotation types have correct PDF names
1396        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            // Also test that it round-trips through annotation creation
1427            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        // Test each flag individually to ensure correct bit position
1441        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        // Simulate concurrent-like additions
1519        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        // Verify all references are unique and sequential
1532        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        // Verify all annotations are stored
1538        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        // Test builder pattern with all methods
1547        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        // Verify all properties were set
1563        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        // Test with precise color values
1576        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                // Verify all values are Real objects
1588                for component in color_array {
1589                    assert!(matches!(component, Object::Real(_)));
1590                }
1591            }
1592        }
1593    }
1594}