Skip to main content

Forms (AcroForms)

A PDF form (an AcroForm) is a set of fields that a reader lets a person fill in: text boxes, check boxes, radio buttons, drop-down lists and buttons. PdfPinata can build a form from nothing, and it can fill in or read a form that another program made. Everything on this page is in the core PdfPinata package. The field types are in the PdfPinata.Pdf.AcroForms namespace and the widget type is in PdfPinata.Pdf.Annotations.

Create the form

A document has at most one form. GetOrCreateAcroForm makes it the first time you call it and returns the same form after that. PdfDocument.AcroForm only reads: it returns null until the document has a form.

src/SampleApp/Demos/FormsDemo.cs
// GetOrCreateAcroForm makes the form, makes it indirect and puts it in the catalogue.
// PdfDocument.AcroForm only reads, and answers null until this has been called.
var form = document.GetOrCreateAcroForm();

// /NeedAppearances asks the viewer to build the appearance streams for the text and
// choice fields, which is what saves this demo from laying out their glyphs. The buttons
// below still carry their own, because a check box's appearance is the thing being
// toggled rather than a rendering of its value.
form.NeedAppearances = true;

// A real size, not the "/Helv 0 Tf" most examples show. Zero means auto-size, and what a
// viewer makes of that on a multiline box is its own business: Ghostscript scales the
// first line to the height of the whole box, which fills the page with one word.
form.DefaultAppearance = "/Helv 9 Tf 0 g";

// The two standard-14 faces a form needs, named as /DA refers to them. Neither is
// embedded and neither has to be: these are the faces every viewer already has.
form.AddStandardFont("/Helv", "/Helvetica");
form.AddStandardFont("/ZaDb", "/ZapfDingbats");

DefaultAppearance is the font, size and colour a reader uses when it draws a field's value. /Helv 9 Tf 0 g means "the font called /Helv, 9 points, black". AddStandardFont tells the form which font /Helv stands for. Use one of the standard 14 PDF fonts here; every reader has them, so nothing is embedded.

Put a field on a page

A field and the box you see on the page are two objects. The field holds the name and the value. The widget annotation is where the field is drawn, and one field can have several widgets. To place a field:

  1. Create the field, for example new PdfTextField(document).
  2. Give it a Name.
  3. Add it to the form with form.Fields.Add(field).
  4. Call field.AddWidget(page, rectangle).

If you call AddWidget before the field is in a form, it throws InvalidOperationException.

The rectangle is in PDF's own page coordinates, measured up from the bottom-left corner of the page. XGraphics measures down from the top left, so convert with gfx.Transformer.WorldToDefaultPage:

src/SampleApp/Demos/FormsDemo.cs
// A field says what it is and what it holds; a widget says where on a page it is drawn.
// AddWidget makes one and links the two - always a separate annotation under /Kids, so a
// field that gains a second widget later does not change shape.
//
// The drawing above is in world space, measured down from the top left. A widget is
// placed in default page space, measured up from the bottom left.
PdfWidgetAnnotation Place(PdfAcroField field, XRect box)
{
return field.AddWidget(page, new PdfRectangle(gfx.Transformer.WorldToDefaultPage(box)));
}

Text fields

PdfTextField holds a string in Text. ToolTip is the text a reader shows when the pointer is over the field. MaxLength, MultiLine and Password are properties of their own, and other behaviour is set through Flags:

src/SampleApp/Demos/FormsDemo.cs
var fullNameBox = Row("Full name", 20);
var fullName = new PdfTextField(document)
{
Name = "fullName",
ToolTip = "Your name as it appears on your passport"
};
form.Fields.Add(fullName);
StyleText(fullName);
Place(fullName, fullNameBox);
fullName.Text = "Ada Lovelace";
EndRow(fullNameBox, "A value in /V, a tooltip in /TU, and a box the library draws itself.");

var emailBox = Row("Email", 20);
var email = new PdfTextField(document)
{
Name = "email",
ToolTip = "Required - we will not use it for anything",
Flags = PdfAcroFieldFlags.Required
};
form.Fields.Add(email);
StyleText(email);
Place(email, emailBox);
EndRow(emailBox, "Required, so a reader marks it when the form is submitted empty.");

