DEV Community

Cover image for My PDF converter returned an empty Markdown for a 200-page prospectus — the text was in a layer I couldn't see
InApp
InApp

Posted on Originally published at imapp.blogspot.com

My PDF converter returned an empty Markdown for a 200-page prospectus — the text was in a layer I couldn't see

A user fed my PDF-to-Markdown endpoint a real financial prospectus and got back a single heading and nothing else. File size: 14 MB. Page count: 200. Markdown output: 47 bytes. I assumed corruption at first, then assumed an encrypted PDF, then actually opened the thing.

The text wasn't missing. It was there — but rendered as a vector outline layer with no extractable text stream. The PDF had been produced by a scanner-adjacent pipeline that rasterized the pages and then overlaid an invisible OCR text layer for accessibility. My extractor was reading the outline layer (headings, bookmarks — which is why I got one heading back) and treating the invisible layer as the document body.

The fix was boring and instructive: I now run two extraction passes — a fast text-stream pass, and if the resulting character density per page falls below a threshold, a fallback pass that pulls from the OCR/invisible layer explicitly. The threshold is the part I keep tuning. Too low and you trigger the slow path on legitimately sparse pages (title pages, section dividers, image-heavy slides). Too high and you miss exactly the kind of document that motivated the fallback.

What I landed on: measure mean characters per page across the document, not per page. A prospectus with 199 dense pages and one blank divider page shouldn't trigger the fallback. A document that averages under ~50 chars/page is almost always image-based, and the slow path is worth it.

The deeper lesson: "PDF" is not one format. It's a container that can hold text, images, vector outlines, invisible accessibility layers, annotations, embedded files, JavaScript, and combinations of all of the above. Any converter that claims to handle "PDFs" without a per-document density check is one weird input away from returning you a confident, empty string.

This is the PDF-to-Markdown endpoint I run at https://x402.freeq.one/tools/pdf_to_markdown.html — the two-pass logic is live now, and the 200-page prospectus converts to ~180k characters of clean Markdown.

Top comments (0)