How Heuristic Table Detection Works in pdf‑inspector: A Deep Dive into Text‑Based PDF Table Extraction

Heuristic table detection in pdf‑inspector parses raw text items from PDF pages through a nine‑stage pipeline—including redline analysis, region localization, alignment validation, and multi‑layered rejection of false positives—to identify tables that lack explicit drawing primitives.

The pdf‑inspector library by Firecrawl extracts structured data from PDFs using three complementary strategies. When rect‑based (detect_rects.rs) and line‑based (detect_lines.rs) detectors fail to find explicit table boundaries, the system falls back to heuristic table detection. This approach operates entirely on text items, making it essential for scanned documents, redlined contracts, and PDFs with invisible table structures.

Core Pipeline: Nine Stages of Heuristic Detection

All heuristic logic resides in src/tables/detect_heuristic.rs. The detect_tables function orchestrates the following stages:

1. Text Pre‑processing: Merging and Expanding

Before analysis begins, raw PDF text items undergo normalization:

  • merge_adjacent_items_preserving — Combines single‑character items into complete words while preserving evidence flags
  • expand_consolidated_items — Splits financial notations like "$ 1,234 $ 5,678" into separate cells

These steps ensure that fragmented PDF text streams become semantically coherent units for downstream analysis.

2. Redline and Underline Analysis

PDFs with tracked changes (common in legal and financial documents) pose special challenges. The detector identifies edit‑track redline blocks—strike‑out plus underline combinations—using:

  • redline_edit_regions — Locates revision markup zones
  • underlined_table_columns — Builds column structures from underlined content representing revised table cells
  • is_heuristic_table_evidence — Filters items to exclude strike‑outs and hidden redline content unless they belong to legitimate revised cells

This prevents edit artifacts from being mistaken for table evidence.

3. Evidence Gathering and Propagation

Every text item receives an evidence flag determined by is_heuristic_table_evidence. During merges and expansions, these flags propagate through:

  • merge_map — Tracks evidence through item consolidation
  • expand_map — Preserves evidence through financial notation expansion

This ensures the final detection items retain accurate provenance information.

4. Two‑Pass Region Localization

The detector locates candidate table regions through complementary strategies:

Pass Function Characteristics
Small‑font find_table_regions Loose criteria, catches tables with reduced font sizes
Body‑font find_table_regions_strict Demands consistent X‑clusters across rows, higher precision

The body‑font pass can be disabled via the skip_body_font parameter when performance matters.

5. Geometry Extraction

Within each candidate region, detect_table_in_region invokes grid utilities from src/tables/grid.rs:

  • grid::find_column_boundaries — X‑coordinate clustering for column edges
  • grid::find_row_boundaries — Y‑coordinate analysis for row divisions

Non‑script items define the geometric skeleton of potential tables.

6. Alignment Validation

Column structure must pass check_column_alignment with mode‑dependent thresholds:

  • 0.5 minimum for small‑font regions (permissive)
  • 0.7 minimum for body‑font regions (strict)

This numerical score quantifies how consistently items align to detected column boundaries.

7. Nine‑Layer Table‑Level Validation

Lines 59‑124 of detect_table_in_region implement aggressive false‑positive rejection:

  1. Tiny numeric fragments — Rejects isolated numbers without context
  2. Missing first‑column content — Tables need leading edge content
  3. Insufficient multi‑column rows — Requires multiple populated columns
  4. Overly wide tables — Excludes page‑spanning layouts
  5. Key‑value layouts — Filters dictionary‑like structures
  6. Inconsistent column fills — Requires regular population patterns
  7. Lack of table‑like data — Content must resemble structured information
  8. Paragraph‑like patterns — Excludes flowing prose
  9. Inline‑leader indexes — Filters TOC‑style entries

The heuristic detector is deliberately conservative: it only reports tables when multiple independent checks agree.

8. Post‑Processing and Index Mapping

The final loop in detect_tables_with_page_width (lines 101‑113) maps cell indices back to original PDF text items, constructing the Table structure with complete provenance.

Using Heuristic Detection

CLI Invocation

The pdf2md binary automatically invokes heuristic detection when rect‑ and line‑based methods fail:


# Convert PDF to Markdown with automatic table detection

pdf2md --json financial-report.pdf > report.md

# JSON output includes table structures with row/column data

Library Integration

Explicit invocation through the public API:

use pdf_inspector::lib::process_pdf_with_options;
use pdf_inspector::types::ProcessOptions;

let pdf_bytes = std::fs::read("contract.pdf")?;
let opts = ProcessOptions {
    // Default enables all three detectors
    ..Default::default()
};
let result = process_pdf_with_options(&pdf_bytes, opts)?;

// Tables from all detection strategies aggregated
for table in result.tables {
    println!("Detected: {} rows × {} cols", 
             table.rows.len(), 
             table.columns.len());
}

Debugging and Extension

Access intermediate evidence for custom analysis:

use pdf_inspector::tables::detect_heuristic::{
    detect_tables_with_page_width, 
    redline_edit_regions
};

let items = /* TextItem collection from page */;
let redlines = redline_edit_regions(&items, page_width);

// Inspect redline regions affecting table detection
for region in redlines {
    println!("Redline: {:?} — excluded from evidence", region.bounds);
}

Architecture Integration

Heuristic detection fits into pdf‑inspector's layered extraction strategy:


┌─────────────────┐
│  Rect-based     │ ← detect_rects.rs
│  (fastest)      │    Explicit rectangle objects
├─────────────────┤
│  Line-based     │ ← detect_lines.rs
│  (explicit PDF  │    Drawn lines as table borders
│   graphics)     │
├─────────────────┤
│  HEURISTIC      │ ← detect_heuristic.rs
│  (text-based    │    Raw text analysis with
│   fallback)     │    multi-stage validation
└─────────────────┘

The process_pdf_with_options function in src/lib.rs orchestrates this cascade, returning aggregated results.

Key Source Files

File Purpose
src/tables/detect_heuristic.rs Core heuristic implementation (560+ lines of detection logic)
src/tables/grid.rs Column/row boundary algorithms shared across detectors
src/tables/detect_rects.rs Primary detector using PDF rectangle objects
src/tables/detect_lines.rs Secondary detector using PDF line operators
src/lib.rs Public API exposing detect_tables and processing pipelines
src/markdown/convert.rs Markdown serialization of detected Table structures

Summary

  • Heuristic table detection activates only when faster detectors fail, parsing raw PDF text items without relying on graphics primitives.
  • Nine processing stages include redline‑aware pre‑processing, dual‑pass region finding, geometric extraction, and conservative multi‑layer validation.
  • Conservative thresholds (0.5/0.7 alignment scores, nine validation checks) minimize false positives on formatted prose and redlined documents.
  • Full Rust API exposure allows programmatic invocation, debugging of evidence maps, and custom integration pipelines.

Frequently Asked Questions

How does pdf‑inspector handle PDFs with tracked changes (redlines)?

The redline_edit_regions and underlined_table_columns functions identify strike‑out and underline combinations as revision markup. Items inside redline zones are excluded from table evidence unless they belong to underlined revised cells. This prevents edit artifacts from triggering false table detection while preserving legitimate table revisions.

Can I disable the heuristic detector for faster processing?

Yes. The detect_tables function accepts a skip_body_font: bool parameter that bypasses the stricter body‑font pass. However, disabling heuristic detection entirely requires modifying ProcessOptions or calling lower‑level APIs directly—most users benefit from the automatic fallback behavior.

Why does heuristic detection sometimes miss tables I can see?

The pipeline is intentionally conservative. Validation checks reject tables with inconsistent column filling, paragraph‑like text flow, or key‑value layouts. To capture borderline cases, you can adjust alignment thresholds or bypass validation in a fork, though this increases false positives on prose documents.

How does pdf‑inspector compare heuristic results with rect‑ and line‑based detection?

All three detectors populate the same Table structure, which the orchestration layer in src/lib.rs aggregates. The Table type includes provenance metadata allowing consumers to identify which strategy detected each table. No deduplication occurs across strategies—downstream consumers should handle overlapping detections if needed.

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 →