If you have ever copy-pasted a table out of a PDF, you already know what happens.
Rows collapse into one long line of text.
Columns interleave.
Numbers land in the wrong cell, or no cell at all.
The table on the page looks perfectly structured, but a PDF has no real concept of "table." It only knows where individual characters sit on a page.
Every extraction tool, ours included, has to reconstruct the table from scratch, using nothing but the position of each word.
That gap between "looks like a table" and "is structured data" is where almost every free PDF tool falls apart.
Here is how we handle it, what actually works, and where it still doesn't.
Two different jobs, two different tools PDFHaul splits this into two separate tools because they solve different problems.
PDF to Excel rebuilds the whole document as a single spreadsheet, in the order it appears on the page: form labels, key-value pairs, section titles, and tables all together.
It is for documents where you want the full content, not just the numbers, things like invoices, time sheets, and reports.
Extract Tables does the opposite.
It ignores everything that isn't a table and hands back one clean sheet per table, nothing else.
It is for people who want structured data out, ready to sum, sort, and filter, not a copy of the document.
Both tools share the same underlying geometry engine.
The difference is what each one keeps and what it throws away.
How Extract Tables actually decides what's a table The core problem with table extraction is that "looks tabular" and "is tabular" are not the same thing.
A vector chart's axis box, a form's outlined signature field, and a two-column list of allergen names all produce something that a naive extractor will happily read as a grid.
None of them are tables.
Our pipeline handles this in four phases, all before anything is written to a spreadsheet: Phase 1: classify the page.
Every page is scored as bordered (has ruled lines or filled-rectangle grid lines), stream (no borders, but rows visibly span both sides of a column gap), columnar (independent parallel lists that never interact), or plain text.
Phase 2 and 3: run the right extractor for the layout.
Bordered pages go through Camelot's lattice mode.
Borderless but structured pages go through Camelot's stream mode.
Neither result is trusted blindly.
Phase 4: catch what the first pass missed, and gate everything.
This is the important part.
Every candidate table, whether it came from Camelot, from an explicit rectangle grid, or from our own zone-based word extraction, has to pass a classifier before it is counted as a table at all.
The classifier checks column count, row density, and how numeric or short the cell contents are.
A form field's outline box or a chart's axis lines will fail this check and get demoted to plain text instead of becoming a two-cell "table." Without this gate, Camelot's own false positives, grid-shaped chart elements, form outlines, would get counted as extracted tables.
With it, only genuine tables make it through.
Underneath all four phases sits a shared geometry layer that does the actual measuring: it finds page gutters by picking the split point that divides the text most evenly (not just the widest gap, which a table's own label-and-value gap can be wider than), clusters words into lines by their vertical center rather than their top or bottom edge (so bold headers and footnote superscripts don't throw off row grouping), strips dot leaders like "Section..........12" before measuring column gaps (leaders fill the whitespace a gap detector relies on), and merges a wrapped label back onto the row it belongs to before anything gets written out.
Tables that span a page break get stitched back together too.
If two adjacent pages produce tables with the same column count, and the second page's first row looks like a repeat of the first page's header (fuzzy-matched, so "Week No." and "Week Number" still count as the same header), they are joined into one