---
name: ironocr
description: >
  Read text, barcodes, QR codes and tables out of images and PDFs in C#/.NET using IronOCR
  (the `IronOcr` NuGet package, Tesseract 5). Use when the task involves OCR, scanned
  documents, extracting text from PNG/JPEG/TIFF/BMP or scanned PDFs, making a scanned PDF
  searchable, reading receipts, passports (MRZ) or licence plates, handwriting, barcode/QR
  decoding, per-word confidence scores, image pre-processing for OCR (deskew, denoise,
  binarize), or multi-language recognition — or whenever a project already references
  `IronOcr`, `IronTesseract`, `OcrInput`, or `OcrResult`.
---

# IronOCR (C# / .NET)

IronOCR is a tuned Tesseract 5 build with a .NET API. It runs **entirely locally** — no
cloud service, no data leaving the machine — over images, multi-frame TIFFs and PDFs, and
can write results back out as searchable PDF, hOCR/HTML, plain text or JSON.

## Scope of this skill

| | |
|---|---|
| Package | `IronOcr` (plus a platform variant, see Install) |
| Versions | 2024.x – 2026.x |
| Namespaces | `IronOcr`, `IronOcr.OcrResults` |
| Runtimes | .NET Framework 4.6.2+, .NET Standard 2.0+, .NET Core 2.0+, .NET 5–10 |
| Engine | Tesseract 5, 125 languages, plus ML models for photo/handwriting/document reads |

## Install — pick the package for the target platform

These are **alternative** packages, not additive: each carries the same managed assembly
with different native binaries. Reference exactly one, and keep every Iron package in the
solution on the same version.

| Target | Package |
|---|---|
| Windows (x64/x86) | `IronOcr` |
| Linux x64 (incl. Docker) | `IronOcr.Linux` |
| macOS | `IronOcr.MacOs` |

```bash
dotnet add package IronOcr           # or IronOcr.Linux / IronOcr.MacOs
```

**Languages.** English ships ready to use. Any other language needs its data pack:

```bash
dotnet add package IronOcr.Languages.German      # .Chinese, .Japanese, .Arabic, .French, ...
```

Then select it — the `OcrLanguage` enum has ~480 entries, most languages appearing in three
flavours: default, `...Fast` (quicker, less accurate) and `...Best` (slower, most accurate).

```csharp
ocr.Language = OcrLanguage.German;              // or GermanBest / GermanFast
ocr.AddSecondaryLanguage(OcrLanguage.English);  // mixed-language documents
```

`Installation.LanguagePackDirectory` overrides where the `.traineddata` files are looked up;
`ocr.UseCustomTesseractLanguageFile(path)` loads a custom-trained pack.

## Licensing — do this first, every time

```csharp
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY");
if (!IronOcr.License.IsLicensed)
    Console.Error.WriteLine("IronOCR is unlicensed — trial restrictions apply.");
```

Read the key from the environment or user secrets. **Never** inline it in source, commit it,
or echo it to the terminal.

**Treat a missing key as a blocker, not a warning.** Without one IronOCR runs under trial
restrictions, and once a trial grace period expires Iron libraries refuse to produce output
rather than degrading quietly. Licence keys are also product-scoped: a key issued for another
Iron product, or for a different product line, is rejected. If `IsLicensed` is false, say so
and ask the user for a key instead of handing over output they cannot use. Trial keys:
<https://ironsoftware.com/csharp/ocr/>. `License.IsValidLicense(key)` tests a key without
applying it; `Installation.LicenseKey` is a synonym for the setter.

## Running a one-off OCR task from the terminal

Requires only the .NET SDK. On **.NET 10+** a single file is the whole program:

```bash
cat > /tmp/ocr.cs <<'EOF'
#:package IronOcr@2026.7.2          # IronOcr.Linux / IronOcr.MacOs off Windows
#:property PublishAot=false
using IronOcr;
IronOcr.License.LicenseKey = Environment.GetEnvironmentVariable("IRONOCR_LICENSE_KEY");
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("scan.png");
Console.WriteLine(ocr.Read(input).Text);
EOF
dotnet run /tmp/ocr.cs
```

`#:property PublishAot=false` is **required**: .NET 10 file-based apps default to Native AOT,
and IronOCR needs runtime code generation. Without it you get
`PlatformNotSupportedException: Dynamic code generation is not supported on this platform`.

On older SDKs use a scratch project, and reuse the directory for later tasks — the NuGet
restore of native binaries is the slow part:

```bash
dotnet new console -o /tmp/ironocr-scratch && cd /tmp/ironocr-scratch
dotnet add package IronOcr
# write Program.cs, then:
dotnet run
```

When the user is building a feature rather than asking for a one-off result, write the same
calls into their application instead of a scratch project.

## Recipes

Every call below is verified against the shipped assembly. `OcrInput` is `IDisposable` —
always `using`, since it holds native image buffers.

### Read an image or a PDF