For a field divided into equal cells, such as a postcode, set MaxLength and the PdfAcroFieldFlags.Comb flag together.

A text field draws its own box and value. BackColor, BorderColor, ForeColor and Font decide how it looks, and the field redraws when any of them or Text changes:

src/SampleApp/Demos/FormsDemo.cs
// A text field draws its own appearance out of these, from the value in /V - so the box a
// reader shows is the library's drawing rather than something built from /MK, and naming
// the colours here is what stops a field losing its box the moment it is given a value.
// The size in /DA is the field's own rather than the form's, for the same reason the form
// names one at all.
void StyleText(PdfTextField field)
{
field.BackColor = XColor.FromArgb(245, 245, 245);
field.BorderColor = XColor.FromArgb(115, 115, 115);
field.DefaultAppearance = "/Helv 9 Tf 0 g";
}

Check boxes

A check box shows one of two drawings: one for "on" and one for "off". You draw both with XGraphics on an XForm and give them to the widget with SetAppearance(state, form). Checked then sets the value and picks the drawing that matches it.

src/SampleApp/Demos/FormsDemo.cs
var tickBox = Row("Subscribe", 16);
tickBox = new XRect(tickBox.X, tickBox.Y, 16, 16);

var subscribe = new PdfCheckBoxField(document)
{
Name = "subscribe",
ToolTip = "Send me the newsletter"
};
form.Fields.Add(subscribe);
var tick = Place(subscribe, tickBox);

var boxOutline = new XPen(XColors.Gray, 1);
var inside = new XRect(0.5, 0.5, 15, 15);

tick.SetAppearance("/Yes", Appearance(tickBox, into =>
{
into.DrawRectangle(boxOutline, XBrushes.White, inside);
into.DrawLines(new XPen(XColors.Black, 2),
new[] { new XPoint(3.5, 8), new XPoint(6.5, 11.5), new XPoint(12.5, 4.5) });
}));
tick.SetAppearance("/Off", Appearance(tickBox, into =>
into.DrawRectangle(boxOutline, XBrushes.White, inside)));

// /AS names which of the two streams is showing; /V is the field's value. Checked keeps
// them in step, reading the names out of the appearances the widget was just given.
subscribe.Checked = true;
EndRow(tickBox, "/AP /N holds one stream per state; /AS names the one on show.");

The demo's Appearance helper makes an XForm the size of the box and draws on it with XGraphics.FromForm.

Radio groups

A radio group is one field with one widget per choice. Options lists the choices:

src/SampleApp/Demos/FormsDemo.cs
var delivery = new PdfRadioButtonField(document)
{
Name = "delivery",
ToolTip = "How soon do you want it",
Flags = PdfAcroFieldFlags.Radio | PdfAcroFieldFlags.NoToggleToOff
};
form.Fields.Add(delivery);

string[] choices = { "Standard", "Express", "Collect" };
delivery.Options = choices;

Each widget's "on" state must have the same name as its choice, because the field's value is compared with those names. AppearanceState sets which drawing each widget shows, and SelectedIndex sets the field's value:

src/SampleApp/Demos/FormsDemo.cs
for (var index = 0; index < choices.Length; index++)
{
var dot = new XRect(FieldX + index * 100, deliveryRow.Y, 14, 14);
var button = Place(delivery, dot);

var ring = new XRect(0.5, 0.5, 13, 13);
var pip = new XRect(3.5, 3.5, 7, 7);

button.SetAppearance("/" + choices[index], Appearance(dot, into =>
{
into.DrawEllipse(boxOutline, XBrushes.White, ring);
into.DrawEllipse(XBrushes.Black, pip);
}));
button.SetAppearance("/Off", Appearance(dot, into =>
into.DrawEllipse(boxOutline, XBrushes.White, ring)));

// Both states are in the file; /AS picks the one on show. SetAppearance points it at
// whichever it has just written, so exactly one button has to be told otherwise -
// and a radio group where two are on is the mistake this prevents.
button.AppearanceState = index == Chosen ? "/" + choices[index] : "/Off";

gfx.DrawString(choices[index], noteFont, XBrushes.Black,
new XPoint(FieldX + index * 100 + 19, deliveryRow.Y + 11));
}

