Recursive AI

OCR a PDF in C#

All oneocr recipes

Pass the PDF to PdfOcr.Read: each page comes back with its text, read with OCR where the page is a scan. The program below reads a scanned invoice, a PDF with no text layer that you can download to try it.

Terminal
dotnet new console -o OcrPdf && cd OcrPdf
dotnet add package RecursiveAI.OneOcr --version 1.0.0
dotnet add package RecursiveAI.OneOcr.Pdf --version 1.0.0
curl -O https://recursiveai.org/oneocr/samples/scanned-invoice.pdf

Put this in Program.cs and run dotnet run:

C#
using OneOcr;
using OneOcr.Pdf;
 
OcrEngine engine = OcrEngine.LoadBundled();
IReadOnlyList<OcrPage> pages = PdfOcr.Read("scanned-invoice.pdf", engine);
 
foreach (OcrPage page in pages)
{
    Console.WriteLine($"--- page {page.Index + 1}, {page.Words.Count} words");
    Console.WriteLine(page.ToText());
}
Text
--- page 1, 99 words
Northwind Supply Ltd
14 Harbour Street
BS1 4DJ Bristol
United Kingdom
VAT ID: GB284193752
INVOICE
Bill to
Blue Heron Bakery
- 88 Elm Road
OX1 2JD Oxford
United Kingdom
Invoice no.	INV-2041
Date	14/10/2026
Due date	13/11/2026
Order no.	PO-7731
Description	Qty	Unit price	VAT	Amount
Flour, 25 kg bag	4 pcs	31.50	0%	126.00
Cane sugar, 10 kg	2 pcs	18.25	0%	36.50
Baking parchment roll	6 pcs	7.40	20%	44.40
Subtotal
VAT total
Total
£206.90
£8.88
£215.78
Payment terms: Payable within 30 days
IBAN: GB33 BUKB 2020 1555 5555 55	BIC: BUKBGB22	Tel. +44 117 496 0321

The scan is a little tilted and grainy; the page was straightened before it was read, and one speck came through as the - before “88 Elm Road”. Each page is an OcrPage, the same model you get from an image, with coordinates in PDF points.

Pages with both text and scans

Many PDFs mix real text with scanned images. The text layer is used where it exists and only the images are read with OCR, so every word says where it came from. This expense report has a typed paragraph and a photo of a receipt:

Terminal
curl -O https://recursiveai.org/oneocr/samples/expense.pdf
C#
using OneOcr;
using OneOcr.Pdf;
 
OcrEngine engine = OcrEngine.LoadBundled();
OcrPage page = PdfOcr.Read("expense.pdf", engine)[0];
 
foreach (var group in page.Words.GroupBy(w => w.Source))
{
    string start = string.Join(" ", group.Take(7).Select(w => w.Text));
    Console.WriteLine($"{group.Key}: {group.Count()} words: {start} ...");
}
foreach (OcrDiagnostic d in page.Diagnostics)
    Console.WriteLine($"{d.Code}: {d.Message}");
Text
TextLayer: 26 words: Expense report EX-118 Submitted by Dana Whitfield ...
Ocr: 49 words: Corner Market 1402 Oak Street 78704 Austin ...
OCR301: 1 regions were read from pixels at 300 DPI (49 words).

TextLayer words are copied from the PDF; Ocr words were read from the image, rendered at 300 DPI. A text layer isn’t trusted blindly: an invisible layer left by an earlier OCR pass is read again, and garbled lines are checked against a render. PDF pages has the rules.

Options

new PdfOcrOptions { … } as the third argument sets RenderDpi (300), FirstPage and PageCount to read part of a file, Password for an encrypted PDF, and TrustExistingOcrLayer to keep an earlier OCR pass as it is. If you already have a PdfDocument open in OnePDF, doc.Ocr(engine) and doc.ToText(engine) do the same.

Limits

  • It reads; it doesn’t write. You get the text and the words with their positions, not a new searchable PDF.
  • Printed Latin, Greek and Cyrillic text. No CJK, Arabic or Indic scripts, and no handwriting, yet.
  • Text drawn as outlines and annotations aren’t read.

For a single image, see Extract text from an image. To pull fields out of a scanned invoice, see Extract data from an invoice.