Home
Put a large, semi-transparent “DRAFT” or “CONFIDENTIAL” across every page of a PDF, or a logo image, with a short script you run with sumatrapdf-tool run.
Available in pre-release 3.7
Save the script below as add-watermark.js and run it on your PDF. At a glance:
  • Run: sumatrapdf-tool run add-watermark.js in.pdf out.pdf
  • Text: TEXT, FONT, COLOR; FONT_SIZE = 0 sizes it to fit the page.
  • Angle: ANGLE = -1 follows the page diagonal; 0 is horizontal.
  • Transparency: OPACITY from 0 (invisible) to 1 (solid).
  • Over or under: UNDER = true draws it behind the page content.
  • Logo: IMAGE adds a PNG or JPEG at the center of every page.

Add a text watermark #

  1. Save the script as add-watermark.js.
  2. Change TEXT and the other settings at the top of the script, if you want.
  3. Run it:
sumatrapdf-tool run add-watermark.js in.pdf out.pdf
out.pdf is a copy of in.pdf with the watermark on every page. SumatraPDF.exe run with the same arguments works the same.
// Add a semi-transparent text or image watermark to every page of a PDF.
// Usage: sumatrapdf-tool run add-watermark.js in.pdf out.pdf

// ---- Settings ----
var TEXT = "DRAFT"          // "" for no text
var FONT = "Helvetica-Bold" // Helvetica, Times-Roman, Courier, Times-Bold, ...
var FONT_SIZE = 0           // in points; 0 fits the text across the page
var COLOR = [1, 0, 0]       // red, green, blue from 0 to 1
var ANGLE = -1              // degrees counter-clockwise; -1 follows the page diagonal
var IMAGE = ""              // path of a PNG or JPEG logo, drawn at the center; "" for none
var IMAGE_WIDTH = 0.4       // image width as a fraction of the page width
var OPACITY = 0.3           // 0 is invisible, 1 is solid
var UNDER = false           // true puts the watermark under the page content
// ------------------

if (scriptArgs.length != 2) {
	print("usage: sumatrapdf-tool run add-watermark.js in.pdf out.pdf")
	quit(1)
}

var doc = Document.openDocument(scriptArgs[0])
var font = new Font(FONT)
var fontRef = doc.addSimpleFont(font)
var gsRef = doc.addObject({ Type: "ExtGState", ca: OPACITY, CA: OPACITY })
var image = IMAGE ? new Image(IMAGE) : null
var imageRef = image ? doc.addImage(image) : null
var saveState = doc.addStream("q\n")

function num(v) {
	return Math.round(v * 1000) / 1000
}

function pdfString(s) {
	return "(" + s.replace(/[\\()]/g, "\\$&") + ")"
}

function textWidth(s, size) {
	var w = 0
	for (var i = 0; i < s.length; i++)
		w += font.advanceGlyph(font.encodeCharacter(s.charCodeAt(i)), 0)
	return w * size
}

// add a resource to the page, e.g. addResource(pageObj, "Font", "WmFont", fontRef)
function addResource(pageObj, type, name, ref) {
	var res = pageObj.getInheritable("Resources")
	if (!res) {
		res = doc.newDictionary()
		pageObj.put("Resources", res)
	}
	if (!res.get(type))
		res.put(type, doc.newDictionary())
	res.get(type).put(name, ref)
}