// Which one the field holds. SelectedIndex looks the choice up in /Opt and writes /V as
// the name the chosen widget's "on" state is called by.
delivery.SelectedIndex = Chosen;

Combo boxes and list boxes

Both take their choices from Options. A combo box is a drop-down. Add PdfAcroFieldFlags.Edit to let the person type a value that is not in the list.

src/SampleApp/Demos/FormsDemo.cs
var country = new PdfComboBoxField(document)
{
Name = "country",
ToolTip = "Pick one, or type your own",
Flags = PdfAcroFieldFlags.Combo | PdfAcroFieldFlags.Edit | PdfAcroFieldFlags.Sort,
Options = new[]
{
"Australia", "Canada", "Ireland", "New Zealand", "United Kingdom"
}
};
form.Fields.Add(country);
country.DefaultAppearance = "/Helv 9 Tf 0 g";
Decorate(Place(country, countryBox), 0.96);
country.SelectedIndex = 4;

A list box shows several rows at once. Set PdfAcroFieldFlags.MultiSelect before you select more than one row with SelectedIndices; without the flag, that throws InvalidOperationException.

src/SampleApp/Demos/FormsDemo.cs
var interests = new PdfListBoxField(document)
{
Name = "interests",
ToolTip = "Choose as many as you like",
Flags = PdfAcroFieldFlags.MultiSelect,
Options = new[]
{
"Typography", "Colour management", "Page imposition", "Tagged PDF"
}
};
form.Fields.Add(interests);
interests.DefaultAppearance = "/Helv 9 Tf 0 g";
Decorate(Place(interests, interestsBox), 0.96);
interests.SelectedIndices = new[] { 0, 3 };

The demo's Decorate helper writes the widget's /MK entry by hand. /MK is the background and border colour a reader uses when it draws the field itself, and PdfPinata has no property for it.

Push buttons

A push button has no value. It exists to do something when clicked, and that action belongs to the widget. PdfPinata has no property for a widget's action, so the demo writes the /A entry directly. A reader does not draw a push button for you, so give it an appearance:

src/SampleApp/Demos/FormsDemo.cs
var help = new PdfPushButtonField(document)
{
Name = "help",
ToolTip = "Opens the PdfPinata repository"
};
form.Fields.Add(help);
var face = Place(help, buttonBox);

face.Elements["/A"] = new PdfLiteral(
"<</S/URI/URI(https://github.com/PinataLabs/PdfPinata)>>");

var caption = new PdfDictionary(document);
caption.Elements.SetString("/CA", "Read the manual");
face.Elements["/MK"] = caption;

// Unlike the text fields, a push button gets no help from /NeedAppearances, so its face
// is drawn here - with the same XGraphics calls that drew the page.
var buttonFont = new XFont(Sans, 10);
face.SetAppearance(Appearance(buttonBox, into =>
{
into.DrawRectangle(new XPen(XColor.FromArgb(89, 115, 153), 1),
new XSolidBrush(XColor.FromArgb(217, 227, 240)), new XRect(0.5, 0.5, 139, 23));
into.DrawString("Read the manual", buttonFont,
new XSolidBrush(XColor.FromArgb(26, 51, 89)),
new XRect(0, 0, 140, 24), XStringFormats.Center);
}));

Flags

Flags takes any combination of PdfAcroFieldFlags. The ones that apply to every field are ReadOnly, Required and NoExport. The others apply to one kind of field, such as Multiline and Comb for text, or Edit, Sort and MultiSelect for choices.

Some bits in the same entry decide what kind of field it is. The Flags setter keeps those bits whatever you assign, so new PdfComboBoxField(document) { Flags = PdfAcroFieldFlags.Required } is still a combo box.

