Help us shape the future of Go! Take the 2026 Go Developer Survey

forms

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: BSD-3-Clause Imports: 12 Imported by: 0

README

forms

Read, fill and draw a PDF's AcroForm — the boxes somebody is meant to type into, the squares they tick, the lists they choose from.

Pure Go, CGO_ENABLED=0, standard library plus the rest of go-pdfkit. Builds for js/wasm and for every architecture the fleet targets.

d, _ := reader.Open(b)
f, ok := forms.Read(d)
if !ok {
    // The document has no form. An AcroForm dictionary with an empty field
    // list is not a form: 561 of the 118 833 files in the figure corpus are
    // exactly that, left behind by a producer.
}
for _, field := range f.Fields() {
    fmt.Println(field.Name, field.Kind, field.Value)
}
f.Fill("f1_02", "Wolfgang Amadeus Mozart")
f.Fill("c1_1", "yes")

What is awkward about forms

A field's value is not what gets drawn. What gets drawn is a little content stream written down beside it, called an appearance, and whoever changes the value has to write a new one. A reader that fills a field and does not do that has filled in nothing anybody can see.

So this draws them. Field.Appearance gives back the stream a value needs, built from the default appearance string the document supplies — a fragment of content stream naming a font, a size and a colour — in which a size of zero means "as large as fits", which is what every form in the corpus asks for.

It handles one line and several, ranged left, centred and ranged right, comb fields divided into one cell a character, list boxes with the chosen rows marked, and the largest size that fits both the width and the height of the box.

A checkbox needs none of this: its widget already carries a drawing for each of its states, so choosing among them is a matter of saying which. That is why every one of the corpus's 2 015 checkboxes has an appearance dictionary and its 11 803 text fields mostly have none — an empty box has nothing to show.

What it was measured against

The fleet's figure corpus is the wrong corpus for forms: of its 118 863 files, 561 carry an AcroForm and every one of those has an empty field list, and there are 40 widget annotations in the whole of it. Nobody puts a fillable form in a figure.

So a second one was fetched: 69 public forms, 68 with a form in them, 13 854 fields — 11 803 text (232 of them several lines, 121 comb), 2 015 checkboxes, 34 push buttons, 2 signature fields; 9 968 quadded, 1 787 with a stated length, 1 435 read-only. Every field that would take one was filled and 10 391 appearances drawn, every one naming a font the document actually carries.

That is what found a file whose fields name HelveticaLTStd-Bold 106 times and which does not carry it. A stream naming a font nothing can find draws nothing at all, so the form's own fallback is used instead.

What it does not do

XFA — Adobe's XML form language. 66 of those 68 forms carry an XFA copy beside the standard one, and every one of them is fillable through the standard one. A file that has both is not made more readable by reading the harder one. Form.HasXFA says when one is there.

Only one weight of each metric-compatible stand-in is carried, so a bold standard face is measured as its regular where the document gives no widths of its own. A bold string is a little wider than that, so an auto-sized bold value comes out a little larger than Acrobat would make it — a point on a line of text, stated here rather than hidden.

Licence

BSD-3-Clause.

Documentation

Overview

Package forms is the fillable half of a PDF: the boxes someone is meant to type into, the squares they tick, and the lists they choose from.

A form is not drawn where it is defined. The document holds one list of fields, each field holds its value, and each field points at one or more widgets — the rectangles on the pages where it shows. What a filled-in field looks like is a little content stream of its own, called an appearance, and the awkward part of forms is that a field's appearance is not derived from its value when the page is drawn: it is written down beside the value, and whoever changes the value has to write a new one. A reader that fills a field and does not do that has filled in nothing anyone can see.

So this package does three things. It reads the field tree — which is a tree, because a field may be a parent of others and inherits from wherever up the tree a thing was last said. It sets values. And it draws the appearance a value needs, from the default appearance string the document supplies, which is a fragment of content stream naming a font, a size and a colour, and in which a size of zero means "as large as fits".

What it does not do is lay out XFA, Adobe's XML form language — and the reason has two halves, because the forms do.

Measured over 2 240 real government forms: 1 499 carry a form, 560 of those carry an XFA package, and 546 of THOSE are static. A static one is a second, proprietary description of a form that is already there: the pages are drawn, the widgets exist, and everything here works on it. Such a file is not made more readable by reading the harder description.

The other fourteen are dynamic. Their pages hold a panel reading "Please wait... your PDF viewer may not be able to display this type of document", and the form is laid out from the XML when it is opened — by Adobe's reader and by nothing else, the format having been removed from PDF 2.0. Laying one out means an XFA layout engine; the reference implementation, pdf.js, spends 395 kilobytes of JavaScript on it.

So this says which kind a document is, through Form.Dynamic, and hands back the XML through Form.Packets — the values a form has been filled with live in its datasets part, as ordinary XML. What it will not do is draw nothing and say nothing, which is what every tool including this one did before: a document that looks blank and is not.

Index

Constants

This section is empty.

