Client-side

WATERMARK PDF

Stamp a customizable text watermark over every page of your PDF file completely in your browser.

🏷️
Drag & drop a PDF file here, or click to browse
Supports single .pdf file

About Watermark PDF Tool

Protect your intellectual property or label document status by stamping custom text watermarks (e.g. 'CONFIDENTIAL', 'DRAFT', or your brand name) on every page of your PDF file. Customize the text, color, rotation angle, size, and opacity to fit your branding.

The watermarking process is performed client-side using pdf-lib. No external server receives your document, keeping your pre-release drafts and confidential files secure.

Developer Reference

Core Algorithm & Standalone Script

Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.

let currentPdfBytes = null;
    let totalPages = 0;
    let fileNameStr = '';

    const uploadZone = document.getElementById('uploadZone');
    const fileInput = document.getElementById('fileInput');
    const configPanel = document.getElementById('configPanel');
    const fileNameEl = document.getElementById('fileName');
    const pageCountEl = document.getElementById('pageCount');
    const watermarkTextInput = document.getElementById('watermarkText');
    const fontSizeInput = document.getElementById('fontSize');
    const textColorSelect = document.getElementById('textColor');
    const textOpacityInput = document.getElementById('textOpacity');
    const textRotationSelect = document.getElementById('textRotation');
    const watermarkBtn = document.getElementById('watermarkBtn');
    const loadingContainer = document.getElementById('loadingContainer');
    const resultContainer = document.getElementById('resultContainer');
    const resultInfo = document.getElementById('resultInfo');
    const downloadBtn = document.getElementById('downloadBtn');

    // Drag and drop event listeners
    ['dragenter', 'dragover'].forEach(eventName => {
      uploadZone.addEventListener(eventName, e => {
        e.preventDefault();
        uploadZone.classList.add('dragover');
      }, false);
    });

    ['dragleave', 'drop'].forEach(eventName => {
      uploadZone.addEventListener(eventName, e => {
        e.preventDefault();
        uploadZone.classList.remove('dragover');
      }, false);
    });

    uploadZone.addEventListener('drop', e => {
      const dt = e.dataTransfer;
      const files = dt.files;
      if (files.length > 0) handleFile(files[0]);
    });

    fileInput.addEventListener('change', e => {
      if (e.target.files.length > 0) handleFile(e.target.files[0]);
    });

    async function handleFile(file) {
      if (file.type !== 'application/pdf') return;
      fileNameStr = file.name;

      try {
        currentPdfBytes = await file.arrayBuffer();
        const { PDFDocument } = PDFLib;
        const pdf = await PDFDocument.load(currentPdfBytes);
        totalPages = pdf.getPageCount();

        fileNameEl.textContent = file.name;
        pageCountEl.textContent = `${totalPages} page${totalPages > 1 ? 's' : ''}`;
        
        configPanel.classList.remove('hidden');
        resultContainer.classList.add('hidden');
      } catch (err) {
        console.error(err);
        alert('Could not read PDF file. Make sure it is not encrypted.');
      }
    }

    function formatBytes(bytes, decimals = 2) {
      if (bytes === 0) return '0 Bytes';
      const k = 1024;
      const dm = decimals < 0 ? 0 : decimals;
      const sizes = ['Bytes', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
    }

    watermarkBtn.addEventListener('click', async () => {
      if (!currentPdfBytes) return;

      const text = watermarkTextInput.value.trim();
      if (!text) {
        alert('Please enter watermark text.');
        return;
      }

      configPanel.classList.add('hidden');
      loadingContainer.classList.remove('hidden');
      resultContainer.classList.add('hidden');

      try {
        const { PDFDocument, rgb, StandardFonts, degrees } = PDFLib;
        const pdfDoc = await PDFDocument.load(currentPdfBytes);
        const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold);

        const fontSize = parseInt(fontSizeInput.value) || 50;
        const opacity = parseFloat(textOpacityInput.value) || 0.3;
        const rotation = parseInt(textRotationSelect.value) || 0;

        let color = rgb(0.5, 0.5, 0.5); // Gray default
        const colorVal = textColorSelect.value;
        if (colorVal === 'red') color = rgb(0.9, 0.1, 0.1);
        else if (colorVal === 'blue') color = rgb(0.1, 0.3, 0.9);
        else if (colorVal === 'black') color = rgb(0.0, 0.0, 0.0);

        const pages = pdfDoc.getPages();

        for (const page of pages) {
          const { width, height } = page.getSize();
          const textWidth = font.widthOfTextAtSize(text, fontSize);
          
          // Render centered watermark
          page.drawText(text, {
            x: width / 2 - (textWidth / 2) * Math.cos(rotation * Math.PI / 180),
            y: height / 2 - (fontSize / 2) * Math.sin(rotation * Math.PI / 180),
            size: fontSize,
            font: font,
            color: color,
            rotate: degrees(rotation),
            opacity: opacity
          });
        }

        const watermarkedBytes = await pdfDoc.save();
        const blob = new Blob([watermarkedBytes], { type: 'application/pdf' });
        const blobUrl = URL.createObjectURL(blob);

        downloadBtn.href = blobUrl;
        const nameWithoutExt = fileNameStr.replace(/\.[^/.]+$/, "");
        downloadBtn.download = `${nameWithoutExt}_watermarked.pdf`;
        resultInfo.textContent = `Applied watermark to ${totalPages} page(s). File size: ${formatBytes(blob.size)}.`;

        loadingContainer.classList.add('hidden');
        resultContainer.classList.remove('hidden');
      } catch (err) {
        console.error(err);
        alert('An error occurred while watermarking PDF.');
        loadingContainer.classList.add('hidden');
        configPanel.classList.remove('hidden');
      }
    });