1use crate::error::DocResult;
16use crate::form_field::{FieldValue, FormFieldType};
17use crate::interactive_form::InteractiveForm;
18
19#[derive(Debug, Clone)]
24pub struct FdfData {
25 pub fields: Vec<(String, FieldValue)>,
27}
28
29impl FdfData {
30 pub fn new() -> Self {
32 Self { fields: Vec::new() }
33 }
34}
35
36impl Default for FdfData {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42pub fn export_fdf(form: &InteractiveForm) -> FdfData {
47 let all = form.all_fields();
48 let mut fields = Vec::new();
49
50 for field in all {
51 if let Some(ref val_str) = field.value {
52 let value = match field.field_type {
53 FormFieldType::Text => FieldValue::String(val_str.clone()),
54 FormFieldType::Button => {
55 let checked = field
56 .appearance_state
57 .as_deref()
58 .is_some_and(|s| s != "Off");
59 FieldValue::Bool(checked)
60 }
61 FormFieldType::Choice => FieldValue::String(val_str.clone()),
62 FormFieldType::Signature => continue,
63 };
64 fields.push((field.name.clone(), value));
65 }
66 }
67
68 FdfData { fields }
69}
70
71pub fn import_fdf(form: &mut InteractiveForm, fdf: &FdfData) -> DocResult<usize> {
78 let mut count = 0;
79
80 for (name, value) in &fdf.fields {
81 let field = match form.field_by_name_mut(name) {
82 Some(f) => f,
83 None => continue,
84 };
85 field.set_value(value.clone())?;
86 count += 1;
87 }
88
89 Ok(count)
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::form_field::{ChoiceOption, FormField, FormFieldFlags};
96
97 fn make_text_field(name: &str, value: Option<&str>) -> FormField {
98 FormField {
99 name: name.to_string(),
100 field_type: FormFieldType::Text,
101 value: value.map(|s| s.to_string()),
102 default_value: None,
103 flags: FormFieldFlags::from_bits(0),
104 tooltip: None,
105 alternate_name: None,
106 mapping_name: None,
107 max_len: None,
108 options: Vec::new(),
109 appearance_state: None,
110 children: Vec::new(),
111 controls: Vec::new(),
112 dirty: false,
113 selected_indices: Vec::new(),
114 additional_actions: None,
115 }
116 }
117
118 fn make_button_field(name: &str, state: &str) -> FormField {
119 FormField {
120 name: name.to_string(),
121 field_type: FormFieldType::Button,
122 value: Some(state.to_string()),
123 default_value: None,
124 flags: FormFieldFlags::from_bits(0),
125 tooltip: None,
126 alternate_name: None,
127 mapping_name: None,
128 max_len: None,
129 options: Vec::new(),
130 appearance_state: Some(state.to_string()),
131 children: Vec::new(),
132 controls: Vec::new(),
133 dirty: false,
134 selected_indices: Vec::new(),
135 additional_actions: None,
136 }
137 }
138
139 fn make_choice_field(name: &str, options: &[&str], value: Option<&str>) -> FormField {
140 FormField {
141 name: name.to_string(),
142 field_type: FormFieldType::Choice,
143 value: value.map(|s| s.to_string()),
144 default_value: None,
145 flags: FormFieldFlags::from_bits(0),
146 tooltip: None,
147 alternate_name: None,
148 mapping_name: None,
149 max_len: None,
150 options: options
151 .iter()
152 .map(|s| ChoiceOption {
153 export_value: s.to_string(),
154 display_value: s.to_string(),
155 })
156 .collect(),
157 appearance_state: None,
158 children: Vec::new(),
159 controls: Vec::new(),
160 dirty: false,
161 selected_indices: Vec::new(),
162 additional_actions: None,
163 }
164 }
165
166 #[test]
167 fn test_export_collects_all_fields() {
168 let form = InteractiveForm {
169 calculation_order: Vec::new(),
170 default_appearance: None,
171 default_alignment: crate::variable_text::Alignment::Left,
172 fields: vec![
173 make_text_field("name", Some("Alice")),
174 make_button_field("agree", "Yes"),
175 make_text_field("empty", None),
176 ],
177 };
178
179 let fdf = export_fdf(&form);
180 assert_eq!(fdf.fields.len(), 2);
182 assert_eq!(fdf.fields[0].0, "name");
183 assert_eq!(fdf.fields[0].1, FieldValue::String("Alice".to_string()));
184 assert_eq!(fdf.fields[1].0, "agree");
185 assert_eq!(fdf.fields[1].1, FieldValue::Bool(true));
186 }
187
188 #[test]
189 fn test_import_updates_matching_fields() {
190 let mut form = InteractiveForm {
191 calculation_order: Vec::new(),
192 default_appearance: None,
193 default_alignment: crate::variable_text::Alignment::Left,
194 fields: vec![
195 make_text_field("name", Some("Alice")),
196 make_text_field("email", Some("a@b.com")),
197 ],
198 };
199
200 let fdf = FdfData {
201 fields: vec![
202 ("name".to_string(), FieldValue::String("Bob".to_string())),
203 (
204 "email".to_string(),
205 FieldValue::String("bob@example.com".to_string()),
206 ),
207 (
208 "missing".to_string(),
209 FieldValue::String("skip".to_string()),
210 ),
211 ],
212 };
213
214 let count = import_fdf(&mut form, &fdf).unwrap();
215 assert_eq!(count, 2);
216 assert_eq!(
217 form.field_by_name("name").unwrap().value.as_deref(),
218 Some("Bob")
219 );
220 assert_eq!(
221 form.field_by_name("email").unwrap().value.as_deref(),
222 Some("bob@example.com")
223 );
224 }
225
226 #[test]
227 fn test_import_type_mismatch_returns_error() {
228 let mut form = InteractiveForm {
229 calculation_order: Vec::new(),
230 default_appearance: None,
231 default_alignment: crate::variable_text::Alignment::Left,
232 fields: vec![make_text_field("name", Some("Alice"))],
233 };
234
235 let fdf = FdfData {
236 fields: vec![("name".to_string(), FieldValue::Bool(true))],
237 };
238
239 let result = import_fdf(&mut form, &fdf);
240 assert!(result.is_err());
241 }
242
243 #[test]
244 fn test_roundtrip_export_import() {
245 let form = InteractiveForm {
246 calculation_order: Vec::new(),
247 default_appearance: None,
248 default_alignment: crate::variable_text::Alignment::Left,
249 fields: vec![
250 make_text_field("name", Some("Alice")),
251 make_choice_field("color", &["Red", "Green", "Blue"], Some("Green")),
252 ],
253 };
254
255 let fdf = export_fdf(&form);
256
257 let mut new_form = InteractiveForm {
259 calculation_order: Vec::new(),
260 default_appearance: None,
261 default_alignment: crate::variable_text::Alignment::Left,
262 fields: vec![
263 make_text_field("name", None),
264 make_choice_field("color", &["Red", "Green", "Blue"], None),
265 ],
266 };
267
268 let count = import_fdf(&mut new_form, &fdf).unwrap();
269 assert_eq!(count, 2);
270 assert_eq!(
271 new_form.field_by_name("name").unwrap().value.as_deref(),
272 Some("Alice")
273 );
274 }
275}