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:
- Text-quality analysis —
analyze_text_qualityanddetect_encoding_issuesflag garbled content - Template-image detection —
has_template_imageviadetector::page_ocr_signalsidentifies scanned pages - Vector-text detection —
has_vector_textviapage_ocr_signalscatches 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.rsthat explain why pages fail text extraction classify_pdf_memoffers lightweight OCR recommendations without full parsingextract_pages_markdown_memreturns per-page markdown, OCR flags, and detailed reason lists viaocr_reasons_by_page- The
PageOcrReasonsstruct 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →