oxidize_pdf/forms/
field.rs

1//! Form field and widget definitions
2
3use crate::geometry::Rectangle;
4use crate::graphics::Color;
5use crate::objects::{Dictionary, Object};
6
7/// Field flags according to ISO 32000-1 Table 221
8#[derive(Debug, Clone, Copy, Default)]
9pub struct FieldFlags {
10    /// Field is read-only
11    pub read_only: bool,
12    /// Field is required
13    pub required: bool,
14    /// Field should not be exported
15    pub no_export: bool,
16}
17
18impl FieldFlags {
19    /// Convert to PDF flags integer
20    pub fn to_flags(&self) -> u32 {
21        let mut flags = 0u32;
22        if self.read_only {
23            flags |= 1 << 0;
24        }
25        if self.required {
26            flags |= 1 << 1;
27        }
28        if self.no_export {
29            flags |= 1 << 2;
30        }
31        flags
32    }
33}
34
35/// Field options
36#[derive(Debug, Clone, Default)]
37pub struct FieldOptions {
38    /// Field flags
39    pub flags: FieldFlags,
40    /// Default appearance string
41    pub default_appearance: Option<String>,
42    /// Quadding (justification): 0=left, 1=center, 2=right
43    pub quadding: Option<i32>,
44}
45
46/// Widget appearance settings
47#[derive(Debug, Clone)]
48pub struct WidgetAppearance {
49    /// Border color
50    pub border_color: Option<Color>,
51    /// Background color
52    pub background_color: Option<Color>,
53    /// Border width
54    pub border_width: f64,
55    /// Border style: S (solid), D (dashed), B (beveled), I (inset), U (underline)
56    pub border_style: BorderStyle,
57}
58
59/// Border style for widgets
60#[derive(Debug, Clone, Copy)]
61pub enum BorderStyle {
62    /// Solid border
63    Solid,
64    /// Dashed border
65    Dashed,
66    /// Beveled border
67    Beveled,
68    /// Inset border
69    Inset,
70    /// Underline only
71    Underline,
72}
73
74impl BorderStyle {
75    /// Get PDF name
76    pub fn pdf_name(&self) -> &'static str {
77        match self {
78            BorderStyle::Solid => "S",
79            BorderStyle::Dashed => "D",
80            BorderStyle::Beveled => "B",
81            BorderStyle::Inset => "I",
82            BorderStyle::Underline => "U",
83        }
84    }
85}
86
87impl Default for WidgetAppearance {
88    fn default() -> Self {
89        Self {
90            border_color: Some(Color::black()),
91            background_color: None,
92            border_width: 1.0,
93            border_style: BorderStyle::Solid,
94        }
95    }
96}
97
98/// Widget annotation for form field
99#[derive(Debug, Clone)]
100pub struct Widget {
101    /// Rectangle for widget position
102    pub rect: Rectangle,
103    /// Appearance settings
104    pub appearance: WidgetAppearance,
105    /// Parent field reference (will be set by FormManager)
106    pub parent: Option<String>,
107    /// Appearance streams (Normal, Rollover, Down)
108    pub appearance_streams: Option<crate::forms::AppearanceDictionary>,
109}
110
111impl Widget {
112    /// Create a new widget
113    pub fn new(rect: Rectangle) -> Self {
114        Self {
115            rect,
116            appearance: WidgetAppearance::default(),
117            parent: None,
118            appearance_streams: None,
119        }
120    }
121
122    /// Set appearance
123    pub fn with_appearance(mut self, appearance: WidgetAppearance) -> Self {
124        self.appearance = appearance;
125        self
126    }
127
128    /// Set appearance streams
129    pub fn with_appearance_streams(mut self, streams: crate::forms::AppearanceDictionary) -> Self {
130        self.appearance_streams = Some(streams);
131        self
132    }
133
134    /// Generate default appearance streams based on field type
135    pub fn generate_appearance(
136        &mut self,
137        field_type: crate::forms::FieldType,
138        value: Option<&str>,
139    ) -> crate::error::Result<()> {
140        use crate::forms::{generate_default_appearance, AppearanceDictionary, AppearanceState};
141
142        let mut app_dict = AppearanceDictionary::new();
143
144        // Generate normal appearance
145        let normal_stream = generate_default_appearance(field_type, self, value)?;
146        app_dict.set_appearance(AppearanceState::Normal, normal_stream);
147
148        // For buttons, also generate rollover and down states
149        if field_type == crate::forms::FieldType::Button {
150            let rollover_stream = generate_default_appearance(field_type, self, value)?;
151            app_dict.set_appearance(AppearanceState::Rollover, rollover_stream);
152
153            let down_stream = generate_default_appearance(field_type, self, value)?;
154            app_dict.set_appearance(AppearanceState::Down, down_stream);
155        }
156
157        self.appearance_streams = Some(app_dict);
158        Ok(())
159    }
160
161    /// Convert to annotation dictionary
162    pub fn to_annotation_dict(&self) -> Dictionary {
163        let mut dict = Dictionary::new();
164
165        // Annotation type
166        dict.set("Type", Object::Name("Annot".to_string()));
167        dict.set("Subtype", Object::Name("Widget".to_string()));
168
169        // Rectangle
170        let rect_array = vec![
171            Object::Real(self.rect.lower_left.x),
172            Object::Real(self.rect.lower_left.y),
173            Object::Real(self.rect.upper_right.x),
174            Object::Real(self.rect.upper_right.y),
175        ];
176        dict.set("Rect", Object::Array(rect_array));
177
178        // Border style
179        let mut bs_dict = Dictionary::new();
180        bs_dict.set("W", Object::Real(self.appearance.border_width));
181        bs_dict.set(
182            "S",
183            Object::Name(self.appearance.border_style.pdf_name().to_string()),
184        );
185        dict.set("BS", Object::Dictionary(bs_dict));
186
187        // Appearance characteristics
188        let mut mk_dict = Dictionary::new();
189
190        if let Some(border_color) = &self.appearance.border_color {
191            let bc = match border_color {
192                Color::Rgb(r, g, b) => vec![Object::Real(*r), Object::Real(*g), Object::Real(*b)],
193                Color::Gray(g) => vec![Object::Real(*g)],
194                Color::Cmyk(c, m, y, k) => vec![
195                    Object::Real(*c),
196                    Object::Real(*m),
197                    Object::Real(*y),
198                    Object::Real(*k),
199                ],
200            };
201            mk_dict.set("BC", Object::Array(bc));
202        }
203
204        if let Some(bg_color) = &self.appearance.background_color {
205            let bg = match bg_color {
206                Color::Rgb(r, g, b) => vec![Object::Real(*r), Object::Real(*g), Object::Real(*b)],
207                Color::Gray(g) => vec![Object::Real(*g)],
208                Color::Cmyk(c, m, y, k) => vec![
209                    Object::Real(*c),
210                    Object::Real(*m),
211                    Object::Real(*y),
212                    Object::Real(*k),
213                ],
214            };
215            mk_dict.set("BG", Object::Array(bg));
216        }
217
218        dict.set("MK", Object::Dictionary(mk_dict));
219
220        // Flags - print flag
221        dict.set("F", Object::Integer(4));
222
223        // Add appearance dictionary if present
224        if let Some(ref app_streams) = self.appearance_streams {
225            dict.set("AP", Object::Dictionary(app_streams.to_dict()));
226        }
227
228        dict
229    }
230}
231
232/// Base field trait
233pub trait Field {
234    /// Get field name
235    fn name(&self) -> &str;
236
237    /// Get field type
238    fn field_type(&self) -> &'static str;
239
240    /// Convert to dictionary
241    fn to_dict(&self) -> Dictionary;
242}
243
244/// Form field with widget
245#[derive(Debug, Clone)]
246pub struct FormField {
247    /// Field dictionary
248    pub field_dict: Dictionary,
249    /// Associated widgets
250    pub widgets: Vec<Widget>,
251}
252
253impl FormField {
254    /// Create from field dictionary
255    pub fn new(field_dict: Dictionary) -> Self {
256        Self {
257            field_dict,
258            widgets: Vec::new(),
259        }
260    }
261
262    /// Add a widget
263    pub fn add_widget(&mut self, widget: Widget) {
264        self.widgets.push(widget);
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use crate::geometry::Point;
272
273    #[test]
274    fn test_field_flags() {
275        let flags = FieldFlags {
276            read_only: true,
277            required: true,
278            no_export: false,
279        };
280
281        assert_eq!(flags.to_flags(), 3); // bits 0 and 1 set
282    }
283
284    #[test]
285    fn test_border_style() {
286        assert_eq!(BorderStyle::Solid.pdf_name(), "S");
287        assert_eq!(BorderStyle::Dashed.pdf_name(), "D");
288        assert_eq!(BorderStyle::Beveled.pdf_name(), "B");
289        assert_eq!(BorderStyle::Inset.pdf_name(), "I");
290        assert_eq!(BorderStyle::Underline.pdf_name(), "U");
291    }
292
293    #[test]
294    fn test_widget_creation() {
295        let rect = Rectangle::new(Point::new(100.0, 100.0), Point::new(200.0, 120.0));
296
297        let widget = Widget::new(rect);
298        assert_eq!(widget.rect.lower_left.x, 100.0);
299        assert_eq!(widget.appearance.border_width, 1.0);
300    }
301
302    #[test]
303    fn test_widget_annotation_dict() {
304        let rect = Rectangle::new(Point::new(50.0, 50.0), Point::new(150.0, 70.0));
305
306        let appearance = WidgetAppearance {
307            border_color: Some(Color::Rgb(0.0, 0.0, 1.0)),
308            background_color: Some(Color::Gray(0.9)),
309            border_width: 2.0,
310            border_style: BorderStyle::Solid,
311        };
312
313        let widget = Widget::new(rect).with_appearance(appearance);
314        let dict = widget.to_annotation_dict();
315
316        assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
317        assert_eq!(
318            dict.get("Subtype"),
319            Some(&Object::Name("Widget".to_string()))
320        );
321        assert!(dict.get("Rect").is_some());
322        assert!(dict.get("BS").is_some());
323        assert!(dict.get("MK").is_some());
324    }
325
326    #[test]
327    fn test_field_options_default() {
328        let options = FieldOptions::default();
329        assert!(!options.flags.read_only);
330        assert!(!options.flags.required);
331        assert!(!options.flags.no_export);
332        assert!(options.default_appearance.is_none());
333        assert!(options.quadding.is_none());
334    }
335
336    #[test]
337    fn test_field_flags_all_combinations() {
338        // Test all flag combinations
339        let flag_combos = [
340            (false, false, false, 0),
341            (true, false, false, 1),
342            (false, true, false, 2),
343            (false, false, true, 4),
344            (true, true, false, 3),
345            (true, false, true, 5),
346            (false, true, true, 6),
347            (true, true, true, 7),
348        ];
349
350        for (read_only, required, no_export, expected) in flag_combos {
351            let flags = FieldFlags {
352                read_only,
353                required,
354                no_export,
355            };
356            assert_eq!(
357                flags.to_flags(),
358                expected,
359                "Failed for read_only={read_only}, required={required}, no_export={no_export}"
360            );
361        }
362    }
363
364    #[test]
365    fn test_field_flags_debug_clone_default() {
366        let flags = FieldFlags::default();
367        let debug_str = format!("{flags:?}");
368        assert!(debug_str.contains("FieldFlags"));
369
370        let cloned = flags;
371        assert_eq!(flags.read_only, cloned.read_only);
372        assert_eq!(flags.required, cloned.required);
373        assert_eq!(flags.no_export, cloned.no_export);
374
375        // Test that Copy trait works
376        let copied = flags;
377        assert_eq!(flags.read_only, copied.read_only);
378    }
379
380    #[test]
381    fn test_border_style_debug_clone_copy() {
382        let style = BorderStyle::Solid;
383        let debug_str = format!("{style:?}");
384        assert!(debug_str.contains("Solid"));
385
386        let cloned = style;
387        assert_eq!(style.pdf_name(), cloned.pdf_name());
388
389        // Test Copy trait
390        let copied = style;
391        assert_eq!(style.pdf_name(), copied.pdf_name());
392    }
393
394    #[test]
395    fn test_all_border_styles() {
396        let styles = [
397            (BorderStyle::Solid, "S"),
398            (BorderStyle::Dashed, "D"),
399            (BorderStyle::Beveled, "B"),
400            (BorderStyle::Inset, "I"),
401            (BorderStyle::Underline, "U"),
402        ];
403
404        for (style, expected) in styles {
405            assert_eq!(style.pdf_name(), expected);
406        }
407    }
408
409    #[test]
410    fn test_widget_appearance_default() {
411        let appearance = WidgetAppearance::default();
412        assert_eq!(appearance.border_color, Some(Color::black()));
413        assert_eq!(appearance.background_color, None);
414        assert_eq!(appearance.border_width, 1.0);
415        match appearance.border_style {
416            BorderStyle::Solid => {}
417            _ => panic!("Expected solid border style"),
418        }
419    }
420
421    #[test]
422    fn test_widget_appearance_debug_clone() {
423        let appearance = WidgetAppearance {
424            border_color: Some(Color::red()),
425            background_color: Some(Color::gray(0.5)),
426            border_width: 2.5,
427            border_style: BorderStyle::Dashed,
428        };
429
430        let debug_str = format!("{appearance:?}");
431        assert!(debug_str.contains("WidgetAppearance"));
432
433        let cloned = appearance.clone();
434        assert_eq!(appearance.border_color, cloned.border_color);
435        assert_eq!(appearance.background_color, cloned.background_color);
436        assert_eq!(appearance.border_width, cloned.border_width);
437    }
438
439    #[test]
440    fn test_widget_debug_clone() {
441        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
442        let widget = Widget::new(rect);
443
444        let debug_str = format!("{widget:?}");
445        assert!(debug_str.contains("Widget"));
446
447        let cloned = widget.clone();
448        assert_eq!(widget.rect.lower_left.x, cloned.rect.lower_left.x);
449        assert_eq!(widget.parent, cloned.parent);
450    }
451
452    #[test]
453    fn test_widget_with_parent() {
454        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
455        let mut widget = Widget::new(rect);
456        widget.parent = Some("TextField1".to_string());
457
458        assert_eq!(widget.parent, Some("TextField1".to_string()));
459    }
460
461    #[test]
462    fn test_widget_annotation_dict_rgb_colors() {
463        let rect = Rectangle::new(Point::new(10.0, 20.0), Point::new(110.0, 40.0));
464        let appearance = WidgetAppearance {
465            border_color: Some(Color::rgb(1.0, 0.0, 0.0)),
466            background_color: Some(Color::rgb(0.0, 1.0, 0.0)),
467            border_width: 1.5,
468            border_style: BorderStyle::Dashed,
469        };
470
471        let widget = Widget::new(rect).with_appearance(appearance);
472        let dict = widget.to_annotation_dict();
473
474        // Check rectangle array
475        if let Some(Object::Array(rect_array)) = dict.get("Rect") {
476            assert_eq!(rect_array.len(), 4);
477            assert_eq!(rect_array[0], Object::Real(10.0));
478            assert_eq!(rect_array[1], Object::Real(20.0));
479            assert_eq!(rect_array[2], Object::Real(110.0));
480            assert_eq!(rect_array[3], Object::Real(40.0));
481        } else {
482            panic!("Expected Rect array");
483        }
484
485        // Check border style
486        if let Some(Object::Dictionary(bs_dict)) = dict.get("BS") {
487            assert_eq!(bs_dict.get("W"), Some(&Object::Real(1.5)));
488            assert_eq!(bs_dict.get("S"), Some(&Object::Name("D".to_string())));
489        } else {
490            panic!("Expected BS dictionary");
491        }
492
493        // Check appearance characteristics
494        if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
495            // Check border color
496            if let Some(Object::Array(bc_array)) = mk_dict.get("BC") {
497                assert_eq!(bc_array.len(), 3);
498                assert_eq!(bc_array[0], Object::Real(1.0));
499                assert_eq!(bc_array[1], Object::Real(0.0));
500                assert_eq!(bc_array[2], Object::Real(0.0));
501            } else {
502                panic!("Expected BC array");
503            }
504
505            // Check background color
506            if let Some(Object::Array(bg_array)) = mk_dict.get("BG") {
507                assert_eq!(bg_array.len(), 3);
508                assert_eq!(bg_array[0], Object::Real(0.0));
509                assert_eq!(bg_array[1], Object::Real(1.0));
510                assert_eq!(bg_array[2], Object::Real(0.0));
511            } else {
512                panic!("Expected BG array");
513            }
514        } else {
515            panic!("Expected MK dictionary");
516        }
517
518        // Check flags
519        assert_eq!(dict.get("F"), Some(&Object::Integer(4)));
520    }
521
522    #[test]
523    fn test_widget_annotation_dict_gray_colors() {
524        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 25.0));
525        let appearance = WidgetAppearance {
526            border_color: Some(Color::gray(0.3)),
527            background_color: Some(Color::gray(0.9)),
528            border_width: 0.5,
529            border_style: BorderStyle::Beveled,
530        };
531
532        let widget = Widget::new(rect).with_appearance(appearance);
533        let dict = widget.to_annotation_dict();
534
535        if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
536            // Check border color (gray)
537            if let Some(Object::Array(bc_array)) = mk_dict.get("BC") {
538                assert_eq!(bc_array.len(), 1);
539                assert_eq!(bc_array[0], Object::Real(0.3));
540            } else {
541                panic!("Expected BC array");
542            }
543
544            // Check background color (gray)
545            if let Some(Object::Array(bg_array)) = mk_dict.get("BG") {
546                assert_eq!(bg_array.len(), 1);
547                assert_eq!(bg_array[0], Object::Real(0.9));
548            } else {
549                panic!("Expected BG array");
550            }
551        } else {
552            panic!("Expected MK dictionary");
553        }
554    }
555
556    #[test]
557    fn test_widget_annotation_dict_cmyk_colors() {
558        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 25.0));
559        let appearance = WidgetAppearance {
560            border_color: Some(Color::cmyk(0.1, 0.2, 0.3, 0.4)),
561            background_color: Some(Color::cmyk(0.5, 0.6, 0.7, 0.8)),
562            border_width: 3.0,
563            border_style: BorderStyle::Inset,
564        };
565
566        let widget = Widget::new(rect).with_appearance(appearance);
567        let dict = widget.to_annotation_dict();
568
569        if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
570            // Check border color (CMYK)
571            if let Some(Object::Array(bc_array)) = mk_dict.get("BC") {
572                assert_eq!(bc_array.len(), 4);
573                assert_eq!(bc_array[0], Object::Real(0.1));
574                assert_eq!(bc_array[1], Object::Real(0.2));
575                assert_eq!(bc_array[2], Object::Real(0.3));
576                assert_eq!(bc_array[3], Object::Real(0.4));
577            } else {
578                panic!("Expected BC array");
579            }
580
581            // Check background color (CMYK)
582            if let Some(Object::Array(bg_array)) = mk_dict.get("BG") {
583                assert_eq!(bg_array.len(), 4);
584                assert_eq!(bg_array[0], Object::Real(0.5));
585                assert_eq!(bg_array[1], Object::Real(0.6));
586                assert_eq!(bg_array[2], Object::Real(0.7));
587                assert_eq!(bg_array[3], Object::Real(0.8));
588            } else {
589                panic!("Expected BG array");
590            }
591        } else {
592            panic!("Expected MK dictionary");
593        }
594    }
595
596    #[test]
597    fn test_widget_annotation_dict_no_colors() {
598        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(50.0, 25.0));
599        let appearance = WidgetAppearance {
600            border_color: None,
601            background_color: None,
602            border_width: 2.0,
603            border_style: BorderStyle::Underline,
604        };
605
606        let widget = Widget::new(rect).with_appearance(appearance);
607        let dict = widget.to_annotation_dict();
608
609        if let Some(Object::Dictionary(mk_dict)) = dict.get("MK") {
610            assert!(mk_dict.get("BC").is_none());
611            assert!(mk_dict.get("BG").is_none());
612        } else {
613            panic!("Expected MK dictionary");
614        }
615    }
616
617    #[test]
618    fn test_field_options_with_values() {
619        let flags = FieldFlags {
620            read_only: true,
621            required: false,
622            no_export: true,
623        };
624
625        let options = FieldOptions {
626            flags,
627            default_appearance: Some("/Helv 12 Tf 0 g".to_string()),
628            quadding: Some(1), // Center alignment
629        };
630
631        assert!(options.flags.read_only);
632        assert!(!options.flags.required);
633        assert!(options.flags.no_export);
634        assert_eq!(
635            options.default_appearance,
636            Some("/Helv 12 Tf 0 g".to_string())
637        );
638        assert_eq!(options.quadding, Some(1));
639    }
640
641    #[test]
642    fn test_field_options_debug_clone_default() {
643        let options = FieldOptions::default();
644        let debug_str = format!("{options:?}");
645        assert!(debug_str.contains("FieldOptions"));
646
647        let cloned = options.clone();
648        assert_eq!(options.flags.read_only, cloned.flags.read_only);
649        assert_eq!(options.default_appearance, cloned.default_appearance);
650        assert_eq!(options.quadding, cloned.quadding);
651    }
652
653    #[test]
654    fn test_form_field_creation() {
655        let mut field_dict = Dictionary::new();
656        field_dict.set("T", Object::String("TestField".to_string()));
657        field_dict.set("FT", Object::Name("Tx".to_string()));
658
659        let form_field = FormField::new(field_dict);
660        assert_eq!(form_field.widgets.len(), 0);
661        assert_eq!(
662            form_field.field_dict.get("T"),
663            Some(&Object::String("TestField".to_string()))
664        );
665    }
666
667    #[test]
668    fn test_form_field_add_widget() {
669        let mut field_dict = Dictionary::new();
670        field_dict.set("T", Object::String("TestField".to_string()));
671
672        let mut form_field = FormField::new(field_dict);
673
674        let rect1 = Rectangle::new(Point::new(10.0, 10.0), Point::new(110.0, 30.0));
675        let widget1 = Widget::new(rect1);
676
677        let rect2 = Rectangle::new(Point::new(10.0, 50.0), Point::new(110.0, 70.0));
678        let widget2 = Widget::new(rect2);
679
680        form_field.add_widget(widget1);
681        form_field.add_widget(widget2);
682
683        assert_eq!(form_field.widgets.len(), 2);
684        assert_eq!(form_field.widgets[0].rect.lower_left.x, 10.0);
685        assert_eq!(form_field.widgets[1].rect.lower_left.y, 50.0);
686    }
687
688    #[test]
689    fn test_form_field_debug_clone() {
690        let field_dict = Dictionary::new();
691        let form_field = FormField::new(field_dict);
692
693        let debug_str = format!("{form_field:?}");
694        assert!(debug_str.contains("FormField"));
695
696        let cloned = form_field.clone();
697        assert_eq!(form_field.widgets.len(), cloned.widgets.len());
698    }
699
700    #[test]
701    fn test_widget_rect_boundary_values() {
702        // Test with various rectangle configurations
703        let test_rects = [
704            // Normal rectangle
705            Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0)),
706            // Small rectangle
707            Rectangle::new(Point::new(5.0, 5.0), Point::new(6.0, 6.0)),
708            // Large rectangle
709            Rectangle::new(Point::new(0.0, 0.0), Point::new(1000.0, 800.0)),
710            // Negative coordinates
711            Rectangle::new(Point::new(-50.0, -25.0), Point::new(50.0, 25.0)),
712        ];
713
714        for rect in test_rects {
715            let widget = Widget::new(rect);
716            let dict = widget.to_annotation_dict();
717
718            if let Some(Object::Array(rect_array)) = dict.get("Rect") {
719                assert_eq!(rect_array.len(), 4);
720                assert_eq!(rect_array[0], Object::Real(rect.lower_left.x));
721                assert_eq!(rect_array[1], Object::Real(rect.lower_left.y));
722                assert_eq!(rect_array[2], Object::Real(rect.upper_right.x));
723                assert_eq!(rect_array[3], Object::Real(rect.upper_right.y));
724            } else {
725                panic!("Expected Rect array");
726            }
727        }
728    }
729
730    #[test]
731    fn test_border_width_variations() {
732        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 50.0));
733        let border_widths = [0.0, 0.5, 1.0, 2.5, 10.0];
734
735        for width in border_widths {
736            let appearance = WidgetAppearance {
737                border_color: Some(Color::black()),
738                background_color: None,
739                border_width: width,
740                border_style: BorderStyle::Solid,
741            };
742
743            let widget = Widget::new(rect).with_appearance(appearance);
744            let dict = widget.to_annotation_dict();
745
746            if let Some(Object::Dictionary(bs_dict)) = dict.get("BS") {
747                assert_eq!(bs_dict.get("W"), Some(&Object::Real(width)));
748            } else {
749                panic!("Expected BS dictionary");
750            }
751        }
752    }
753
754    #[test]
755    fn test_quadding_values() {
756        let test_quadding = [
757            (None, "no quadding"),
758            (Some(0), "left alignment"),
759            (Some(1), "center alignment"),
760            (Some(2), "right alignment"),
761        ];
762
763        for (quadding, description) in test_quadding {
764            let options = FieldOptions {
765                flags: FieldFlags::default(),
766                default_appearance: None,
767                quadding,
768            };
769
770            assert_eq!(options.quadding, quadding, "Failed for {description}");
771        }
772    }
773}