In modern corporate litigation, regulatory investigations, patent disputes, and international arbitration, legal teams are required to produce thousands—often millions—of evidentiary pages during the discovery phase. To maintain an indisputable chain of custody and allow attorneys, judges, and expert witnesses to reference specific pages precisely during depositions and trials, the legal profession relies on Bates Stamping (also known as Bates Numbering).
Originating in the late 19th century with Edwin G. Bates's mechanical sequential numbering stamp machine, modern Bates stamping has evolved into a sophisticated digital process applied to PDF files. Concurrently, academic publications, formal contracts, technical manuals, and corporate reports require standardized Page X of Y headers and footers with precise typographical alignment.
However, implementing digital numbering in PDF documents presents subtle mathematical and architectural hurdles: inverted Cartesian coordinate spaces, font glyph width calculation, margin clipping avoidance, and batch sequence preservation across multi-volume document bundles. In this technical guide, we break down PDF coordinate mathematics, explore Bates stamping conventions, and provide production-ready JavaScript code for stamping litigation bundles client-side with 100% data privacy.
PDF Typographic Coordinate Space & Alignment Geometry
The most common error developers encounter when programmatically stamping headers and footers in PDF files is assuming the coordinate space behaves like web CSS or HTML5 Canvas (where $(0, 0)$ is the top-left corner). In the PostScript/PDF model (ISO 32000), the origin $(0, 0)$ is fixed at the lower-left corner of the page.
Font Metric Calculations
Because PDF renderers draw glyphs starting from the left baseline anchor of the text, placing text flush-right requires looking up the exact glyph width metrics of each character in the string at the target font size (e.g., 9pt or 10pt Helvetica). Subtracting textWidth from the right margin ensures the final digit aligns perfectly with the margin line across all pages.
Add Page Numbers & Bates Stamps to PDFs
Number single documents or batch-stamp legal bundles with custom prefixes, zero padding, and header/footer positioning in your browser.
Launch In-Browser PDF Page Numbering Tool →Bates Stamping Standards in Civil & Criminal Discovery
Federal Rules of Civil Procedure (FRCP Rule 34) and judicial standing orders in courts worldwide prescribe strict formats for document production. Standard Bates numbers follow an explicit alphanumeric formula:
ACME-CORP-PROD-0001429 or PLTF-EXHIBIT-000045
Bates Stamping Key Requirements
- Uniform Zero-Padding: Numbers must be padded with leading zeros (e.g.,
000001to000100) to prevent string-sorting errors in litigation databases like Relativity, Concordance, or Everlaw. Standard padding widths range between 6 and 8 digits. - Continuous Bundle Sequence: When producing a bundle of 50 distinct PDF files, the sequence counter must not reset to 1 on each file; Document 2 must start at $(N_{\text{Doc1\_pages}} + 1)$.
- Confidentiality Label Conjunction: Under Protective Orders, pages subject to trade secret or privacy protections must pair the Bates stamp with a confidentiality legend (e.g., "CONFIDENTIAL - ATTORNEYS' EYES ONLY").
JavaScript Code: In-Browser Bates Stamping Engine
The following standalone TypeScript/JavaScript function applies continuous Bates numbering or Page X of Y formulas to a PDF in the browser using pdf-lib:
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
/**
* Stamps sequential Bates numbers and page counts onto a PDF document.
* @param {ArrayBuffer} pdfBuffer - Input PDF binary
* @param {Object} options - Numbering configuration
* @returns {Promise<Uint8Array>} Modified PDF binary
*/
export async function applyBatesStamping(pdfBuffer, options = {}) {
const {
prefix = 'PLTF-',
startNumber = 1,
padding = 6,
format = '{prefix}{bates}', // or 'Page {page} of {total}'
position = 'bottom-right', // 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'
fontSize = 9,
margin = 36, // 0.5 inch (36 points)
confidentialText = null
} = options;
const pdfDoc = await PDFDocument.load(pdfBuffer);
const pages = pdfDoc.getPages();
const totalPages = pages.length;
const font = await pdfDoc.embedStandardFont(StandardFonts.HelveticaBold);
let currentSequence = startNumber;
for (let i = 0; i < totalPages; i++) {
const page = pages[i];
const { width, height } = page.getSize();
const pageNum = i + 1;
// 1. Format zero-padded number
const batesStr = String(currentSequence).padStart(padding, '0');
currentSequence++;
// 2. Interpolate template string
let stampText = format
.replace('{prefix}', prefix)
.replace('{bates}', batesStr)
.replace('{page}', pageNum)
.replace('{total}', totalPages);
const textWidth = font.widthOfTextAtSize(stampText, fontSize);
const textHeight = font.heightAtSize(fontSize);
// 3. Compute X coordinate
let x = margin;
if (position.includes('center')) {
x = (width - textWidth) / 2;
} else if (position.includes('right')) {
x = width - margin - textWidth;
}
// 4. Compute Y coordinate
let y = margin;
if (position.includes('top')) {
y = height - margin - textHeight;
}
// 5. Draw text
page.drawText(stampText, {
x,
y,
size: fontSize,
font: font,
color: rgb(0.1, 0.1, 0.1)
});
// Optional Confidential Label on opposite corner
if (confidentialText) {
const confWidth = font.widthOfTextAtSize(confidentialText, fontSize);
const confX = position.includes('right') ? margin : width - margin - confWidth;
page.drawText(confidentialText, {
x: confX,
y: y,
size: fontSize,
font: font,
color: rgb(0.8, 0.1, 0.1)
});
}
}
return await pdfDoc.save();
}
Dynamic Page X of Y Formulas vs. Fixed Indices
For non-litigation business applications, the Page X of Y format is the universal standard for financial prospectuses, technical specifications, and contracts. It provides immediate notice if a page has been omitted or detached from a physical printout.
When generating dynamic headers/footers, developers should support token variables:
{page}: Current 1-based page number.{total}: Total count of pages in the current document.{date}: Export or generation date formatted as ISO 8601 or localized.{title}: Document title extracted from the PDF Info dictionary.
Frequently Asked Questions
/Rotate 90) require dynamic coordinate checking. A robust stamping engine dynamically reads each page's specific width, height, and rotation matrix, recalculating $(x, y)$ coordinates per page rather than applying fixed coordinates across the entire document.Summary & Implementation Checklist
Mastering PDF coordinate space, typographical alignment mathematics, and zero-padding sequential logic allows legal and software engineering teams to produce flawless discovery sets, professional corporate documentation, and regulatory filings directly in the browser with speed, precision, and privacy.