Home
Stamp “Page 3 of 10” (or only “3”) on every page of a PDF with a short script you run with sumatrapdf-tool run.
Available in pre-release 3.7
Save the script below as add-page-numbers.js and run it on your PDF. At a glance:
  • Run: sumatrapdf-tool run add-page-numbers.js in.pdf out.pdf
  • Text: FORMAT is Page {n} of {total}, {n}, - {n} -, …
  • Position: POSITION puts the number at the bottom or top, left, center or right, and MARGIN sets the distance from the edge.
  • Look: FONT, FONT_SIZE, COLOR.
  • Cover page: SKIP_PAGES leaves the first pages without a number, and FIRST_NUMBER sets where counting starts.

Add page numbers #

  1. Save the script as add-page-numbers.js.
  2. Change the settings at the top of the script, if you want.
  3. Run it:
sumatrapdf-tool run add-page-numbers.js in.pdf out.pdf
out.pdf is a copy of in.pdf with a page number on every page. SumatraPDF.exe run with the same arguments works the same.
The bottom of a page with the default page number: Page 3 of 6
// Add page numbers to every page of a PDF.
// Info: Add page numbers to every page.
// Arg: <INPUT> : Input PDF
// Arg: <OUTPUT> : New output PDF
// Usage: sumatrapdf-tool run add-page-numbers.js in.pdf out.pdf

// ---- Settings ----
var FORMAT = "Page {n} of {total}" // "{n}" for just the number
var POSITION = "bottom-center"     // bottom-left, bottom-center, bottom-right,
                                   // top-left, top-center, top-right
var MARGIN = 24                    // distance from the page edge, in points (72 = 1 inch)
var FONT = "Helvetica"             // Helvetica, Times-Roman, Courier, Helvetica-Bold, ...
var FONT_SIZE = 10                 // in points
var COLOR = [0, 0, 0]              // red, green, blue from 0 to 1
var SKIP_PAGES = 0                 // leave the first N pages (e.g. a cover) without a number
var FIRST_NUMBER = 1               // number shown on the first numbered page
// ------------------

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

var doc = Document.openDocument(scriptArgs[0])
var font = new Font(FONT)
var fontRef = doc.addSimpleFont(font)
var total = FIRST_NUMBER + doc.countPages() - SKIP_PAGES - 1

// open "q" before the page's own content, close it with "Q" after,
// so changes the page makes to the graphics state don't affect our text
var saveState = doc.addStream("q\n")

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

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

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

for (var i = SKIP_PAGES; i < doc.countPages(); i++) {
	var page = doc.loadPage(i)
	var pageObj = page.getObject()

	// add the font to the page resources
	var res = pageObj.getInheritable("Resources")
	if (!res) {
		res = doc.newDictionary()
		pageObj.put("Resources", res)
	}
	if (!res.get("Font"))
		res.put("Font", doc.newDictionary())
	res.get("Font").put("PageNumFont", fontRef)

	// 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]

	var text = FORMAT.replace("{n}", FIRST_NUMBER + i - SKIP_PAGES).replace("{total}", total)
	var tw = textWidth(text)
	var x = MARGIN, y = MARGIN
	if (POSITION.indexOf("center") >= 0) x = (w - tw) / 2
	if (POSITION.indexOf("right") >= 0) x = w - MARGIN - tw
	if (POSITION.indexOf("top") >= 0) y = h - MARGIN - FONT_SIZE

	// map "bottom-left corner 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 m = Matrix.concat([1, 0, 0, -1, b[0], b[3]], Matrix.invert(page.getTransform()))

	var stamp = "\nQ q " + m.map(num).join(" ") + " cm " +
		"BT /PageNumFont " + FONT_SIZE + " Tf " + COLOR.join(" ") + " rg " +
		num(x) + " " + num(y) + " Td " + pdfString(text) + " Tj ET Q\n"

	// new page content: q, the old content, Q, page number
	var contents = doc.newArray()
	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)
	contents.push(doc.addStream(stamp))
	pageObj.put("Contents", contents)
}

doc.save(scriptArgs[1], "garbage,compress")
The numbers are part of the page, so they show in every PDF viewer and print. They are real text, so you can search for them and select them.
The number is placed on the page as you see it. Landscape pages, pages with a rotation (/Rotate) and cropped pages get it at the bottom of the visible page, reading left to right.

Change the text and position #

Edit the settings at the top of the script:
You want Setting
Only the number: 7 FORMAT = "{n}"
- 7 - FORMAT = "- {n} -"
Number in the bottom right corner POSITION = "bottom-right"
Number at the top POSITION = "top-center"
Closer to the edge MARGIN = 12
Bigger, bold, gray FONT_SIZE = 14, FONT = "Helvetica-Bold", COLOR = [0.5, 0.5, 0.5]
No number on the cover, page 2 shows 1 SKIP_PAGES = 1
No number on the cover, page 2 shows 2 SKIP_PAGES = 1, FIRST_NUMBER = 2
Continue from a previous file, start at 41 FIRST_NUMBER = 41
Where POSITION puts the page number: top-left, top-center, top-right, bottom-left, bottom-center or bottom-right, MARGIN points from the edge of the page top-left top-center top-right bottom-left bottom-center bottom-right MARGIN
{total} is the last number printed, so Page {n} of {total} stays right when you skip pages or start at another number.
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, Helvetica-BoldOblique. Standard fonts are not embedded, so the file stays small. Use only Latin characters in FORMAT.

Number many PDFs #

Run the script in a loop. In cmd.exe, to write numbered copies of every PDF in the current folder to a numbered folder:
mkdir numbered
for %f in (*.pdf) do sumatrapdf-tool run add-page-numbers.js "%f" "numbered\%f"
More: Batch process PDFs.

Tips #

  • Write to a new file. Saving over the input file fails with cannot remove file.
  • Keep the original, because running the script twice stamps the numbers twice.
  • Open out.pdf in SumatraPDF to check the position before you number many files.
  • Adding numbers changes the pages, so it breaks existing digital signatures. Number the PDF before you sign it.
  • To number only part of a document, split it, number the part, and merge it back.

See also #