use crate::geometry::Rectangle;
use crate::objects::{Dictionary, Object};
pub fn create_text_field_dict(
name: &str,
rect: Rectangle,
default_value: Option<&str>,
) -> Dictionary {
let mut dict = Dictionary::new();
dict.set("Type", Object::Name("Annot".to_string()));
dict.set("Subtype", Object::Name("Widget".to_string()));
dict.set("FT", Object::Name("Tx".to_string())); dict.set("T", Object::String(name.to_string()));
dict.set(
"Rect",
Object::Array(vec![
Object::Real(rect.lower_left.x),
Object::Real(rect.lower_left.y),
Object::Real(rect.upper_right.x),
Object::Real(rect.upper_right.y),
]),
);
dict.set("DA", Object::String("/Helv 12 Tf 0 g".to_string()));
if let Some(value) = default_value {
dict.set("V", Object::String(value.to_string()));
dict.set("DV", Object::String(value.to_string()));
}
dict.set("F", Object::Integer(4));
let mut mk = Dictionary::new();
mk.set("BC", Object::Array(vec![Object::Real(0.0)])); mk.set("BG", Object::Array(vec![Object::Real(1.0)])); dict.set("MK", Object::Dictionary(mk));
let mut bs = Dictionary::new();
bs.set("W", Object::Real(1.0));
bs.set("S", Object::Name("S".to_string())); dict.set("BS", Object::Dictionary(bs));
dict
}
pub fn create_checkbox_dict(name: &str, rect: Rectangle, checked: bool) -> Dictionary {
let mut dict = Dictionary::new();
dict.set("Type", Object::Name("Annot".to_string()));
dict.set("Subtype", Object::Name("Widget".to_string()));
dict.set("FT", Object::Name("Btn".to_string())); dict.set("T", Object::String(name.to_string()));
dict.set(
"Rect",
Object::Array(vec![
Object::Real(rect.lower_left.x),
Object::Real(rect.lower_left.y),
Object::Real(rect.upper_right.x),
Object::Real(rect.upper_right.y),
]),
);
if checked {
dict.set("V", Object::Name("Yes".to_string()));
dict.set("AS", Object::Name("Yes".to_string())); } else {
dict.set("V", Object::Name("Off".to_string()));
dict.set("AS", Object::Name("Off".to_string()));
}
dict.set("F", Object::Integer(4));
let mut mk = Dictionary::new();
mk.set("BC", Object::Array(vec![Object::Real(0.0)])); mk.set("BG", Object::Array(vec![Object::Real(1.0)])); dict.set("MK", Object::Dictionary(mk));
dict
}
pub fn create_radio_button_dict(
name: &str,
rect: Rectangle,
export_value: &str,
checked: bool,
) -> Dictionary {
let mut dict = Dictionary::new();
dict.set("Type", Object::Name("Annot".to_string()));
dict.set("Subtype", Object::Name("Widget".to_string()));
dict.set("FT", Object::Name("Btn".to_string())); dict.set("T", Object::String(name.to_string()));
dict.set(
"Rect",
Object::Array(vec![
Object::Real(rect.lower_left.x),
Object::Real(rect.lower_left.y),
Object::Real(rect.upper_right.x),
Object::Real(rect.upper_right.y),
]),
);
dict.set("Ff", Object::Integer((1 << 15) | (1 << 16)));
if checked {
dict.set("V", Object::Name(export_value.to_string()));
dict.set("AS", Object::Name(export_value.to_string()));
} else {
dict.set("AS", Object::Name("Off".to_string()));
}
dict.set("F", Object::Integer(4));
let mut mk = Dictionary::new();
mk.set("BC", Object::Array(vec![Object::Real(0.0)])); mk.set("BG", Object::Array(vec![Object::Real(1.0)])); mk.set("CA", Object::String("l".to_string())); dict.set("MK", Object::Dictionary(mk));
dict
}
pub fn create_combo_box_dict(
name: &str,
rect: Rectangle,
options: Vec<(&str, &str)>, default_value: Option<&str>,
) -> Dictionary {
let mut dict = Dictionary::new();
dict.set("Type", Object::Name("Annot".to_string()));
dict.set("Subtype", Object::Name("Widget".to_string()));
dict.set("FT", Object::Name("Ch".to_string())); dict.set("T", Object::String(name.to_string()));
dict.set(
"Rect",
Object::Array(vec![
Object::Real(rect.lower_left.x),
Object::Real(rect.lower_left.y),
Object::Real(rect.upper_right.x),
Object::Real(rect.upper_right.y),
]),
);
dict.set("Ff", Object::Integer(1 << 17));
let opt_array: Vec<Object> = options
.iter()
.map(|(export, display)| {
if export == display {
Object::String((*display).to_string())
} else {
Object::Array(vec![
Object::String((*export).to_string()),
Object::String((*display).to_string()),
])
}
})
.collect();
dict.set("Opt", Object::Array(opt_array));
if let Some(value) = default_value {
dict.set("V", Object::String(value.to_string()));
dict.set("DV", Object::String(value.to_string()));
}
dict.set("DA", Object::String("/Helv 12 Tf 0 g".to_string()));
dict.set("F", Object::Integer(4));
let mut mk = Dictionary::new();
mk.set("BC", Object::Array(vec![Object::Real(0.0)])); mk.set("BG", Object::Array(vec![Object::Real(1.0)])); dict.set("MK", Object::Dictionary(mk));
let mut bs = Dictionary::new();
bs.set("W", Object::Real(1.0));
bs.set("S", Object::Name("S".to_string())); dict.set("BS", Object::Dictionary(bs));
dict
}
pub fn create_list_box_dict(
name: &str,
rect: Rectangle,
options: Vec<(&str, &str)>, selected: Vec<usize>, multi_select: bool,
) -> Dictionary {
let mut dict = Dictionary::new();
dict.set("Type", Object::Name("Annot".to_string()));
dict.set("Subtype", Object::Name("Widget".to_string()));
dict.set("FT", Object::Name("Ch".to_string())); dict.set("T", Object::String(name.to_string()));
dict.set(
"Rect",
Object::Array(vec![
Object::Real(rect.lower_left.x),
Object::Real(rect.lower_left.y),
Object::Real(rect.upper_right.x),
Object::Real(rect.upper_right.y),
]),
);
let mut flags = 0u32;
if multi_select {
flags |= 1 << 21; }
dict.set("Ff", Object::Integer(flags as i64));
let opt_array: Vec<Object> = options
.iter()
.map(|(export, display)| {
if export == display {
Object::String((*display).to_string())
} else {
Object::Array(vec![
Object::String((*export).to_string()),
Object::String((*display).to_string()),
])
}
})
.collect();
dict.set("Opt", Object::Array(opt_array));
if !selected.is_empty() {
if multi_select {
let indices: Vec<Object> = selected
.iter()
.map(|&i| Object::Integer(i as i64))
.collect();
dict.set("I", Object::Array(indices));
} else if let Some(&index) = selected.first() {
if let Some((export_value, _)) = options.get(index) {
dict.set("V", Object::String((*export_value).to_string()));
}
}
}
dict.set("DA", Object::String("/Helv 12 Tf 0 g".to_string()));
dict.set("F", Object::Integer(4));
let mut mk = Dictionary::new();
mk.set("BC", Object::Array(vec![Object::Real(0.0)])); mk.set("BG", Object::Array(vec![Object::Real(1.0)])); dict.set("MK", Object::Dictionary(mk));
let mut bs = Dictionary::new();
bs.set("W", Object::Real(1.0));
bs.set("S", Object::Name("I".to_string())); dict.set("BS", Object::Dictionary(bs));
dict
}
pub fn create_push_button_dict(name: &str, rect: Rectangle, caption: &str) -> Dictionary {
let mut dict = Dictionary::new();
dict.set("Type", Object::Name("Annot".to_string()));
dict.set("Subtype", Object::Name("Widget".to_string()));
dict.set("FT", Object::Name("Btn".to_string())); dict.set("T", Object::String(name.to_string()));
dict.set(
"Rect",
Object::Array(vec![
Object::Real(rect.lower_left.x),
Object::Real(rect.lower_left.y),
Object::Real(rect.upper_right.x),
Object::Real(rect.upper_right.y),
]),
);
dict.set("Ff", Object::Integer(1 << 16));
dict.set("F", Object::Integer(4));
let mut mk = Dictionary::new();
mk.set(
"BC",
Object::Array(vec![
Object::Real(0.2),
Object::Real(0.2),
Object::Real(0.2),
]),
); mk.set(
"BG",
Object::Array(vec![
Object::Real(0.9),
Object::Real(0.9),
Object::Real(0.9),
]),
); mk.set("CA", Object::String(caption.to_string())); dict.set("MK", Object::Dictionary(mk));
let mut bs = Dictionary::new();
bs.set("W", Object::Real(2.0));
bs.set("S", Object::Name("B".to_string())); dict.set("BS", Object::Dictionary(bs));
dict.set("DA", Object::String("/Helv 12 Tf 0 g".to_string()));
dict
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Point;
fn create_test_rect() -> Rectangle {
Rectangle {
lower_left: Point { x: 100.0, y: 200.0 },
upper_right: Point { x: 300.0, y: 250.0 },
}
}
#[test]
fn test_create_text_field_dict_basic() {
let rect = create_test_rect();
let dict = create_text_field_dict("test_field", rect, None);
assert_eq!(dict.get("Type"), Some(&Object::Name("Annot".to_string())));
assert_eq!(
dict.get("Subtype"),
Some(&Object::Name("Widget".to_string()))
);
assert_eq!(dict.get("FT"), Some(&Object::Name("Tx".to_string())));
assert_eq!(
dict.get("T"),
Some(&Object::String("test_field".to_string()))
);
let expected_rect = Object::Array(vec![
Object::Real(100.0),
Object::Real(200.0),
Object::Real(300.0),
Object::Real(250.0),
]);
assert_eq!(dict.get("Rect"), Some(&expected_rect));
assert_eq!(
dict.get("DA"),
Some(&Object::String("/Helv 12 Tf 0 g".to_string()))
);
assert_eq!(dict.get("F"), Some(&Object::Integer(4)));
assert!(dict.get("V").is_none());
assert!(dict.get("DV").is_none());
}
#[test]
fn test_create_text_field_dict_with_value() {
let rect = create_test_rect();
let dict = create_text_field_dict("test_field", rect, Some("default text"));
assert_eq!(
dict.get("V"),
Some(&Object::String("default text".to_string()))
);
assert_eq!(
dict.get("DV"),
Some(&Object::String("default text".to_string()))
);
}
#[test]
fn test_create_text_field_dict_appearance_characteristics() {
let rect = create_test_rect();
let dict = create_text_field_dict("test_field", rect, None);
if let Some(Object::Dictionary(mk)) = dict.get("MK") {
assert_eq!(mk.get("BC"), Some(&Object::Array(vec![Object::Real(0.0)])));
assert_eq!(mk.get("BG"), Some(&Object::Array(vec![Object::Real(1.0)])));
} else {
panic!("MK dictionary not found or wrong type");
}
if let Some(Object::Dictionary(bs)) = dict.get("BS") {
assert_eq!(bs.get("W"), Some(&Object::Real(1.0)));
assert_eq!(bs.get("S"), Some(&Object::Name("S".to_string())));
} else {
panic!("BS dictionary not found or wrong type");
}
}
#[test]
fn test_create_checkbox_dict_unchecked() {
let rect = create_test_rect();
let dict = create_checkbox_dict("checkbox_field", rect, false);
assert_eq!(dict.get("FT"), Some(&Object::Name("Btn".to_string())));
assert_eq!(dict.get("V"), Some(&Object::Name("Off".to_string())));
assert_eq!(dict.get("AS"), Some(&Object::Name("Off".to_string())));
}
#[test]
fn test_create_checkbox_dict_checked() {
let rect = create_test_rect();
let dict = create_checkbox_dict("checkbox_field", rect, true);
assert_eq!(dict.get("V"), Some(&Object::Name("Yes".to_string())));
assert_eq!(dict.get("AS"), Some(&Object::Name("Yes".to_string())));
}
#[test]
fn test_create_radio_button_dict_unchecked() {
let rect = create_test_rect();
let dict = create_radio_button_dict("radio_field", rect, "option1", false);
assert_eq!(dict.get("FT"), Some(&Object::Name("Btn".to_string())));
let expected_flags = (1 << 15) | (1 << 16);
assert_eq!(dict.get("Ff"), Some(&Object::Integer(expected_flags)));
assert_eq!(dict.get("AS"), Some(&Object::Name("Off".to_string())));
assert!(dict.get("V").is_none());
}
#[test]
fn test_create_radio_button_dict_checked() {
let rect = create_test_rect();
let dict = create_radio_button_dict("radio_field", rect, "option1", true);
assert_eq!(dict.get("V"), Some(&Object::Name("option1".to_string())));
assert_eq!(dict.get("AS"), Some(&Object::Name("option1".to_string())));
if let Some(Object::Dictionary(mk)) = dict.get("MK") {
assert_eq!(mk.get("CA"), Some(&Object::String("l".to_string())));
} else {
panic!("MK dictionary not found");
}
}
#[test]
fn test_create_combo_box_dict_basic() {
let rect = create_test_rect();
let options = vec![("val1", "Display 1"), ("val2", "Display 2")];
let dict = create_combo_box_dict("combo_field", rect, options, None);
assert_eq!(dict.get("FT"), Some(&Object::Name("Ch".to_string())));
assert_eq!(dict.get("Ff"), Some(&Object::Integer(1 << 17)));
if let Some(Object::Array(opt_array)) = dict.get("Opt") {
assert_eq!(opt_array.len(), 2);
if let Object::Array(first_opt) = &opt_array[0] {
assert_eq!(first_opt[0], Object::String("val1".to_string()));
assert_eq!(first_opt[1], Object::String("Display 1".to_string()));
} else {
panic!("First option should be an array");
}
} else {
panic!("Opt array not found");
}
}
#[test]
fn test_create_combo_box_dict_with_default() {
let rect = create_test_rect();
let options = vec![("val1", "Display 1"), ("val2", "Display 2")];
let dict = create_combo_box_dict("combo_field", rect, options, Some("val1"));
assert_eq!(dict.get("V"), Some(&Object::String("val1".to_string())));
assert_eq!(dict.get("DV"), Some(&Object::String("val1".to_string())));
}
#[test]
fn test_create_combo_box_dict_same_export_display() {
let rect = create_test_rect();
let options = vec![("Option1", "Option1"), ("Option2", "Option2")];
let dict = create_combo_box_dict("combo_field", rect, options, None);
if let Some(Object::Array(opt_array)) = dict.get("Opt") {
assert_eq!(opt_array[0], Object::String("Option1".to_string()));
assert_eq!(opt_array[1], Object::String("Option2".to_string()));
} else {
panic!("Opt array not found");
}
}
#[test]
fn test_create_list_box_dict_single_select() {
let rect = create_test_rect();
let options = vec![("val1", "Display 1"), ("val2", "Display 2")];
let selected = vec![0];
let dict = create_list_box_dict("list_field", rect, options, selected, false);
assert_eq!(dict.get("FT"), Some(&Object::Name("Ch".to_string())));
assert_eq!(dict.get("Ff"), Some(&Object::Integer(0)));
assert_eq!(dict.get("V"), Some(&Object::String("val1".to_string())));
assert!(dict.get("I").is_none());
}
#[test]
fn test_create_list_box_dict_multi_select() {
let rect = create_test_rect();
let options = vec![
("val1", "Display 1"),
("val2", "Display 2"),
("val3", "Display 3"),
];
let selected = vec![0, 2];
let dict = create_list_box_dict("list_field", rect, options, selected, true);
assert_eq!(dict.get("Ff"), Some(&Object::Integer(1 << 21)));
if let Some(Object::Array(indices)) = dict.get("I") {
assert_eq!(indices.len(), 2);
assert_eq!(indices[0], Object::Integer(0));
assert_eq!(indices[1], Object::Integer(2));
} else {
panic!("I array not found for multiselect");
}
assert!(dict.get("V").is_none());
}
#[test]
fn test_create_list_box_dict_no_selection() {
let rect = create_test_rect();
let options = vec![("val1", "Display 1")];
let selected = vec![];
let dict = create_list_box_dict("list_field", rect, options, selected, false);
assert!(dict.get("V").is_none());
assert!(dict.get("I").is_none());
}
#[test]
fn test_create_push_button_dict() {
let rect = create_test_rect();
let dict = create_push_button_dict("button_field", rect, "Click Me");
assert_eq!(dict.get("FT"), Some(&Object::Name("Btn".to_string())));
assert_eq!(dict.get("Ff"), Some(&Object::Integer(1 << 16)));
if let Some(Object::Dictionary(mk)) = dict.get("MK") {
assert_eq!(mk.get("CA"), Some(&Object::String("Click Me".to_string())));
if let Some(Object::Array(bc)) = mk.get("BC") {
assert_eq!(bc.len(), 3);
assert_eq!(bc[0], Object::Real(0.2));
assert_eq!(bc[1], Object::Real(0.2));
assert_eq!(bc[2], Object::Real(0.2));
} else {
panic!("BC array not found");
}
if let Some(Object::Array(bg)) = mk.get("BG") {
assert_eq!(bg.len(), 3);
assert_eq!(bg[0], Object::Real(0.9));
assert_eq!(bg[1], Object::Real(0.9));
assert_eq!(bg[2], Object::Real(0.9));
} else {
panic!("BG array not found");
}
} else {
panic!("MK dictionary not found");
}
if let Some(Object::Dictionary(bs)) = dict.get("BS") {
assert_eq!(bs.get("W"), Some(&Object::Real(2.0)));
assert_eq!(bs.get("S"), Some(&Object::Name("B".to_string())));
} else {
panic!("BS dictionary not found");
}
}
#[test]
fn test_all_fields_have_required_properties() {
let rect = create_test_rect();
let fields = vec![
create_text_field_dict("text", rect, None),
create_checkbox_dict("checkbox", rect, false),
create_radio_button_dict("radio", rect, "val", false),
create_combo_box_dict("combo", rect, vec![("a", "A")], None),
create_list_box_dict("list", rect, vec![("a", "A")], vec![], false),
create_push_button_dict("button", rect, "Button"),
];
for dict in fields {
assert!(dict.get("Type").is_some(), "Missing Type");
assert!(dict.get("Subtype").is_some(), "Missing Subtype");
assert!(dict.get("FT").is_some(), "Missing FT");
assert!(dict.get("T").is_some(), "Missing T");
assert!(dict.get("Rect").is_some(), "Missing Rect");
assert!(dict.get("F").is_some(), "Missing F");
assert!(dict.get("MK").is_some(), "Missing MK");
}
}
#[test]
fn test_field_name_preservation() {
let rect = create_test_rect();
let field_names = ["simple", "with spaces", "unicode_ñ", "123numbers"];
for name in &field_names {
let dict = create_text_field_dict(name, rect, None);
assert_eq!(dict.get("T"), Some(&Object::String((*name).to_string())));
}
}
#[test]
fn test_rectangle_coordinates() {
let rects = vec![
Rectangle {
lower_left: Point { x: 0.0, y: 0.0 },
upper_right: Point { x: 100.0, y: 50.0 },
},
Rectangle {
lower_left: Point { x: -50.0, y: -25.0 },
upper_right: Point { x: 50.0, y: 25.0 },
},
Rectangle {
lower_left: Point {
x: 200.5,
y: 300.75,
},
upper_right: Point {
x: 400.25,
y: 500.125,
},
},
];
for rect in rects {
let dict = create_text_field_dict("test", rect, None);
let expected_rect = Object::Array(vec![
Object::Real(rect.lower_left.x),
Object::Real(rect.lower_left.y),
Object::Real(rect.upper_right.x),
Object::Real(rect.upper_right.y),
]);
assert_eq!(dict.get("Rect"), Some(&expected_rect));
}
}
}