Variables

View Source
var ErrKind = errors.New("forms: the field does not hold that sort of value")

ErrKind is what a field says when it is asked for a value of the wrong sort — a checkbox given a sentence, or a text field ticked.

View Source
var ErrReadOnly = errors.New("forms: the field is read-only")

ErrReadOnly is what a field says when the document asks that it not be filled in.

Functions

This section is empty.

Types

type Appearance

type Appearance struct {
	// Content is the stream itself.
	Content []byte
	// BBox is the box it is drawn in, which is the widget's own size with its
	// corner at the origin: an appearance is drawn in a space of its own and
	// placed by the widget's rectangle.
	BBox [4]float64
	// FontName is the name Content uses for its font, and Font the object in
	// the form's resource dictionary that name stands for. Whoever writes the
	// stream out has to give it a resource dictionary saying so.
	FontName string
	Font     reader.Object
}

An Appearance is the little content stream that shows a field's value, and everything needed to put it in the document.

type Field

type Field struct {
	// Name is the whole name, with a dot between each part, which is how a
	// field is addressed from outside: the document's own names are only
	// unique among their brothers.
	Name string
	Kind Kind

	// Value is what the field holds. For a checkbox or a radio button it is
	// the name of the chosen state, and Off means not chosen.
	Value string
	// Values is what a list box holds when more than one row is chosen.
	Values []string
	// Options are the rows of a choice field: what is stored and what is
	// shown, which a file may say separately.
	Options []Option

	// MaxLen is how many characters a text field takes, or zero for as many
	// as one likes.
	MaxLen int
	// Quadding is 0 for ranged left, 1 for centred and 2 for ranged right.
	Quadding int

	ReadOnly bool
	Required bool
	NoExport bool
	// Multiline, Password and Comb are the ways a text field can be unusual.
	// A comb field is one divided into MaxLen equal cells, one character to
	// each, which is how a form asks for a code or a number one digit at a
	// time.
	Multiline bool
	Password  bool
	Comb      bool
	// Sorted and MultiSelect belong to a choice field, and Editable says a
	// combo box will take something that is not one of its rows.
	Sorted      bool
	MultiSelect bool
	Editable    bool

	// Widgets are the places on the pages where the field shows. Nearly every
	// field has one; a field asking the same thing on several pages has more.
	Widgets []Widget
	// contains filtered or unexported fields
}

A Field is one thing a person fills in.

func (*Field) Appearance

func (f *Field) Appearance(w Widget) (Appearance, bool)

Appearance draws the stream that shows what a field holds, for one of its widgets.

A checkbox or a radio button does not need one: its widget already carries a drawing for every state it has, and choosing among them is a matter of saying which — which is why every one of the 2 015 checkboxes in the corpus has an appearance dictionary and 11 803 text fields mostly have none, since an empty box has nothing to show.

func (*Field) Changed

func (f *Field) Changed() bool

Changed says a value was put here rather than read from the file, which is what tells whoever writes the document out what has to be written.

func (*Field) Checked

func (f *Field) Checked() bool

Checked says whether a box is ticked or a button pressed.

func (*Field) Choose

func (f *Field) Choose(values ...string) error

Choose picks rows of a choice field. A list box that says it takes several takes several; anything else takes one.

func (*Field) Dict added in v0.2.0

func (f *Field) Dict() reader.Dict

Dict is the field's own dictionary, as the document holds it. Whoever writes the document out copies this and changes what has to change, rather than building a field from nothing and losing everything this package does not know about — a field's actions, its appearance characteristics, the border somebody chose.

func (*Field) Press

func (f *Field) Press(state string) error

Press chooses one particular button of a radio group by the state its widget names, which is what to use when a group has more than two buttons.

func (*Field) Ref added in v0.2.0

func (f *Field) Ref() (reader.Ref, bool)

Ref is where the field's own dictionary lives in the document. A field written inside another object rather than as one of its own has none, which happens in files that put a whole form in a single array.

func (*Field) SetChecked

func (f *Field) SetChecked(on bool) error

SetChecked ticks or unticks a checkbox, and presses a radio button.

A ticked box does not hold "true": it holds the name of the state its widget calls chosen, which every widget names for itself. That is how a row of radio buttons sharing one value says which of them was pressed.

func (*Field) SetText

func (f *Field) SetText(s string) error

SetText puts a string in a text field, or in a combo box that takes one.

func (*Field) States

func (f *Field) States() []string

States are the names the field's buttons answer to, which is what Press takes. Off is not among them; it is what every button is when none is pressed.

func (*Field) Text

func (f *Field) Text() string

Text is what the field shows: its value, or for a choice field the label its value stands for, since a file may store one thing and show another.

type Form

type Form struct {
	// contains filtered or unexported fields
}

A Form is a document's fillable part.

func Read

func Read(d *reader.Document) (*Form, bool)

Read reads a document's form, or reports that it has none. A document with an AcroForm dictionary and no fields in it has none: producers leave the dictionary behind, and 561 of the 118 833 files in the figure corpus have exactly that and nothing else.

