# `PdfElixide.Form`
[🔗](https://github.com/r8/pdf_elixide/blob/v0.16.0/lib/pdf_elixide/form.ex#L1)

AcroForm field access for documents and editors.

`fields/1`, `field/2` and `value/2` read from *either* source — a read-only
`PdfElixide.Document` or a mutable `PdfElixide.Editor` (`t:source/0`) — so
inspecting a form needs no editor. Writing needs one: `put_value/3`,
`put_values/2`, `update_value/3` and `flatten/1,2` take an `Editor` only, and
each returns it, so filling and saving compose as a pipeline.

    "form.pdf"
    |> PdfElixide.Editor.open!()
    |> PdfElixide.Form.put_value!("full_name", "Jane Doe")
    |> PdfElixide.Editor.save!("filled.pdf")
    |> PdfElixide.Editor.close()

`flatten/1,2` is how a filled form stops being fillable: it draws the field
values into the page and takes the interactive fields away. Like every other
edit it takes effect when the editor is written.

Fields come back as one struct per field type, listed in
`PdfElixide.Form.Field`, each carrying the widget it is as a `:kind`, its
decoded `/Ff` bits as `:flags`, and the metadata a filler needs to render it —
a tooltip, a box, a default value, and a choice field's permitted options —
and are addressed by the fully qualified name each carries. Only an existing
field can be set — there is no way to add one — and a name that is not in the
form is `{:error, %PdfElixide.Error{reason: :not_found}}`, from `field/2` and
`value/2` as much as from `put_value/3`. Signature fields are not fillable and
are not reported at all — `PdfElixide.Signature` reads those — and writing a
check box or radio group back is not always faithful.

A form whose field hierarchy is cyclic, or nested far deeper than any real
form, is reported as an error by every function here rather than read; the
[Forms](guides/forms.md) guide gives the reason atoms.

Which lock a call takes follows its source: a shared read on a
`PdfElixide.Document`, and the editor's exclusive lock on a
`PdfElixide.Editor` — which `fields/1` takes too, so concurrent form work on
one editor serializes even when it only reads. See the
[Concurrency](guides/concurrency.md) guide.

The [Forms](guides/forms.md) guide covers the field structs, both filling
shapes, saving, and the signature and check-box caveats in full.

# `source`

```elixir
@type source() :: PdfElixide.Document.t() | PdfElixide.Editor.t()
```

# `field`

```elixir
@spec field(source(), String.t()) ::
  {:ok, PdfElixide.Form.Field.t()} | {:error, PdfElixide.Error.t()}
```

Returns the single form field carrying the given name.

The name is the fully qualified one `PdfElixide.Form.Field` describes. A name
that is not in the form is `{:error, %PdfElixide.Error{reason: :not_found}}`;
a form with two fields of the same name answers with the first.

    PdfElixide.Form.field(doc, "full_name")
    #=> {:ok, %PdfElixide.Form.Field.Text{name: "full_name", kind: :single_line,
    #     value: "John Doe", flags: %PdfElixide.Form.Field.Text.Flags{…}}}

# `field!`

```elixir
@spec field!(source(), String.t()) :: PdfElixide.Form.Field.t()
```

Same as `field/2` but raises an error if it fails.

# `fields`

```elixir
@spec fields(source()) ::
  {:ok, [PdfElixide.Form.Field.t()]} | {:error, PdfElixide.Error.t()}
```

Extracts form fields from the given PDF document or editor.

A document with no AcroForm answers `{:ok, []}`, as does one whose form
declares no fields.

Signature fields are not reported; this API covers fillable form fields only.
`PdfElixide.Signature.list/1` reads the signed ones and
`PdfElixide.Signature.unsigned_fields/1` names the rest. See the
[Forms](guides/forms.md) guide.

# `fields!`

```elixir
@spec fields!(source()) :: [PdfElixide.Form.Field.t()]
```

Extracts form fields from the given PDF document or editor,
raising an error if it fails.

# `flatten`

```elixir
@spec flatten(PdfElixide.Editor.t()) ::
  {:ok, PdfElixide.Editor.t()} | {:error, PdfElixide.Error.t()}
```

Marks every page's form fields for flattening.

Flattening draws each field's appearance into the page content and removes the
interactive fields, so the written PDF is no longer fillable. Nothing happens
until the next full write: `PdfElixide.Editor.save/3` without `:incremental`,
or `PdfElixide.Editor.to_binary/2`. An incremental save ignores the mark
entirely.

This also removes the document's AcroForm, and any signature field goes with
it — the dictionary stays in the file, but nothing references it any more, so
the document comes back unsigned. `flatten/2` keeps the ones whose widgets
survive.

A document carrying no form is not an error. The mark cannot be removed —
reopen the source for an unflattened document.

Returns the editor. Check `PdfElixide.Editor.flatten_warnings/1` after the
write: a field value can be flattened wrongly and still produce a valid PDF.
See the "Flattening" section of the [Forms](guides/forms.md) guide.

# `flatten`

```elixir
@spec flatten(PdfElixide.Editor.t(), non_neg_integer()) ::
  {:ok, PdfElixide.Editor.t()} | {:error, PdfElixide.Error.t()}
```

Marks the form fields of the page at the given zero-based index for flattening.

Deferred until the next full write, exactly as `flatten/1` is. Unlike
`flatten/1` the AcroForm is kept, rebuilt to hold only the fields that still
have a widget on a page left unflattened; a field whose widgets do not name a
page is kept as it was. A signature field is kept or dropped by that same rule,
so one whose widget is on a flattened page is lost as it would be by
`flatten/1`.

Returns the editor, or `{:error, %PdfElixide.Error{reason: :out_of_range}}` if
the page does not exist. See the "Flattening" section of the
[Forms](guides/forms.md) guide.

# `flatten!`

```elixir
@spec flatten!(PdfElixide.Editor.t()) :: PdfElixide.Editor.t()
```

Marks every page's form fields for flattening, raising an error if it fails.

# `flatten!`

```elixir
@spec flatten!(PdfElixide.Editor.t(), non_neg_integer()) :: PdfElixide.Editor.t()
```

Marks the form fields of the page at the given zero-based index for flattening,
raising an error if it fails.

# `put_value`

```elixir
@spec put_value(PdfElixide.Editor.t(), String.t(), PdfElixide.Form.Field.value()) ::
  {:ok, PdfElixide.Editor.t()} | {:error, PdfElixide.Error.t()}
```

Writes the value of an existing form field on the given editor, and returns
the editor it was given.

The value is a plain term, the same shape `fields/1` returns — a string,
`true`/`false`, a list of strings, or `nil`. Anything else raises
`ArgumentError`; see `t:PdfElixide.Form.Field.value/0` for the full set.

    {:ok, editor} = PdfElixide.Form.put_value(editor, "full_name", "Jane Doe")
    {:ok, editor} = PdfElixide.Form.put_value(editor, "subscribe", true)

Button fields are limited to `/Yes` and `/Off`, and a signature field cannot be
written at all — it answers `:not_found`. The [Forms](guides/forms.md) guide
has both.

# `put_value!`

```elixir
@spec put_value!(PdfElixide.Editor.t(), String.t(), PdfElixide.Form.Field.value()) ::
  PdfElixide.Editor.t()
```

Writes the value of an existing form field on the given editor, raising an
error if it fails.

# `put_values`

```elixir
@spec put_values(
  PdfElixide.Editor.t(),
  Enumerable.t({String.t(), PdfElixide.Form.Field.value()})
) ::
  {:ok, PdfElixide.Editor.t()} | {:error, PdfElixide.Error.t()}
```

Writes several form field values on the given editor, and returns the editor.

Takes a map with string keys, or a list of `{name, value}` pairs.

Everything is validated before anything is written, against a single `fields/1`
read. A name the form does not carry — a signature field included — is
`{:error, %PdfElixide.Error{reason: :not_found}}`, and a duplicated name, a
name that is not a string, or a value outside
`t:PdfElixide.Form.Field.value/0` raises `ArgumentError` naming the field.

**This is not a transaction.** What can still fail after validation is the
handle itself — `:closed`, `:panic`, `:lock_poisoned` — and that stops at the
first error, leaving earlier writes applied.

A list is applied in its own order; a map is applied in `Enum` order, which is
unspecified, so pass a list where the order matters. Empty input returns
`{:ok, editor}` and makes no native call, so it leaves
`PdfElixide.Editor.modified?/1` alone and answers the same way for a closed
editor.

It is a convenience, not a batching optimization — see the
[Concurrency](guides/concurrency.md) guide for what it locks.

# `put_values!`

```elixir
@spec put_values!(
  PdfElixide.Editor.t(),
  Enumerable.t({String.t(), PdfElixide.Form.Field.value()})
) ::
  PdfElixide.Editor.t()
```

Writes several form field values on the given editor, raising an error if it
fails.

# `update_value`

```elixir
@spec update_value(
  PdfElixide.Editor.t(),
  String.t(),
  (PdfElixide.Form.Field.value() -&gt;
     PdfElixide.Form.Field.value())
) ::
  {:ok, PdfElixide.Editor.t()} | {:error, PdfElixide.Error.t()}
```

Reads a form field's value, applies `fun` to it, writes the result back, and
returns the editor.

A field carrying no value hands `fun` a `nil`. A name the form does not carry
is `{:error, %PdfElixide.Error{reason: :not_found}}` and `fun` is not called.
Whatever `fun` returns is written by `put_value/3` and must be a
`t:PdfElixide.Form.Field.value/0`.

**This is a read and then a write, not an atomic read-modify-write** — another
process holding the same editor can write in between; see the
[Concurrency](guides/concurrency.md) guide.

# `update_value!`

```elixir
@spec update_value!(
  PdfElixide.Editor.t(),
  String.t(),
  (PdfElixide.Form.Field.value() -&gt;
     PdfElixide.Form.Field.value())
) ::
  PdfElixide.Editor.t()
```

Reads a form field's value, applies `fun` to it and writes the result back,
raising an error if it fails.

# `value`

```elixir
@spec value(source(), String.t()) ::
  {:ok, PdfElixide.Form.Field.value()} | {:error, PdfElixide.Error.t()}
```

Returns the value of the single form field carrying the given name.

`{:ok, nil}` means the field exists but carries no value — distinct from
`{:error, %PdfElixide.Error{reason: :not_found}}`, which means no field
carries that name. Reach for `field/2` when the field's type is needed too.

    PdfElixide.Form.value(doc, "full_name")
    #=> {:ok, "John Doe"}

# `value!`

```elixir
@spec value!(source(), String.t()) :: PdfElixide.Form.Field.value()
```

Same as `value/2` but raises an error if it fails.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