To lock one field, set field.ReadOnly = true. To lock every top-level field in the form, call document.MakeAcroFormsReadOnly().

Nest fields

A field can hold other fields. Their names join with periods into a full name, and you look a field up by that full name. A field's own Name must not contain a period; if it does, the setter throws ArgumentException. To get the name applicant.name, nest one field inside another:

PdfTextField applicant = new PdfTextField(document) { Name = "applicant" };
form.Fields.Add(applicant);

PdfTextField fullName = new PdfTextField(document) { Name = "name" };
applicant.Fields.Add(fullName);
fullName.AddWidget(page, new PdfRectangle(new XRect(60, 700, 200, 20)));

// form.Fields["applicant.name"] now finds fullName.

Fill in or read an existing form

Open the document in Modify mode, find each field by its full name, and set the typed property:

using PdfDocument document = PdfReader.Open("application.pdf", PdfDocumentOpenMode.Modify);
PdfAcroForm form = document.AcroForm;

foreach (string fieldName in form.Fields.DescendantNames)
Console.WriteLine(fieldName);

if (form.Fields["applicant.name"] is PdfTextField applicantName)
applicantName.Text = "Ada Lovelace";

if (form.Fields["subscribe"] is PdfCheckBoxField subscribe)
subscribe.Checked = true;

document.Save("application-filled.pdf");

PdfReader is in PdfPinata.Pdf.IO. DescendantNames lists the full name of every field that has no child fields. To read a value, use the same typed properties: Text, Checked, SelectedIndex or SelectedIndices. Value returns the raw PDF object.

If the document is signed, Save breaks the signatures. Open it in Append mode and save with SaveIncremental instead; see incremental saving. If a signature certifies the document and does not allow form filling, setting a value throws InvalidOperationException.

Things to know

  • Flattening is not offered. PdfPinata cannot turn fields into ordinary page content. Making the fields read-only is the nearest option.
  • A text field needs a font resolver. Creating a PdfTextField, or reading one from a file, asks the registered font resolver for its default font. Register one first; see Installation.
  • Name a real font size. A size of 0 in DefaultAppearance means "fit to the box", and readers do different things with it. Ghostscript scales the first line of a multi-line field to the height of the whole box.
  • The value a text field draws is one line. PdfPinata draws the value from the top-left of the box in Font and does not wrap it. With NeedAppearances set, a reader can draw the field again itself.
  • A plain text field has no drawing of its own. If it has no background, no border and no value, PdfPinata removes its appearance so that the reader draws it from /MK.
  • Password hides what is typed, nothing more. The value is still stored in the file. Do not use a form field to keep a secret.
  • A field's children are not all fields. A field's widgets sit in the same list as its child fields, and widgets have no name. Allow for that if you walk a form yourself.
  • Every widget prints. AddWidget sets the print flag, so the field appears on paper.
  • Coordinates go up from the bottom. Widget rectangles are in PDF page coordinates, not the top-left coordinates XGraphics draws in.

See it in action

The Forms demo builds one page with every kind of field, and a second page that lists what the form API can and cannot do.

The full Forms demo
src/SampleApp/Demos/FormsDemo.cs
const string Sans = "Liberation Sans";

var document = new PdfDocument();
document.Info.Title = "Interactive form";

var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);

var titleFont = new XFont(Sans, 18, XFontStyle.Bold);
var labelFont = new XFont(Sans, 9, XFontStyle.Bold);
var noteFont = new XFont(Sans, 7.5);

// ---- The form ----------------------------------------------------------------
//
// GetOrCreateAcroForm makes the form, makes it indirect and puts it in the catalogue.
// PdfDocument.AcroForm only reads, and answers null until this has been called.
var form = document.GetOrCreateAcroForm();

// /NeedAppearances asks the viewer to build the appearance streams for the text and
// choice fields, which is what saves this demo from laying out their glyphs. The buttons
// below still carry their own, because a check box's appearance is the thing being
// toggled rather than a rendering of its value.
form.NeedAppearances = true;

