How pdf-inspector Detects Tables in PDFs: A Complete Technical Guide
pdf‑inspector uses three complementary detection pipelines—rectangle‑based, line‑based, and heuristic—tried in priority order until a valid table is found, with strict validation heuristics to filter false positives.
The firecrawl/pdf-inspector crate extracts tabular data from PDF documents by analyzing layout primitives including text items, rectangles, and vector drawing operators. Its detection strategy prioritizes explicit structural evidence over guesswork, falling back to increasingly speculative methods only when necessary.
The Three-Stage Detection Pipeline
The main entry point detect_tables() in src/lib.rs orchestrates the detection process. It attempts rectangle‑based detection first, then line‑based detection, and finally heuristic detection—returning the first valid table found.
// src/lib.rs (excerpt)
pub fn detect_tables(items: &[TextItem], page_width: f32, ocr: bool) -> Vec<Table> {
// 1️⃣ Try rect‑based detection
if let Some(rects) = extract_rects(items) {
if let Some(t) = detect_tables_from_rects(&items, &rects) {
return vec![t];
}
}
// 2️⃣ Try line‑based detection
if let Some(lines) = extract_path_lines(items) {
if let Some(t) = detect_tables_from_lines(&items, &lines) {
return vec![t];
}
}
// 3️⃣ Heuristic detection (merge chars, split financial, column‑based)
let heuristics = detect_tables(&items, page_width, ocr);
heuristics
}
Rectangle‑Based Detection
Rectangle‑based detection clusters axis‑aligned bounding boxes from the PDF's structure tree or OCR‑generated rectangles. This approach is implemented in src/tables/detect_rects.rs.
The algorithm uses a union‑find algorithm to group overlapping rectangles with a configurable tolerance:
- Extract
PdfRectobjects from the page's drawing operators - Cluster overlapping rects into connected components
- Filter components that meet minimum table thresholds
// Conceptual flow from detect_rects.rs
let components = union_find_cluster(rects, tolerance=2.0);
let table_candidates: Vec<Table> = components
.into_iter()
.filter(|c| c.rects.len() >= MIN_RECTS_FOR_TABLE)
.filter_map(|c| build_table_from_rect_grid(&items, &c))
.collect();
This method excels when PDFs contain explicit table structures from the document's internal representation.
Line‑Based Detection
When no structural rectangles exist, line‑based detection searches for explicit PDF path commands that draw horizontal and vertical rules. This is implemented in src/tables/detect_lines.rs.
The detector parses PDF drawing operators:
m— move tol— line toS— stroke path
Horizontal rules are merged via merge_horizontal_segments, split into runs, and assembled into a grid by snapping rule edges. Text items falling inside grid cells are assigned to corresponding rows and columns via assign_items_to_grid.
Heuristic Detection: The Fallback Arsenal
Heuristic detection in src/tables/detect_heuristic.rs handles cases without explicit graphics. It contains three sub‑strategies tried sequentially:
Text Merging and Column Detection
First, the detector merges adjacent glyph‑level items and optionally expands financial‑style consolidated items. It then uses the layout engine's column detection to infer column boundaries.
Key‑Value Table Detection
For two‑column "label‑value" tables (common in specification sheets), the detector:
- Groups text rows by Y‑coordinate
- Infers a vertical split (
split_x) from the biggest gap between word clusters - Applies label‑vs‑value guards including header inference and "EDGAR tag" handling
The functions try_build_key_value_table_from_rows() and infer_key_value_split_x() implement this logic.
Column‑Based Detection
When the layout engine has already discovered column regions (e.g., from newspaper‑style column detection), try_build_table_from_columns() builds a table directly from those column X‑ranges.
Validation Heuristics Prevent False Positives
Every candidate table undergoes strict validation in detect_heuristic.rs (lines 334–363, 386–441, 463–485, 509–525):
| Check | Threshold | Purpose |
|---|---|---|
| Fill‑rate | ≥15% cells non‑empty | Reject sparse layouts |
| Row span | ≥3 rows, ≥4 columns | Ensure table‑like dimensions |
| Prose detection | Cells <40 chars, minimal sentence punctuation | Exclude paragraph text |
| Column asymmetry | <60% cells in single column | Distinguish from newspaper layouts |
Failure on any check causes immediate rejection, ensuring the detector does not return false positives on multi‑column prose.
Table Construction and Classification
All pipelines return a Table struct defined in src/tables/mod.rs. The struct captures:
- Column X positions
- Row Y positions
- Cell matrix
- Classification via
TableKind
The Table::new() method calls is_table_of_contents(&cells) to distinguish data tables from tables of contents by scanning the first column for page‑number patterns.
// From src/tables/mod.rs
pub struct Table {
pub columns: Vec<f32>,
pub rows: Vec<f32>,
pub cells: Vec<Vec<Vec<TextItem>>>,
pub kind: TableKind,
}
pub enum TableKind {
Data, // Standard data table
Toc, // Table of contents
}
Practical Usage Examples
Extract Tables from a PDF (Rust)
use pdf_inspector::lib::{process_pdf_with_options, ExtractionOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let opts = ExtractionOptions {
extract_tables: true,
..Default::default()
};
let result = process_pdf_with_options("sample.pdf", &opts)?;
for table in result.tables {
println!("--- Table ({} columns × {} rows) ---",
table.columns.len(), table.rows.len());
println!("{}", pdf_inspector::tables::format::table_to_markdown(&table));
}
Ok(())
}
Direct Rectangle Detection (Python Bindings)
import pdf_inspector
items = pdf_inspector.extract_text_items("sample.pdf")
tables = pdf_inspector.tables.detect_tables_from_rects(
items,
pdf_inspector.tables.detect_rects.cluster_rects(...)
)
for t in tables:
print(pdf_inspector.tables.format.table_to_markdown(t))
Customize Detection Thresholds
use pdf_inspector::tables::detect_heuristic::detect_tables_with_page_width;
let custom_page_width = 595.0; // A4 width in points
let tables = detect_tables_with_page_width(&items, custom_page_width, false);
// The `ocr` flag adapts heuristics for OCR‑generated PDFs
Key Source Files
| File | Responsibility |
|---|---|
src/tables/mod.rs |
Public API, Table struct, formatting helpers |
src/tables/detect_rects.rs |
Rectangle clustering with union‑find |
src/tables/detect_lines.rs |
PDF path parsing, line merging, grid construction |
src/tables/detect_heuristic.rs |
Heuristic detection, validation, key‑value handling |
src/tables/grid.rs |
Boundary finding, cell assignment, header recovery |
src/tables/format.rs |
Markdown and JSON output conversion |
src/tables/detect_struct.rs |
Tagged PDF structure‑tree detection |
This architecture—explicit‑first with rigorous validation—makes pdf‑inspector robust across diverse PDF generation methods while maintaining low false‑positive rates.
Summary
- pdf‑inspector detects tables through three prioritized pipelines: rectangle‑based, line‑based, and heuristic
- Rectangle detection uses union‑find clustering of
PdfRectobjects from the PDF structure tree - Line detection parses PDF path commands (
m,l,S) to build explicit grids - Heuristic detection includes column‑based, key‑value, and text‑clustering strategies
- Strict validation enforces fill‑rate, dimensional, and prose constraints to eliminate false positives
- Table classification distinguishes data tables from tables of contents via content pattern analysis
Frequently Asked Questions
What makes pdf‑inspector's table detection different from other PDF parsers?
pdf‑inspector prioritizes explicit structural evidence (rectangles and drawing lines) before resorting to heuristics, and applies rigorous validation checks including fill‑rate thresholds and prose detection. This explicit‑first approach with multi‑layer validation reduces false positives compared to purely heuristic detectors.
How does pdf‑inspector handle tables without visible borders?
For borderless tables, pdf‑inspector falls back to heuristic detection in detect_heuristic.rs. It merges text items, infers column boundaries from X‑coordinate clustering, builds rows from Y‑coordinate clustering, and validates the candidate against strict heuristics before accepting it as a table.
Can pdf‑inspector detect financial tables with merged cells?
Yes. The heuristic detector includes optional financial‑style consolidated item expansion that handles merged cells typical in financial reports. The expand_financial_consolidated_items preprocessing step splits combined cell content before grid assignment.
What is the minimum table size pdf‑inspector will recognize?
The validation heuristics require at least 3 rows and 4 columns (columns.len() < 4 triggers rejection). Additionally, at least 15% of cells must contain content. These thresholds filter out incidental text alignments that do not constitute genuine data tables.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →