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

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

The library defines four primary triggers in 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) provides the foundation for OCR reporting:

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):

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

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 Public API, OCR constants, PageOcrReasons, classify_pdf_mem, extract_pages_markdown_mem
src/detector.rs PDF type detection, pages_needing_ocr computation, page_ocr_signals for image/vector detection
src/text_quality.rs Text quality analysis, encoding issue detection
src/markdown/mod.rs, src/markdown/convert.rs Markdown conversion for extracted content
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 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), 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.

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 →