pdfluent-forms 1.0.0-beta.16

AcroForm engine for PDF interactive forms.
Documentation
# pdfluent-forms

AcroForm engine for PDF interactive forms — parse, fill, inspect, and export
form data.  Powers the form layer of the [PDFluent](https://pdfluent.com) Rust
SDK and all language bindings.

**Free for evaluation. Production use requires a valid license.**

## What it does

- **Parse** any ISO 32000 AcroForm into a typed field tree — text, checkbox,
  radio, combo/list, push-button, and signature fields; hierarchical names
  (`parent.kid`); inherited flags and attributes.
- **Fill** fields reliably: `apply_field_value` is the single writeback chain
  for the whole SDK. One call keeps `/V`, per-widget `/AS`, and regenerated
  `/AP` streams consistent, working in every viewer without `/NeedAppearances`.
- **Inspect** the complete form model: one `FormFieldModel` per logical field
  with per-page widget rectangles, typed kind, on-state names, choice options,
  comb/multiline/password flags, access flags, and resolved font info.
- **Regenerate** appearances for documents filled by other tools that only
  wrote `/V` (e.g. with `/NeedAppearances true`).

## Quick Start

```rust
use std::sync::Arc;
use lopdf::Document;
use pdfluent_forms::{
    apply_field_value, build_form_model, parse_acroform, WriteValue,
};

// --- Read the form model ---
let pdf = pdf_syntax::Pdf::new(Arc::new(std::fs::read("form.pdf")?)).unwrap();
let tree = parse_acroform(&pdf).expect("no AcroForm");

for field in build_form_model(&tree) {
    println!("[{:?}] {} = {:?}", field.kind, field.name, field.value);
}

// --- Fill a field ---
let mut doc = Document::load("form.pdf")?;
apply_field_value(&mut doc, "Name", WriteValue::Text("Jane Doe"))?;
apply_field_value(&mut doc, "Agree", WriteValue::Checkbox(true))?;
apply_field_value(&mut doc, "Country", WriteValue::Radio("NL"))?;
doc.save("form-filled.pdf")?;
```

## Writeback Guarantees

`apply_field_value` maintains the following invariants, mirroring the
convergent behaviour of pdfium, mupdf, and pdf.js:

| Property | Guarantee |
|---|---|
| `/V` encoding | ASCII → PDF literal string; non-ASCII → UTF-16BE+BOM whole string |
| Button `/V` | Byte-exact `/Name` object; WinAnsi re-encoding fallback for Latin-1 on-state names |
| `/AS` consistency | Set per widget to the on-state iff that name is a key of the widget's own `/AP /N`; else `/Off` (mupdf `set_check_grp` rule) |
| `/AP /N` | Regenerated as a self-contained Form XObject: `/Tx BMC … EMC`, WinAnsiEncoding show-text bytes, embedded Standard-14 AFM widths, comb/multiline/quadding support, ~2 pt inset |
| Font resources | Self-contained `/Resources` with explicit `WinAnsiEncoding` — never references `/DR` fonts (which carry PDFDocEncoding Differences that remap characters) |
| `/NeedAppearances` | Set `true` only for non-WinAnsi values (Cyrillic, CJK, …); stale `/AP` removed in that case |
| Read-only | Rejected at set-time (`WritebackError::ReadOnly`) — the SDK has no UI layer, so this is the only enforceable point |
| Inline `/AcroForm` | Promoted to an indirect object before mutation (92% of real-world corpora use inline `/AcroForm`) |
| Hierarchical names | Resolved through `/Kids` recursion, not top-level-only |

## API Reference

### Entry points

| Function | Description |
|---|---|
| `parse_acroform(pdf)` | Parse an AcroForm from a `pdf_syntax::Pdf`; `None` if absent |
| `build_form_model(tree)` | Build `Vec<FormFieldModel>` from a parsed `FieldTree` |
| `apply_field_value(doc, name, value)` | Fill one field in a `lopdf::Document` |
| `apply_choice_multi(doc, name, &[String])` | Fill a multi-select list box: `/V` array + rebuilt sorted `/I` index cache |
| `regenerate_appearances(doc)` | Regenerate all appearance streams in a `lopdf::Document` |

### Key types

| Type | Description |
|---|---|
| `FieldTree` | Arena-based tree mirroring the AcroForm parent/child hierarchy |
| `FieldNode` | A single field or group (partial name, type, value, flags, …) |
| `FieldId` | Opaque index into the `FieldTree` arena |
| `FieldType` | `Text` / `Button` / `Choice` / `Signature` |
| `FieldValue` | Text string or array of selected strings |
| `FieldFlags` | Bit-field from `/Ff` — read-only, required, multiline, comb, … |
| `FormFieldModel` | Complete editor-facing model for one logical field |
| `FormFieldKind` | Typed variant: `Text`, `Checkbox`, `RadioGroup`, `ComboBox`, `ListBox`, `PushButton`, `Signature` |
| `WidgetModel` | One widget annotation: page index, rect, on-state, `/AS` |
| `DaInfo` | Resolved `/DA`: font name, size, color |
| `WriteValue` | `Text(&str)` / `Checkbox(bool)` / `Radio(&str)` / `Choice(&str)` |
| `WriteOutcome` | How many appearances were generated, whether fallback was used |
| `WritebackError` | `FieldNotFound` / `ReadOnly` / `WrongType` / `InvalidOption` / `Malformed` |

### `FormFieldKind` variants

```rust
pub enum FormFieldKind {
    Text { multiline: bool, comb: bool, password: bool },
    Checkbox { on_state: String, checked: bool },
    RadioGroup { options: Vec<String> },     // one entry per widget
    ComboBox  { editable: bool, options: Vec<ChoiceOption> },
    ListBox   { multi_select: bool, options: Vec<ChoiceOption> },
    PushButton,
    Signature,
}
```

### `FormFieldModel`

```rust
pub struct FormFieldModel {
    pub name:          String,               // fully qualified ("parent.kid")
    pub kind:          FormFieldKind,
    pub value:         Option<String>,       // current /V
    pub default_value: Option<String>,       // /DV
    pub tooltip:       Option<String>,       // /TU
    pub read_only:     bool,
    pub required:      bool,
    pub max_len:       Option<u32>,          // /MaxLen (text fields)
    pub quadding:      Quadding,             // /Q: Left/Center/Right
    pub da:            Option<DaInfo>,       // resolved /DA
    pub widgets:       Vec<WidgetModel>,     // one per visual occurrence
}
```

## Known Limitations

- **Non-WinAnsi values** (Cyrillic, CJK): `/V` is always lossless via UTF-16BE;
  appearance generation falls back to `/NeedAppearances true`. The SDK renderer
  does not honour `/NeedAppearances` — call `regenerate_appearances` after
  opening such documents for correct rendering.
- **DA fonts outside Standard-14**: the nearest standard face is substituted in
  generated appearances (Helvetica unless `/DA` names a Times/Courier variant).
- **Widget rotation** (`/MK /R`): not yet applied in generated appearances.

## Licensing

- Free for evaluation, development, and testing
- Production use requires a valid PDFluent commercial license
- Redistribution requires the OEM Redistribution add-on

See [LICENSE](LICENSE) for full terms, or visit <https://pdfluent.com/terms>.

## Links

- Main crate: <https://crates.io/crates/pdfluent>
- Documentation: <https://pdfluent.com/docs>
- Trial: <https://pdfluent.com/trial>
- Pricing: <https://pdfluent.com/pricing>
- Changelog: [CHANGELOG.md]CHANGELOG.md