What my browser OCR prototype still needs
·3 min read

What my browser OCR prototype still needs

The OCR prototype already knows which PDF page it's processing. It logs the page count, the current page and Tesseract's progress. If you're using the app, though, all you see is a button that says Processing....

TypeScript
console.log("PDF Loaded");
console.log("Number of pages: " + numPages);
console.log("Processing page: " + i);
TypeScript
worker = await createWorker({ logger: (m) => console.log(m) });

I noticed that while rereading the project. I'd wired up the information I needed to show progress, then left it in the console where only I would see it.

From a PDF to text

ocr-something is a small Vite, React and TypeScript app. You choose an image or PDF, and recognition happens in the browser using Tesseract. There's no application backend.

For PDFs, PDF.js first renders each page to a canvas at a scale of 1.5. The canvas becomes a PNG data URL, which is passed to the OCR worker. Images take the shorter route through a file reader.

That intermediate image matters. The render scale changes the image the OCR engine gets and the amount of work the browser has to do. I picked 1.5 without a comparison of recognition quality, so I can't tell you it was the best setting.

What I put in Redux

I remembered using Redux to organise the processing state. Reading the store showed that I'd only modelled the selected file and the result:

TypeScript
export interface FileState {
selectedFile: FileData | null;
fileType: FileType;
fileContent: string[] | null;
result: string | null;
}

There's no current page, progress value or error in that shape. The loading indicator is a local boolean in the OCR component. That's enough to disable a button, but it doesn't help someone decide whether a long document is still making progress.

I'd keep per-page results and a processing status if I returned to this. That would also give errors somewhere to go. At the moment, the OCR catch logs an error, while the PDF loading promise doesn't have a rejection handler. A damaged or password-protected file can leave the person waiting without an explanation.

The smaller bugs around recognition

Recognised text is appended like this:

TypeScript
ocrText += text;

There's no separator between pages. Keeping the text by page would preserve the boundary and let the interface show completed pages before the whole file finishes.

The file type also claims more certainty than it has:

TypeScript
export type FileType = "image/png" | "application/pdf" | null;

The upload casts file.type to that union, even when the file is a JPEG. The current branch checks for PDF and treats the rest as images, so the mismatch can go unnoticed. Validating the accepted types would make the code and the interface agree about what is supported.

What I'd work on first

I'd start with progress and failure states, using the events already available from the libraries. Then I'd try a few representative documents and compare render settings. Those changes would make it easier to tell whether the app is working before spending time on performance tweaks.

The recognition pipeline was a useful prototype. The unfinished part is what happens around it: keeping page boundaries, showing progress and explaining a failure. Those are the things I'd miss if I were using it without the console open.