Adding a watermark to a PDF looks like a ten-minute job: draw some translucent text on every page. I wrote the script, then tried it on four kinds of PDF, and found five places where "draw text on every page" quietly fails. This post is the script plus those five problems, each with what I measured.
The script
It uses PyMuPDF (pip install pymupdf). This version is the one that survived my tests:
import math, pymupdf
def add_text_watermark(src, dst, text, angle=45, opacity=0.25, size=64,
color=(0.8, 0.1, 0.1), overlay=True):
doc = pymupdf.open(src)
for page in doc:
rot = page.rotation # 0, 90, 180 or 270
shown = page.rect # the page as displayed
w = pymupdf.get_text_length(text, fontname="helv", fontsize=size)
centre = pymupdf.Point(shown.width / 2, shown.height / 2)
a = math.radians(angle)
direction = (math.cos(a), -math.sin(a)) # y grows downwards
perp = (math.sin(a), math.cos(a))
start = pymupdf.Point(centre.x - w / 2 * direction[0] + size * 0.35 * perp[0],
centre.y - w / 2 * direction[1] + size * 0.35 * perp[1])
p = start * page.derotation_matrix # displayed -> stored coordinates
page.insert_text(p, text, fontsize=size, fontname="helv", color=color,
fill_opacity=opacity, rotate=rot,
morph=(p, pymupdf.Matrix(angle)), overlay=overlay)
doc.save(dst, garbage=4, deflate=True)
add_text_watermark("in.pdf", "out.pdf", "CONFIDENTIAL")
That's about 20 lines. The five problems below are why the first version I wrote wasn't this one.
I used four test files, all generated with reportlab: T_text.pdf (10 pages of text), S_scan.pdf (3 pages that are each one full-page JPEG, like a scanner output), R_rotated.pdf (a page with /Rotate 90) and a 2.2 MB logo image.
Problem 1: on a scan, a watermark "behind the content" is invisible
Most tools offer an overlay (on top) or underlay (behind) option. Underlay looks nicer on text pages because the watermark doesn't cover the words. I rendered each result and counted red watermark pixels:
| Page type | Overlay | Underlay |
|---|---|---|
| Text page | 5,826 red pixels | 4,267 red pixels (the part under text is hidden) |
| Scan (full-page image) | 5,828 | 0 |
On the scanned page the page is one opaque image. A watermark placed behind it is still in the file, but nobody can see it. If your PDFs are scans or full-page exports, use overlay with transparency. A tool that only offers "behind the text" is the wrong choice for scans.
Problem 2: pages with /Rotate come out sideways
A PDF page can be stored upright and displayed rotated (/Rotate 90, common for scanned landscape pages). Drawing at a coordinate without accounting for that puts your watermark in the stored orientation, not the displayed one.
I put the word ZEBRAMARK on the rotated page two ways and measured its bounding box as displayed:
| Method | Size on screen (width × height) |
|---|---|
| Naive (stored coordinates) | 99 × 448 pt: vertical |
Rotation-aware (derotation_matrix + rotate=, as in the script) |
448 × 99 pt: horizontal |
I then ran the final script on the same page with /Rotate set to 0, 90, 180 and 270. Every time the text came out at 45° on screen and was centred within about 3 pt. If your watermarks occasionally appear sideways on some pages of a batch, this is the usual cause.
Problem 3: a text watermark is real text
The script writes the watermark as actual text. That has side effects. I placed CONFIDENTIAL as a 12-position tile on each page of the 10-page file and extracted the text:
| Extraction | Watermark words found |
|---|---|
PyMuPDF get_text()
|
90 copies in total |
poppler pdftotext
|
0 |
The original lines of text stayed intact in both cases (500 of 500 lines matched exactly), so the damage is limited, but searching or copy-pasting in a viewer that does read rotated text may land on watermark words. Different tools disagree, so test with the viewer your readers use.
If you draw the watermark as a transparent PNG instead, nothing is added to the text layer: PyMuPDF found 0 copies. The trade-off is a file that doesn't contain the watermark text as text (so no accessibility reading, no search hit on it). Pick by what you need.
Problem 4: logos and file size
I added a 2.2 MB logo (deliberately incompressible noise, so file size reflects the image, not clever compression) to all 10 pages of a 9.7 KB document:
| Method | Resulting file |
|---|---|
PyMuPDF insert_image(stream=…) on every page |
1.9 MB |
the same with save(garbage=4)
|
1.8 MB |
PyMuPDF reusing the image xref
|
1.9 MB |
pypdf merge_page() of a one-page watermark PDF |
2.4 MB |
I expected ten copies (about 20 MB) and none of the four did that: the logo is stored once and referenced from every page. The lesson is different. The size of the file you pass in is the size you pay, even if the logo is drawn 100 pt wide. Resize the logo to the displayed size (at 2× for sharpness) before you stamp. My noise image exaggerates this, since real logos compress better, but a 2000 px logo drawn in a 1-inch box is still wasted bytes.
Problem 5: a watermark can be taken off unless you flatten
A watermark added as above sits in the page as a separate object. That is exactly what makes it flexible, and it's also why a PDF editor can usually delete it. I didn't test removal tools, and this post isn't about removing them. The point is for you: if the watermark is meant to deter copying, think about what a determined person can do with a copy of the file.
The strongest option is to flatten: turn every page into an image. I rendered the watermarked 10-page file and rebuilt it from page images:
| Flattened as | File size | × the watermarked PDF | Text in the file |
|---|---|---|---|
| 150 dpi, JPEG q80 | 4.8 MB | 129× | 0 characters |
| 100 dpi, JPEG q60 | 2.1 MB | 55× | 0 characters |
Flattening keeps the page's appearance and removes the text. In my test, OCR recovered 35 to 43 of 50 tagged lines on page 1, so searchable text must be re-created. File size grows a lot (JPEG on dense text is wasteful, and a smarter format would be smaller). It also makes the document harder to read with a screen reader. So flatten when the watermark must resist casual editing, and keep the original unflattened file for yourself.
A watermark is a deterrent, not protection. To control who can read a file, use an open password (see my other post on what a PDF password protects).
What online tools say about their watermark features (read Nov 2026)
I tested a script, not these sites. This is what each page states; "not stated" means I didn't find it on the page.
| Tool | Text / image | Options stated | Limits and sign-in | Retention and location |
|---|---|---|---|---|
| PDF24 | Text (image: not stated) | Font, colour, transparency, angle, 3×3 position grid, "Fill page", spacing | "No limits"; no registration | Deleted after one hour; "servers in Germany" |
| Sejda | Text and image (JPG, PNG, GIF, SVG) | Opacity, rotation handle, X/Y coordinates, all pages or a page range | "3 tasks per hour", 50 MB or 200 pages (the page shows both figures), 5 MB per image, one file per task; no registration | Deleted after 2 hours |
| Smallpdf | A "Watermark PDF" link sits in its Edit menu; the page I could read gives no details | Not stated | No limits stated; sign-in not stated | Not stated |
| ToolTiny | Text (image: not stated) | Font size, opacity, angle; applied to every page | Max size not stated; no account | The page says files are deleted 10 minutes after processing and 10 minutes after download; it also says both "works entirely in your browser" and "deleted from our servers", so where processing happens isn't clear from the page |
I tried to read iLovePDF's and Adobe's watermark pages, but my fetch failed, so I'm making no claims about them.
Compare the "options stated" column with the five problems above. Almost none of these pages says whether the watermark goes over or under the page, or how it's handled on rotated pages, or whether the output is flattened. Those are the questions that decide the result on a scan. Test with a scan and a rotated page before you trust a tool with a batch.
Checklist before stamping a batch
- Is any page a full-page image? Use overlay and test it. If the watermark disappears, it's underlay.
- Rotated pages? Check at least one landscape or rotated page.
- Text or image watermark? Text is searchable and selectable; image isn't.
- Logo size. Resize to the displayed size first.
- Does it need to resist removal? If yes, flatten and keep the original.
- Always view the result at normal zoom, on the first, last and an odd page.
Limits of this post
- Four small synthetic files, one main library (PyMuPDF on MuPDF 1.28, plus pypdf for one row). Other tools and PDF producers will differ.
- I measured appearance and text extraction, not how easily a watermark can be removed.
- OCR figures come from one synthetic page with small dense text and are noisy.
- Vendor statements are as they appeared on the day I read them.
If you have a PDF where a watermark landed in the wrong place or disappeared, describe its structure (scan, rotated, form…) in the comments and I'll add it to the test set.

Top comments (0)