Watermarking a PDF — adding semi-transparent text over pages — sounds like something only desktop software handles. But with pdf-lib and a bit of canvas math, you can build a fully browser-based watermark tool.
This post walks through the implementation details, including text rendering, rotation, and multi-page support.
Why client-side?
Traditional watermark tools upload your file, process it on a server, and send the result back. For documents that might be confidential or contain sensitive information, this introduces an unnecessary privacy risk. A browser-based approach:
- Processes everything locally
- Keeps files on the user’s device
- Works offline after loading
- Avoids server-side bandwidth costs
The stack
- Vue 3 + Composition API
- pdf-lib for PDF manipulation and watermarks
-
PDF.js (
pdfjs-dist) for preview rendering - Vite for bundling
Adding a text watermark
pdf-lib provides a built-in PDFDocument.embedFont() method for custom fonts and page.drawText() for placing text. Here’s how to add a rotated, semi-transparent watermark across all pages:
<script setup lang="ts">
import { ref } from 'vue'
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib'
const file = ref<File | null>(null)
const watermarkText = ref('DRAFT')
const opacity = ref(0.3)
const fontSize = ref(72)
const rotationDeg = ref(-45)
const applying = ref(false)
async function handleFileUpload(selected: File) {
file.value = selected
}
async function applyWatermark() {
if (!file.value) return
applying.value = true
try {
const arrayBuffer = await file.value.arrayBuffer()
const pdfDoc = await PDFDocument.load(arrayBuffer)
// Embed the Helvetica font — required for correct rendering
const helveticaFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold)
const pages = pdfDoc.getPages()
pages.forEach((page) => {
const { width, height } = page.getSize()
page.drawText(watermarkText.value, {
x: (width - helveticaFont.widthOfTextAtSize(watermarkText.value, fontSize.value)) / 2,
y: height / 2,
size: fontSize.value,
font: helveticaFont,
color: rgb(128, 128, 128), // mid-gray
rotate: {
type: 'rad',
angle: (rotationDeg.value * Math.PI) / 180,
},
})
})
const watermarkedBytes = await pdfDoc.save()
const blob = new Blob([watermarkedBytes], { type: 'application/pdf' })
downloadBlob(blob, `watermarked_${file.value.name}`)
} finally {
applying.value = false
}
}
function downloadBlob(blob: Blob, name: string) {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = name
a.click()
URL.revokeObjectURL(url)
}
</script>
Enter fullscreen mode Exit fullscreen mode
The rotation challenge
The tricky part is rotating text around its own center. pdf-lib rotates text relative to the origin point (x, y) — which is the bottom-left corner of the character, not the center of the string. This means rotating at -45° doesn’t look centered.
To fix this, I compute the bounding box of the text first, then offset the position so the visual center aligns with the page center:
function getCenteredPosition(page: Page, text: string, fontSize: number, font: PDFFont) {
const { width, height } = page.getSize()
const textWidth = font.widthOfTextAtSize(text, fontSize)
const textHeight = fontSize
// Account for rotation — the bounding box changes when text is rotated
const radians = (-45 * Math.PI) / 180
const cos = Math.abs(Math.cos(radians))
const sin = Math.abs(Math.sin(radians))
// Rotated bounding box dimensions
const rotatedWidth = textWidth * cos + textHeight * sin
const rotatedHeight = textWidth * sin + textHeight * cos
return {
x: (width - rotatedWidth) / 2,
y: (height - rotatedHeight) / 2,
}
}
Enter fullscreen mode Exit fullscreen mode
Making it configurable
For a production tool, you need more than just a one-size-fits-all approach:
Setting Range Default Purpose Text Any Unicode “DRAFT” What to display Font size 12–200pt 72 Visibility level Opacity 0.05–0.8 0.3 Subtlety control Rotation 0–360° -45° Angle preference Color Hex/RGB Gray Brand matching Position Center/Custom Center Where to place Pages All/Range/Selected All Scope controlHandling large PDFs
For PDFs with 100+ pages, adding watermarks can take noticeable time. Key optimizations:
- Batch processing: Group page operations together instead of saving after each page
- Progressive rendering: Show a preview of just the first few pages before committing to all
- Cancel option: Let users stop the operation mid-process
Going further
For simple watermarking, pdf-lib‘s built-in drawText() is enough. If you need image-based watermarks (PNG logos with transparency), you’ll need to embed the image as a form and draw it as an overlay. That’s a separate topic worth exploring in another post.
Want to see the full source? github.com/sunshey/pdf-tool.
If you need advanced watermarking features — batch operations, logo overlays, or scheduled processing — check out Wondershare PDFelement.
답글 남기기