# Core Document Links
Source: https://docs.pdf4llm.com/core-docs
This page cites the Read The Docs documentation guides for MuPDF software.
## PyMuPDF
[https://pymupdf.readthedocs.io](https://pymupdf.readthedocs.io)
## MuPDF.NET
[https://mupdfnet.readthedocs.io](https://mupdfnet.readthedocs.io)
## MuPDF.js
[https://mupdfjs.readthedocs.io](https://mupdfjs.readthedocs.io)
## MuPDF
[https://mupdf.readthedocs.io](https://mupdf.readthedocs.io)
# API
Source: https://docs.pdf4llm.com/dotnet/api/index
Complete reference for all PDF4LLM methods and types.
## Extraction methods
The three primary extraction methods share a common interface — they all accept a file path string or an open `MuPDF.NET.Document`, support the `pages` parameter for partial extraction, and return a string you can write directly to disk or pass downstream.
Extract content as a GitHub-compatible Markdown string. The primary method for LLM ingestion and RAG pipelines. Supports image extraction, OCR, and per-page output via `LlamaMarkdownReader`.
Extract content as structured JSON with bounding boxes and layout data for every block on the page. Use for custom pipelines, positional filtering, and debugging extraction output.
Extract content as plain text, stripped of all Markdown syntax. Use for search indexing, NLP pipelines, and systems that render Markdown literally.
***
## Layout and structure methods
Analyse the visual layout of a document and return a typed `ParsedDocument` object — pages, text blocks, tables, and image regions — with bounding boxes and reading order. The in-process equivalent of `ToJson()`.
Extract all interactive AcroForm field names, values, and page locations from a PDF. Use for structured data extraction from filled-in forms.
***
## Reader types
A LlamaIndex-compatible document reader. Created via `PdfExtractor.LlamaMarkdownReader()`. Loads a PDF and returns one `LlamaDocument` per page, each with Markdown text and metadata including page number and source file path.
***
## Return types
Typed .NET object returned by `ParseDocument()`. Contains a list of `ParsedPage` objects, each with its blocks, tables, images, and dimensions.
Represents a single AcroForm field returned by `GetKeyValues()`. Exposes `Name`, `Value`, and `Page` properties.
***
## Quick reference
| Method / Type | Returns | Key parameters |
| ------------------------------------ | --------------------- | --------------------------------------------------------------------------- |
| `PdfExtractor.ToMarkdown()` | `string` | `pages`, `writeImages`, `embedImages`, `useOcr`, `ocrLanguage`, `forceText` |
| `PdfExtractor.ToJson()` | `string` (JSON) | `pages`, `showProgress` |
| `PdfExtractor.ToText()` | `string` | `pages`, `useOcr`, `ocrLanguage`, `forceText` |
| `PdfExtractor.ParseDocument()` | `ParsedDocument` | `pages`, `useOcr`, `ocrLanguage` |
| `PdfExtractor.GetKeyValues()` | `List` | `doc` only — no `pages` parameter |
| `PdfExtractor.LlamaMarkdownReader()` | `PDFMarkdownReader` | — |
| `PDFMarkdownReader.LoadData()` | `List` | `filePath`, `extraInfo` |
# FAQ
Source: https://docs.pdf4llm.com/dotnet/getting-started/faq/index
Common questions about the `PDF4LLM` package for .NET.
## How do I install PDF4LLM for .NET?
Install via the .NET CLI:
```bash theme={null}
dotnet add package PDF4LLM
```
Or via the Visual Studio Package Manager Console:
```powershell theme={null}
Install-Package PDF4LLM
```
Or by adding a `PackageReference` directly to your `.csproj`:
```xml theme={null}
```
`MuPDF.NET` is installed automatically as a dependency — you do not need to add it separately.
## Which .NET targets are supported?
PDF4LLM targets .NET Standard 2.0, making it compatible with any framework that implements that standard:
| Target framework | Supported |
| -------------------- | --------- |
| .NET 8.0 | ✓ |
| .NET 7.0 | ✓ |
| .NET 6.0 | ✓ |
| .NET 5.0 | ✓ |
| .NET Standard 2.0 | ✓ |
| .NET Framework 4.8 | ✓ |
| .NET Framework 4.7.2 | ✓ |
| .NET Framework 4.6.1 | ✓ |
## How do I verify my installation?
Add a `using` directive and call `ToMarkdown` on any PDF:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
string markdown = PdfExtractor.ToMarkdown("document.pdf");
Console.WriteLine(markdown);
```
If this prints Markdown to the console, everything is wired up correctly.
## I'm seeing an "assembly with the same simple name" conflict. How do I fix it?
Your installed version of `MuPDF.NET` already bundles PDF4LLM internally. Having both packages referenced simultaneously causes the conflict.
Remove the explicit `PDF4LLM` package reference and rely on the bundled version:
```bash theme={null}
dotnet remove package PDF4LLM
```
Or remove the line from your `.csproj` manually:
```xml theme={null}
```
The API is identical either way — `using PDF4LLM;` and `PdfExtractor.*` work regardless of which package supplies the assembly.
> A future release of MuPDF.NET will stop bundling PDF4LLM, allowing both packages to coexist without conflict.
## How do I convert a PDF to Markdown?
Open a `Document` and pass it to `PdfExtractor.ToMarkdown()`:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
Document doc = new Document("my-document.pdf");
string markdown = PdfExtractor.ToMarkdown(doc);
doc.Close();
Console.WriteLine(markdown);
```
To save the result to a file:
```csharp theme={null}
File.WriteAllText("output.md", markdown, System.Text.Encoding.UTF8);
```
## What output formats are supported?
There are three extraction methods on `PdfExtractor`, all sharing a consistent interface:
| Method | Returns | Best for |
| -------------- | --------------- | ----------------------------------------------------------- |
| `ToMarkdown()` | `string` | LLM ingestion and RAG pipelines |
| `ToJson()` | `string` (JSON) | Custom pipelines needing bounding boxes and layout metadata |
| `ToText()` | `string` | Search indexing and NLP preprocessing |
```csharp theme={null}
string markdown = PdfExtractor.ToMarkdown(doc);
string json = PdfExtractor.ToJson(doc);
string text = PdfExtractor.ToText(doc);
```
## How do I extract only specific pages?
Pass a zero-based list of page indices to the `pages` parameter. This works across all three extraction methods:
```csharp theme={null}
string markdown = PdfExtractor.ToMarkdown(
doc,
pages: new List { 0, 1, 2 }
);
```
Page numbers are zero-indexed — page 1 of the document is `0`, page 2 is `1`, and so on.
## What document formats are supported as input?
Standard formats — PDF, XPS, EPUB, MOBI, and more — are supported out of the box.
See the [Supported Formats guide](/dotnet/getting-started/supported-formats) for a full list of supported input and output formats.
## How do I analyse visual layout regions (columns, figures, sidebars)?
Use `PdfExtractor.ParseDocument()`. It analyses the document and returns a typed `ParsedDocument` object containing a list of `ParsedPage` objects, each with its detected blocks, tables, images, and bounding boxes in reading order.
```csharp theme={null}
ParsedDocument parsed = PdfExtractor.ParseDocument(doc);
foreach (var page in parsed.Pages)
{
Console.WriteLine($"Page {page.Number}: {page.Blocks.Count} blocks");
}
```
## How do I extract AcroForm field values from a filled PDF?
Use `PdfExtractor.GetKeyValues()`. It returns a `List`, each with `Name`, `Value`, and `Page` properties:
```csharp theme={null}
List fields = PdfExtractor.GetKeyValues(doc);
foreach (var field in fields)
{
Console.WriteLine($"{field.Name} (page {field.Page}): {field.Value}");
}
```
> Note: `GetKeyValues()` does not accept a `pages` parameter — it always processes the full document.
## Does it handle scanned or image-based PDFs?
Yes, via Tesseract OCR. Unlike the Python library, OCR is **not** triggered automatically — you must opt in with `useOcr: true`:
```csharp theme={null}
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true);
```
The same flag works across all three extraction methods:
```csharp theme={null}
string text = PdfExtractor.ToText(doc, useOcr: true);
ParsedDocument parsed = PdfExtractor.ParseDocument(doc, useOcr: true);
```
OCR output goes through the same layout analysis as native text, so reading order, heading detection, and table detection all apply.
## How do I install Tesseract for OCR?
Tesseract must be installed on the host system and available on the `PATH`. PDF4LLM does not bundle it.
**Windows** — Download the installer from [UB Mannheim Tesseract builds](https://github.com/UB-Mannheim/tesseract/wiki) and add the install directory (e.g. `C:\Program Files\Tesseract-OCR`) to your `PATH`.
**macOS**
```bash theme={null}
brew install tesseract
```
**Linux (Debian / Ubuntu)**
```bash theme={null}
sudo apt-get install tesseract-ocr
```
Verify Tesseract is reachable from the application's environment:
```bash theme={null}
tesseract --version
```
If Tesseract is installed but not on the `PATH`, you will get a `TesseractNotFoundException` at runtime.
## How do I use OCR with a non-English language?
Pass a Tesseract language code to `ocrLanguage`. The default is `"eng"`. Combine multiple languages with a `+`:
```csharp theme={null}
// Single language
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true, ocrLanguage: "fra");
// Multiple languages mixed on the same pages
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true, ocrLanguage: "eng+deu");
```
The corresponding Tesseract language packs must be installed on your system first. On Debian/Ubuntu:
```bash theme={null}
sudo apt-get install tesseract-ocr-fra tesseract-ocr-deu
```
Common language codes: `eng` (English), `fra` (French), `deu` (German), `spa` (Spanish), `jpn` (Japanese), `chi_sim` (Simplified Chinese), `chi_tra` (Traditional Chinese).
## How do I handle documents that mix scanned and native text pages?
Use a per-page native probe to identify which pages need OCR, then extract each set separately:
```csharp theme={null}
var scannedPages = new List();
for (int i = 0; i < doc.PageCount; i++)
{
string native = PdfExtractor.ToText(doc, pages: new List { i });
if (native.Trim().Length < 50)
scannedPages.Add(i);
}
string ocrMarkdown = scannedPages.Count > 0
? PdfExtractor.ToMarkdown(doc, pages: scannedPages, useOcr: true)
: string.Empty;
var nativePages = Enumerable.Range(0, doc.PageCount).Except(scannedPages).ToList();
string nativeMarkdown = nativePages.Count > 0
? PdfExtractor.ToMarkdown(doc, pages: nativePages)
: string.Empty;
```
Adjust the character threshold (`< 50`) to suit your documents — pages with only a page number or short heading will score low.
## How do I run OCR in a Docker container?
Add Tesseract to your `Dockerfile`:
```dockerfile theme={null}
FROM mcr.microsoft.com/dotnet/runtime:8.0
RUN apt-get update && apt-get install -y \
tesseract-ocr \
tesseract-ocr-eng \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
```
Verify Tesseract is on the `PATH` inside the built image:
```bash theme={null}
docker run --rm your-image tesseract --version
```
If you install language data to a custom location, set `TESSDATA_PREFIX` explicitly:
```dockerfile theme={null}
ENV TESSDATA_PREFIX=/usr/share/tesseract-ocr/4.00/tessdata/
```
## Does it integrate with LlamaIndex?
Yes. Use `PdfExtractor.LlamaMarkdownReader()` to get a `PDFMarkdownReader` instance, then call `LoadData()` to get one `LlamaDocument` per page with Markdown text and metadata:
```csharp theme={null}
var reader = PdfExtractor.LlamaMarkdownReader();
var pages = reader.LoadData("product-manual.pdf");
foreach (var page in pages)
{
int pageNum = (int)page.ExtraInfo["page"];
string filePath = (string)page.ExtraInfo["file_path"];
Console.WriteLine($"Page {pageNum}: {page.Text.Length} chars");
}
```
## How do I use PDF4LLM with Azure OpenAI?
Install the Azure OpenAI SDK alongside PDF4LLM:
```bash theme={null}
dotnet add package PDF4LLM
dotnet add package Azure.AI.OpenAI
```
Extract Markdown and pass it to a chat completion for summarisation or Q\&A:
```csharp theme={null}
using Azure;
using Azure.AI.OpenAI;
using MuPDF.NET;
using PDF4LLM;
AzureOpenAIClient client = new(
new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!),
new AzureKeyCredential(Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY")!)
);
Document doc = new Document("briefing.pdf");
string markdown = PdfExtractor.ToMarkdown(doc);
doc.Close();
var chatClient = client.GetChatClient("gpt-4o");
var result = await chatClient.CompleteChatAsync(
[
new SystemChatMessage("You are a precise document summariser."),
new UserChatMessage($"Summarise this document in five bullet points:\n\n{markdown}")
]);
Console.WriteLine(result.Value.Content[0].Text);
```
For RAG pipelines, embed per-page chunks using `LlamaMarkdownReader` and `text-embedding-3-small`, then retrieve by cosine similarity before passing context to a chat model. See the [Azure OpenAI integration guide](https://docs.pdf4llm.com/dotnet/integrations/azure) for full patterns including batched embedding, parallel OCR, multimodal image input, and Managed Identity auth.
## How do I use Managed Identity instead of an API key for Azure OpenAI?
Replace `AzureKeyCredential` with `DefaultAzureCredential`:
```csharp theme={null}
using Azure.Identity;
AzureOpenAIClient client = new(
new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!),
new DefaultAzureCredential()
);
```
`DefaultAzureCredential` works in Azure App Service, Azure Functions, AKS, and other managed environments. For local development, run `az login` first, or use `AzureCliCredential` explicitly.
Assign the `Cognitive Services OpenAI User` role to the managed identity in the Azure Portal or via the CLI:
```bash theme={null}
az role assignment create \
--role "Cognitive Services OpenAI User" \
--assignee \
--scope /subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts/
```
# Installation
Source: https://docs.pdf4llm.com/dotnet/getting-started/installation/index
Install PDF4LLM via NuGet, understand the MuPDF.NET dependency, and resolve the common assembly conflict.
# Installation
## NuGet package
```bash theme={null}
dotnet add package PDF4LLM
```
Or via the Visual Studio Package Manager Console:
```powershell theme={null}
Install-Package PDF4LLM
```
Or by adding the package reference directly to your `.csproj`:
```xml theme={null}
```
***
## Dependencies
PDF4LLM depends on [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) and lists it as a NuGet dependency. It is installed automatically — you do not need to add MuPDF.NET separately.
After installation, two native DLLs are required at runtime:
| File | Contents |
| ----------------- | ------------------------------------- |
| `mupdfcpp64.dll` | The MuPDF C library with C++ bindings |
| `mupdfcsharp.dll` | The C# bindings for MuPDF |
The NuGet package copies these to your project's output directory on build. If your deployment environment does not allow automatic DLL placement, both files must be present in the same directory as your application executable, or on a path accessible via the `PATH` environment variable.
***
## Supported targets
| Target framework | Supported |
| -------------------- | --------- |
| .NET 8.0 | ✓ |
| .NET 7.0 | ✓ |
| .NET 6.0 | ✓ |
| .NET 5.0 | ✓ |
| .NET Standard 2.0 | ✓ |
| .NET Framework 4.8 | ✓ |
| .NET Framework 4.7.2 | ✓ |
| .NET Framework 4.6.1 | ✓ |
PDF4LLM targets .NET Standard 2.0, making it compatible with any framework that implements that standard.
***
## Verify the installation
Add a `using` directive and call `ToMarkdown` on any PDF to confirm everything is wired up:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
string markdown = PdfExtractor.ToMarkdown("document.pdf");
Console.WriteLine(markdown);
```
***
## Resolving the assembly conflict
If you see this error at build or runtime:
```
An assembly with the same simple name 'PDF4LLM' has already been imported
```
Your installed version of MuPDF.NET already bundles PDF4LLM internally. Having both packages referenced simultaneously causes the conflict.
**Fix:** Remove the explicit `PDF4LLM` package reference and rely on the bundled version:
```bash theme={null}
dotnet remove package PDF4LLM
```
Or remove the line from your `.csproj` manually:
```xml theme={null}
```
The API is identical either way — `using PDF4LLM;` and `PdfExtractor.*` work regardless of which package supplies the assembly.
A future release of MuPDF.NET will stop bundling PDF4LLM, allowing both packages to coexist without conflict. Until then, use one or the other.
***
## Optional: OCR support
OCR is not required for most PDFs. Enable it only when working with scanned documents or pages that contain no selectable text.
OCR requires [Tesseract](https://github.com/tesseract-ocr/tesseract) to be installed on the host system and available on the `PATH`. PDF4LLM does not bundle Tesseract.
**Windows** — Download the installer from [UB Mannheim Tesseract builds](https://github.com/UB-Mannheim/tesseract/wiki) and add the install directory to your `PATH`.
**macOS**
```bash theme={null}
brew install tesseract
```
**Linux (Debian / Ubuntu)**
```bash theme={null}
sudo apt-get install tesseract-ocr
```
Verify Tesseract is reachable:
```bash theme={null}
tesseract --version
```
See the [OCR guide](/dotnet/guides/OCR) for language pack installation and usage.
# Quickstart
Source: https://docs.pdf4llm.com/dotnet/getting-started/quickstart/index
Go from zero to a working PDF-to-Markdown conversion in under five minutes.
# Quickstart
This page gets you from a blank terminal to a working PDF extraction in as few steps as possible. No prior knowledge of MuPDF.NET required.
***
## 1. Create a project
```bash theme={null}
dotnet new console -n Pdf4LlmDemo
cd Pdf4LlmDemo
```
***
## 2. Install PDF4LLM
```bash theme={null}
dotnet add package PDF4LLM
```
***
## 3. Add a PDF
Copy any PDF into the project folder and note its filename. If you don't have one to hand, download a sample:
```bash theme={null}
curl -o sample.pdf https://www.w3.org/WAI/WCAG21/wcag-2.1.pdf
```
***
## 4. Convert to Markdown
Replace the contents of `Program.cs` with:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
Document doc = new Document("sample.pdf");
string markdown = PdfExtractor.ToMarkdown(doc);
doc.Close();
Console.WriteLine(markdown);
```
Run it:
```bash theme={null}
dotnet run
```
You should see the PDF content printed to the console as Markdown — headings prefixed with `#`, tables as pipe syntax, bold and italic preserved.
***
## 5. Save the output
Write the result to a file instead of printing:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
using System.IO;
Document doc = new Document("sample.pdf");
string markdown = PdfExtractor.ToMarkdown(doc);
doc.Close();
File.WriteAllText("output.md", markdown, System.Text.Encoding.UTF8);
Console.WriteLine("Saved to output.md");
```
***
## 6. Try the other output formats
Switch the extraction method to see different representations of the same document.
**Plain text** — same layout analysis, no Markdown syntax:
```csharp theme={null}
string text = PdfExtractor.ToText(doc);
```
**JSON** — full layout structure with bounding boxes and block types:
```csharp theme={null}
string json = PdfExtractor.ToJson(doc);
File.WriteAllText("layout.json", json, System.Text.Encoding.UTF8);
```
***
## 7. Extract specific pages
Pass a zero-based list to process only the pages you need:
```csharp theme={null}
string markdown = PdfExtractor.ToMarkdown(
doc,
pages: new List { 0, 1, 2 }
);
```
***
## You're up and running
That's the core loop: open a `Document`, call an extractor method, close the document. Everything else — OCR, image extraction, LlamaIndex loading, form fields — builds on this pattern.
# Supported Formats
Source: https://docs.pdf4llm.com/dotnet/getting-started/supported-formats/index
Input formats MuPDF.NET can read, and output formats it can produce.
## Input Formats
MuPDF.NET can open and extract content from the following document types:
| Format | Extensions | Notes |
| ----------- | ------------------------ | --------------------------------------------- |
| PDF | `.pdf` | All versions, including encrypted and scanned |
| XPS | `.xps` | Microsoft XML Paper Specification |
| eBooks | `.epub`, `.mobi`, `.fb2` | Reflowable content is linearised per chapter |
| Comic Books | `.cbz` | Image-based pages; OCR recommended |
***
## Output Formats
MuPDF.NET can produce output in four formats depending on your use case:
| Format | Function | Best For |
| ---------- | ------------------------------- | ------------------------------------------------------- |
| Markdown | `ToMarkdown()` | LLM ingestion, RAG pipelines, readable docs |
| JSON | `ToJson()` | Custom pipelines needing bounding boxes and layout data |
| Plain Text | `ToText()` | Simple text extraction, search indexing |
| Images | `ToMarkdown(writeImages: true)` | Preserving figures, charts, and diagrams |
### Markdown
The default and most commonly used output format. Text is extracted in reading order with headings, lists, tables, and inline formatting preserved where detectable.
```csharp theme={null}
string mdText = PdfExtractor.ToMarkdown("document.pdf");
```
### JSON
Returns structured data including bounding boxes, font information, and layout metadata for every block on the page. Useful for building custom post-processing pipelines.
```csharp theme={null}
string json_output = PdfExtractor.ToJson("document.pdf");
```
### Plain Text
Strips all formatting and returns raw text content. Ideal when downstream tools do not need Markdown syntax.
```csharp theme={null}
string text = PdfExtractor.ToText("document.pdf");
```
### Images
When `writeImages: true` is passed to `ToMarkdown()`, embedded images and graphics are extracted and saved to disk. Image paths are referenced inline in the Markdown output.
```csharp theme={null}
string mdText = PdfExtractor.ToMarkdown("document.pdf", writeImages: true, imagePath: "images/");
```
***
## Next Steps
Full walkthrough of `ToMarkdown()` with common options.
Controlling image extraction, DPI, and output path.
# OCR
Source: https://docs.pdf4llm.com/dotnet/guides/OCR/index
Use Tesseract OCR to extract text from scanned PDFs, image-based pages, and documents where native text selection returns nothing useful.
# Overview
Most PDFs contain selectable text — characters stored as font glyphs with known positions. PDF4LLM extracts these directly, without OCR, at high speed.
Some PDFs don't. A document scanned on a photocopier, a fax saved as PDF, or a report exported from a system that rasterises each page before writing it — these contain no machine-readable text at all. Every page is just an image. Native extraction returns empty strings.
For these documents, PDF4LLM can invoke Tesseract OCR before running layout analysis. Pass `useOcr: true` to any extraction method and Tesseract will read the text from each page image before it is converted to Markdown, plain text, or JSON.
***
## Prerequisites
OCR requires Tesseract to be installed on the host system and available on the `PATH`. PDF4LLM does not bundle Tesseract.
### Windows
Download and run the installer from the [UB Mannheim Tesseract builds](https://github.com/UB-Mannheim/tesseract/wiki) — the most actively maintained Windows distribution. During installation, select any additional language packs you need.
After installation, add the Tesseract directory to your `PATH`:
```
C:\Program Files\Tesseract-OCR
```
Verify the installation:
```powershell theme={null}
tesseract --version
```
### macOS
```bash theme={null}
brew install tesseract
# With additional language packs
brew install tesseract-lang
```
### Linux (Debian / Ubuntu)
```bash theme={null}
sudo apt-get install tesseract-ocr
# Add language packs individually
sudo apt-get install tesseract-ocr-fra # French
sudo apt-get install tesseract-ocr-deu # German
sudo apt-get install tesseract-ocr-chi-sim # Simplified Chinese
```
### Verify Tesseract is on the PATH
PDF4LLM calls Tesseract as a subprocess. If Tesseract is installed but not on the `PATH`, you will get a `TesseractNotFoundException` at runtime. Confirm it is reachable from the process running your application:
```bash theme={null}
tesseract --version
# Should print: tesseract x.x.x
```
If running under a service account or in a Docker container, ensure the Tesseract binary is accessible from the application's environment — not just your interactive shell.
***
## Basic usage
Pass `useOcr: true` to `ToMarkdown`, `ToText`, or `ParseDocument`:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
Document doc = new Document("scanned-report.pdf");
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true);
doc.Close();
```
The same flag works across all three extraction methods:
```csharp theme={null}
// Markdown
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true);
// Plain text
string text = PdfExtractor.ToText(doc, useOcr: true);
// Structured object model
ParsedDocument parsed = PdfExtractor.ParseDocument(doc, useOcr: true);
```
OCR output goes through the same layout analysis as native text — reading order, heading detection, table detection — before being converted to your chosen format.
***
## Specifying a language
Tesseract uses language-specific data files to improve recognition accuracy. The default is English (`"eng"`). Pass a Tesseract language code to `ocrLanguage` for other languages:
```csharp theme={null}
// French
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true, ocrLanguage: "fra");
// German
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true, ocrLanguage: "deu");
// Japanese
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true, ocrLanguage: "jpn");
// Simplified Chinese
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true, ocrLanguage: "chi_sim");
// Traditional Chinese
string markdown = PdfExtractor.ToMarkdown(doc, useOcr: true, ocrLanguage: "chi_tra");
```
The language data file for each language must be installed on the host system. If the language pack is missing, Tesseract will fall back to English or throw an error depending on the version.
### Common Tesseract language codes
| Language | Code | Language | Code |
| ---------- | ----- | ------------------- | --------- |
| English | `eng` | Russian | `rus` |
| French | `fra` | Arabic | `ara` |
| German | `deu` | Hindi | `hin` |
| Spanish | `spa` | Japanese | `jpn` |
| Italian | `ita` | Korean | `kor` |
| Portuguese | `por` | Simplified Chinese | `chi_sim` |
| Dutch | `nld` | Traditional Chinese | `chi_tra` |
| Polish | `pol` | Turkish | `tur` |
The full list of available language codes is maintained in the [Tesseract documentation](https://tesseract-ocr.github.io/tessdoc/Data-Files-in-different-versions.html).
### Multi-language documents
If a document contains text in more than one language on the same page, pass a `+`-separated list of language codes:
```csharp theme={null}
// English and French mixed on the same page
string markdown = PdfExtractor.ToMarkdown(
doc,
useOcr: true,
ocrLanguage: "eng+fra"
);
```
Using multiple languages increases recognition time and can reduce accuracy when the languages use very different character sets. Use multi-language mode only when the document genuinely mixes languages on the same page. For documents where different pages use different languages, restrict OCR to each language on its own set of pages using the `pages` parameter.
***
## Performance
OCR is significantly slower than native text extraction. Tesseract rasterises each page to an image and runs a trained neural network over it — this typically takes 1–5 seconds per page depending on page dimensions, resolution, and hardware, compared to milliseconds for native extraction.
For a 200-page scanned document, OCR may take 5–15 minutes. Plan for this in your pipeline.
### Process only the pages that need OCR
The most impactful optimisation is to apply OCR only to pages that actually need it. Many documents are partially scanned — a cover page or appendix may be a rasterised image while the body contains selectable text.
Identify scanned pages by checking whether native extraction returns useful content:
```csharp theme={null}
Document doc = new Document("mixed-document.pdf");
var scannedPages = new List();
for (int i = 0; i < doc.PageCount; i++)
{
// Quick native probe — fast, no OCR
string native = PdfExtractor.ToText(doc, pages: new List { i });
// If the page has very little native text, it is likely a scanned image
if (native.Trim().Length < 50)
scannedPages.Add(i);
}
// Run OCR only on the pages that need it
string ocrMarkdown = scannedPages.Count > 0
? PdfExtractor.ToMarkdown(doc, pages: scannedPages, useOcr: true)
: string.Empty;
// Run native extraction on the remaining pages
var nativePages = Enumerable.Range(0, doc.PageCount)
.Except(scannedPages)
.ToList();
string nativeMarkdown = nativePages.Count > 0
? PdfExtractor.ToMarkdown(doc, pages: nativePages)
: string.Empty;
doc.Close();
```
Adjust the character threshold (`< 50`) to your documents. Pages with only a page number or a short chapter title will score low on native extraction — tune conservatively to avoid classifying lightly-populated text pages as scanned.
### Process pages in parallel
For large all-scanned documents, parallelise across pages. Each call to `ToMarkdown` with a single-page `pages` list is independent:
```csharp theme={null}
Document doc = new Document("large-scanned.pdf");
int pageCount = doc.PageCount;
var results = new string[pageCount];
await Parallel.ForEachAsync(
Enumerable.Range(0, pageCount),
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
async (pageIndex, ct) =>
{
results[pageIndex] = PdfExtractor.ToMarkdown(
doc,
pages: new List { pageIndex },
useOcr: true,
ocrLanguage: "eng"
);
}
);
doc.Close();
string fullMarkdown = string.Join("\n\n---\n\n", results);
```
Confirm whether your version of MuPDF.NET supports concurrent access to a shared `Document` object before using this pattern. If it does not, open a separate `Document` per task using the file path overload to avoid race conditions.
### Show progress for long runs
For documents where OCR will take a noticeable amount of time, enable progress reporting:
```csharp theme={null}
string markdown = PdfExtractor.ToMarkdown(
doc,
useOcr: true,
showProgress: true
);
```
***
## Mixed documents
A mixed document contains both selectable text pages and scanned image pages. The pattern below produces a single Markdown string in page order, using native extraction where possible and OCR where not:
```csharp theme={null}
Document doc = new Document("mixed.pdf");
var markdownParts = new List<(int Page, string Content)>();
for (int i = 0; i < doc.PageCount; i++)
{
string native = PdfExtractor.ToText(doc, pages: new List { i });
string content = native.Trim().Length >= 50
? PdfExtractor.ToMarkdown(doc, pages: new List { i })
: PdfExtractor.ToMarkdown(doc, pages: new List { i }, useOcr: true);
markdownParts.Add((i, content));
}
doc.Close();
string fullMarkdown = string.Join(
"\n\n---\n\n",
markdownParts.OrderBy(p => p.Page).Select(p => p.Content)
);
```
This calls `ToText` once per page as a cheap probe — native extraction is fast — then calls `ToMarkdown` with the appropriate setting. The total cost is one native-speed pass over every page plus OCR only on the pages that require it.
***
## OCR accuracy
Tesseract accuracy depends heavily on the quality of the input image. Several factors affect results.
**Resolution** — Tesseract is trained on 300 DPI images. Scans below 200 DPI produce noticeably worse results, especially for small or condensed text. If you control the scanning process, scan at 300 DPI minimum.
**Skew** — Pages rotated even a few degrees during scanning significantly reduce accuracy. Most modern scanners de-skew automatically; if yours doesn't, apply de-skewing pre-processing before extraction.
**Noise and artefacts** — Coffee stains, smudges, fax compression artefacts, and paper grain all reduce accuracy. These cannot be corrected within PDF4LLM. Apply image pre-processing — binarisation, noise removal, contrast enhancement — to extracted page images before passing them to Tesseract if accuracy is critical for your use case.
**Font type** — Tesseract performs best on standard serif and sans-serif fonts. Handwriting, decorative fonts, and highly stylised typefaces are recognised poorly and should not be expected to produce reliable output.
**Language selection** — Using the wrong language model reduces accuracy even for text that looks superficially similar between languages. Always set `ocrLanguage` to match the document language.
### Diagnosing poor accuracy
Use `ToText` with OCR to inspect raw recognition output without the added complexity of Markdown formatting:
```csharp theme={null}
string rawOcrText = PdfExtractor.ToText(
doc,
pages: new List { suspectPageIndex },
useOcr: true
);
Console.WriteLine(rawOcrText);
```
Common misrecognition patterns and their likely causes:
| What you see | Likely cause |
| ---------------------------- | ----------------------------------------------------- |
| `l` / `1` / `I` confusion | Low resolution or thin font strokes |
| `0` / `O` confusion | Low resolution or sans-serif font at small size |
| Missing spaces between words | Page DPI below 200 or high background noise |
| Garbled non-Latin characters | Wrong `ocrLanguage` or missing language pack |
| Entire paragraphs absent | Region classified as an image, not a text block |
| Correct words in wrong order | Multi-column layout not linearised correctly post-OCR |
***
## OCR in containerised environments
When running PDF4LLM in Docker or a CI pipeline, Tesseract must be present in the container image. Add it to your `Dockerfile`:
```dockerfile theme={null}
FROM mcr.microsoft.com/dotnet/runtime:8.0
# Install Tesseract and the English language data pack
RUN apt-get update && apt-get install -y \
tesseract-ocr \
tesseract-ocr-eng \
&& rm -rf /var/lib/apt/lists/*
# Add additional language packs as needed
# RUN apt-get install -y tesseract-ocr-fra tesseract-ocr-deu
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
```
Verify Tesseract is on the PATH inside the built image:
```bash theme={null}
docker run --rm your-image tesseract --version
```
### Tessdata path
Tesseract looks for language data files in the directory specified by the `TESSDATA_PREFIX` environment variable, or in the default system location (`/usr/share/tesseract-ocr/*/tessdata/` on Debian/Ubuntu). If you install language data files to a custom location, set the variable explicitly in your `Dockerfile`:
```dockerfile theme={null}
ENV TESSDATA_PREFIX=/usr/share/tesseract-ocr/4.00/tessdata/
```
***
## Troubleshooting
**`TesseractNotFoundException` at runtime**
Tesseract is not on the `PATH` for the process running your application. Verify with `tesseract --version` in the same environment — not just your interactive shell. In Docker, check with `docker run --rm your-image tesseract --version`.
**Empty or near-empty output despite `useOcr: true`**
The pages may already contain selectable text that is being extracted natively without invoking OCR. Run `ToText` without `useOcr` first and check whether content is returned. If OCR is invoked but still returns nothing, the scan DPI is likely very low — check the source image resolution.
**Garbled or nonsensical text**
The most common cause is a mismatched `ocrLanguage`. Confirm the document language and set the correct Tesseract code. For non-Latin scripts (Arabic, CJK, Devanagari), ensure the appropriate language pack is installed and the correct code is used.
**OCR is extremely slow**
Processing time scales linearly with page count. Use the page-filtering pattern to restrict OCR to only scanned pages. For bulk pipelines, distribute work across multiple workers rather than processing large documents serially.
**Tables in scanned documents are not being detected**
Table detection from OCR output relies on the spatial alignment of recognised character positions, which is less reliable than detecting tables in native PDF text. For scanned documents with critical table data, inspect `ToJson` output to see how the blocks were classified, and consider building a custom table renderer from `ParseDocument` for these cases.
**Language pack missing error from Tesseract**
Install the required pack for your platform. Debian/Ubuntu: `sudo apt-get install tesseract-ocr-{code}`. macOS: `brew install tesseract-lang`. Windows: re-run the Tesseract installer and select the language from the component list.
***
## Next steps
Table extraction explained.
Process only specific pages to speed up OCR-heavy documents.
Install Tesseract and the OCR optional dependency.
Extract embedded images alongside OCR'd text.
# Tesseract Language Packs
Source: https://docs.pdf4llm.com/dotnet/guides/OCR/tesseract-language-packs
How to install additional Tesseract language packs on Windows, macOS, and Linux for use with PDF4LLM OCR.
## Overview
Tesseract identifies languages using three-letter [ISO 639-2](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes) codes. English (`eng`) is installed by default on most platforms. For any other language, you need to install the corresponding language pack before PDF4LLM can use it for OCR.
A full list of supported language codes is available on the [Tesseract tessdata repository](https://github.com/tesseract-ocr/tessdata).
To see which languages are already installed on your system, run `tesseract --list-langs` in your terminal.
***
## Windows
### During installation (recommended)
The Tesseract Windows installer from [UB Mannheim](https://github.com/UB-Mannheim/tesseract/wiki) lets you select additional language packs during setup. When you reach the **Choose Components** screen, expand **Additional language data** and tick the languages you need.
### After installation (manual)
If Tesseract is already installed, download language packs manually:
1. Go to [github.com/tesseract-ocr/tessdata](https://github.com/tesseract-ocr/tessdata)
2. Download the `.traineddata` file for your language (e.g. `fra.traineddata` for French)
3. Copy the file into your Tesseract `tessdata` folder, typically:
```
C:\Program Files\Tesseract-OCR\tessdata\
```
The Chocolatey (`choco install tesseract`) package only includes English. All additional languages must be added manually using the steps above.
### Verify the install
Open Command Prompt or PowerShell and run:
```powershell theme={null}
tesseract --list-langs
```
Your newly installed language should appear in the output.
***
## Linux
Language pack installation varies slightly by distribution.
```bash theme={null}
# List all available language packs
apt-cache search tesseract-ocr
# Install a specific language (e.g. German)
sudo apt install tesseract-ocr-deu
# Install all available languages at once
sudo apt install tesseract-ocr-all
```
Language packages follow the naming pattern `tesseract-ocr-`, for example `tesseract-ocr-fra` for French or `tesseract-ocr-chi-sim` for Simplified Chinese.
```bash theme={null}
# Search for available language packs
dnf search tesseract
# Install a specific language (e.g. German)
sudo dnf install tesseract-langpack-deu
# Install all language packs
sudo dnf install tesseract-langpack-*
```
On Fedora, packages are named `tesseract-langpack-`.
```bash theme={null}
# Search for available language packs
pacman -Ss tesseract-data
# Install a specific language (e.g. German)
sudo pacman -S tesseract-data-deu
```
On Arch, packages are named `tesseract-data-`.
### Manual installation (all distros)
If a language pack is not available through your package manager, download the `.traineddata` file directly from GitHub and copy it to your Tesseract data directory:
```bash theme={null}
# Download language pack (e.g. French)
curl -L https://github.com/tesseract-ocr/tessdata/raw/main/fra.traineddata \
-o fra.traineddata
# Copy to tessdata directory (path varies by distro)
sudo cp fra.traineddata /usr/share/tesseract-ocr/4.00/tessdata/
# or
sudo cp fra.traineddata /usr/share/tessdata/
```
Common tessdata locations on Linux:
| Distribution | Path |
| --------------- | ----------------------------------------- |
| Ubuntu / Debian | `/usr/share/tesseract-ocr/4.00/tessdata/` |
| Fedora / RHEL | `/usr/share/tesseract/tessdata/` |
| Arch Linux | `/usr/share/tessdata/` |
***
## macOS
The recommended approach on macOS is [Homebrew](https://brew.sh). There are two options depending on how much disk space you want to use.
### Install all languages at once
The `tesseract-lang` formula bundles Tesseract with every available language pack:
```bash theme={null}
brew install tesseract-lang
```
### Install specific languages
If you only need a few languages, install `tesseract` first and then manually download the `.traineddata` files you need:
```bash theme={null}
# Install Tesseract engine only
brew install tesseract
# Find the tessdata directory
brew info tesseract
# Look for a line like: /opt/homebrew/share/tessdata
# Download a specific language pack (e.g. French)
curl -L https://github.com/tesseract-ocr/tessdata/raw/main/fra.traineddata \
-o /opt/homebrew/share/tessdata/fra.traineddata
```
Replace `fra` with your target language code and adjust the tessdata path to match what `brew info tesseract` reports on your machine.
If you installed Tesseract via MacPorts instead of Homebrew, use `port install tesseract-`, for example `sudo port install tesseract-fra`.
***
## Docker
For containerised .NET applications, install language packs in your `Dockerfile` alongside Tesseract:
```dockerfile theme={null}
FROM mcr.microsoft.com/dotnet/runtime:8.0
# Install Tesseract with specific language packs
RUN apt-get update && apt-get install -y \
tesseract-ocr \
tesseract-ocr-eng \
tesseract-ocr-fra \
tesseract-ocr-deu \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
```
If the language you need is not available through `apt`, copy the `.traineddata` file directly into the image:
```dockerfile theme={null}
# Copy a manually downloaded .traineddata file into the image
COPY fra.traineddata /usr/share/tesseract-ocr/4.00/tessdata/fra.traineddata
```
If Tesseract cannot find your language data at runtime, set the `TESSDATA_PREFIX` environment variable to the directory containing your `.traineddata` files:
```dockerfile theme={null}
ENV TESSDATA_PREFIX=/usr/share/tesseract-ocr/4.00/tessdata/
```
***
## Using a language with PDF4LLM
Once a language pack is installed, pass its code to `ToMarkdown()`, `ToText()`, or `ParseDocument()` via the `ocrLanguage` parameter:
```csharp theme={null}
using PDF4LLM;
// Single language
string md = PdfExtractor.ToMarkdown("document.pdf", useOcr: true, ocrLanguage: "fra");
// Multiple languages combined with +
string md = PdfExtractor.ToMarkdown("document.pdf", useOcr: true, ocrLanguage: "eng+fra+deu");
```
The same parameter works across all OCR-capable methods:
```csharp theme={null}
// Plain text with OCR
string text = PdfExtractor.ToText("document.pdf", useOcr: true, ocrLanguage: "jpn");
// Structured layout with OCR
ParsedDocument parsed = PdfExtractor.ParseDocument("document.pdf", useOcr: true, ocrLanguage: "ara");
```
***
## Common language codes
| Language | Code |
| ------------------- | --------- |
| English | `eng` |
| French | `fra` |
| German | `deu` |
| Spanish | `spa` |
| Italian | `ita` |
| Portuguese | `por` |
| Simplified Chinese | `chi_sim` |
| Traditional Chinese | `chi_tra` |
| Japanese | `jpn` |
| Korean | `kor` |
| Arabic | `ara` |
| Russian | `rus` |
| Hindi | `hin` |
For the full list of supported languages and their codes, see the [Tesseract tessdata repository](https://github.com/tesseract-ocr/tessdata).
# Extract JSON
Source: https://docs.pdf4llm.com/dotnet/guides/extract-JSON/index
Use [ToJson()](/dotnet/api/PdfExtractor#tojson) to get bounding boxes, layout data, and structured page content for custom pipelines.
## Overview
`ToJson()` returns document content as structured data rather than a Markdown string. Every text block, image, and table on each page is represented as a JSON object with positional metadata attached.
This makes it the right choice when you need to:
* Build a custom rendering or post-processing pipeline
* Access bounding box coordinates for text and image regions
* Detect headings, bold text, or other styled elements programmatically
* Pass structured layout data to a downstream model or search index
```csharp theme={null}
using PDF4LLM;
string json = PdfExtractor.ToJson("document.pdf");
```
***
## Output structure
The return value is a JSON array — one object per processed page. See the [JSON schema guide](/dotnet/reference/JSON-schema) for a full field reference.
***
## Working with bounding boxes
Every block carries a `bbox` field — a four-element array `[x0, y0, x1, y1]` describing the rectangle that bounds that element.
```csharp theme={null}
using Newtonsoft.Json.Linq;
using PDF4LLM;
string json = PdfExtractor.ToJson("document.pdf");
JToken root = JToken.Parse(json);
JArray pages = root switch
{
JArray arr => arr,
JObject obj when obj["pages"] is JArray arr => arr,
_ => throw new InvalidOperationException("Expected a JSON array or an object containing a 'pages' array.")
};
foreach (JObject page in pages)
{
int pageNum = page["page_number"]!.Value();
Console.WriteLine($"\nPage {pageNum}");
foreach (JObject box in (page["boxes"] as JArray)?.OfType() ?? Enumerable.Empty())
{
float x0 = box["x0"]!.Value();
float y0 = box["y0"]!.Value();
float x1 = box["x1"]!.Value();
float y1 = box["y1"]!.Value();
string boxClass = box["boxclass"]?.Value() ?? "unknown";
Console.WriteLine($"{boxClass} at ({x0:F1}, {y0:F1}) -> ({x1:F1}, {y1:F1})");
}
}
```
***
## Extracting span-level data
Spans are the most granular unit in the JSON output. Each span represents a run of text sharing the same font, size, and style flags. This lets you identify headings, bold text, and other styled elements programmatically:
```csharp theme={null}
using Newtonsoft.Json.Linq;
using PDF4LLM;
string json = PdfExtractor.ToJson("document.pdf");
JToken root = JToken.Parse(json);
JArray pages = root switch
{
JArray arr => arr,
JObject obj when obj["pages"] is JArray arr => arr,
_ => throw new InvalidOperationException("Expected a JSON array or an object containing a 'pages' array.")
};
foreach (JObject page in pages)
{
foreach (JObject box in (page["boxes"] as JArray)?.OfType() ?? Enumerable.Empty())
{
foreach (JObject line in (box["textlines"] as JArray)?.OfType() ?? Enumerable.Empty())
{
foreach (JObject span in (line["spans"] as JArray)?.OfType() ?? Enumerable.Empty())
{
string text = span["text"]!.Value()!;
float size = span["size"]!.Value();
int flags = span["flags"]!.Value();
if (size >= 14)
Console.WriteLine($"Heading candidate: \"{text}\" (size {size})");
if ((flags & 16) != 0) // bold flag
Console.WriteLine($"Bold text: \"{text}\"");
}
}
}
}
```
### Font flags reference
The `flags` field is a bitmask encoding font properties:
| Bit | Value | Meaning |
| --- | ----- | --------------- |
| 0 | `1` | Superscript |
| 1 | `2` | Italic |
| 2 | `4` | Serifed font |
| 3 | `8` | Monospaced font |
| 4 | `16` | Bold |
#### Example interpretation
Consider the following span JSON:
```json theme={null}
"spans": [
{
"size": 12,
"flags": 6,
"font": "MinionPro-It",
"text": "Italic text.",
"bbox": [72, 435.9, 122.6, 444.6]
},
{
"size": 12,
"flags": 0,
"font": "Arial",
"text": "Hello World!",
"bbox": [122.6, 436.3, 184.8, 444.6]
},
{
"size": 12,
"flags": 20,
"font": "MinionPro-Bold",
"text": "This is bold",
"bbox": [187.5, 436.0, 246.0, 444.6]
}
]
```
#### flags = 6
`flags = 6` on `"Italic text."` with font `MinionPro-It`
`6 = 2 + 4`
Consistent with italic + serifed text.
#### flags = 0
`flags = 0` on `"Hello World!"` with font `Arial`
`0` is consistent with regular (unstyled) text.
#### flags = 20
`flags = 20` on `"This is bold"` with font `MinionPro-Bold`
`20 = 16 + 4`
Consistent with bold + serifed text.
So the extracted styling in plain English is:
`"Italic text."` → italic
`"Hello World!"` → regular
`"This is bold"` → bold
***
## Page selection
As with [`ToMarkdown()`](/dotnet/api/PdfExtractor#tomarkdown), you can limit extraction to specific pages:
```csharp theme={null}
string json = PdfExtractor.ToJson("document.pdf", pages: new List { 0, 1, 2 });
```
***
## Saving JSON output
Write the result to a `.json` file:
```csharp theme={null}
using System.IO;
using PDF4LLM;
string json = PdfExtractor.ToJson("document.pdf");
File.WriteAllText("output.json", json, System.Text.Encoding.UTF8);
```
Always specify `System.Text.Encoding.UTF8` explicitly when writing to disk. The two-argument `File.WriteAllText` overload uses the platform default encoding, which may corrupt non-Latin characters such as accented letters, CJK characters, and symbols on Windows.
***
## Full example — building a custom text pipeline
```csharp theme={null}
using Newtonsoft.Json.Linq;
using PDF4LLM;
string json = PdfExtractor.ToJson("document.pdf");
JToken root = JToken.Parse(json);
JArray pages = root switch
{
JArray arr => arr,
JObject obj when obj["pages"] is JArray arr => arr,
_ => throw new InvalidOperationException("Expected a JSON array or an object containing a 'pages' array.")
};
static object ParseSpanFlags(int flags) => new
{
Superscript = (flags & 1) != 0,
Italic = (flags & 2) != 0,
Serifed = (flags & 4) != 0,
Monospaced = (flags & 8) != 0,
Bold = (flags & 16) != 0,
};
foreach (JObject page in pages)
{
int pageNum = page["page_number"]!.Value();
Console.WriteLine($"\nPage {pageNum}");
foreach (JObject box in (page["boxes"] as JArray)?.OfType() ?? Enumerable.Empty())
{
foreach (JObject line in (box["textlines"] as JArray)?.OfType() ?? Enumerable.Empty())
{
foreach (JObject span in (line["spans"] as JArray)?.OfType() ?? Enumerable.Empty())
{
string text = span["text"]?.Value() ?? "";
int flags = span["flags"]?.Value() ?? 0;
var styles = ParseSpanFlags(flags);
Console.WriteLine(new { text, flags, styles });
}
}
}
}
```
***
For the full API signature, see the [ToJson() API reference](/dotnet/api/PdfExtractor#tojson").
***
## Next steps
Full field descriptions for every object in the JSON output.
Preserve structure and formatting for LLM pipelines.
Get clean, plain text output.
# Extract Markdown
Source: https://docs.pdf4llm.com/dotnet/guides/extract-Markdown/index
A full walkthrough of [ToMarkdown()](/dotnet/api/PdfExtractor#tomarkdown) with common options and use cases.
## Overview
`ToMarkdown()` is the primary extraction method in PDF4LLM. It reads a document and returns its content as a Markdown string, preserving headings, lists, tables, code blocks, images, and reading order as closely as possible.
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
string mdText = PdfExtractor.ToMarkdown("document.pdf");
```
***
## Common options
### Page selection
Extract only specific pages by passing a list of zero-based page indices:
```csharp theme={null}
// Extract pages 1, 2, and 3 (zero-based: 0, 1, 2)
string mdText = PdfExtractor.ToMarkdown(
"document.pdf",
pages: new List { 0, 1, 2 }
);
```
Extract every other page by building the page list with Linq:
```csharp theme={null}
// Extract every other page
Document doc = new Document("document.pdf");
var everyOther = Enumerable.Range(0, doc.PageCount)
.Where(i => i % 2 == 0)
.ToList();
string mdText = PdfExtractor.ToMarkdown(doc, pages: everyOther);
doc.Close();
```
For large documents, limiting extraction to the pages you need can dramatically reduce processing time — especially when OCR is involved.
### Per-page chunks
Use `LlamaMarkdownReader` to return one document object per page instead of a single concatenated string. Each chunk includes the page's Markdown text and associated metadata:
```csharp theme={null}
var reader = PdfExtractor.LlamaMarkdownReader();
var chunks = reader.LoadData("document.pdf");
foreach (var chunk in chunks)
{
int page = (int)chunk.ExtraInfo["page"];
string text = chunk.Text;
Console.WriteLine($"Page {page}");
Console.WriteLine(text);
}
```
### Headers and footers
PDF4LLM uses bounding box position to identify and exclude repeating page headers and footers. Filter them by building the page list and using `ToJson` to identify the margin bands, or exclude them at the chunking stage by filtering short leading and trailing lines from each page chunk.
For documents with consistent header and footer heights, the most reliable approach is to filter blocks by their bounding box position using `ParseDocument`:
```csharp theme={null}
ParsedDocument parsed = PdfExtractor.ParseDocument("document.pdf");
foreach (ParsedPage page in parsed.Pages)
{
// Exclude blocks in the top and bottom 60pt margin bands
var bodyBlocks = page.Blocks
.Where(b => b.BoundingBox.Y0 > 60 && b.BoundingBox.Y1 < (page.Height - 60))
.ToList();
// Render body blocks only
}
```
### Images
To extract embedded images and reference them inline in the Markdown output:
```csharp theme={null}
string mdText = PdfExtractor.ToMarkdown(
"document.pdf",
writeImages: true,
imagePath: "assets/images/",
imageFormat: "png"
);
```
Image references are embedded as standard Markdown image syntax:
```markdown theme={null}

```
See [Image extraction](/dotnet/guides/images-and-graphics) for a full breakdown of image options.
### Tables
Table extraction runs automatically. PDF4LLM renders detected tables as GitHub-flavoured Markdown tables:
```markdown theme={null}
| Column A | Column B | Column C |
|----------|----------|----------|
| Value 1 | Value 2 | Value 3 |
```
***
## Full example
A more complete call combining several options:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
using System.IO;
// Ensure image output directory exists
Directory.CreateDirectory("assets/");
// Extract the first five pages with images
string mdText = PdfExtractor.ToMarkdown(
"report.pdf",
pages: new List { 0, 1, 2, 3, 4 }, // first five pages only
writeImages: true, // extract images to disk
imagePath: "assets/", // image output directory
imageFormat: "png" // image format
);
// Save the full output as a single Markdown file
File.WriteAllText("output/report.md", mdText, System.Text.Encoding.UTF8);
```
To save each page as a separate file, use `LlamaMarkdownReader` for per-page output:
```csharp theme={null}
var reader = PdfExtractor.LlamaMarkdownReader();
var chunks = reader.LoadData("report.pdf");
Directory.CreateDirectory("output");
foreach (var chunk in chunks)
{
int pageNum = (int)chunk.ExtraInfo["page"];
string filePath = $"output/page-{pageNum}.md";
File.WriteAllText(filePath, chunk.Text, System.Text.Encoding.UTF8);
}
```
***
For the full API signature including all parameters and return types, see the [ToMarkdown() API reference](/dotnet/api/PdfExtractor#tomarkdown).
***
## Next steps
Bounding boxes and layout data for custom pipelines.
Get clean, plain text output.
# Extract Text
Source: https://docs.pdf4llm.com/dotnet/guides/extract-Text/index
Use [ToText()](/dotnet/api/PdfExtractor#totext) to get clean, plain text output stripped of all Markdown formatting.
## Overview
`ToText()` extracts the content of a document as a plain text string — no Markdown syntax, no bounding boxes, no metadata. It's the simplest output format and the right choice when your downstream tool doesn't need formatting or structure, just the words.
```csharp theme={null}
using PDF4LLM;
string text = PdfExtractor.ToText("document.pdf");
Console.WriteLine(text);
```
***
## When to use plain text
| Use case | Recommended format |
| ----------------------------- | ----------------------------------- |
| Search indexing | ✅ Plain text |
| Keyword extraction / NLP | ✅ Plain text |
| LLM summarisation (simple) | ✅ Plain text |
| RAG pipelines with chunking | ⚠️ Consider Markdown or page chunks |
| Preserving document structure | ❌ Use Markdown |
| Custom layout pipelines | ❌ Use JSON |
If you're feeding content into an LLM and document structure matters — headings, lists, tables — use `ToMarkdown()` instead. LLMs handle Markdown well and the added structure improves output quality.
***
## Page selection
Extract only the pages you need:
```csharp theme={null}
string text = PdfExtractor.ToText(
"document.pdf",
pages: new List { 0, 1, 2 }
);
```
***
## Per-page chunks
Use `LlamaMarkdownReader` to return one document object per page instead of a single concatenated string. Each chunk includes the page's plain text and a metadata dictionary with the page number and source file path:
```csharp theme={null}
var reader = PdfExtractor.LlamaMarkdownReader();
var chunks = reader.LoadData("document.pdf");
foreach (var chunk in chunks)
{
int page = (int)chunk.ExtraInfo["page"];
string text = chunk.Text;
Console.WriteLine($"Page {page}: {text.Length} chars");
}
```
Each chunk's `Text` property contains the plain Markdown for that page. For plain text specifically, strip Markdown syntax after loading, or call `ToText` per page using the `pages` parameter:
```csharp theme={null}
using MuPDF.NET;
Document doc = new Document("document.pdf");
var chunks = new List<(int Page, string Text)>();
for (int i = 0; i < doc.PageCount; i++)
{
string pageText = PdfExtractor.ToText(doc, pages: new List { i });
chunks.Add((i, pageText));
}
doc.Close();
foreach (var chunk in chunks)
Console.WriteLine($"Page {chunk.Page}: {chunk.Text.Length} chars");
```
***
## Saving to a file
Write the output to a `.txt` file:
```csharp theme={null}
using System.IO;
using PDF4LLM;
string text = PdfExtractor.ToText("document.pdf");
File.WriteAllText("output.txt", text, System.Text.Encoding.UTF8);
```
To save each page as a separate file:
```csharp theme={null}
using MuPDF.NET;
using System.IO;
Document doc = new Document("document.pdf");
Directory.CreateDirectory("output");
for (int i = 0; i < doc.PageCount; i++)
{
string pageText = PdfExtractor.ToText(doc, pages: new List { i });
File.WriteAllText($"output/page-{i}.txt", pageText, System.Text.Encoding.UTF8);
}
doc.Close();
```
***
## OCR behaviour
Like `ToMarkdown()`, `ToText()` can invoke Tesseract OCR on pages that contain no selectable text. Pass `useOcr: true` to enable it:
```csharp theme={null}
// Enable OCR on all pages
string text = PdfExtractor.ToText("document.pdf", useOcr: true);
// Enable OCR with a specific language
string text = PdfExtractor.ToText("document.pdf", useOcr: true, ocrLanguage: "fra");
```
See [OCR](/dotnet/guides/OCR) for a full walkthrough of Tesseract installation, language codes, and patterns for mixed documents.
***
For the full API signature, see the [ToText() API reference](/dotnet/api/PdfExtractor#totext).
***
## Next steps
Preserve structure and formatting for LLM pipelines.
Access bounding boxes and layout data for custom pipelines.
Control OCR behaviour and language configuration.
# Images & Graphics
Source: https://docs.pdf4llm.com/dotnet/guides/images-and-graphics/index
Extract embedded images and vector graphics from documents — controlling output path, format, and whether images are written to disk or embedded inline.
## Overview
PDF4LLM can extract images and graphics from documents in two ways: writing them as files to disk, or embedding them as Base64-encoded data URIs directly in the Markdown output. When images are written to disk, their paths are referenced inline using standard Markdown image syntax.
Image extraction is disabled by default. To enable it, pass `writeImages: true` to `ToMarkdown()`.
```csharp theme={null}
using PDF4LLM;
string mdText = PdfExtractor.ToMarkdown("document.pdf", writeImages: true);
```
***
## Writing images to disk
When `writeImages: true` is set, each image found in the document is saved as an individual file. The path to each image is embedded in the Markdown output:
```markdown theme={null}

```
By default, images are written to the process working directory. Use `imagePath` to specify a different output directory:
```csharp theme={null}
string mdText = PdfExtractor.ToMarkdown(
"document.pdf",
writeImages: true,
imagePath: "assets/images/"
);
```
Unlike the Python library, PDF4LLM for .NET does **not** create the output directory automatically. Create it before calling `ToMarkdown()` or you will get a `DirectoryNotFoundException`:
```csharp theme={null}
Directory.CreateDirectory("assets/images/");
```
***
## Image format
Use the `imageFormat` parameter to control the file format of extracted images. Pass the format as a lowercase file extension string:
```csharp theme={null}
string mdText = PdfExtractor.ToMarkdown(
"document.pdf",
writeImages: true,
imagePath: "assets/images/",
imageFormat: "jpg"
);
```
| Format | Best for | Notes |
| -------- | ----------------------------- | ---------------------------------------- |
| `"png"` | Diagrams, screenshots, charts | Lossless. Larger file size. Default. |
| `"jpg"` | Photographs, scanned pages | Lossy. Smaller file size. |
| `"webp"` | Web delivery | Good compression, broad browser support. |
| `"tiff"` | Archival, OCR pre-processing | Lossless. Large file size. |
| `"bmp"` | Maximum compatibility | Uncompressed. Very large file size. |
| `"pnm"` | OCR pre-processing pipelines | Portable bitmap format. |
Use `"png"` when image fidelity matters — for example, when extracting charts, diagrams, or figures that contain readable text. Use `"jpg"` for photographic content where file size is a concern.
***
## Embedded vs. file images
### File images (write to disk)
When using `ToMarkdown()` with `writeImages: true`, images are saved to disk and referenced by path in the Markdown output:
```csharp theme={null}
string mdText = PdfExtractor.ToMarkdown(
"document.pdf",
writeImages: true,
imagePath: "assets/",
imageFormat: "png"
);
```
The Markdown output will contain image references like:
```markdown theme={null}
Some preceding text.

Some following text.
```
### Embedded images (inline Base64)
Set `embedImages: true` to encode images as Base64 data URIs and embed them directly in the Markdown — no files are written to disk:
```csharp theme={null}
string mdText = PdfExtractor.ToMarkdown("document.pdf", embedImages: true);
```
The Markdown output will contain inline data URIs:
```markdown theme={null}
Some preceding text.

Some following text.
```
This produces a fully self-contained output string with no external file dependencies — useful when passing Markdown directly to an LLM or storing it in a vector store.
`writeImages` and `embedImages` are mutually exclusive. If both are set to `true`, `embedImages` takes precedence and no files are written to disk.
***
## Vector graphics
PDF4LLM detects vector drawings — lines, shapes, and filled regions — and includes their bounding boxes in the layout analysis. Vector graphic regions are represented as `"image"` type blocks in `ToJson()` output, giving you their position on the page so you can identify and handle them in your pipeline.
***
## Image file naming
Extracted image files are named automatically using the pattern:
```
{imagePath}/{sourceFilename}-{pageNumber}-{imageIndex}.{imageFormat}
```
For example, the second image on page 3 of `document.pdf`, saved as PNG to `assets/images/`:
```
assets/images/document.pdf-2-2.png
```
Page numbers are zero-based. Image indices are one-based and reset on each new page.
***
## Full example
```csharp theme={null}
using System.IO;
using PDF4LLM;
string imagePath = "output/images/";
Directory.CreateDirectory(imagePath);
// Extract Markdown with images saved to disk
string mdText = PdfExtractor.ToMarkdown(
"report.pdf",
writeImages: true,
imagePath: imagePath,
imageFormat: "png"
);
// Save the Markdown file
File.WriteAllText("output/report.md", mdText, System.Text.Encoding.UTF8);
Console.WriteLine("Done.");
Console.WriteLine($"Images saved to: {imagePath}");
Console.WriteLine("Markdown saved to: output/report.md");
```
***
For the full API signature, see the [ToMarkdown() API reference](/dotnet/api/PdfExtractor#tomarkdown).
***
## Next steps
Full walkthrough of ToMarkdown() with all common options.
Access image bounding boxes via the JSON output.
Table extraction explained.
# Page Selection
Source: https://docs.pdf4llm.com/dotnet/guides/page-selection/index
Use the pages parameter to extract content from specific pages rather than processing an entire document.
## Overview
By default, PDF4LLM processes every page in a document. The `pages` parameter lets you specify exactly which pages to extract — as a `List` of zero-based page indices. It is supported by `ToMarkdown()`, `ToJson()`, and `ToText()`.
```csharp theme={null}
using PDF4LLM;
// Extract only the first three pages
string mdText = PdfExtractor.ToMarkdown("document.pdf", pages: new List { 0, 1, 2 });
```
***
## Zero-based indexing
Page numbers in PDF4LLM are **zero-based** — the first page of a document is page `0`, the second is page `1`, and so on.
| Document page | `pages` index |
| ------------- | ------------- |
| Page 1 | `0` |
| Page 2 | `1` |
| Page 10 | `9` |
| Last page | `n - 1` |
Passing a page index that doesn't exist in the document will raise an exception. Always check the document's page count (`doc.PageCount`) before constructing a dynamic page list.
***
## Common patterns
### First N pages
```csharp theme={null}
int n = 5;
var pages = Enumerable.Range(0, n).ToList();
string mdText = PdfExtractor.ToMarkdown("document.pdf", pages: pages);
```
### Last N pages
```csharp theme={null}
using MuPDF.NET;
Document doc = new Document("document.pdf");
int pageCount = doc.PageCount;
var lastFive = Enumerable.Range(pageCount - 5, 5).ToList();
string mdText = PdfExtractor.ToMarkdown(doc, pages: lastFive);
doc.Close();
```
### A specific range
```csharp theme={null}
// Pages 10–19 (zero-based)
var pages = Enumerable.Range(10, 10).ToList();
string mdText = PdfExtractor.ToMarkdown("document.pdf", pages: pages);
```
### Non-contiguous pages
```csharp theme={null}
// Cover page, table of contents, and appendix
string mdText = PdfExtractor.ToMarkdown(
"document.pdf",
pages: new List { 0, 1, 47, 48, 49 }
);
```
### Every other page
```csharp theme={null}
// Even pages only (0, 2, 4, ...)
var evenPages = Enumerable.Range(0, 50)
.Where(i => i % 2 == 0)
.ToList();
string mdText = PdfExtractor.ToMarkdown("document.pdf", pages: evenPages);
```
***
## Getting the page count
Open a `Document` to inspect the page count before building your `pages` list:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
Document doc = new Document("document.pdf");
int pageCount = doc.PageCount;
Console.WriteLine($"Total pages: {pageCount}");
// Extract the second half of the document
int midpoint = pageCount / 2;
var pages = Enumerable.Range(midpoint, pageCount - midpoint).ToList();
string mdText = PdfExtractor.ToMarkdown(doc, pages: pages);
doc.Close();
```
***
## Page selection with per-page chunks
When using `LlamaMarkdownReader`, the returned list will only contain chunks for the pages you specify if you pre-filter the results. Each chunk's `ExtraInfo` preserves the original page number from the document:
```csharp theme={null}
using PDF4LLM;
var reader = PdfExtractor.LlamaMarkdownReader();
var allChunks = reader.LoadData("document.pdf");
// Filter to pages 4, 5, and 6 after loading
var chunks = allChunks
.Where(c => new[] { 4, 5, 6 }.Contains((int)c.ExtraInfo["page"]))
.ToList();
foreach (var chunk in chunks)
{
int page = (int)chunk.ExtraInfo["page"];
Console.WriteLine($"Page {page}: {chunk.Text.Length} chars");
}
// Page 4: 1842 chars
// Page 5: 2103 chars
// Page 6: 987 chars
```
The `page` value in `ExtraInfo` reflects the **original document page number**, not the position in the returned list. Page 4 in the document is always reported as `4`, regardless of how many pages were skipped.
***
## Page selection with ToJson() and ToText()
The `pages` parameter works identically across all three extraction methods:
```csharp theme={null}
// JSON output — specific pages only
string json = PdfExtractor.ToJson("document.pdf", pages: new List { 0, 1, 2 });
// Plain text — specific pages only
string text = PdfExtractor.ToText("document.pdf", pages: new List { 0, 1, 2 });
```
***
## Processing a document in batches
For very large documents, process pages in batches to manage memory usage:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
using System.IO;
Document doc = new Document("large-document.pdf");
int batchSize = 20;
var results = new List();
for (int start = 0; start < doc.PageCount; start += batchSize)
{
int count = Math.Min(batchSize, doc.PageCount - start);
var batch = Enumerable.Range(start, count).ToList();
Console.WriteLine($"Processing pages {batch.First()}–{batch.Last()}...");
string chunk = PdfExtractor.ToMarkdown(doc, pages: batch);
results.Add(chunk);
}
doc.Close();
string fullText = string.Join("\n\n", results);
File.WriteAllText("output.md", fullText, System.Text.Encoding.UTF8);
Console.WriteLine($"Done. {doc.PageCount} pages processed.");
```
***
## Skipping blank or cover pages
Combine page selection with a quick content check to skip pages that return no meaningful text:
```csharp theme={null}
using MuPDF.NET;
using PDF4LLM;
Document doc = new Document("document.pdf");
var nonBlank = new List();
for (int i = 0; i < doc.PageCount; i++)
{
// Quick native probe — fast, no OCR
string native = PdfExtractor.ToText(doc, pages: new List { i });
if (native.Trim().Length > 0)
nonBlank.Add(i);
}
Console.WriteLine($"{nonBlank.Count} of {doc.PageCount} pages contain text");
string mdText = PdfExtractor.ToMarkdown(doc, pages: nonBlank);
doc.Close();
```
***
The `pages` parameter is supported by `ToMarkdown()`, `ToJson()`, and `ToText()`. For full API signatures see the [API reference](/dotnet/api/PdfExtractor).
***
## Next steps
Write extracted pages to .md, .json, and .txt files.
Full walkthrough of ToMarkdown() with all common options.
Bounding boxes and layout data for custom pipelines.
Process scanned pages with Tesseract OCR.
# Saving Output
Source: https://docs.pdf4llm.com/dotnet/guides/saving-output/index
Write extracted Markdown, JSON, and plain text to disk using System.IO.
## Overview
PDF4LLM's extraction methods return plain .NET strings — writing them to disk is handled by the standard library. The recommended approach is `System.IO.File.WriteAllText()`, which is straightforward, cross-platform, and available without additional dependencies.
***
## Saving Markdown
```csharp theme={null}
using System.IO;
using PDF4LLM;
string mdText = PdfExtractor.ToMarkdown("document.pdf");
File.WriteAllText("output.md", mdText, System.Text.Encoding.UTF8);
```
Always pass `System.Text.Encoding.UTF8` explicitly when writing text files. The two-argument overload of `File.WriteAllText` uses the platform default encoding, which can silently corrupt special characters, symbols, and non-Latin scripts on Windows.
***
## Saving JSON
`ToJson()` returns a JSON string directly — no additional serialisation step is needed:
```csharp theme={null}
using System.IO;
using PDF4LLM;
string json = PdfExtractor.ToJson("document.pdf");
File.WriteAllText("output.json", json, System.Text.Encoding.UTF8);
```
The returned JSON is compact by default. To write human-readable indented JSON, round-trip it through `System.Text.Json`:
```csharp theme={null}
using System.IO;
using System.Text.Json;
using PDF4LLM;
string json = PdfExtractor.ToJson("document.pdf");
var parsed = JsonSerializer.Deserialize