Skip to main content
Table of Contents

Interactive form fields

A PDF does not have to be read-only. TTMSFNCPDFLib can place AcroForm fields on a page — text edits, memos, password fields, check boxes, radio groups, combo boxes, 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.

Form fields are the right tool when the recipient supplies the data. When the data is already known at generation time, draw it as ordinary text instead: it is simpler, it prints identically everywhere, and it cannot be changed by accident. A common middle ground is a pre-filled field marked read-only, covered below.

Note

FormFields is available where the library uses its built-in PDF writer: VCL, FMX on Windows and Linux, and TMS WEB Core. On FMX for macOS, iOS, and Android the library writes through the platform's native PDF engine and the FormFields property is not declared at all, so code that uses it does not compile for those targets. In a cross-platform FMX project, put the form-field code under a conditional such as {$IF DEFINED(MSWINDOWS) or DEFINED(LINUX)}.

Adding fields

Add form fields via p.FormFields:

Type Method Returns
Text edit AddEdit ITMSFNCCustomPDFFormFieldEdit
Multiline text edit AddMemo ITMSFNCCustomPDFFormFieldEdit
Password edit AddPasswordEdit ITMSFNCCustomPDFFormFieldEdit
Combo box AddComboBox ITMSFNCCustomPDFFormFieldComboBox
List box AddListBox ITMSFNCCustomPDFFormFieldListBox
Check box AddCheckBox —
Radio group AddRadioGroup —
Radio button AddRadioButton —

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 top-left page coordinates as any drawing call. A field is added to the page that is current when the call is made, so call NewPage first and add each page's fields before moving on to the next page.

Adding the first form fieldsPascalC++
procedure TForm1.BuildMinimalForm(const AFileName: string);
var
  p: TTMSFNCPDFLib;
begin
  p := TTMSFNCPDFLib.Create;
  try
    p.Header := '';
    p.Footer := '';
    p.BeginDocument(AFileName);
    try
      { Fields are added to the page that is current when the call is made,
        so NewPage comes first. }
      p.NewPage;
      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, 110, 110),
        ['item1', 'item2', 'item3'], 1);
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
void __fastcall TForm1::BuildMinimalForm(const String AFileName)
{
  TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
  try
  {
    p->Header = "";
    p->Footer = "";
    p->BeginDocument(AFileName);
    try
    {
      // Fields are added to the page that is current when the call is made,
      // so NewPage comes first.
      p->NewPage();
      p->FormFields->AddEdit("Textfield1", RectF(10, 10, 210, 32), "Default text",
        TTMSFNCGraphicsTextAlign::gtaLeading);
      p->FormFields->AddCheckBox("Checkbox1", RectF(10, 50, 30, 70), false);
      TTMSFNCPDFLibStringArray Items;
      Items.Length = 3;
      Items[0] = "item1";
      Items[1] = "item2";
      Items[2] = "item3";
      p->FormFields->AddComboBox("Combobox1", RectF(10, 90, 110, 110), Items, 1);
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

Every Add method except AddRadioGroup also takes optional AReadOnly and AHidden flags (see Read-only and hidden fields). The text and choice methods return an interface to the new field, through which you can change its Text, Items, ItemIndex, Font, and other properties after it was added.

Text fields: edit, memo, and password

A text edit takes one line of input, a memo takes several, and a password edit masks what is typed. AddEdit and AddMemo each have an overload with initial text and one without; AddPasswordEdit has no initial-text overload. The AHorizontalAlign parameter (gtaLeading by default) aligns the text inside the field — gtaTrailing suits amounts and other numbers.

The returned ITMSFNCCustomPDFFormFieldEdit exposes Text, HorizontalAlign, Font, and FontAutoSize, plus the FieldName, ReadOnly, and Hidden properties every field has:

Text, memo and password fieldsPascalC++
procedure TForm1.BuildAccountRequest(const AFileName: string);
var
  p: TTMSFNCPDFLib;
  Notes: ITMSFNCCustomPDFFormFieldEdit;
begin
  p := TTMSFNCPDFLib.Create;
  try
    p.Header := '';
    p.Footer := '';
    p.BeginDocument(AFileName);
    try
      p.NewPage;
      p.Graphics.Font.Size := 10;

      p.Graphics.DrawText('User name', PointF(40, 44));
      p.FormFields.AddEdit('UserName', RectF(150, 40, 400, 62));

      { Numbers read best right-aligned. }
      p.Graphics.DrawText('Monthly budget', PointF(40, 76));
      p.FormFields.AddEdit('Budget', RectF(150, 72, 260, 94), '0.00', gtaTrailing);

      { A memo is a multi-line edit: the recipient can type several lines. }
      p.Graphics.DrawText('Motivation', PointF(40, 108));
      Notes := p.FormFields.AddMemo('Motivation', RectF(150, 104, 400, 184),
        'Describe why the account is needed.');
      { The returned interface changes the field after it was added. }
      Notes.Font.Size := 9;
      Notes.FontAutoSize := False;

      { A password field masks what the recipient types. It has no overload
        for initial text. }
      p.Graphics.DrawText('Temporary PIN', PointF(40, 200));
      p.FormFields.AddPasswordEdit('TemporaryPIN', RectF(150, 196, 260, 218));

      { The field belongs to the document and is destroyed with it: release
        the reference before the document is ended. }
      Notes := nil;
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
void __fastcall TForm1::BuildAccountRequest(const String AFileName)
{
  TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
  try
  {
    p->Header = "";
    p->Footer = "";
    p->BeginDocument(AFileName);
    try
    {
      p->NewPage();
      p->Graphics()->Font->Size = 10;

      p->Graphics()->DrawText("User name", PointF(40, 44));
      p->FormFields->AddEdit("UserName", RectF(150, 40, 400, 62));

      // Numbers read best right-aligned.
      p->Graphics()->DrawText("Monthly budget", PointF(40, 76));
      p->FormFields->AddEdit("Budget", RectF(150, 72, 260, 94), "0.00",
        TTMSFNCGraphicsTextAlign::gtaTrailing);

      // A memo is a multi-line edit: the recipient can type several lines.
      p->Graphics()->DrawText("Motivation", PointF(40, 108));
      _di_ITMSFNCCustomPDFFormFieldEdit Notes = p->FormFields->AddMemo("Motivation",
        RectF(150, 104, 400, 184), "Describe why the account is needed.");
      // The returned interface changes the field after it was added.
      Notes->Font->Size = 9;
      Notes->FontAutoSize = false;

      // A password field masks what the recipient types. It has no overload
      // for initial text.
      p->Graphics()->DrawText("Temporary PIN", PointF(40, 200));
      p->FormFields->AddPasswordEdit("TemporaryPIN", RectF(150, 196, 260, 218));

      // The field belongs to the document and is destroyed with it: release
      // the reference before the document is ended.
      Notes = nullptr;
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

Choice fields: combo box and list box

A combo box shows one value and drops down the rest; a list box shows several options at once and suits a short list the recipient should see in full. Both take their items either as a string array or as a TStrings, and an AItemIndex for the preselected item (-1, the default, selects nothing). A combo box also takes AEditable: when True, the recipient can type a value that is not in the list.

The TStrings overloads copy the items into the field, so a list you pass stays yours to free. The returned interfaces expose Items, ItemIndex, and Font; the combo box interface adds Editable and FontAutoSize.

Combo box and list box fieldsPascalC++
procedure TForm1.BuildShippingForm(const AFileName: string);
var
  p: TTMSFNCPDFLib;
  Carriers: TStringList;
begin
  { The TStrings overloads copy the items, so the list stays yours to free. }
  Carriers := TStringList.Create;
  try
    Carriers.Add('Standard post');
    Carriers.Add('Express courier');
    Carriers.Add('Pallet freight');
    Carriers.Add('Collect in store');

    p := TTMSFNCPDFLib.Create;
    try
      p.Header := '';
      p.Footer := '';
      p.BeginDocument(AFileName);
      try
        p.NewPage;
        p.Graphics.Font.Size := 10;

        { A combo box shows one value and drops down the rest. AEditable = True
          also lets the recipient type a country that is not in the list. }
        p.Graphics.DrawText('Country', PointF(40, 44));
        p.FormFields.AddComboBox('Country', RectF(150, 40, 330, 62),
          ['Belgium', 'France', 'Germany', 'Netherlands'], 0, True);

        { A list box shows several options at once. ItemIndex 1 preselects
          "Express courier"; pass -1 to preselect nothing. }
        p.Graphics.DrawText('Carrier', PointF(40, 80));
        p.FormFields.AddListBox('Carrier', RectF(150, 76, 330, 146), Carriers, 1);
      finally
        p.EndDocument;
      end;
    finally
      p.Free;
    end;
  finally
    Carriers.Free;
  end;
end;
void __fastcall TForm1::BuildShippingForm(const String AFileName)
{
  // The TStrings overloads copy the items, so the list stays yours to free.
  TStringList *Carriers = new TStringList();
  try
  {
    Carriers->Add("Standard post");
    Carriers->Add("Express courier");
    Carriers->Add("Pallet freight");
    Carriers->Add("Collect in store");

    TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
    try
    {
      p->Header = "";
      p->Footer = "";
      p->BeginDocument(AFileName);
      try
      {
        p->NewPage();
        p->Graphics()->Font->Size = 10;

        // A combo box shows one value and drops down the rest. AEditable = true
        // also lets the recipient type a country that is not in the list.
        TTMSFNCPDFLibStringArray Countries;
        Countries.Length = 4;
        Countries[0] = "Belgium";
        Countries[1] = "France";
        Countries[2] = "Germany";
        Countries[3] = "Netherlands";
        p->Graphics()->DrawText("Country", PointF(40, 44));
        p->FormFields->AddComboBox("Country", RectF(150, 40, 330, 62),
          Countries, 0, true);

        // A list box shows several options at once. ItemIndex 1 preselects
        // "Express courier"; pass -1 to preselect nothing.
        p->Graphics()->DrawText("Carrier", PointF(40, 80));
        p->FormFields->AddListBox("Carrier", RectF(150, 76, 330, 146), Carriers, 1);
      }
      __finally
      {
        p->EndDocument();
      }
    }
    __finally
    {
      delete p;
    }
  }
  __finally
  {
    delete Carriers;
  }
}

Check boxes

AddCheckBox(AFieldName, ARect, AIsChecked) adds an independent on/off field; the third parameter is its initial state. Check boxes return nothing, so everything about them — name, position, state, and the read-only and hidden flags — is set in the call itself. The check box has no caption, so draw its label next to the rectangle, as in the sample in the next section.

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. Declare it with AddRadioGroup(AFieldName, AGroupID), then add each button with AddRadioButton(AFieldName, ARect, AGroupID, AIsChecked). The group id is what ties the buttons to the group; each button still needs a field name of its own. Buttons with the same group id are mutually exclusive; buttons that accidentally end up with different group ids are not, which is the usual cause of "my radio buttons can all be selected at once".

Mark exactly one button as checked. When more than one is marked, only the first one added counts as the group's value.

Check boxes and a radio groupPascalC++
procedure TForm1.BuildPreferencesForm(const AFileName: string);
const
  Sizes: array[0..2] of string = ('Small', 'Medium', 'Large');
var
  p: TTMSFNCPDFLib;
  I: Integer;
  x: Single;
begin
  p := TTMSFNCPDFLib.Create;
  try
    p.Header := '';
    p.Footer := '';
    p.BeginDocument(AFileName);
    try
      p.NewPage;
      p.Graphics.Font.Size := 10;

      { Check boxes are independent: each has its own name and state. }
      p.FormFields.AddCheckBox('Newsletter', RectF(40, 40, 56, 56), True);
      p.Graphics.DrawText('Send me the newsletter', PointF(64, 42));
      p.FormFields.AddCheckBox('Terms', RectF(40, 64, 56, 80), False);
      p.Graphics.DrawText('I accept the terms of delivery', PointF(64, 66));

      { The group is declared once with its own field name and a group id;
        each button joins it through that id and still needs a unique field
        name of its own. Mark exactly one button as checked. }
      p.Graphics.DrawText('Size', PointF(40, 102));
      p.FormFields.AddRadioGroup('Size', 'SizeGroup');
      for I := 0 to High(Sizes) do
      begin
        x := 100 + I * 90;
        p.FormFields.AddRadioButton('Size' + Sizes[I], RectF(x, 100, x + 16, 116),
          'SizeGroup', I = 1);
        p.Graphics.DrawText(Sizes[I], PointF(x + 22, 102));
      end;
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
void __fastcall TForm1::BuildPreferencesForm(const String AFileName)
{
  const String Sizes[3] = {"Small", "Medium", "Large"};

  TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
  try
  {
    p->Header = "";
    p->Footer = "";
    p->BeginDocument(AFileName);
    try
    {
      p->NewPage();
      p->Graphics()->Font->Size = 10;

      // Check boxes are independent: each has its own name and state.
      p->FormFields->AddCheckBox("Newsletter", RectF(40, 40, 56, 56), true);
      p->Graphics()->DrawText("Send me the newsletter", PointF(64, 42));
      p->FormFields->AddCheckBox("Terms", RectF(40, 64, 56, 80), false);
      p->Graphics()->DrawText("I accept the terms of delivery", PointF(64, 66));

      // The group is declared once with its own field name and a group id;
      // each button joins it through that id and still needs a unique field
      // name of its own. Mark exactly one button as checked.
      p->Graphics()->DrawText("Size", PointF(40, 102));
      p->FormFields->AddRadioGroup("Size", "SizeGroup");
      for (int I = 0; I < 3; I++)
      {
        float x = 100 + I * 90;
        p->FormFields->AddRadioButton("Size" + Sizes[I], RectF(x, 100, x + 16, 116),
          "SizeGroup", I == 1);
        p->Graphics()->DrawText(Sizes[I], PointF(x + 22, 102));
      }
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

Read-only and hidden fields

AReadOnly := True shows the value but stops the recipient from changing it — the natural choice for a claim number, a customer name, or any value your application supplies and the returned form must keep. AHidden := True keeps the field out of sight while it stays part of the form data, which is a way to carry an internal id back with the filled-in form. Both flags are also available afterwards as the ReadOnly and Hidden properties of a returned field interface.

Read-only and hidden fieldsPascalC++
procedure TForm1.BuildReturnForm(const AFileName, AReference: string; ACustomerId: Integer);
var
  p: TTMSFNCPDFLib;
begin
  p := TTMSFNCPDFLib.Create;
  try
    p.Header := '';
    p.Footer := '';
    p.BeginDocument(AFileName);
    try
      p.NewPage;
      p.Graphics.Font.Size := 10;

      { Visible but locked: the value is part of the form data, and the
        recipient cannot overwrite it. }
      p.Graphics.DrawText('Return number', PointF(40, 44));
      p.FormFields.AddEdit('ReturnNumber', RectF(150, 40, 300, 62), AReference,
        gtaLeading, True);

      { Not shown at all, but still part of the form data - a way to carry an
        internal id back with the filled-in form. }
      p.FormFields.AddEdit('CustomerId', RectF(0, 0, 1, 1), IntToStr(ACustomerId),
        gtaLeading, True, True);

      p.Graphics.DrawText('Reason for return', PointF(40, 80));
      p.FormFields.AddMemo('Reason', RectF(150, 76, 400, 146));
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
void __fastcall TForm1::BuildReturnForm(const String AFileName, const String AReference, int ACustomerId)
{
  TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
  try
  {
    p->Header = "";
    p->Footer = "";
    p->BeginDocument(AFileName);
    try
    {
      p->NewPage();
      p->Graphics()->Font->Size = 10;

      // Visible but locked: the value is part of the form data, and the
      // recipient cannot overwrite it.
      p->Graphics()->DrawText("Return number", PointF(40, 44));
      p->FormFields->AddEdit("ReturnNumber", RectF(150, 40, 300, 62), AReference,
        TTMSFNCGraphicsTextAlign::gtaLeading, true);

      // Not shown at all, but still part of the form data - a way to carry an
      // internal id back with the filled-in form.
      p->FormFields->AddEdit("CustomerId", RectF(0, 0, 1, 1), IntToStr(ACustomerId),
        TTMSFNCGraphicsTextAlign::gtaLeading, true, true);

      p->Graphics()->DrawText("Reason for return", PointF(40, 80));
      p->FormFields->AddMemo("Reason", RectF(150, 76, 400, 146));
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

Field fonts

FormFields.Font and FormFields.FontAutoSize are the defaults a field copies when it is added. Set them before the first Add call: changing them afterwards only affects fields added later. Text, combo box, and list box fields can then override the font through the Font of their returned interface; check boxes and radio buttons have no font.

Property Effect
FontAutoSize When True, the reader scales the font to fit the field; available on FormFields (the default) and on edit and combo box fields
IgnoreFonts When True, the field font settings are ignored and the reader uses its own default form font to draw the fields

FontAutoSize is the practical answer to a field whose default text is clipped: rather than shrinking the font for every field, let that one field size its own. IgnoreFonts is for forms where the reader's standard form font is acceptable and the fields do not need to match the document's typography; it applies to the form fields only, and the rest of the document keeps its own fonts.

Default and per-field form fontsPascalC++
procedure TForm1.BuildFontedForm(const AFileName: string);
var
  p: TTMSFNCPDFLib;
  Total: ITMSFNCCustomPDFFormFieldEdit;
begin
  p := TTMSFNCPDFLib.Create;
  try
    p.Header := '';
    p.Footer := '';
    p.BeginDocument(AFileName);
    try
      p.NewPage;

      { Every field copies these defaults at the moment it is added, so set
        them before the first Add call. Changing them later only affects the
        fields added afterwards. }
      p.FormFields.Font.Name := 'Segoe UI';
      p.FormFields.Font.Size := 10;
      p.FormFields.Font.Color := gcDarkslategray;
      p.FormFields.FontAutoSize := False;

      p.Graphics.Font.Size := 10;
      p.Graphics.DrawText('Description', PointF(40, 44));
      p.FormFields.AddEdit('Description', RectF(150, 40, 450, 62));

      { One field with its own font: a larger, bold total. }
      p.Graphics.DrawText('Total', PointF(40, 80));
      Total := p.FormFields.AddEdit('Total', RectF(150, 74, 300, 100), '', gtaTrailing);
      Total.Font.Size := 14;
      Total.Font.Style := [TFontStyle.fsBold];
      Total.Font.Color := gcBlack;

      { A narrow field whose default text would be clipped: let the reader
        scale the font to the field instead. }
      p.Graphics.DrawText('Code', PointF(40, 116));
      p.FormFields.AddEdit('Code', RectF(150, 112, 210, 128), 'EU-2026-0142').FontAutoSize := True;

      Total := nil;
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
void __fastcall TForm1::BuildFontedForm(const String AFileName)
{
  TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
  try
  {
    p->Header = "";
    p->Footer = "";
    p->BeginDocument(AFileName);
    try
    {
      p->NewPage();

      // Every field copies these defaults at the moment it is added, so set
      // them before the first Add call. Changing them later only affects the
      // fields added afterwards.
      p->FormFields->Font->Name = "Segoe UI";
      p->FormFields->Font->Size = 10;
      p->FormFields->Font->Color = gcDarkslategray;
      p->FormFields->FontAutoSize = false;

      p->Graphics()->Font->Size = 10;
      p->Graphics()->DrawText("Description", PointF(40, 44));
      p->FormFields->AddEdit("Description", RectF(150, 40, 450, 62));

      // One field with its own font: a larger, bold total.
      p->Graphics()->DrawText("Total", PointF(40, 80));
      _di_ITMSFNCCustomPDFFormFieldEdit Total = p->FormFields->AddEdit("Total",
        RectF(150, 74, 300, 100), "", TTMSFNCGraphicsTextAlign::gtaTrailing);
      Total->Font->Size = 14;
      Total->Font->Style = TFontStyles() << TFontStyle::fsBold;
      Total->Font->Color = gcBlack;

      // A narrow field whose default text would be clipped: let the reader
      // scale the font to the field instead.
      p->Graphics()->DrawText("Code", PointF(40, 116));
      p->FormFields->AddEdit("Code", RectF(150, 112, 210, 128), "EU-2026-0142")->FontAutoSize = true;

      Total = nullptr;
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

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

  • Adding fields before NewPage. With no current page an Add call does nothing; the text and choice methods then return nil, so the next property access on the result fails.
  • Setting FormFields.Font after the fields. Each field copies the default font when it is added; set the defaults first.
  • Keeping a field reference past EndDocument. The returned interfaces point at fields owned by the document, which are released when the document is ended. Configure a field right after adding it and set your variable to nil before EndDocument.
  • Moving a field through its Rect property. Rect holds the field's position in the PDF's native coordinates (origin at the bottom-left), not the top-left page coordinates passed to Add; position fields through the Add call.
  • Reusing a field name. Names must be unique across the document; a duplicate makes two controls share one value.
  • Radio buttons that are not mutually exclusive. They are only exclusive when they share one group id; also mark exactly one of them as checked.
  • Expecting FormFields on FMX for macOS, iOS, or Android. The property does not exist there — see the note at the top.
  • Clipped default text. Set FontAutoSize := True on the field rather than reducing the font for every field.
  • Expecting a caption. Fields are unlabelled; draw the text yourself.

Putting it together

A complete fillable page: shared field-font defaults set before the first field, drawn labels beside every control, a read-only claim number, text, amount, and memo fields, a combo box and a list box, a check box, and a radio group with two buttons.

A complete fillable expense claimPascalC++
procedure TForm1.BuildExpenseClaim(const AFileName, AClaimNumber: string);
var
  p: TTMSFNCPDFLib;
begin
  p := TTMSFNCPDFLib.Create;
  try
    p.Header := '';
    p.Footer := '';
    p.BeginDocument(AFileName);
    try
      p.NewPage;

      { Field font defaults first: each field copies them when it is added. }
      p.FormFields.Font.Name := 'Segoe UI';
      p.FormFields.Font.Size := 10;
      p.FormFields.FontAutoSize := False;

      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 - each label is ordinary drawn
        text, kept on the line before the field it belongs to. }
      p.Graphics.DrawText('Claim number', PointF(40, 76));
      p.FormFields.AddEdit('ClaimNumber', RectF(150, 72, 300, 94), AClaimNumber,
        gtaLeading, True);

      p.Graphics.DrawText('Employee', PointF(40, 108));
      p.FormFields.AddEdit('Employee', RectF(150, 104, 400, 126));

      p.Graphics.DrawText('Amount', PointF(40, 140));
      p.FormFields.AddEdit('Amount', RectF(150, 136, 260, 158), '0.00', gtaTrailing);

      p.Graphics.DrawText('Cost centre', PointF(40, 172));
      p.FormFields.AddComboBox('CostCentre', RectF(150, 168, 300, 190),
        ['Engineering', 'Sales', 'Support', 'Operations'], 0);

      p.Graphics.DrawText('Category', PointF(40, 204));
      p.FormFields.AddListBox('Category', RectF(150, 200, 300, 262),
        ['Travel', 'Meals', 'Lodging', 'Equipment'], -1);

      p.Graphics.DrawText('Description', PointF(40, 276));
      p.FormFields.AddMemo('Description', RectF(150, 272, 400, 332));

      p.FormFields.AddCheckBox('ReceiptAttached', RectF(150, 344, 166, 360), False);
      p.Graphics.DrawText('Receipt attached', PointF(172, 346));

      { One group, two buttons joined by the group id, one of them checked. }
      p.Graphics.DrawText('Payment', PointF(40, 380));
      p.FormFields.AddRadioGroup('Payment', 'PaymentGroup');
      p.FormFields.AddRadioButton('PaymentTransfer', RectF(150, 378, 166, 394),
        'PaymentGroup', True);
      p.Graphics.DrawText('Bank transfer', PointF(172, 380));
      p.FormFields.AddRadioButton('PaymentCash', RectF(280, 378, 296, 394),
        'PaymentGroup', False);
      p.Graphics.DrawText('Cash', PointF(302, 380));
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
void __fastcall TForm1::BuildExpenseClaim(const String AFileName, const String AClaimNumber)
{
  TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
  try
  {
    p->Header = "";
    p->Footer = "";
    p->BeginDocument(AFileName);
    try
    {
      p->NewPage();

      // Field font defaults first: each field copies them when it is added.
      p->FormFields->Font->Name = "Segoe UI";
      p->FormFields->Font->Size = 10;
      p->FormFields->FontAutoSize = false;

      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 - each label is ordinary drawn
      // text, kept on the line before the field it belongs to.
      p->Graphics()->DrawText("Claim number", PointF(40, 76));
      p->FormFields->AddEdit("ClaimNumber", RectF(150, 72, 300, 94), AClaimNumber,
        TTMSFNCGraphicsTextAlign::gtaLeading, true);

      p->Graphics()->DrawText("Employee", PointF(40, 108));
      p->FormFields->AddEdit("Employee", RectF(150, 104, 400, 126));

      p->Graphics()->DrawText("Amount", PointF(40, 140));
      p->FormFields->AddEdit("Amount", RectF(150, 136, 260, 158), "0.00",
        TTMSFNCGraphicsTextAlign::gtaTrailing);

      TTMSFNCPDFLibStringArray CostCentres;
      CostCentres.Length = 4;
      CostCentres[0] = "Engineering";
      CostCentres[1] = "Sales";
      CostCentres[2] = "Support";
      CostCentres[3] = "Operations";
      p->Graphics()->DrawText("Cost centre", PointF(40, 172));
      p->FormFields->AddComboBox("CostCentre", RectF(150, 168, 300, 190), CostCentres, 0);

      TTMSFNCPDFLibStringArray Categories;
      Categories.Length = 4;
      Categories[0] = "Travel";
      Categories[1] = "Meals";
      Categories[2] = "Lodging";
      Categories[3] = "Equipment";
      p->Graphics()->DrawText("Category", PointF(40, 204));
      p->FormFields->AddListBox("Category", RectF(150, 200, 300, 262), Categories, -1);

      p->Graphics()->DrawText("Description", PointF(40, 276));
      p->FormFields->AddMemo("Description", RectF(150, 272, 400, 332));

      p->FormFields->AddCheckBox("ReceiptAttached", RectF(150, 344, 166, 360), false);
      p->Graphics()->DrawText("Receipt attached", PointF(172, 346));

      // One group, two buttons joined by the group id, one of them checked.
      p->Graphics()->DrawText("Payment", PointF(40, 380));
      p->FormFields->AddRadioGroup("Payment", "PaymentGroup");
      p->FormFields->AddRadioButton("PaymentTransfer", RectF(150, 378, 166, 394),
        "PaymentGroup", true);
      p->Graphics()->DrawText("Bank transfer", PointF(172, 380));
      p->FormFields->AddRadioButton("PaymentCash", RectF(280, 378, 296, 394),
        "PaymentGroup", false);
      p->Graphics()->DrawText("Cash", PointF(302, 380));
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

See also