# How does pdf-inspector detect PDF types (TextBased, Scanned, Mixed, ImageBased)

> Discover how pdf-inspector accurately detects PDF types TextBased, Scanned, Mixed, or ImageBased by analyzing content streams and applying a decision tree. Learn more now.

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

---

**pdf-inspector classifies PDF documents by sampling pages and analyzing content streams for text operators, images, and font metadata, then aggregates these metrics through a hierarchical decision tree to determine whether a file is TextBased, Scanned, Mixed, or ImageBased.**

The pdf-inspector library, developed by Firecrawl, determines document composition by inspecting internal PDF structure rather than relying on file metadata. Written in Rust, the tool parses content streams to identify extractable text, raster images, vector graphics, and font encoding capabilities, providing per-page OCR recommendations through its detection pipeline.

## The Three-Phase Detection Pipeline

The core detection engine resides in **[`src/detector.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/detector.rs)**, exposing functionality through **[`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs)** and the CLI binary at **[`src/bin/detect_pdf.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/bin/detect_pdf.rs)**. The classification process follows three distinct phases: page sampling, content analysis, and hierarchical classification.

### Phase 1: Intelligent Page Sampling

Rather than processing every page, pdf-inspector employs a configurable `ScanStrategy` to select representative pages. The default `Sample(8)` strategy distributes up to eight pages evenly across the document, always including the first and last page regardless of length. For a 100-page document, this typically selects indices such as `[1, 13, 25, 37, 49, 61, 73, 85, 100]`.

Available strategies include:

- **`Sample(N)`**: Analyze N evenly distributed pages (default)
- **`Full`**: Inspect every page in the document
- **`Pages([...])`**: Explicitly specify page numbers to analyze
- **`EarlyExit`**: Stop sampling early if initial pages show consistent patterns

### Phase 2: Content Stream Analysis

For each sampled page, the `analyze_page_content` function parses the PDF content stream and populates a `PageAnalysis` struct with granular metrics:

- **Text operators**: Counts `Tj` and `TJ` operators (text painting commands). A page qualifies as text-rich when `text_operator_count` meets or exceeds `min_text_ops_per_page` (default: 3).
- **Image detection**: Identifies `Do` operators (image drawing) and calculates whether a page contains a *template image*—a single large background image covering most of the page area.
- **Vector text detection**: Detects text rendered as vector paths rather than proper text operators. Pages exceeding 1,000 path operations with fewer than 30 unique alphanumeric characters trigger the `has_vector_text` flag.
- **Font analysis**: Resolves font references through PDF resource inheritance to determine encoding capabilities:
  - `has_identity_h_no_tounicode`: Identity-H/V fonts lacking ToUnicode CMaps and fallback decoding
  - `has_only_type3_fonts`: Documents using exclusively Type 3 fonts without Unicode mappings
  - `has_decodable_text_fonts`: Presence of at least one font capable of producing Unicode text via ToUnicode, standard encoding, or embedded CMaps

### Phase 3: Hierarchical Classification

The classifier aggregates per-page metrics into a `PdfTypeResult` (lines 10,030–10,335 in [`src/detector.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/detector.rs)) following this strict priority hierarchy:

1. **Template-image PDFs**: If `has_template_images` is true and `pages_with_text > 0`, classify as `Mixed` (OCR recommended to capture image context).
2. **Text-rich PDFs**: If the ratio of text pages to sampled pages exceeds `text_page_ratio_threshold` (default: 0.6), classify as `TextBased`.
3. **Pure image documents**: If `pages_with_text == 0` but images or vector text exist, classify as `Scanned` (raster images) or `ImageBased` (vector text).
4. **Mixed content**: Documents containing both text pages and non-text elements receive the `Mixed` classification.
5. **Fallback**: Edge cases default to `TextBased` to prevent unnecessary OCR processing.

After primary classification, a newspaper-layout heuristic may upgrade `ocr_recommended` for dense, multi-column layouts.

## Configuration and Customization

The `DetectionConfig` struct controls detection sensitivity:

```rust
pub struct DetectionConfig {
    pub strategy: ScanStrategy,          // Page selection method
    pub min_text_ops_per_page: u32,      // Minimum Tj/TJ ops for text page (default: 3)
    pub text_page_ratio_threshold: f32,  // Text page ratio for TextBased (default: 0.6)
}

```