// A real size, not the "/Helv 0 Tf" most examples show. Zero means auto-size, and what a
// viewer makes of that on a multiline box is its own business: Ghostscript scales the
// first line to the height of the whole box, which fills the page with one word.
form.DefaultAppearance = "/Helv 9 Tf 0 g";

// The two standard-14 faces a form needs, named as /DA refers to them. Neither is
// embedded and neither has to be: these are the faces every viewer already has.
form.AddStandardFont("/Helv", "/Helvetica");
form.AddStandardFont("/ZaDb", "/ZapfDingbats");

gfx.DrawString("Interactive form", titleFont, XBrushes.Black, new XPoint(56, 68));
gfx.DrawLine(new XPen(XColors.SteelBlue, 1.5), 56, 78, 539, 78);
gfx.DrawString("Open this in a reader that supports forms - the fields below are fillable.",
noteFont, XBrushes.DimGray, new XPoint(56, 92));

const double FieldX = 210;
const double FieldW = 300;

// One cursor down the page, rather than a measured coordinate per row. The note under
// each field is what forces it: the notes are wider than the label column, so anything
// that placed them by hand would sooner or later run one of them under a field box.
double cursor = 116;

XRect Row(string label, double height)
{
gfx.DrawString(label, labelFont, XBrushes.Black, new XPoint(56, cursor + 12));
return new XRect(FieldX, cursor, FieldW, height);
}

void EndRow(XRect box, string note)
{
gfx.DrawString(note, noteFont, XBrushes.DimGray,
new XPoint(FieldX, box.Bottom + 12));
cursor = box.Bottom + 28;
}

// ---- Putting a field on the page ---------------------------------------------
//
// A field says what it is and what it holds; a widget says where on a page it is drawn.
// AddWidget makes one and links the two - always a separate annotation under /Kids, so a
// field that gains a second widget later does not change shape.
//
// The drawing above is in world space, measured down from the top left. A widget is
// placed in default page space, measured up from the bottom left.
PdfWidgetAnnotation Place(PdfAcroField field, XRect box)
{
return field.AddWidget(page, new PdfRectangle(gfx.Transformer.WorldToDefaultPage(box)));
}

// /MK is what a viewer paints a field's box and border from when it is building the
// appearance itself, which for the two choice fields below it is. It is appearance
// characteristics rather than an appearance, and the library wraps no part of it, so
// this and the push button's action are the only entries the demo still writes by name.
void Decorate(PdfWidgetAnnotation widget, double grey)
{
var appearance = new PdfDictionary(document)
{
Elements =
{
["/BG"] = new PdfArray(document, new PdfReal(grey)),
["/BC"] = new PdfArray(document, new PdfReal(0.45))
}
};
widget.Elements["/MK"] = appearance;
}

// A text field draws its own appearance out of these, from the value in /V - so the box a
// reader shows is the library's drawing rather than something built from /MK, and naming
// the colours here is what stops a field losing its box the moment it is given a value.
// The size in /DA is the field's own rather than the form's, for the same reason the form
// names one at all.
void StyleText(PdfTextField field)
{
field.BackColor = XColor.FromArgb(245, 245, 245);
field.BorderColor = XColor.FromArgb(115, 115, 115);
field.DefaultAppearance = "/Helv 9 Tf 0 g";
}

// An appearance stream is a form XObject, and XGraphics draws onto one exactly as it
// draws onto a page - which is what SetAppearance takes. This demo's first draft drew
// its radio rings with an "arc" operator, which is PostScript: a PDF path knows only
// m, l, c, v, y, re and h, and a viewer handed an operator it does not know draws
// nothing and reports nothing. Drawing through XGraphics puts that mistake out of reach.
XForm Appearance(XRect box, Action<XGraphics> draw)
{
var appearance = new XForm(document, new XSize(box.Width, box.Height));
using (var into = XGraphics.FromForm(appearance))
draw(into);
return appearance;
}

