use crate::capability::Capability;
use crate::error::{internal_error, Result};
use crate::license;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FieldType {
Text,
TextArea,
Checkbox,
Radio,
Dropdown,
ListBox,
Signature,
Button,
}
#[derive(Debug, Clone)]
pub struct FormField {
pub name: String,
pub field_type: FieldType,
pub value: String,
pub required: bool,
pub read_only: bool,
}
pub struct PdfFormMut<'a> {
lopdf: &'a mut lopdf::Document,
license_override: Option<&'a str>,
}
impl std::fmt::Debug for PdfFormMut<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PdfFormMut").finish_non_exhaustive()
}
}
impl<'a> PdfFormMut<'a> {
pub(crate) fn new(lopdf: &'a mut lopdf::Document, license_override: Option<&'a str>) -> Self {
Self {
lopdf,
license_override,
}
}
pub fn set_text(&mut self, name: &str, value: &str) -> Result<&mut Self> {
self.require_fill()?;
apply(self.lopdf, name, pdf_forms::WriteValue::Text(value))?;
Ok(self)
}
pub fn set_checkbox(&mut self, name: &str, value: bool) -> Result<&mut Self> {
self.require_fill()?;
apply(self.lopdf, name, pdf_forms::WriteValue::Checkbox(value))?;
Ok(self)
}
pub fn set_radio(&mut self, name: &str, value: &str) -> Result<&mut Self> {
self.require_fill()?;
apply(self.lopdf, name, pdf_forms::WriteValue::Radio(value))?;
Ok(self)
}
pub fn set_dropdown(&mut self, name: &str, value: &str) -> Result<&mut Self> {
self.require_fill()?;
apply(self.lopdf, name, pdf_forms::WriteValue::Choice(value))?;
Ok(self)
}
pub fn set_multi_select(&mut self, name: &str, values: &[&str]) -> Result<&mut Self> {
self.require_fill()?;
let owned: Vec<String> = values.iter().map(|s| (*s).to_string()).collect();
pdf_forms::apply_choice_multi(self.lopdf, name, &owned)
.map_err(|e| internal_error(e.to_string()))?;
Ok(self)
}
fn require_fill(&self) -> Result<()> {
license::require_capability_with_override(Capability::AcroFormFill, self.license_override)
}
}
pub(crate) fn read_acroform_fields(doc: &lopdf::Document) -> Vec<FormField> {
use lopdf::Object;
let catalog_id = match doc.trailer.get(b"Root") {
Ok(Object::Reference(id)) => *id,
_ => return Vec::new(),
};
let catalog = match doc.get_object(catalog_id).and_then(|o| o.as_dict()) {
Ok(d) => d,
Err(_) => return Vec::new(),
};
let acroform = match catalog.get(b"AcroForm") {
Ok(Object::Reference(id)) => match doc.get_object(*id).and_then(|o| o.as_dict()) {
Ok(d) => d,
Err(_) => return Vec::new(),
},
Ok(Object::Dictionary(d)) => d,
_ => return Vec::new(),
};
let fields_array = match acroform.get(b"Fields") {
Ok(Object::Array(arr)) => arr,
_ => return Vec::new(),
};
let mut out = Vec::with_capacity(fields_array.len());
for field_obj in fields_array {
let field_dict = match field_obj {
Object::Reference(id) => match doc.get_object(*id).and_then(|o| o.as_dict()) {
Ok(d) => d,
Err(_) => continue,
},
Object::Dictionary(d) => d,
_ => continue,
};
let name = field_dict
.get(b"T")
.ok()
.and_then(|o| lopdf::decode_text_string(o).ok())
.unwrap_or_default();
let flags = field_dict
.get(b"Ff")
.ok()
.and_then(|o| match o {
Object::Integer(i) => Some(*i),
_ => None,
})
.unwrap_or(0);
let field_type = field_dict
.get(b"FT")
.ok()
.and_then(|o| match o {
Object::Name(bytes) => Some(bytes.as_slice()),
_ => None,
})
.map(|ft| classify_field_type(ft, flags))
.unwrap_or(FieldType::Text);
let value = field_dict
.get(b"V")
.ok()
.and_then(|o| match o {
Object::Name(bytes) => std::str::from_utf8(bytes).ok().map(str::to_owned),
_ => lopdf::decode_text_string(o).ok(),
})
.unwrap_or_default();
let read_only = (flags & 0x1) != 0;
let required = (flags & 0x2) != 0;
out.push(FormField {
name,
field_type,
value,
required,
read_only,
});
}
out
}
fn classify_field_type(ft: &[u8], flags: i64) -> FieldType {
match ft {
b"Tx" => {
if (flags & 0x1000) != 0 {
FieldType::TextArea
} else {
FieldType::Text
}
}
b"Btn" => {
if (flags & 0x10000) != 0 {
FieldType::Button
} else if (flags & 0x8000) != 0 {
FieldType::Radio
} else {
FieldType::Checkbox
}
}
b"Ch" => {
if (flags & 0x20000) != 0 {
FieldType::Dropdown
} else {
FieldType::ListBox
}
}
b"Sig" => FieldType::Signature,
_ => FieldType::Text,
}
}
fn apply(
doc: &mut lopdf::Document,
name: &str,
value: pdf_forms::WriteValue<'_>,
) -> Result<pdf_forms::WriteOutcome> {
pdf_forms::apply_field_value(doc, name, value).map_err(|e| internal_error(e.to_string()))
}