Getting Text from PDFs Is Harder Than It Looks (and How Tabula Handles It for RAG)

PDFs lack structure, making text extraction for RAG painful. See how the Tabula Go library uses native parsing to rebuild clean, semantic chunks.


The problem

Ask a Go developer to "just pull the text out of a PDF" and you'll usually get a confident estimate followed, a few days later, with a different story. The trouble is that a PDF is not a normal "document" in any meaningful sense; it's really a set of drawing instructions. A word like "invoice" isn't always stored as a string — it's stored as five glyphs placed at specific x/y coordinates, possibly in a subsetted font with a custom character map, possibly split across two content layers, possibly drawn in an order that has nothing to do with reading order.

There is no paragraph. There is no heading. There is no "next line." Those are all things a human eye reconstructs from spatial layout, and a naive extractor throws them away, or returns a garbled mess.

That's the good case — a PDF with real, embedded text. It gets worse:

  • Scanned documents contain no text at all, just an image of a page.
  • Design-heavy PDFs often render body copy as vector path outlines rather than fonts, so the "text" is invisible even to a careful parser and to image extraction.
  • Scanner compression formats like JBIG2 and JPEG2000 have no pure-Go decoder in existence.
  • Rotation, multi-column layouts, watermarks, damaged cross-reference tables, and encryption each break a different assumption.

In Go specifically, the pain compounds. The ecosystem has no batteries-included PDF text layer, and the moment you reach for OCR or exotic image codecs, you're into cgo — build tags, libc header mismatches on Alpine, Homebrew include paths on Apple Silicon, and libraries that aren't goroutine-safe. What started as "just get the text" becomes a serious problem.

How Tabula approaches it


Tabula is a Go library built around a blunt observation: since a PDF has no semantic structure, you have to reconstruct it before the text is worth anything. It does this with a layered, native-first strategy.

Native extraction comes first. The entire PDF parser — fonts, character maps, encryption, layout reconstruction, even recovery of damaged files — is hand-written in pure Go with zero required dependencies. It pulls positioned glyph fragments and then rebuilds reading order: grouping lines, handling multiple columns, de-duplicating text drawn on overlapping layers, and honoring page rotation.

OCR is a fallback, not a default. When a page comes back with almost no native text, Tabula treats it as scanned or vector-drawn and rasterizes the full page before handing it to Tesseract. Rendering the whole page — rather than fishing out embedded images — is what catches text that was drawn as vector outlines, a case that defeats most extractors. This path, along with the JBIG2 and JPEG2000 decoders it needs, is quarantined behind a build tag so the common case stays pure Go and dependency-free.

Why this matters for RAG


None of this is academic once you're feeding a vector database. RAG quality is downstream of chunk quality, and chunk quality is downstream of structure. If your extractor emits one undifferentiated wall of text, your chunker will slice paragraphs mid-sentence, strand headings away from the content they introduce, and produce fragments that don't work very well.

Tabula's chunker works on the reconstructed document, so it can be smart about boundaries. It detects headings — even recovering chapter markers from OCR'd books that have no markup — and keeps them attached to the section they belong to. It coalesces chunks that are too small to be useful and splits oversized ones at semantic boundaries, rebalancing the cut so it never strands a tiny orphan fragment. Every chunk carries metadata: section title, page range, token estimate, and whether it contains a table, list, or image.

The result is that the messy, deeply un-semantic reality of the PDF format gets turned into clean, structured, retrieval-ready text — which is exactly what a RAG system needs and exactly what a PDF refuses to give you for free.

Here is a link that you may find useful:
https://github.com/tsawler/tabula


Also check out my RAG Python course: Building a RAG Application in Python


NEW COURSE COMING SOON!

Want to build smarter, more reliable RAG systems? Get on the Newsletter list and be the first to access the full course when it drops. 

Categories: : DevLife, RAG