// ---- Text fields -------------------------------------------------------------
var fullNameBox = Row("Full name", 20);
var fullName = new PdfTextField(document)
{
Name = "fullName",
ToolTip = "Your name as it appears on your passport"
};
form.Fields.Add(fullName);
StyleText(fullName);
Place(fullName, fullNameBox);
fullName.Text = "Ada Lovelace";
EndRow(fullNameBox, "A value in /V, a tooltip in /TU, and a box the library draws itself.");

var emailBox = Row("Email", 20);
var email = new PdfTextField(document)
{
Name = "email",
ToolTip = "Required - we will not use it for anything",
Flags = PdfAcroFieldFlags.Required
};
form.Fields.Add(email);
StyleText(email);
Place(email, emailBox);
EndRow(emailBox, "Required, so a reader marks it when the form is submitted empty.");

var secretBox = Row("Passphrase", 20);
var secret = new PdfTextField(document)
{
Name = "secret",
ToolTip = "Typed back as bullets",
Password = true
};
form.Fields.Add(secret);
StyleText(secret);
Place(secret, secretBox);
// The flag masks what is typed and nothing more. ISO 32000-1 Table 228 adds only a note
// that a reader "should never store the value", which is advice to the reader rather than
// a guarantee to the author - so a form field is not somewhere to keep a secret.
EndRow(secretBox, "Password: echoed as bullets. Advisory only - not secret storage.");

var postcodeBox = Row("Postcode", 20);
var postcode = new PdfTextField(document)
{
Name = "postcode",
ToolTip = "Six cells, one character each",
MaxLength = 6,
Flags = PdfAcroFieldFlags.Comb
};
form.Fields.Add(postcode);
StyleText(postcode);
Place(postcode, postcodeBox);
// Comb is bit 25, and was the one field flag PdfAcroFieldFlags did not have. It divides
// the box into as many equal cells as /MaxLen allows characters, which is how a form
// draws the boxes for a postcode or a card number.
EndRow(postcodeBox, "Comb + MaxLength: one character per cell, evenly spaced.");

var notesBox = Row("Notes", 56);
var notes = new PdfTextField(document)
{
Name = "notes",
ToolTip = "Anything else we should know",
MultiLine = true
};
form.Fields.Add(notes);
StyleText(notes);
Place(notes, notesBox);
notes.Text = "Multiline: this box wraps and scrolls.";
EndRow(notesBox, "Multiline, so a reader wraps the value rather than scrolling it sideways.");

// ---- A check box -------------------------------------------------------------
//
// Both states are drawn here rather than left to /NeedAppearances. What a check box
// shows IS its value, so the two streams are the field rather than a rendering of it.
var tickBox = Row("Subscribe", 16);
tickBox = new XRect(tickBox.X, tickBox.Y, 16, 16);

var subscribe = new PdfCheckBoxField(document)
{
Name = "subscribe",
ToolTip = "Send me the newsletter"
};
form.Fields.Add(subscribe);
var tick = Place(subscribe, tickBox);

var boxOutline = new XPen(XColors.Gray, 1);
var inside = new XRect(0.5, 0.5, 15, 15);

tick.SetAppearance("/Yes", Appearance(tickBox, into =>
{
into.DrawRectangle(boxOutline, XBrushes.White, inside);
into.DrawLines(new XPen(XColors.Black, 2),
new[] { new XPoint(3.5, 8), new XPoint(6.5, 11.5), new XPoint(12.5, 4.5) });
}));
tick.SetAppearance("/Off", Appearance(tickBox, into =>
into.DrawRectangle(boxOutline, XBrushes.White, inside)));

// /AS names which of the two streams is showing; /V is the field's value. Checked keeps
// them in step, reading the names out of the appearances the widget was just given.
subscribe.Checked = true;
EndRow(tickBox, "/AP /N holds one stream per state; /AS names the one on show.");

// ---- A radio group -----------------------------------------------------------
//
// One field, three widgets. The field holds the name and the value; each widget's "on"
// state is named after the choice it stands for, and that name is what /V is compared
// against - so the two have to agree exactly.
var deliveryRow = Row("Delivery", 14);

var delivery = new PdfRadioButtonField(document)
{
Name = "delivery",
ToolTip = "How soon do you want it",
Flags = PdfAcroFieldFlags.Radio | PdfAcroFieldFlags.NoToggleToOff
};
form.Fields.Add(delivery);

string[] choices = { "Standard", "Express", "Collect" };
delivery.Options = choices;

const int Chosen = 0;

for (var index = 0; index < choices.Length; index++)
{
var dot = new XRect(FieldX + index * 100, deliveryRow.Y, 14, 14);
var button = Place(delivery, dot);

var ring = new XRect(0.5, 0.5, 13, 13);
var pip = new XRect(3.5, 3.5, 7, 7);

button.SetAppearance("/" + choices[index], Appearance(dot, into =>
{
into.DrawEllipse(boxOutline, XBrushes.White, ring);
into.DrawEllipse(XBrushes.Black, pip);
}));
button.SetAppearance("/Off", Appearance(dot, into =>
into.DrawEllipse(boxOutline, XBrushes.White, ring)));

// Both states are in the file; /AS picks the one on show. SetAppearance points it at
// whichever it has just written, so exactly one button has to be told otherwise -
// and a radio group where two are on is the mistake this prevents.
button.AppearanceState = index == Chosen ? "/" + choices[index] : "/Off";

gfx.DrawString(choices[index], noteFont, XBrushes.Black,
new XPoint(FieldX + index * 100 + 19, deliveryRow.Y + 11));
}

// Which one the field holds. SelectedIndex looks the choice up in /Opt and writes /V as
// the name the chosen widget's "on" state is called by.
delivery.SelectedIndex = Chosen;
EndRow(deliveryRow,
"One field, three widgets under /Kids. The field holds the name and the value.");

// ---- Choice fields -----------------------------------------------------------
var countryBox = Row("Country", 20);
var country = new PdfComboBoxField(document)
{
Name = "country",
ToolTip = "Pick one, or type your own",
Flags = PdfAcroFieldFlags.Combo | PdfAcroFieldFlags.Edit | PdfAcroFieldFlags.Sort,
Options = new[]
{
"Australia", "Canada", "Ireland", "New Zealand", "United Kingdom"
}
};
form.Fields.Add(country);
country.DefaultAppearance = "/Helv 9 Tf 0 g";
Decorate(Place(country, countryBox), 0.96);
country.SelectedIndex = 4;
EndRow(countryBox,
"Combo + Edit, so the list can also be typed into. Sort orders it for display.");

var interestsBox = Row("Interests", 56);
var interests = new PdfListBoxField(document)
{
Name = "interests",
ToolTip = "Choose as many as you like",
Flags = PdfAcroFieldFlags.MultiSelect,
Options = new[]
{
"Typography", "Colour management", "Page imposition", "Tagged PDF"
}
};
form.Fields.Add(interests);
interests.DefaultAppearance = "/Helv 9 Tf 0 g";
Decorate(Place(interests, interestsBox), 0.96);
interests.SelectedIndices = new[] { 0, 3 };
EndRow(interestsBox,
"A list box is a choice field without the Combo flag. /I carries the selected rows.");

// ---- A push button -----------------------------------------------------------
//
// A push button has no value at all - it exists for its action. This one opens a URL,
// which lives on the widget rather than on the field, because an action is something a
// person does to an annotation.
var buttonBox = Row("Then", 24);
buttonBox = new XRect(buttonBox.X, buttonBox.Y, 140, 24);

var help = new PdfPushButtonField(document)
{
Name = "help",
ToolTip = "Opens the PdfPinata repository"
};
form.Fields.Add(help);
var face = Place(help, buttonBox);

face.Elements["/A"] = new PdfLiteral(
"<</S/URI/URI(https://github.com/PinataLabs/PdfPinata)>>");

var caption = new PdfDictionary(document);
caption.Elements.SetString("/CA", "Read the manual");
face.Elements["/MK"] = caption;