```csharp
var ocr = new IronTesseract();
using var input = new OcrInput();
input.LoadImage("receipt.png");                 // png/jpg/bmp/gif/tif; also byte[]/Stream/AnyBitmap
input.LoadPdf("statement.pdf", Password: "");   // rasterises every page, then reads
OcrResult result = ocr.Read(input);

Console.WriteLine(result.Text);                 // all text
Console.WriteLine(result.Confidence);           // 0–100, average
Console.WriteLine(result.Pages.Count);
```

Other loaders on `OcrInput`: `LoadImageFrame(path, frame)` and `LoadImageFrames(path,
int[])` for a specific TIFF frame, `AddMultiFrameTiff(path)` for every frame,
`LoadPdfPage(path, pageIndex)` / `LoadPdfPages(path, int[])` for selected pages,
`LoadScannedPdf(path, pageIndexes)` when the PDF is purely scanned images, and
`AddImage(path, region)` to read only part of an image.

Read just a region (coordinates in pixels):

```csharp
var region = new IronSoftware.Drawing.Rectangle(x: 50, y: 100, width: 400, height: 60);
using var cropped = new OcrInput("invoice.png", region);
string total = ocr.Read(cropped).Text;
```

Shorthand for a single file, no `OcrInput` needed: `ocr.Read("scan.png")` and
`ocr.Read("scan.png", region)`.

### Structured results

```csharp
foreach (var page in result.Pages)
{
    Console.WriteLine($"page {page.PageNumber}: {page.WordCount} words, {page.Confidence}%");
    foreach (var line in page.Lines)
        Console.WriteLine(line.Text);
    foreach (var word in page.Words)
        Console.WriteLine($"{word.Text} @ ({word.X},{word.Y}) {word.Width}x{word.Height} conf={word.Confidence}");
}
```

The hierarchy is `OcrResult` → `Pages` → `Blocks` → `Paragraphs` → `Lines` → `Words` →
`Characters`, and each element carries `Text`, `Confidence`, `X`, `Y`, `Width`, `Height`,
`Font` (name, size, bold/italic/serif flags). `result.ExtractTextFromPage(i)` pulls one
page. Filter low-confidence output rather than trusting everything: words below ~60 are
usually wrong.

### Save the result

```csharp
result.SaveAsSearchablePdf("searchable.pdf");   // invisible text layer over the original image
result.SaveAsTextFile("out.txt");
result.SaveAsHocrFile("out.hocr");              // + SaveAsHocrString()
result.SaveAsHtmlDocument("out.html", title: "Scan");   // title is required
result.SaveJsonAs("out.json");                  // + ToJson()
byte[] bytes = result.SaveAsSearchablePdfBytes();
```

One-liner for the most common request — make a scan searchable:

```csharp
ocr.ConvertToSearchablePdf("scan.pdf", "searchable.pdf");
```

### Barcodes, QR codes and tables

```csharp
ocr.Configuration.ReadBarCodes = true;
ocr.Configuration.ReadDataTables = true;
var r = ocr.Read(input);

foreach (var b in r.Barcodes)
    Console.WriteLine($"{b.Format}: {b.Value}");     // QRCode, Code128, EAN13, PDF417, DataMatrix, ...

foreach (var table in r.Pages[0].Tables)
{
    System.Data.DataTable dt = table.DataTable;      // rows and columns
}
```

### Bad scans: pre-process before reading

Filters mutate the loaded pages in place; apply the minimum that fixes the actual defect —
each one costs time and an unnecessary filter can make accuracy *worse*.

```csharp
using var input = new OcrInput();
input.LoadImage("crooked-fax.tif");
input.Deskew();                 // straighten a rotated/skewed scan (also HoughTransformStraighten)
input.DeNoise();                // remove digital noise / speckle (also Despeckle)
input.Binarize();               // hard black-and-white (also AdaptiveThreshold, ToGrayScale)
input.Contrast(1.5f);
input.Invert();                 // white-on-black text
input.Rotate(90);
input.Scale(200);               // percent; upscale small text
input.EnhanceResolution(300);   // raise effective DPI
input.Sharpen();
input.ReplaceColor(from, to, tolerance: 20);
input.SelectTextColor(color, tolerance: 20);   // isolate one ink colour
```

Diagnostics that help when accuracy is poor: `input.DetectPageOrientation(...)`,
`input.FindTextRegion()` / `FindMultipleTextRegions()` to crop to just the text,
`input.SaveAsImages("debug_*.png")` to see exactly what the engine is being fed, and
`input.PageCount()` (a method, not a property) / `input.Pages`.

### Specialised reads

```csharp
var passport = ocr.ReadPassport(input);          // MRZ fields
var plate    = ocr.ReadLicensePlate(input);
var hand     = ocr.ReadHandwriting(input);
var photo    = ocr.ReadPhoto(input, ModelType.Enhanced);   // phone photos, uneven lighting
var screen   = ocr.ReadScreenShot(input);
var doc      = ocr.ReadDocumentAdvanced(input, ModelType.Enhanced);  // ML document model
```

Each has an `...Async(input, timeoutMs)` counterpart. `ModelType.Enhanced` is slower and more
accurate than `ModelType.Normal`.

