use crate::object::{Object, ObjectRef};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct AcroFormBuilder {
fields: Vec<ObjectRef>,
need_appearances: bool,
sig_flags: Option<u32>,
default_appearance: Option<String>,
calc_order: Vec<ObjectRef>,
xfa: Option<Object>,
}
impl Default for AcroFormBuilder {
fn default() -> Self {
Self::new()
}
}
impl AcroFormBuilder {
pub fn new() -> Self {
Self {
fields: Vec::new(),
need_appearances: true, sig_flags: None,
default_appearance: Some("/Helv 12 Tf 0 g".to_string()),
calc_order: Vec::new(),
xfa: None,
}
}
pub fn add_field(&mut self, field_ref: ObjectRef) {
self.fields.push(field_ref);
}
pub fn add_fields(&mut self, fields: impl IntoIterator<Item = ObjectRef>) {
self.fields.extend(fields);
}
pub fn need_appearances(mut self) -> Self {
self.need_appearances = true;
self
}
pub fn no_need_appearances(mut self) -> Self {
self.need_appearances = false;
self
}
pub fn with_need_appearances(mut self, need: bool) -> Self {
self.need_appearances = need;
self
}
pub fn with_default_appearance(mut self, da: impl Into<String>) -> Self {
self.default_appearance = Some(da.into());
self
}
pub fn with_sig_flags(mut self, flags: u32) -> Self {
self.sig_flags = Some(flags);
self
}
pub fn signatures_exist(mut self) -> Self {
let flags = self.sig_flags.unwrap_or(0);
self.sig_flags = Some(flags | 1);
self
}
pub fn append_only(mut self) -> Self {
let flags = self.sig_flags.unwrap_or(0);
self.sig_flags = Some(flags | 2);
self
}
pub fn with_calc_order(mut self, order: Vec<ObjectRef>) -> Self {
self.calc_order = order;
self
}
pub fn has_fields(&self) -> bool {
!self.fields.is_empty()
}
pub fn field_count(&self) -> usize {
self.fields.len()
}
pub fn build(&self, font_dict_ref: Option<ObjectRef>) -> HashMap<String, Object> {
let mut dict = HashMap::new();
let fields: Vec<Object> = self.fields.iter().map(|r| Object::Reference(*r)).collect();
dict.insert("Fields".to_string(), Object::Array(fields));
if self.need_appearances {
dict.insert("NeedAppearances".to_string(), Object::Boolean(true));
}
if let Some(flags) = self.sig_flags {
dict.insert("SigFlags".to_string(), Object::Integer(flags as i64));
}
if let Some(ref da) = self.default_appearance {
dict.insert("DA".to_string(), Object::String(da.as_bytes().to_vec()));
}
if let Some(font_ref) = font_dict_ref {
let mut dr = HashMap::new();
let mut font = HashMap::new();
font.insert("Helv".to_string(), Object::Reference(font_ref));
dr.insert("Font".to_string(), Object::Dictionary(font));
dict.insert("DR".to_string(), Object::Dictionary(dr));
}
if !self.calc_order.is_empty() {
let co: Vec<Object> = self
.calc_order
.iter()
.map(|r| Object::Reference(*r))
.collect();
dict.insert("CO".to_string(), Object::Array(co));
}
if let Some(ref xfa) = self.xfa {
dict.insert("XFA".to_string(), xfa.clone());
}
dict
}
pub fn build_default_resources() -> HashMap<String, Object> {
let mut dr = HashMap::new();
let mut fonts = HashMap::new();
let mut helv = HashMap::new();
helv.insert("Type".to_string(), Object::Name("Font".to_string()));
helv.insert("Subtype".to_string(), Object::Name("Type1".to_string()));
helv.insert("BaseFont".to_string(), Object::Name("Helvetica".to_string()));
helv.insert("Encoding".to_string(), Object::Name("WinAnsiEncoding".to_string()));
fonts.insert("Helv".to_string(), Object::Dictionary(helv));
let mut cour = HashMap::new();
cour.insert("Type".to_string(), Object::Name("Font".to_string()));
cour.insert("Subtype".to_string(), Object::Name("Type1".to_string()));
cour.insert("BaseFont".to_string(), Object::Name("Courier".to_string()));
cour.insert("Encoding".to_string(), Object::Name("WinAnsiEncoding".to_string()));
fonts.insert("Cour".to_string(), Object::Dictionary(cour));
let mut tiro = HashMap::new();
tiro.insert("Type".to_string(), Object::Name("Font".to_string()));
tiro.insert("Subtype".to_string(), Object::Name("Type1".to_string()));
tiro.insert("BaseFont".to_string(), Object::Name("Times-Roman".to_string()));
tiro.insert("Encoding".to_string(), Object::Name("WinAnsiEncoding".to_string()));
fonts.insert("TiRo".to_string(), Object::Dictionary(tiro));
let mut zadb = HashMap::new();
zadb.insert("Type".to_string(), Object::Name("Font".to_string()));
zadb.insert("Subtype".to_string(), Object::Name("Type1".to_string()));
zadb.insert("BaseFont".to_string(), Object::Name("ZapfDingbats".to_string()));
fonts.insert("ZaDb".to_string(), Object::Dictionary(zadb));
dr.insert("Font".to_string(), Object::Dictionary(fonts));
dr
}
pub fn build_with_resources(&self) -> HashMap<String, Object> {
let mut dict = HashMap::new();
let fields: Vec<Object> = self.fields.iter().map(|r| Object::Reference(*r)).collect();
dict.insert("Fields".to_string(), Object::Array(fields));
if self.need_appearances {
dict.insert("NeedAppearances".to_string(), Object::Boolean(true));
}
if let Some(flags) = self.sig_flags {
dict.insert("SigFlags".to_string(), Object::Integer(flags as i64));
}
if let Some(ref da) = self.default_appearance {
dict.insert("DA".to_string(), Object::String(da.as_bytes().to_vec()));
}
let dr = Self::build_default_resources();
dict.insert("DR".to_string(), Object::Dictionary(dr));
if !self.calc_order.is_empty() {
let co: Vec<Object> = self
.calc_order
.iter()
.map(|r| Object::Reference(*r))
.collect();
dict.insert("CO".to_string(), Object::Array(co));
}
dict
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_acroform_new() {
let acroform = AcroFormBuilder::new();
assert!(!acroform.has_fields());
assert!(acroform.need_appearances);
assert!(acroform.default_appearance.is_some());
}
#[test]
fn test_acroform_add_fields() {
let mut acroform = AcroFormBuilder::new();
acroform.add_field(ObjectRef::new(5, 0));
acroform.add_field(ObjectRef::new(6, 0));
assert_eq!(acroform.field_count(), 2);
assert!(acroform.has_fields());
}
#[test]
fn test_acroform_need_appearances() {
let acroform = AcroFormBuilder::new().no_need_appearances();
assert!(!acroform.need_appearances);
let acroform = acroform.need_appearances();
assert!(acroform.need_appearances);
}
#[test]
fn test_acroform_default_appearance() {
let acroform = AcroFormBuilder::new().with_default_appearance("/Cour 10 Tf 0 0 1 rg");
assert_eq!(acroform.default_appearance, Some("/Cour 10 Tf 0 0 1 rg".to_string()));
}
#[test]
fn test_acroform_sig_flags() {
let acroform = AcroFormBuilder::new().signatures_exist().append_only();
assert_eq!(acroform.sig_flags, Some(3)); }
#[test]
fn test_acroform_build() {
let mut acroform = AcroFormBuilder::new();
acroform.add_field(ObjectRef::new(10, 0));
let dict = acroform.build(None);
assert!(dict.contains_key("Fields"));
assert!(dict.contains_key("NeedAppearances"));
assert!(dict.contains_key("DA"));
if let Some(Object::Array(fields)) = dict.get("Fields") {
assert_eq!(fields.len(), 1);
}
}
#[test]
fn test_acroform_build_with_resources() {
let mut acroform = AcroFormBuilder::new();
acroform.add_field(ObjectRef::new(10, 0));
let dict = acroform.build_with_resources();
assert!(dict.contains_key("Fields"));
assert!(dict.contains_key("DR"));
if let Some(Object::Dictionary(dr)) = dict.get("DR") {
assert!(dr.contains_key("Font"));
if let Some(Object::Dictionary(fonts)) = dr.get("Font") {
assert!(fonts.contains_key("Helv"));
assert!(fonts.contains_key("ZaDb"));
}
}
}
#[test]
fn test_default_resources() {
let dr = AcroFormBuilder::build_default_resources();
assert!(dr.contains_key("Font"));
if let Some(Object::Dictionary(fonts)) = dr.get("Font") {
assert!(fonts.contains_key("Helv"));
assert!(fonts.contains_key("Cour"));
assert!(fonts.contains_key("TiRo"));
assert!(fonts.contains_key("ZaDb"));
}
}
#[test]
fn test_acroform_calc_order() {
let acroform = AcroFormBuilder::new()
.with_calc_order(vec![ObjectRef::new(5, 0), ObjectRef::new(6, 0)]);
let dict = acroform.build(None);
assert!(dict.contains_key("CO"));
if let Some(Object::Array(co)) = dict.get("CO") {
assert_eq!(co.len(), 2);
}
}
}