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