func (*Form) Changed

func (f *Form) Changed() []*Field

Changed lists the fields somebody has filled in, which is what a writer needs and nothing more.

func (*Form) DefaultAppearance added in v0.2.0

func (f *Form) DefaultAppearance() string

DefaultAppearance is the form's own, which a field with none of its own falls back on.

func (*Form) Dict added in v0.2.0

func (f *Form) Dict() reader.Dict

Dict is the form's own dictionary — the AcroForm — which holds the defaults and the resources every field falls back on.

func (*Form) Dynamic added in v0.3.0

func (f *Form) Dynamic() bool

Dynamic says the document's pages are a placeholder and its real form exists only as XML.

This is the difference that matters about XFA, and it is not the same question as Form.HasXFA.

A STATIC XFA form carries a full standard form beside the XML: the pages are drawn, the widgets are there, and everything in this package works on it. Of 1 499 forms in a corpus of 2 240 real documents, 560 carry XFA and 546 are of this kind — the XML is a second description of a form that is already readable, and reading the harder one gains nothing.

A DYNAMIC one is different in kind. Its pages hold a panel reading "Please wait... your PDF viewer may not be able to display this type of document", and the form is laid out from the XML when it is opened. Fourteen of those 2 240 are like this. Every viewer but Adobe's own shows the panel: the format was removed from PDF 2.0, and neither poppler nor any browser lays it out.

So a caller meeting one of these has a document that looks blank and is not. Saying so is worth more than drawing nothing quietly, which is what every tool including this one did before.

func (*Form) Field

func (f *Form) Field(name string) (*Field, bool)

Field finds one by its whole name.

func (*Form) Fields

func (f *Form) Fields() []*Field

Fields are every field that can hold a value, in the document's own order.

func (*Form) Fill

func (f *Form) Fill(name, value string) error

Fill sets a field by name, choosing the right sort of setter for what it is, which is what a command line or a map of values wants.

func (*Form) HasXFA

func (f *Form) HasXFA() bool

HasXFA says the document also carries Adobe's XML form description. Nothing here reads it: a file that has both is fillable through the standard half, which is the half this reads.

func (*Form) NeedAppearances added in v0.2.0

func (f *Form) NeedAppearances() bool

NeedAppearances is a document saying that the values in it are right and the drawings beside them are not, and asking whoever opens it to draw them again. Fifteen of the corpus's forms say it. A writer that fills a field and draws its appearance should clear it, since it has just done what was asked.

func (*Form) Packets added in v0.3.0

func (f *Form) Packets() []Packet

Packets are the parts of the XFA package, in the order the document names them, or nil when there is no package.

The form cannot be laid out from here — that wants a layout engine this package does not have — but the values can be read: what has been filled in lives in the datasets part, as ordinary XML.

func (*Form) Resources added in v0.2.0

func (f *Form) Resources() reader.Dict

Resources is the dictionary a default appearance's font is named in, or nil when the form has none.

type Kind

type Kind uint8

A Kind says what sort of a field it is, which decides both what a value means and what its appearance looks like.

const (
	Text Kind = iota
	Checkbox
	Radio
	PushButton
	ComboBox
	ListBox
	Signature
)

The kinds a field may be. A push button is a field that holds no value at all — it is a place to click — and a signature field is one this package will read and will not pretend to fill.

func (Kind) String

func (k Kind) String() string

String names the kind, for a message someone has to read.

type Option

type Option struct {
	// Value is what is stored when the row is chosen and Label what is shown.
	// A file that gives only one gives the value, and the label is the same.
	Value string
	Label string
}

An Option is one row of a choice field.

type Packet added in v0.3.0

type Packet struct {
	Name string
	Data []byte
}

Packet is one part of a document's XFA package: a name and the XML under it.

A package is split into parts — template, datasets, config, localeSet — and the two worth reading are template, which describes the form, and datasets, which holds what has been filled in.

type Widget

type Widget struct {
	// Page counts from one. It is zero when the document does not say which
	// page the widget is on, which happens in files whose page tree and
	// annotation lists disagree.
	Page int
	// Rect is where on that page, in the page's own coordinates, lower left
	// corner first.
	Rect [4]float64
	// On is the state name that means "chosen", for a checkbox or a radio
	// button. Every such widget has one of its own, which is how a set of
	// radio buttons sharing a value tells which one was pressed.
	On string
	// Hidden says the document asks for this one not to be drawn.
	Hidden bool
	// contains filtered or unexported fields
}

A Widget is one place on a page where a field shows.

func (Widget) Dict added in v0.2.0

func (w Widget) Dict() reader.Dict

Dict is the widget's own dictionary.

func (Widget) Ref added in v0.2.0

func (w Widget) Ref() (reader.Ref, bool)

Ref is where a widget's dictionary lives. For the common field — one that shows in exactly one place — this is the same object as the field's own, since a file merges the two.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL