Under the Hood: The Architecture of Sensitive PDF Text Redactor (Blackout Marker)
Most “PDF redaction online” products draw an annotation rectangle and call it done. The original operators—Tj glyph runs, ToUnicode maps, embedded files—remain in the xref table. Anyone who “selects all” or runs pdftotext recovers the secrets under the black box. Cloud redactors that do flatten still upload the document, creating document privacy risk through server-side caching and support access. This tool’s contract is permanent blackout: pdf.js rasterizes each page to a canvas, the user paints opaque rectangles, and pdf-lib rebuilds a new PDF whose pages are JPEG images. There is no hidden text layer.
Execution is 100% client-side. FileReader is not required; file.arrayBuffer() feeds pdf.js. Drawing uses PointerEvent coordinates normalized to the canvas. Export uses CanvasRenderingContext2D, HTMLCanvasElement.toBlob, and pdf-lib embedJpg. Corporate telemetry has no byte stream to attach to: the source PDF never leaves RAM.
Memory Allocation & Browser Threading Mechanics
pdf.js parses the PDF into page dictionaries on the main thread (the worker script is the pdf.js worker for decode, not a network worker). Each visible page is rendered once at scale 1.25 into an offscreen snapshot canvas. That bitmap is the allocation you feel: width × height × 4 bytes of RGBA. Committed redaction boxes are tiny POJOs (normalized x, y, w, h). Live drag does not re-parse the PDF; it drawImages the snapshot and fills the in-progress rectangle so the UI thread only does blit + fillRect at pointer rate.
Flattening walks every page at scale 2, which is the expensive pass. It is awaited sequentially so you do not hold N full-resolution canvases at once. After each page is encoded to JPEG, the temporary canvas can be discarded. This loop pattern keeps the tab from allocating a multi-hundred-megabyte array of bitmaps on a 40-page contract.
Algorithmic Deep-Dive: Code Snippet & Logical Analysis
function liveBox(start, end) {
const x = Math.min(start.x, end.x);
const y = Math.min(start.y, end.y);
return { x, y, w: Math.abs(end.x - start.x), h: Math.abs(end.y - start.y) };
}
function paint(snapshot, canvas, marks, live) {
const ctx = canvas.getContext("2d");
ctx.drawImage(snapshot, 0, 0);
ctx.fillStyle = "#000";
for (const b of marks) {
ctx.fillRect(b.x * canvas.width, b.y * canvas.height,
b.w * canvas.width, b.h * canvas.height);
}
if (live) {
ctx.fillStyle = "rgba(0,0,0,0.72)";
ctx.fillRect(live.x * canvas.width, live.y * canvas.height,
live.w * canvas.width, live.h * canvas.height);
}
}Coordinates are normalized 0–1 so boxes survive the preview scale versus the export scale. On pointermove, liveBox is painted but not committed; on pointerup, boxes with area below a 0.5% threshold are discarded to ignore clicks. Export repeats the same fillRect math on a 2× canvas, then JPEG-encodes. Because JPEG is an image, PDF viewers cannot extract the original string operators. That is the difference between a blackout marker and a redaction.
The Local Storage Sandbox & Volatile Data Pipelines
The source File is held in React state only. Boxes live in a Record<pageNumber, RedactBox[]>. The output PDF is a Uint8Array from pdf-lib save(), wrapped in a Blob with type application/pdf, downloaded via object URL, then revoked. Snapshot canvases are replaced on page change; previous bitmaps become unreachable and GC sweeps them. No IndexedDB stash of “draft redactions” exists, because a half-redacted ID scan should not persist across sessions on a shared machine.
Verifying True Privacy: How to Audit Your Browser's Network Tab
Press F12, open Network, clear the log. Drop a dummy PDF, draw a box, download. You will see the initial page’s JS and the local /pdf.worker.min.mjs worker fetch from this origin—that worker is a static file, not your document. Confirm there is no request whose payload size matches your PDF. No WebSocket should open for redaction. If a tracker pixel fires, it will be unrelated third-party ads, not a document POST. That audit is how you verify PDF redaction online without surrendering document privacy.