Building a Fully Offline PDF Parser, Browser OCR, and Spatial DOCX Generator
We’ve all been there: you need to build a feature that extracts text from a PDF, inspects the page layout, and drops the output into a…
Building a Fully Offline PDF Parser, Browser OCR, and Spatial DOCX Generator
We’ve all been there: you need to build a feature that extracts text from a PDF, inspects the page layout, and drops the output into a styled Microsoft Word (.docx) file. Usually, you'd spin up an API server, slap on some Python libraries, pay a cloud provider for processing power, and worry about data privacy.
But what if you could do all of that fully client-side, 100% offline, directly in the browser sandbox?
In this post, we’ll walk through how we built LiteParse Web — a client-side PDF processing pipeline using WebAssembly, Web Workers, and custom spatial layout mapping. We’ll also dive into the technical solutions we implemented to bypass runtime limitations, align differing coordinate systems, and generate high-quality spatial Word files.
The Stack: WebAssembly Meets Client-Side OCR
We wanted a fast, modern UI with robust layout extraction. Here’s what we picked:
- Astro.js + Tailwind CSS for a super-clean, modern dark-mode interface.
**@llamaindex/liteparse-wasm** to handle the heavy lifting of extracting text and structure locally using WebAssembly.- PDF.js for rendering PDF pages to HTML5 canvases.
- Tesseract.js for browser-based Optical Character Recognition (OCR) running inside Web Workers.
**docx** (npm library) for programmatically generating OpenXML Word documents.
🛠️ The Architecture & Solutions
1. Browser-Safe OCR Orchestration
The LiteParse WASM engine is written in Rust. By default, its OCR features attempt to run asynchronous operations that require a native Tokio runtime. When executed directly inside browser-based WebAssembly, this triggers a there is no reactor running panic.
To keep the application fully sandboxed and robust, we designed a client-side coordinator:
- Digital PDFs: We run the WASM engine with
ocrEnabled: falseto extract text instantly. - Scanned/Image PDFs: If digital extraction returns little to no text, we run a custom JS-orchestrated OCR flow. We render the page using PDF.js onto an HTML5 canvas, read the pixel data, and feed it into
Tesseract.jsworker threads.
This hybrid pipeline provides high-performance native parsing for digital PDFs and fallback OCR for scanned documents without crashing the main thread.
2. Projecting Bottom-Up Coordinates to Top-Down HTML
Dealing with coordinates across document standards means translating spatial layouts:
- PDFs place
(0, 0)at the bottom-left of the page (bottom-up layout). - HTML Canvases and Word Documents place
(0, 0)at the top-left (top-down layout).
To align these two spaces, we project the word boundaries returned by the OCR worker back into standard PDF points ($1\text{ inch} = 72\text{ pt}$):
$$\text{renderScale} = \frac{\text{DPI}}{72}$$
$$\text{pdfY}{\text{top}} = \frac{\text{canvasHeight} — \text{canvasY}{\text{pixel}}}{\text{renderScale}}$$
With this projection, we can render interactive bounding boxes directly over the page visualization and ensure they line up with the original text.
3. Creating Clean Spatial Word Files (Line Grouping)
The docx library allows absolute positioning on the page using OpenXML text frames (measured in twips, where $1\text{ pt} = 20\text{ twips}$). However, since our parser extracts items at a word level, generating a separate text frame for every single word results in thousands of overlapping micro-boxes. Word processors render these poorly, and they are nearly impossible to edit.
To produce a clean, readable layout, we built a vertical-proximity clustering algorithm:
- Vertical Proximity Clustering: We sort all text blocks from top to bottom (descending Y). If two consecutive words have vertical coordinates within a threshold (e.g.,
4pt), we group them on the same line. - Horizontal Sorting: We sort the grouped words left-to-right (ascending X).
- String Merging: We join the words with spaces and calculate a single bounding box for the entire line:
$\text{Line Width} = (\text{Last Word’s } X + \text{Width}) — \text{First Word’s } X$
$\text{Line Height} = \text{Maximum height of any word in the cluster}$
function groupIntoLines(textItems, yTolerance = 4) {
items.sort((a, b) => b.y - a.y || a.x - b.x);
const lines = [];
let currentLine = [items[0]];
for (let i = 1; i < items.length; i++) {
const prev = currentLine[0];
const curr = items[i];
if (Math.abs(curr.y - prev.y) <= yTolerance) {
currentLine.push(curr);
} else {
lines.push(currentLine);
currentLine = [curr];
}
}
lines.push(currentLine);
return lines.map(words => {
words.sort((a, b) => a.x - b.x);
return {
text: words.map(w => w.text).join(' '),
x: words[0].x,
y: words[0].y,
width: (words[words.length - 1].x + words[words.length - 1].width) - words[0].x,
height: Math.max(...words.map(w => w.height))
};
});
}
Generating a single text frame per line creates a document that matches the original PDF layout visually while remaining editable and clean in Microsoft Word.
👁️ Interactive DOCX Preview in the Browser
To tie the experience together, we added a DOCX Preview tab.
Instead of forcing the user to download the file blindly, this tab renders an A4 layout sheet inside the dark-themed dashboard. Using our line-grouping coordinates, it absolute-positions HTML elements on a simulated white page, matching the exact layout scale. What you see in the browser is exactly what you get when you hit Export DOCX.
🏁 Wrapping Up
Building client-side applications with heavy document parsing and OCR is now completely viable. By combining Rust WebAssembly engines, browser-based OCR fallback workers, and a bit of math to handle line grouping and coordinate mapping, you can deliver premium tools that are fast, private, and offline-first.
you can check the full-code implementation here
메타데이터
- post_id
- 31645a8d9c10
- slug
- building-a-fully-offline-pdf-parser-browser-ocr-and-spatial-docx-generator-31645a8d9c10
- url
- https://medium.com/@ahsanmubariz/building-a-fully-offline-pdf-parser-browser-ocr-and-spatial-docx-generator-31645a8d9c10
- canonical_url
- https://medium.com/@ahsanmubariz/building-a-fully-offline-pdf-parser-browser-ocr-and-spatial-docx-generator-31645a8d9c10
- author_url
- https://medium.com/@ahsanmubariz
- status
- ok
- fetched_at
- 2026-06-22 00:13:37