Interactive form fields
A PDF does not have to be read-only. TTMSFNCPDFLib can place AcroForm fields on
a page — text edits, check boxes, radio groups, combo and list boxes — so the
generated document can be filled in by the recipient in any standard reader and
returned. Use it for application forms, expense claims, consent sheets, and
anything where the alternative is printing, filling by hand, and scanning back.
Note
Form fields are supported on Windows and Linux only. On other platforms the document still generates; the fields are simply not produced, so do not rely on them for a cross-platform deliverable without testing the target.
Adding fields
Add form fields via p.FormFields:
| Type | Method |
|---|---|
| Text edit | AddEdit |
| Multiline text edit | AddMemo |
| Password edit | AddPasswordEdit |
| Check box | AddCheckBox |
| Radio group | AddRadioGroup |
| Radio button | AddRadioButton |
| Combo box | AddComboBox |
| List box | AddListBox |
Each field requires a unique name — it is the key the reader submits the value
under, so duplicates silently collapse into one value. All fields except radio
groups also require a TRectF position, in the same page coordinates as any
drawing call.
p.FormFields.AddEdit('Textfield1', RectF(10, 10, 210, 32), 'Default text', gtaLeading);
p.FormFields.AddCheckBox('Checkbox1', RectF(10, 50, 30, 70), False);
p.FormFields.AddComboBox('Combobox1', RectF(10, 90, 70, 110), ['item1', 'item2', 'item3'], 1);
Mutually exclusive radio buttons
A radio group is the one field type that does not take a rectangle, because it is
a logical container rather than a drawn control: create the group by name with
AddRadioGroup, then add each AddRadioButton into it with its own rectangle.
Buttons in the same group are mutually exclusive; buttons that accidentally end up
in different groups are not, which is the usual cause of "my radio buttons can all
be selected at once".
Field fonts
FormFields.Font sets the default font for all fields, and individual fields can
override it. Two switches are worth knowing:
| Property | Effect |
|---|---|
FontAutoSize |
When True, scales the font to fit the field bounds |
IgnoreFonts |
When True, suppresses font embedding for form fields |
FontAutoSize is the practical answer to a field whose default text is clipped:
rather than shrinking the font globally, let each field size its own.
IgnoreFonts reduces file size when the reader's standard fonts are acceptable
for the fillable areas — the rest of the document keeps its embedded fonts.
Labelling fields
Form fields carry no caption of their own. Draw the label as ordinary text next to the field rectangle, the same way you would label anything else on the page — see Text and fonts. Keep the label and the field rectangle in one place in the code so they cannot drift apart when the layout changes.
Common mistakes
- Reusing a field name. Names must be unique across the document; a duplicate makes two controls share one value.
- Expecting fields on macOS/iOS/Android. Windows and Linux only — see the note above.
- Radio buttons that are not mutually exclusive. They are only exclusive
within one
AddRadioGroup. - Clipped default text. Set
FontAutoSize := Trueon the field rather than reducing the document font. - Expecting a caption. Fields are unlabelled; draw the text yourself.
Putting it together
A complete fillable page: a shared field font, drawn labels beside every control, and one of each field family — edit, combo box, memo, check box, and a radio group with two buttons.
procedure TForm1.BuildFormPage(const AFileName: string);
var
p: TTMSFNCPDFLib;
begin
p := TTMSFNCPDFLib.Create;
try
p.Header := '';
p.Footer := '';
p.BeginDocument(AFileName);
try
p.NewPage;
{ One font for every field; individual fields may still override it.
FontAutoSize keeps long default text from being clipped. }
p.FormFields.Font.Name := 'Segoe UI';
p.FormFields.Font.Size := 10;
p.FormFields.FontAutoSize := True;
p.Graphics.Font.Name := 'Segoe UI';
p.Graphics.Font.Size := 14;
p.Graphics.DrawText('Expense claim', PointF(40, 40));
p.Graphics.Font.Size := 10;
{ Fields carry no caption of their own - the label is ordinary drawn
text, kept next to the field it belongs to. }
p.Graphics.DrawText('Employee', PointF(40, 88));
p.FormFields.AddEdit('Employee', RectF(140, 84, 400, 106), '', gtaLeading);
p.Graphics.DrawText('Cost centre', PointF(40, 120));
p.FormFields.AddComboBox('CostCentre', RectF(140, 116, 300, 138),
['Engineering', 'Sales', 'Support', 'Operations'], 0);
p.Graphics.DrawText('Description', PointF(40, 152));
p.FormFields.AddMemo('Description', RectF(140, 148, 400, 220), '');
p.Graphics.DrawText('Receipt attached', PointF(40, 236));
p.FormFields.AddCheckBox('ReceiptAttached', RectF(140, 232, 160, 252), False);
{ A radio group is a logical container with no rectangle of its own: it
is declared with its own field name AND a group id, and every button
then joins that group id. Each button still needs its own unique field
name - buttons are only mutually exclusive within one group id. }
p.Graphics.DrawText('Payment', PointF(40, 268));
p.FormFields.AddRadioGroup('Payment', 'PaymentGroup');
p.FormFields.AddRadioButton('PaymentTransfer', RectF(140, 264, 160, 284),
'PaymentGroup', True);
p.Graphics.DrawText('Bank transfer', PointF(166, 268));
p.FormFields.AddRadioButton('PaymentCash', RectF(300, 264, 320, 284),
'PaymentGroup', False);
p.Graphics.DrawText('Cash', PointF(326, 268));
finally
p.EndDocument;
end;
finally
p.Free;
end;
end;