Traceable document copies

Give every copy a traceable fingerprint.

Each copy carries a unique document ID as a discreet, decorative ring pattern — so you can trace any copy back to its source, by machine or by eye with no tools.

$ npm install pdf-ring-watermark
A document with a subtle concentric-ring watermark

Why

A forensic channel that survives the real world

Metadata is trivially stripped. The ring pattern is the channel that lasts: it looks like a guilloché-style background, stays legible under the content, and survives copying, re-export, screenshots and printing. Each ring is one bit of an error-protected codeword — solid = 1, dashed = 0, dash-dot = start.

Features

Built for tracing, not decoration

🫥

Discreet

Renders as a faint background texture; the document stays fully readable.

🧩

Crop-robust

The codeword tiles across the page, so even a ~3 × 3 cm fragment carries the full ID.

🛡️

Error-protected

Optional Hamming SECDED corrects one misread element and detects two.

👁️

Human-readable

No tools needed — read the pattern by eye, type the bits, recover the ID.

🎯

Matched detection

Even a partial or slightly wrong read is matched against your issued-ID list.

⚙️

Parameterizable

Pattern mode, ID capacity, protection, spacing, stroke, darkness, opacity.

Three modes

Pick the carrier that fits

Same bits, same codec — different geometry. Rings hide best and spread in 2-D; lines are axis-aligned, easiest to read and to decode automatically.

How it works

From ID to pattern and back

01

Encode

The ID becomes an error-protected codeword; each bit sets one element's line style.

02

Tag

The pattern is drawn full-bleed onto the PDF; the ID also goes into the metadata.

03

Recover

Measure each element's ink duty cycle into soft bits — from a scan or by eye.

04

Identify

Decode the codeword, or match the soft bits against your list of issued IDs.

Calculator

Capacity & crop size

How many IDs do you need, and how much error protection? See how long the codeword becomes and how large a crop must be to read one ID in full.

Settings

pt

Smaller = more discreet and more repetitions, but tighter elements.

Result

Possible IDs

Parity bits

Codeword length

Elements per codeword

Safe crop (square)

1 = solid 0 = dashed start ring safe crop

Read by hand — interactive

Wherever you look, the whole ID is there

Drag the window across the pattern. Any window-sized crop — one codeword, the safe ~3 × 3 cm — holds all 20 bits, so no matter where you land you can read the full ID. Solid = 1, dashed = 0, dash-dot = the start ring (!).

Codeword under the window Document ID

Install & use

Two lines to tag, one to trace

$ npm install pdf-ring-watermark

In your app (library)

// tag a copy with document ID 10234
import { createRingWatermark } from "pdf-ring-watermark";

const wm = createRingWatermark();
const tagged = await wm.tagPdfOnPaper(pdf, { userId: 10234 });

// later — recover from a manual read
wm.decodeBits("!01110011111111110100");
// → { userId: 10234, corrected: false }

From the terminal (CLI)

# full read → ID (corrects 1 bit / 1 gap)
npx pdf-ring-watermark decode-bits \
  "1110100,0111001111"

# partial read → ranked known IDs
npx pdf-ring-watermark match-bits \
  --ids issued-ids.txt "0111,00x11111"

Support

Keep this tool running

pdf-ring-watermark is free and open source. Your donation covers hosting and upkeep — thank you!