PdfElixide.Form.fields/1 reads a PDF's AcroForm fields, from a read-only
PdfElixide.Document or from a mutable PdfElixide.Editor alike. Writing needs
an editor, since a document cannot be changed.
alias PdfElixide.Document
alias PdfElixide.Editor
alias PdfElixide.Form
doc = Document.open!("path/to/form.pdf")
Form.fields!(doc)
#=> [%PdfElixide.Form.Field.Text{name: "full_name", kind: :single_line, value: "John Doe", …},
# %PdfElixide.Form.Field.Button{name: "subscribe", kind: :check_box, value: true, …},
# %PdfElixide.Form.Field.Choice{name: "country", kind: :list_box, value: nil, …}]Fields and their values
A field comes back as one struct per field type, so the type is what you match
on: PdfElixide.Form.Field.Text (/Tx), .Button (/Btn — push buttons, check
boxes and radio groups), .Choice (/Ch), and .Unknown for a field with no
recognized type, which includes the grouping parents a nested form reports.
PdfElixide.Form.Field is the umbrella defining the union. Which widget a
button or choice field is, the struct's :kind says — see below.
Every struct carries the same three keys. :name is the field's fully
qualified name, dotted for a field nested under a parent — "person.first", not
"first" — and is what every other function here addresses it by. :value is a
plain term: a string, true/false, a list of strings, or nil for a field
carrying no value. :flags is described under "Field kinds and flags" below,
along with the :kind the first three also carry.
A value is written back exactly as it was read. PdfElixide.Form.Field.value/0
is both what a field reports and what PdfElixide.Form.put_value/3 accepts —
anything else raises ArgumentError — so a value read from one form goes
straight into another. Button fields are the exception, described below.
For one field there is no need to walk the list. PdfElixide.Form.field/2
returns the struct and PdfElixide.Form.value/2 just its value, from either
source:
Form.value!(doc, "full_name")
#=> "John Doe"
Form.field(doc, "country")
#=> {:ok, %PdfElixide.Form.Field.Choice{name: "country", kind: :list_box, value: nil, …}}{:ok, nil} and :not_found are different answers. A field that exists but
carries no value is {:ok, nil}; a name the form does not carry is
{:error, %PdfElixide.Error{reason: :not_found}}, from field/2 and value/2
as much as from put_value/3. The bang variants raise it instead.
Field kinds and flags
A field's /FT says only that it is a button, a choice field or a text field.
Which widget it is — a check box or a radio group, a combo box or a list box —
is decided by bits in its /Ff entry, and those bits are what :kind reports:
| Struct | :kind | Default |
|---|---|---|
PdfElixide.Form.Field.Button | :check_box, :radio, :push | :check_box |
PdfElixide.Form.Field.Choice | :combo_box, :list_box | :list_box |
PdfElixide.Form.Field.Text | :single_line, :multiline | :single_line |
case Form.field!(doc, "subscribe") do
%Form.Field.Button{kind: :check_box, value: checked?} -> checked?
%Form.Field.Button{kind: :radio, value: selected} -> selected
%Form.Field.Button{kind: :push} -> nil
endA field declaring no /Ff is not an unknown — every bit is clear, and the
defaults above are what the PDF specification says that means. Many real forms
declare no /Ff at all.
A field inherits /Ff from its ancestors. A radio group is commonly a parent
carrying the flags over kids that carry none, and each kid reports the parent's
kind. A kid declaring its own /Ff replaces the inherited value outright rather
than merging bit by bit, so a :push button under a :radio parent stays a push
button.
:flags carries the whole entry decoded, one boolean per bit the specification
names for that type, plus :raw for anything it does not:
Form.field!(doc, "notes").flags
#=> %PdfElixide.Form.Field.Text.Flags{multiline: true, password: false,
# read_only: false, required: false, comb: false, …, raw: 4096}Each type has its own flags struct — PdfElixide.Form.Field.Text.Flags,
.Button.Flags, .Choice.Flags — because the same bit means different things
on different types. PdfElixide.Form.Field.Unknown carries
PdfElixide.Form.Field.Flags, which holds the three bits every field has:
:read_only, :required and :no_export.
PdfElixide.Document.Annotation reports the same classification for a widget
annotation, through its :field_type, so the two surfaces agree about a field
that appears on both.
Filling a form
Open the file as an editor, write values, then persist. Every call that changes an editor returns it, so the whole thing is one pipeline:
"path/to/form.pdf"
|> Editor.open!()
|> Form.put_value!("full_name", "Jane Doe")
|> Form.put_value!("subscribe", true)
|> Editor.save!("path/to/filled.pdf")
|> Editor.close()
#=> :okThe values are the plain terms fields/1 returns — no wrapper, no tag. Fields
are addressed by name and only an existing field can be written: there is no
way to add one, so a name the form does not carry is an error rather than a new
field.
The tuple-returning half
The non-bang functions are uniform in the same way — each returns
{:ok, editor} — so they read as one with/1 with no shape changes in the
middle:
values = %{"full_name" => "Jane Doe", "subscribe" => true}
with {:ok, editor} <- Editor.open("path/to/form.pdf"),
{:ok, editor} <- Form.put_values(editor, values),
{:ok, editor} <- Editor.save(editor, "path/to/filled.pdf") do
Editor.close(editor)
end
#=> :okPdfElixide.Editor.to_binary/2 and PdfElixide.Editor.close/1 are the two ways
such a pipeline ends: one hands back bytes, the other :ok. Everything before
them hands back the editor.
Several fields at once
PdfElixide.Form.put_values/2 takes a map with string keys, or a list of
{name, value} pairs, and validates all of them before it writes any:
unknown names, duplicates, names that are not strings and values outside
PdfElixide.Form.Field.value/0 are all caught up front, against a single
fields/1 read.
{:ok, editor} = Form.put_values(editor, %{"full_name" => "Jane Doe", "subscribe" => true})
# A list when the order matters — a map is applied in `Enum` order, which is unspecified.
{:ok, editor} = Form.put_values(editor, [{"full_name", "Jane Doe"}, {"country", ["Canada"]}])It is not a transaction. A failure after validation stops at the first error, with any earlier writes already applied. It is a convenience for validation and composition, not an atomic batch.
PdfElixide.Form.update_value/3 transforms a field in place, handing fun the
current value and writing back whatever it returns:
{:ok, editor} = Form.update_value(editor, "full_name", &String.upcase/1)
# A field carrying no value hands `fun` a nil.
{:ok, editor} = Form.update_value(editor, "country", fn
nil -> ["Canada"]
other -> other
end)That is a read and then a write, not an atomic read-modify-write — another process holding the same editor can write in between.
Saving
Nothing is written until PdfElixide.Editor.save/3 writes a file or
PdfElixide.Editor.to_binary/2 hands back the bytes, and neither consumes the
editor: keep editing and write again. close/1 discards unsaved edits, so
write before you close.
{:ok, editor} = Editor.save(editor, "path/to/filled.pdf")
{:ok, bytes} = Editor.to_binary(editor)Both take the same options (PdfElixide.Editor.save_opts/0): :incremental,
:compress, :linearize and :garbage_collect. For form filling against an
existing PDF, an incremental save appends only the field-value updates and
leaves the original AcroForm structure as it was:
{:ok, editor} = Editor.save(editor, "path/to/filled.pdf", incremental: true)One asymmetry worth knowing: to_binary/2 clears
PdfElixide.Editor.modified?/1 even though it writes no file, while an
incremental save/3 leaves it set.
An editor is a handle, not a value
Rebinding does not fork it. The editor a mutating call returns is the one that went in:
editor = Editor.open!("path/to/form.pdf")
filled = Form.put_value!(editor, "full_name", "Jane")
# `editor` and `filled` are the same handle — the original is filled too.So a pipeline sequences effects rather than threading a value: an earlier binding will not give you the document as it was before the edit. Reopen the source for that.
Signature fields
A signature field (/FT /Sig) is not a fillable field, and this API does not
have one: fields/1 omits it, and field/2, value/2 and put_value/3 all
answer {:error, %PdfElixide.Error{reason: :not_found}} for its name, exactly as
they would for a name the form does not carry. put_values/2 reports it the same
way, from the fields/1 read it validates against.
put_value/3 has to refuse it rather than merely fail to find it. A signature's
/V is a signature dictionary rather than a value, and writing any value over
it — nil included — replaces that dictionary, so a filled form would silently
come back unsigned.
This holds for a field whose /FT is declared on an ancestor rather than on the
field itself, which the PDF specification permits. Reading, verifying and
producing signatures is a separate capability, and not one this library offers.
Check boxes and radio groups
:kind tells the two apart, per "Field kinds and flags" above. What follows
applies to both, and to writing rather than reading.
Setting a button field writes /Yes for true and /Off for false, and those
are the only two states put_value/3 can produce. That makes the read-then-write
round trip lossy for some check boxes and radio groups, in two ways.
A box whose on-state is /On rather than /Yes comes back unchecked. It
reads as true, since both names mean "checked", but writing that true back
emits /Yes — which is not the state the widget declares. Nothing in the value
reveals this; the two spellings are indistinguishable once read. (/No collapses
to false and writes /Off in the same way, but harmlessly: /Off is the off
state for every check box.)
A box whose on-state is a custom name — /Export1, say — cannot be checked
at all. true writes /Yes, which matches no widget state, and no other value
writes a PDF name either. Writing the on-state's name as a string is not a
workaround and makes matters worse: it goes into /V and is copied into the
widget's /AS, where the PDF specification requires a name, so a reader may
render the field wrongly.
Either field needs its dictionaries edited directly, which this library does not expose. Reading such a field is unaffected — only writing one back is.
What a form call locks
Reading follows the source: fields/1 on a PdfElixide.Document takes the
handle's lock shared, like every other document read, while fields/1 on an
editor takes the editor's lock exclusively — the same lock a write takes — so
concurrent form work on one editor serializes even when it only reads. field/2
and value/2 are fields/1 filtered in Elixir, so reading several fields one at
a time costs more than one fields/1. Give each process its own editor if you
need them to work at once; see the Concurrency guide.