Skip to main content

oxidize_pdf/forms/
form_data.rs

1//! Form data management and AcroForm generation
2
3use crate::error::Result;
4use crate::forms::{
5    CheckBox, ComboBox, FieldOptions, FormField, ListBox, PushButton, RadioButton, TextField,
6    Widget,
7};
8use crate::objects::{Dictionary, Object, ObjectReference};
9use std::collections::HashMap;
10
11/// Interactive form dictionary (AcroForm)
12#[derive(Debug, Clone)]
13pub struct AcroForm {
14    /// Form fields
15    pub fields: Vec<ObjectReference>,
16    /// Need appearances flag
17    pub need_appearances: bool,
18    /// Signature flags
19    pub sig_flags: Option<i32>,
20    /// Calculation order
21    pub co: Option<Vec<ObjectReference>>,
22    /// Default resources
23    pub dr: Option<Dictionary>,
24    /// Default appearance
25    pub da: Option<String>,
26    /// Quadding
27    pub q: Option<i32>,
28}
29
30impl AcroForm {
31    /// Create a new AcroForm
32    pub fn new() -> Self {
33        Self {
34            fields: Vec::new(),
35            need_appearances: true,
36            sig_flags: None,
37            co: None,
38            dr: None,
39            da: Some("/Helv 12 Tf 0 g".to_string()), // Default: Helvetica 12pt black
40            q: None,
41        }
42    }
43
44    /// Add a field reference
45    pub fn add_field(&mut self, field_ref: ObjectReference) {
46        self.fields.push(field_ref);
47    }
48
49    /// Convert to dictionary
50    pub fn to_dict(&self) -> Dictionary {
51        let mut dict = Dictionary::new();
52
53        // Fields array
54        let fields: Vec<Object> = self.fields.iter().map(|r| Object::Reference(*r)).collect();
55        dict.set("Fields", Object::Array(fields));
56
57        // Need appearances
58        dict.set("NeedAppearances", Object::Boolean(self.need_appearances));
59
60        // Signature flags
61        if let Some(sig_flags) = self.sig_flags {
62            dict.set("SigFlags", Object::Integer(sig_flags as i64));
63        }
64
65        // Calculation order
66        if let Some(ref co) = self.co {
67            let co_refs: Vec<Object> = co.iter().map(|r| Object::Reference(*r)).collect();
68            dict.set("CO", Object::Array(co_refs));
69        }
70
71        // Default resources
72        if let Some(ref dr) = self.dr {
73            dict.set("DR", Object::Dictionary(dr.clone()));
74        }
75
76        // Default appearance
77        if let Some(ref da) = self.da {
78            dict.set("DA", Object::String(da.clone()));
79        }
80
81        // Quadding
82        if let Some(q) = self.q {
83            dict.set("Q", Object::Integer(q as i64));
84        }
85
86        dict
87    }
88}
89
90impl Default for AcroForm {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96/// Form data extracted from PDF
97#[derive(Debug, Clone)]
98pub struct FormData {
99    /// Field values by name
100    pub values: HashMap<String, String>,
101}
102
103impl FormData {
104    /// Create new form data
105    pub fn new() -> Self {
106        Self {
107            values: HashMap::new(),
108        }
109    }
110
111    /// Set field value
112    pub fn set_value(&mut self, name: impl Into<String>, value: impl Into<String>) {
113        self.values.insert(name.into(), value.into());
114    }
115
116    /// Get field value
117    pub fn get_value(&self, name: &str) -> Option<&str> {
118        self.values.get(name).map(|s| s.as_str())
119    }
120}
121
122impl Default for FormData {
123    fn default() -> Self {
124        Self::new()
125    }
126}
127
128/// Form manager for creating and managing forms
129#[derive(Debug)]
130pub struct FormManager {
131    /// Registered fields
132    fields: HashMap<String, FormField>,
133    /// Placeholder `ObjectReference` returned to callers for each field,
134    /// keyed by field name. The reference is a *placeholder* — its object
135    /// number comes from the FormManager's local counter and is disjoint
136    /// from the writer's global object-id allocator. The writer builds a
137    /// placeholder → real-ObjectId map at serialization time so widget
138    /// annotations created via `Page::add_form_widget_with_ref` can
139    /// resolve their `/Parent` to the correct real id.
140    field_refs: HashMap<String, ObjectReference>,
141    /// AcroForm dictionary
142    acro_form: AcroForm,
143    /// Next field ID
144    next_field_id: u32,
145}
146
147impl FormManager {
148    /// Create a new form manager
149    pub fn new() -> Self {
150        Self {
151            fields: HashMap::new(),
152            field_refs: HashMap::new(),
153            acro_form: AcroForm::new(),
154            next_field_id: 1,
155        }
156    }
157
158    /// Add a text field
159    pub fn add_text_field(
160        &mut self,
161        field: TextField,
162        widget: Widget,
163        options: Option<FieldOptions>,
164    ) -> Result<ObjectReference> {
165        // Preserve the typed /DA on the FormField so `Document::fill_field`
166        // can pick the right font without re-parsing the PDF string in
167        // `field_dict["DA"]`. `to_dict()` consumes the visible state; stash
168        // the typed view first.
169        let typed_da = field.default_appearance.clone();
170        let mut field_dict = field.to_dict();
171
172        // Apply options
173        if let Some(opts) = options {
174            if opts.flags.to_flags() != 0 {
175                field_dict.set("Ff", Object::Integer(opts.flags.to_flags() as i64));
176            }
177            if let Some(da) = opts.default_appearance {
178                field_dict.set("DA", Object::String(da));
179            }
180            if let Some(q) = opts.quadding {
181                field_dict.set("Q", Object::Integer(q as i64));
182            }
183        }
184
185        let field_name = field.name;
186        let mut form_field = FormField::new(field_dict);
187        form_field.default_appearance = typed_da;
188        form_field.add_widget(widget);
189
190        // Create object reference
191        let obj_ref = ObjectReference::new(self.next_field_id, 0);
192        self.next_field_id += 1;
193        self.field_refs.insert(field_name.clone(), obj_ref);
194        self.fields.insert(field_name, form_field);
195        self.acro_form.add_field(obj_ref);
196
197        Ok(obj_ref)
198    }
199
200    /// Add a combo box field
201    pub fn add_combo_box(
202        &mut self,
203        combo: ComboBox,
204        widget: Widget,
205        options: Option<FieldOptions>,
206    ) -> Result<ObjectReference> {
207        // Preserve the typed /DA on the FormField so `Document::fill_field`
208        // can pick the right font without re-parsing the PDF string in
209        // `field_dict["DA"]`. Same pattern as `add_text_field` (issue #212).
210        let typed_da = combo.default_appearance.clone();
211        let mut field_dict = combo.to_dict();
212
213        // If a typed DA is present and no explicit options.default_appearance
214        // overrides it, render the typed DA to its PDF string form so the
215        // serialised dict carries `/DA` for viewers that read it directly.
216        if let Some(ref da) = typed_da {
217            field_dict.set("DA", Object::String(da.to_da_string()));
218        }
219
220        // Apply options
221        if let Some(opts) = options {
222            if opts.flags.to_flags() != 0 {
223                field_dict.set("Ff", Object::Integer(opts.flags.to_flags() as i64));
224            }
225            if let Some(da) = opts.default_appearance {
226                field_dict.set("DA", Object::String(da));
227            }
228        }
229
230        let field_name = combo.name;
231        let mut form_field = FormField::new(field_dict);
232        form_field.default_appearance = typed_da;
233        form_field.add_widget(widget);
234
235        // Create object reference
236        let obj_ref = ObjectReference::new(self.next_field_id, 0);
237        self.next_field_id += 1;
238        self.field_refs.insert(field_name.clone(), obj_ref);
239        self.fields.insert(field_name, form_field);
240        self.acro_form.add_field(obj_ref);
241
242        Ok(obj_ref)
243    }
244
245    /// Add a list box field
246    pub fn add_list_box(
247        &mut self,
248        listbox: ListBox,
249        widget: Widget,
250        options: Option<FieldOptions>,
251    ) -> Result<ObjectReference> {
252        let mut field_dict = listbox.to_dict();
253
254        // Apply options
255        if let Some(opts) = options {
256            if opts.flags.to_flags() != 0 {
257                field_dict.set("Ff", Object::Integer(opts.flags.to_flags() as i64));
258            }
259            if let Some(da) = opts.default_appearance {
260                field_dict.set("DA", Object::String(da));
261            }
262        }
263
264        let field_name = listbox.name;
265        let mut form_field = FormField::new(field_dict);
266        form_field.add_widget(widget);
267
268        // Create object reference
269        let obj_ref = ObjectReference::new(self.next_field_id, 0);
270        self.next_field_id += 1;
271        self.field_refs.insert(field_name.clone(), obj_ref);
272        self.fields.insert(field_name, form_field);
273        self.acro_form.add_field(obj_ref);
274
275        Ok(obj_ref)
276    }
277
278    /// Add a radio button group
279    pub fn add_radio_button(
280        &mut self,
281        radio: RadioButton,
282        widgets: Option<Vec<Widget>>,
283        options: Option<FieldOptions>,
284    ) -> Result<ObjectReference> {
285        let mut field_dict = radio.to_dict();
286
287        // Apply options
288        if let Some(opts) = options {
289            if opts.flags.to_flags() != 0 {
290                field_dict.set("Ff", Object::Integer(opts.flags.to_flags() as i64));
291            }
292            if let Some(da) = opts.default_appearance {
293                field_dict.set("DA", Object::String(da));
294            }
295        }
296
297        let field_name = radio.name;
298        let mut form_field = FormField::new(field_dict);
299
300        // Add widgets if provided
301        if let Some(widgets) = widgets {
302            for widget in widgets {
303                form_field.add_widget(widget);
304            }
305        }
306
307        // Create object reference
308        let obj_ref = ObjectReference::new(self.next_field_id, 0);
309        self.next_field_id += 1;
310        self.field_refs.insert(field_name.clone(), obj_ref);
311        self.fields.insert(field_name, form_field);
312        self.acro_form.add_field(obj_ref);
313
314        Ok(obj_ref)
315    }
316
317    /// Add a checkbox
318    pub fn add_checkbox(
319        &mut self,
320        checkbox: CheckBox,
321        widget: Widget,
322        options: Option<FieldOptions>,
323    ) -> Result<ObjectReference> {
324        let mut field_dict = checkbox.to_dict();
325
326        // Apply options
327        if let Some(opts) = options {
328            if opts.flags.to_flags() != 0 {
329                field_dict.set("Ff", Object::Integer(opts.flags.to_flags() as i64));
330            }
331        }
332
333        let field_name = checkbox.name;
334        let mut form_field = FormField::new(field_dict);
335        form_field.add_widget(widget);
336
337        // Create object reference
338        let obj_ref = ObjectReference::new(self.next_field_id, 0);
339        self.next_field_id += 1;
340        self.field_refs.insert(field_name.clone(), obj_ref);
341        self.fields.insert(field_name, form_field);
342        self.acro_form.add_field(obj_ref);
343
344        Ok(obj_ref)
345    }
346
347    /// Add a push button
348    pub fn add_push_button(
349        &mut self,
350        button: PushButton,
351        widget: Widget,
352        options: Option<FieldOptions>,
353    ) -> Result<ObjectReference> {
354        let mut field_dict = button.to_dict();
355
356        // Apply options
357        if let Some(opts) = options {
358            if opts.flags.to_flags() != 0 {
359                field_dict.set("Ff", Object::Integer(opts.flags.to_flags() as i64));
360            }
361        }
362
363        let field_name = button.name;
364        let mut form_field = FormField::new(field_dict);
365        form_field.add_widget(widget);
366
367        // Create object reference
368        let obj_ref = ObjectReference::new(self.next_field_id, 0);
369        self.next_field_id += 1;
370        self.field_refs.insert(field_name.clone(), obj_ref);
371        self.fields.insert(field_name, form_field);
372        self.acro_form.add_field(obj_ref);
373
374        Ok(obj_ref)
375    }
376
377    /// Add a radio button group
378    pub fn add_radio_buttons(
379        &mut self,
380        radio: RadioButton,
381        widgets: Vec<Widget>,
382        options: Option<FieldOptions>,
383    ) -> Result<ObjectReference> {
384        let mut field_dict = radio.to_dict();
385
386        // Apply options
387        if let Some(opts) = options {
388            if opts.flags.to_flags() != 0 {
389                field_dict.set("Ff", Object::Integer(opts.flags.to_flags() as i64));
390            }
391        }
392
393        let field_name = radio.name;
394        let mut form_field = FormField::new(field_dict);
395
396        // Add all widgets
397        for widget in widgets {
398            form_field.add_widget(widget);
399        }
400
401        // Create object reference
402        let obj_ref = ObjectReference::new(self.next_field_id, 0);
403        self.next_field_id += 1;
404        self.field_refs.insert(field_name.clone(), obj_ref);
405        self.fields.insert(field_name, form_field);
406        self.acro_form.add_field(obj_ref);
407
408        Ok(obj_ref)
409    }
410
411    /// Get the AcroForm dictionary
412    pub fn get_acro_form(&self) -> &AcroForm {
413        &self.acro_form
414    }
415
416    /// Get all fields
417    pub fn fields(&self) -> &HashMap<String, FormField> {
418        &self.fields
419    }
420
421    /// Get field by name
422    pub fn get_field(&self, name: &str) -> Option<&FormField> {
423        self.fields.get(name)
424    }
425
426    /// Get a mutable reference to a field by name.
427    ///
428    /// This is the primary entry point for mutating an AcroForm field's
429    /// dictionary (for example, to set `/V` during form filling) and its
430    /// associated widgets' appearance streams. It returns `None` if the
431    /// field does not exist — callers are expected to surface that as an
432    /// error to the user (see `Document::fill_field`).
433    pub fn get_field_mut(&mut self, name: &str) -> Option<&mut FormField> {
434        self.fields.get_mut(name)
435    }
436
437    /// Get the placeholder `ObjectReference` recorded for a field by name.
438    ///
439    /// The reference is a *placeholder* produced by `FormManager`'s local
440    /// counter at `add_*_field` time. It is the same value that widget
441    /// annotations created via `Page::add_form_widget_with_ref` store as
442    /// their `/Parent`, so this accessor is the bridge used by
443    /// `Document::fill_field` to locate the matching widget annotations on
444    /// page dictionaries without relying on field-name matching (which
445    /// would be fragile for nested field hierarchies in the future).
446    ///
447    /// Returns `None` if the field does not exist. Scoped `pub(crate)`
448    /// because the placeholder id is a writer-internal concept — the
449    /// public surface is `Document::fill_field`, not direct manipulation
450    /// of placeholder refs.
451    pub(crate) fn field_ref(&self, name: &str) -> Option<ObjectReference> {
452        self.field_refs.get(name).copied()
453    }
454
455    /// Set default appearance for all fields
456    pub fn set_default_appearance(&mut self, da: impl Into<String>) {
457        self.acro_form.da = Some(da.into());
458    }
459
460    /// Set default resources
461    pub fn set_default_resources(&mut self, resources: Dictionary) {
462        self.acro_form.dr = Some(resources);
463    }
464}
465
466impl Default for FormManager {
467    fn default() -> Self {
468        Self::new()
469    }
470}
471
472impl FormManager {
473    /// Get the number of fields managed by this FormManager
474    pub fn field_count(&self) -> usize {
475        self.fields.len()
476    }
477
478    /// Iterate over fields in a deterministic (alphabetical by name) order,
479    /// paired with the placeholder `ObjectReference` that was returned to
480    /// the caller when the field was added.
481    ///
482    /// The underlying storage is a `HashMap`, which has non-deterministic
483    /// iteration. Serializers that need reproducible output (diff-stable
484    /// `/AcroForm/Fields` arrays, object-id allocations, etc.) MUST use
485    /// this method instead of `fields().iter()`. The placeholder ref is
486    /// the one the writer uses to build the placeholder → real-id map
487    /// consumed by widget-annotation `/Parent` resolution.
488    ///
489    /// Scoped to `pub(crate)` because the placeholder `ObjectReference` is
490    /// a writer-internal concept — leaking it in the public API would
491    /// couple external callers to a serialization detail that is expected
492    /// to evolve. Public iteration can go through [`FormManager::fields`].
493    ///
494    /// # Panics
495    ///
496    /// The iterator panics if the internal `fields` / `field_refs` maps
497    /// desynchronize — a "can't happen" invariant that every
498    /// `add_*_field` method is responsible for upholding. Panicking
499    /// here is acceptable because (a) the invariant is entirely under
500    /// this crate's control and (b) a broken invariant signals memory
501    /// corruption or a logic bug in `add_*_field`, not an ISO 32000-1
502    /// conformance issue — the writer has no meaningful recovery path.
503    /// The previous `Result` wrapper was removed (PR3 / QUAL-6) because
504    /// its error arm was observably unreachable and forced every call
505    /// site to thread `?` for no benefit.
506    pub(crate) fn iter_fields_sorted(
507        &self,
508    ) -> impl Iterator<Item = (&String, &FormField, ObjectReference)> {
509        let mut keys: Vec<&String> = self.fields.keys().collect();
510        keys.sort();
511        keys.into_iter().map(move |k| {
512            // `k` was just produced by `self.fields.keys()`, so this
513            // lookup is infallible under a single immutable borrow.
514            let field = self
515                .fields
516                .get(k)
517                .expect("FormManager invariant: key from fields.keys() must resolve in fields");
518            let placeholder = *self.field_refs.get(k).expect(
519                "FormManager invariant: every field in `fields` must have an entry in \
520                 `field_refs` — check add_*_field",
521            );
522            (k, field, placeholder)
523        })
524    }
525}
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530    use crate::forms::FieldFlags;
531    use crate::geometry::{Point, Rectangle};
532
533    #[test]
534    fn test_acro_form() {
535        let mut acro_form = AcroForm::new();
536        acro_form.add_field(ObjectReference::new(1, 0));
537        acro_form.add_field(ObjectReference::new(2, 0));
538
539        let dict = acro_form.to_dict();
540        assert!(dict.get("Fields").is_some());
541        assert_eq!(dict.get("NeedAppearances"), Some(&Object::Boolean(true)));
542        assert!(dict.get("DA").is_some());
543    }
544
545    #[test]
546    fn test_form_data() {
547        let mut form_data = FormData::new();
548        form_data.set_value("name", "John Doe");
549        form_data.set_value("email", "john@example.com");
550
551        assert_eq!(form_data.get_value("name"), Some("John Doe"));
552        assert_eq!(form_data.get_value("email"), Some("john@example.com"));
553        assert_eq!(form_data.get_value("phone"), None);
554    }
555
556    #[test]
557    fn test_form_manager_text_field() {
558        let mut manager = FormManager::new();
559
560        let field = TextField::new("username").with_default_value("guest");
561        let widget = Widget::new(Rectangle::new(
562            Point::new(100.0, 100.0),
563            Point::new(300.0, 120.0),
564        ));
565
566        let obj_ref = manager.add_text_field(field, widget, None).unwrap();
567        assert_eq!(obj_ref.number(), 1);
568        assert!(manager.get_field("username").is_some());
569    }
570
571    #[test]
572    fn test_form_manager_checkbox() {
573        let mut manager = FormManager::new();
574
575        let checkbox = CheckBox::new("agree").checked();
576        let widget = Widget::new(Rectangle::new(
577            Point::new(100.0, 100.0),
578            Point::new(115.0, 115.0),
579        ));
580
581        let obj_ref = manager.add_checkbox(checkbox, widget, None).unwrap();
582        assert_eq!(obj_ref.number(), 1);
583        assert!(manager.get_field("agree").is_some());
584    }
585
586    /// PR3 / QUAL-6: `iter_fields_sorted` is non-fallible after cleanup.
587    /// Exercises two invariants at once:
588    ///   * The iterator yields the flat tuple `(&String, &FormField, ObjectReference)`
589    ///     — not a `Result<_>`, because the only previously-documented
590    ///     error path ("sister maps desynchronised") is a "can't happen"
591    ///     invariant upheld by every `add_*_field` call.
592    ///   * Fields are emitted in ASCII-lexicographic order of their
593    ///     partial names, regardless of insertion order. This is what
594    ///     keeps writer object-id allocation reproducible across runs.
595    #[test]
596    fn iter_fields_sorted_is_non_fallible_and_ordered() {
597        let mut manager = FormManager::new();
598        // Deliberately unsorted insertion: "zeta", "alpha", "mu".
599        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(100.0, 20.0));
600        manager
601            .add_text_field(TextField::new("zeta"), Widget::new(rect), None)
602            .expect("add zeta");
603        manager
604            .add_text_field(TextField::new("alpha"), Widget::new(rect), None)
605            .expect("add alpha");
606        manager
607            .add_text_field(TextField::new("mu"), Widget::new(rect), None)
608            .expect("add mu");
609
610        let names: Vec<String> = manager
611            .iter_fields_sorted()
612            .map(|(name, _field, _placeholder)| name.clone())
613            .collect();
614        assert_eq!(
615            names,
616            vec!["alpha".to_string(), "mu".to_string(), "zeta".to_string()],
617            "iter_fields_sorted must yield keys in ASCII-lexicographic order"
618        );
619    }
620
621    #[test]
622    fn test_form_manager_multiple_fields() {
623        let mut manager = FormManager::new();
624
625        // Add text field
626        let text_field = TextField::new("name");
627        let text_widget = Widget::new(Rectangle::new(
628            Point::new(100.0, 200.0),
629            Point::new(300.0, 220.0),
630        ));
631        manager
632            .add_text_field(text_field, text_widget, None)
633            .unwrap();
634
635        // Add checkbox
636        let checkbox = CheckBox::new("subscribe");
637        let check_widget = Widget::new(Rectangle::new(
638            Point::new(100.0, 150.0),
639            Point::new(115.0, 165.0),
640        ));
641        manager.add_checkbox(checkbox, check_widget, None).unwrap();
642
643        assert_eq!(manager.fields().len(), 2);
644        assert!(manager.get_field("name").is_some());
645        assert!(manager.get_field("subscribe").is_some());
646    }
647
648    #[test]
649    fn test_acro_form_comprehensive() {
650        let mut acro_form = AcroForm::new();
651
652        // Test initial state
653        assert_eq!(acro_form.fields.len(), 0);
654        assert!(acro_form.need_appearances);
655        assert!(acro_form.sig_flags.is_none());
656        assert!(acro_form.co.is_none());
657        assert!(acro_form.dr.is_none());
658        assert!(acro_form.da.is_some());
659        assert!(acro_form.q.is_none());
660
661        // Add multiple fields
662        for i in 1..=5 {
663            acro_form.add_field(ObjectReference::new(i, 0));
664        }
665        assert_eq!(acro_form.fields.len(), 5);
666
667        // Set optional properties
668        acro_form.sig_flags = Some(3);
669        acro_form.co = Some(vec![ObjectReference::new(1, 0), ObjectReference::new(2, 0)]);
670        acro_form.q = Some(2); // Right alignment
671
672        let mut dr = Dictionary::new();
673        dr.set("Font", Object::String("Helvetica".to_string()));
674        acro_form.dr = Some(dr);
675
676        // Test dictionary conversion
677        let dict = acro_form.to_dict();
678
679        // Verify Fields array
680        if let Some(Object::Array(fields)) = dict.get("Fields") {
681            assert_eq!(fields.len(), 5);
682            for (i, field) in fields.iter().enumerate() {
683                assert_eq!(
684                    *field,
685                    Object::Reference(ObjectReference::new((i + 1) as u32, 0))
686                );
687            }
688        } else {
689            panic!("Expected Fields array");
690        }
691
692        // Verify other properties
693        assert_eq!(dict.get("NeedAppearances"), Some(&Object::Boolean(true)));
694        assert_eq!(dict.get("SigFlags"), Some(&Object::Integer(3)));
695        assert_eq!(dict.get("Q"), Some(&Object::Integer(2)));
696        assert!(dict.get("CO").is_some());
697        assert!(dict.get("DR").is_some());
698        assert!(dict.get("DA").is_some());
699    }
700
701    #[test]
702    fn test_acro_form_default() {
703        let acro_form = AcroForm::default();
704        let default_form = AcroForm::new();
705
706        assert_eq!(acro_form.fields.len(), default_form.fields.len());
707        assert_eq!(acro_form.need_appearances, default_form.need_appearances);
708        assert_eq!(acro_form.sig_flags, default_form.sig_flags);
709        assert_eq!(acro_form.da, default_form.da);
710    }
711
712    #[test]
713    fn test_acro_form_debug_clone() {
714        let mut acro_form = AcroForm::new();
715        acro_form.add_field(ObjectReference::new(1, 0));
716        acro_form.sig_flags = Some(1);
717
718        let debug_str = format!("{acro_form:?}");
719        assert!(debug_str.contains("AcroForm"));
720
721        let cloned = acro_form.clone();
722        assert_eq!(acro_form.fields, cloned.fields);
723        assert_eq!(acro_form.sig_flags, cloned.sig_flags);
724        assert_eq!(acro_form.need_appearances, cloned.need_appearances);
725    }
726
727    #[test]
728    fn test_form_data_comprehensive() {
729        let mut form_data = FormData::new();
730
731        // Test initial state
732        assert_eq!(form_data.values.len(), 0);
733        assert_eq!(form_data.get_value("any"), None);
734
735        // Test setting various string types
736        form_data.set_value("string_literal", "test");
737        form_data.set_value(String::from("string_owned"), "test2");
738        form_data.set_value("number", "42");
739        form_data.set_value("empty", "");
740        form_data.set_value("unicode", "café ñoño 你好");
741
742        assert_eq!(form_data.values.len(), 5);
743        assert_eq!(form_data.get_value("string_literal"), Some("test"));
744        assert_eq!(form_data.get_value("string_owned"), Some("test2"));
745        assert_eq!(form_data.get_value("number"), Some("42"));
746        assert_eq!(form_data.get_value("empty"), Some(""));
747        assert_eq!(form_data.get_value("unicode"), Some("café ñoño 你好"));
748
749        // Test overwriting values
750        form_data.set_value("string_literal", "overwritten");
751        assert_eq!(form_data.get_value("string_literal"), Some("overwritten"));
752        assert_eq!(form_data.values.len(), 5); // Count shouldn't change
753
754        // Test case sensitivity
755        form_data.set_value("Test", "uppercase");
756        form_data.set_value("test", "lowercase");
757        assert_eq!(form_data.get_value("Test"), Some("uppercase"));
758        assert_eq!(form_data.get_value("test"), Some("lowercase"));
759        assert_eq!(form_data.values.len(), 7);
760    }
761
762    #[test]
763    fn test_form_data_edge_cases() {
764        let mut form_data = FormData::new();
765
766        // Empty field names
767        form_data.set_value("", "empty_name");
768        assert_eq!(form_data.get_value(""), Some("empty_name"));
769
770        // Very long field names and values
771        let long_name = "a".repeat(1000);
772        let long_value = "b".repeat(2000);
773        form_data.set_value(&long_name, &long_value);
774        assert_eq!(form_data.get_value(&long_name), Some(long_value.as_str()));
775
776        // Special characters in field names
777        form_data.set_value("field/with/slashes", "value1");
778        form_data.set_value("field with spaces", "value2");
779        form_data.set_value("field.with.dots", "value3");
780        form_data.set_value("field-with-dashes", "value4");
781        form_data.set_value("field_with_underscores", "value5");
782
783        assert_eq!(form_data.get_value("field/with/slashes"), Some("value1"));
784        assert_eq!(form_data.get_value("field with spaces"), Some("value2"));
785        assert_eq!(form_data.get_value("field.with.dots"), Some("value3"));
786        assert_eq!(form_data.get_value("field-with-dashes"), Some("value4"));
787        assert_eq!(
788            form_data.get_value("field_with_underscores"),
789            Some("value5")
790        );
791    }
792
793    #[test]
794    fn test_form_data_default_debug_clone() {
795        let form_data = FormData::default();
796        assert_eq!(form_data.values.len(), 0);
797
798        let default_form = FormData::new();
799        assert_eq!(form_data.values.len(), default_form.values.len());
800
801        let debug_str = format!("{form_data:?}");
802        assert!(debug_str.contains("FormData"));
803
804        let cloned = form_data.clone();
805        assert_eq!(form_data.values.len(), cloned.values.len());
806    }
807
808    #[test]
809    fn test_form_manager_comprehensive() {
810        let mut manager = FormManager::new();
811
812        // Test initial state
813        assert_eq!(manager.field_count(), 0);
814        assert_eq!(manager.fields().len(), 0);
815        assert!(manager.get_field("nonexistent").is_none());
816
817        let acroform = manager.get_acro_form();
818        assert_eq!(acroform.fields.len(), 0);
819        assert!(acroform.need_appearances);
820
821        // Test field ID generation
822        let field1 = TextField::new("field1");
823        let widget1 = Widget::new(Rectangle::new(
824            Point::new(0.0, 0.0),
825            Point::new(100.0, 20.0),
826        ));
827        let ref1 = manager.add_text_field(field1, widget1, None).unwrap();
828        assert_eq!(ref1.number(), 1);
829
830        let field2 = TextField::new("field2");
831        let widget2 = Widget::new(Rectangle::new(
832            Point::new(0.0, 30.0),
833            Point::new(100.0, 50.0),
834        ));
835        let ref2 = manager.add_text_field(field2, widget2, None).unwrap();
836        assert_eq!(ref2.number(), 2);
837
838        let field3 = TextField::new("field3");
839        let widget3 = Widget::new(Rectangle::new(
840            Point::new(0.0, 60.0),
841            Point::new(100.0, 80.0),
842        ));
843        let ref3 = manager.add_text_field(field3, widget3, None).unwrap();
844        assert_eq!(ref3.number(), 3);
845
846        assert_eq!(manager.field_count(), 3);
847        assert_eq!(manager.get_acro_form().fields.len(), 3);
848    }
849
850    #[test]
851    fn test_form_manager_push_button() {
852        let mut manager = FormManager::new();
853
854        let button = PushButton::new("submit").with_caption("Submit");
855        let widget = Widget::new(Rectangle::new(
856            Point::new(200.0, 100.0),
857            Point::new(280.0, 130.0),
858        ));
859
860        let obj_ref = manager.add_push_button(button, widget, None).unwrap();
861        assert_eq!(obj_ref.number(), 1);
862        assert!(manager.get_field("submit").is_some());
863
864        let form_field = manager.get_field("submit").unwrap();
865        let dict = &form_field.field_dict;
866        assert_eq!(dict.get("T"), Some(&Object::String("submit".to_string())));
867        assert_eq!(dict.get("FT"), Some(&Object::Name("Btn".to_string())));
868    }
869
870    #[test]
871    fn test_form_manager_radio_buttons() {
872        let mut manager = FormManager::new();
873
874        let radio = RadioButton::new("gender")
875            .add_option("M", "Male")
876            .add_option("F", "Female")
877            .with_selected(0);
878
879        let widgets = vec![
880            Widget::new(Rectangle::new(
881                Point::new(100.0, 100.0),
882                Point::new(115.0, 115.0),
883            )),
884            Widget::new(Rectangle::new(
885                Point::new(150.0, 100.0),
886                Point::new(165.0, 115.0),
887            )),
888        ];
889
890        let obj_ref = manager.add_radio_buttons(radio, widgets, None).unwrap();
891        assert_eq!(obj_ref.number(), 1);
892        assert!(manager.get_field("gender").is_some());
893
894        let form_field = manager.get_field("gender").unwrap();
895        assert_eq!(form_field.widgets.len(), 2);
896
897        let dict = &form_field.field_dict;
898        assert_eq!(dict.get("T"), Some(&Object::String("gender".to_string())));
899        assert_eq!(dict.get("V"), Some(&Object::Name("M".to_string())));
900    }
901
902    #[test]
903    fn test_form_manager_list_box() {
904        let mut manager = FormManager::new();
905
906        let listbox = ListBox::new("languages")
907            .add_option("en", "English")
908            .add_option("es", "Spanish")
909            .add_option("fr", "French")
910            .multi_select()
911            .with_selected(vec![0, 2]);
912
913        let widget = Widget::new(Rectangle::new(
914            Point::new(100.0, 100.0),
915            Point::new(200.0, 150.0),
916        ));
917
918        let obj_ref = manager.add_list_box(listbox, widget, None).unwrap();
919        assert_eq!(obj_ref.number(), 1);
920        assert!(manager.get_field("languages").is_some());
921
922        let form_field = manager.get_field("languages").unwrap();
923        let dict = &form_field.field_dict;
924        assert_eq!(
925            dict.get("T"),
926            Some(&Object::String("languages".to_string()))
927        );
928        assert_eq!(dict.get("FT"), Some(&Object::Name("Ch".to_string())));
929    }
930
931    #[test]
932    fn test_form_manager_combo_box() {
933        let mut manager = FormManager::new();
934
935        let combo = ComboBox::new("country")
936            .add_option("US", "United States")
937            .add_option("CA", "Canada")
938            .editable()
939            .with_value("US");
940
941        let widget = Widget::new(Rectangle::new(
942            Point::new(100.0, 100.0),
943            Point::new(300.0, 120.0),
944        ));
945
946        let obj_ref = manager.add_combo_box(combo, widget, None).unwrap();
947        assert_eq!(obj_ref.number(), 1);
948        assert!(manager.get_field("country").is_some());
949
950        let form_field = manager.get_field("country").unwrap();
951        let dict = &form_field.field_dict;
952        assert_eq!(dict.get("T"), Some(&Object::String("country".to_string())));
953        assert_eq!(dict.get("V"), Some(&Object::String("US".to_string())));
954    }
955
956    #[test]
957    fn test_form_manager_with_field_options() {
958        let mut manager = FormManager::new();
959
960        let options = FieldOptions {
961            flags: FieldFlags {
962                read_only: true,
963                required: true,
964                no_export: false,
965            },
966            default_appearance: Some("/Times-Roman 12 Tf 0 g".to_string()),
967            quadding: Some(1), // Center
968        };
969
970        let field = TextField::new("readonly_field").with_value("Read Only");
971        let widget = Widget::new(Rectangle::new(
972            Point::new(50.0, 50.0),
973            Point::new(250.0, 70.0),
974        ));
975
976        manager
977            .add_text_field(field, widget, Some(options))
978            .unwrap();
979
980        let form_field = manager.get_field("readonly_field").unwrap();
981        let dict = &form_field.field_dict;
982
983        // Check flags were applied
984        if let Some(Object::Integer(flags)) = dict.get("Ff") {
985            assert_ne!(*flags & (1 << 0), 0); // Read-only flag
986            assert_ne!(*flags & (1 << 1), 0); // Required flag
987            assert_eq!(*flags & (1 << 2), 0); // No-export flag not set
988        } else {
989            panic!("Expected Ff field");
990        }
991
992        // Check appearance and quadding
993        assert_eq!(
994            dict.get("DA"),
995            Some(&Object::String("/Times-Roman 12 Tf 0 g".to_string()))
996        );
997        assert_eq!(dict.get("Q"), Some(&Object::Integer(1)));
998    }
999
1000    #[test]
1001    fn test_form_manager_appearance_resources() {
1002        let mut manager = FormManager::new();
1003
1004        // Test default appearance
1005        manager.set_default_appearance("/Courier 10 Tf 0.5 g");
1006        let acroform = manager.get_acro_form();
1007        assert_eq!(acroform.da, Some("/Courier 10 Tf 0.5 g".to_string()));
1008
1009        // Test default resources
1010        let mut resources = Dictionary::new();
1011        let mut font_dict = Dictionary::new();
1012        font_dict.set("F1", Object::String("Helvetica".to_string()));
1013        font_dict.set("F2", Object::String("Times-Roman".to_string()));
1014        resources.set("Font", Object::Dictionary(font_dict));
1015
1016        let mut color_dict = Dictionary::new();
1017        color_dict.set(
1018            "Red",
1019            Object::Array(vec![
1020                Object::Real(1.0),
1021                Object::Real(0.0),
1022                Object::Real(0.0),
1023            ]),
1024        );
1025        resources.set("ColorSpace", Object::Dictionary(color_dict));
1026
1027        manager.set_default_resources(resources);
1028
1029        let acroform = manager.get_acro_form();
1030        assert!(acroform.dr.is_some());
1031
1032        let dict = acroform.to_dict();
1033        assert_eq!(
1034            dict.get("DA"),
1035            Some(&Object::String("/Courier 10 Tf 0.5 g".to_string()))
1036        );
1037
1038        if let Some(Object::Dictionary(dr_dict)) = dict.get("DR") {
1039            assert!(dr_dict.get("Font").is_some());
1040            assert!(dr_dict.get("ColorSpace").is_some());
1041        } else {
1042            panic!("Expected DR dictionary");
1043        }
1044    }
1045
1046    #[test]
1047    fn test_form_manager_mixed_field_types() {
1048        let mut manager = FormManager::new();
1049
1050        // Add one of each field type
1051        let text_field = TextField::new("name").with_value("John");
1052        let text_widget = Widget::new(Rectangle::new(
1053            Point::new(10.0, 100.0),
1054            Point::new(210.0, 120.0),
1055        ));
1056        manager
1057            .add_text_field(text_field, text_widget, None)
1058            .unwrap();
1059
1060        let checkbox = CheckBox::new("agree").checked();
1061        let check_widget = Widget::new(Rectangle::new(
1062            Point::new(10.0, 80.0),
1063            Point::new(25.0, 95.0),
1064        ));
1065        manager.add_checkbox(checkbox, check_widget, None).unwrap();
1066
1067        let button = PushButton::new("submit");
1068        let button_widget = Widget::new(Rectangle::new(
1069            Point::new(10.0, 50.0),
1070            Point::new(80.0, 75.0),
1071        ));
1072        manager
1073            .add_push_button(button, button_widget, None)
1074            .unwrap();
1075
1076        let radio = RadioButton::new("choice").add_option("A", "Option A");
1077        let radio_widgets = vec![Widget::new(Rectangle::new(
1078            Point::new(10.0, 30.0),
1079            Point::new(25.0, 45.0),
1080        ))];
1081        manager
1082            .add_radio_buttons(radio, radio_widgets, None)
1083            .unwrap();
1084
1085        let listbox = ListBox::new("items").add_option("1", "Item 1");
1086        let list_widget = Widget::new(Rectangle::new(
1087            Point::new(10.0, 10.0),
1088            Point::new(110.0, 25.0),
1089        ));
1090        manager.add_list_box(listbox, list_widget, None).unwrap();
1091
1092        let combo = ComboBox::new("selection").add_option("opt", "Option");
1093        let combo_widget = Widget::new(Rectangle::new(
1094            Point::new(120.0, 10.0),
1095            Point::new(220.0, 25.0),
1096        ));
1097        manager.add_combo_box(combo, combo_widget, None).unwrap();
1098
1099        // Verify all fields were added
1100        assert_eq!(manager.field_count(), 6);
1101        assert!(manager.get_field("name").is_some());
1102        assert!(manager.get_field("agree").is_some());
1103        assert!(manager.get_field("submit").is_some());
1104        assert!(manager.get_field("choice").is_some());
1105        assert!(manager.get_field("items").is_some());
1106        assert!(manager.get_field("selection").is_some());
1107
1108        // Verify AcroForm has all references
1109        let acroform = manager.get_acro_form();
1110        assert_eq!(acroform.fields.len(), 6);
1111
1112        // Verify object references are sequential
1113        for (i, field_ref) in acroform.fields.iter().enumerate() {
1114            assert_eq!(field_ref.number(), (i + 1) as u32);
1115            assert_eq!(field_ref.generation(), 0);
1116        }
1117    }
1118
1119    #[test]
1120    fn test_form_manager_debug_default() {
1121        let manager = FormManager::new();
1122        let debug_str = format!("{manager:?}");
1123        assert!(debug_str.contains("FormManager"));
1124
1125        let default_manager = FormManager::default();
1126        assert_eq!(manager.field_count(), default_manager.field_count());
1127    }
1128
1129    #[test]
1130    fn test_form_manager_error_scenarios() {
1131        let mut manager = FormManager::new();
1132
1133        // Test with empty field names
1134        let empty_field = TextField::new("");
1135        let widget = Widget::new(Rectangle::new(
1136            Point::new(0.0, 0.0),
1137            Point::new(100.0, 20.0),
1138        ));
1139        let result = manager.add_text_field(empty_field, widget, None);
1140        assert!(result.is_ok());
1141        assert!(manager.get_field("").is_some());
1142
1143        // Test duplicate field names (should be allowed)
1144        let field1 = TextField::new("duplicate");
1145        let widget1 = Widget::new(Rectangle::new(
1146            Point::new(0.0, 0.0),
1147            Point::new(100.0, 20.0),
1148        ));
1149        manager.add_text_field(field1, widget1, None).unwrap();
1150
1151        let field2 = TextField::new("duplicate");
1152        let widget2 = Widget::new(Rectangle::new(
1153            Point::new(0.0, 30.0),
1154            Point::new(100.0, 50.0),
1155        ));
1156        manager.add_text_field(field2, widget2, None).unwrap();
1157
1158        // Second field should replace the first
1159        assert_eq!(manager.field_count(), 2); // Empty field + duplicate
1160        assert!(manager.get_field("duplicate").is_some());
1161    }
1162
1163    #[test]
1164    fn test_acro_form_calculation_order() {
1165        let mut acro_form = AcroForm::new();
1166
1167        // Set calculation order
1168        let calc_order = vec![
1169            ObjectReference::new(3, 0),
1170            ObjectReference::new(1, 0),
1171            ObjectReference::new(2, 0),
1172        ];
1173        acro_form.co = Some(calc_order);
1174
1175        let dict = acro_form.to_dict();
1176
1177        if let Some(Object::Array(co_array)) = dict.get("CO") {
1178            assert_eq!(co_array.len(), 3);
1179            assert_eq!(co_array[0], Object::Reference(ObjectReference::new(3, 0)));
1180            assert_eq!(co_array[1], Object::Reference(ObjectReference::new(1, 0)));
1181            assert_eq!(co_array[2], Object::Reference(ObjectReference::new(2, 0)));
1182        } else {
1183            panic!("Expected CO array");
1184        }
1185    }
1186
1187    #[test]
1188    fn test_acro_form_without_optional_fields() {
1189        let acro_form = AcroForm::new();
1190        let dict = acro_form.to_dict();
1191
1192        // These should not be present
1193        assert!(dict.get("SigFlags").is_none());
1194        assert!(dict.get("CO").is_none());
1195        assert!(dict.get("DR").is_none());
1196        assert!(dict.get("Q").is_none());
1197
1198        // These should be present
1199        assert!(dict.get("Fields").is_some());
1200        assert!(dict.get("NeedAppearances").is_some());
1201        assert!(dict.get("DA").is_some());
1202    }
1203}