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_rects union-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 splittingsplit_wide_cluster divides 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 fallbackdetect_merged_cluster_table attempts 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

  1. Path parsing – Horizontal and vertical segments are extracted from m (move), l (line), and S (stroke) operators
  2. Horizontal mergingmerge_horizontal_segments combines collinear horizontal segments that share Y coordinates
  3. 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 tablesbuild_text_anchor_table infers column edges from anchored text row X-positions
  • Open-edge gridsderive_columns_from_horizontal_segments synthesizes column boundaries from horizontal line endpoints when vertical rules are absent
  • Dense-row anchorsbuild_dense_row_anchor_table handles 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 handlingredline_edit_regions identifies and excludes strikethrough/underline edit overlaps
  • Underline-based columnsunderlined_table_columns discovers columns with sufficient underlined items
  • Glyph mergingmerge_adjacent_items_preserving combines adjacent single-character items into words
  • Financial expansionexpand_consolidated_items splits 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 size
  • find_column_boundaries – Derives column edges from non-script item X-positions
  • find_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, and src/tables/detect_heuristic.rs respectively
  • Rectangle detection runs first and provides the fastest path for explicitly bordered tables, with cluster_rects and grid validation as core operations
  • Line detection serves as secondary fallback, using merge_horizontal_segments and 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →