use crate::error::DocResult;
use crate::form_field::{FieldValue, FormFieldType};
use crate::interactive_form::InteractiveForm;
#[derive(Debug, Clone)]
pub struct FdfData {
pub fields: Vec<(String, FieldValue)>,
}
impl FdfData {
pub fn new() -> Self {
Self { fields: Vec::new() }
}
}
impl Default for FdfData {
fn default() -> Self {
Self::new()
}
}
pub fn export_fdf(form: &InteractiveForm) -> FdfData {
let all = form.all_fields();
let mut fields = Vec::new();
for field in all {
if let Some(ref val_str) = field.value {
let value = match field.field_type {
FormFieldType::Text => FieldValue::String(val_str.clone()),
FormFieldType::Button => {
let checked = field
.appearance_state
.as_deref()
.is_some_and(|s| s != "Off");
FieldValue::Bool(checked)
}
FormFieldType::Choice => FieldValue::String(val_str.clone()),
FormFieldType::Signature => continue,
};
fields.push((field.name.clone(), value));
}
}
FdfData { fields }
}
pub fn import_fdf(form: &mut InteractiveForm, fdf: &FdfData) -> DocResult<usize> {
let mut count = 0;
for (name, value) in &fdf.fields {
let field = match form.field_by_name_mut(name) {
Some(f) => f,
None => continue,
};
field.set_value(value.clone())?;
count += 1;
}
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::form_field::{ChoiceOption, FormField, FormFieldFlags};
fn make_text_field(name: &str, value: Option<&str>) -> FormField {
FormField {
name: name.to_string(),
field_type: FormFieldType::Text,
value: value.map(|s| s.to_string()),
default_value: None,
flags: FormFieldFlags::from_bits(0),
tooltip: None,
alternate_name: None,
mapping_name: None,
max_len: None,
options: Vec::new(),
appearance_state: None,
children: Vec::new(),
controls: Vec::new(),
dirty: false,
selected_indices: Vec::new(),
additional_actions: None,
}
}
fn make_button_field(name: &str, state: &str) -> FormField {
FormField {
name: name.to_string(),
field_type: FormFieldType::Button,
value: Some(state.to_string()),
default_value: None,
flags: FormFieldFlags::from_bits(0),
tooltip: None,
alternate_name: None,
mapping_name: None,
max_len: None,
options: Vec::new(),
appearance_state: Some(state.to_string()),
children: Vec::new(),
controls: Vec::new(),
dirty: false,
selected_indices: Vec::new(),
additional_actions: None,
}
}
fn make_choice_field(name: &str, options: &[&str], value: Option<&str>) -> FormField {
FormField {
name: name.to_string(),
field_type: FormFieldType::Choice,
value: value.map(|s| s.to_string()),
default_value: None,
flags: FormFieldFlags::from_bits(0),
tooltip: None,
alternate_name: None,
mapping_name: None,
max_len: None,
options: options
.iter()
.map(|s| ChoiceOption {
export_value: s.to_string(),
display_value: s.to_string(),
})
.collect(),
appearance_state: None,
children: Vec::new(),
controls: Vec::new(),
dirty: false,
selected_indices: Vec::new(),
additional_actions: None,
}
}
#[test]
fn test_export_collects_all_fields() {
let form = InteractiveForm {
calculation_order: Vec::new(),
default_appearance: None,
default_alignment: crate::variable_text::Alignment::Left,
fields: vec![
make_text_field("name", Some("Alice")),
make_button_field("agree", "Yes"),
make_text_field("empty", None),
],
};
let fdf = export_fdf(&form);
assert_eq!(fdf.fields.len(), 2);
assert_eq!(fdf.fields[0].0, "name");
assert_eq!(fdf.fields[0].1, FieldValue::String("Alice".to_string()));
assert_eq!(fdf.fields[1].0, "agree");
assert_eq!(fdf.fields[1].1, FieldValue::Bool(true));
}
#[test]
fn test_import_updates_matching_fields() {
let mut form = InteractiveForm {
calculation_order: Vec::new(),
default_appearance: None,
default_alignment: crate::variable_text::Alignment::Left,
fields: vec![
make_text_field("name", Some("Alice")),
make_text_field("email", Some("a@b.com")),
],
};
let fdf = FdfData {
fields: vec![
("name".to_string(), FieldValue::String("Bob".to_string())),
(
"email".to_string(),
FieldValue::String("bob@example.com".to_string()),
),
(
"missing".to_string(),
FieldValue::String("skip".to_string()),
),
],
};
let count = import_fdf(&mut form, &fdf).unwrap();
assert_eq!(count, 2);
assert_eq!(
form.field_by_name("name").unwrap().value.as_deref(),
Some("Bob")
);
assert_eq!(
form.field_by_name("email").unwrap().value.as_deref(),
Some("bob@example.com")
);
}
#[test]
fn test_import_type_mismatch_returns_error() {
let mut form = InteractiveForm {
calculation_order: Vec::new(),
default_appearance: None,
default_alignment: crate::variable_text::Alignment::Left,
fields: vec![make_text_field("name", Some("Alice"))],
};
let fdf = FdfData {
fields: vec![("name".to_string(), FieldValue::Bool(true))],
};
let result = import_fdf(&mut form, &fdf);
assert!(result.is_err());
}
#[test]
fn test_roundtrip_export_import() {
let form = InteractiveForm {
calculation_order: Vec::new(),
default_appearance: None,
default_alignment: crate::variable_text::Alignment::Left,
fields: vec![
make_text_field("name", Some("Alice")),
make_choice_field("color", &["Red", "Green", "Blue"], Some("Green")),
],
};
let fdf = export_fdf(&form);
let mut new_form = InteractiveForm {
calculation_order: Vec::new(),
default_appearance: None,
default_alignment: crate::variable_text::Alignment::Left,
fields: vec![
make_text_field("name", None),
make_choice_field("color", &["Red", "Green", "Blue"], None),
],
};
let count = import_fdf(&mut new_form, &fdf).unwrap();
assert_eq!(count, 2);
assert_eq!(
new_form.field_by_name("name").unwrap().value.as_deref(),
Some("Alice")
);
}
}