DEV Community

Cover image for Bates Numbering in Rust — Automating Legal Document Stamping
hiyoyo
hiyoyo

Posted on

Bates Numbering in Rust — Automating Legal Document Stamping

All tests run on an 8-year-old MacBook Air.
All results from shipping 7 Mac apps as a solo developer. No sponsored opinion.
Bates numbering is sequential page stamping used in legal documents. Every page gets a unique identifier: CASE-001, CASE-002, etc.
I built this into Hiyoko PDF Vault. Here's how it works in Rust.

What Bates numbering actually is
A Bates stamp is a text label added to a fixed position on each page — usually bottom-right or bottom-left. The label increments sequentially across a document set.
Format: [PREFIX][NUMBER][SUFFIX] where number is zero-padded to a fixed width.
Examples: SMITH-000001, EXHIBIT_A_0042, DOC00100

The implementation with lopdf
rustuse lopdf::{Document, Object, Stream, Dictionary, content::Content};

pub struct BatesConfig {
pub prefix: String,
pub suffix: String,
pub start_number: u64,
pub pad_width: usize,
pub position: BatesPosition,
pub font_size: f32,
}

pub enum BatesPosition {
BottomRight,
BottomLeft,
TopRight,
TopLeft,
}

pub fn apply_bates(doc: &mut Document, config: &BatesConfig) -> Result<(), AppError> {
let page_ids: Vec<_> = doc.page_iter().collect();

for (i, page_id) in page_ids.iter().enumerate() {
    let number = config.start_number + i as u64;
    let label = format!(
        "{}{}{}",
        config.prefix,
        format!("{:0>width$}", number, width = config.pad_width),
        config.suffix
    );

    stamp_page(doc, *page_id, &label, &config)?;
}

Ok(())
Enter fullscreen mode Exit fullscreen mode

}

Stamping a page
Adding text to a PDF page requires appending to its content stream:
rustfn stamp_page(
doc: &mut Document,
page_id: (u32, u16),
label: &str,
config: &BatesConfig,
) -> Result<(), AppError> {
let (x, y) = calculate_position(doc, page_id, &config.position)?;

let stamp_content = format!(
    "BT /F1 {} Tf {} {} Td ({}) Tj ET",
    config.font_size, x, y, label
);

// Append to existing page content
// Ensure font is available in page resources
append_content_to_page(doc, page_id, &stamp_content)?;

Ok(())
Enter fullscreen mode Exit fullscreen mode

}

The font dependency
PDF text rendering requires a font reference in the page's resource dictionary. If the page doesn't already have a suitable font, you need to embed one or reference a standard PDF font (Helvetica, Times, Courier — guaranteed to be available in any PDF viewer).
For Bates stamps, Helvetica works fine and requires no font embedding.

Batch processing
The real use case is stamping hundreds of pages across multiple documents. Process sequentially with progress events back to the frontend. Don't try to parallelize PDF mutation — document state is not thread-safe with lopdf's mutable references.

If this was useful, a ❤️ helps more than you'd think — thanks!
Hiyoko PDF Vault → https://hiyokoko.gumroad.com/l/HiyokoPDFVault
X → @hiyoyok

Top comments (0)