### Tuning and throughput

```csharp
ocr.MultiThreaded = true;                        // parallel pages — the big win on long documents
ocr.Configuration.PageSegmentationMode = TesseractPageSegmentationMode.SingleBlock;
ocr.Configuration.EngineMode = TesseractEngineMode.LstmOnly;
ocr.Configuration.WhiteListCharacters = "0123456789.,";     // e.g. amounts only
ocr.Configuration.BlackListCharacters = "|~";
ocr.Configuration.RenderSearchablePdf = true;    // needed before SaveAsSearchablePdf on some paths
ocr.Configuration.RenderHocr = true;             // needed before SaveAsHocr*
input.TargetDPI = 225;                           // PDF rasterisation DPI: 200–300 is the sweet spot
var r = await ocr.ReadAsync(input, timeoutMs: 60_000);
ocr.OcrProgress += (s, e) => { /* progress reporting */ };
```

Speed, in order of impact: crop to the region you actually need → `MultiThreaded = true` →
a `...Fast` language variant → fewer filters → sensible `TargetDPI` (higher is not better;
above ~300 you pay a lot for nothing).

## Working with IronPDF

If the project also references IronPDF, the two compose:

- **Scanned PDF → searchable PDF:** `input.LoadPdf(path)` → `ocr.Read(input)` →
  `result.SaveAsSearchablePdf(out)`. Afterwards `PdfDocument.FromFile(out).ExtractAllText()`
  returns real text (see the **ironpdf** skill).
- **PDF page → image → OCR:** `pdf.ToPngImages("p_*.png", DPI: 300)` then
  `input.LoadImage(...)` — useful when you only need one page or region.
- IronPDF's own `pdf.PerformOcr()` uses IronOCR when both packages are present.

Deciding which to use: if `ExtractAllText()` on a PDF returns empty or garbage, it is a scan
— OCR it. If it returns clean text, do **not** OCR; extraction is faster and exact.

## Deployment

| Environment | What to do |
|---|---|
| Docker / Linux | Use `IronOcr.Linux`. Either install the native deps in the image, or set `Installation.LinuxAndDockerDependenciesAutoConfig = true` (first run spends minutes on package installs and needs root — it fails in restricted containers, so baking deps into the image is more reliable). |
| Azure | Use a paid App Service tier or a container; give the process a writable temp directory. |
| AWS Lambda | Container image, ≥1 GB memory (the ML models for `Enhanced`/photo reads need more), writable `/tmp`. |
| Language packs | The `.traineddata` files must reach the deployed output. Verify they are copied, or point `Installation.LanguagePackDirectory` at where they actually live. |
| ML models | `Installation.MachineLearningModelsDirectory` for the photo/handwriting/document models. |
| Diagnostics | `Installation.LoggingMode = Installation.LoggingModes.File; Installation.LogFilePath = "ocr.log";` plus `ocr.EnableTesseractConsoleMessages = true`. |

## When something fails

| Symptom | Cause and fix |
|---|---|
| Empty or gibberish text | Wrong pre-processing. Check `input.SaveAsImages(...)`, then apply `Deskew()`, `Binarize()`, `Scale(200)`. Verify DPI: below ~150 the engine has nothing to work with. |
| Non-English text unreadable | Language pack missing. Install `IronOcr.Languages.<Name>` and set `ocr.Language`. |
| `DllNotFoundException` / native load error | Wrong platform package. Use `IronOcr.Linux` / `IronOcr.MacOs`, and only one of them. |
| `PlatformNotSupportedException: Dynamic code generation…` | Native AOT. Set `PublishAot=false` (or `#:property PublishAot=false` in a file-based app). |
| Very slow on a long PDF | `ocr.MultiThreaded = true`, drop `TargetDPI` to ~200, use a `...Fast` language, crop to the region of interest. |
| Searchable PDF has no text layer | Set `ocr.Configuration.RenderSearchablePdf = true` before `Read`. |
| Barcodes not detected | `ocr.Configuration.ReadBarCodes = true` — it is off by default. |
| Out of memory on a big TIFF/PDF | Read in page batches (`LoadPdfPages(path, new[]{0,1,2})`) and dispose each `OcrInput`. |

## Rules

- **Never invent a member.** Confirm against the XML documentation that ships in the package
  before writing code:
  `grep -o 'name="[MPF]:IronOcr\.[^"]*Searchable[^"]*"' ~/.nuget/packages/ironocr/<version>/lib/netstandard2.0/IronOcr.xml`
  That file is the authoritative surface for the installed version.
- Always `using` an `OcrInput` — it owns native memory.
- Keep licence keys out of source and out of terminal output.
- Say when results are trial-restricted, and report confidence scores rather than presenting
  OCR output as certain — OCR is probabilistic, and the user needs to know what to check.
- Don't OCR a PDF that already has a text layer.
- Apply the fewest filters that fix the actual defect.
- Official docs and full API reference: <https://ironsoftware.com/csharp/ocr/docs/>. Support:
  support@ironsoftware.com.