for (var i = 0; i < doc.countPages(); i++) {
	var page = doc.loadPage(i)
	var pageObj = page.getObject()
	addResource(pageObj, "ExtGState", "WmGS", gsRef)

	// page size as you see it (crop box, after rotation)
	var b = page.getBounds()
	var w = b[2] - b[0], h = b[3] - b[1]

	// map "center of the page as you see it, y up" to the page's own coordinates;
	// handles /Rotate and crop boxes that don't start at 0,0
	var center = Matrix.concat([1, 0, 0, -1, b[0] + w / 2, b[1] + h / 2],
		Matrix.invert(page.getTransform()))

	var s = "q /WmGS gs\n"
	if (TEXT) {
		addResource(pageObj, "Font", "WmFont", fontRef)
		var angle = ANGLE == -1 ? Math.atan2(h, w) * 180 / Math.PI : ANGLE
		var size = FONT_SIZE
		if (!size) {
			// the biggest size at which the rotated text fits in 80% of the page
			var c = Math.abs(Math.cos(angle * Math.PI / 180))
			var sn = Math.abs(Math.sin(angle * Math.PI / 180))
			var tw = textWidth(TEXT, 1), th = 0.7 // text height (capitals), at size 1
			size = 0.8 * Math.min(w / (tw * c + th * sn), h / (tw * sn + th * c))
		}
		var m = Matrix.concat(Matrix.rotate(angle), center)
		s += "q " + m.map(num).join(" ") + " cm BT /WmFont " + num(size) + " Tf " +
			COLOR.join(" ") + " rg " + num(-textWidth(TEXT, size) / 2) + " " +
			num(-0.35 * size) + " Td " + pdfString(TEXT) + " Tj ET Q\n"
	}
	if (image) {
		addResource(pageObj, "XObject", "WmImage", imageRef)
		var iw = IMAGE_WIDTH * w
		var ih = iw * image.getHeight() / image.getWidth()
		var m = Matrix.concat([iw, 0, 0, ih, -iw / 2, -ih / 2], center)
		s += "q " + m.map(num).join(" ") + " cm /WmImage Do Q\n"
	}
	s += "Q\n"

	// new page content: watermark, then the old content, or
	// q, the old content, Q, then watermark
	var contents = doc.newArray()
	if (UNDER)
		contents.push(doc.addStream(s))
	else
		contents.push(saveState)
	var old = pageObj.get("Contents")
	if (old && old.isArray())
		old.forEach(function (c) { contents.push(c) })
	else if (old)
		contents.push(old)
	if (!UNDER)
		contents.push(doc.addStream("\nQ " + s))
	pageObj.put("Contents", contents)
}

doc.save(scriptArgs[1], "garbage,compress")
The watermark is centered on the page as you see it, including landscape pages, pages with a rotation (/Rotate) and cropped pages. With FONT_SIZE = 0 the text is as big as fits in 80% of each page, so it scales with the page size.

Change the look #

Edit the settings at the top of the script:
You want Setting
Different text TEXT = "CONFIDENTIAL"
Gray instead of red COLOR = [0.5, 0.5, 0.5]
Lighter OPACITY = 0.15
Horizontal ANGLE = 0
Always 45 degrees ANGLE = 45
Fixed size, e.g. 72 points FONT_SIZE = 72
Serif font FONT = "Times-Bold"
FONT is a standard PDF font: Helvetica, Times-Roman or Courier, or a bold or italic variant such as Helvetica-Bold, Times-Italic, Courier-Oblique. Standard fonts are not embedded, so the file stays small. Use only Latin characters in TEXT.

Put the watermark under the content #

Set UNDER = true. The page’s text and pictures are drawn on top of the watermark, so it never covers anything.
Note: Anything opaque on the page hides the watermark under it: images, colored backgrounds, and the whole page of a scanned document. For scans, keep UNDER = false.

Add a logo or image watermark #

Set IMAGE to the path of a PNG or JPEG file:
var TEXT = ""               // "" for no text
var IMAGE = "C:\\Pictures\\logo.png"
var IMAGE_WIDTH = 0.4       // image width as a fraction of the page width
  • The image is centered, IMAGE_WIDTH of the page width, with OPACITY applied.
  • Keep TEXT to get both the text and the image.
  • Transparent areas of a PNG stay transparent.
  • Write \\ for each \ in the path, or use /: "C:/Pictures/logo.png".

Tips #

  • Write to a new file. Saving over the input file fails with cannot remove file.
  • Keep the original: the watermark becomes part of the page content and there is no easy way to take it out.
  • The watermark text is real text: it shows up when you search or copy text from the page.
  • Adding a watermark changes the pages, so it breaks existing digital signatures. Add it before you sign.
  • To watermark many files, run the script in a loop: see Batch process PDFs.

See also #