use anyhow::{Context, Result};
use crate::cli::args::FormsArgs;
use crate::cli::utils::parse_coords;
pub fn forms(args: &FormsArgs) -> Result<()> {
use printwell::forms::FormBuilder;
let pdf_data = std::fs::read(&args.input)
.with_context(|| format!("Failed to read input file: {}", args.input))?;
let mut builder = FormBuilder::new(&pdf_data).context("Failed to initialize form builder")?;
for spec in &args.text_field {
let field = parse_text_field_spec(spec)
.with_context(|| format!("Invalid text field spec: {spec}"))?;
builder
.add_text_field(field)
.context("Failed to add text field")?;
}
for spec in &args.checkbox {
let field =
parse_checkbox_spec(spec).with_context(|| format!("Invalid checkbox spec: {spec}"))?;
builder
.add_checkbox(field)
.context("Failed to add checkbox")?;
}
for spec in &args.dropdown {
let field =
parse_dropdown_spec(spec).with_context(|| format!("Invalid dropdown spec: {spec}"))?;
builder
.add_dropdown(field)
.context("Failed to add dropdown")?;
}
for spec in &args.signature_field {
let field = parse_signature_field_spec(spec)
.with_context(|| format!("Invalid signature field spec: {spec}"))?;
builder
.add_signature_field(field)
.context("Failed to add signature field")?;
}
let result = builder
.build()
.context("Failed to build PDF with form fields")?;
std::fs::write(&args.output, result)
.with_context(|| format!("Failed to write output file: {}", args.output))?;
eprintln!("Written to: {}", args.output);
Ok(())
}
struct FieldSpec {
name: String,
page: u32,
coords: Vec<f64>,
extra: Option<String>,
}
fn parse_field_spec(
spec: &str,
expected_parts: usize,
coord_count: usize,
format_hint: &str,
) -> Result<FieldSpec> {
let parts: Vec<&str> = spec.split(':').collect();
if parts.len() != expected_parts {
anyhow::bail!("Invalid format. Expected {expected_parts} parts. Use: {format_hint}");
}
let name = parts[0].to_string();
let page: u32 = parts[1]
.parse()
.with_context(|| format!("Invalid page number: {}", parts[1]))?;
let coords = parse_coords(parts[2], coord_count)
.with_context(|| format!("Invalid coordinates: {}", parts[2]))?;
let extra = if expected_parts > 3 {
Some(parts[3].to_string())
} else {
None
};
Ok(FieldSpec {
name,
page,
coords,
extra,
})
}
fn parse_text_field_spec(spec: &str) -> Result<printwell::forms::TextField> {
let f = parse_field_spec(spec, 3, 4, "name:page:x,y,w,h")?;
Ok(printwell::forms::TextField::builder()
.name(f.name)
.page(f.page)
.rect(printwell::forms::Rect {
x: f.coords[0],
y: f.coords[1],
width: f.coords[2],
height: f.coords[3],
})
.build())
}
fn parse_checkbox_spec(spec: &str) -> Result<printwell::forms::Checkbox> {
let f = parse_field_spec(spec, 3, 3, "name:page:x,y,size")?;
Ok(printwell::forms::Checkbox::builder()
.name(f.name)
.page(f.page)
.rect(printwell::forms::Rect {
x: f.coords[0],
y: f.coords[1],
width: f.coords[2],
height: f.coords[2],
})
.build())
}
fn parse_dropdown_spec(spec: &str) -> Result<printwell::forms::Dropdown> {
let f = parse_field_spec(spec, 4, 4, "name:page:x,y,w,h:opt1,opt2,...")?;
let options: Vec<String> = f
.extra
.ok_or_else(|| anyhow::anyhow!("Dropdown requires options"))?
.split(',')
.map(|s| s.trim().to_string())
.collect();
Ok(printwell::forms::Dropdown::builder()
.name(f.name)
.page(f.page)
.rect(printwell::forms::Rect {
x: f.coords[0],
y: f.coords[1],
width: f.coords[2],
height: f.coords[3],
})
.options(options)
.build())
}
fn parse_signature_field_spec(spec: &str) -> Result<printwell::forms::SignatureField> {
let f = parse_field_spec(spec, 3, 4, "name:page:x,y,w,h")?;
Ok(printwell::forms::SignatureField::builder()
.name(f.name)
.page(f.page)
.rect(printwell::forms::Rect {
x: f.coords[0],
y: f.coords[1],
width: f.coords[2],
height: f.coords[3],
})
.build())
}