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

> Discover how heuristic table detection in pdf-inspector extracts tables from PDFs using text analysis. Learn about its nine-stage pipeline for accurate results.

- Repository: [Firecrawl/pdf-inspector](https://github.com/firecrawl/pdf-inspector)
- Tags: deep-dive
- Published: 2026-09-01

---

**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`](https://github.com/firecrawl/pdf-inspector/blob/main/detect_rects.rs)) and **line‑based** ([`detect_lines.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/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`](https://github.com/firecrawl/pdf-inspector/blob/main/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`](https://github.com/firecrawl/pdf-inspector/blob/main/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:

```bash

# 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:

```rust
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:

```rust
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`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs)** orchestrates this cascade, returning aggregated results.

## Key Source Files

| File | Purpose |
|------|---------|
| [`src/tables/detect_heuristic.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/tables/detect_heuristic.rs) | Core heuristic implementation (560+ lines of detection logic) |
| [`src/tables/grid.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/tables/grid.rs) | Column/row boundary algorithms shared across detectors |
| [`src/tables/detect_rects.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/tables/detect_rects.rs) | Primary detector using PDF rectangle objects |
| [`src/tables/detect_lines.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/tables/detect_lines.rs) | Secondary detector using PDF line operators |
| [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs) | Public API exposing `detect_tables` and processing pipelines |
| [`src/markdown/convert.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/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`](https://github.com/firecrawl/pdf-inspector/blob/main/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.