Skip to main content
Table of Contents

Creating a PDF document

Every document made with TTMSFNCPDFLib follows the same skeleton: begin the document, add at least one page, draw, end the document. Everything else on this page — page size, metadata, headers, security — is configuration you apply around that skeleton, and almost all of it must be set before the page it should affect is created.

This chapter covers the document itself. Drawing on the page is covered in Drawing graphics and Text and fonts.

Starting a new document

Call BeginDocument to start a document, always paired with EndDocument. When AFileName is empty, EndDocument returns the content as a TMemoryStream instead of writing to disk — which is what you want when the PDF is going to a database blob, an HTTP response, or an email attachment. Pass True as the second parameter to EndDocument to open the generated file in the default PDF viewer.

Starting a New DocumentPascalC++
procedure TForm1.GeneratePDF(AFileName: string);
var
  p: TTMSFNCPDFLib;
begin
  p := TTMSFNCPDFLib.Create;
  try
    p.BeginDocument(AFileName);
    p.NewPage;
    p.EndDocument;
  finally
    p.Free;
  end;
end;
void __fastcall TForm1::GeneratePDF(String AFileName)
{
  TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
  try
  {
    p->BeginDocument(AFileName);
    p->NewPage();
    p->EndDocument();
  }
  __finally
  {
    delete p;
  }
}

Add the appropriate unit to your uses clause:

pdf-libraryPascalC++
uses
  FMX.TMSFNCPDFLib;   // FMX
  VCL.TMSFNCPDFLib;   // VCL
  WEBLib.TMSFNCPDFLib; // WEB Core
#include <FMX.TMSFNCPDFLib.hpp>   // FMX

Key class: TTMSFNCPDFLib

Adding pages

Call NewPage to start a new page. Each NewPage call clears the content buffer but preserves appearance settings — fill, stroke, font — from the previous page, so a document-wide look is set once rather than re-applied per page. EndDocument raises an error if no page was added.

Call Purpose
NewPage Appends a page and makes it current
InsertPage(APageIndex) Inserts a page at a specific position
GetPageCount Total number of pages
GetPageIndex Zero-based index of the current page

Page size and orientation

Property Description
PageSize Standard size (A4, Letter, Legal, …) or psCustom
PageOrientation poPortrait or poLandscape
PageWidth / PageHeight Custom size when PageSize = psCustom

Supported sizes include ISO A0–A8, B0–B10, C2–C6, SRA and RA series, US Letter, Legal, Ledger, Tabloid, Executive, ANSI C–E, and traditional British sizes.

Tip

psCustom is not only for exotic paper. Fitting the page to the content is the simplest way to produce a PDF with no trailing empty space — a receipt, a label, or a one-block report. The example on the chapter index uses a 595 × 440 custom page for exactly that reason.

Metadata

Set Author, Creator, Title, Subject, and Keywords directly on the TTMSFNCPDFLib instance before BeginDocument — the document information is written when the document starts, so a value assigned afterwards does not reach the file. These populate the document properties a PDF reader shows, and Title is what most readers put in the window caption instead of the file name.

Headers, footers, and page numbering

A header or footer is drawn automatically on every page, so it is configured once on the document rather than redrawn per page.

Property Description
Header / Footer Content text (supports HTML)
HeaderSize / FooterSize Height of the area
HeaderMargins / FooterMargins Margins around the area
HeaderAlignment / FooterAlignment Horizontal text alignment
HeaderFont / FooterFont Font for the text

Use GetHeaderRect and GetFooterRect to retrieve the bounding rectangles — for example to keep body content clear of them.

Page numbering is separate, because it needs to be resolved after the total page count is known:

Property Description
PageNumber pnNone (default), pnHeader, or pnFooter
PageNumberFormat Format string
PageNumberSize / PageNumberMargins / PageNumberAlignment / PageNumberFont Layout and appearance

GetPageNumberRect retrieves the page number bounding rectangle.

Note

Header and Footer default to a non-empty value. If a generated page shows an unexpected caption at the top, set Header := '' and Footer := '' explicitly before BeginDocument.

Security, archival output, and the generation engine

