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));

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);

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));

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:
PDFPageCountRefis a plain string constant fromFMX.TMSFNCPDFCoreLibBase— compose it withFormator ordinary concatenation, do not try to escape it.- It is substituted by the general PDF engine used by
TTMSFNCPDFLibon Windows. On platforms that render through a native PDF engine, compute the total yourself and draw it as literal text. PageCountXPosInfosandPageCountHintonTTMSFNCPDFGraphicsLibOutputWriterare 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.
Line height and link font
Graphics.LineHeightFactor— scales vertical line spacing. CallLineHeightFactorResetToDefaultto 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.
Alignmentis 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
EmbedFontsprevents; 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;