Page overlays and underlays
Draw on a page of an existing document using the same graphics API you use to generate one — above the original content, or beneath it.
Stamping an approval mark, adding a page footer to a supplier's invoice, putting
a "DRAFT" watermark behind a contract, marking up a scan: all of these mean
drawing on a page whose content you did not create and do not want to disturb.
BeginPageEdit opens a drawing surface over one page of an opened document,
Graphics gives you the full PDF drawing API on it — text, HTML text, shapes,
paths, images, gradients — and EndPageEdit merges the result into the page. The
original content is never rewritten: your drawing is merged in as a separate
layer, either on top of or underneath what was already there. Use this rather
than regenerating the page whenever you only need to add something; regenerate
only when you own the content and are producing it from data anyway.
The edit lifecycle
A page edit is a small transaction inside the document transaction. It begins, you draw, and it either merges or is discarded — and until it merges, the page is unchanged.
procedure TForm1.StampApproved(const AFileName: string; APageIndex: Integer);
var
p: TTMSFNCPDFLib;
Info: TTMSFNCPDFPageInfo;
begin
p := TTMSFNCPDFLib.Create;
try
p.OpenDocument(AFileName);
try
Info := p.GetDocumentPageInfo(APageIndex);
{ The edit surface is the VISIBLE page box, so (0,0) is its top-left
corner and Info.Width / Info.Height are its extents in points. }
p.BeginPageEdit(APageIndex, pelOverlay);
try
p.Graphics.Stroke.Color := gcGreen;
p.Graphics.Stroke.Width := 2;
p.Graphics.Fill.Kind := gfkNone;
p.Graphics.DrawRectangle(RectF(Info.Width - 220, 40, Info.Width - 40, 100));
p.Graphics.Font.Name := 'Segoe UI';
p.Graphics.Font.Size := 14;
p.Graphics.Font.Color := gcGreen;
p.Graphics.DrawText('APPROVED', PointF(Info.Width - 205, 55));
p.Graphics.Font.Size := 9;
p.Graphics.DrawText(DateToStr(Date), PointF(Info.Width - 205, 78));
{ EndPageEdit merges the drawing into the page. Until it runs, nothing
has changed. }
p.EndPageEdit;
except
p.CancelPageEdit;
raise;
end;
p.SaveDocument(AFileName);
finally
p.CloseDocument;
end;
finally
p.Free;
end;
end;
| Call | Effect |
|---|---|
BeginPageEdit(PageIndex, Layer) |
Opens a drawing surface for one zero-based page of a document opened with OpenDocument. Layer defaults to pelOverlay. |
Graphics |
While an edit is active, returns the canvas of the edit surface instead of the generation canvas. |
EndPageEdit |
Merges the drawing into the page. |
CancelPageEdit |
Discards the drawing; the page stays exactly as it was. |
IsPageEditing |
True between begin and end/cancel. |
Only one page edit can be active at a time, and structural operations
(DeletePage, MergeDocument, SaveDocument, …) raise ETMSFNCPDFDocument
instead of running while one is open. Two calls are the exception:
CloseDocument and OpenDocument cancel an active edit silently, so an
unfinished edit is discarded rather than reported. Pair every BeginPageEdit
with an EndPageEdit or a CancelPageEdit, and put the cancel on the exception
path as the snippets here do.
An edit that draws nothing is still valid: EndPageEdit merges an empty layer
and leaves the page exactly as it was. A loop that stamps only the pages matching
some condition therefore does not have to decide whether to open the edit before
it knows what it will draw — opening one and ending it unused is safe.
The edit surface and its coordinate space
Getting a stamp to land in the right corner of every page is the part that usually goes wrong, because a document that arrives from elsewhere is rarely one uniform page size. The edit surface removes the problem by measuring in the page's own visible geometry.
The surface is created at the visible size of the page it targets: the
crop box with UserUnit scaling and rotation already applied — the same
Width and Height that GetDocumentPageInfo reports. So (0, 0) is the
top-left corner of the page as a reader sees it, and you position against
Info.Width / Info.Height rather than against a page-size constant.
This is what makes stamping a mixed document reliable: a portrait A4 page, a rotated landscape scan and an oversized drawing each get a correctly sized surface, and the same layout code lands in the right place on all three.
The surface also inherits EmbedFonts, EmbedFontType, BitmapContainer and
the font fall-back list from the TTMSFNCPDFLib instance, so fonts and named
bitmaps behave exactly as they do when you generate a document. Stamping every
page of a long document does not multiply its size for that reason: on save,
objects whose contents are byte-identical are shared, so one embedded font or
logo is written once no matter how many overlays referenced it.
What the surface does not inherit is the automatic page furniture. Header,
Footer and automatic page numbering are deliberately switched off on it — a
document you did not generate has its own margins, and an automatic header would
land on top of them. Draw your own footer instead, as the combined example below
does.
Overlay or underlay
TTMSFNCPDFPageEditLayer decides which side of the original content your
drawing lands on. It is the difference between a stamp and a watermark.
| Value | Draws | Use for |
|---|---|---|
pelOverlay |
Above the original content | Stamps, annotations, footers, redaction boxes, page numbers |
pelUnderlay |
Below the original content | Watermarks, background tints, letterhead, pre-printed forms |
An underlay is usually the right choice for anything that covers a large area, because the original text keeps drawing over it and stays perfectly readable without any transparency work:
procedure TForm1.WatermarkDocument(const AFileName, AText: string);
var
p: TTMSFNCPDFLib;
Info: TTMSFNCPDFPageInfo;
I: Integer;
begin
p := TTMSFNCPDFLib.Create;
try
p.OpenDocument(AFileName);
try
for I := 0 to p.GetPageCount - 1 do
begin
Info := p.GetDocumentPageInfo(I);
{ pelUnderlay draws BELOW the original content, so the watermark never
hides the text - which is what makes it readable without transparency. }
p.BeginPageEdit(I, pelUnderlay);
try
p.Graphics.Font.Name := 'Segoe UI';
p.Graphics.Font.Size := 64;
p.Graphics.Font.Color := gcGainsboro;
p.Graphics.Alignment := gtaCenter;
p.Graphics.DrawText(AText,
RectF(0, Info.Height / 2 - 50, Info.Width, Info.Height / 2 + 50));
p.EndPageEdit;
except
p.CancelPageEdit;
raise;
end;
end;
p.SaveDocument(AFileName);
finally
p.CloseDocument;
end;
finally
p.Free;
end;
end;
To put the same watermark on top, switch the layer to pelOverlay — and expect
to have to lighten the colour so it does not swallow the text underneath.
Links and annotations
Not everything the drawing API produces is drawing. AddURL writes visible text
and a link annotation over it, and an annotation is page furniture rather than
page content — so it has to be transferred separately when the edit merges. It
is: annotations created on the edit surface are carried onto the page with their
rectangles put through the same transform as the drawing, so a link stays over
its own text on a rotated page, a page with a CropBox offset and a page with a
UserUnit scale alike. That makes stamping the straightforward way to turn part
of a flat, incoming document into something clickable.
Two details of AddURL are easy to miss. It draws its text with URLFont,
not Font: for the duration of the call it assigns URLFont into Font and
restores Font afterwards, so styling a link through Font has no effect.
And the annotation covers the text that was actually drawn inside the rectangle
you pass — positioned in it according to Alignment — rather than the whole
rectangle.
procedure TForm1.StampOrderLink(const AFileName, AURL: string);
var
p: TTMSFNCPDFLib;
Info: TTMSFNCPDFPageInfo;
LinkRect: TRectF;
begin
p := TTMSFNCPDFLib.Create;
try
p.OpenDocument(AFileName);
try
Info := p.GetDocumentPageInfo(0);
p.BeginPageEdit(0, pelOverlay);
try
{ AddURL draws its text with URLFont, not Font: it assigns URLFont into
Font for the duration of the call and restores Font afterwards. Style
the link through URLFont, which starts out blue and underlined. }
p.Graphics.URLFont.Name := 'Segoe UI';
p.Graphics.URLFont.Size := 9;
p.Graphics.URLFont.Color := gcBlue;
{ AddURL draws the text and adds a link annotation over the text it
actually drew inside ARect. The annotation is carried onto the page
when the edit is merged, with its rectangle transformed exactly like
the drawing. }
LinkRect := RectF(40, Info.Height - 60, 300, Info.Height - 40);
p.Graphics.AddURL('View this order online', AURL, LinkRect);
p.EndPageEdit;
except
p.CancelPageEdit;
raise;
end;
p.SaveDocument(AFileName);
finally
p.CloseDocument;
end;
finally
p.Free;
end;
end;
The result is the same on every platform. Some graphics backends cannot emit an
annotation while they are drawing, so the link is recorded as the page is drawn
and written once its content exists. That is what makes AddURL inside a page
edit behave identically on Windows, Linux, macOS, iOS and Android.
One class of annotation is deliberately dropped: anything that points inside
the edit surface's own page tree. The surface is a single-page document, so an
internal destination or a GoTo action — what either AddGoTo overload
produces — would resolve against a page that does not exist in the document
being stamped, and a link to nowhere is worse than no link. External AddURL
links transfer; AddGoTo links do not. To cross-link the pages of an assembled
document, add the links while generating the document you merge in, then merge
it: a merge clones each page with its annotations intact. See
Rearranging pages.
Abandoning an edit
Not every edit should go through. CancelPageEdit throws the drawing away and
leaves the page untouched, which makes it the natural response to "nothing to
stamp after all" as well as to an exception.
procedure TForm1.MarkOverdue(APDF: TTMSFNCPDFLib; APageIndex: Integer;
const ANotice: string);
begin
APDF.BeginPageEdit(APageIndex, pelOverlay);
try
if ANotice = '' then
begin
{ CancelPageEdit discards the drawing and leaves the page exactly as it
was. The document stays open and can be edited or saved afterwards. }
APDF.CancelPageEdit;
Exit;
end;
APDF.Graphics.Font.Size := 11;
APDF.Graphics.Font.Color := gcRed;
APDF.Graphics.DrawText(ANotice, PointF(40, 40));
APDF.EndPageEdit;
except
{ A structural mutation raises while an edit is active, so always leave the
page-edit state clean before letting the exception travel. }
if APDF.IsPageEditing then
APDF.CancelPageEdit;
raise;
end;
end;
By the time an exception reaches the handler the edit may already have merged, so
the handler checks IsPageEditing before cancelling. CancelPageEdit is a
no-op when no edit is active, so the guard is not strictly required — it makes
the intent explicit in a handler that covers both the drawing and everything
after EndPageEdit.
Putting it together
Branding a document that arrives from elsewhere usually needs both layers: a watermark underneath and a footer on top, sized per page and applied across the whole document. This combines the lifecycle, the coordinate space and both layer modes in one pass, and saves under a new name.
procedure TForm1.BrandExistingDocument(const ASourceFile, ATargetFile,
AWatermark: string);
var
p: TTMSFNCPDFLib;
Info: TTMSFNCPDFPageInfo;
I, PageCount: Integer;
begin
p := TTMSFNCPDFLib.Create;
try
p.OpenDocument(ASourceFile);
try
PageCount := p.GetPageCount;
for I := 0 to PageCount - 1 do
begin
{ Each page can have its own size and rotation, so read the geometry
per page instead of assuming the size of page 1. }
Info := p.GetDocumentPageInfo(I);
{ Pass 1 - the watermark goes underneath the original content. }
p.BeginPageEdit(I, pelUnderlay);
try
p.Graphics.Font.Name := 'Segoe UI';
p.Graphics.Font.Size := 56;
p.Graphics.Font.Color := gcGainsboro;
p.Graphics.Alignment := gtaCenter;
p.Graphics.DrawText(AWatermark,
RectF(0, Info.Height / 2 - 40, Info.Width, Info.Height / 2 + 40));
p.EndPageEdit;
except
p.CancelPageEdit;
raise;
end;
{ Pass 2 - the footer goes on top, so it stays legible over content. }
p.BeginPageEdit(I, pelOverlay);
try
p.Graphics.Stroke.Color := gcSilver;
p.Graphics.Stroke.Width := 1;
p.Graphics.DrawLine(PointF(40, Info.Height - 46),
PointF(Info.Width - 40, Info.Height - 46));
p.Graphics.Font.Name := 'Segoe UI';
p.Graphics.Font.Size := 9;
p.Graphics.Font.Color := gcGray;
p.Graphics.Alignment := gtaCenter;
p.Graphics.DrawText(Format('Page %d of %d', [I + 1, PageCount]),
RectF(0, Info.Height - 40, Info.Width, Info.Height - 20));
p.EndPageEdit;
except
p.CancelPageEdit;
raise;
end;
end;
p.SaveDocument(ATargetFile);
finally
p.CloseDocument;
end;
finally
p.Free;
end;
end;
Common mistakes
- Drawing before
BeginPageEdit. Outside an edit,Graphicsaddresses the generation canvas, so the drawing silently goes nowhere near the opened document. - Leaving an edit open. The next structural call — including
SaveDocument— raisesETMSFNCPDFDocumentuntil you end or cancel. - Expecting
CloseDocumentto complain about it. It does not:CloseDocumentandOpenDocumentcancel an active edit silently. An edit left open when afinallycloses the document is discarded without a word, so the page comes out unstamped rather than raising. - Styling a link through
Font.AddURLdraws withURLFontand putsFontback when it returns, so set the name, size and colour onURLFont. - Expecting an internal link to survive. An
AddGoTolink made on the edit surface targets that surface's own single page, so it is dropped when the edit merges. UseAddURLfor external targets. - Reaching for a page edit when you need a new page.
BeginPageEditdraws on a page that already exists;NewPageon an opened document raises rather than appending one. Generate the page in its own document and bring it in withMergeDocument— see Editing existing documents. - Nesting edits. Finish the page you are on before beginning the next; two concurrent edits are not supported.
- Laying out against a fixed page size. Use
GetDocumentPageInfoper page; A4 is an assumption, not a guarantee. - Expecting an automatic header or footer. They are switched off on the edit surface by design — draw them yourself.
- Overlaying a large opaque shape. Use
pelUnderlay, or the content you were trying to annotate disappears.
See also
- Editing existing documents — open, inspect, save.
- Rearranging pages — restructure the page list.
- PDF Library guides — the full drawing API used on the edit surface.
TTMSFNCPDFLib— full class reference.