Three document-level choices decide what kind of file comes out of EndDocument: whether it is password-protected, whether it claims PDF/A archival conformance, and how its page boxes are laid out for print. All three are captured when the document or page starts, and the first two depend on which generation engine the platform uses — so it pays to know that before relying on either.

Setting Type Default Applies to
OwnerPassword String empty Encryption and permission enforcement
UserPassword String empty Password prompt when opening
AllowsPrinting Boolean True Permission to print
AllowsCopying Boolean True Permission to copy content
PDFStandard TTMSFNCPDFLibStandard pdfNone pdfNone for plain PDF, pdfA1 for PDF/A-1

These five properties are declared protected on TTMSFNCPDFLib, so p.OwnerPassword := … on a plain TTMSFNCPDFLib variable does not compile (E2362 Cannot access protected symbol). Declare a one-line descendant in your own unit — TSecurePDFLib = class(TTMSFNCPDFLib); — and create that instead; it behaves identically and makes the properties reachable. Both snippets below use this pattern.

The component picks its engine per platform, and the engines do not honour these settings equally:

Platform Engine Passwords and permissions pdfA1
FMX on Windows and Linux, VCL General (built-in writer) Not written — the file is unencrypted Applied
TMS WEB Core General (built-in writer) Not written — the file is unencrypted Raises an exception
FMX on macOS and iOS Native (Core Graphics) Applied Ignored — plain PDF
FMX on Android Native (Android) Not written — the file is unencrypted Ignored — plain PDF

Warning

On every platform except macOS and iOS, the password and permission properties are stored but not written: the generated file opens without a prompt and allows printing and copying, and no error is raised. Do not rely on them to protect confidential content there — encrypt the finished file with a dedicated tool, or protect it at the transport or storage level.

Protecting a document with passwords

Protect a document when it leaves your control but its use should still be limited — a salary statement that may be printed but not copied into another file, or a quotation that should only open for the intended recipient.

A PDF distinguishes two passwords. The user password is the one a reader must type to open the file; leave it empty and the document opens freely. The owner password unlocks full control, and it is what switches encryption on: permissions are stored inside the encrypted file, so without an owner password the user password and both Allows… flags have nothing to attach to and are not applied. Always set an owner password when you set permissions, and make it different from the user password — a reader who opens the file with the owner password bypasses every restriction.

Assign all security properties before BeginDocument; the native engine hands them to the operating system's PDF writer when the document is created.

Generate a password-protected PDF with restricted permissionsPascalC++
type
  { OwnerPassword, UserPassword, AllowsPrinting, AllowsCopying and PDFStandard
    are protected on TTMSFNCPDFLib. A descendant declared in your own unit
    makes them reachable without changing the library. }
  TSecurePDFLib = class(TTMSFNCPDFLib);

procedure TForm1.GenerateProtectedPDF(const AFileName: string);
var
  p: TSecurePDFLib;
begin
  p := TSecurePDFLib.Create;
  try
    p.Header := '';
    p.Footer := '';
    p.Title := 'Salary statement - March 2026';
    p.Author := 'Payroll department';

    { Security is written when the document starts, so everything below must
      be assigned before BeginDocument. }
    p.PDFStandard := pdfNone;           { PDF/A forbids encryption }
    p.OwnerPassword := 'Payroll#Owner-2026';
    p.UserPassword := 'march2026';      { leave empty to open without a prompt }
    p.AllowsPrinting := True;
    p.AllowsCopying := False;

    p.BeginDocument(AFileName);
    try
      p.NewPage;
      p.Graphics.Font.Name := 'Segoe UI';
      p.Graphics.Font.Size := 16;
      p.Graphics.DrawText('Salary statement', PointF(40, 40));
      p.Graphics.Font.Size := 10;
      p.Graphics.DrawText('Printing is allowed; copying text is not.',
        PointF(40, 70));
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
// OwnerPassword, UserPassword, AllowsPrinting, AllowsCopying and PDFStandard
// are protected on TTMSFNCPDFLib. A descendant declared in your own unit
// makes them reachable without changing the library.
class TSecurePDFLib : public TTMSFNCPDFLib
{
public:
  void __fastcall Protect(String AOwnerPassword, String AUserPassword,
    bool AAllowPrinting, bool AAllowCopying)
  {
    PDFStandard = TTMSFNCPDFLibStandard::pdfNone;   // PDF/A forbids encryption
    OwnerPassword = AOwnerPassword;
    UserPassword = AUserPassword;                   // leave empty to open without a prompt
    AllowsPrinting = AAllowPrinting;
    AllowsCopying = AAllowCopying;
  }
};

void __fastcall TForm1::GenerateProtectedPDF(const String AFileName)
{
  TSecurePDFLib *p = new TSecurePDFLib();
  try
  {
    p->Header = "";
    p->Footer = "";
    p->Title = "Salary statement - March 2026";
    p->Author = "Payroll department";

    // Security is written when the document starts, so everything below must
    // be assigned before BeginDocument.
    p->Protect("Payroll#Owner-2026", "march2026", true, false);

    p->BeginDocument(AFileName);
    try
    {
      p->NewPage();
      p->Graphics()->Font->Name = "Segoe UI";
      p->Graphics()->Font->Size = 16;
      p->Graphics()->DrawText("Salary statement", PointF(40, 40));
      p->Graphics()->Font->Size = 10;
      p->Graphics()->DrawText("Printing is allowed; copying text is not.",
        PointF(40, 70));
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

Permissions are honoured by the PDF reader, not by the file format itself. Mainstream readers respect them, but they restrict ordinary use rather than prevent a determined user from extracting content — treat them as a usage policy, and use the user password when the content itself must stay private.

Producing PDF/A archival documents

Choose PDF/A when the file has to render identically years from now, without the fonts, colour settings or software that produced it — invoices and contracts kept for statutory retention periods, records submitted to public authorities, or any document an archive will only accept as PDF/A.

Setting PDFStandard := pdfA1 makes the general engine write the parts PDF/A-1 requires on top of the normal output: a PDF 1.4 header, an sRGB output intent with its embedded colour profile, an XMP metadata stream built from Title, Author, Subject, Keywords and Creator, the marked-content flag, and a document identifier in the trailer.

Generate a PDF/A-1 archival documentPascalC++
type
  { PDFStandard is protected on TTMSFNCPDFLib; a local descendant exposes it. }
  TArchivalPDFLib = class(TTMSFNCPDFLib);

procedure TForm1.GenerateArchivalPDF(const AFileName: string);
var
  p: TArchivalPDFLib;
begin
  p := TArchivalPDFLib.Create;
  try
    p.Header := '';
    p.Footer := '';
    p.PageSize := psA4;

    { The archival identification and its metadata are written when the
      document starts: select the standard and fill in the metadata first. }
    p.PDFStandard := pdfA1;
    p.EmbedFonts := True;               { PDF/A requires every font embedded }
    p.Title := 'Invoice 2026-0412';
    p.Author := 'Accounts receivable';
    p.Subject := 'Customer invoice, retained for ten years';
    p.Keywords.Add('invoice');
    p.Keywords.Add('archive');

    { No OwnerPassword or UserPassword: PDF/A does not allow encryption. }

    p.BeginDocument(AFileName);
    try
      p.NewPage;
      p.Graphics.Font.Name := 'Segoe UI';
      p.Graphics.Font.Size := 18;
      p.Graphics.DrawText('Invoice 2026-0412', PointF(40, 40));
      p.Graphics.Font.Size := 10;
      p.Graphics.DrawText('Total due: 1.250,00', PointF(40, 72));
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
// PDFStandard is protected on TTMSFNCPDFLib; a local descendant exposes it.
class TArchivalPDFLib : public TTMSFNCPDFLib
{
public:
  void __fastcall SetStandard(TTMSFNCPDFLibStandard AStandard) { PDFStandard = AStandard; }
};

void __fastcall TForm1::GenerateArchivalPDF(const String AFileName)
{
  TArchivalPDFLib *p = new TArchivalPDFLib();
  try
  {
    p->Header = "";
    p->Footer = "";
    p->PageSize = TTMSFNCPDFLibPageSize::psA4;

    // The archival identification and its metadata are written when the
    // document starts: select the standard and fill in the metadata first.
    p->SetStandard(TTMSFNCPDFLibStandard::pdfA1);
    p->EmbedFonts = true;               // PDF/A requires every font embedded
    p->Title = "Invoice 2026-0412";
    p->Author = "Accounts receivable";
    p->Subject = "Customer invoice, retained for ten years";
    p->Keywords->Add("invoice");
    p->Keywords->Add("archive");

    // No OwnerPassword or UserPassword: PDF/A does not allow encryption.

    p->BeginDocument(AFileName);
    try
    {
      p->NewPage();
      p->Graphics()->Font->Name = "Segoe UI";
      p->Graphics()->Font->Size = 18;
      p->Graphics()->DrawText("Invoice 2026-0412", PointF(40, 40));
      p->Graphics()->Font->Size = 10;
      p->Graphics()->DrawText("Total due: 1.250,00", PointF(40, 72));
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

PDF/A trades flexibility for durability, and the restrictions come with it:

  • No encryption. PDF/A forbids it, so leave OwnerPassword and UserPassword empty. A document can be protected or archival, not both — if you need both, keep the PDF/A file as the archived master and hand out a protected copy.
  • Every font embedded. EmbedFonts is True by default; keep it that way. The standard does not switch it back on for you, so turning it off produces a file that is labelled PDF/A but does not conform.
  • Metadata first. The XMP stream is written when the document starts, so set the metadata properties before BeginDocument or the archival metadata is empty.
  • No validation. The library writes the PDF/A identification and structures; it does not inspect what you draw. When conformance is contractual, check the output with a PDF/A validator as part of your release process.

Page boxes for print production

A screen-only PDF needs nothing beyond the page size. A PDF headed for a commercial printer usually does: the printer needs to know where the sheet ends, where it will be cut, and how far artwork may run past the cut so no white edge shows if the blade is slightly off. The five page boxes carry exactly that information.

Property Describes
MediaBox The full sheet, including bleed; also the drawing surface
BleedBox How far artwork may extend past the cut
TrimBox The finished size after cutting
ArtBox The area that holds the meaningful content
CropBox The region a viewer displays

All five are TRectF values in points. Assigning MediaBox also resizes the drawing surface to match, so graphics can reach into the bleed.

Set up page boxes for a print shopPascalC++
procedure TForm1.GeneratePrintReadyFlyer(const AFileName: string);
var
  p: TTMSFNCPDFLib;
  Bleed, Safe, W, H: Single;
begin
  p := TTMSFNCPDFLib.Create;
  try
    p.Header := '';
    p.Footer := '';

    { Assigning PageSize or PageOrientation resets every box to the media box,
      so choose the trim size first and set the boxes afterwards. }
    p.PageSize := psA5;
    W := p.MediaBox.Width;              { finished (trim) width in points }
    H := p.MediaBox.Height;
    Bleed := MillimeterToPixel(3);
    Safe := MillimeterToPixel(8);

    { The sheet is the trim size plus the bleed on every side. }
    p.MediaBox := RectF(0, 0, W + 2 * Bleed, H + 2 * Bleed);
    p.BleedBox := p.MediaBox;
    p.CropBox := p.MediaBox;
    p.TrimBox := RectF(Bleed, Bleed, Bleed + W, Bleed + H);
    p.ArtBox := RectF(Bleed + Safe, Bleed + Safe,
      Bleed + W - Safe, Bleed + H - Safe);

    p.BeginDocument(AFileName);
    try
      p.NewPage;

      { A background that runs off the trim edge into the bleed, so no white
        sliver appears if the cut is slightly off. }
      p.Graphics.Fill.Kind := gfkSolid;
      p.Graphics.Fill.Color := gcSteelblue;
      p.Graphics.Stroke.Kind := gskNone;
      p.Graphics.DrawRectangle(p.MediaBox);

      { Text stays inside the art box, clear of the cut. }
      p.Graphics.Font.Name := 'Segoe UI';
      p.Graphics.Font.Color := gcWhite;
      p.Graphics.Font.Size := 24;
      p.Graphics.DrawText('Open day - 12 April', PointF(p.ArtBox.Left, p.ArtBox.Top));
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
void __fastcall TForm1::GeneratePrintReadyFlyer(const String AFileName)
{
  TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
  try
  {
    p->Header = "";
    p->Footer = "";

    // Assigning PageSize or PageOrientation resets every box to the media box,
    // so choose the trim size first and set the boxes afterwards.
    p->PageSize = TTMSFNCPDFLibPageSize::psA5;
    float W = p->MediaBox.Width();      // finished (trim) width in points
    float H = p->MediaBox.Height();
    float Bleed = MillimeterToPixel(3);
    float Safe = MillimeterToPixel(8);

    // The sheet is the trim size plus the bleed on every side.
    p->MediaBox = RectF(0, 0, W + 2 * Bleed, H + 2 * Bleed);
    p->BleedBox = p->MediaBox;
    p->CropBox = p->MediaBox;
    p->TrimBox = RectF(Bleed, Bleed, Bleed + W, Bleed + H);
    p->ArtBox = RectF(Bleed + Safe, Bleed + Safe,
      Bleed + W - Safe, Bleed + H - Safe);

    p->BeginDocument(AFileName);
    try
    {
      p->NewPage();

      // A background that runs off the trim edge into the bleed, so no white
      // sliver appears if the cut is slightly off.
      p->Graphics()->Fill->Kind = TTMSFNCGraphicsFillKind::gfkSolid;
      p->Graphics()->Fill->Color = gcSteelblue;
      p->Graphics()->Stroke->Kind = TTMSFNCGraphicsStrokeKind::gskNone;
      p->Graphics()->DrawRectangle(p->MediaBox);

      // Text stays inside the art box, clear of the cut.
      p->Graphics()->Font->Name = "Segoe UI";
      p->Graphics()->Font->Color = gcWhite;
      p->Graphics()->Font->Size = 24;
      p->Graphics()->DrawText("Open day - 12 April", PointF(p->ArtBox.Left, p->ArtBox.Top));
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

Three rules keep the boxes where you intend them:

  • Set boxes after the page size. Assigning PageSize, PageOrientation, PageWidth or PageHeight resets all four inner boxes to the media box. Assigning MediaBox does not, so after enlarging it, set every other box explicitly — a stale CropBox would make viewers show only part of the sheet.
  • Set boxes before the page. Each NewPage takes the box values current at that moment, so change them before the NewPage they should affect.
  • Keep insets symmetric. Box rectangles are written in PDF page space, whose origin is the bottom-left corner — unlike the top-left drawing coordinates. An inset that is the same on all sides, as in the snippet, lands in the same place in both systems. The general engine also rounds box edges to whole points.

Generation callbacks

The document raises events around the parts it draws for you, so you can replace the default rendering rather than switch it off and rebuild it by hand. Set ADefaultDraw := False in a Before… handler to suppress the built-in output and draw your own.

Event Fires
OnBeforeDrawHeader / OnAfterDrawHeader Around the header
OnBeforeDrawFooter / OnAfterDrawFooter Around the footer
OnBeforeDrawPageNumber / OnAfterDrawPageNumber Around the page number
OnNewPageStarted After each NewPage; provides APageIndex
EventsPascalC++
procedure TForm1.PDFBeforeDrawHeader(Sender: TObject; APageIndex: Integer;
  AHeader: UnicodeString; ARect: TRectF;
  AGraphics: ITMSFNCCustomPDFGraphicsLib; var ADefaultDraw: Boolean);
begin
  ADefaultDraw := False;
  AGraphics.Font.Size := 9;
  AGraphics.DrawText('Page ' + IntToStr(APageIndex + 1), ARect.TopLeft);
end;
void __fastcall TForm1::PDFBeforeDrawHeader(TObject *Sender, int APageIndex,
  String AHeader, const TRectF &ARect,
  _di_ITMSFNCCustomPDFGraphicsLib AGraphics, bool &ADefaultDraw)
{
  ADefaultDraw = false;
  AGraphics->Font->Size = 9;
  AGraphics->DrawText("Page " + IntToStr(APageIndex + 1), ARect.TopLeft());
}

Working in millimetres or inches

PDF coordinates are points (1/72 inch) with the origin at the top-left. When your measurements come from a print spec instead, convert rather than hand-computing. All functions accept an optional ADPI parameter (default 72.0):

Function Description
InchToPixel(AInch, ADPI) Inches → PDF points
MillimeterToPixel(AMillimeter, ADPI) Millimetres → PDF points
MillimeterRect(AX, AY, AWidth, AHeight, ADPI) TRectF from mm coordinates
InchRect(AX, AY, AWidth, AHeight, ADPI) TRectF from inch coordinates
Pascal
p.Graphics.DrawRectangle(MillimeterRect(10, 10, 100, 150));

Common mistakes

  • Ending a document with no page. EndDocument raises an error if NewPage was never called.
  • Setting page size after the page exists. PageSize, PageWidth and PageHeight apply to pages created afterwards — set them before BeginDocument or before the NewPage they should affect.
  • Expecting appearance to reset per page. It does not: fill, stroke and font carry over from the previous page by design.
  • Forgetting the default header. See the note above — an unexplained caption at the top of the page is almost always the default Header.
  • Configuring the document after it started. Metadata, passwords, permissions and PDFStandard are written by BeginDocument; assignments made afterwards are silently lost.
  • Assuming a password protects the file everywhere. Only the macOS and iOS engine encrypts. Elsewhere the file is written unprotected without an error — see the platform table above.
  • Setting permissions without an owner password. The owner password is what turns encryption on; AllowsPrinting := False on its own restricts nothing.
  • Combining PDF/A with a password. PDF/A forbids encryption. Pick one per document.

Putting it together

The report on the chapter index is this chapter's skeleton with content on top: a custom page fitted to its table, both default captions cleared, and everything drawn between one BeginDocument/EndDocument pair. It is the source of the screenshot there, so the code and the picture cannot drift apart.

A complete one-page PDF reportPascalC++
procedure TForm1.BuildReport(const AFileName: string);
const
  Rows: array[0..7, 0..2] of string = (
    ('Northern Europe', 'Alice Moreau', '184.000'),
    ('Southern Europe', 'Bob Devlin',   '72.500'),
    ('North America',   'Carol Yang',   '412.800'),
    ('South America',   'Dan Pereira',  '43.000'),
    ('Middle East',     'Eve Novak',    '159.000'),
    ('South Asia',      'Frank Bauer',  '91.000'),
    ('East Asia',       'Grace Lim',    '213.000'),
    ('Oceania',         'Hugo Marsh',   '66.500'));
var
  p: TTMSFNCPDFLib;
  i: Integer;
  y: Single;
  Euro: string;
begin
  { Build the euro sign from its code point so the source stays ASCII-safe. }
  Euro := Char($20AC);

  p := TTMSFNCPDFLib.Create;
  try
    { Header/Footer default to a non-empty value - clear them before drawing
      your own, or the built-in caption appears at the top of every page. }
    p.Header := '';
    p.Footer := '';

    { A custom page fitted to the content, so the PDF has no trailing empty
      space below the table. }
    p.PageSize := psCustom;
    p.PageWidth := 595;
    p.PageHeight := 440;

    p.BeginDocument(AFileName);
    try
      p.NewPage;

      { Header band: a filled rectangle with no stroke, drawn before the text
        that sits on top of it. }
      p.Graphics.Fill.Color := gcSteelblue;
      p.Graphics.Fill.Kind := gfkSolid;
      p.Graphics.Stroke.Kind := gskNone;
      p.Graphics.DrawRectangle(RectF(40, 40, 555, 92));

      p.Graphics.Font.Color := gcWhite;
      p.Graphics.Font.Name := 'Segoe UI';
      p.Graphics.Font.Size := 18;
      p.Graphics.DrawText('Regional sales report', PointF(54, 55));
      p.Graphics.Font.Size := 10;
      p.Graphics.DrawText('Fiscal year 2026 - all regions', PointF(54, 76));

      { Mini HTML for a paragraph that mixes weight and colour inline. }
      p.Graphics.Font.Color := gcBlack;
      p.Graphics.Font.Size := 10;
      p.Graphics.DrawHTMLText(
        'Revenue by region for the full year. Figures in <b>EUR</b>, ' +
        'rounded to the nearest thousand. Regions above ' +
        '<font color="#1F7A1F"><b>150.000</b></font> are on track.',
        RectF(40, 108, 555, 148));

      { Table header. Alignment is canvas state, so it is set for the currency
        column and restored immediately afterwards. }
      y := 158;
      p.Graphics.Fill.Color := gcGainsboro;
      p.Graphics.Stroke.Kind := gskSolid;
      p.Graphics.Stroke.Color := gcSilver;
      p.Graphics.DrawRectangle(RectF(40, y, 555, y + 24));
      p.Graphics.Font.Size := 10;
      p.Graphics.DrawText('Region', PointF(50, y + 6));
      p.Graphics.DrawText('Account manager', PointF(220, y + 6));
      p.Graphics.Alignment := gtaTrailing;
      p.Graphics.DrawText('Revenue', RectF(390, y + 6, 545, y + 22));
      p.Graphics.Alignment := gtaLeading;

      { Rows, with alternating band fills. }
      y := y + 24;
      for i := 0 to High(Rows) do
      begin
        if i mod 2 = 1 then
          p.Graphics.Fill.Color := gcWhitesmoke
        else
          p.Graphics.Fill.Color := gcWhite;
        p.Graphics.DrawRectangle(RectF(40, y, 555, y + 22));
        p.Graphics.DrawText(Rows[i][0], PointF(50, y + 5));
        p.Graphics.DrawText(Rows[i][1], PointF(220, y + 5));
        p.Graphics.Alignment := gtaTrailing;
        p.Graphics.DrawText(Euro + ' ' + Rows[i][2],
          RectF(390, y + 5, 545, y + 21));
        p.Graphics.Alignment := gtaLeading;
        y := y + 22;
      end;

      { Totals row. }
      p.Graphics.Fill.Color := gcLightsteelblue;
      p.Graphics.DrawRectangle(RectF(40, y, 555, y + 24));
      p.Graphics.DrawText('Total', PointF(50, y + 6));
      p.Graphics.Alignment := gtaTrailing;
      p.Graphics.DrawText(Euro + ' 1.241.800', RectF(390, y + 6, 555, y + 22));
      p.Graphics.Alignment := gtaLeading;

      { Footer rule and caption, clear of the table. }
      p.Graphics.Stroke.Color := gcSilver;
      p.Graphics.DrawLine(PointF(40, 398), PointF(555, 398));
      p.Graphics.Font.Size := 8;
      p.Graphics.Font.Color := gcGray;
      p.Graphics.DrawText('Generated with TMS FNC PDF Library', PointF(40, 406));
      p.Graphics.Alignment := gtaTrailing;
      p.Graphics.DrawText('Page 1 of 1', RectF(390, 406, 555, 420));
      p.Graphics.Alignment := gtaLeading;
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;
void __fastcall TForm1::BuildReport(const String AFileName)
{
  const String Rows[8][3] = {
    {"Northern Europe", "Alice Moreau", "184.000"},
    {"Southern Europe", "Bob Devlin",   "72.500"},
    {"North America",   "Carol Yang",   "412.800"},
    {"South America",   "Dan Pereira",  "43.000"},
    {"Middle East",     "Eve Novak",    "159.000"},
    {"South Asia",      "Frank Bauer",  "91.000"},
    {"East Asia",       "Grace Lim",    "213.000"},
    {"Oceania",         "Hugo Marsh",   "66.500"}};

  // Build the euro sign from its code point so the source stays ASCII-safe.
  String Euro = WideChar(0x20AC);

  TTMSFNCPDFLib *p = new TTMSFNCPDFLib();
  try
  {
    // Header/Footer default to a non-empty value - clear them before drawing
    // your own, or the built-in caption appears at the top of every page.
    p->Header = "";
    p->Footer = "";

    // A custom page fitted to the content, so the PDF has no trailing empty
    // space below the table.
    p->PageSize = TTMSFNCPDFLibPageSize::psCustom;
    p->PageWidth = 595;
    p->PageHeight = 440;

    p->BeginDocument(AFileName);
    try
    {
      p->NewPage();

      // Header band: a filled rectangle with no stroke, drawn before the text
      // that sits on top of it.
      p->Graphics()->Fill->Color = gcSteelblue;
      p->Graphics()->Fill->Kind = TTMSFNCGraphicsFillKind::gfkSolid;
      p->Graphics()->Stroke->Kind = TTMSFNCGraphicsStrokeKind::gskNone;
      p->Graphics()->DrawRectangle(RectF(40, 40, 555, 92));

      p->Graphics()->Font->Color = gcWhite;
      p->Graphics()->Font->Name = "Segoe UI";
      p->Graphics()->Font->Size = 18;
      p->Graphics()->DrawText("Regional sales report", PointF(54, 55));
      p->Graphics()->Font->Size = 10;
      p->Graphics()->DrawText("Fiscal year 2026 - all regions", PointF(54, 76));

      // Mini HTML for a paragraph that mixes weight and colour inline.
      p->Graphics()->Font->Color = gcBlack;
      p->Graphics()->Font->Size = 10;
      p->Graphics()->DrawHTMLText(
        "Revenue by region for the full year. Figures in <b>EUR</b>, "
        "rounded to the nearest thousand. Regions above "
        "<font color=\"#1F7A1F\"><b>150.000</b></font> are on track.",
        RectF(40, 108, 555, 148));

      // Table header. Alignment is canvas state, so it is set for the currency
      // column and restored immediately afterwards.
      float y = 158;
      p->Graphics()->Fill->Color = gcGainsboro;
      p->Graphics()->Stroke->Kind = TTMSFNCGraphicsStrokeKind::gskSolid;
      p->Graphics()->Stroke->Color = gcSilver;
      p->Graphics()->DrawRectangle(RectF(40, y, 555, y + 24));
      p->Graphics()->Font->Size = 10;
      p->Graphics()->DrawText("Region", PointF(50, y + 6));
      p->Graphics()->DrawText("Account manager", PointF(220, y + 6));
      p->Graphics()->Alignment = TTMSFNCGraphicsTextAlign::gtaTrailing;
      p->Graphics()->DrawText("Revenue", RectF(390, y + 6, 545, y + 22));
      p->Graphics()->Alignment = TTMSFNCGraphicsTextAlign::gtaLeading;

      // Rows, with alternating band fills.
      y = y + 24;
      for (int i = 0; i < 8; i++)
      {
        if (i % 2 == 1)
          p->Graphics()->Fill->Color = gcWhitesmoke;
        else
          p->Graphics()->Fill->Color = gcWhite;
        p->Graphics()->DrawRectangle(RectF(40, y, 555, y + 22));
        p->Graphics()->DrawText(Rows[i][0], PointF(50, y + 5));
        p->Graphics()->DrawText(Rows[i][1], PointF(220, y + 5));
        p->Graphics()->Alignment = TTMSFNCGraphicsTextAlign::gtaTrailing;
        p->Graphics()->DrawText(Euro + " " + Rows[i][2],
          RectF(390, y + 5, 545, y + 21));
        p->Graphics()->Alignment = TTMSFNCGraphicsTextAlign::gtaLeading;
        y = y + 22;
      }

      // Totals row.
      p->Graphics()->Fill->Color = gcLightsteelblue;
      p->Graphics()->DrawRectangle(RectF(40, y, 555, y + 24));
      p->Graphics()->DrawText("Total", PointF(50, y + 6));
      p->Graphics()->Alignment = TTMSFNCGraphicsTextAlign::gtaTrailing;
      p->Graphics()->DrawText(Euro + " 1.241.800", RectF(390, y + 6, 555, y + 22));
      p->Graphics()->Alignment = TTMSFNCGraphicsTextAlign::gtaLeading;

      // Footer rule and caption, clear of the table.
      p->Graphics()->Stroke->Color = gcSilver;
      p->Graphics()->DrawLine(PointF(40, 398), PointF(555, 398));
      p->Graphics()->Font->Size = 8;
      p->Graphics()->Font->Color = gcGray;
      p->Graphics()->DrawText("Generated with TMS FNC PDF Library", PointF(40, 406));
      p->Graphics()->Alignment = TTMSFNCGraphicsTextAlign::gtaTrailing;
      p->Graphics()->DrawText("Page 1 of 1", RectF(390, 406, 555, 420));
      p->Graphics()->Alignment = TTMSFNCGraphicsTextAlign::gtaLeading;
    }
    __finally
    {
      p->EndDocument();
    }
  }
  __finally
  {
    delete p;
  }
}

See also