Skip to main content

oxidize_pdf/forms/
working_field.rs

1//! Working form field implementation
2//!
3//! This module provides a simpler, working implementation of form fields
4
5use crate::geometry::Rectangle;
6use crate::objects::{Dictionary, Object};
7
8/// Create a working text field dictionary
9pub fn create_text_field_dict(
10    name: &str,
11    rect: Rectangle,
12    default_value: Option<&str>,
13) -> Dictionary {
14    let mut dict = Dictionary::new();
15
16    // Annotation properties
17    dict.set("Type", Object::Name("Annot".to_string()));
18    dict.set("Subtype", Object::Name("Widget".to_string()));
19
20    // Field properties
21    dict.set("FT", Object::Name("Tx".to_string())); // Text field
22    dict.set("T", Object::String(name.to_string())); // Field name
23
24    // Rectangle
25    dict.set(
26        "Rect",
27        Object::Array(vec![
28            Object::Real(rect.lower_left.x),
29            Object::Real(rect.lower_left.y),
30            Object::Real(rect.upper_right.x),
31            Object::Real(rect.upper_right.y),
32        ]),
33    );
34
35    // Appearance
36    dict.set("DA", Object::String("/Helv 12 Tf 0 g".to_string()));
37
38    // Value
39    if let Some(value) = default_value {
40        dict.set("V", Object::String(value.to_string()));
41        dict.set("DV", Object::String(value.to_string()));
42    }
43
44    // Flags
45    dict.set("F", Object::Integer(4)); // Print flag
46
47    // Appearance characteristics
48    let mut mk = Dictionary::new();
49    mk.set("BC", Object::Array(vec![Object::Real(0.0)])); // Black border
50    mk.set("BG", Object::Array(vec![Object::Real(1.0)])); // White background
51    dict.set("MK", Object::Dictionary(mk));
52
53    // Border style
54    let mut bs = Dictionary::new();
55    bs.set("W", Object::Real(1.0));
56    bs.set("S", Object::Name("S".to_string())); // Solid
57    dict.set("BS", Object::Dictionary(bs));
58
59    dict
60}
61
62/// Create a working checkbox field dictionary
63pub fn create_checkbox_dict(name: &str, rect: Rectangle, checked: bool) -> Dictionary {
64    let mut dict = Dictionary::new();
65
66    // Annotation properties
67    dict.set("Type", Object::Name("Annot".to_string()));
68    dict.set("Subtype", Object::Name("Widget".to_string()));
69
70    // Field properties
71    dict.set("FT", Object::Name("Btn".to_string())); // Button field
72    dict.set("T", Object::String(name.to_string())); // Field name
73
74    // Rectangle
75    dict.set(
76        "Rect",
77        Object::Array(vec![
78            Object::Real(rect.lower_left.x),
79            Object::Real(rect.lower_left.y),
80            Object::Real(rect.upper_right.x),
81            Object::Real(rect.upper_right.y),
82        ]),
83    );
84
85    // Value
86    if checked {
87        dict.set("V", Object::Name("Yes".to_string()));
88        dict.set("AS", Object::Name("Yes".to_string())); // Appearance state
89    } else {
90        dict.set("V", Object::Name("Off".to_string()));
91        dict.set("AS", Object::Name("Off".to_string()));
92    }
93
94    // Flags - no radio, no pushbutton
95    dict.set("F", Object::Integer(4)); // Print flag
96
97    // Appearance characteristics
98    let mut mk = Dictionary::new();
99    mk.set("BC", Object::Array(vec![Object::Real(0.0)])); // Black border
100    mk.set("BG", Object::Array(vec![Object::Real(1.0)])); // White background
101    dict.set("MK", Object::Dictionary(mk));
102
103    dict
104}
105
106/// Create a working radio button field dictionary
107pub fn create_radio_button_dict(
108    name: &str,
109    rect: Rectangle,
110    export_value: &str,
111    checked: bool,
112) -> Dictionary {
113    let mut dict = Dictionary::new();
114
115    // Annotation properties
116    dict.set("Type", Object::Name("Annot".to_string()));
117    dict.set("Subtype", Object::Name("Widget".to_string()));
118
119    // Field properties
120    dict.set("FT", Object::Name("Btn".to_string())); // Button field
121    dict.set("T", Object::String(name.to_string())); // Field name
122
123    // Rectangle
124    dict.set(
125        "Rect",
126        Object::Array(vec![
127            Object::Real(rect.lower_left.x),
128            Object::Real(rect.lower_left.y),
129            Object::Real(rect.upper_right.x),
130            Object::Real(rect.upper_right.y),
131        ]),
132    );
133
134    // Radio button specific flags (bit 16 = radio, bit 15 = no toggle to off)
135    dict.set("Ff", Object::Integer((1 << 15) | (1 << 16)));
136
137    // Value and appearance state
138    if checked {
139        dict.set("V", Object::Name(export_value.to_string()));
140        dict.set("AS", Object::Name(export_value.to_string()));
141    } else {
142        dict.set("AS", Object::Name("Off".to_string()));
143    }
144
145    // Flags
146    dict.set("F", Object::Integer(4)); // Print flag
147
148    // Appearance characteristics
149    let mut mk = Dictionary::new();
150    mk.set("BC", Object::Array(vec![Object::Real(0.0)])); // Black border
151    mk.set("BG", Object::Array(vec![Object::Real(1.0)])); // White background
152    mk.set("CA", Object::String("l".to_string())); // Circle style
153    dict.set("MK", Object::Dictionary(mk));
154
155    dict
156}
157
158/// Create a working combo box (dropdown) field dictionary
159pub fn create_combo_box_dict(
160    name: &str,
161    rect: Rectangle,
162    options: Vec<(&str, &str)>, // (export_value, display_text) pairs
163    default_value: Option<&str>,
164) -> Dictionary {
165    let mut dict = Dictionary::new();
166
167    // Annotation properties
168    dict.set("Type", Object::Name("Annot".to_string()));
169    dict.set("Subtype", Object::Name("Widget".to_string()));
170
171    // Field properties
172    dict.set("FT", Object::Name("Ch".to_string())); // Choice field
173    dict.set("T", Object::String(name.to_string())); // Field name
174
175    // Rectangle
176    dict.set(
177        "Rect",
178        Object::Array(vec![
179            Object::Real(rect.lower_left.x),
180            Object::Real(rect.lower_left.y),
181            Object::Real(rect.upper_right.x),
182            Object::Real(rect.upper_right.y),
183        ]),
184    );
185
186    // Combo box flag (bit 18)
187    dict.set("Ff", Object::Integer(1 << 17)); // Combo flag
188
189    // Options array
190    let opt_array: Vec<Object> = options
191        .iter()
192        .map(|(export, display)| {
193            if export == display {
194                Object::String((*display).to_string())
195            } else {
196                Object::Array(vec![
197                    Object::String((*export).to_string()),
198                    Object::String((*display).to_string()),
199                ])
200            }
201        })
202        .collect();
203    dict.set("Opt", Object::Array(opt_array));
204
205    // Default value
206    if let Some(value) = default_value {
207        dict.set("V", Object::String(value.to_string()));
208        dict.set("DV", Object::String(value.to_string()));
209    }
210
211    // Appearance
212    dict.set("DA", Object::String("/Helv 12 Tf 0 g".to_string()));
213
214    // Flags
215    dict.set("F", Object::Integer(4)); // Print flag
216
217    // Appearance characteristics
218    let mut mk = Dictionary::new();
219    mk.set("BC", Object::Array(vec![Object::Real(0.0)])); // Black border
220    mk.set("BG", Object::Array(vec![Object::Real(1.0)])); // White background
221    dict.set("MK", Object::Dictionary(mk));
222
223    // Border style
224    let mut bs = Dictionary::new();
225    bs.set("W", Object::Real(1.0));
226    bs.set("S", Object::Name("S".to_string())); // Solid
227    dict.set("BS", Object::Dictionary(bs));
228
229    dict
230}
231
232/// Create a working list box field dictionary
233pub fn create_list_box_dict(
234    name: &str,
235    rect: Rectangle,
236    options: Vec<(&str, &str)>, // (export_value, display_text) pairs
237    selected: Vec<usize>,       // Selected indices
238    multi_select: bool,
239) -> Dictionary {
240    let mut dict = Dictionary::new();
241
242    // Annotation properties
243    dict.set("Type", Object::Name("Annot".to_string()));
244    dict.set("Subtype", Object::Name("Widget".to_string()));
245
246    // Field properties
247    dict.set("FT", Object::Name("Ch".to_string())); // Choice field
248    dict.set("T", Object::String(name.to_string())); // Field name
249
250    // Rectangle
251    dict.set(
252        "Rect",
253        Object::Array(vec![
254            Object::Real(rect.lower_left.x),
255            Object::Real(rect.lower_left.y),
256            Object::Real(rect.upper_right.x),
257            Object::Real(rect.upper_right.y),
258        ]),
259    );
260
261    // List box flags
262    let mut flags = 0u32;
263    if multi_select {
264        flags |= 1 << 21; // MultiSelect flag
265    }
266    dict.set("Ff", Object::Integer(flags as i64));
267
268    // Options array
269    let opt_array: Vec<Object> = options
270        .iter()
271        .map(|(export, display)| {
272            if export == display {
273                Object::String((*display).to_string())
274            } else {
275                Object::Array(vec![
276                    Object::String((*export).to_string()),
277                    Object::String((*display).to_string()),
278                ])
279            }
280        })
281        .collect();
282    dict.set("Opt", Object::Array(opt_array));
283
284    // Selected indices
285    if !selected.is_empty() {
286        if multi_select {
287            let indices: Vec<Object> = selected
288                .iter()
289                .map(|&i| Object::Integer(i as i64))
290                .collect();
291            dict.set("I", Object::Array(indices));
292        } else if let Some(&index) = selected.first() {
293            if let Some((export_value, _)) = options.get(index) {
294                dict.set("V", Object::String((*export_value).to_string()));
295            }
296        }
297    }
298
299    // Appearance
300    dict.set("DA", Object::String("/Helv 12 Tf 0 g".to_string()));
301
302    // Flags
303    dict.set("F", Object::Integer(4)); // Print flag
304
305    // Appearance characteristics
306    let mut mk = Dictionary::new();
307    mk.set("BC", Object::Array(vec![Object::Real(0.0)])); // Black border
308    mk.set("BG", Object::Array(vec![Object::Real(1.0)])); // White background
309    dict.set("MK", Object::Dictionary(mk));
310
311    // Border style
312    let mut bs = Dictionary::new();
313    bs.set("W", Object::Real(1.0));
314    bs.set("S", Object::Name("I".to_string())); // Inset
315    dict.set("BS", Object::Dictionary(bs));
316
317    dict
318}
319
320/// Create a working push button field dictionary
321pub fn create_push_button_dict(name: &str, rect: Rectangle, caption: &str) -> Dictionary {
322    let mut dict = Dictionary::new();
323
324    // Annotation properties
325    dict.set("Type", Object::Name("Annot".to_string()));
326    dict.set("Subtype", Object::Name("Widget".to_string()));
327
328    // Field properties
329    dict.set("FT", Object::Name("Btn".to_string())); // Button field
330    dict.set("T", Object::String(name.to_string())); // Field name
331
332    // Rectangle
333    dict.set(
334        "Rect",
335        Object::Array(vec![
336            Object::Real(rect.lower_left.x),
337            Object::Real(rect.lower_left.y),
338            Object::Real(rect.upper_right.x),
339            Object::Real(rect.upper_right.y),
340        ]),
341    );
342
343    // Push button flag (bit 17)
344    dict.set("Ff", Object::Integer(1 << 16)); // Pushbutton flag
345
346    // Flags
347    dict.set("F", Object::Integer(4)); // Print flag
348
349    // Appearance characteristics
350    let mut mk = Dictionary::new();
351    mk.set(
352        "BC",
353        Object::Array(vec![
354            Object::Real(0.2),
355            Object::Real(0.2),
356            Object::Real(0.2),
357        ]),
358    ); // Dark gray border
359    mk.set(
360        "BG",
361        Object::Array(vec![
362            Object::Real(0.9),
363            Object::Real(0.9),
364            Object::Real(0.9),
365        ]),
366    ); // Light gray background
367    mk.set("CA", Object::String(caption.to_string())); // Caption
368    dict.set("MK", Object::Dictionary(mk));
369
370    // Border style - beveled for 3D effect
371    let mut bs = Dictionary::new();
372    bs.set("W", Object::Real(2.0));
373    bs.set("S", Object::Name("B".to_string())); // Beveled
374    dict.set("BS", Object::Dictionary(bs));
375
376    // Default appearance
377    dict.set("DA", Object::String("/Helv 12 Tf 0 g".to_string()));
378
379    dict
380}
381
382#[cfg(test)]
383mod tests {
384    use super::*;
385    use crate::geometry::Point;
386
387    fn create_test_rect() -> Rectangle {
388        Rectangle {
389            lower_left: Point { x: 100.0, y: 200.0 },
390            upper_right: Point { x: 300.0, y: 250.0 },
391        }
392    }
393
394    #[test]
395    fn test_create_text_field_dict_basic() {
396        let rect = create_test_rect();
397        let dict = create_text_field_dict("test_field", rect, None);
398
399        // Check annotation properties
400        assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
401        assert_eq!(
402            dict.get("Subtype"),
403            Some(&Object::Name("Widget".to_string()))
404        );
405
406        // Check field properties
407        assert_eq!(dict.get("FT"), Some(&Object::Name("Tx".to_string())));
408        assert_eq!(
409            dict.get("T"),
410            Some(&Object::String("test_field".to_string()))
411        );
412
413        // Check rectangle
414        let expected_rect = Object::Array(vec![
415            Object::Real(100.0),
416            Object::Real(200.0),
417            Object::Real(300.0),
418            Object::Real(250.0),
419        ]);
420        assert_eq!(dict.get("Rect"), Some(&expected_rect));
421
422        // Check appearance
423        assert_eq!(
424            dict.get("DA"),
425            Some(&Object::String("/Helv 12 Tf 0 g".to_string()))
426        );
427
428        // Check flags
429        assert_eq!(dict.get("F"), Some(&Object::Integer(4)));
430
431        // Check that no value is set when None provided
432        assert!(dict.get("V").is_none());
433        assert!(dict.get("DV").is_none());
434    }
435
436    #[test]
437    fn test_create_text_field_dict_with_value() {
438        let rect = create_test_rect();
439        let dict = create_text_field_dict("test_field", rect, Some("default text"));
440
441        // Check that value and default value are set
442        assert_eq!(
443            dict.get("V"),
444            Some(&Object::String("default text".to_string()))
445        );
446        assert_eq!(
447            dict.get("DV"),
448            Some(&Object::String("default text".to_string()))
449        );
450    }
451
452    #[test]
453    fn test_create_text_field_dict_appearance_characteristics() {
454        let rect = create_test_rect();
455        let dict = create_text_field_dict("test_field", rect, None);
456
457        // Check MK (appearance characteristics)
458        if let Some(Object::Dictionary(mk)) = dict.get("MK") {
459            assert_eq!(mk.get("BC"), Some(&Object::Array(vec![Object::Real(0.0)])));
460            assert_eq!(mk.get("BG"), Some(&Object::Array(vec![Object::Real(1.0)])));
461        } else {
462            panic!("MK dictionary not found or wrong type");
463        }
464
465        // Check BS (border style)
466        if let Some(Object::Dictionary(bs)) = dict.get("BS") {
467            assert_eq!(bs.get("W"), Some(&Object::Real(1.0)));
468            assert_eq!(bs.get("S"), Some(&Object::Name("S".to_string())));
469        } else {
470            panic!("BS dictionary not found or wrong type");
471        }
472    }
473
474    #[test]
475    fn test_create_checkbox_dict_unchecked() {
476        let rect = create_test_rect();
477        let dict = create_checkbox_dict("checkbox_field", rect, false);
478
479        // Check field type
480        assert_eq!(dict.get("FT"), Some(&Object::Name("Btn".to_string())));
481
482        // Check unchecked state
483        assert_eq!(dict.get("V"), Some(&Object::Name("Off".to_string())));
484        assert_eq!(dict.get("AS"), Some(&Object::Name("Off".to_string())));
485    }
486
487    #[test]
488    fn test_create_checkbox_dict_checked() {
489        let rect = create_test_rect();
490        let dict = create_checkbox_dict("checkbox_field", rect, true);
491
492        // Check checked state
493        assert_eq!(dict.get("V"), Some(&Object::Name("Yes".to_string())));
494        assert_eq!(dict.get("AS"), Some(&Object::Name("Yes".to_string())));
495    }
496
497    #[test]
498    fn test_create_radio_button_dict_unchecked() {
499        let rect = create_test_rect();
500        let dict = create_radio_button_dict("radio_field", rect, "option1", false);
501
502        // Check field type
503        assert_eq!(dict.get("FT"), Some(&Object::Name("Btn".to_string())));
504
505        // Check radio button flags (bit 15 | bit 16)
506        let expected_flags = (1 << 15) | (1 << 16);
507        assert_eq!(dict.get("Ff"), Some(&Object::Integer(expected_flags)));
508
509        // Check unchecked state (only AS should be set to Off)
510        assert_eq!(dict.get("AS"), Some(&Object::Name("Off".to_string())));
511        assert!(dict.get("V").is_none());
512    }
513
514    #[test]
515    fn test_create_radio_button_dict_checked() {
516        let rect = create_test_rect();
517        let dict = create_radio_button_dict("radio_field", rect, "option1", true);
518
519        // Check checked state
520        assert_eq!(dict.get("V"), Some(&Object::Name("option1".to_string())));
521        assert_eq!(dict.get("AS"), Some(&Object::Name("option1".to_string())));
522
523        // Check appearance characteristics for circle style
524        if let Some(Object::Dictionary(mk)) = dict.get("MK") {
525            assert_eq!(mk.get("CA"), Some(&Object::String("l".to_string())));
526        } else {
527            panic!("MK dictionary not found");
528        }
529    }
530
531    #[test]
532    fn test_create_combo_box_dict_basic() {
533        let rect = create_test_rect();
534        let options = vec![("val1", "Display 1"), ("val2", "Display 2")];
535        let dict = create_combo_box_dict("combo_field", rect, options, None);
536
537        // Check field type
538        assert_eq!(dict.get("FT"), Some(&Object::Name("Ch".to_string())));
539
540        // Check combo box flag (bit 17)
541        assert_eq!(dict.get("Ff"), Some(&Object::Integer(1 << 17)));
542
543        // Check options array
544        if let Some(Object::Array(opt_array)) = dict.get("Opt") {
545            assert_eq!(opt_array.len(), 2);
546
547            // First option (different export/display values)
548            if let Object::Array(first_opt) = &opt_array[0] {
549                assert_eq!(first_opt[0], Object::String("val1".to_string()));
550                assert_eq!(first_opt[1], Object::String("Display 1".to_string()));
551            } else {
552                panic!("First option should be an array");
553            }
554        } else {
555            panic!("Opt array not found");
556        }
557    }
558
559    #[test]
560    fn test_create_combo_box_dict_with_default() {
561        let rect = create_test_rect();
562        let options = vec![("val1", "Display 1"), ("val2", "Display 2")];
563        let dict = create_combo_box_dict("combo_field", rect, options, Some("val1"));
564
565        // Check default value
566        assert_eq!(dict.get("V"), Some(&Object::String("val1".to_string())));
567        assert_eq!(dict.get("DV"), Some(&Object::String("val1".to_string())));
568    }
569
570    #[test]
571    fn test_create_combo_box_dict_same_export_display() {
572        let rect = create_test_rect();
573        let options = vec![("Option1", "Option1"), ("Option2", "Option2")];
574        let dict = create_combo_box_dict("combo_field", rect, options, None);
575
576        // When export and display are the same, should use string directly
577        if let Some(Object::Array(opt_array)) = dict.get("Opt") {
578            assert_eq!(opt_array[0], Object::String("Option1".to_string()));
579            assert_eq!(opt_array[1], Object::String("Option2".to_string()));
580        } else {
581            panic!("Opt array not found");
582        }
583    }
584
585    #[test]
586    fn test_create_list_box_dict_single_select() {
587        let rect = create_test_rect();
588        let options = vec![("val1", "Display 1"), ("val2", "Display 2")];
589        let selected = vec![0];
590        let dict = create_list_box_dict("list_field", rect, options, selected, false);
591
592        // Check field type
593        assert_eq!(dict.get("FT"), Some(&Object::Name("Ch".to_string())));
594
595        // Check single select (no multiselect flag)
596        assert_eq!(dict.get("Ff"), Some(&Object::Integer(0)));
597
598        // Check selected value (should use export value of first option)
599        assert_eq!(dict.get("V"), Some(&Object::String("val1".to_string())));
600
601        // Should not have indices array for single select
602        assert!(dict.get("I").is_none());
603    }
604
605    #[test]
606    fn test_create_list_box_dict_multi_select() {
607        let rect = create_test_rect();
608        let options = vec![
609            ("val1", "Display 1"),
610            ("val2", "Display 2"),
611            ("val3", "Display 3"),
612        ];
613        let selected = vec![0, 2];
614        let dict = create_list_box_dict("list_field", rect, options, selected, true);
615
616        // Check multiselect flag (bit 21)
617        assert_eq!(dict.get("Ff"), Some(&Object::Integer(1 << 21)));
618
619        // Check indices array
620        if let Some(Object::Array(indices)) = dict.get("I") {
621            assert_eq!(indices.len(), 2);
622            assert_eq!(indices[0], Object::Integer(0));
623            assert_eq!(indices[1], Object::Integer(2));
624        } else {
625            panic!("I array not found for multiselect");
626        }
627
628        // Should not have single value for multiselect
629        assert!(dict.get("V").is_none());
630    }
631
632    #[test]
633    fn test_create_list_box_dict_no_selection() {
634        let rect = create_test_rect();
635        let options = vec![("val1", "Display 1")];
636        let selected = vec![];
637        let dict = create_list_box_dict("list_field", rect, options, selected, false);
638
639        // No selection - should have neither V nor I
640        assert!(dict.get("V").is_none());
641        assert!(dict.get("I").is_none());
642    }
643
644    #[test]
645    fn test_create_push_button_dict() {
646        let rect = create_test_rect();
647        let dict = create_push_button_dict("button_field", rect, "Click Me");
648
649        // Check field type
650        assert_eq!(dict.get("FT"), Some(&Object::Name("Btn".to_string())));
651
652        // Check pushbutton flag (bit 16)
653        assert_eq!(dict.get("Ff"), Some(&Object::Integer(1 << 16)));
654
655        // Check appearance characteristics
656        if let Some(Object::Dictionary(mk)) = dict.get("MK") {
657            // Check caption
658            assert_eq!(mk.get("CA"), Some(&Object::String("Click Me".to_string())));
659
660            // Check border color (dark gray)
661            if let Some(Object::Array(bc)) = mk.get("BC") {
662                assert_eq!(bc.len(), 3);
663                assert_eq!(bc[0], Object::Real(0.2));
664                assert_eq!(bc[1], Object::Real(0.2));
665                assert_eq!(bc[2], Object::Real(0.2));
666            } else {
667                panic!("BC array not found");
668            }
669
670            // Check background color (light gray)
671            if let Some(Object::Array(bg)) = mk.get("BG") {
672                assert_eq!(bg.len(), 3);
673                assert_eq!(bg[0], Object::Real(0.9));
674                assert_eq!(bg[1], Object::Real(0.9));
675                assert_eq!(bg[2], Object::Real(0.9));
676            } else {
677                panic!("BG array not found");
678            }
679        } else {
680            panic!("MK dictionary not found");
681        }
682
683        // Check beveled border style
684        if let Some(Object::Dictionary(bs)) = dict.get("BS") {
685            assert_eq!(bs.get("W"), Some(&Object::Real(2.0)));
686            assert_eq!(bs.get("S"), Some(&Object::Name("B".to_string())));
687        } else {
688            panic!("BS dictionary not found");
689        }
690    }
691
692    #[test]
693    fn test_all_fields_have_required_properties() {
694        let rect = create_test_rect();
695
696        let fields = vec![
697            create_text_field_dict("text", rect, None),
698            create_checkbox_dict("checkbox", rect, false),
699            create_radio_button_dict("radio", rect, "val", false),
700            create_combo_box_dict("combo", rect, vec![("a", "A")], None),
701            create_list_box_dict("list", rect, vec![("a", "A")], vec![], false),
702            create_push_button_dict("button", rect, "Button"),
703        ];
704
705        for dict in fields {
706            // All fields should have these basic properties
707            assert!(dict.get("Type").is_some(), "Missing Type");
708            assert!(dict.get("Subtype").is_some(), "Missing Subtype");
709            assert!(dict.get("FT").is_some(), "Missing FT");
710            assert!(dict.get("T").is_some(), "Missing T");
711            assert!(dict.get("Rect").is_some(), "Missing Rect");
712            assert!(dict.get("F").is_some(), "Missing F");
713            assert!(dict.get("MK").is_some(), "Missing MK");
714        }
715    }
716
717    #[test]
718    fn test_field_name_preservation() {
719        let rect = create_test_rect();
720        let field_names = ["simple", "with spaces", "unicode_ñ", "123numbers"];
721
722        for name in &field_names {
723            let dict = create_text_field_dict(name, rect, None);
724            assert_eq!(dict.get("T"), Some(&Object::String((*name).to_string())));
725        }
726    }
727
728    #[test]
729    fn test_rectangle_coordinates() {
730        let rects = vec![
731            Rectangle {
732                lower_left: Point { x: 0.0, y: 0.0 },
733                upper_right: Point { x: 100.0, y: 50.0 },
734            },
735            Rectangle {
736                lower_left: Point { x: -50.0, y: -25.0 },
737                upper_right: Point { x: 50.0, y: 25.0 },
738            },
739            Rectangle {
740                lower_left: Point {
741                    x: 200.5,
742                    y: 300.75,
743                },
744                upper_right: Point {
745                    x: 400.25,
746                    y: 500.125,
747                },
748            },
749        ];
750
751        for rect in rects {
752            let dict = create_text_field_dict("test", rect, None);
753
754            let expected_rect = Object::Array(vec![
755                Object::Real(rect.lower_left.x),
756                Object::Real(rect.lower_left.y),
757                Object::Real(rect.upper_right.x),
758                Object::Real(rect.upper_right.y),
759            ]);
760
761            assert_eq!(dict.get("Rect"), Some(&expected_rect));
762        }
763    }
764}