Ruled lines are not table structure
TLDR
Lattice table extraction, the kind that rebuilds a grid from ruled-line intersections, treats ink as the source of truth for structure. Real tables encode some of their structure purely in whitespace, and financial documents do it constantly. If your extractor cannot split a column that no line ever drew, it will fuse labels with values and call it fidelity.
What the industry does
The dominant open-source pattern, popularized by camelot and inherited by most PDF table tools, splits extraction into two modes. "Lattice" rebuilds the grid from detected rules; "stream" infers structure from text alignment when there are no rules.
The split exists for a good reason: rules, when present, are close to ground truth, and alignment inference is noisy. So when a table has any rules at all, tools take the lattice path and trust it completely.
Why it fails for this problem class
Designers do not draw every boundary they mean. A balance sheet has horizontal rules and zebra fills, so it routes to lattice mode, but its value columns are separated from the label column by nothing except a gap and right-alignment.
Lattice mode rebuilds exactly what the lines encode. For our test case, an earnings-release balance sheet, that was 31 rows by 2 columns, with rows like:
| Cash and cash equivalents $ 78,779 | $ 86,810 |
The 2024 value fused into the label cell. Nothing errored; the extractor faithfully reproduced the ink and silently dropped a column that every human reader sees.
The better approach
Trust the lines for what they assert and treat consistent whitespace as an additional structural signal. After assigning text to line-derived cells, scan each column for a persistent empty x-channel, an interval crossed by zero items in every row. If one exists, wide enough and with content on both sides in enough rows, it is a column boundary the designer drew with alignment instead of ink. Split there and register a synthetic line so downstream colspan logic respects it.
The guards matter more than the scan. We require the channel to be interior, at least 16px wide, and both resulting columns occupied in at least 25% of rows. That last one stops a lone "$" prefix or a one-off aside from becoming its own column.
Evidence
The same table, same document, before and after the whitespace pass:
before: 31 rows, 2 cols | Cash and cash equivalents $ 78,779 | $ 86,810 |
after: 31 rows, 3 cols | Inventories | 34,214 | 38,325 |
Regression runs over four other table-heavy documents (a tax form, two table-sample PDFs, an invoice) produced identical table dimensions before and after, so the guards held: the pass fires only where a real channel exists.
What you give up
Determinism gets slightly softer. A table's column count now depends on its content layout, not only on its ink, so two visually identical grids with different data density can extract differently.
You also inherit a new failure mode: a spanning header row that crosses the channel suppresses the split. One table in the same document keeps six values fused in a cell for exactly this reason. I kept the guard anyway, because a missed split reads as awkward, while a false split corrupts data pairing.
When the common pattern is right
If your documents are engineered tables, database exports, LaTeX booktabs with full rules, invoices from a single template, pure lattice is simpler and its failure modes are visible. And if there are no rules at all, alignment inference should own the whole job rather than patching a grid.
The whitespace pass earns its complexity in the middle ground: designed documents where someone chose which boundaries to draw. Which is most documents people actually ask you to extract.