Add a watermark to every page of a PDF in C#
A watermark is a PDF template: a PdfTemplate holds the text or image once and the library repeats it on every page of the document. Load an existing PDF, add a template that covers the page, put a rotated, semi-transparent text in it and save.
Compiled and run: Linux ARM64 · Linux x64 · macOS ARM64 · Windows x64
Build it step by step.
-
1Install the NuGet packages
SelectPdf.Universalis the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.win-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-arm64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.osx-arm64
-
2Create a console app
Any .NET 8 or .NET 10 project works. To follow along, create a console app, run the install commands above inside its folder, then replace
Program.cswith the code below.dotnet new console -n PdfSample cd PdfSample
-
3Import the namespace
using SelectPdf.Universal;
Everything used here is in
SelectPdf.Universal. -
4Create a PDF to watermark
// 1. Make a three-page report to stamp. HtmlToPdf converter = new HtmlToPdf(); PdfDocument report = converter.ConvertHtmlString(""" <h1>Quarterly report</h1><p>Revenue, costs and forecast.</p> <h2 style="page-break-before: always">Costs</h2><p>Details by department.</p> <h2 style="page-break-before: always">Forecast</h2><p>Next quarter.</p> """); report.Save("report.pdf"); report.Close();To keep the sample self-contained,
HtmlToPdfconverts a three-page report intoreport.pdf;page-break-beforestarts the second and third pages. In your application this is the PDF you already have. -
5Load it and add a page-sized template
// 2. Load the PDF and add a template that covers the whole page. PdfDocument doc = new PdfDocument("report.pdf"); var area = doc.Pages[0].ClientRectangle; PdfTemplate stamp = doc.Templates.Add(area);new PdfDocument(fileName)loads the existing file.Templates.Addwith the page'sClientRectanglecreates a template that covers the whole page. Whatever you add to a template repeats on every page of the document. -
6Build the rotated, semi-transparent text
// 3. A large red text, rotated 45 degrees and semi-transparent. PdfFont font = doc.Fonts.Add(PdfStandardFont.HelveticaBold); font.Size = 72; string label = "CONFIDENTIAL"; float width = font.GetTextWidth(label); float side = width * 0.7071f; // the rotated text spans width * cos 45 each way PdfTextElement text = new PdfTextElement((area.Width - side) / 2, (area.Height + side) / 2, width + 1, label, font, new PdfColor(220, 0, 0)); text.Direction = 45; text.Transparency = 30; stamp.Add(text);A 72-point Helvetica Bold font measures the label with
GetTextWidth. Rotated by 45 degrees, the text spans about 0.7 of its width both across and up, so the start point is moved left and down by half of that to center it on the page. The element gets an explicit width, slightly larger than the text, so the label stays on one line.Direction = 45rotates it counter-clockwise around its starting point, andTransparency = 30keeps the page content readable underneath. -
7Save the stamped copy
// 4. The template repeats on every page. Save the stamped copy. doc.Save("report-watermarked.pdf"); doc.Close();Savewrites all three pages, each with the watermark, to a new file, so the original stays untouched.Closereleases the document. -
8Run it
dotnet run
The program writes
report-watermarked.pdfto the working folder. Without a license key, every page carries a trial watermark; setGlobalProperties.LicenseKeybefore the first conversion to remove it.
The complete Program.cs.
This is the exact file our samples pipeline compiled and ran on Linux ARM64 and Linux x64 and macOS ARM64 and Windows x64 to produce the output on this page.
using SelectPdf.Universal;
// 1. Make a three-page report to stamp.
HtmlToPdf converter = new HtmlToPdf();
PdfDocument report = converter.ConvertHtmlString("""
<h1>Quarterly report</h1><p>Revenue, costs and forecast.</p>
<h2 style="page-break-before: always">Costs</h2><p>Details by department.</p>
<h2 style="page-break-before: always">Forecast</h2><p>Next quarter.</p>
""");
report.Save("report.pdf");
report.Close();
// 2. Load the PDF and add a template that covers the whole page.
PdfDocument doc = new PdfDocument("report.pdf");
var area = doc.Pages[0].ClientRectangle;
PdfTemplate stamp = doc.Templates.Add(area);
// 3. A large red text, rotated 45 degrees and semi-transparent.
PdfFont font = doc.Fonts.Add(PdfStandardFont.HelveticaBold);
font.Size = 72;
string label = "CONFIDENTIAL";
float width = font.GetTextWidth(label);
float side = width * 0.7071f; // the rotated text spans width * cos 45 each way
PdfTextElement text = new PdfTextElement((area.Width - side) / 2, (area.Height + side) / 2,
width + 1, label, font, new PdfColor(220, 0, 0));
text.Direction = 45;
text.Transparency = 30;
stamp.Add(text);
// 4. The template repeats on every page. Save the stamped copy.
doc.Save("report-watermarked.pdf");
doc.Close();
Open the project on GitHub → this program and the variations below, ready to dotnet run
The PDF it produces.
Download report-watermarked.pdf ↓ the file this sample produced, unedited
Variations you will need next.
Each variation is a separate file in the same project, compiled and run with the program above.
Watermark with a logo image
PdfImageElement puts an image in the template; the (x, y, width, bytes) constructor scales it to 300 points wide and keeps the aspect ratio. Transparency = 25 fades it, and Background = true draws it behind the page content. The logo is made with HtmlToImage to keep the sample self-contained; in your application, read your PNG file.
using SelectPdf.Universal;
static class ImageWatermark
{
public static void Run()
{
// a logo as PNG bytes (in your application, File.ReadAllBytes("logo.png"))
HtmlToImage imager = new HtmlToImage();
imager.WebPageWidth = 400;
imager.WebPageHeight = 120;
byte[] logo = imager.ConvertHtmlString(
"<div style='font: bold 64px sans-serif; color: #1a4fa0'>ACME Corp</div>");
HtmlToPdf converter = new HtmlToPdf();
PdfDocument doc = converter.ConvertHtmlString("""
<h1>Price list</h1><p>Valid until the end of the year.</p>
<h2 style="page-break-before: always">Terms</h2><p>Payment within 30 days.</p>
""");
// the logo, 300 points wide, in the middle of every page, behind the content
var area = doc.Pages[0].ClientRectangle;
PdfTemplate watermark = doc.Templates.Add(area);
watermark.Background = true;
PdfImageElement img = new PdfImageElement((area.Width - 300) / 2, area.Height / 2 - 45, 300, logo);
img.Transparency = 25;
watermark.Add(img);
doc.Save("price-list-watermarked.pdf");
doc.Close();
}
}
Stamp the converter output, behind the text
The document returned by ConvertHtmlString is stamped before it is saved, so there is no intermediate file. Background = true draws the gray DRAFT label behind the page content, so the text of the contract stays on top. Leave it unset and the template is drawn in front, which is the default.
using SelectPdf.Universal;
static class DraftBehindContent
{
public static void Run()
{
// stamp the converter output before saving it: no second file needed
HtmlToPdf converter = new HtmlToPdf();
PdfDocument doc = converter.ConvertHtmlString("""
<h1>Contract draft</h1>
<p style="font-size: 32px">The parties agree to the terms below.
The text stays readable on top of the DRAFT label.</p>
""");
var area = doc.Pages[0].ClientRectangle;
PdfTemplate draft = doc.Templates.Add(area);
draft.Background = true; // behind the page content; the default is in front
PdfFont font = doc.Fonts.Add(PdfStandardFont.HelveticaBold);
font.Size = 160;
PdfTextElement text = new PdfTextElement(0, 20, area.Width, "DRAFT", font, new PdfColor(200, 200, 200));
text.HorizontalAlign = PdfTextHorizontalAlign.Center;
draft.Add(text);
doc.Save("contract-draft.pdf");
doc.Close();
}
}
A corner stamp on every page except the first
A template does not have to cover the page: this one is a 120 by 40 point box in the top-right corner. DisplayOnFirstPage = false leaves the cover page clean. DisplayOnOddPages and DisplayOnEvenPages control the other pages the same way.
using SelectPdf.Universal;
using SelectPdf.Universal.Drawing;
static class SkipFirstPage
{
public static void Run()
{
HtmlToPdf converter = new HtmlToPdf();
PdfDocument doc = converter.ConvertHtmlString("""
<h1>Cover page</h1>
<h2 style="page-break-before: always">Section 1</h2>
<h2 style="page-break-before: always">Section 2</h2>
""");
// a small stamp in the top-right corner of every page except the cover
var area = doc.Pages[0].ClientRectangle;
PdfTemplate stamp = doc.Templates.Add(new RectangleF(area.Width - 130, 10, 120, 40));
stamp.DisplayOnFirstPage = false;
PdfFont font = doc.Fonts.Add(PdfStandardFont.HelveticaBold);
font.Size = 24;
PdfTextElement text = new PdfTextElement(0, 5, 120, "COPY", font, new PdfColor(0, 90, 200));
text.HorizontalAlign = PdfTextHorizontalAlign.Center;
stamp.Add(text);
doc.Save("report-copy.pdf");
doc.Close();
}
}
Common problems and fixes.
The watermark text wraps onto two lines
Without a width, a text element uses the available page width, so a long label can break onto a second line. Pass a width at least as large as font.GetTextWidth(text), as the sample does.
The watermark hides the page content
Lower Transparency (0 is fully transparent, 100 fully opaque), or set Background = true on the template so it is drawn behind the content.
The watermark is not centered
Direction rotates around the starting point (X, Y), not around the center of the text. Move the start point by half of the rotated size, as the sample does for 45 degrees.
The source PDF is password-protected
Load it with the PdfDocument(fileName, password) constructor. PdfDocument also loads from a stream.
Good to know.
- Stamps and watermarks are done with PDF templates, which contain elements that repeat on each page of the PDF document. Source
- Once an existing PDF is loaded, you can add new elements to its pages and add new PDF templates. Source
- PdfTemplate.Background renders the template behind the page content; Foreground (the default) renders it in front. Source
- PdfTextElement.Direction rotates the text counter-clockwise, in degrees, around its starting point. Source
- Transparency goes from 0 (completely transparent) to 100 (completely opaque). Source
Questions, answered.
Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.