// Unlike the text fields, a push button gets no help from /NeedAppearances, so its face
// is drawn here - with the same XGraphics calls that drew the page.
var buttonFont = new XFont(Sans, 10);
face.SetAppearance(Appearance(buttonBox, into =>
{
into.DrawRectangle(new XPen(XColor.FromArgb(89, 115, 153), 1),
new XSolidBrush(XColor.FromArgb(217, 227, 240)), new XRect(0.5, 0.5, 139, 23));
into.DrawString("Read the manual", buttonFont,
new XSolidBrush(XColor.FromArgb(26, 51, 89)),
new XRect(0, 0, 140, 24), XStringFormats.Center);
}));
EndRow(buttonBox,
"A push button carries an action instead of a value: /A here is a URI action.");

// ---- Page two: what the typed API can and cannot do ---------------------------
var notesPage = document.AddPage();
var notesGfx = XGraphics.FromPdfPage(notesPage);

notesGfx.DrawString("What the typed AcroForm API does", titleFont, XBrushes.Black,
new XPoint(56, 68));
notesGfx.DrawLine(new XPen(XColors.SteelBlue, 1.5), 56, 78, 539, 78);

var body = new XFont(Sans, 9.5);
var mono = new XFont("Source Code Pro", 8.5);

string[] paragraphs =
{
"Every field on page one is a PdfTextField, PdfCheckBoxField, PdfRadioButtonField,",
"PdfComboBoxField, PdfListBoxField or PdfPushButtonField, made with new, named, given",
"flags, added to the form and put on the page. None of it is assembled by hand.",
"",
"It used to be. Every constructor under PdfPinata.Pdf.AcroForms was internal,",
"PdfAcroFieldCollection had no Add, PdfWidgetAnnotation was internal and there was no",
"way to make a form at all - so the only route was to write the dictionaries of",
"ISO 32000-1 section 12.7 yourself and hang them off the catalogue's /AcroForm."
};

double lineY = 104;
foreach (var paragraph in paragraphs)
{
notesGfx.DrawString(paragraph, body, XBrushes.Black, new XPoint(56, lineY));
lineY += 14;
}

(string Capability, string State)[] table =
{
("Make a form", "PdfDocument.GetOrCreateAcroForm()"),
("Create a field of any type", "new PdfTextField(document), and so on"),
("Add a field to a form or a field", "PdfAcroFieldCollection.Add"),
("Put a field on a page", "PdfAcroField.AddWidget"),
("Name it, describe it, flag it", "Name, ToolTip, Flags"),
("Give a widget an appearance", "PdfAnnotation.SetAppearance"),
("Offer choices", "PdfChoiceField.Options"),
("Read and fill a form somebody wrote", "AcroForm.Fields[name].Value"),
("Make every field read-only", "PdfDocument.MakeAcroFormsReadOnly()"),
("Sign a document", "PdfPinata.Signing - see the Signing demo"),
("Flatten a form into page content", "not offered")
};

lineY += 16;
notesGfx.DrawString("One capability to a line, and where it lives", labelFont,
XBrushes.Black, new XPoint(56, lineY));
lineY += 8;
notesGfx.DrawLine(XPens.LightGray, 56, lineY, 539, lineY);
lineY += 16;

foreach (var row in table)
{
notesGfx.DrawString(row.Capability, body, XBrushes.Black, new XPoint(56, lineY));
notesGfx.DrawString(row.State, mono, XBrushes.DimGray, new XPoint(250, lineY));
lineY += 16;
}

lineY += 14;
string[] closing =
{
"Two entries above are still written by name, because nothing wraps them: /MK, which a",
"viewer paints a field's box from when it builds the appearance itself, and the push",
"button's /A action. Everything else on page one goes through a property or a method.",
"",
"One rule to know. A partial field name may not contain a period, because a period is",
"what joins nested names into the path a field is found by - so Name = \"name.full\" is",
"refused at the call rather than left to produce a field nobody can look up."
};

foreach (var line in closing)
{
notesGfx.DrawString(line, body, XBrushes.Black, new XPoint(56, lineY));
lineY += 14;
}