Table of Contents

Text and fonts in a PDF document

Text is where a PDF stops being a drawing and becomes a document — and where most of the layout decisions live. TTMSFNCPDFLib draws text three ways: plain text at a point, wrapped text in a rectangle, and Mini HTML for anything that needs mixed formatting inside one block. Fonts are embedded by default so the file renders the same on a machine that does not have them.

Drawing text

Use DrawText to draw plain or word-wrapped text. Control font appearance via p.Graphics.Font:

p.Graphics.Font.Name := 'Segoe UI';
p.Graphics.Font.Size := 16;
p.Graphics.Font.Color := gcRed;
p.Graphics.Font.Style := [TFontStyle.fsBold];
p.Graphics.DrawText('Hello World !', PointF(10, 50));

Text drawn on a PDF page

Which overload you call decides the layout behaviour:

Call Behaviour
DrawText(Text, Point) Draws at a point; no wrapping
DrawText(Text, Rect) Wraps within the rectangle
DrawText(Text, Rect, Columns) Flows the text across Columns columns
DrawText(Text, Rects) Flows the text through an array of rectangles

Passing a TRectF instead of a TPointF is what enables word wrapping. Pass an integer column count as a third argument to flow text across multiple columns:

p.Graphics.DrawText(s, RectF(10, 50, 500, 400), 3);

Text flowed across three columns

DrawText returns either the calculated text rectangle or the character overflow count, depending on the overload. Use CalculateTextOverflow(Text, Rect, Columns) to measure overflow without drawing — the basis of a "does this fit, or do I need another page?" loop.

HTML text

DrawHTMLText renders Mini HTML-formatted text in a rectangle, which is the way to mix bold, colour, and links inside a single paragraph without splitting it into separate DrawText calls and computing each fragment's width.

Supported tags: <b>, <i>, <u>, <s>, <sup>, <sub>, <br>, <font>, <a href>, and <img src>.

p.Graphics.DrawHTMLText(s, RectF(10, 50, 300, 400));

HTML-formatted text in a PDF

Alignment

Set p.Graphics.Alignment to gtaLeading (default), gtaCenter, or gtaTrailing. Alignment is a property of the canvas, not an argument to DrawText, so it applies to every subsequent call until it is changed — set it, draw, and set it back. Right-aligned currency in a table is the usual reason to reach for it.

Note

For multiline HTML text, use a <p> tag for alignment rather than Graphics.Alignment.

Total page count placeholder

A footer that reads "Page 3 of 12" is impossible to draw honestly in a single pass: the total is only known once the last page has been written. Rather than buffering the whole document or making two passes, embed the PDFPageCountRef constant in the text and let the library substitute the real total during finalisation. Reach for it whenever a running header or footer has to state the document length; for anything else, plain Format with a number you already know is simpler.

uses
  FMX.TMSFNCPDFLib, FMX.TMSFNCPDFCoreLibBase, FMX.TMSFNCGraphicsTypes;

procedure TForm1.GenerateNumberedReport(const AFileName: string;
  const ALines: TStrings);
var
  p: TTMSFNCPDFLib;
  I: Integer;
  Footer: TRectF;
begin
  p := TTMSFNCPDFLib.Create;
  try
    p.BeginDocument(AFileName);
    try
      for I := 0 to ALines.Count - 1 do
      begin
        p.NewPage;

        p.Graphics.Alignment := gtaLeading;
        p.Graphics.Font.Size := 12;
        p.Graphics.DrawText(ALines[I], RectF(40, 40, 555, 740));

        { PDFPageCountRef is a literal placeholder token. It survives into the
          content stream and is replaced with the real total page count when
          the document is finalised - so it can be drawn on page 1 even though
          the total is not known yet. }
        Footer := RectF(40, 760, 555, 790);
        p.Graphics.Alignment := gtaTrailing;
        p.Graphics.Font.Size := 9;
        p.Graphics.DrawText(Format('Page %d of %s', [I + 1, PDFPageCountRef]),
          Footer);
        p.Graphics.Alignment := gtaLeading;
      end;
    finally
      p.EndDocument;
    end;
  finally
    p.Free;
  end;
end;

The mechanism has one subtlety worth understanding, because it explains the only way this can look wrong. Text width is measured while the page is written, but the placeholder's width is not yet known, so measurement substitutes PageCountHint — the library's running estimate of the total. For gtaLeading text that is harmless: the line starts at the left edge regardless of how wide it ends up. For gtaCenter and gtaTrailing text the start x depends on the final width, so instead of committing a position the library writes an x-position placeholder and records it in the output writer's PageCountXPosInfos array, together with the font, font size, character spacing, and the width of the line excluding the page-count token. When the document is finalised, each recorded entry is replayed with the true total and the correct x is written back. This is why a centred or right-aligned "Page N of M" stays correctly aligned even when the total rolls from 9 to 10 pages.

Practical consequences:

  • PDFPageCountRef is a plain string constant from FMX.TMSFNCPDFCoreLibBase — compose it with Format or ordinary concatenation, do not try to escape it.
  • It is substituted by the general PDF engine used by TTMSFNCPDFLib on Windows. On platforms that render through a native PDF engine, compute the total yourself and draw it as literal text.
  • PageCountXPosInfos and PageCountHint on TTMSFNCPDFGraphicsLibOutputWriter are the bookkeeping for this pass. They are read-only diagnostics from an application's point of view; the correction runs automatically and there is nothing to trigger by hand.

Line break mode

Value Description
bmLineBreakModeWordWrap Wrap at word boundaries (default)
bmLineBreakModeCharacterWrap Wrap at any character
bmLineBreakModeClip Clip at the boundary
bmLineBreakModeHeadTruncation Truncate at the start
bmLineBreakModeMiddleTruncation Truncate in the middle
bmLineBreakModeTailTruncation Truncate at the end

Full support on macOS/iOS; limited on other platforms. Truncation modes are the right choice for a fixed-width cell where an ellipsis reads better than a clipped glyph.

  • Graphics.LineHeightFactor — scales vertical line spacing. Call LineHeightFactorResetToDefault to restore the default.
  • Graphics.URLFont — controls the appearance of <a href> links in HTML text.

Font embedding

By default, a font subset is embedded in the PDF for portability. Set EmbedFonts := False to disable embedding — smaller files and faster generation, at the cost of correct rendering anywhere the font is missing. That trade is only safe for print-only workflows on machines you control.

EmbedFontType controls the embedding strategy:

Value Description
eftNative Platform native embedding (default)
eftCustom TMS cross-platform embedding

On Android, set Graphics.Font.FileName to a TTF file path to use a font that is not registered in the system directory.

Common mistakes

  • Alignment "leaking" into later text. Alignment is canvas state, not a per-call argument. Reset it after the aligned block.
  • Expecting wrapping from the point overload. DrawText(Text, Point) never wraps — pass a rectangle.
  • A missing font on the target machine. That is what EmbedFonts prevents; do not disable it for documents that leave your control.
  • Aligning multiline HTML with Graphics.Alignment. Use a <p> tag instead.

Putting it together: the full report

Plain text, Mini HTML, and trailing alignment in one page — the source of the screenshot on the chapter index. Note how Alignment is set immediately before each currency figure and restored straight afterwards:

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;

See also