“Runs in your browser” is easy to put on a landing page. It is harder to make it a property of the whole OCR pipeline.
An OCR page can have a static interface and still send the selected file to an API. It can keep the image local but put the extracted text into error reporting. It can avoid both and still leak a protected PDF password through a URL or form submission. If privacy is part of the product, the useful question is not where the JavaScript was downloaded from. It is: which user-derived bytes can cross a process or network boundary, and why?
For an image or PDF OCR session, treat all of these as sensitive: source bytes; filenames and MIME metadata; PDF passwords; rendered pages and crops; recognized text and table output; parser and model errors that could contain user-derived values.
The default rule is that none of them may be placed in fetch, forms, beacons, analytics payloads, URLs, browser storage, or logs. Downloading application code, OCR models, and a public character dictionary is different: those resources are the same for every user and contain no document data.
That distinction makes the network policy testable. A test can select a uniquely marked fixture, perform OCR, and fail if the marker or source bytes appear in any outgoing request.
OCR is both CPU- and memory-heavy. Running it on the UI thread creates frozen progress indicators and tempts developers to move the whole job to a server.
A dedicated Web Worker gives a better local architecture: The page decodes a selected file into an ImageBitmap. Ownership of the bitmap is transferred to the worker. The worker loads ONNX Runtime Web and the selected detection and recognition models. It reports coarse progress states such as model loading, detection, and recognition. It returns structured lines and then closes the bitmap.
Transferable objects matter here. Copying a large RGBA buffer between the main thread and a worker can briefly double its memory cost. Transferring an ImageBitmap avoids that copy, while explicitly closing it keeps repeated batches from accumulating GPU-backed resources.
File size is not enough to predict decode cost. A compressed image can expand into tens or hundreds of megabytes of pixels. A practical pipeline checks: accepted MIME types and extensions; compressed byte size; decoded width, height, and pixel count; a maximum working dimension for inference; a separate hard ceiling for safe decoding.
For formats with no universal browser decoder, isolate the decoder. HEIC can run in a short-lived worker; TIFF and BMP can have explicit dimension and buffer checks. After decoding, constrain the bitmap before inference. The OCR detector rarely needs a full 12-megapixel phone photo to find lines of text.
The important failure behavior is deterministic: reject an unsafe image before allocating another full-size buffer, return a localized error, and release anything already created.
A useful local OCR pipeline is not one giant black box. Mine has two model stages: a detector produces a text probability map; connected regions become candidate boxes and nearby boxes are merged; each crop is deskewed into a normalized canvas; a recognition model produces character probabilities; CTC decoding turns those probabilities into text and confidence values.
The detector can operate on a bounded, resized image. Recognition still maps boxes back to the source bitmap, so each line gets a cleaner crop. This also makes table-oriented output possible: the UI retains text plus geometry instead of receiving one opaque string.
Two recognition models are a reasonable product tradeoff. A small model reduces first-use download and latency; a larger model can be loaded only when the user asks for higher accuracy. “High accuracy” should describe the relative model choice, not promise perfect OCR.
A PDF workflow is not just an image workflow with a different file extension. It introduces page ranges, passwords, malformed structures, and potentially hundreds of render operations.
Render only selected pages, cap the document size and page count, and keep the password in memory. Each page should be converted to a bounded bitmap and passed through the same worker pipeline. Release page canvases as soon as their OCR result is complete.
If a user can pause or stop a batch, make that a real state transition. It should stop scheduling new pages, not merely hide a spinner while work continues in the background.
“Works offline” does not mean the first visit works without a network. The application shell and a chosen OCR model have to be downloaded first.
