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 documentPages([...]): Explicitly specify page numbers to analyzeEarlyExit: 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
TjandTJoperators (text painting commands). A page qualifies as text-rich whentext_operator_countmeets or exceedsmin_text_ops_per_page(default: 3). - Image detection: Identifies
Dooperators (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_textflag. - 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 decodinghas_only_type3_fonts: Documents using exclusively Type 3 fonts without Unicode mappingshas_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:
- Template-image PDFs: If
has_template_imagesis true andpages_with_text > 0, classify asMixed(OCR recommended to capture image context). - Text-rich PDFs: If the ratio of text pages to sampled pages exceeds
text_page_ratio_threshold(default: 0.6), classify asTextBased. - Pure image documents: If
pages_with_text == 0but images or vector text exist, classify asScanned(raster images) orImageBased(vector text). - Mixed content: Documents containing both text pages and non-text elements receive the
Mixedclassification. - Fallback: Edge cases default to
TextBasedto 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
PdfTypeResultprovides granular OCR recommendations at the page level viapages_needing_ocrandocr_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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →