Drawing graphics on a PDF page
After NewPage, p.Graphics is the drawing surface for the current page. It
offers two levels: the low-level primitives on this page — lines, rectangles,
paths, images — and, when you would rather reuse code you already have, the
graphics engine, which exposes the same API as any other
FNC canvas so an existing paint routine can render straight into the PDF.
Coordinates are PDF points (1/72 inch) with the origin at the top-left of the page. See Working in millimetres or inches when your measurements come from a print spec.
Lines, rectangles, and paths
p.Graphics.Stroke.Color := gcRed;
p.Graphics.Stroke.Width := 3;
p.Graphics.Stroke.Kind := gskDashDotDot;
p.Graphics.DrawLine(PointF(10, 50), PointF(100, 50));

Rectangles support fill with optional gradients:
p.Graphics.Fill.Kind := gfkGradient;
p.Graphics.Fill.Color := gcBlue;
p.Graphics.Fill.ColorTo := gcOrange;
p.Graphics.DrawRectangle(RectF(10, 50, 100, 150));

Custom paths
Build paths with DrawPathBegin / DrawPathMoveToPoint /
DrawPathAddLineToPoint / DrawPathClose / DrawPathEnd:
p.Graphics.DrawPathBegin;
p.Graphics.DrawPathMoveToPoint(PointF(x, y));
for I := 1 to 5 do
begin
x := rad * sin((i * 4 * pi + angle) / 5) + st.X;
y := rad * cos((i * 4 * pi + angle) / 5) + st.Y;
p.Graphics.DrawPathAddLineToPoint(PointF(x, y));
end;
p.Graphics.DrawPathClose;
p.Graphics.DrawPathEnd;

Path drawing modes
DrawPathEnd takes the mode that decides whether the path is stroked, filled, or
used as a clip. This is the parameter that most often explains "my path is
invisible" — a path ended in a fill-only mode with no fill colour set draws
nothing.
Clipping paths
Ending a path in a clipping mode restricts every subsequent drawing operation to that shape until the clip is reset. Use it for a rounded-corner photo, a masked chart area, or any shape a rectangle cannot express.
Linear gradient paths
A path can be filled with a linear gradient rather than a solid colour, which is what produces a banded header or a shaded chart series without drawing dozens of adjacent rectangles.
Transforms
Transforms apply a matrix to subsequent drawing — translation, rotation, scaling —
so a repeated element (a rotated watermark, a rotated column header) is described
once and positioned by the matrix rather than recomputed per instance. See
TTMSFNCGraphicsMatrix for the
coefficient layout.
Clearing a region
DrawClear(Rect) erases the content inside a rectangle by painting it with the
background colour — useful when stamping over existing page content.
Images
Draw images with DrawImage, DrawImageFromFile, or DrawImageWithName (the
last requires a BitmapContainer, which is the right choice when the same logo
appears on many pages — it is embedded once and referenced per use):
p.BitmapContainer := TMSFNCBitmapContainer1;
p.Graphics.DrawImageWithName('MyImage', PointF(10, 50));
p.Graphics.DrawImageFromFile('MyImage.jpg', PointF(160, 50));
MyImage.LoadFromFile('MyImage.jpg');
p.Graphics.DrawImage(MyImage, PointF(310, 50));

The graphics engine
TTMSFNCGraphicsPDFEngine exposes the same drawing API as a regular FNC canvas,
eliminating the need to build raw PDF paths manually. This is the shortest route
from "I already draw this on screen" to "I can put it in a PDF": point the engine
at the document and run the same paint code.
Add FMX.TMSFNCGraphicsPDFEngine to the uses clause:
uses
FMX.TMSFNCPDFLib, FMX.TMSFNCGraphicsTypes, FMX.TMSFNCGraphicsPDFEngine;
var
p: TTMSFNCPDFLib;
g: TTMSFNCGraphicsPDFEngine;
pth: TTMSFNCGraphicsPath;
begin
p := TTMSFNCPDFLib.Create;
g := TTMSFNCGraphicsPDFEngine.Create(p);
pth := TTMSFNCGraphicsPath.Create;
try
p.BeginDocument('output.pdf');
p.NewPage;
pth.MoveTo(PointF(200, 200));
pth.AddLine(PointF(200, 200), PointF(300, 300));
pth.ClosePath;
g.DrawPath(pth);
g.DrawText(RectF(100, 200, 300, 400), 'Hello!', False, gtaCenter, gtaCenter, gttNone, -45);
p.EndDocument(True);
finally
pth.Free;
g.Free;
p.Free;
end;
end;

Tip
If the goal is to export a whole existing control or canvas rather than to run
custom paint code, use TTMSFNCGraphicsPDFIO instead — it wraps the same engine
in a one-call export. See the PDF Library overview.
Common mistakes
- A path that draws nothing. Check the mode passed to
DrawPathEndand that a fill or stroke colour is actually set for that mode. - A clip that never ends. A clipping path stays in effect for subsequent drawing; reset it before drawing content that should not be masked.
- Text hidden behind a shape. Drawing order is painter's order — draw fills first, text last.
- A missing image with no error.
DrawImageFromFileneeds a path that exists at generation time; verify it rather than assuming the draw failed silently for another reason.
Putting it together: the full report
The header band, the ruled and alternately-filled table rows, and the footer rule in the chapter index screenshot are all shapes from this page, drawn before the text that sits on them:
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;