§HOW-TO GUIDE · C# · .NET 8 & 10

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

Video walkthrough · 3:24 · Watch on YouTube · Short · 0:57
§STEP BY STEP

Build it step by step.

  1. 1Install the NuGet packages

    SelectPdf.Universal is the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.

    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.win-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-arm64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.osx-arm64
  2. 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.cs with the code below.

    terminal
    dotnet new console -n PdfSample
    cd PdfSample
  3. 3Import the namespace

    Program.cs · lines 1–1
    using SelectPdf.Universal;

    Everything used here is in SelectPdf.Universal.

  4. 4Create a PDF to watermark

    Program.cs · lines 3–11
    // 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, HtmlToPdf converts a three-page report into report.pdf; page-break-before starts the second and third pages. In your application this is the PDF you already have.

  5. 5Load it and add a page-sized template

    Program.cs · lines 13–16
    // 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.Add with the page's ClientRectangle creates a template that covers the whole page. Whatever you add to a template repeats on every page of the document.

  6. 6Build the rotated, semi-transparent text

    Program.cs · lines 18–28
    // 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 = 45 rotates it counter-clockwise around its starting point, and Transparency = 30 keeps the page content readable underneath.

  7. 7Save the stamped copy

    Program.cs · lines 30–32
    // 4. The template repeats on every page. Save the stamped copy.
    doc.Save("report-watermarked.pdf");
    doc.Close();

    Save writes all three pages, each with the watermark, to a new file, so the original stays untouched. Close releases the document.

  8. 8Run it

    terminal
    dotnet run

    The program writes report-watermarked.pdf to the working folder. Without a license key, every page carries a trial watermark; set GlobalProperties.LicenseKey before the first conversion to remove it.

§COMPLETE PROGRAM

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.

Program.cs
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

§OUTPUT

The PDF it produces.

First page of report-watermarked.pdf, the PDF this sample produces
report-watermarked.pdf, page 1

Download report-watermarked.pdf ↓ the file this sample produced, unedited

§VARIATIONS

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.

ImageWatermark.cs
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.

DraftBehindContent.cs
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.

SkipFirstPage.cs
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();
    }
}
§TROUBLESHOOTING

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

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
?FAQ

Questions, answered.

Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.

Load the PDF into a PdfDocument, add a PdfTemplate with doc.Templates.Add, add a PdfTextElement or PdfImageElement to the template, and save. The template repeats on every page of the document.
Set PdfTextElement.Direction to an angle in degrees. The text is rotated counter-clockwise around its starting point; 45 gives a diagonal from bottom left to top right.
Yes. Set PdfTemplate.Background to true and the template is drawn behind the page content. By default a template is drawn in front.
No. Editing existing PDFs is part of the commercial SelectPdf.Universal library. The Community Edition is HTML to PDF only.