Lowering `min_text_ops_per_page` captures sparse text layouts, while increasing `text_page_ratio_threshold` requires higher text density before labeling documents as text-based.

## Using the Library and CLI

### Rust API Integration

The public API in **[`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs)** provides `detect_pdf_type` for default configurations and `detect_pdf_type_with_config` for custom strategies.

Basic detection:

```rust
use pdf_inspector::detect_pdf_type;

let result = detect_pdf_type("document.pdf")?;
println!("Type: {:?}, Confidence: {}", result.pdf_type, result.confidence);

```

Custom configuration with specific page targeting:

```rust
use pdf_inspector::{detect_pdf_type_with_config, DetectionConfig, ScanStrategy};

let config = DetectionConfig {
    strategy: ScanStrategy::Pages(vec![1, 2, 3]),
    min_text_ops_per_page: 5,
    text_page_ratio_threshold: 0.7,
};

let result = detect_pdf_type_with_config("document.pdf", config)?;

```

Processing OCR recommendations:

```rust
if result.ocr_recommended {
    for page in &result.pages_needing_ocr {
        println!("Page {} needs OCR: {:?}", 
                 page, 
                 result.ocr_reasons_by_page[page]);
    }
}

```

### Command Line Interface

The binary at **[`src/bin/detect_pdf.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/bin/detect_pdf.rs)** supports both human-readable and JSON output:

```bash

# Basic detection

detect-pdf path/to/document.pdf

# JSON output for programmatic pipelines

detect-pdf --json path/to/document.pdf

# Analyze specific pages only

detect-pdf --strategy=pages=1,50,100 path/to/document.pdf

```

## Summary

- **pdf-inspector** detects PDF types by sampling content streams rather than parsing metadata, with core logic implemented in [`src/detector.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/detector.rs).
- The default **Sample(8)** strategy examines eight evenly distributed pages plus first and last pages, balancing speed and accuracy.
- Detection relies on counting **text operators** (`Tj`/`TJ`), identifying **images** and **template backgrounds**, and analyzing **font encoding capabilities** including Identity-H/V and Type 3 fonts.
- Documents classify as **TextBased** when ≥60% of sampled pages contain sufficient text operators; **Scanned** and **ImageBased** indicate image or vector-text dominance.
- The `PdfTypeResult` provides granular **OCR recommendations** at the page level via `pages_needing_ocr` and `ocr_reasons_by_page`, specifying exactly which pages require optical character recognition.

## Frequently Asked Questions

### How does pdf-inspector determine if a PDF needs OCR?

The library examines each sampled page for undecodable fonts, template images, and vector text rendering. Pages lacking extractable text operators containing sufficient alphanumeric characters, or those relying on fonts without ToUnicode CMaps, are added to `pages_needing_ocr` with specific explanations in `ocr_reasons_by_page`. For **Scanned** or **ImageBased** documents, the library typically recommends OCR for all pages, while **Mixed** documents receive selective page-level recommendations.

### What distinguishes Scanned from ImageBased PDF types?

**Scanned** documents consist primarily of raster images without text operators or vector text representations—typical of scanned paper documents. **ImageBased** documents contain vector graphics masquerading as text, specifically pages with extensive path operations (>1,000) but minimal alphanumeric content (<30 unique characters). Both types set `ocr_recommended: true`, but ImageBased indicates computer-generated vector art rather than photographed paper.

### Can I control how many pages pdf-inspector analyzes?

Yes, through the `DetectionConfig` struct's `strategy` field. Use `ScanStrategy::Sample(N)` to analyze N evenly distributed pages, `ScanStrategy::Full` to scan every page, or `ScanStrategy::Pages(vec![...])` to target specific page numbers. The CLI exposes these via the `--strategy` flag using syntax like `sample=8` or `pages=1,10,20`.

### Why does pdf-inspector flag some TextBased PDFs for OCR?

Even TextBased documents may require OCR for specific pages containing undecodable fonts. The detector identifies pages where `has_identity_h_no_tounicode` or `has_only_type3_fonts` prevent Unicode extraction, adding these to `pages_needing_ocr` despite the overall TextBased classification. This ensures no text loss when fonts lack proper encoding mappings or ToUnicode CMaps.