PDF-Inspector OCR Reasons: Complete Guide to When and Why OCR Triggers
PDF-Inspector triggers OCR when native text extraction produces unreliable or unreadable characters, with seven specific OCR reasons defined in src/vision/pipeline.rs that determine per-page OCR necessity.
The pdf-inspector repository by Firecrawl provides intelligent PDF text extraction that automatically detects when Optical Character Recognition (OCR) is required. Understanding these OCR reasons helps developers debug extraction failures and optimize processing pipelines.
What Are OCR Reasons in PDF-Inspector?
OCR reasons are categorical flags that explain why a specific PDF page cannot be reliably processed through native text extraction alone. These reasons are collected in a Vec<PageOcrReasons> structure and exposed through both the Rust API and language bindings (Python, Node.js, WASM).
The decision logic resides in src/vision/pipeline.rs, while the public data structures are defined in src/lib.rs (PageOcrReasons at lines 128–149).
The Seven OCR Reasons Explained
pdf-inspector defines seven distinct OCR reasons as constants in the vision pipeline. Each reason corresponds to a specific failure mode in native PDF text extraction.
OCR_REASON_SUSPECTED_GARBLED_TEXT
Trigger: The decoded text contains a very low proportion of alphanumeric characters or excessive replacement symbols.
Cause: Custom CMaps, shifted-cipher fonts, or corrupted font encodings produce unreadable glyphs that look like text but aren't meaningful.
Example: A PDF using non-standard encoding where "Hello" appears as random Unicode private-use characters.
OCR_REASON_IDENTITY_H_NO_TOUU
Trigger: A font uses the Identity-H cmap without a ToUnicode CMap entry.
Cause: Identity-H maps glyph IDs directly to 16-bit Unicode values, but without ToUnicode, the extractor cannot reverse this mapping to obtain actual text.
Impact: Zero extractable text from affected regions, forcing OCR fallback.
OCR_REASON_SHIFTED_CIPHER
Trigger: Font character codes show systematic offset patterns (constant shift) producing nonsensical text.
Cause: Intentional or accidental glyph ID shifting in the font's encoding vector.
Detection: The extractor identifies systematic mismatch between expected and actual character values.
OCR_REASON_ZERO_AREA
Trigger: A detected text region has zero width or height.
Cause: Placeholder regions, malformed bounding boxes, or image-mounted text layers.
Behavior: Pipeline treats these as OCR candidates since they cannot contain genuine extractable text.
OCR_REASON_NON_EXISTENT_PAGE
Trigger: A region references a page number not present in the PDF's page tree.
Cause: Corrupted page tree structures or malformed PDFs with dangling references.
Response: OCR runs on the entire page to ensure no content is missed.
OCR_REASON_LOW_ALPHANUMERIC_RATIO
Trigger: Fewer than 50% of characters on a page are alphanumeric after initial scan.
Heuristic: Flags pages likely to be scanned images, diagrams, or heavily encoded content.
Threshold: The 50% ratio is configurable in the pipeline logic.
OCR_REASON_WATERMARK_ONLY
Trigger: Page consists solely of a raster watermark without extractable text elements.
Example: A "CONFIDENTIAL" stamp across an otherwise empty or image-based page.
Pipeline behavior: OCR is offered to recover any hidden or embed text beneath the watermark.
Accessing OCR Reasons in Code
Rust API
use pdf_inspector::{process_pdf_with_options, PdfOptions, OcrOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let pdf = std::fs::read("document.pdf")?;
let opts = PdfOptions::default()
.ocr(OcrOptions::new().auto()); // Enable auto-detect
let result = process_pdf_with_options(&pdf, opts)?;
println!("Pages needing OCR: {:?}", result.pages_needing_ocr);
for reason in &result.ocr_reasons_by_page {
println!("Page {}: {:?}", reason.page, reason.reasons);
}
Ok(())
}
Python Binding
from pdf_inspector import PdfOptions, OcrOptions, process_pdf
with open("scanned_document.pdf", "rb") as f:
pdf_bytes = f.read()
options = PdfOptions().ocr(OcrOptions().auto())
result = process_pdf(pdf_bytes, options)
print(f"Pages flagged: {result.pages_needing_ocr}")
for page_reasons in result.ocr_reasons_by_page:
print(f"Page {page_reasons.page}: {page_reasons.reasons}")
CLI Output
The pdf2md CLI tool (in src/bin/pdf2md.rs) formats OCR reasons through the format_ocr_reasons_by_page function:
$ pdf2md --ocr auto input.pdf
Processing page 1... OK
Processing page 2... OCR triggered [Suspected garbled native text, Missing ToUnicode]
Processing page 3... OK
Key Source Files
| File | Purpose | Key Elements |
|---|---|---|
src/vision/pipeline.rs |
OCR decision engine | Reason constants, detection logic |
src/lib.rs |
Public API structures | PageOcrReasons, PdfProcessResult |
src/bin/pdf2md.rs |
Command-line interface | format_ocr_reasons_by_page |
docs/ocr-runtime.md |
Runtime behavior docs | Configuration, thresholds |
How OCR Reasons Flow Through the Pipeline
- Extraction attempt: Native text extraction runs on each page
- Quality analysis: Pipeline evaluates character ratios, font mappings, and region geometry
- Reason accumulation: Failing checks add corresponding OCR reasons to
PageOcrReasons - Decision point: Pages with any reasons are queued for OCR processing
- Result assembly:
PdfProcessResultcontains both extracted text andocr_reasons_by_page
Summary
- pdf-inspector defines seven OCR reasons in
src/vision/pipeline.rsthat explain why native extraction fails - Reasons range from encoding problems (
OCR_REASON_IDENTITY_H_NO_TOUU,OCR_REASON_SHIFTED_CIPHER) to content heuristics (OCR_REASON_LOW_ALPHANUMERIC_RATIO,OCR_REASON_WATERMARK_ONLY) - The
PageOcrReasonsstruct insrc/lib.rsexposes these through multiple API layers - Auto-detect mode uses these reasons to minimize unnecessary OCR while ensuring text recovery
- Understanding these reasons helps optimize PDF processing pipelines and debug extraction quality issues
Frequently Asked Questions
How do I disable OCR entirely in pdf-inspector?
Pass OcrOptions::new().disabled() or equivalent in your language binding. The pipeline will skip OCR even if reasons are detected, though garbled text may result.
Can I customize the 50% alphanumeric threshold?
Not directly through the public API. The threshold is hardcoded in src/vision/pipeline.rs. Forking and modifying the constant requires recompilation.
What's the difference between OCR_REASON_SUSPECTED_GARBLED_TEXT and OCR_REASON_SHIFTED_CIPHER?
SUSPECTED_GARBLED_TEXT detects general low-quality output with many replacement characters, while SHIFTED_CIPHER specifically identifies systematic offset patterns in character codes. Both trigger OCR, but the latter indicates a specific encoding strategy.
Do OCR reasons appear in the final Markdown output?
No. OCR reasons are metadata in PdfProcessResult. They inform processing decisions but don't appear in extracted text. Log them separately for debugging or pipeline monitoring.
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 →