# PDF-Inspector OCR Reasons: Complete Guide to When and Why OCR Triggers

> Discover PDF-Inspector OCR reasons. Learn when and why OCR triggers for unreliable text extraction with our complete guide. Understand the 7 defined reasons for accurate document analysis.

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

---

**PDF-Inspector triggers OCR when native text extraction produces unreliable or unreadable characters, with seven specific OCR reasons defined in [`src/vision/pipeline.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/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`](https://github.com/firecrawl/pdf-inspector/blob/main/src/vision/pipeline.rs), while the public data structures are defined in [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/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

```rust
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

```python
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`](https://github.com/firecrawl/pdf-inspector/blob/main/src/bin/pdf2md.rs)) formats OCR reasons through the `format_ocr_reasons_by_page` function:

```bash
$ 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`](https://github.com/firecrawl/pdf-inspector/blob/main/src/vision/pipeline.rs) | OCR decision engine | Reason constants, detection logic |
| [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs) | Public API structures | `PageOcrReasons`, `PdfProcessResult` |
| [`src/bin/pdf2md.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/bin/pdf2md.rs) | Command-line interface | `format_ocr_reasons_by_page` |
| [`docs/ocr-runtime.md`](https://github.com/firecrawl/pdf-inspector/blob/main/docs/ocr-runtime.md) | Runtime behavior docs | Configuration, thresholds |

## How OCR Reasons Flow Through the Pipeline

1. **Extraction attempt:** Native text extraction runs on each page
2. **Quality analysis:** Pipeline evaluates character ratios, font mappings, and region geometry
3. **Reason accumulation:** Failing checks add corresponding OCR reasons to `PageOcrReasons`
4. **Decision point:** Pages with any reasons are queued for OCR processing
5. **Result assembly:** `PdfProcessResult` contains both extracted text and `ocr_reasons_by_page`

## Summary

- pdf-inspector defines **seven OCR reasons** in [`src/vision/pipeline.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/vision/pipeline.rs) that 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 `PageOcrReasons` struct in [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs) exposes 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`](https://github.com/firecrawl/pdf-inspector/blob/main/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.