How pdf-inspector Handles Table Detection Strategies: Three Complementary Pipelines Explained
pdf-inspector uses three prioritized detection strategies—rectangle-based, line-based, and heuristic text-only analysis—to extract tables from PDFs, running them in sequence and falling back to later methods when earlier ones fail.
The firecrawl/pdf-inspector Rust library implements a multi-stage table detection system designed to handle the diverse ways PDFs encode tabular data. Unlike single-strategy approaches, pdf-inspector's architecture recognizes that tables can be rendered as vector rectangles, ruled lines, or even pure text alignment—and processes all three possibilities in a fixed priority order.
The Three Table Detection Strategies
pdf-inspector executes its detection pipelines in strict sequence. Each strategy is implemented in its own source module and contributes fallback hints to subsequent stages.
| Priority | Strategy | Source File | Input Signals |
|---|---|---|---|
| 1 | Rectangle-based detection | src/tables/detect_rects.rs |
PDF re (rectangle) operators |
| 2 | Line-based detection | src/tables/detect_lines.rs |
Path operators (m/l/S for lines) |
| 3 | Heuristic text-only detection | src/tables/detect_heuristic.rs |
TextItem positions and font properties |
A valid result from any stage can short-circuit later pipelines, though hint regions from failed stages improve downstream accuracy.
Rectangle-Based Table Detection
The fastest and most precise pipeline, rectangle-based detection in src/tables/detect_rects.rs exploits explicit table borders drawn as PDF rectangles.
Normalization and Clustering
Raw rectangles undergo preprocessing before analysis:
- Normalization – Negative widths/heights are flipped; tiny decorative rects and page-spanning backgrounds are filtered using
DOMINANT_PAGE_BACKGROUND_MIN_REPETITIONS - Spatial clustering – The
cluster_rectsunion-find structure merges overlapping rectangles (with tolerance) into component groups
Grid Construction and Validation
For each cluster containing ≥6 rectangles, detect_table_from_rect_group attempts full grid validation:
- Rectangles are assigned to table cells based on spatial intersection
- Valid grids require consistent row/column structure per
COMPETING_TABLE_MIN_ROWS
Fallback Mechanisms
When rectangle clusters fail strict validation, the pipeline provides intelligent recovery:
- Wide cluster splitting –
split_wide_clusterdivides clusters at the largest X-gap to isolate adjacent tables - Hint region generation – Failed rectangle groups pass boundary hints to the heuristic detector
- Merged cluster fallback –
detect_merged_cluster_tableattempts row-stripe detection across all clusters when individual groups yield only narrow tables
The MAX_CLUSTER_RECTS constant prevents runaway processing on vector-heavy pages.
Line-Based Table Detection
When rectangle detection fails or produces weak results, line-based detection in src/tables/detect_lines.rs analyzes explicit ruled borders constructed from PDF path operations.
Segment Extraction and Merging
- Path parsing – Horizontal and vertical segments are extracted from
m(move),l(line), andS(stroke) operators - Horizontal merging –
merge_horizontal_segmentscombines collinear horizontal segments that share Y coordinates - Rule formation – Merged segments become logical rules grouped by similar X-span via
group_rules_by_span
Independent Table Regions
Large vertical gaps or explicit "Table N" captions trigger split_independent_rule_runs, isolating separate tables before construction begins.
Multiple Construction Strategies
Each rule run attempts several specialized constructors:
- Text-anchor tables –
build_text_anchor_tableinfers column edges from anchored text row X-positions - Open-edge grids –
derive_columns_from_horizontal_segmentssynthesizes column boundaries from horizontal line endpoints when vertical rules are absent - Dense-row anchors –
build_dense_row_anchor_tablehandles tables with many vertical rules but sparse horizontals, using dense text rows as fallback anchors
Scoring and Selection
Candidate tables receive table_evidence_score ratings. The select_non_overlapping_hypotheses function retains the highest-scoring, mutually exclusive table set.
Heuristic Text-Only Table Detection
The final heuristic detection stage in src/tables/detect_heuristic.rs requires no vector graphics—only extracted text positions. This handles PDFs where tables exist purely as aligned text.
Preprocessing Pipeline
Before structural analysis, text items undergo extensive normalization:
- Redline handling –
redline_edit_regionsidentifies and excludes strikethrough/underline edit overlaps - Underline-based columns –
underlined_table_columnsdiscovers columns with sufficient underlined items - Glyph merging –
merge_adjacent_items_preservingcombines adjacent single-character items into words - Financial expansion –
expand_consolidated_itemssplits consolidated strings like "$ 1,234" into sub-items for proper column alignment
Region Discovery and Boundary Inference
Two complementary passes locate candidate table regions:
find_table_regions– Small-font pass for tables with reduced font sizefind_column_boundaries– Derives column edges from non-script item X-positionsfind_row_boundaries– Establishes row divisions from Y-position clustering
Structural Validation
Every candidate table must satisfy validation functions in the detection pipeline:
- Column count between 2–25
- Minimum 2 rows
- Column alignment consistency (
check_column_alignment) - Multi-column occupancy patterns
- Numeric-like content detection (
has_table_like_content) - Exclusion of key-value layouts (
is_key_value_layout,is_paragraph_layout) - Column consistency across rows (
has_consistent_columns)
Two-Pass Architecture
| Pass | Target | Font Size Window | Region Criteria |
|---|---|---|---|
| Small-font | Tables in reduced type | ≤ 0.9 × base size | Standard (find_table_regions) |
| Body-font | Primary content tables | 0.85–1.05 × base size | Strict (find_table_regions_strict) |
The body-font pass auto-skips pages where it would generate excessive false positives. Both passes map indices back to original TextItem positions before returning final Table structs.
Using pdf-inspector's Table Detection
Command-Line Extraction
# Extract PDF to Markdown with automatic table detection
pdf2md --json annual-report.pdf > annual-report.md
The pdf2md binary internally executes all three pipelines in priority order and merges non-conflicting results.
Rust Library Integration
use pdf_inspector::lib::{process_pdf_with_options, PdfOptions};
use pdf_inspector::types::Table;
fn extract_document(path: &str) -> Result<Vec<Table>, Box<dyn std::error::Error>> {
let options = PdfOptions::default();
let result = process_pdf_with_options(path, &options)?;
// Tables contain rows, columns, and cell content vectors
for table in &result.tables {
println!("Detected: {} rows × {} columns",
table.rows.len(),
table.columns.len()
);
}
Ok(result.tables)
}
Direct Strategy Invocation (Advanced)
use pdf_inspector::tables::{
detect_tables_from_rects,
detect_tables_from_lines,
detect_tables
};
// Strategy 1: Rectangle-based detection
let (rect_tables, rect_hints) = detect_tables_from_rects(
&text_items,
&pdf_rects,
page_number
);
// Strategy 2: Line-based detection
let line_tables = detect_tables_from_lines(
&text_items,
&pdf_lines,
page_number
);
// Strategy 3: Heuristic text-only detection
// rect_hints improves accuracy on hinted regions
let heuristic_tables = detect_tables(
&text_items,
base_font_size,
/*skip_body_font=*/ false
);
Key Source Files and Architecture
| File | Responsibility |
|---|---|
src/tables/detect_rects.rs |
cluster_rects, detect_table_from_rect_group, detect_merged_cluster_table |
src/tables/detect_lines.rs |
merge_horizontal_segments, build_text_anchor_table, table_evidence_score |
src/tables/detect_heuristic.rs |
expand_consolidated_items, find_column_boundaries, validation suite |
src/tables/grid.rs |
join_cell_items, boundary helpers, header recovery |
src/types.rs |
PdfRect, PdfLine, TextItem, Table struct definitions |
src/lib.rs |
process_pdf_with_options orchestration API |
docs/rust-api.md |
Library documentation for Rust consumers |
The modular architecture allows each detection strategy to evolve independently while sharing validated Table output structures.
Summary
- pdf-inspector implements three prioritized table detection strategies: rectangle-based, line-based, and heuristic text-only analysis, defined in
src/tables/detect_rects.rs,src/tables/detect_lines.rs, andsrc/tables/detect_heuristic.rsrespectively - Rectangle detection runs first and provides the fastest path for explicitly bordered tables, with
cluster_rectsand grid validation as core operations - Line detection serves as secondary fallback, using
merge_horizontal_segmentsand multiple constructor strategies (build_text_anchor_table,build_dense_row_anchor_table) for ruled tables - Heuristic detection handles pure text tables through two-pass analysis with preprocessing (
expand_consolidated_items,merge_adjacent_items_preserving) and strict validation (has_consistent_columns,is_key_value_layout) - Hint passing between stages improves overall accuracy: failed rectangle clusters guide heuristic region selection
- The public API (
process_pdf_with_options) abstracts pipeline orchestration, while individual strategies remain accessible for specialized use cases
Frequently Asked Questions
What order does pdf-inspector run its table detection strategies?
pdf-inspector always executes detection in fixed priority: rectangle-based first, line-based second, heuristic text-only last. This ordering prioritizes the most explicit visual signals (drawn rectangles and lines) before computationally intensive text analysis. Each stage can produce valid tables that prevent later pipelines from running, though hint regions improve downstream accuracy when early stages fail partially.
How does pdf-inspector handle tables without any borders or lines?
Tables rendered purely as aligned text trigger the heuristic detection pipeline in src/tables/detect_heuristic.rs. This strategy analyzes TextItem positions through glyph merging, financial string expansion, and column/row boundary inference. A two-pass approach (small-font then body-font) catches tables at different scales, while validation functions like check_column_alignment and has_table_like_content filter false positives from paragraph text.
Can I use only one detection strategy instead of all three?
Yes—individual strategies are directly callable through detect_tables_from_rects(), detect_tables_from_lines(), and detect_tables(). However, the full three-pipeline approach in process_pdf_with_options() is recommended for production use, as automatic fallback and hint passing between stages substantially improves extraction accuracy across diverse PDF sources.
Why does rectangle detection sometimes fail on obvious tables?
Rectangle detection fails when PDFs use line operators (m/l/S) rather than rectangle operators (re) to draw borders, or when decorative vector elements trigger filtering constants like DOMINANT_PAGE_BACKGROUND_MIN_REPETITIONS. The split_wide_cluster logic may also over-segment adjacent tables. These cases are intentionally delegated to line-based or heuristic detection, which handle border styles and explicit validation scoring that rectangles alone cannot express.
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 →