# How to Get OCR Recommendations Per Page with Reasons from pdf-inspector

> Get per-page OCR recommendations and reasons with pdf-inspector. Use extract_pages_markdown_mem or classify_pdf_mem for clear insights into OCR needs.

- Repository: [Firecrawl/pdf-inspector](https://github.com/firecrawl/pdf-inspector)
- Tags: how-to-guide
- Published: 2026-08-10

---

**Use `extract_pages_markdown_mem` or `classify_pdf_mem` from pdf-inspector to retrieve per-page OCR recommendations with human-readable reasons explaining why each page requires OCR processing.**

The `pdf-inspector` library (firecrawl/pdf-inspector) provides a Rust-based pipeline for analyzing PDFs and determining which pages need optical character recognition. Unlike simple OCR triggers, it exposes granular **OCR reasons**—machine-readable identifiers that explain exactly why a page failed text extraction. This article covers the public API, the four core OCR reason constants, and how to interpret per-page results.

## OCR Reason Constants in [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs)

The library defines four primary triggers in [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs) that classify pages requiring OCR:

- **`OCR_REASON_SUSPECTED_GARBLED_TEXT`** — Broken font decoding, CID garbage, or mojibake detected during text extraction (lines 105-109)
- **`OCR_REASON_SCANNED`** — The page is a full-page raster image, typically from document scanning (lines 110-112)
- **`OCR_REASON_NO_TEXT`** — No extractable text and no image content suitable for OCR (lines 113-115)
- **`OCR_REASON_VECTOR_TEXT`** — Text rendered as vector outlines without proper text operators (lines 117-119)

These constants feed into the `PageOcrReasons` struct, which pairs a **1-indexed page number** with a vector of applicable reasons.

## Per-Page OCR Result Structure

The `PageOcrReasons` struct (lines 25-32 of [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs)) provides the foundation for OCR reporting:

```rust
pub struct PageOcrReasons {
    pub page: usize,           // 1-indexed page number
    pub reasons: Vec<String>,  // Human-readable OCR triggers
}

```

Public API methods return `Vec<PageOcrReasons>` alongside extraction results, enabling downstream systems to log, filter, or route pages based on specific failure modes.

## Two API Patterns for OCR Recommendations

### Lightweight Classification with `classify_pdf_mem`

For scenarios where you only need OCR recommendations without full markdown extraction, use `classify_pdf_mem` (lines 89-101):

```rust
let pdf_bytes = std::fs::read("document.pdf")?;
let classification = pdf_inspector::classify_pdf_mem(&pdf_bytes)?;

// classification.pages_needing_ocr contains 0-indexed page indices
// classification also includes pdf_type and confidence score

```

This function loads the PDF from a byte slice, runs `detector::detect_from_document`, and converts the internal 1-indexed list to 0-indexed indices for caller convenience.

### Full Extraction with `extract_pages_markdown_mem`

The comprehensive method `extract_pages_markdown_mem` (starting at line 66) performs three signal analyses per page:

1. **Text-quality analysis** — `analyze_text_quality` and `detect_encoding_issues` flag garbled content
2. **Template-image detection** — `has_template_image` via `detector::page_ocr_signals` identifies scanned pages
3. **Vector-text detection** — `has_vector_text` via `page_ocr_signals` catches outline-based text

Each signal triggers `add_ocr_reason` to populate a per-page map, with `page_ocr_reason` building the final explanation list.

The method returns a `PagesExtractionResult` containing:

| Field | Description |
|-------|-------------|
| `pages` | `Vec<PageMarkdown>` with markdown content and `needs_ocr` boolean |
| `pages_needing_ocr` | 1-indexed list of pages requiring OCR |
| `ocr_reasons_by_page` | `Vec<PageOcrReasons>` with detailed explanations (lines 22-38) |

## Complete Working Example

```rust
use pdf_inspector::{classify_pdf_mem, extract_pages_markdown_mem};

fn main() -> Result<(), pdf_inspector::PdfError> {
    let bytes = std::fs::read("sample.pdf")?;

    // Approach 1: Lightweight OCR recommendation
    let class = classify_pdf_mem(&bytes)?;
    println!("PDF type: {:?}", class.pdf_type);
    println!("Pages needing OCR (0-based): {:?}", class.pages_needing_ocr);

    // Approach 2: Full extraction with per-page reasons
    let result = extract_pages_markdown_mem(&bytes, None)?;
    
    for page in result.pages {
        if page.needs_ocr {
            let reason = result
                .ocr_reasons_by_page
                .iter()
                .find(|r| r.page == page.page + 1)
                .map(|r| r.reasons.join(", "));
            
            println!("Page {} → OCR required: {}", 
                page.page + 1, 
                reason.unwrap_or("unknown"));
        } else {
            println!("Page {} extracted successfully", page.page + 1);
        }
    }
    Ok(())
}

```

Convenience wrappers `classify_pdf` and `extract_pages_markdown` accept file paths directly instead of byte slices.

## Key Source Files in the OCR Pipeline

| File | Purpose |
|------|---------|
| [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs) | Public API, OCR constants, `PageOcrReasons`, `classify_pdf_mem`, `extract_pages_markdown_mem` |
| [`src/detector.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/detector.rs) | PDF type detection, `pages_needing_ocr` computation, `page_ocr_signals` for image/vector detection |
| [`src/text_quality.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/text_quality.rs) | Text quality analysis, encoding issue detection |
| [`src/markdown/mod.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/markdown/mod.rs), [`src/markdown/convert.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/markdown/convert.rs) | Markdown conversion for extracted content |
| [`src/extractor/content_stream.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/extractor/content_stream.rs) | Low-level content stream parser, GID-encoded font tracking |

## Summary

- pdf-inspector provides **four specific OCR reasons** defined in [`src/lib.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/lib.rs) that explain why pages fail text extraction
- **`classify_pdf_mem`** offers lightweight OCR recommendations without full parsing
- **`extract_pages_markdown_mem`** returns per-page markdown, OCR flags, and detailed reason lists via `ocr_reasons_by_page`
- The `PageOcrReasons` struct uses **1-indexed page numbers** while API results expose **0-indexed indices** for consistency with external systems
- Three signal sources—text quality, template images, and vector text—drive all OCR decisions

## Frequently Asked Questions

### How do I map between 0-indexed and 1-indexed page numbers?

The `PageOcrReasons` struct stores 1-indexed page numbers (`page: usize`), but `classify_pdf_mem` returns `pages_needing_ocr` as 0-indexed indices for direct array access. When cross-referencing, add 1 to the API result or subtract 1 from `PageOcrReasons.page`.

### Can I get OCR recommendations without extracting markdown?

Yes. Use `classify_pdf_mem` for a lightweight scan that returns the PDF type, confidence score, and pages needing OCR without running the full markdown extraction pipeline. This is significantly faster for batch classification tasks.

### What causes `OCR_REASON_VECTOR_TEXT` to trigger?

This reason appears when text is drawn as vector outlines rather than proper PDF text operators. The library detects this via `has_vector_text` in `page_ocr_signals` (from [`src/detector.rs`](https://github.com/firecrawl/pdf-inspector/blob/main/src/detector.rs)), which examines content streams for path-based text rendering that bypasses standard text extraction.

### How does pdf-inspector distinguish scanned pages from regular images?

The `has_template_image` signal identifies large background images that cover most of a page, combined with minimal or no extractable text. This pattern indicates a scanned document where the visible content exists only as raster data, triggering `OCR_REASON_SCANNED` accordingly.