use super::{FieldType, FormField, TextAlign};
use crate::objects::{PdfArray, PdfDict, PdfName, PdfObject, PdfRef, PdfString};
pub struct CheckboxAppearanceRefs {
pub off_ref: PdfRef,
pub yes_ref: PdfRef,
}
pub fn create_widget_annotation(
field: &FormField,
page_ref: PdfRef,
appearance_ref: Option<PdfRef>,
checkbox_ap: Option<CheckboxAppearanceRefs>,
) -> PdfDict {
let mut dict = PdfDict::new();
dict.set("Type", PdfObject::Name(PdfName::new("Annot")));
dict.set("Subtype", PdfObject::Name(PdfName::new("Widget")));
let rect = PdfArray::from(vec![
PdfObject::Real(field.rect[0]),
PdfObject::Real(field.rect[1]),
PdfObject::Real(field.rect[2]),
PdfObject::Real(field.rect[3]),
]);
dict.set("Rect", PdfObject::Array(rect));
dict.set("P", PdfObject::Reference(page_ref));
dict.set("T", PdfObject::String(PdfString::from_text(&field.name)));
dict.set(
"FT",
PdfObject::Name(PdfName::new(field.field_type.pdf_name())),
);
if field.flags.value() != 0 {
dict.set("Ff", PdfObject::Integer(field.flags.value() as i64));
}
if let Some(ref value) = field.value {
let v_obj = match field.field_type {
FieldType::Text | FieldType::Choice => PdfObject::String(PdfString::from_text(value)),
FieldType::CheckBox | FieldType::Radio | FieldType::Button => {
PdfObject::Name(PdfName::new(value))
}
FieldType::Signature => PdfObject::Name(PdfName::new(value)),
};
dict.set("V", v_obj.clone());
dict.set("DV", v_obj);
}
if let Some(ref tooltip) = field.tooltip {
dict.set("TU", PdfObject::String(PdfString::from_text(tooltip)));
}
if field.align != TextAlign::Left {
dict.set("Q", PdfObject::Integer(field.align as i64));
}
if let Some(max_len) = field.max_length {
dict.set("MaxLen", PdfObject::Integer(max_len as i64));
}
if !field.options.is_empty() {
let opts: Vec<PdfObject> = field
.options
.iter()
.map(|s| PdfObject::String(PdfString::from_text(s)))
.collect();
dict.set("Opt", PdfObject::Array(PdfArray::from(opts)));
}
let da = format!(
"/{} {} Tf {} {} {} rg",
field.font,
if field.font_size == 0.0 {
12.0
} else {
field.font_size
},
field.text_color[0],
field.text_color[1],
field.text_color[2]
);
dict.set("DA", PdfObject::String(da.into()));
if field.border_color.is_some() {
let mut bs = PdfDict::new();
bs.set("W", PdfObject::Integer(1)); bs.set("S", PdfObject::Name(PdfName::new("S"))); dict.set("BS", PdfObject::Dict(bs));
}
let has_bg = field.background_color.is_some();
let has_bc = field.border_color.is_some();
let is_checkbox = field.field_type == FieldType::CheckBox;
if has_bg || has_bc || is_checkbox {
let mut mk = PdfDict::new();
if let Some(ref bg) = field.background_color {
let bg_arr = PdfArray::from(vec![
PdfObject::Real(bg[0]),
PdfObject::Real(bg[1]),
PdfObject::Real(bg[2]),
]);
mk.set("BG", PdfObject::Array(bg_arr));
}
if let Some(ref bc) = field.border_color {
let bc_arr = PdfArray::from(vec![
PdfObject::Real(bc[0]),
PdfObject::Real(bc[1]),
PdfObject::Real(bc[2]),
]);
mk.set("BC", PdfObject::Array(bc_arr));
}
if is_checkbox {
mk.set("CA", PdfObject::String("4".into())); }
dict.set("MK", PdfObject::Dict(mk));
}
if let Some(cb_ap) = checkbox_ap {
let mut n_dict = PdfDict::new();
n_dict.set("Yes", PdfObject::Reference(cb_ap.yes_ref));
n_dict.set("Off", PdfObject::Reference(cb_ap.off_ref));
let mut ap = PdfDict::new();
ap.set("N", PdfObject::Dict(n_dict));
dict.set("AP", PdfObject::Dict(ap));
let is_checked = field.value.as_deref() == Some("Yes");
dict.set(
"AS",
PdfObject::Name(PdfName::new(if is_checked { "Yes" } else { "Off" })),
);
} else if let Some(ap_ref) = appearance_ref {
let mut ap = PdfDict::new();
ap.set("N", PdfObject::Reference(ap_ref));
dict.set("AP", PdfObject::Dict(ap));
}
dict.set("F", PdfObject::Integer(4));
dict
}
pub fn create_acro_form_dict(
field_refs: &[PdfRef],
font_refs: &[(String, PdfRef)],
need_appearances: bool,
default_appearance: Option<&str>,
) -> PdfDict {
let mut dict = PdfDict::new();
let fields: Vec<PdfObject> = field_refs
.iter()
.map(|r| PdfObject::Reference(*r))
.collect();
dict.set("Fields", PdfObject::Array(PdfArray::from(fields)));
if need_appearances {
dict.set("NeedAppearances", PdfObject::Bool(true));
}
if let Some(da) = default_appearance {
dict.set("DA", PdfObject::String(da.into()));
}
if !font_refs.is_empty() {
let mut dr = PdfDict::new();
let mut font_dict = PdfDict::new();
for (name, font_ref) in font_refs {
font_dict.set(name, PdfObject::Reference(*font_ref));
}
add_standard_font_resources(&mut font_dict);
dr.set("Font", PdfObject::Dict(font_dict));
dict.set("DR", PdfObject::Dict(dr));
}
dict
}
fn add_standard_font_resources(_font_dict: &mut PdfDict) {
}
pub fn create_text_value(text: &str) -> PdfObject {
PdfObject::String(PdfString::from_text(text))
}
pub fn create_choice_value(selected: &str) -> PdfObject {
PdfObject::String(PdfString::from_text(selected))
}
pub fn create_button_value(checked: bool) -> PdfObject {
if checked {
PdfObject::Name(PdfName::new("Yes"))
} else {
PdfObject::Name(PdfName::new("Off"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_widget_annotation() {
let field = FormField::text("test", [100.0, 700.0, 300.0, 720.0]);
let page_ref = PdfRef::new(1);
let dict = create_widget_annotation(&field, page_ref, None, None);
assert_eq!(dict.get_type(), Some("Annot"));
assert!(dict.get("Rect").is_some());
assert!(dict.get("T").is_some());
}
#[test]
fn test_checkbox_widget_annotation_has_ap_states_and_as() {
let field = FormField::checkbox("agree", [100.0, 650.0, 120.0, 670.0], true);
let page_ref = PdfRef::new(1);
let cb_ap = CheckboxAppearanceRefs {
off_ref: PdfRef::new(10),
yes_ref: PdfRef::new(11),
};
let dict = create_widget_annotation(&field, page_ref, None, Some(cb_ap));
let ap = dict.get("AP").expect("missing AP");
if let PdfObject::Dict(ap_dict) = ap {
let n = ap_dict.get("N").expect("missing AP/N");
if let PdfObject::Dict(n_dict) = n {
assert!(n_dict.get("Yes").is_some(), "missing AP/N/Yes");
assert!(n_dict.get("Off").is_some(), "missing AP/N/Off");
} else {
panic!("AP/N should be a Dict, got {:?}", n);
}
} else {
panic!("AP should be a Dict");
}
let as_entry = dict.get("AS").expect("missing AS");
if let PdfObject::Name(name) = as_entry {
assert_eq!(name.as_str(), "Yes");
} else {
panic!("AS should be a Name");
}
}
#[test]
fn test_checkbox_unchecked_has_as_off() {
let field = FormField::checkbox("agree", [100.0, 650.0, 120.0, 670.0], false);
let page_ref = PdfRef::new(1);
let cb_ap = CheckboxAppearanceRefs {
off_ref: PdfRef::new(10),
yes_ref: PdfRef::new(11),
};
let dict = create_widget_annotation(&field, page_ref, None, Some(cb_ap));
let as_entry = dict.get("AS").expect("missing AS");
if let PdfObject::Name(name) = as_entry {
assert_eq!(name.as_str(), "Off");
} else {
panic!("AS should be a Name");
}
}
#[test]
fn test_create_acro_form_dict() {
let field_refs = vec![PdfRef::new(5), PdfRef::new(6)];
let font_refs = vec![];
let dict = create_acro_form_dict(&field_refs, &font_refs, true, Some("/Helv 12 Tf 0 g"));
assert!(dict.get("Fields").is_some());
assert!(dict.get("NeedAppearances").is_some());
}
}