mod appearance;
mod fields;
pub use appearance::*;
pub use fields::*;
use crate::objects::PdfDict;
#[derive(Debug, Clone, Copy, Default)]
pub struct FieldFlags(u32);
impl FieldFlags {
pub const NONE: u32 = 0;
pub const READ_ONLY: u32 = 1 << 0;
pub const REQUIRED: u32 = 1 << 1;
pub const NO_EXPORT: u32 = 1 << 2;
pub const MULTILINE: u32 = 1 << 12;
pub const PASSWORD: u32 = 1 << 13;
pub const FILE_SELECT: u32 = 1 << 20;
pub const DO_NOT_SPELL_CHECK: u32 = 1 << 22;
pub const DO_NOT_SCROLL: u32 = 1 << 23;
pub const COMB: u32 = 1 << 24;
pub const RICH_TEXT: u32 = 1 << 25;
pub const NO_TOGGLE_TO_OFF: u32 = 1 << 14;
pub const RADIO: u32 = 1 << 15;
pub const PUSH_BUTTON: u32 = 1 << 16;
pub const RADIOS_IN_UNISON: u32 = 1 << 25;
pub const COMBO: u32 = 1 << 17;
pub const EDIT: u32 = 1 << 18;
pub const SORT: u32 = 1 << 19;
pub const MULTI_SELECT: u32 = 1 << 21;
pub const COMMIT_ON_SEL_CHANGE: u32 = 1 << 26;
pub fn new(flags: u32) -> Self {
FieldFlags(flags)
}
pub fn value(&self) -> u32 {
self.0
}
pub fn set(&mut self, flag: u32) {
self.0 |= flag;
}
pub fn clear(&mut self, flag: u32) {
self.0 &= !flag;
}
pub fn has(&self, flag: u32) -> bool {
self.0 & flag != 0
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum TextAlign {
#[default]
Left = 0,
Center = 1,
Right = 2,
}
pub type Rect = [f64; 4];
#[derive(Debug, Clone)]
pub struct FormField {
pub name: String,
pub field_type: FieldType,
pub rect: Rect,
pub flags: FieldFlags,
pub value: Option<String>,
pub tooltip: Option<String>,
pub align: TextAlign,
pub max_length: Option<u32>,
pub options: Vec<String>,
pub font: String,
pub font_size: f64,
pub border_color: Option<[f64; 3]>,
pub background_color: Option<[f64; 3]>,
pub text_color: [f64; 3],
pub page_index: usize,
}
impl FormField {
pub fn text(name: impl Into<String>, rect: Rect) -> Self {
FormField {
name: name.into(),
field_type: FieldType::Text,
rect,
flags: FieldFlags::default(),
value: None,
tooltip: None,
align: TextAlign::Left,
max_length: None,
options: Vec::new(),
font: "Helvetica".to_string(),
font_size: 0.0, border_color: Some([0.0, 0.0, 0.0]),
background_color: Some([1.0, 1.0, 1.0]),
text_color: [0.0, 0.0, 0.0],
page_index: 0,
}
}
pub fn checkbox(name: impl Into<String>, rect: Rect, checked: bool) -> Self {
FormField {
name: name.into(),
field_type: FieldType::CheckBox,
rect,
flags: FieldFlags::default(),
value: Some(if checked { "Yes" } else { "Off" }.to_string()),
tooltip: None,
align: TextAlign::Left,
max_length: None,
options: Vec::new(),
font: "ZapfDingbats".to_string(),
font_size: 0.0,
border_color: Some([0.0, 0.0, 0.0]),
background_color: Some([1.0, 1.0, 1.0]),
text_color: [0.0, 0.0, 0.0],
page_index: 0,
}
}
pub fn dropdown(name: impl Into<String>, rect: Rect, options: Vec<String>) -> Self {
let mut flags = FieldFlags::default();
flags.set(FieldFlags::COMBO);
FormField {
name: name.into(),
field_type: FieldType::Choice,
rect,
flags,
value: options.first().cloned(),
tooltip: None,
align: TextAlign::Left,
max_length: None,
options,
font: "Helvetica".to_string(),
font_size: 0.0,
border_color: Some([0.0, 0.0, 0.0]),
background_color: Some([1.0, 1.0, 1.0]),
text_color: [0.0, 0.0, 0.0],
page_index: 0,
}
}
pub fn listbox(name: impl Into<String>, rect: Rect, options: Vec<String>) -> Self {
FormField {
name: name.into(),
field_type: FieldType::Choice,
rect,
flags: FieldFlags::default(),
value: None,
tooltip: None,
align: TextAlign::Left,
max_length: None,
options,
font: "Helvetica".to_string(),
font_size: 0.0,
border_color: Some([0.0, 0.0, 0.0]),
background_color: Some([1.0, 1.0, 1.0]),
text_color: [0.0, 0.0, 0.0],
page_index: 0,
}
}
pub fn read_only(mut self) -> Self {
self.flags.set(FieldFlags::READ_ONLY);
self
}
pub fn required(mut self) -> Self {
self.flags.set(FieldFlags::REQUIRED);
self
}
pub fn with_value(mut self, value: impl Into<String>) -> Self {
self.value = Some(value.into());
self
}
pub fn with_tooltip(mut self, tooltip: impl Into<String>) -> Self {
self.tooltip = Some(tooltip.into());
self
}
pub fn with_align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
pub fn with_max_length(mut self, max: u32) -> Self {
self.max_length = Some(max);
self
}
pub fn multiline(mut self) -> Self {
self.flags.set(FieldFlags::MULTILINE);
self
}
pub fn password(mut self) -> Self {
self.flags.set(FieldFlags::PASSWORD);
self
}
pub fn with_font(mut self, font: impl Into<String>, size: f64) -> Self {
self.font = font.into();
self.font_size = size;
self
}
pub fn with_border_color(mut self, r: f64, g: f64, b: f64) -> Self {
self.border_color = Some([r, g, b]);
self
}
pub fn no_border(mut self) -> Self {
self.border_color = None;
self
}
pub fn with_background_color(mut self, r: f64, g: f64, b: f64) -> Self {
self.background_color = Some([r, g, b]);
self
}
pub fn no_background(mut self) -> Self {
self.background_color = None;
self
}
pub fn with_text_color(mut self, r: f64, g: f64, b: f64) -> Self {
self.text_color = [r, g, b];
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldType {
Text,
CheckBox,
Radio,
Button,
Choice,
Signature,
}
impl FieldType {
pub fn pdf_name(&self) -> &'static str {
match self {
FieldType::Text => "Tx",
FieldType::CheckBox | FieldType::Radio | FieldType::Button => "Btn",
FieldType::Choice => "Ch",
FieldType::Signature => "Sig",
}
}
}
#[derive(Debug, Default)]
pub struct AcroForm {
pub fields: Vec<FormField>,
pub need_appearances: bool,
pub sig_flags: u32,
pub default_appearance: Option<String>,
pub default_resources: Option<PdfDict>,
}
impl AcroForm {
pub fn new() -> Self {
AcroForm {
fields: Vec::new(),
need_appearances: true,
sig_flags: 0,
default_appearance: Some("/Helv 0 Tf 0 g".to_string()),
default_resources: None,
}
}
pub fn add_field(&mut self, field: FormField) {
self.fields.push(field);
}
pub fn has_fields(&self) -> bool {
!self.fields.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_flags() {
let mut flags = FieldFlags::default();
assert!(!flags.has(FieldFlags::READ_ONLY));
flags.set(FieldFlags::READ_ONLY);
assert!(flags.has(FieldFlags::READ_ONLY));
flags.clear(FieldFlags::READ_ONLY);
assert!(!flags.has(FieldFlags::READ_ONLY));
}
#[test]
fn test_text_field() {
let field = FormField::text("name", [100.0, 700.0, 300.0, 720.0])
.with_value("John Doe")
.required();
assert_eq!(field.name, "name");
assert_eq!(field.field_type, FieldType::Text);
assert!(field.flags.has(FieldFlags::REQUIRED));
assert_eq!(field.value, Some("John Doe".to_string()));
}
#[test]
fn test_checkbox_field() {
let field = FormField::checkbox("agree", [100.0, 650.0, 120.0, 670.0], true);
assert_eq!(field.field_type, FieldType::CheckBox);
assert_eq!(field.value, Some("Yes".to_string()));
}
#[test]
fn test_dropdown_field() {
let options = vec!["Option 1".to_string(), "Option 2".to_string()];
let field = FormField::dropdown("choice", [100.0, 600.0, 300.0, 620.0], options);
assert_eq!(field.field_type, FieldType::Choice);
assert!(field.flags.has(FieldFlags::COMBO));
assert_eq!(field.options.len(), 2);
}
}