How pdf-inspector Detects Multi-Column Layouts: Newspaper vs. Tabular Reading Order Explained

pdf-inspector distinguishes between newspaper-style and tabular reading orders by analyzing horizontal projection histograms for column detection, pre-masking spanning lines, then classifying layouts based on vertical alignment patterns and line-grid presence.

The firecrawl/pdf-inspector Rust library extracts structured text from PDFs with intelligent layout understanding. Multi-column documents pose a fundamental challenge: should text flow top-to-bottom within each column (newspaper-style), or should rows be read across columns before moving down (tabular-style)? This article examines exactly how pdf-inspector solves this detection problem.

Understanding the Two Multi-Column Layout Types

pdf-inspector recognizes two distinct reading patterns that determine how extracted text is ordered:

Layout type Reading order Visual characteristics
Newspaper Sequential columns (top-to-bottom, left-to-right) Continuous text flow with irregular vertical alignment between columns
Tabular Interleaved rows (across columns, then down) Table-like structures with items sharing horizontal baselines or visible grid lines

The detection pipeline runs during layout extraction, primarily in src/extractor/layout.rs and src/extractor/reading_order.rs.

Stage 1: Column Detection via Horizontal Projection Histograms

The first step identifies column boundaries mathematically. In src/extractor/layout.rs, the detect_columns routine builds a horizontal projection histogram by counting text items at each x-coordinate.

Valleys in this histogram represent whitespace gaps between columns. The algorithm extracts x-bounds from these valleys to define column rectangles:

// Simplified from src/extractor/layout.rs
let histogram = build_horizontal_projection(&text_items);
let valleys   = find_valleys(&histogram);
let columns   = valleys.iter().map(|v| Column::from_valley(v)).collect();

Every TextItem is subsequently assigned to one of these detected columns based on its x-position.

Stage 2: Spanning-Line Pre-Masking

Headlines, captions, and other spanning elements would corrupt column detection if left unhandled. pdf-inspector implements pre-masking in pre_mask_spanning_lines within src/extractor/layout.rs:

if line.width > column_threshold {
    // Exclude from histogram to prevent false valleys
}

Lines exceeding a column-aware threshold (a fraction of page width) are temporarily removed from the histogram calculation. After column boundaries stabilize, these masked lines are re-inserted and assigned to appropriate columns.

Stage 3: Newspaper vs. Tabular Classification

The final decision occurs in src/extractor/reading_order.rs. The classify_page_reading_order function evaluates two primary signals:

Tabular detection triggers when:

  • Text baselines show strong vertical alignment across columns (same y-coordinates within tolerance)
  • Horizontal or vertical line primitives are detected (from src/tables/detect_lines.rs)

Newspaper fallback applies when neither condition is satisfied:

// Core logic in src/extractor/reading_order.rs
if has_strong_vertical_grid(&page_items) || has_aligned_rows(&page_items) {
    ReadingOrder::Tabular
} else {
    ReadingOrder::Newspaper
}

The classification directly impacts output: tabular documents interleave rows across columns, while newspaper documents concatenate full columns left-to-right.

Practical Implementation

Enable automatic reading order detection when processing PDFs:

use pdf_inspector::process_pdf_with_options;
use pdf_inspector::options::PdfOptions;

let options = PdfOptions::default()
    .with_reading_order(true);

let markdown = process_pdf_with_options("document.pdf", options)
    .expect("PDF processing failed");

// Output respects detected newspaper or tabular sequence
println!("{}", markdown);

The reading_order field in the returned structure exposes the detected classification for inspection or override.

Key Source Files

File Responsibility
src/extractor/layout.rs Histogram creation, valley detection, spanning-line masking
src/extractor/reading_order.rs Classification logic between Newspaper and Tabular modes
src/tables/detect_lines.rs Line primitive detection for grid-based tabular identification
src/markdown/convert.rs Applies determined reading order during Markdown emission

Summary

  • Horizontal projection histograms identify column boundaries by finding whitespace valleys in detect_columns
  • Spanning-line pre-masking prevents headlines from corrupting column detection via pre_mask_spanning_lines
  • Tabular classification depends on vertical alignment patterns and detected line grids in has_strong_vertical_grid and has_aligned_rows
  • Newspaper fallback provides the default sequential column reading when tabular signals are absent
  • The ReadingOrder enum propagates through src/markdown/convert.rs to ensure correct text sequencing

Frequently Asked Questions

How does pdf-inspector handle PDFs with mixed layout types on different pages?

pdf-inspector classifies each page independently through classify_page_reading_order. A document may contain newspaper-style content pages alongside tabular data pages, with each page receiving its appropriate reading order during the conversion pipeline.

What causes false tabular detection in otherwise normal documents?

Dense multi-column layouts with coincidental vertical alignment—such as bulleted lists or code blocks—can trigger tabular classification. The has_aligned_rows function uses tolerance thresholds, but highly regular spacing may still misclassify. Force newspaper mode via PdfOptions when needed.

Does pdf-inspector detect tables that lack visible grid lines?

Yes. The vertical alignment heuristic in has_aligned_rows detects tabular structure from baseline patterns alone, even without line primitives from src/tables/detect_lines.rs. This handles "invisible grid" tables common in academic papers.

Can I extract text without any reading order detection?

The with_reading_order(true) flag enables classification; omitting it or using with_reading_order(false) produces raw text extraction without column-aware ordering. However, multi-column PDFs will likely yield scrambled output as text items default to arrival order in the PDF content stream.

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 →