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

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, exposing functionality through src/lib.rs and the CLI binary at 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) 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:

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 provides detect_pdf_type for default configurations and detect_pdf_type_with_config for custom strategies.

Basic detection:

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:

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:

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 supports both human-readable and JSON output:


# 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.
  • 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.

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 →