printwell-cli 0.1.11

Command-line tool for HTML to PDF conversion
Documentation
//! Forms command implementation.

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")?;

    // Parse and add text fields
    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")?;
    }

    // Parse and add checkboxes
    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")?;
    }

    // Parse and add dropdowns
    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")?;
    }

    // Parse and add signature fields
    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())
}