# 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} ![image](assets/images/document.pdf-0-1.png) ``` 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} ![image](assets/images/document.pdf-0-1.png) ``` 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. ![image](assets/document.pdf-0-1.png) 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. ![image](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...) 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(json); string indented = JsonSerializer.Serialize(parsed, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText("output.json", indented, System.Text.Encoding.UTF8); ``` For large documents where file size matters, skip the indentation step and write the compact string directly. *** ## Saving plain text ```csharp theme={null} using System.IO; using PDF4LLM; string text = PdfExtractor.ToText("document.pdf"); File.WriteAllText("output.txt", text, System.Text.Encoding.UTF8); ``` *** ## Saving per-page chunks When using `LlamaMarkdownReader`, save each page as a separate file using the page number from the chunk metadata to name each file: ```csharp theme={null} using System.IO; using PDF4LLM; string outputDir = "output/pages"; Directory.CreateDirectory(outputDir); var reader = PdfExtractor.LlamaMarkdownReader(); var chunks = reader.LoadData("document.pdf"); foreach (var chunk in chunks) { int pageNum = (int)chunk.ExtraInfo["page"]; string filePath = Path.Combine(outputDir, $"page-{pageNum}.md"); File.WriteAllText(filePath, chunk.Text, System.Text.Encoding.UTF8); Console.WriteLine($"Saved {filePath}"); } ``` *** ## Saving with a matching filename To derive the output filename from the input document automatically: ```csharp theme={null} using System.IO; using PDF4LLM; string inputPath = "reports/annual-report-2025.pdf"; string mdText = PdfExtractor.ToMarkdown(inputPath); string outputPath = Path.ChangeExtension(inputPath, ".md"); File.WriteAllText(outputPath, mdText, System.Text.Encoding.UTF8); Console.WriteLine($"Saved to {outputPath}"); // Saved to reports/annual-report-2025.md ``` `Path.ChangeExtension()` swaps the file extension cleanly, keeping the same directory and stem. *** ## Saving to a different directory To write output to a different folder while keeping the original filename: ```csharp theme={null} using System.IO; using PDF4LLM; string inputPath = "source/document.pdf"; string outputDir = "extracted"; Directory.CreateDirectory(outputDir); string mdText = PdfExtractor.ToMarkdown(inputPath); string outputName = Path.ChangeExtension(Path.GetFileName(inputPath), ".md"); string outputPath = Path.Combine(outputDir, outputName); File.WriteAllText(outputPath, mdText, System.Text.Encoding.UTF8); Console.WriteLine($"Saved to {outputPath}"); // Saved to extracted/document.md ``` *** ## Processing multiple files To extract and save output for an entire folder of PDFs: ```csharp theme={null} using System.IO; using PDF4LLM; string inputDir = "documents/"; string outputDir = "extracted/"; Directory.CreateDirectory(outputDir); string[] pdfFiles = Directory.GetFiles(inputDir, "*.pdf"); Console.WriteLine($"Found {pdfFiles.Length} PDF(s)"); foreach (string pdfPath in pdfFiles) { Console.WriteLine($"Processing {Path.GetFileName(pdfPath)}..."); try { string mdText = PdfExtractor.ToMarkdown(pdfPath); string outputName = Path.ChangeExtension(Path.GetFileName(pdfPath), ".md"); string outputPath = Path.Combine(outputDir, outputName); File.WriteAllText(outputPath, mdText, System.Text.Encoding.UTF8); Console.WriteLine($" ✓ Saved to {outputPath}"); } catch (Exception ex) { Console.WriteLine($" ✗ Failed: {ex.Message}"); } } Console.WriteLine("Done."); ``` *** ## Saving images alongside Markdown When `writeImages: true` is used, image files are written to disk automatically during extraction. Create the image directory first, then save the Markdown file alongside it: ```csharp theme={null} using System.IO; using PDF4LLM; string imageDir = "output/images"; Directory.CreateDirectory(imageDir); string mdText = PdfExtractor.ToMarkdown( "document.pdf", writeImages: true, imagePath: imageDir, imageFormat: "png" ); File.WriteAllText("output/document.md", mdText, System.Text.Encoding.UTF8); ``` Image paths in the Markdown output are relative to wherever the `.md` file is opened from. Keep your Markdown file and image directory in the same parent folder to ensure image links resolve correctly. *** ## File format summary | Output | Method | Extension | How to write | | --------------- | -------------------------------- | --------------- | ------------------------------------ | | Markdown | `ToMarkdown()` | `.md` | `File.WriteAllText()` | | JSON | `ToJson()` | `.json` | `File.WriteAllText()` directly | | Plain text | `ToText()` | `.txt` | `File.WriteAllText()` | | Per-page chunks | `LlamaMarkdownReader.LoadData()` | `.md` per page | `File.WriteAllText()` in a loop | | Images | `ToMarkdown(writeImages: true)` | `.png` / `.jpg` | Written automatically to `imagePath` | *** ## Next steps Full walkthrough of ToMarkdown() with all common options. Bounding boxes and layout data for custom pipelines. Plain text extraction and whitespace handling. Controlling image extraction, format, and output path. # Tables Source: https://docs.pdf4llm.com/dotnet/guides/tables/index How PDF4LLM detects, extracts, and renders tables as Markdown — and how to access raw table data for custom pipelines.
## Overview PDF4LLM includes automatic table detection. When a table is found on a page, it is extracted and rendered as a GitHub-flavoured Markdown table in `ToMarkdown()` output, or returned as a structured `"table"` block in `ToJson()` output. Table extraction is enabled by default — no configuration required. ```csharp theme={null} using PDF4LLM; string mdText = PdfExtractor.ToMarkdown("document.pdf"); Console.WriteLine(mdText); ``` A detected table will appear in the Markdown output like this: ```markdown theme={null} | A | B | C | D | |---|---|---|---| | 0 | 1 | 2 | 3 | | 0 | 1 | 2 | 3 | ``` *** ## How table detection works PDF4LLM detects tables by analysing the visual structure of the page — looking for ruled lines, column alignment, and consistent row spacing. It does not rely on tagged PDF structure, so it works on both tagged and untagged PDFs. Detection handles: * Tables with explicit borders (ruled lines on all sides) * Tables with partial borders (header rule only, or row dividers only) * Borderless tables detected through column alignment and whitespace * Multi-line cell content * Merged header cells Tables that span multiple pages may not be detected perfectly in all cases. If a table is not rendering as expected, see [Troubleshooting](#troubleshooting) below. *** ## Accessing raw table data When using `ToJson()`, detected tables are returned as `"table"` blocks with full cell-level data: ```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()) { if (!string.Equals(box["boxclass"]?.Value(), "table", StringComparison.Ordinal)) continue; var tableToken = box["table"]; var rows = tableToken switch { // Legacy/simple shape: "table" is directly a 2D array. JArray arr => arr.ToObject>>() ?? [], // Current shape: "table" is an object and text content is in "extract". JObject obj when obj["extract"] is JArray extract => extract.ToObject>>() ?? [], _ => [] }; int rowCount = rows.Count; int columnCount = rows.Count > 0 ? rows.Max(r => r?.Count ?? 0) : 0; Console.WriteLine($"Table: {rowCount} rows × {columnCount} columns"); foreach (var row in rows) Console.WriteLine(string.Join(" | ", row ?? [])); } } ``` ### Table block structure Each `"table"` block in the JSON output has the following shape: ```json theme={null} { "type": "table", "bbox": [72.0, 200.0, 523.0, 420.0], "content": [ ["A", "B", "C", "D" ], ["A1", "B1", "C1", "D1"], ["A2", "B2", "C2", "D2"] ] } ``` | Field | Type | Description | | --------- | ------------------ | ------------------------------------------------------------------------ | | `type` | `string` | Always `"table"` for table blocks. | | `bbox` | `[x0, y0, x1, y1]` | Bounding box of the entire table in PDF coordinates. | | `content` | `string[][]` | Two-dimensional array of cell text. Rows first, columns within each row. | The first row in `content` is typically the header row, but is not explicitly flagged as such — treat `content[0]` as the header for tables that clearly have column labels, and validate against a sample of your documents. *** ## Extracting tables to CSV Use the `content` array from `ToJson()` to export table data directly to CSV: ```csharp theme={null} using System.IO; using System.Linq; 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.") }; var csvLines = new List(); foreach (JObject page in pages) { foreach (JObject box in (page["boxes"] as JArray)?.OfType() ?? Enumerable.Empty()) { if (!string.Equals(box["boxclass"]?.Value(), "table", StringComparison.Ordinal)) continue; var tableToken = box["table"]; var rows = tableToken switch { // Legacy/simple shape: "table" is directly a 2D array. JArray arr => arr.ToObject>>() ?? [], // Current shape: "table" is an object and text content is in "extract". JObject obj when obj["extract"] is JArray extract => extract.ToObject>>() ?? [], _ => [] }; foreach (var row in rows) { // Quote cells that contain commas or quotes var escaped = row.Select(cell => cell.Contains(',') || cell.Contains('"') ? $"\"{cell.Replace("\"", "\"\"")}\"" : cell ); csvLines.Add(string.Join(",", escaped)); } csvLines.Add(""); // blank line between tables } } File.WriteAllLines("tables.csv", csvLines, System.Text.Encoding.UTF8); ``` *** ## Multi-page tables Tables that span across page boundaries are not automatically merged. Each page's fragment is returned as a separate table block. To stitch them together, match on column count and append rows manually, skipping the header row on continuation pages: ```csharp theme={null} using Newtonsoft.Json.Linq; using PDF4LLM; string json = PdfExtractor.ToJson("report.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.") }; var mergedRows = new List>(); int? prevColCount = null; foreach (JObject page in pages) { foreach (JObject box in (page["boxes"] as JArray)?.OfType() ?? Enumerable.Empty()) { if (!string.Equals(box["boxclass"]?.Value(), "table", StringComparison.Ordinal)) continue; var tableToken = box["table"]; var rows = tableToken switch { // Legacy/simple shape: "table" is directly a 2D array. JArray arr => arr.ToObject>>() ?? [], // Current shape: "table" is an object and text content is in "extract". JObject obj when obj["extract"] is JArray extract => extract.ToObject>>() ?? [], _ => [] }; int colCount = rows[0].Count; if (colCount == prevColCount) { // Continuation page — skip the header row mergedRows.AddRange(rows.Skip(1)); } else { // New table or first page — include the header mergedRows.AddRange(rows); } prevColCount = colCount; } } Console.WriteLine($"Merged table: {mergedRows.Count} rows"); ``` *** ## Troubleshooting ### Table not detected If a table is being returned as plain text rather than a `"table"` block, use `ToJson()` to inspect the raw layout on that page and confirm how the blocks are classified: ```csharp theme={null} string json = PdfExtractor.ToJson( "document.pdf", pages: new List { suspectPageIndex } ); // Open the JSON and look for the table content. // If it appears as multiple "type": "text" blocks rather than a // single "type": "table" block, the layout engine did not detect // a tabular structure. ``` Common causes: * The table is borderless with inconsistent column spacing — the layout engine could not find a reliable grid * The table is an image (scanned) — enable OCR and check whether cells are being recognised * The table has only one column, or is very narrow, and was classified as a text block ### Incorrect column splitting If columns are being merged or split incorrectly, the table may have irregular spacing or proportional fonts that disrupt alignment detection. Accessing the raw `content` array via `ToJson()` and post-processing it manually often gives better results than relying on the Markdown rendering for these cases. ### Merged cells Tables with horizontally or vertically merged cells (a single cell spanning multiple columns or rows) are not fully represented in the `content` array — the merged cell's text is preserved but the span relationship is flattened. Use `ParseDocument()` if you need to inspect cell structure at a lower level, or handle the span reconstruction in your own post-processing step. *** ## Next steps Enable OCR for scanned tables that contain no selectable text. Full guide to working with the JSON output format. Markdown extraction with all common options. Complete field reference for the JSON output structure. # Azure OpenAI Source: https://docs.pdf4llm.com/dotnet/integrations/azure Chunk PDFs with PDF4LLM and feed them into Azure OpenAI embeddings and chat completions — end-to-end patterns for .NET RAG pipelines.
# Azure OpenAI This guide shows how to connect PDF4LLM's extraction output to Azure OpenAI — specifically, how to embed extracted text using `text-embedding-3-small` (or equivalent) and how to pass document content to a chat completion endpoint for summarisation, Q\&A, and RAG. The guide assumes you have an active Azure OpenAI resource with at least one deployment for an embedding model and one for a chat model (e.g. `gpt-4o`). *** ## Prerequisites Install the Azure OpenAI .NET SDK alongside PDF4LLM: ```bash theme={null} dotnet add package PDF4LLM dotnet add package Azure.AI.OpenAI ``` You will need: | Value | Where to find it | | -------------------------- | ------------------------------------------------------- | | Azure OpenAI endpoint | Azure Portal → your OpenAI resource → Keys and Endpoint | | API key | Azure Portal → your OpenAI resource → Keys and Endpoint | | Embedding deployment name | Azure OpenAI Studio → Deployments | | Chat model deployment name | Azure OpenAI Studio → Deployments | *** ## Client setup ```csharp theme={null} using Azure; using Azure.AI.OpenAI; using PDF4LLM; string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!; string apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY")!; AzureOpenAIClient client = new(new Uri(endpoint), new AzureKeyCredential(apiKey)); string embeddingDeployment = "text-embedding-3-small"; string chatDeployment = "gpt-4o"; ``` Store credentials in environment variables or a secrets manager — never hardcode them in source files. *** ## Pattern 1 — Embed a PDF for semantic search The most common use of PDF4LLM with Azure OpenAI is building a searchable index from document content: extract text, split into chunks, embed each chunk, and store embeddings for later retrieval. ### Step 1 — Extract and chunk ```csharp theme={null} using MuPDF.NET; using PDF4LLM; Document doc = new Document("technical-spec.pdf"); string markdown = PdfExtractor.ToMarkdown(doc); doc.Close(); // Split on H2 headings for semantic chunks — adjust to match document structure string[] chunks = System.Text.RegularExpressions.Regex.Split( markdown, @"(?=^## )", RegexOptions.Multiline ); var cleanChunks = chunks .Select(c => c.Trim()) .Where(c => c.Length >= 100) // discard very short fragments .ToList(); Console.WriteLine($"Extracted {cleanChunks.Count} chunks"); ``` ### Step 2 — Embed each chunk ```csharp theme={null} var embeddingClient = client.GetEmbeddingClient(embeddingDeployment); var embeddings = new List<(string Text, float[] Embedding)>(); foreach (string chunk in cleanChunks) { EmbeddingGenerationOptions options = new() { Dimensions = 1536 }; ClientResult result = await embeddingClient.GenerateEmbeddingAsync(chunk, options); float[] vector = result.Value.ToFloats().ToArray(); embeddings.Add((chunk, vector)); } Console.WriteLine($"Generated {embeddings.Count} embeddings"); ``` Azure OpenAI rate limits vary by tier. For documents with many chunks, add a small delay between requests or use `GenerateEmbeddingsAsync` with a batch of inputs rather than one call per chunk. ### Step 3 — Batch embedding for efficiency The `GenerateEmbeddingsAsync` overload accepts a list of inputs, reducing round-trips: ```csharp theme={null} var embeddingClient = client.GetEmbeddingClient(embeddingDeployment); // Embed in batches of 16 to stay within token limits per request const int batchSize = 16; var allEmbeddings = new List<(string Text, float[] Embedding)>(); for (int i = 0; i < cleanChunks.Count; i += batchSize) { var batch = cleanChunks.Skip(i).Take(batchSize).ToList(); var result = await embeddingClient.GenerateEmbeddingsAsync(batch); for (int j = 0; j < batch.Count; j++) { float[] vector = result.Value[j].ToFloats().ToArray(); allEmbeddings.Add((batch[j], vector)); } } ``` *** ## Pattern 2 — Retrieval-augmented generation (RAG) A full RAG pipeline has three stages: **ingest** (embed and store), **retrieve** (find relevant chunks for a query), and **generate** (pass retrieved chunks to the LLM). PDF4LLM handles the extraction step in the ingest stage. ### Ingest ```csharp theme={null} // In-memory store for this example. // In production, replace with Azure AI Search, Qdrant, or another vector store. var vectorStore = new List<(string Text, float[] Embedding, string Source, int Page)>(); var reader = PdfExtractor.LlamaMarkdownReader(); var pages = reader.LoadData("product-manual.pdf"); var embeddingClient = client.GetEmbeddingClient(embeddingDeployment); foreach (var page in pages) { string text = page.Text.Trim(); int pageNum = (int)page.ExtraInfo["page"]; string filePath = (string)page.ExtraInfo["file_path"]; if (text.Length < 50) continue; // skip near-empty pages var result = await embeddingClient.GenerateEmbeddingAsync(text); float[] vector = result.Value.ToFloats().ToArray(); vectorStore.Add((text, vector, filePath, pageNum)); } Console.WriteLine($"Indexed {vectorStore.Count} pages"); ``` ### Retrieve — cosine similarity search ```csharp theme={null} static float CosineSimilarity(float[] a, float[] b) { float dot = 0f, magA = 0f, magB = 0f; for (int i = 0; i < a.Length; i++) { dot += a[i] * b[i]; magA += a[i] * a[i]; magB += b[i] * b[i]; } return dot / (MathF.Sqrt(magA) * MathF.Sqrt(magB)); } async Task> RetrieveAsync( string query, int topK = 5) { var queryResult = await embeddingClient.GenerateEmbeddingAsync(query); float[] queryVec = queryResult.Value.ToFloats().ToArray(); return vectorStore .Select(entry => ( entry.Text, entry.Source, entry.Page, Score: CosineSimilarity(queryVec, entry.Embedding) )) .OrderByDescending(r => r.Score) .Take(topK) .ToList(); } ``` ### Generate — pass retrieved chunks to gpt-4o ```csharp theme={null} async Task AskAsync(string question) { var retrieved = await RetrieveAsync(question, topK: 5); // Build a context block from the top results string context = string.Join("\n\n---\n\n", retrieved.Select(r => $"[Source: {Path.GetFileName(r.Source)}, Page {r.Page + 1}]\n{r.Text}")); string systemPrompt = "You are a helpful assistant. Answer questions using only the " + "provided context. If the answer is not in the context, say so. " + "Cite the source and page number for each factual claim."; string userPrompt = $""" Context: {context} Question: {question} """; var chatClient = client.GetChatClient(chatDeployment); ClientResult result = await chatClient.CompleteChatAsync( [ new SystemChatMessage(systemPrompt), new UserChatMessage(userPrompt) ]); return result.Value.Content[0].Text; } // Usage string answer = await AskAsync("What is the maximum operating temperature?"); Console.WriteLine(answer); ``` *** ## Pattern 3 — Summarisation For summarising a document or a set of pages, pass the extracted Markdown directly to a chat completion without embedding: ```csharp theme={null} Document doc = new Document("executive-briefing.pdf"); string markdown = PdfExtractor.ToMarkdown(doc, pages: new List { 0, 1, 2 }); doc.Close(); var chatClient = client.GetChatClient(chatDeployment); string prompt = $""" Summarise the following document in three to five bullet points. Focus on key decisions, numbers, and action items. Do not include information not present in the document. Document: {markdown} """; ClientResult result = await chatClient.CompleteChatAsync( [ new SystemChatMessage("You are a precise document summariser."), new UserChatMessage(prompt) ]); Console.WriteLine(result.Value.Content[0].Text); ``` For long documents that exceed the model's context window, summarise page-by-page and then summarise the summaries: ```csharp theme={null} Document doc = new Document("annual-report.pdf"); var chatClient = client.GetChatClient(chatDeployment); var pageSummaries = new List(); for (int i = 0; i < doc.PageCount; i++) { string pageText = PdfExtractor.ToMarkdown(doc, pages: new List { i }); if (pageText.Trim().Length < 100) continue; var result = await chatClient.CompleteChatAsync( [ new SystemChatMessage("Summarise the following page in two sentences."), new UserChatMessage(pageText) ]); pageSummaries.Add($"Page {i + 1}: {result.Value.Content[0].Text.Trim()}"); } doc.Close(); // Final roll-up summary string rollup = string.Join("\n", pageSummaries); var finalResult = await chatClient.CompleteChatAsync( [ new SystemChatMessage("Produce a five-sentence executive summary from the page summaries below."), new UserChatMessage(rollup) ]); Console.WriteLine(finalResult.Value.Content[0].Text); ``` *** ## Pattern 4 — Multimodal: PDF pages with images For documents where images carry meaningful information — technical diagrams, charts, infographics — embed images alongside text using `gpt-4o`'s vision capability: ```csharp theme={null} Document doc = new Document("system-diagram.pdf"); string markdown = PdfExtractor.ToMarkdown( doc, embedImages: true, // inline images as Base64 data URIs pages: new List { 0 } ); doc.Close(); // The markdown string contains both text and embedded images. // gpt-4o accepts markdown with inline data URIs as message content. var chatClient = client.GetChatClient(chatDeployment); ClientResult result = await chatClient.CompleteChatAsync( [ new SystemChatMessage( "You are a technical document analyst. " + "Describe both the text content and any diagrams or charts present."), new UserChatMessage(markdown) ]); Console.WriteLine(result.Value.Content[0].Text); ``` Not all Azure OpenAI deployments support vision input. Confirm that your `gpt-4o` deployment has the vision capability enabled in Azure OpenAI Studio before using this pattern. *** ## Pattern 5 — Form data extraction and LLM enrichment Combine structured form field extraction with an LLM call to normalise, validate, or enrich the extracted values: ```csharp theme={null} Document doc = new Document("insurance-claim.pdf"); var fields = PdfExtractor.GetKeyValues(doc); doc.Close(); var formData = fields.ToDictionary(f => f.Name, f => f.Value); string formJson = System.Text.Json.JsonSerializer.Serialize( formData, new System.Text.Json.JsonSerializerOptions { WriteIndented = true } ); var chatClient = client.GetChatClient(chatDeployment); string prompt = $""" The following JSON represents form fields extracted from an insurance claim PDF. Respond with a JSON object containing: - "valid": true/false — whether all required fields are present and plausible - "missing_fields": array of field names that are empty or absent - "anomalies": array of strings describing any values that look incorrect or unusual - "summary": a one-sentence plain-English description of the claim Respond with JSON only. No explanation or markdown fences. Form data: {formJson} """; ClientResult result = await chatClient.CompleteChatAsync( [ new SystemChatMessage("You are a document validation assistant. Respond only with JSON."), new UserChatMessage(prompt) ]); string analysisJson = result.Value.Content[0].Text; Console.WriteLine(analysisJson); ``` *** ## Token budgeting Every pattern above passes text to an Azure OpenAI endpoint that has a token limit per request. Keep these constraints in mind: | Model | Context window | Practical limit for RAG context | | ---------------------- | -------------- | ---------------------------------------------------------- | | gpt-4o | 128 000 tokens | \~100 000 tokens (leave room for system prompt + response) | | gpt-4o-mini | 128 000 tokens | \~100 000 tokens | | text-embedding-3-small | 8 191 tokens | Chunk to ≤ 512 tokens for best embedding quality | | text-embedding-ada-002 | 8 191 tokens | Chunk to ≤ 512 tokens | A token is approximately 4 characters for English text. A typical A4 page of dense text is 400–600 tokens. To stay safely within limits, estimate chunk token counts before sending: ```csharp theme={null} // Rough estimate — replace with SharpToken for accuracy static int EstimateTokens(string text) => text.Length / 4; var safeChunks = cleanChunks .Where(c => EstimateTokens(c) <= 512) .ToList(); // For chunks over the limit, split further var oversized = cleanChunks.Where(c => EstimateTokens(c) > 512).ToList(); // ... apply token-based splitting from the Page Selection & Chunking guide ``` *** ## Error handling Azure OpenAI requests can fail due to rate limits, transient network errors, or content filtering. Wrap requests in retry logic: ```csharp theme={null} using System.Net; static async Task RetryAsync( Func> operation, int maxRetries = 3, int delayMs = 1000) { for (int attempt = 0; attempt < maxRetries; attempt++) { try { return await operation(); } catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.TooManyRequests || ex.Status == (int)HttpStatusCode.ServiceUnavailable) { if (attempt == maxRetries - 1) throw; int backoff = delayMs * (int)Math.Pow(2, attempt); // exponential backoff Console.WriteLine($"Rate limited — retrying in {backoff}ms (attempt {attempt + 1})"); await Task.Delay(backoff); } } throw new InvalidOperationException("Unreachable"); } // Usage var result = await RetryAsync(() => embeddingClient.GenerateEmbeddingAsync(chunk)); ``` *** ## Using Azure Managed Identity For production deployments, prefer Managed Identity over API keys to avoid storing credentials: ```csharp theme={null} using Azure.Identity; // Works in Azure App Service, Azure Functions, AKS, and other managed environments AzureOpenAIClient client = new( new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!), new DefaultAzureCredential() ); ``` Assign the `Cognitive Services OpenAI User` role to the managed identity in the Azure Portal, or via the Azure CLI: ```bash theme={null} az role assignment create \ --role "Cognitive Services OpenAI User" \ --assignee \ --scope /subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts/ ``` *** ## Troubleshooting **`RequestFailedException` with status 401** The API key is incorrect, expired, or the endpoint URL does not match the key's resource. Verify both in the Azure Portal under Keys and Endpoint. **`RequestFailedException` with status 429 (Too Many Requests)** You have exceeded the tokens-per-minute or requests-per-minute quota for your deployment. Apply the retry-with-backoff pattern above, reduce batch sizes, or request a quota increase in Azure OpenAI Studio. **`RequestFailedException` with status 400 on embedding calls** The input text exceeds the embedding model's token limit (8 191 tokens). Reduce chunk sizes — the text being embedded is too long for a single embedding call. **Content filter triggered (status 400 with a content filter error code)** Azure OpenAI applies content filtering by default. If document content triggers the filter, the request fails with a content filter error rather than a rate limit error. Check `ex.ErrorCode` to distinguish. For legitimate documents triggering false positives, content filter configuration can be adjusted in Azure OpenAI Studio under Content Filters. **Empty or low-quality embedding results** Very short chunks (fewer than \~20 tokens) and very long chunks (over 512 tokens) both produce lower-quality embeddings. The short-chunk problem is common for page separators and headers extracted as standalone chunks — filter them with a minimum length check. The long-chunk problem requires splitting before embedding. **Managed Identity auth fails locally** `DefaultAzureCredential` works in managed environments but requires `az login` locally. Run `az login` in your terminal, or switch to `AzureCliCredential` explicitly for local development: ```csharp theme={null} #if DEBUG AzureOpenAIClient client = new(new Uri(endpoint), new AzureCliCredential()); #else AzureOpenAIClient client = new(new Uri(endpoint), new DefaultAzureCredential()); #endif ``` *** ## Next steps Complete method signatures and parameters. Access bounding boxes and layout data for custom pipelines. Extracting images for multimodal model input. Control OCR behaviour and language configuration. # JSON Schema Source: https://docs.pdf4llm.com/dotnet/reference/JSON-schema Full field reference for the structured output returned by [ToJson()](/dotnet/api/PdfExtractor#tojson).
## Overview `ToJson()` returns a JSON string representing a single parsed PDF — its pages, layout boxes, text content, tables, images, and metadata. Deserialise it with your preferred library to traverse the hierarchy. PDF4LLM JSON Schema Diagram ```csharp theme={null} using Newtonsoft.Json.Linq; using PDF4LLM; string json = PdfExtractor.ToJson("document.pdf"); JObject root = JObject.Parse(json); ``` This page documents every object and field in the output hierarchy. Positional coordinates are in PDF points (1 point = 1/72 inch). The origin `(0, 0)` is the **top-left** corner of the page. ```json theme={null} { "filename": "hello-world.pdf", "page_count": 2, "toc": [], "pages": [ { "page_number": 1, "width": 595.2, "height": 841.92, "boxes": [ { "x0": 72, "y0": 72, "x1": 334.47, "y1": 273.38, "boxclass": "picture", "image": "images/hello-world.pdf-0001-00.png", "table": null, "textlines": [] }, { "x0": 70.69, "y0": 295.88, "x1": 197.28, "y1": 304.63, "boxclass": "text", "image": null, "table": null, "textlines": [ { "bbox": [70.69, 295.88, 197.28, 304.63], "spans": [ { "size": 12, "flags": 0, "font": "Arial", "color": 0, "alpha": 255, "text": "Hello World!", "origin": [70.69, 304.47], "bbox": [70.69, 295.88, 136.09, 304.61], "line": 0, "block": 0, "dir": [1, 0] }, { "size": 12, "flags": 20, "font": "MinionPro-Bold", "color": 0, "alpha": 255, "text": "This is bold", "origin": [138.83, 304.47], "bbox": [138.83, 296.03, 197.28, 304.63], "line": 0, "block": 0, "dir": [1, 0] } ] } ] } ], "full_ocred": false, "text_ocred": false, "fulltext": [...], "words": [], "links": [] }, { "page_number": 2, "width": 595.2, "height": 841.92, "boxes": [ { "x0": 72, "y0": 72, "x1": 524, "y1": 118, "boxclass": "table", "image": null, "table": { "bbox": [71.15, 72.19, 523.22, 117.68], "row_count": 3, "col_count": 4, "cells": [ [[71.15, 72.19, 184.6, 87.36], [184.6, 72.19, 297.16, 87.36], ...], ... ], "extract": [ ["A", "B", "C", "D" ], ["A1", "B1", "C1", "D1"], ["A2", "B2", "C2", "D2"] ], "markdown": "|A|B|C|D|\n|---|---|---|---|\n|A1|B1|C1|D1|\n|A2|B2|C2|D2|\n\n" }, "textlines": null } ], "full_ocred": false, "text_ocred": false, "fulltext": [...], "words": [], "links": [] } ], "metadata": { "format": "PDF 1.6", "title": "", "author": "", "subject": "", "keywords": "", "creator": "", "producer": "", "creationDate": "D:20240722172345Z", "modDate": "D:20260318153118Z", "trapped": "", "encryption": null } } ``` *** ## Root object The top-level object returned for every extraction. ```json theme={null} { "filename": "hello-world.pdf", "page_count": 2, "toc": [], "pages": [...], "metadata": {...} } ``` The name of the source PDF file that was parsed. Total number of pages in the PDF. Table of contents entries extracted from the PDF. Each entry is an array of `[page_index, title, page_number]`. Empty when the PDF has no bookmarks or outline. Array of [page objects](#page-object), one per page in the PDF. PDF document metadata. See [metadata object](#metadata-object). #### Accessing the root in C\# ```csharp theme={null} JObject root = JObject.Parse(PdfExtractor.ToJson("document.pdf")); string filename = root["filename"]!.Value()!; int pageCount = root["page_count"]!.Value(); JArray pages = (JArray)root["pages"]!; JArray toc = (JArray)root["toc"]!; ``` *** ## Page object Represents a single page of the PDF. Found in `pages[]`. ```json theme={null} { "page_number": 1, "width": 595.2, "height": 841.92, "boxes": [...], "fulltext": [...], "full_ocred": false, "text_ocred": false, "words": [], "links": [] } ``` 1-based index of this page within the document. Page width in PDF points. A standard A4 page is 595.28 pt wide. Page height in PDF points. A standard A4 page is 841.89 pt tall. Detected content regions on the page. Each entry is a [box object](#box-object). Boxes are classified as `"text"`, `"picture"`, or `"table"`. Raw text blocks extracted directly from the PDF's content stream, independent of the layout box structure. Each entry is a [fulltext block](#fulltext-block). Reflects the logical reading order as encoded in the PDF's internal stream. `true` if the entire page was processed through OCR because no native text layer was found. `true` if individual text regions on the page were OCR'd rather than extracted natively. Word-level bounding boxes. Empty in the default output; populated when `extractWords` is enabled. Hyperlinks found on the page. Empty when no links are present. #### Iterating pages in C\# ```csharp theme={null} JObject root = JObject.Parse(PdfExtractor.ToJson("document.pdf")); JArray pages = (JArray)root["pages"]!; foreach (JObject page in pages) { int pageNumber = page["page_number"]!.Value(); double width = page["width"]!.Value(); double height = page["height"]!.Value(); bool wasOcred = page["full_ocred"]!.Value(); Console.WriteLine($"Page {pageNumber} ({width}×{height}pt, OCR: {wasOcred})"); } ``` *** ## Box object A detected content region on a page. Found in `pages[].boxes[]`. Boxes are the primary layout unit. Each box covers a rectangular area and is classified into one of these types: ```text theme={null} text picture table caption title section-header page-header page-footer list-item footnote formula ``` Which fields are populated depends on `boxclass`. ```json theme={null} { "x0": 70.69, "y0": 295.88, "x1": 197.28, "y1": 304.63, "boxclass": "text", "image": null, "table": null, "textlines": [...] } ``` ```json theme={null} { "x0": 72, "y0": 72, "x1": 334.47, "y1": 273.38, "boxclass": "picture", "image": "images/hello-world.pdf-0001-00.png", "table": null, "textlines": [] } ``` ```json theme={null} { "x0": 72, "y0": 72, "x1": 524, "y1": 118, "boxclass": "table", "image": null, "table": {...}, "textlines": null } ``` Left edge of the box in PDF points, measured from the left of the page. Top edge of the box in PDF points, measured from the top of the page. Right edge of the box in PDF points. Bottom edge of the box in PDF points. Classification of the content region. One of: * `"text"` — contains text lines and spans * `"picture"` — contains an embedded image or graphic * `"table"` — contains a detected table structure Relative path to the extracted image file when `boxclass` is `"picture"`. `null` for all other box types. A [table object](#table-object) when `boxclass` is `"table"`. `null` for all other box types. Array of [textline objects](#textline-object) when `boxclass` is `"text"`. Empty array `[]` for picture boxes. `null` for table boxes. #### Iterating boxes by type in C\# ```csharp theme={null} foreach (JObject page in pages) { foreach (JObject box in page["boxes"]!) { string boxclass = box["boxclass"]!.Value()!; switch (boxclass) { case "text": foreach (JObject line in box["textlines"]!) foreach (JObject span in line["spans"]!) Console.WriteLine(span["text"]!.Value()); break; case "picture": string? imagePath = box["image"]?.Value(); Console.WriteLine($"Image: {imagePath}"); break; case "table": var rows = box["table"]!["extract"]! .ToObject>>()!; Console.WriteLine($"Table: {rows.Count} rows"); break; } } } ``` *** ## Table object Structured data for a detected table. Found in `boxes[].table` when `boxclass` is `"table"`. ```json theme={null} { "bbox": [71.15, 72.19, 523.22, 117.68], "row_count": 3, "col_count": 4, "cells": [ [[71.15, 72.19, 184.6, 87.36], [184.6, 72.19, 297.16, 87.36], ...], ... ], "extract": [ ["A", "B", "C", "D" ], ["A1", "B1", "C1", "D1"], ["A2", "B2", "C2", "D2"] ], "markdown": "|A|B|C|D|\n|---|---|---|---|\n|A1|B1|C1|D1|\n|A2|B2|C2|D2|\n\n" } ``` Bounding box of the entire table as `[x0, y0, x1, y1]` in PDF points. Number of rows in the table, including any header row. Number of columns in the table. A 3D array of cell bounding boxes. `cells[row][col]` gives `[x0, y0, x1, y1]` for that cell in PDF points. Useful for mapping extracted text back to exact positions on the page. A 2D array of cell text values. `extract[row][col]` gives the string content of that cell. The first row is typically the header row. The table pre-rendered as a Markdown pipe table string, ready for display or further processing. #### Accessing table data in C\# ```csharp theme={null} JObject tableObj = (JObject)box["table"]!; int rowCount = tableObj["row_count"]!.Value(); int colCount = tableObj["col_count"]!.Value(); var rows = tableObj["extract"]!.ToObject>>()!; // Header row Console.WriteLine(string.Join(" | ", rows[0])); // Data rows foreach (var row in rows.Skip(1)) Console.WriteLine(string.Join(" | ", row)); // Pre-rendered Markdown string md = tableObj["markdown"]!.Value()!; ``` *** ## Textline object A single line of text within a text box. Found in `boxes[].textlines[]`. ```json theme={null} { "bbox": [70.69, 295.88, 197.28, 304.63], "spans": [...] } ``` Bounding box of this text line as `[x0, y0, x1, y1]` in PDF points. Array of [span objects](#span-object). A single line is typically split into multiple spans wherever the font, size, or style changes. *** ## Span object The smallest unit of text, sharing a single consistent style. Found in `textlines[].spans[]` and `fulltext[].lines[].spans[]`. A span break occurs at any change of font, size, weight, colour, or style — so a line reading "Hello World! **This is bold**" produces two separate spans. See [Font Flags Reference](/dotnet/guides/extract-JSON#font-flags-reference) for how to interpret the `flags` field. ```json theme={null} { "size": 12, "flags": 0, "bidi": 0, "char_flags": 16, "font": "Arial", "color": 0, "alpha": 255, "ascender": 0.8, "descender": -0.2, "text": "Hello World!", "origin": [70.69, 304.47], "bbox": [70.69, 295.88, 136.09, 304.61], "line": 0, "block": 0, "dir": [1, 0] } ``` ```json theme={null} { "size": 12, "flags": 20, "bidi": 0, "char_flags": 24, "font": "MinionPro-Bold", "color": 0, "alpha": 255, "ascender": 0.8, "descender": -0.2, "text": "This is bold", "origin": [138.83, 304.47], "bbox": [138.83, 296.03, 197.28, 304.63], "line": 0, "block": 0, "dir": [1, 0] } ``` The actual text content of this span. Full PostScript font name, e.g. `"Arial"`, `"MinionPro-Bold"`, `"Aptos"`. The font name often encodes weight and style — e.g. `-Bold`, `-It`. Font size in points. Bitmask of font style flags from the PDF spec. Common values: * `0` — regular * `4` — serifed font (bit 2) * `16` — bold (bit 4) * `20` — bold + serifed (bits 2 and 4) See [Font Flags Reference](/dotnet/guides/extract-JSON#font-flags-reference) for the full bitmask table. Additional character-level flags from the MuPDF structured text API. Refer to the [MuPDF structured-text header](https://github.com/ArtifexSoftware/mupdf/blob/66ef5879c18bc7cc0831fd9b915b257ab717b79e/include/mupdf/fitz/structured-text.h#L489) for the enumeration. Text colour as a packed RGB integer. `0` is black (`#000000`). Decode with: `r = (color >> 16) & 0xFF`, `g = (color >> 8) & 0xFF`, `b = color & 0xFF`. Opacity of the text from `0` (fully transparent) to `255` (fully opaque). Font ascender as a fraction of the font size. Typically `0.8`, meaning the ascender reaches 80% of the em above the baseline. Font descender as a fraction of the font size. Typically `-0.2`, meaning the descender extends 20% of the em below the baseline. Tight bounding box of the rendered glyphs as `[x0, y0, x1, y1]` in PDF points. The text origin point `[x, y]` — the position of the baseline at the start of the span, in PDF points. Unicode bidirectional level. `0` for standard left-to-right text. Index of the line this span belongs to within its parent block. Index of the block this span belongs to within the page's content stream. Text direction as a unit vector `[x, y]`. `[1, 0]` is standard left-to-right horizontal text. `[0, -1]` indicates top-to-bottom vertical text. #### Reading span data in C\# ```csharp theme={null} foreach (JObject span in line["spans"]!) { string text = span["text"]!.Value()!; float size = span["size"]!.Value(); int flags = span["flags"]!.Value(); string font = span["font"]!.Value()!; bool isBold = (flags & 16) != 0; bool isSerifed = (flags & 4) != 0; var bbox = span["bbox"]!.ToObject()!; Console.WriteLine($"{text} ({font}, {size}pt, bold:{isBold}) @ [{string.Join(", ", bbox)}]"); } ``` *** ## Fulltext block A raw text block from the PDF content stream, independent of visual layout. Found in `pages[].fulltext[]`. The `fulltext` array captures text in the order it appears in the PDF's internal stream, which may differ from the visual reading order delivered by `boxes`. Each block contains one or more lines, and each line contains spans. ```json theme={null} { "type": 0, "number": 0, "flags": 0, "bbox": [70.69, 295.88, 197.28, 304.63], "lines": [ { "spans": [...], "wmode": 0, "dir": [1, 0], "bbox": [70.69, 295.88, 197.28, 304.63] } ] } ``` Block type from the PDF spec. `0` indicates a text block. Sequential index of this block within the page's content stream. Block-level flags. `0` for standard text blocks. Bounding box of the entire block as `[x0, y0, x1, y1]` in PDF points. Array of line objects within this block. Each line contains: * `spans` — array of [span objects](#span-object) * `wmode` — writing mode (`0` = horizontal, `1` = vertical) * `dir` — line direction vector, e.g. `[1, 0]` for left-to-right * `bbox` — bounding box of the line as `[x0, y0, x1, y1]` *** ## Metadata object PDF document-level metadata. Found at the root as `metadata`. ```json theme={null} { "format": "PDF 1.6", "title": "", "author": "", "subject": "", "keywords": "", "creator": "", "producer": "", "creationDate": "D:20240722172345Z", "modDate": "D:20260318153118Z", "trapped": "", "encryption": null } ``` PDF version string, e.g. `"PDF 1.4"` or `"PDF 1.6"`. Document title as set in the PDF's document properties. Empty string if not set. Document author. Empty string if not set. Document subject. Empty string if not set. Keywords associated with the document. Empty string if not set. The application that originally created the document before any PDF conversion, e.g. `"Microsoft Word"`. Empty string if not set. The application that produced or last saved the PDF file, e.g. `"macOS Quartz PDFContext"`. Empty string if not set. Creation timestamp in PDF date format: `D:YYYYMMDDHHmmSSOHH'mm'`. Example: `"D:20240722172345Z"` = 22 July 2024, 17:23:45 UTC. Last modification timestamp in the same PDF date format. PDF trapping status. Rarely set in practice; empty string if not applicable. Encryption details if the PDF is encrypted. `null` for unencrypted documents. #### Reading metadata in C\# ```csharp theme={null} JObject root = JObject.Parse(PdfExtractor.ToJson("document.pdf")); JObject meta = (JObject)root["metadata"]!; string format = meta["format"]!.Value()!; string title = meta["title"]!.Value()!; string author = meta["author"]!.Value()!; string created = meta["creationDate"]!.Value()!; Console.WriteLine($"{title} by {author} ({format}, created {created})"); ``` *** ## See also Schema for `pageChunks: true` output from `ToMarkdown()`. Working walkthrough with filtering and pipeline examples. Full API reference for ToJson(). Extracting and working with table blocks. # Changelog Source: https://docs.pdf4llm.com/dotnet/reference/changelog Version history and release notes for PDF4LLM.NET.
## 1.27.2.4 * Fixed `PDFMarkdownReader` to keep page `extraInfo` isolated per page. ## 1.27.2.3 * Fixed `ToMarkdown`, `ToJson`, and `ToText` to support file path string input parameters. ## 1.27.2.2 * Initial release (port of `pymudfl4llm` `1.27.2.2`). # Chunk Schema Source: https://docs.pdf4llm.com/dotnet/reference/chunk-schema Full schema for each page chunk returned when `pageChunks=true` is passed to [ToMarkdown()](/dotnet/api/PdfExtractor#tomarkdown) or [ToText()](/dotnet/api/PdfExtractor#totext).
## Overview When `pageChunks: true` is passed to [ToMarkdown()](/dotnet/api/PdfExtractor#tomarkdown) or [ToText()](/dotnet/api/PdfExtractor#totext), the return value is a JSON string containing an array of page objects — one per page — rather than a single concatenated string. Each object in the array follows the schema described on this page. PDF4LLM Chunk Schema Diagram Deserialise the JSON string with your preferred library to work with the chunks in C#: ```csharp theme={null} using Newtonsoft.Json.Linq; using PDF4LLM; string json = PdfExtractor.ToMarkdown("document.pdf", pageChunks: true); JArray chunks = JArray.Parse(json); foreach (JObject chunk in chunks) { foreach (var prop in chunk.Properties()) { Console.WriteLine(prop.Name); Console.WriteLine("----"); Console.WriteLine(prop.Value); } } ``` ### Why use page chunks? Page chunking is the recommended approach for any pipeline that needs to process, search, or embed a PDF's content. Rather than working with one large string, you get a structured array where each page is a self-contained unit carrying both its text and the metadata needed to make that text useful. This matters most in RAG applications, where you need to attach source information — file path, page number, document title — to every embedded chunk so that retrieved passages can be traced back to their origin. The layout data in `page_boxes` adds another layer of utility: you can filter out headers, footers, and captions before embedding, or treat tables and body text differently depending on your retrieval strategy. Rather than post-processing a flat Markdown string and trying to guess where page boundaries or section headings fall, chunking gives you that structure directly from the PDF's own layout engine. **Example — extracting page numbers and first 100 characters of text from each chunk:** ```csharp theme={null} using Newtonsoft.Json.Linq; using PDF4LLM; string json = PdfExtractor.ToMarkdown("document.pdf", pageChunks: true); JArray chunks = JArray.Parse(json); foreach (JObject chunk in chunks) { int pageNumber = chunk["metadata"]!["page_number"]!.Value(); string text = chunk["text"]!.Value()!; Console.WriteLine($"{pageNumber}: {text[..Math.Min(100, text.Length)]}"); } ``` This is the recommended approach for RAG pipelines — it lets you attach rich metadata to each piece of content before embedding or indexing it. *** ## Chunk schema Each item in the returned JSON array is an object with four top-level keys: ```json theme={null} { "metadata": { ... }, "toc_items": [ ... ], "page_boxes": [ ... ], "text": "..." } ``` *** ### `metadata` Contains both document-level properties (consistent across all chunks) and page-level properties (unique per chunk). ```json theme={null} { "format": "PDF 1.7", "title": "My Document", "author": "Jane Smith", "subject": "", "keywords": "", "creator": "pdf-lib", "producer": "pdf-lib", "creationDate": "D:20260206183204Z", "modDate": "D:20260206183204Z", "trapped": "", "encryption": null, "file_path": "document.pdf", "page_count": 19, "page_number": 1 } ``` The PDF version string, e.g. `"PDF 1.7"`. Document title from PDF metadata. Empty string if not set. Document author from PDF metadata. Empty string if not set. The application that originally created the PDF. The application that produced or converted the PDF. PDF creation date string in `D:YYYYMMDDHHmmSSZ` format. Date the PDF was last modified, in the same format as `creationDate`. Encryption method if the document is encrypted, otherwise `null`. The file path of the source document as provided to `ToMarkdown()`. Total number of pages in the document. The 1-based page number this chunk represents. #### Usage example ```csharp theme={null} foreach (JObject chunk in chunks) { var meta = chunk["metadata"]!; int pageNum = meta["page_number"]!.Value(); int pageCount = meta["page_count"]!.Value(); string filePath = meta["file_path"]!.Value()!; Console.WriteLine($"Page {pageNum} of {pageCount} — {filePath}"); } ``` *** ### `toc_items` A list of Table of Contents entries that fall on this page. Each entry is an array in the format `[level, title, page_number]`. ```json theme={null} "toc_items": [ [1, "Introduction", 3], [2, "Background", 3], [2, "Problem Statement", 3] ] ``` Heading hierarchy depth. `1` = top-level chapter, `2` = section, `3` = subsection, etc. The heading text as it appears in the Table of Contents. The page number the TOC entry points to (1-based). `toc_items` is an empty array `[]` for pages that have no TOC entries, or for documents without a Table of Contents. Always check before iterating. #### Usage example ```csharp theme={null} foreach (JObject chunk in chunks) { foreach (JArray entry in chunk["toc_items"]!) { int level = entry[0].Value(); string title = entry[1].Value()!; int page = entry[2].Value(); string indent = new string(' ', (level - 1) * 2); Console.WriteLine($"{indent}{title} (p.{page})"); } } ``` *** ### `page_boxes` A list of layout elements detected on the page by the layout analysis engine. Each element describes a discrete visual block — a paragraph, heading, image, table, list item — along with its position on the page and its character offsets within the page's `text` string. ```json theme={null} "page_boxes": [ { "index": 0, "class": "section-header", "bbox": [58, 55, 560, 108], "pos": [0, 88] }, { "index": 1, "class": "text", "bbox": [36, 125, 574, 209], "pos": [88, 524] } ] ``` Zero-based position of this box in the page's reading order (top to bottom). The type of layout element detected. See the [box classes](#box-classes) table below. Bounding box of the element in PDF page coordinates: `[x0, y0, x1, y1]`. Origin is the top-left of the page. Units are PDF points (1 pt = 1/72 inch). Character offsets into the page's `text` string: `[start, end]`. Use these to slice the exact text that corresponds to this layout element. #### Box classes | Class | Description | | ---------------- | ---------------------------------------- | | `text` | Body paragraph or general prose | | `section-header` | A heading or section title | | `list-item` | A bullet or numbered list entry | | `table` | A detected table | | `picture` | An image or figure | | `caption` | A caption beneath a figure or table | | `page-footer` | Footer content at the bottom of the page | | `page-header` | Header content at the top of the page | #### Usage example — extract only headings ```csharp theme={null} foreach (JObject chunk in chunks) { string text = chunk["text"]!.Value()!; foreach (JObject box in chunk["page_boxes"]!) { if (box["class"]!.Value() != "section-header") continue; int start = box["pos"]![0]!.Value(); int end = box["pos"]![1]!.Value(); string headingText = text[start..end].Trim(); Console.WriteLine(headingText); } } ``` #### Usage example — get bounding boxes for all images ```csharp theme={null} foreach (JObject chunk in chunks) { int page = chunk["metadata"]!["page_number"]!.Value(); foreach (JObject box in chunk["page_boxes"]!) { if (box["class"]!.Value() != "picture") continue; var bbox = box["bbox"]!.ToObject()!; Console.WriteLine($"Page {page}: image at [{string.Join(", ", bbox)}]"); } } ``` *** ### `text` The full Markdown-formatted text content of the page as a single string. Headings, bold text, tables, and list items are represented using standard Markdown syntax. ```json theme={null} "text": "## Introduction\n\nWe highlight four promising research opportunities to improve\n_Large Language Model_ inference for datacenter AI...\n\n## **BACKGROUND**\n\n...\n" ``` Markdown string for the entire page. Newlines separate logical blocks. Images that cannot be extracted are replaced with a placeholder such as `==> picture [535 x 193] intentionally omitted <==`. The character offsets in each `page_boxes[n]["pos"]` correspond directly to positions within this string. Use them to precisely extract the text for any layout element without re-parsing the Markdown. #### Usage example — slice text by layout element ```csharp theme={null} JObject chunk = (JObject)chunks[0]; string text = chunk["text"]!.Value()!; foreach (JObject box in chunk["page_boxes"]!) { int start = box["pos"]![0]!.Value(); int end = box["pos"]![1]!.Value(); string cls = box["class"]!.Value()!; string snippet = text[start..end].Trim(); if (snippet.Length > 80) snippet = snippet[..80]; Console.WriteLine($"[{cls}] {snippet}"); } ``` *** ## Full iteration example ```csharp theme={null} using Newtonsoft.Json.Linq; using PDF4LLM; string json = PdfExtractor.ToMarkdown("document.pdf", pageChunks: true); JArray chunks = JArray.Parse(json); foreach (JObject chunk in chunks) { var meta = chunk["metadata"]!; var toc = chunk["toc_items"]!; var boxes = chunk["page_boxes"]!; string text = chunk["text"]!.Value()!; int pageNum = meta["page_number"]!.Value(); int pageCount = meta["page_count"]!.Value(); Console.WriteLine($"\n--- Page {pageNum} of {pageCount} ---"); // TOC entries on this page foreach (JArray entry in toc) { int level = entry[0].Value(); string title = entry[1].Value()!; Console.WriteLine($" TOC [{level}]: {title}"); } // Layout elements foreach (JObject box in boxes) { int start = box["pos"]![0]!.Value(); int end = box["pos"]![1]!.Value(); string cls = box["class"]!.Value()!; string snippet = text[start..end].Trim().Replace("\n", " "); if (snippet.Length > 60) snippet = snippet[..60]; Console.WriteLine($" [{cls}] {snippet}"); } } ``` *** ## Related | Method | Description | | --------------------------------------------------------- | --------------------------------------------------------- | | [`ToMarkdown()`](/dotnet/api/PdfExtractor#tomarkdown) | Produces chunks when `pageChunks: true` | | [`ToText()`](/dotnet/api/PdfExtractor#totext) | Plain text equivalent with `pageChunks: true` | | [`ToJson()`](/dotnet/api/PdfExtractor#tojson) | Alternative export with full bounding box and layout data | | [`GetKeyValues()`](/dotnet/api/PdfExtractor#getkeyvalues) | Extract form field data from a PDF | Full schema reference for the JSON output, including text, image, table, and drawing blocks with bounding boxes. Working walkthrough with filtering and pipeline examples. Full API reference for `ToJson()`. Best practices for extracting tables with layout data and converting to DataTables or CSV. # API Source: https://docs.pdf4llm.com/python/api/index Complete reference for all PyMuPDF4LLM functions and classes.
## Extraction Functions The three primary extraction functions share a common interface — they all accept a document path or `pymupdf.Document` instance, support the `pages` parameter for partial extraction, and handle OCR automatically. Extract content as a Markdown string or per-page chunk dictionaries. The primary function for LLM ingestion and RAG pipelines. Extract content as structured JSON with bounding boxes, font metadata, and layout data for every block on the page. Extract content as plain text, stripped of all Markdown syntax. *** ## Analysis Functions Analyse the visual layout of a document and return detected regions — columns, headers, figures, sidebars — with reading order and bounding boxes. Extract every word in the document as an individual dictionary with its bounding box and positional indices. Used for redaction, search, and ML pipelines. *** ## Classes A LlamaIndex `BaseReader` implementation. Loads documents as `Document` objects for use in LlamaIndex pipelines and vector stores. Detects repeating page headers and footers. Returns bounding boxes and a `get_margins()` helper for passing directly to extraction functions. Extracts heading hierarchy from an embedded table of contents or infers it from font sizes. Returns a structured list of heading entries with levels and page numbers. *** ## Utilities Returns the version string for PyMuPDF4LLM. *** ## Quick Reference | Function / Class | Returns | Key Parameters | | --------------------------------- | --------------------- | ---------------------------------------------------- | | `to_markdown()` | `str` or `list[dict]` | `pages`, `page_chunks`, `use_layout`, `write_images` | | `to_json()` | `list[dict]` | `pages`, `margins` | | `to_text()` | `str` or `list[dict]` | `pages`, `page_chunks`, `page_separator` | | `use_layout()` | `list[dict]` | `pages`, `margins` | | `get_key_values()` | `list[dict]` | `pages`, `force_ocr` | | `LlamaMarkdownReader.load_data()` | `list[Document]` | `file`, `pages`, `extra_info` | | `IdentifyHeaders.get_margins()` | `tuple` | `body_limit` | | `TocHeaders.headers` | `list[dict]` | `body_limit` | | `version` | `str` | — | # FAQ Source: https://docs.pdf4llm.com/python/getting-started/faq/index Common questions about the `pymupdf4llm` Python library.
## How do I install pymupdf4llm? Install from PyPI with a single command: ```bash theme={null} pip install pymupdf4llm ``` PyMuPDF is installed automatically as a dependency. Python 3.8 or later is required. To verify the installation worked, run: ```python theme={null} import pymupdf4llm print(pymupdf4llm.version) ``` ## How do I convert a PDF to Markdown? Call `to_markdown()` with a file path. It returns a single Markdown string with reading order preserved, tables intact, and images handled. ```python theme={null} import pymupdf4llm md_text = pymupdf4llm.to_markdown("my-document.pdf") print(md_text) ``` To save the output to a file, use Python's `pathlib`: ```python theme={null} from pathlib import Path Path("output.md").write_text(md_text) ``` ## What output formats are supported? There are three extraction functions, all sharing a consistent interface: | Function | Output | Best for | | --------------- | ----------------------------------------------------- | ---------------------------------------- | | `to_markdown()` | Markdown string or per-page chunk dicts | LLM ingestion and RAG pipelines | | `to_json()` | Structured JSON with bounding boxes and font metadata | Custom pipelines needing positional data | | `to_text()` | Plain text, stripped of all Markdown syntax | Search indexing and NLP preprocessing | ## How do I extract only specific pages? Pass a list of zero-based page numbers to the `pages` parameter. This works on all three extraction functions. ```python theme={null} md_text = pymupdf4llm.to_markdown("my-document.pdf", pages=[0, 1, 2]) ``` Page numbers are zero-indexed, so page 1 of the document is `0`, page 2 is `1`, and so on. This is especially useful for speeding up OCR-heavy documents by limiting which pages are processed. ## How do I get per-page chunks for a RAG pipeline? Set `page_chunks=True` on `to_markdown()`. This returns a list of dictionaries — one per page — each containing the text and rich metadata. ```python theme={null} chunks = pymupdf4llm.to_markdown("my-document.pdf", page_chunks=True) for chunk in chunks: print(chunk["metadata"]["page"]) # page number print(chunk["text"]) # Markdown content ``` Each chunk includes bounding box data, page dimensions, TOC entries, and document metadata — everything a downstream pipeline needs. ## What document formats are supported as input? Standard formats — PDF, XPS, EPUB, MOBI, and more — are supported out of the box with no extra configuration. Office formats such as DOCX, PPTX, and XLSX require PyMuPDF Pro, which unlocks them via the same consistent API. See the [Supported Formats guide](/python/getting-started/supported-formats) for a full list of supported input and output formats. ## Does it handle scanned or image-based PDFs? Yes. OCR runs automatically when a page contains no selectable text. Pages with native digital text skip OCR entirely, keeping processing fast. The resulting output is seamless — OCR'd pages and native pages are combined with no distinction. ```python theme={null} # OCR triggers automatically where needed md_text = pymupdf4llm.to_markdown("scanned-document.pdf") ``` Tesseract is the default OCR engine, included by default. RapidOCR and PaddleOCR are also available as optional engines. ## How do I force OCR on every page? Use `force_ocr=True` to bypass auto-detection. This is useful when the native text layer is corrupt or misaligned with the visual content. ```python theme={null} md_text = pymupdf4llm.to_markdown("document.pdf", force_ocr=True) ``` > **Note:** Forcing OCR on clean, text-based PDFs will slow processing significantly and may reduce output quality. Only use it when you have reason to distrust the native text layer. You can also target specific pages: ```python theme={null} md_text = pymupdf4llm.to_markdown("document.pdf", pages=[2, 3], force_ocr=True) ``` ## How do I disable OCR entirely? Set `use_ocr=False`. Pages with no selectable text will return empty strings. This is useful when you know your documents are always text-based, or when you want to handle OCR yourself in a downstream step. ```python theme={null} md_text = pymupdf4llm.to_markdown("document.pdf", use_ocr=False) ``` ## How do I use OCR with a non-English language? Pass a Tesseract language code to `ocr_language`. The default is `"eng"`. Combine multiple languages with a `+`: ```python theme={null} md_text = pymupdf4llm.to_markdown("multilingual.pdf", ocr_language="eng+deu") ``` The corresponding Tesseract language packs must be installed on your system first. On Ubuntu: ```bash theme={null} sudo apt install tesseract-ocr-deu tesseract-ocr-fra ``` See [Tesseract Language Packs](/python/guides/OCR/tesseract-language-packs/) for more installation instructions. ## Does it integrate with LangChain or LlamaIndex? Yes. There are native loaders for both frameworks. The `LlamaMarkdownReader` class implements a LlamaIndex `BaseReader` that loads documents as `Document` objects for use in pipelines and vector stores. For LangChain, a dedicated integration is also documented. Both plug into existing pipelines with no glue code. ## What does `to_json()` return and when should I use it? `to_json()` returns a list of dictionaries with bounding boxes, font metadata, and layout data for every block on every page. Use it when your pipeline needs precise positional data — for example, building redaction tools, ML pipelines, or custom rendering logic. It accepts the same `pages` and `margins` parameters as the other extraction functions. See the [JSON schema](/python/reference/JSON-schema) reference for full details. ## How do I detect and strip repeating headers and footers? Use the `IdentifyHeaders` class. It detects repeating page headers and footers and returns bounding boxes, plus a `get_margins()` helper that produces a tuple you can pass directly to any extraction function to exclude those regions. # Installation Source: https://docs.pdf4llm.com/python/getting-started/installation/index Install PyMuPDF4LLM and its optional dependencies.
## Requirements PyMuPDF4LLM requires **Python 3.8+**. It is built on top of [PyMuPDF](https://pymupdf.readthedocs.io/), which is installed automatically as a dependency. *** ## Basic Installation Install PyMuPDF4LLM from PyPI using pip: ```bash theme={null} pip install pymupdf4llm ``` This gives you full access to Markdown, JSON, and plain text extraction from document files. *** ## Optional Dependencies ### OCR Support Enables automatic Optical Character Recognition for PDFs containing scanned or image-based content. [Tesseract](https://github.com/tesseract-ocr/tesseract) is included by default. Support for [Rapid OCR](https://github.com/RapidAI/RapidOCR) and [Paddle OCR](https://github.com/PaddlePaddle/PaddleOCR) is also available as optional OCR engines and should be installed if required. OCR is only triggered automatically when PyMuPDF4LLM detects that a page that requires it. See: * [Hybrid OCR Strategy](/python/guides/OCR#hybrid-ocr-strategy) * [How OCR is Triggered](/python/guides/OCR#how-ocr-is-triggered) *** ## Verify Your Installation ```python theme={null} import pymupdf4llm print(pymupdf4llm.version) ``` *** ## Next Steps Convert your first PDF to Markdown in a few lines. See all supported input and output formats. # Quickstart Source: https://docs.pdf4llm.com/python/getting-started/quickstart/index Convert a PDF to Markdown in a couple of lines of Python.
## Convert a PDF to Markdown ```python theme={null} import pymupdf4llm md_text = pymupdf4llm.to_markdown("my-document.pdf") print(md_text) ``` That's it. PyMuPDF4LLM reads every page, extracts content in reading order, and returns a single Markdown string. *** ## Save the Output to a File To write the result to a `.md` file, pass the output to Python's built-in `pathlib`: ```python theme={null} import pymupdf4llm from pathlib import Path md_text = pymupdf4llm.to_markdown("my-document.pdf") Path("output.md").write_text(md_text) ``` `write_text` automatically uses UTF-8 encoding when writing Markdown files, ensuring special characters and symbols are preserved correctly. *** ## Process Specific Pages To extract only a subset of pages, pass a list of zero-based page numbers: ```python theme={null} md_text = pymupdf4llm.to_markdown("my-document.pdf", pages=[0, 1, 2]) ``` *** ## Extract as Page Chunks For RAG pipelines and LLM ingestion, `page_chunks=True` returns a list of dictionaries — one per page — with the text and metadata: ```python theme={null} chunks = pymupdf4llm.to_markdown("my-document.pdf", page_chunks=True) for chunk in chunks: print(chunk["metadata"]["page"]) # page number print(chunk["text"]) # Markdown content ``` Each chunk includes bounding box data, page dimensions, and document metadata. See [Chunk Schema](/python/reference/chunk-schema) for the full schema. *** ## What Happens Under the Hood When you call `to_markdown()`, PyMuPDF4LLM: 1. Opens the document with PyMuPDF 2. Analyses the layout of each page — detecting columns, headings, tables, and images 3. Reconstructs reading order from the visual structure 4. Detects pages with no selectable text and triggers OCR automatically if installed 5. Returns the result as a Markdown string or list of chunk dictionaries *** ## Next Steps See every supported input and output format. Write .md, .json, and .txt files with pathlib. # Supported Formats Source: https://docs.pdf4llm.com/python/getting-started/supported-formats/index Input formats PyMuPDF4LLM can read, and output formats it can produce.
## Input Formats PyMuPDF4LLM 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 | | Office Documents | `.doc`, `.docx`, `.ppt`, `.pptx`, `.xls`, `.xlsx`, `.hwp`, `.hwpx` | **PyMuPDF Pro only** — see below | Standard PyMuPDF4LLM supports PDF, XPS, eBooks, and CBZ out of the box. Office format support requires a [PyMuPDF Pro](https://pymupdf.readthedocs.io/en/latest/pymupdf-pro) licence. ### Office Documents (Pro Only) Processing Office files requires PyMuPDF Pro, which converts documents to PDF internally before extraction. This means all standard extraction options — layout analysis, OCR, page chunks — work identically on Office files. ```python theme={null} import pymupdf4llm # Requires PyMuPDF Pro licence md_text = pymupdf4llm.to_markdown("report.doc") ``` Learn how to install and activate PyMuPDF Pro for Office document support. *** ## Output Formats PyMuPDF4LLM can produce output in four formats depending on your use case: | Format | Function | Best For | | ---------- | -------------------------------- | ------------------------------------------------------- | | Markdown | `to_markdown()` | LLM ingestion, RAG pipelines, readable docs | | JSON | `to_json()` | Custom pipelines needing bounding boxes and layout data | | Plain Text | `to_text()` | Simple text extraction, search indexing | | Images | `to_markdown(write_images=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. ```python theme={null} md_text = pymupdf4llm.to_markdown("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. ```python theme={null} json_output = pymupdf4llm.to_json("document.pdf") ``` ### Plain Text Strips all formatting and returns raw text content. Ideal when downstream tools do not need Markdown syntax. ```python theme={null} text = pymupdf4llm.to_text("document.pdf") ``` ### Images When `write_images=True` is passed to `to_markdown()`, embedded images and graphics are extracted and saved to disk. Image paths are referenced inline in the Markdown output. ```python theme={null} md_text = pymupdf4llm.to_markdown("document.pdf", write_images=True, image_path="images/") ``` *** ## Next Steps Full walkthrough of `to_markdown()` with common options. Controlling image extraction, DPI, and output path. Unlock Office document support with PyMuPDF Pro. # OCR Source: https://docs.pdf4llm.com/python/guides/OCR/index How automatic OCR works in PyMuPDF4LLM, when to force it, and how to swap in a different OCR engine.
## Overview PyMuPDF4LLM includes built-in OCR support for scanned documents and image-based PDFs. By default, OCR runs **automatically** when needed — you don't have to opt in. For more control, you can force OCR on specific pages, disable it entirely, or swap in a different OCR engine using the [adaptor interface](#ocr-engines). ## Hybrid OCR strategy PyMuPDF4LLM applies OCR only when it is genuinely required to obtain the complete text of a PDF page. If a page already contains sufficient extractable text, OCR is skipped entirely — avoiding unnecessary work and eliminating the risk of degrading high-quality digital text. When OCR is needed, PyMuPDF4LLM automatically selects the most suitable OCR plugin available in the runtime environment, balancing detection accuracy with processing speed. Its built-in OCR plugins implement a Hybrid OCR strategy: only those regions lacking extractable, legible text are passed to the OCR engine. This selective approach typically reduces OCR processing time by around 50% while improving recognition accuracy, since the engine focuses exclusively on the problematic regions. The recognized text is then merged back into the original page, enriching it without disturbing existing digital content. *** ## Auto-OCR Behaviour PyMuPDF4LLM inspects each page before extracting text. If a page contains **no selectable text** — meaning all content is rasterised into images — OCR is triggered automatically for that page. Pages that contain native text only are never sent through OCR. This keeps processing fast and avoids degrading already-clean text. ```python theme={null} import pymupdf4llm # OCR runs automatically on any page with no selectable text md_text = pymupdf4llm.to_markdown("scanned-document.pdf") ``` The resulting Markdown is seamless — pages extracted via OCR and pages extracted natively are combined into a single output with no distinction between them. *** ## How OCR is triggered There are two scenarios where OCR is applied automatically: **No text at all** — if a page contains roughly no text but is covered with images or many character-sized vectors, PyMuPDF4LLM checks whether text is *probably* detectable on the page. This distinguishes image-based text (e.g. a scanned document) from ordinary pictures like photographs. **Garbled text** — if a page does contain text but too many characters are unreadable (e.g. `"�����"`), OCR is applied **for the affected text areas only**, not the full page. This preserves already-readable text, images, and vectors while recovering only what is broken. *** ## Forcing OCR In some cases you may want to force OCR even on pages that contain selectable text — for example, when the native text layer is corrupt, misencoded, or misaligned with the visual content. Use `force_ocr=True` to bypass the auto-detection check entirely: ```python theme={null} md_text = pymupdf4llm.to_markdown("document.pdf", force_ocr=True) ``` Forcing OCR on clean, text-based PDFs will slow down processing significantly and may reduce output quality. Only use `force_ocr=True` when you have reason to distrust the native text layer. You can also force OCR on specific pages rather than the whole document: ```python theme={null} md_text = pymupdf4llm.to_markdown( "document.pdf", pages=[2, 3, 4], force_ocr=True ) ``` *** ## Disabling OCR To prevent OCR from running at all — even on pages with no selectable text — set `use_ocr=False`: ```python theme={null} md_text = pymupdf4llm.to_markdown("document.pdf", use_ocr=False) ``` Pages with no selectable text will return empty strings in this mode. This is useful when you know your documents are always text-based, or when you want to handle OCR yourself in a downstream step. *** ## OCR Engines Other OCR Engines (otherwise known as OCR Adaptors or Plugins) can be used with PyMuPDF4LLM. See [OCR Plugins](/python/guides/OCR/plugins) for details on how to use different OCR engines with PyMuPDF4LLM, including Tesseract, RapidOCR, and how to implement your own custom OCR function. *** ## OCR Language Support When using the default Tesseract adaptor, you can specify one or more languages using Tesseract's language codes. Specify the language to be used by the Tesseract OCR engine. Default is `"eng"` (English). Make sure that the respective language data files are installed. Remember to use correct Tesseract language codes. Multiple languages can be specified by concatenating the respective codes with a plus sign `"+"`, for example `"eng+deu"` for English and German. ```python theme={null} md_text = pymupdf4llm.to_markdown("multilingual.pdf", ocr_language="eng+deu") ``` Tesseract language packs must be installed on your system. For example, on Ubuntu: ```bash theme={null} sudo apt install tesseract-ocr-deu tesseract-ocr-fra ``` See: [Tesseract Language Packs](/python/guides/OCR/tesseract-language-packs) for further details. *** ## Performance Tips OCR is the most compute-intensive part of the extraction pipeline. A few ways to keep it fast: * **Process only the pages you need** using the `pages` parameter to avoid running OCR on the entire document. * **Cache results** — write the output to disk after the first run so you don't re-process the same file. * **Use `force_ocr=False`** (the default) so clean pages skip OCR entirely. * **Resize images before passing to OCR** — very high DPI scans can slow Tesseract down without improving accuracy. *** ## 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. # OCR Plugins Source: https://docs.pdf4llm.com/python/guides/OCR/plugins How to use OCR engines other than Tesseract with PyMuPDF4LLM, and how to create your own custom OCR plugin.
# Overview PyMuPDF4LLM supports default OCR functions. They come in the form of plugins that are present in its `ocr` subpackage. They are based on currently 3 popular OCR engines, Tesseract OCR, RapidOCR and PaddleOCR. Some engines can be combined to make use of their strengths and mitigate their weaknesses. For example, Tesseract OCR is very good at **recognizing** text, while RapidOCR is better at **detecting** text bounding boxes in images with complex backgrounds. By combining the two engines, we can achieve better overall OCR results while at the same time also reducing the overall OCR processing time. Here is an overview of the available default plugins: | Plugin Name | Engines | Description | | ---------------- | ------------------------- | -------------------------------------------------------------------------------- | | `rapidocr_api` | RapidOCR | Uses RapidOCR for both text **detection** and text **recognition** | | `paddleocr_api` | PaddleOCR | Uses PaddleOCR for both text **detection** and text **recognition** | | `tesseract_api` | Tesseract OCR | Uses Tesseract OCR for both text **detection** and text **recognition** | | `rapidtess_api` | RapidOCR + Tesseract OCR | Uses RapidOCR for text **detection** and Tesseract OCR for text **recognition** | | `paddletess_api` | PaddleOCR + Tesseract OCR | Uses PaddleOCR for text **detection** and Tesseract OCR for text **recognition** | If not explicitly selected via the `ocr_function` parameter, PyMuPDF4LLM will check the availability of the three OCR engines and pick one of the above plugins in the following order of preference: 1. `rapidtess_api` (if both RapidOCR and Tesseract OCR are available) 2. `paddletess_api` (if both PaddleOCR and Tesseract OCR are available) 3. `rapidocr_api` (if RapidOCR is available, but not Tesseract OCR) 4. `paddleocr_api` (if PaddleOCR is available, but not Tesseract OCR) 5. `tesseract_api` (if Tesseract OCR is available, but neither RapidOCR nor PaddleOCR are available) If none of these engines is available (and no own plugin is provided), no OCR will be performed at all. If the `force_ocr` parameter is `True`, an error will be raised. Otherwise, the document will be processed without OCR and a warning will be displayed. The chosen plugin is displayed as an information message. ## How Default Plugins Work The provided default plugins use the following **"hybrid"** OCR approach: 1. Each page is cleaned from any existing standard text content. 2. The remaining page is rendered as an image and passed to the OCR engine for text detection and recognition. 3. Only the detected text is inserted back into the original page as standard text content. In this way, all original content (text and other elements) is preserved and only **augmented** with the newly recognized text. This allows for a more accurate and complete text extraction while also preserving the original document structure and formatting as much as possible. It also allows for a more efficient OCR processing since only the non-extractable text is processed by the OCR engine. This can significantly reduce the overall processing time. It also increases the chances for a successful layout detection, because other original content like vectors remain intact and will not be rendered to pixels. ## Forcing the Choice of a Default Plugin The default plugins are designed to be used as is, without any need for configuration. However, if you want to use a specific plugin, you can do so by using the following approach (which enforces for instance using RapidOCR and skipping the above selection process). Please note that all plugins have a function named `exec_ocr` that does the actual OCR. ### RapidOCR If [RapidOCR](https://github.com/RapidAI/RapidOCR?tab=readme-ov-file) and the RapidOCR ONNX Runtime are available, you can use a pre-made callable OCR function for it, which is provided in the `pymupdf4llm.ocr` module as `rapidocr_api.exec_ocr`. ```python theme={null} import pymupdf4llm from pymupdf4llm.ocr import rapidocr_api my_ocr_function = rapidocr_api.exec_ocr # Use my_ocr_function as the OCR function in PyMuPDF4LLM md_text = pymupdf4llm.to_markdown("input.pdf", ocr_function=my_ocr_function) ``` ### RapidOCR & Tesseract Side-by-Side If you want to use both OCR engines side-by-side, you can do so by implementing a custom OCR function which calls both OCR engines — one for bbox recognition (RapidOCR) and the other for text recognition (Tesseract) — and then combines their results. This pre-made callable OCR function can be found in the `pymupdf4llm.ocr` module as `rapidtess_api.exec_ocr()`. **Example** ```python theme={null} from pymupdf4llm.ocr import rapidtess_api md = pymupdf4llm.to_markdown( doc, ocr_function=rapidtess_api.exec_ocr, force_ocr=True ) ``` | Adaptor | Engines | Notes | | ------------------------ | -------------------- | --------------------------------------------------------------- | | `rapidocr_api.exec_ocr` | RapidOCR | Requires RapidOCR and ONNX Runtime | | `rapidtess_api.exec_ocr` | RapidOCR & Tesseract | Better accuracy for bounding box detection and text recognition | ## Providing your Own Plugin If you want to use your own OCR function, you can do so as follows: ```python theme={null} import pymupdf4llm def my_ocr_function(page, pixmap=None, dpi=300, language="eng"): # Your OCR implementation here return None # Use my_ocr_function as the OCR function in PyMuPDF4LLM md_text = pymupdf4llm.to_markdown("input.pdf", ocr_function=my_ocr_function) ``` Your plugin must accept at least the `page` parameter which is a PyMuPDF Page object. The other parameters are optional. The plugin must create (or extend) the text of the passed-in page object by simply inserting text (using any of PyMuPDF's text insertion methods). No return values expected. Be prepared to accept `None` or a PyMuPDF Pixmap object as the `pixmap` parameter, which is the rendered image of the page if provided. Parameters `dpi` and `language` are passed through from the respective function parameters. ## Selecting Pages for OCR Usually in document processing, the vast majority of pages contain extractable text and do not require OCR. PyMuPDF4LLM contains logic that analyzes the content based on a number of criteria including (but not restricted to) the following: * Presence of extractable and legible (!) text * Presence of images that appear to contain text * Presence of vector graphics that simulate text * Presence of text generated by previous OCR activities The OCR decision is internally based on the results of the following function: ```python theme={null} from pymupdf4llm.helpers.utils import analyze_page analysis = analyze_page(page) ``` The result `analysis` is a dictionary with the following keys and values. The area-related float values are computed as fractions of the total covered area. | Key | Type | Description | | ---------------- | -------------- | ----------------------------------------------------- | | `covered` | `pymupdf.Rect` | Page area covered by content | | `img_joins` | `float` | Fraction of area of the joined images | | `img_area` | `float` | Fraction of **sum** of image area sizes | | `txt_joins` | `float` | Fraction of area of the joined text spans | | `txt_area` | `float` | Fraction of **sum** of text span bbox area sizes | | `vec_joins` | `float` | Fraction of area of the joined vector characters | | `vec_area` | `float` | Fraction of **sum** of vector character area sizes | | `chars_total` | `int` | Count of visible characters | | `chars_bad` | `int` | Count of Replacement Unicode characters | | `ocr_spans` | `int` | Count of text spans with ignored text (render mode 3) | | `img_var` | `float` | Area-weighted image variance | | `img_edges` | `float` | Area-weighted image edge energy | | `vec_suspicious` | `int` | Minimum number of suspected vector-based glyphs | | `reason` | `str` | Reason for the OCR decision, else `None` | | `needs_ocr` | `bool` | OCR decision (recommendation) | The reason is one of the following values: * `"chars_bad"` — more than 10% of all characters are illegible (i.e. Replacement Unicode characters) * `"ocr_spans"` — there exist text spans created from previous OCR executions (render mode 3) * `"vec_text"` — there exist suspected vector-based glyphs * `"img_text"` — there exist images which (probably) contain recognizable text Based on this analysis, PyMuPDF4LLM will decide whether to invoke or skip OCR for a page. This is done to optimize processing time and resource usage by only performing OCR when it is likely to yield additional text content that cannot be extracted by other means. You can override this logic in the following ways: 1. By setting `force_ocr=True` in the output functions (`to_markdown`, `to_text`, `to_json`). All pages will then be OCRed with the selected or provided OCR function regardless of their content. This will obviously have a massive impact on your execution time: expect several seconds duration per each page. 2. Do as before, but add your own selection logic to the OCR plugin: ```python theme={null} import pymupdf4llm from pymupdf4llm.ocr import rapidocr_api from pymupdf4llm.helpers.utils import analyze_page def my_ocr_function(page, pixmap=None, dpi=300, language="eng"): # analyze the page content and perform OCR only if necessary analysis = analyze_page(page) # inspect the items of the analysis dictionary to make your own # decision about whether to perform OCR or not, e.g.: if not analysis["needs_ocr"]: # accept decision NOT to perform OCR: return None # if OCR is recommended, you can decide differently based on # your own insights, e.g. we might want to accept previous OCR # results and skip OCR if there are already text spans created # from previous OCR executions (render mode 3): if analysis["reason"] == "ocr_spans": return None # execute desired OCR engine rapidocr_api.exec_ocr(page, pixmap=pixmap, dpi=dpi, language=language) return None md_text = pymupdf4llm.to_markdown("input.pdf", force_ocr=True, ocr_function=my_ocr_function, ...) ``` # Tesseract Language Packs Source: https://docs.pdf4llm.com/python/guides/OCR/tesseract-language-packs How to install additional Tesseract language packs on macOS, Linux, and Windows.
## 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 pymupdf4llm 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. *** ## 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/` | *** ## 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. *** ## 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`. *** ## Using a language with pymupdf4llm Once a language pack is installed, pass its code to `to_markdown()` via the `ocr_language` parameter: ```python theme={null} import pymupdf4llm # Single language md = pymupdf4llm.to_markdown("document.pdf", ocr_language="fra") # Multiple languages md = pymupdf4llm.to_markdown("document.pdf", ocr_language="eng+fra+deu") ``` *** ## 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/python/guides/extract-JSON/index Use [to_json()](../api/to_json) to get bounding boxes, layout data, and structured page content for custom pipelines.
## Overview `to_json()` returns document content as structured data rather than a Markdown string. Every text block, image, table, and drawing on each page is represented as a dictionary with positional and styling 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 regions * Preserve font, size, and color information * Pass structured layout data to a downstream ML model or search index ```python theme={null} import pymupdf4llm data = pymupdf4llm.to_json("document.pdf") ``` *** ## Output Structure The return value is a list of page objects — one per page and file metadata. See the [JSON Schema](/python/reference/JSON-schema) for a full field reference. *** ## Working with Bounding Boxes Every block, line, and span carries a `bbox` field — a four-element list `[x0, y0, x1, y1]` describing the rectangle that bounds that element. ```python theme={null} import json json_str = pymupdf4llm.to_json("document.pdf") data = json.loads(json_str) for page in data: print(f"\nPage {page_num}") for block in page.get("boxes", []): print(f"Block at ({block['x0']:.1f}, {block['y0']:.1f}) → ({block['x1']:.1f}, {block['y1']:.1f})") ``` *** ## Extracting Span-Level Data Spans are the most granular unit in the JSON output. Each span represents a run of text that shares the same font, size, and color. This lets you identify headings, bold text, and other styled elements programmatically: ```python theme={null} import json json_str = pymupdf4llm.to_json("document.pdf") data = json.loads(json_str) for page_num, page in enumerate(data.get("pages", [])): for block in page.get("boxes", []): if block["boxclass"] == "text": textlines = block["textlines"] for line in textlines: for span in line["spans"]: print(span) if span["size"] >= 14: print(f"Heading candidate: {span['text']!r} (size {span['size']})") if span["flags"] & 2**4: # bold flag print(f"Bold text: {span['text']!r}") ``` ### 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 If we consider the following JSON: ```json theme={null} "spans": [ { "size": 12, "flags": 6, "bidi": 0, "char_flags": 16, "font": "MinionPro-It", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "Italic text.", "origin": [ 72, 444.47998046875 ], "bbox": [ 72, 435.93597412109375, 122.60799407958984, 444.6239929199219 ], "line": 0, "block": 0, "dir": [ 1, 0 ] }, { "size": 12, "flags": 0, "bidi": 0, "char_flags": 16, "font": "Arial", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "Hello World!", "origin": [ 122.625, 444.47998046875 ], "bbox": [ 122.625, 436.31787109375, 184.802001953125, 444.59130859375 ], "line": 0, "block": 0, "dir": [ 1, 0 ] }, { "size": 12, "flags": 20, "bidi": 0, "char_flags": 24, "font": "MinionPro-Bold", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "This is bold", "origin": [ 187.53399658203125, 444.47998046875 ], "bbox": [ 187.53399658203125, 436.0439758300781, 245.98001098632812, 444.635986328125 ], "line": 0, "block": 0, "dir": [ 1, 0 ] } ] ``` A practical parse of the span flags might look like this: #### flags = 6 `flags = 6` on "Italic text." with font MinionPro-It `6 = 2 + 4` this is consistent with italic + serifed text. #### flags = 0 `flags = 0` on "Hello World!" with font Arial `0` is consistent with regular text in PyMuPDF's span flag scheme. #### flags = 20 `flags = 20` on "This is bold" with font MinionPro-Bold `20 = 16 + 4` this is 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 [`to_markdown()`](/python/api/to_markdown), you can limit extraction to specific pages: ```python theme={null} data = pymupdf4llm.to_json("document.pdf", pages=[0, 1, 2]) ``` *** ## Saving JSON Output Write the result to a `.json` file using Python's `json` module: ```python theme={null} import pymupdf4llm import json from pathlib import Path data = pymupdf4llm.to_json("document.pdf") Path("output.json").write_text( json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8" ) ``` Use `ensure_ascii=False` to preserve non-Latin characters such as accented letters, CJK characters, and symbols. *** ## Full Example: Building a Custom Text Pipeline ```python theme={null} import pymupdf4llm import json # parse the file json_str = pymupdf4llm.to_json("document.pdf") # Convert JSON to Python Dictionary and iterate through the content data = json.loads(json_str) def parse_span_flags(flags: int): return { "superscript": bool(flags & 1), "italic": bool(flags & 2), "serifed": bool(flags & 4), "monospaced": bool(flags & 8), "bold": bool(flags & 16), } # iterate through the document for page_num, page in enumerate(data.get("pages", [])): print(f"\nPage {page_num}") for block in page.get("boxes", []): if block["boxclass"] == "text": for line in block["textlines"]: for span in line["spans"]: text = span.get("text", "") flags = span.get("flags", 0) styles = parse_span_flags(flags) print({ "text": text, "flags": flags, "styles": styles }) ``` *** For the full API signature, see the [`to_json()` API reference](/python/api/to_json). *** ## Next Steps Full field descriptions for every object in the JSON output. Preserve structure and formatting for LLM pipelines. Get clean, plain text output. Table block structure explained. # Extract Markdown Source: https://docs.pdf4llm.com/python/guides/extract-Markdown/index A full walkthrough of [to_markdown()](../api/to_markdown) with common options and use cases.
## Overview `to_markdown()` is the primary extraction function in PyMuPDF4LLM. 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. ```python theme={null} import pymupdf4llm md_text = pymupdf4llm.to_markdown("document.pdf") ``` *** ## Common Options ### Page Selection Extract only specific pages by passing a list of zero-based page indices: ```python theme={null} # Extract pages 1, 2, and 3 (zero-based: 0, 1, 2) md_text = pymupdf4llm.to_markdown("document.pdf", pages=[0, 1, 2]) ``` Extract every other page by slicing the page list: ```python theme={null} # Extract every other page doc = pymupdf.open("document.pdf") pages = list(range(doc.page_count)) every_other_page = pages[::2] md = pymupdf4llm.to_markdown( doc, pages=every_other_page ) ``` For large documents, limiting extraction to the pages you need can dramatically reduce processing time — especially when OCR is involved. ### Page Chunks Return a list of per-page dictionaries instead of a single concatenated string. Each chunk includes the page's Markdown text and associated metadata: ```python theme={null} chunks = pymupdf4llm.to_markdown("document.pdf", page_chunks=True) for chunk in chunks: print(f"Page {chunk['metadata']['page']}") print(chunk["text"]) ``` This is the recommended mode for RAG pipelines and LLM ingestion workflows. See [Chunk Schema](/python/reference/chunk-schema) for more details on the structure of the returned dictionaries. ### Headers and Footers PyMuPDF4LLM can detect and exclude repeating page headers and footers to keep the output clean: ```python theme={null} md_text = pymupdf4llm.to_markdown("document.pdf", header=False, footer=False) ``` ### Images To extract embedded images and reference them inline in the Markdown output: ```python theme={null} md_text = pymupdf4llm.to_markdown( "document.pdf", write_images=True, image_path="assets/images/", image_format="png", dpi=150 ) ``` Image references are embedded as standard Markdown image syntax: ```markdown theme={null} ![](assets/images/page-1-image-0.png) ``` See [Images & Graphics](/python/guides/images-and-graphics) for a full breakdown of image options. ### Tables Table extraction is enabled by default. PyMuPDF4LLM renders detected tables as GitHub-flavoured Markdown tables: ```markdown theme={null} | Column A | Column B | Column C | |----------|----------|----------| | Value 1 | Value 2 | Value 3 | ``` See [Tables](/python/guides/tables) for more detail on table extraction and edge cases. *** ## Full Example A more complete call combining several options: ```python theme={null} import pymupdf4llm from pathlib import Path chunks = pymupdf4llm.to_markdown( "report.pdf", pages=[0, 1, 2, 3, 4], # first five pages only page_chunks=True, # return per-page dictionaries write_images=True, # extract images to disk image_path="assets/", # image output directory image_format="png", # image format dpi=200 # image resolution ) # Save each page as a separate Markdown file for chunk in chunks: page_num = chunk["metadata"]["page"] Path(f"output/page-{page_num}.md").write_text(chunk["text"], encoding="utf-8") ``` *** For the full API signature including all parameters and return types, see the [`to_markdown()` API reference](/python/api/to_markdown). *** ## Next Steps Bounding boxes and layout data for custom pipelines. Get clean, plain text output. Table extraction explained. Write out data to file with pathlib. # Extract Text Source: https://docs.pdf4llm.com/python/guides/extract-Text/index Use [to_text()](../api/to_text) to get clean, plain text output stripped of all Markdown formatting.
## Overview `to_text()` 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. ```python theme={null} import pymupdf4llm text = pymupdf4llm.to_text("document.pdf") print(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 `to_markdown()` instead. LLMs handle Markdown well and the added structure improves output quality. *** ## Page Selection Extract only the pages you need: ```python theme={null} text = pymupdf4llm.to_text("document.pdf", pages=[0, 1, 2]) ``` *** ## Page Chunks As with `to_markdown()`, you can return a list of per-page dictionaries using `page_chunks=True`: ```python theme={null} chunks = pymupdf4llm.to_text("document.pdf", page_chunks=True) for chunk in chunks: print(f"Page {chunk['metadata']}: {len(chunk['text'])} chars") ``` Each chunk contains a `text` object with the plain text for that page and a `metadata` dictionary with page number and document information. *** ## Saving to a File Write the output to a `.txt` file using `pathlib`: ```python theme={null} import pymupdf4llm from pathlib import Path text = pymupdf4llm.to_text("document.pdf") Path("output.txt").write_text(text, encoding="utf-8") ``` *** ## OCR Behaviour Like `to_markdown()`, `to_text()` triggers OCR automatically on pages with no selectable text. To enable or disable auto-OCR capabilities: ```python theme={null} # Enable OCR on all pages text = pymupdf4llm.to_text("document.pdf", use_ocr=True) # Disable OCR entirely text = pymupdf4llm.to_text("document.pdf", use_ocr=False) ``` See [OCR](/python/guides/OCR) for a full walkthrough of OCR options and adaptors. *** For the full API signature, see the [`to_text()` API reference](/python/api/to_text). *** ## Next Steps Preserve structure and formatting for LLM pipelines. Access bounding boxes and layout data for custom pipelines. Write .md, .json, and .txt files with pathlib. Control automatic OCR behaviour and adaptors. # Images & Graphics Source: https://docs.pdf4llm.com/python/guides/images-and-graphics/index Extract embedded images and vector graphics from documents — controlling output path, DPI, format, and whether images are written to disk or embedded inline.
## Overview PyMuPDF4LLM can extract images and graphics from documents in two ways: writing them as files to disk, or embedding them as base64-encoded data in the JSON output. When images are written to disk, their paths are referenced inline in the Markdown output using standard image syntax. Image extraction is disabled by default. To enable it, pass `write_images=True` to `to_markdown()`. ```python theme={null} import pymupdf4llm md_text = pymupdf4llm.to_markdown("document.pdf", write_images=True) ``` *** ## Writing Images to Disk When `write_images=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} ![](assets/images/page-1-image-0.png) ``` By default, images are written to the current working directory. Use `image_path` to specify a different output directory: ```python theme={null} md_text = pymupdf4llm.to_markdown( "document.pdf", write_images=True, image_path="assets/images/" ) ``` PyMuPDF4LLM will create the output directory automatically. *** ## Image Format Use the `image_format` parameter to control the file format of extracted images. Supported formats are `'png'`, `'pnm'`, `'pgm'`, `'ppm'`, `'pbm'`, `'pam'`, `'psd'`, `'ps'`, `'jpg'`, `'jpeg'`: ```python theme={null} md_text = pymupdf4llm.to_markdown( "document.pdf", write_images=True, image_path="assets/images/", image_format="jpeg" ) ``` | Format | Best For | Notes | | -------- | ----------------------------- | ------------------------------------ | | `"png"` | Diagrams, screenshots, charts | Lossless. Larger file size. Default. | | `"jpeg"` | Photographs, scanned pages | Lossy. Smaller file size. | Use `"png"` when image fidelity matters — for example, when extracting charts, diagrams, or figures that contain readable text. Use `"jpeg"` for photographic content where file size is a concern. *** ## DPI and Resolution The `dpi` parameter controls the resolution at which raster images are rendered. The default is `150` DPI, which is a good balance between file size and clarity. ```python theme={null} md_text = pymupdf4llm.to_markdown( "document.pdf", write_images=True, dpi=300 # higher quality, larger file size ) ``` | DPI | Use Case | | ----- | ----------------------------------- | | `72` | Low-quality preview thumbnails | | `150` | Standard extraction (default) | | `300` | Print-quality or OCR pre-processing | High DPI values significantly increase both file sizes and processing time, especially for documents with many images. Only increase DPI if you have a specific need for higher resolution. *** ## Embedded vs. File Images ### File Images (Markdown) When using `to_markdown()` with `write_images=True`, images are written to disk and referenced by path in the Markdown: ```python theme={null} md_text = pymupdf4llm.to_markdown( "document.pdf", write_images=True, image_path="assets/", image_format="png", dpi=150 ) ``` The Markdown output will contain image references like: ```markdown theme={null} Some preceding text. ![](assets/page-1-image-0.png) Some following text. ``` ### Embedded Images When using `to_markdown()` or `to_json()`, images can be included directly in the output as base64-encoded byte strings by setting the `embed_images` parameter to `True`— no files are written to disk: ```python theme={null} import pymupdf4llm data = pymupdf4llm.to_json("document.pdf", write_images=True, embed_images=True) ``` For example the image block in JSON output will be presented as follows: ```json theme={null} { "boxes": [ { "x0": 72.0, "y0": 72.0, "x1": 523.2999877929688, "y1": 418.2499694824219, "boxclass": "picture", "image": "" } ] } ``` *** ## Vector Graphics PyMuPDF4LLM detects vector drawings — lines, shapes, filled regions and can rasterise them to image files by default, but their bounding boxes are preserved so you can identify and handle them in your pipeline. *** ## Image File Naming Extracted image files are named automatically using the pattern: ``` filename-{page_number}-{image_index}.{ext} ``` For example, the second image on page 3 for a document called `document.pdf` would be saved as: ``` document-0003-01.png ``` Page numbers are zero-based and indices increment per page, resetting on each new page. *** ## Full Example ```python theme={null} import pymupdf4llm # Extract Markdown with images saved to disk md_text = pymupdf4llm.to_markdown( "report.pdf", write_images=True, image_path="output/images/", image_format="png", dpi=150 ) # Save the Markdown file Path("output/report.md").write_text(md_text, encoding="utf-8") print("Done.") print(f"Images saved to: output/images/") print(f"Markdown saved to: output/report.md") ``` *** For the full API signature, see the [`to_markdown()` API reference](python/api/to_markdown) & [`to_json()` API reference](python/api/to_json). *** ## Next Steps Full walkthrough of to\_markdown() with all common options. Access embedded image data via the JSON output. Table extraction explained. Write Markdown and image files together with pathlib. # Page Selection Source: https://docs.pdf4llm.com/python/guides/page-selection/index Use the pages parameter to extract content from specific pages rather than processing an entire document.
## Overview By default, PyMuPDF4LLM 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 `to_markdown()`, `to_json()`, and `to_text()`. ```python theme={null} import pymupdf4llm # Extract only the first three pages md_text = pymupdf4llm.to_markdown("document.pdf", pages=[0, 1, 2]) ``` *** ## Zero-Based Indexing Page numbers in PyMuPDF4LLM 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 error. Always check the document's page count (`doc.page_count`) before constructing a dynamic page list. *** ## Common Patterns ### First N Pages ```python theme={null} n = 5 md_text = pymupdf4llm.to_markdown("document.pdf", pages=list(range(n))) ``` ### Last N Pages ```python theme={null} import pymupdf doc = pymupdf.open("document.pdf") page_count = doc.page_count last_5 = list(range(page_count - 5, page_count)) md_text = pymupdf4llm.to_markdown("document.pdf", pages=last_5) ``` ### A Specific Range ```python theme={null} # Pages 10–19 (zero-based) pages = list(range(10, 20)) md_text = pymupdf4llm.to_markdown("document.pdf", pages=pages) ``` ### Non-Contiguous Pages ```python theme={null} # Cover page, table of contents, and appendix md_text = pymupdf4llm.to_markdown("document.pdf", pages=[0, 1, 47, 48, 49]) ``` ### Every Other Page ```python theme={null} # Even pages only (0, 2, 4, ...) md_text = pymupdf4llm.to_markdown("document.pdf", pages=list(range(0, 50, 2))) ``` *** ## Getting the Page Count Use PyMuPDF directly to inspect a document's page count before building your `pages` list: ```python theme={null} import pymupdf import pymupdf4llm doc = pymupdf.open("document.pdf") print(f"Total pages: {doc.page_count}") # Extract the second half of the document midpoint = doc.page_count // 2 pages = list(range(midpoint, doc.page_count)) md_text = pymupdf4llm.to_markdown("document.pdf", pages=pages) ``` *** ## Page Selection with Page Chunks When using `page_chunks=True`, the returned list will only contain chunks for the pages you specified. Chunk metadata preserves the original page number from the document: ```python theme={null} chunks = pymupdf4llm.to_markdown( "document.pdf", pages=[4, 5, 6], page_chunks=True ) for chunk in chunks: print(f"Page {chunk['metadata']['page']}: {len(chunk['text'])} chars") # Page 4: 1842 chars # Page 5: 2103 chars # Page 6: 987 chars ``` The `page` value in chunk metadata 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 to\_json() and to\_text() The `pages` parameter works identically across all three extraction functions: ```python theme={null} # JSON output — specific pages only data = pymupdf4llm.to_json("document.pdf", pages=[0, 1, 2]) # Plain text — specific pages only text = pymupdf4llm.to_text("document.pdf", pages=[0, 1, 2]) ``` *** ## Processing a Document in Batches For very large documents, you may want to process pages in batches to manage memory usage: ```python theme={null} import pymupdf import pymupdf4llm from pathlib import Path doc = pymupdf.open("large-document.pdf") batch_size = 20 results = [] for start in range(0, doc.page_count, batch_size): batch = list(range(start, min(start + batch_size, doc.page_count))) print(f"Processing pages {batch[0]}–{batch[-1]}...") chunk = pymupdf4llm.to_markdown(doc, pages=batch) results.append(chunk) full_text = "\n\n".join(results) Path("output.md").write_text(full_text, encoding="utf-8") print(f"Done. {doc.page_count} pages processed.") ``` *** ## Skipping Blank or Cover Pages Combine page selection with a quick content check to skip pages that return no meaningful text: ```python theme={null} import pymupdf import pymupdf4llm doc = pymupdf.open("document.pdf") # Find pages that have selectable text non_blank = [ i for i in range(doc.page_count) if doc[i].get_text().strip() ] print(f"{len(non_blank)} of {doc.page_count} pages contain text") md_text = pymupdf4llm.to_markdown(doc, pages=non_blank) ``` *** The `pages` parameter is supported by `to_markdown()`, `to_json()`, and `to_text()`. For full API signatures see the [API Reference](/python/api/). *** ## Next Steps Write extracted pages to .md, .json, and .txt files. Full walkthrough of to\_markdown() with all common options. Bounding boxes and layout data for custom pipelines. Control automatic OCR behaviour and adaptors. # Saving Output Source: https://docs.pdf4llm.com/python/guides/saving-output/index Write extracted Markdown, JSON, and plain text to disk using pathlib.
## Overview PyMuPDF4LLM's extraction functions return strings or Python objects — writing them to disk is handled by standard Python. The recommended approach is `pathlib.Path`, which is clean, cross-platform, and available in the standard library with no additional dependencies. *** ## Saving Markdown ```python theme={null} import pymupdf4llm from pathlib import Path md_text = pymupdf4llm.to_markdown("document.pdf") Path("output.md").write_text(md_text, encoding="utf-8") ``` Always specify `encoding="utf-8"` when writing text files to ensure special characters, symbols, and non-Latin scripts are preserved correctly. *** ## Saving JSON Use Python's built-in `json` module to serialise the output before writing: ```python theme={null} import pymupdf4llm import json from pathlib import Path data = pymupdf4llm.to_json("document.pdf") Path("output.json").write_text( json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8" ) ``` `indent=2` produces human-readable JSON. For large documents where file size matters, omit it to write compact single-line JSON: ```python theme={null} Path("output.json").write_text( json.dumps(data, ensure_ascii=False), encoding="utf-8" ) ``` *** ## Saving Plain Text ```python theme={null} import pymupdf4llm from pathlib import Path text = pymupdf4llm.to_text("document.pdf") Path("output.txt").write_text(text, encoding="utf-8") ``` *** ## Saving Page Chunks When using `page_chunks=True`, you'll typically want to save each page as a separate file. Use the page number from the chunk metadata to name each file: ```python theme={null} import pymupdf4llm from pathlib import Path output_dir = Path("output/pages") output_dir.mkdir(parents=True, exist_ok=True) chunks = pymupdf4llm.to_markdown("document.pdf", page_chunks=True) for chunk in chunks: page_num = chunk["metadata"]["page"] filepath = output_dir / f"page-{page_num}.md" filepath.write_text(chunk["text"], encoding="utf-8") print(f"Saved {filepath}") ``` *** ## Saving with a Matching Filename To derive the output filename from the input document automatically: ```python theme={null} import pymupdf4llm from pathlib import Path input_path = Path("reports/annual-report-2025.pdf") md_text = pymupdf4llm.to_markdown(str(input_path)) output_path = input_path.with_suffix(".md") output_path.write_text(md_text, encoding="utf-8") print(f"Saved to {output_path}") # Saved to reports/annual-report-2025.md ``` `Path.with_suffix()` swaps the file extension cleanly, keeping the same directory and stem. *** ## Saving to a Different Directory To write output to a different folder while keeping the original filename: ```python theme={null} import pymupdf4llm from pathlib import Path input_path = Path("source/document.pdf") output_dir = Path("extracted") output_dir.mkdir(parents=True, exist_ok=True) md_text = pymupdf4llm.to_markdown(str(input_path)) output_path = output_dir / input_path.with_suffix(".md").name output_path.write_text(md_text, encoding="utf-8") print(f"Saved to {output_path}") # Saved to extracted/document.md ``` *** ## Processing Multiple Files To extract and save output for an entire folder of PDFs: ```python theme={null} import pymupdf4llm from pathlib import Path input_dir = Path("documents/") output_dir = Path("extracted/") output_dir.mkdir(parents=True, exist_ok=True) pdf_files = list(input_dir.glob("*.pdf")) print(f"Found {len(pdf_files)} PDF(s)") for pdf_path in pdf_files: print(f"Processing {pdf_path.name}...") try: md_text = pymupdf4llm.to_markdown(str(pdf_path)) output_path = output_dir / pdf_path.with_suffix(".md").name output_path.write_text(md_text, encoding="utf-8") print(f" ✓ Saved to {output_path}") except Exception as e: print(f" ✗ Failed: {e}") print("Done.") ``` *** ## Saving Images Alongside Markdown When `write_images=True` is used, images are written to disk automatically during extraction: ```python theme={null} import pymupdf4llm from pathlib import Path image_dir = Path("output/images") image_dir.mkdir(parents=True, exist_ok=True) md_text = pymupdf4llm.to_markdown( "document.pdf", write_images=True, image_path=str(image_dir), image_format="png", dpi=150 ) Path("output/document.md").write_text(md_text, encoding="utf-8") ``` Image paths in the Markdown output are relative to wherever the `.md` file is opened from. Keep your Markdown file and image directory in the same parent folder to ensure image links resolve correctly. *** ## File Format Summary | Output | Function | Extension | Write Method | | ----------- | -------------------------------- | ---------------- | ------------------------------------ | | Markdown | `to_markdown()` | `.md` | `Path.write_text()` | | JSON | `to_json()` | `.json` | `json.dumps()` + `Path.write_text()` | | Plain text | `to_text()` | `.txt` | `Path.write_text()` | | Page chunks | `to_markdown(page_chunks=True)` | `.md` per page | `Path.write_text()` in a loop | | Images | `to_markdown(write_images=True)` | `.png` / `.jpeg` | Written automatically | *** ## Next Steps Full walkthrough of to\_markdown() with all common options. Bounding boxes and layout data for custom pipelines. Plain text extraction and whitespace handling. Controlling image extraction, DPI, format, and output path. # Tables Source: https://docs.pdf4llm.com/python/guides/tables/index How PyMuPDF4LLM detects, extracts, and renders tables as Markdown — and how to access raw table data for custom pipelines.
## Overview PyMuPDF4LLM includes automatic table detection. When a table is found on a page, it is extracted and rendered as a GitHub-flavoured Markdown table in `to_markdown()` output, or returned as a structured block in `to_json()` output. Table extraction is enabled by default — no configuration required. ```python theme={null} import pymupdf4llm md_text = pymupdf4llm.to_markdown("document.pdf") print(md_text) ``` A detected table will appear in the Markdown output like this: ```markdown theme={null} | A | B | C | D | |---|---|---|---| | 0 | 1 | 2 | 3 | | 0 | 1 | 2 | 3 | ``` *** ## How Table Detection Works PyMuPDF4LLM detects tables by analysing the visual structure of the page — looking for ruled lines, column alignment, and consistent row spacing. It does not rely on tagged PDF structure, so it works on both tagged and untagged PDFs. Detection handles: * Tables with explicit borders (ruled lines on all sides) * Tables with partial borders (header rule only, or row dividers only) * Borderless tables detected through column alignment and whitespace * Multi-line cell content * Merged header cells Tables that span multiple pages may not be detected perfectly in all cases. If a table is not rendering as expected, see [Troubleshooting](#troubleshooting) below. *** ## Accessing Raw Table Data When using `to_json()`, detected tables are returned as `"table"` blocks with full cell-level data including bounding boxes: ```python theme={null} json_str = pymupdf4llm.to_json("document.pdf") data = json.loads(json_str) for page_num, page in enumerate(data.get("pages", [])): print(f"\nPage {page_num}") for block in page.get("boxes", []): if block["boxclass"] == "table": print(f"Table details: {block['table']}") ``` ### Table Block Structure ```json theme={null} { "boxclass": "table", "table": { "bbox": ["x0","y0","x1","y1"], "row_count": 3, "col_count": 4, "cells": [], "extract": [ ["A", "B", "C", "D"], ["A1", "B1", "C1", "D1"], ["A2", "B2", "C2", "D2"] ], "markdown": "|A|B|C|D|\n|---|---|---|---|\n|A1|B1|C1|D1|\n|A2|B2|C2|D2|\n\n" } } ``` *** ## Troubleshooting ### Table Not Detected If a table is being returned as plain text rather than a table block: * The table may be borderless with inconsistent spacing — ensure that [`use_layout(True)`](/python/api/use_layout) is enabled to improve detection * The table may be an image (scanned) — enable OCR and check whether cells are being recognised * The table may be very small or have only one column ### Incorrect Column Splitting If columns are being merged or split incorrectly, the table may have irregular spacing. Accessing the raw data via `to_json()` and post-processing it manually often gives better results than relying on the Markdown rendering. *** For the full API signature, see the [`to_markdown()` API reference](/python/api/to_markdown) and [`to_json()` API reference](/python/api/to_json). *** ## Next Steps Control automatic OCR behaviour and adaptors. Full guide to working with the JSON output format. Markdown extraction with all common options. Complete field reference for the JSON output structure. # LangChain Source: https://docs.pdf4llm.com/python/integrations/LangChain Use PyMuPDF4LLM as a LangChain document loader to feed PDF content into chains, agents, and retrieval pipelines.
## Overview PyMuPDF4LLM integrates with LangChain through a custom document loader that wraps `to_markdown()` and returns LangChain `Document` objects. Each document carries the page's Markdown content in its `page_content` field and PyMuPDF4LLM's page metadata in its `metadata` field. ```python theme={null} from langchain_pymupdf4llm import PyMuPDF4LLMLoader loader = PyMuPDF4LLMLoader("document.pdf") documents = loader.load() ``` *** ## Installation Make sure PyMuPDF4LLM LangChain is installed: ```bash theme={null} pip install -qU langchain-pymupdf4llm ``` *** ## Basic Usage `PyMuPDF4LLMLoader` follows the LangChain `BaseLoader` interface. Call `load()` to get a list of `Document` objects — one per page. ```python theme={null} from langchain_pymupdf4llm import PyMuPDF4LLMLoader loader = PyMuPDF4LLMLoader("report.pdf") documents = loader.load() print(f"Loaded {len(documents)} page(s)") for doc in documents: print(doc.page_content[:200]) print(doc.metadata) ``` *** ## Document Structure Each `Document` contains: * **`page_content`** — the Markdown text of the page * **`metadata`** — a dictionary of page and document-level metadata ```python theme={null} doc = documents[0] print(doc.page_content) # Markdown string print(doc.metadata) # Metadata dict ``` Example metadata: ```json theme={null} { "page": 0, "page_count": 18, "source": "report.pdf", "title": "Q3 Financial Report", "author": "Finance Team", "creation_date": "2025-09-01" } ``` *** ## Building a RAG Pipeline Combine `PyMuPDF4LLMLoader` with LangChain's `Chroma` vector store and a chat model to build a retrieval-augmented generation pipeline: ```python theme={null} from langchain_pymupdf4llm import PyMuPDF4LLMLoader from langchain_community.vectorstores import Chroma from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain.chains import RetrievalQA # Load documents loader = PyMuPDF4LLMLoader("report.pdf") documents = loader.load() # Embed and store vectorstore = Chroma.from_documents(documents, OpenAIEmbeddings()) # Build QA chain qa_chain = RetrievalQA.from_chain_type( llm=ChatOpenAI(model="gpt-4o"), retriever=vectorstore.as_retriever() ) response = qa_chain.invoke("What were the main revenue drivers in Q3?") print(response["result"]) ``` *** ## Text Splitting For large documents, split pages into smaller chunks before embedding to improve retrieval precision. LangChain's `MarkdownHeaderTextSplitter` is a natural fit because PyMuPDF4LLM output preserves Markdown headings: ```python theme={null} from langchain_pymupdf4llm import PyMuPDF4LLMLoader from langchain.text_splitter import MarkdownHeaderTextSplitter loader = PyMuPDF4LLMLoader("document.pdf") documents = loader.load() splitter = MarkdownHeaderTextSplitter( headers_to_split_on=[ ("#", "heading_1"), ("##", "heading_2"), ("###", "heading_3"), ] ) chunks = [] for doc in documents: splits = splitter.split_text(doc.page_content) # Carry original page metadata forward into each chunk for split in splits: split.metadata.update(doc.metadata) chunks.append(split) print(f"Created {len(chunks)} chunk(s) from {len(documents)} page(s)") ``` `MarkdownHeaderTextSplitter` produces semantically meaningful chunks by splitting on headings rather than character count. This works especially well with PyMuPDF4LLM output because heading structure is faithfully preserved. You can also use `RecursiveCharacterTextSplitter` for a simpler fixed-size approach: ```python theme={null} from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200 ) chunks = splitter.split_documents(documents) print(f"Created {len(chunks)} chunk(s)") ``` *** ## Lazy Loading For large documents or memory-constrained environments, use `lazy_load()` to yield documents one at a time rather than loading everything into memory at once: ```python theme={null} from langchain_pymupdf4llm import PyMuPDF4LLMLoader loader = PyMuPDF4LLMLoader("large-document.pdf") for doc in loader.lazy_load(): print(f"Page {doc.metadata['page']}: {len(doc.page_content)} chars") # Process one page at a time ``` *** ## Loading Multiple Documents Combine multiple loaders to build an index across a folder of PDFs: ```python theme={null} from pathlib import Path from langchain_pymupdf4llm import PyMuPDF4LLMLoader from langchain_community.vectorstores import Chroma from langchain_openai import OpenAIEmbeddings all_documents = [] for pdf_path in Path("documents/").glob("*.pdf"): print(f"Loading {pdf_path.name}...") loader = PyMuPDF4LLMLoader(str(pdf_path)) all_documents.extend(loader.load()) print(f"Loaded {len(all_documents)} page(s) in total") vectorstore = Chroma.from_documents(all_documents, OpenAIEmbeddings()) ``` *** ## Using with LCEL PyMuPDF4LLMLoader works naturally inside LangChain Expression Language (LCEL) chains. Here's a complete retrieval chain using the pipe syntax: ```python theme={null} from langchain_pymupdf4llm import PyMuPDF4LLMLoader from langchain_community.vectorstores import Chroma from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_core.runnables import RunnablePassthrough # Load and index loader = PyMuPDF4LLMLoader("document.pdf") vectorstore = Chroma.from_documents(loader.load(), OpenAIEmbeddings()) retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) # Build LCEL chain prompt = ChatPromptTemplate.from_template( "Answer the question using only the context below.\n\n" "Context:\n{context}\n\n" "Question: {question}" ) chain = ( {"context": retriever, "question": RunnablePassthrough()} | prompt | ChatOpenAI(model="gpt-4o") | StrOutputParser() ) print(chain.invoke("What is the document about?")) ``` *** ## Metadata Filtering Because each document carries source and page metadata, you can scope retrieval to specific pages or files using metadata filters: ```python theme={null} vectorstore = Chroma.from_documents(all_documents, OpenAIEmbeddings()) # Retrieve only from a specific source file retriever = vectorstore.as_retriever( search_kwargs={ "k": 5, "filter": {"source": "annual-report.pdf"} } ) ``` *** ## Full Pipeline Example ```python theme={null} from pathlib import Path from langchain_pymupdf4llm import PyMuPDF4LLMLoader from langchain.text_splitter import MarkdownHeaderTextSplitter from langchain_community.vectorstores import Chroma from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain.chains import RetrievalQA # Load all PDFs all_docs = [] for pdf in Path("reports/").glob("*.pdf"): loader = PyMuPDF4LLMLoader(str(pdf)) all_docs.extend(loader.load()) print(f"Loaded {len(all_docs)} page(s)") # Split on Markdown headings splitter = MarkdownHeaderTextSplitter( headers_to_split_on=[("#", "h1"), ("##", "h2"), ("###", "h3")] ) chunks = [] for doc in all_docs: for split in splitter.split_text(doc.page_content): split.metadata.update(doc.metadata) chunks.append(split) print(f"Split into {len(chunks)} chunk(s)") # Embed and index vectorstore = Chroma.from_documents(chunks, OpenAIEmbeddings()) # Query qa = RetrievalQA.from_chain_type( llm=ChatOpenAI(model="gpt-4o"), retriever=vectorstore.as_retriever(search_kwargs={"k": 5}) ) print(qa.invoke("Summarise the key findings across all reports.")["result"]) ``` *** ## Next Steps Use PyMuPDF4LLM with Office documents. Full walkthrough of to\_markdown() options. Enable OCR for scanned PDFs before indexing. # PyMuPDF Pro Source: https://docs.pdf4llm.com/python/integrations/PyMuPDF-Pro Unlock Office document support in PyMuPDF4LLM — extract content from `.doc`, `.ppt`, `.xls`, and more.
## Overview PyMuPDF Pro extends PyMuPDF4LLM with support for Microsoft Office formats. Without Pro, PyMuPDF4LLM is limited to PDF, XPS, and eBook inputs. With Pro activated, you can pass Office files directly to any extraction function — no conversion step required. Everything else stays the same. All standard options — page selection, layout analysis, OCR, page chunks, image extraction — work identically on Office documents. Need a Commercial Licence for **PyMuPDF Pro**? Contact the sales team to discuss options and pricing. *** ## Supported Office Formats | Format | Extensions | Notes | | ---------- | --------------- | --------------------------------------------- | | Word | `.docx`, `.doc` | Full text, tables, images, and headers | | PowerPoint | `.pptx`, `.ppt` | Slide content, speaker notes, embedded images | | Excel | `.xlsx`, `.xls` | Sheet data rendered as tables | | Hangul | `.hwpx`, `.hwp` | Hangul Word Processor format | Office documents are converted to PDF internally by PyMuPDF Pro before extraction. This means all PyMuPDF4LLM features work on Office files exactly as they do on PDFs. *** ## Installation Install PyMuPDF Pro: ```bash theme={null} pip install pymupdfpro ``` PyMuPDF Pro requires a valid licence key. [Request a trial or purchase a licence](https://pymupdf.readthedocs.io/en/latest/pymupdf-pro/) from the PyMuPDF website. *** ## Usage ### Trial Keys Without a valid licence key, PyMuPDF Pro functionality is restricted to only the first 3 pages of any document. This applies to all supported formats, including PDFs. To unlock full functionality you should [obtain a trial key](https://pymupdf.pro/try-pro/). To obtain a trial license key [please fill out the form on this page](https://pymupdf.pro/try-pro/). You will then have the trial key emailed to the address you submitted. Trial keys are valid for 60 days and allow you to test the full functionality of PyMuPDF Pro on any document. This is ideal for evaluation and development purposes. ### Activating Your Licence Activate the licence explicitly at the start of your script: ```python theme={null} import pymupdf.pro pymupdf.pro.unlock("your-licence-key-here") ``` Call `unlock()` once before making any extraction calls. A good place to do this is at application startup or in your environment initialisation. Never hardcode your licence key directly in source code that will be committed to version control. Use environment variables or a secrets manager instead. ### Commercial License Keys Commercial licence keys are also supported. If you have a commercial key, simply pass it to `unlock()` instead of the trial key. Commercial keys do not have the time limit restriction and may also include additional features or support options. [Contact the PyMuPDF sales team](https://artifex.com/contact/pymupdf-pro) for more information on commercial licences. Need a Commercial Licence for **PyMuPDF Pro**? Contact the sales team to discuss options and pricing. *** ## Extracting Office Documents Once Pro is activated, pass Office files to any extraction function exactly as you would a PDF: ### Word Documents ```python theme={null} import pymupdf.pro import pymupdf4llm pymupdf.pro.unlock() md_text = pymupdf4llm.to_markdown("contract.docx") print(md_text) ``` ### PowerPoint Presentations ```python theme={null} # Each slide is treated as a page chunks = pymupdf4llm.to_markdown("presentation.pptx", page_chunks=True) for chunk in chunks: print(f"Slide {chunk['metadata']['page'] + 1}") print(chunk["text"]) print("---") ``` ### Excel Spreadsheets ```python theme={null} # Each sheet is treated as a page; tables are rendered as Markdown tables md_text = pymupdf4llm.to_markdown("data.xlsx") print(md_text) ``` ### Hangul Documents ```python theme={null} md_text = pymupdf4llm.to_markdown("korean.hwpx") print(md_text) ``` *** ## Converting an Office document to PDF The following code snippet can convert your Office document to PDF format: ```python theme={null} import pymupdf.pro pymupdf.pro.unlock() doc = pymupdf.open("my-office-doc.xlsx") pdfdata = doc.convert_to_pdf() with open('output.pdf', 'wb') as f: f.write(pdfdata) ``` *** ## Using All Standard Options Because Office documents are converted to PDF internally, every standard PyMuPDF4LLM option works without modification: ```python theme={null} import pymupdf.pro import pymupdf4llm from pathlib import Path pymupdf.pro.unlock() # Layout analysis, image extraction, and page chunks on a Word doc chunks = pymupdf4llm.to_markdown( "annual-report.docx", page_chunks=True, write_images=True, image_path="output/images/", image_format="png", dpi=150 ) Path("output/images").mkdir(parents=True, exist_ok=True) for chunk in chunks: page = chunk["metadata"]["page"] Path(f"output/page-{page}.md").write_text(chunk["text"], encoding="utf-8") ``` *** ## Processing a Mixed Document Library With Pro activated you can process a folder containing a mix of PDFs and Office files using the same code path: ```python theme={null} import pymupdf.pro import pymupdf4llm from pathlib import Path pymupdf.pro.unlock() SUPPORTED = {".pdf", ".docx", ".doc", ".pptx", ".ppt", ".xlsx", ".xls", ".hwpx", ".hwp"} input_dir = Path("documents/") output_dir = Path("extracted/") output_dir.mkdir(parents=True, exist_ok=True) for file_path in input_dir.iterdir(): if file_path.suffix.lower() not in SUPPORTED: continue print(f"Processing {file_path.name}...") try: md_text = pymupdf4llm.to_markdown(str(file_path)) out = output_dir / file_path.with_suffix(".md").name out.write_text(md_text, encoding="utf-8") print(f" ✓ Saved to {out}") except Exception as e: print(f" ✗ Failed: {e}") ``` *** ## PyMuPDF Pro and Fonts By default `pymupdf.pro.unlock()` searches for all installed font directories. This can be controlled with keyword-only args: * `fontpath`: specific font directories, either as a list/tuple or `os.sep`-separated string. * `None` (the default) * If not `None` we use the value set in `os.environ['PYMUPDFPRO_FONT_PATH']`. * `fontpath_auto`: Whether to append system font directories. * `None` (the default) * We use `True` if `os.environ['PYMUPDFPRO_FONT_PATH_AUTO']` is `1`, then all system font directories are appended. Function `pymupdf.pro.get_fontpath()` returns a tuple of all font directories used by `unlock()`. ## Next Steps Load Office documents into LangChain pipelines. Full list of supported input and output formats. All to\_markdown() options that work with Office files. # JSON Schema Source: https://docs.pdf4llm.com/python/reference/JSON-schema Full field reference for the structured output returned by [to_json()](/python/api/to_json).
## Overview `to_json()` returns a list of page objects — one per extracted page. Each page contains a list of blocks (`boxes`), and each block contains type-specific fields. This page documents every object and field in the output hierarchy. PyMuPDF4LLM JSON Schema Diagram ```json theme={null} { "filename": "hello-world.pdf", "page_count": 2, "toc": [], "pages": [ { "page_number": 1, "width": 595.2000122070312, "height": 841.9199829101562, "boxes": [ { "x0": 72, "y0": 71.99996948242188, "x1": 334.470947265625, "y1": 273.3801574707031, "boxclass": "picture", "image": "images/hello-world.pdf-0001-00.png", "table": null, "textlines": [] }, { "x0": 70.69100189208984, "y0": 295.880126953125, "x1": 197.27691650390625, "y1": 304.62628173828125, "boxclass": "text", "image": null, "table": null, "textlines": [ { "bbox": [ 70.69100189208984, 295.880126953125, 197.27691650390625, 304.62628173828125 ], "spans": [ { "size": 12, "flags": 0, "bidi": 0, "char_flags": 16, "font": "Arial", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "Hello World!", "origin": [ 70.69100189208984, 304.469970703125 ], "bbox": [ 70.69100189208984, 295.880126953125, 136.09201049804688, 304.610595703125 ], "line": 0, "block": 0, "dir": [ 1, 0 ] }, { "size": 12, "flags": 20, "bidi": 0, "char_flags": 24, "font": "MinionPro-Bold", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "This is bold", "origin": [ 138.8310089111328, 304.469970703125 ], "bbox": [ 138.8310089111328, 296.0342712402344, 197.27691650390625, 304.62628173828125 ], "line": 0, "block": 0, "dir": [ 1, 0 ] } ] } ] } ], "full_ocred": false, "text_ocred": false, "fulltext": [ { "type": 0, "number": 0, "flags": 0, "bbox": [ 70.69100189208984, 295.880126953125, 197.27691650390625, 304.62628173828125 ], "lines": [ { "spans": [ { "size": 12, "flags": 0, "bidi": 0, "char_flags": 16, "font": "Arial", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "Hello World!", "origin": [ 70.69100189208984, 304.469970703125 ], "bbox": [ 70.69100189208984, 295.880126953125, 136.09201049804688, 304.610595703125 ], "line": 0, "block": 0, "dir": [ 1, 0 ] }, { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "MinionPro-Regular", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": " ", "origin": [ 136.09201049804688, 304.469970703125 ], "bbox": [ 136.09201049804688, 304.469970703125, 138.81600952148438, 304.469970703125 ] }, { "size": 12, "flags": 20, "bidi": 0, "char_flags": 24, "font": "MinionPro-Bold", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "This is bold", "origin": [ 138.8310089111328, 304.469970703125 ], "bbox": [ 138.8310089111328, 296.0342712402344, 197.27691650390625, 304.62628173828125 ], "line": 0, "block": 0, "dir": [ 1, 0 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 70.69100189208984, 295.880126953125, 197.27691650390625, 304.62628173828125 ] } ] } ], "words": [], "links": [] }, { "page_number": 2, "width": 595.2000122070312, "height": 841.9199829101562, "boxes": [ { "x0": 72, "y0": 72, "x1": 524, "y1": 118, "boxclass": "table", "image": null, "table": { "bbox": [ 71.15000104904175, 72.19200134277344, 523.219970703125, 117.67998962402343 ], "row_count": 3, "col_count": 4, "cells": [ [ [ 71.15000104904175, 72.19200134277344, 184.60000038146973, 87.3599967956543 ], [ 184.60000038146973, 72.19200134277344, 297.1599922180176, 87.3599967956543 ], [ 297.1599922180176, 72.19200134277344, 409.96001052856445, 87.3599967956543 ], [ 409.96001052856445, 72.19200134277344, 523.219970703125, 87.3599967956543 ] ], [ [ 71.15000104904175, 87.3599967956543, 184.60000038146973, 102.4799919128418 ], [ 184.60000038146973, 87.3599967956543, 297.1599922180176, 102.4799919128418 ], [ 297.1599922180176, 87.3599967956543, 409.96001052856445, 102.4799919128418 ], [ 409.96001052856445, 87.3599967956543, 523.219970703125, 102.4799919128418 ] ], [ [ 71.15000104904175, 102.4799919128418, 184.60000038146973, 117.67998962402343 ], [ 184.60000038146973, 102.4799919128418, 297.1599922180176, 117.67998962402343 ], [ 297.1599922180176, 102.4799919128418, 409.96001052856445, 117.67998962402343 ], [ 409.96001052856445, 102.4799919128418, 523.219970703125, 117.67998962402343 ] ] ], "extract": [ [ "A", "B", "C", "D" ], [ "A1", "B1", "C1", "D1" ], [ "A2", "B2", "C2", "D2" ] ], "markdown": "|A|B|C|D|\n|---|---|---|---|\n|A1|B1|C1|D1|\n|A2|B2|C2|D2|\n\n" }, "textlines": null } ], "full_ocred": false, "text_ocred": false, "fulltext": [ { "type": 0, "number": 0, "flags": 0, "bbox": [ 77.76000213623047, 75.767822265625, 426.34820556640625, 83.865478515625 ], "lines": [ { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "A ", "origin": [ 77.76000213623047, 83.760009765625 ], "bbox": [ 77.76000213623047, 75.873291015625, 87.26829528808594, 83.760009765625 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 77.76000213623047, 75.873291015625, 87.26829528808594, 83.760009765625 ] }, { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "B ", "origin": [ 190.32000732421875, 83.760009765625 ], "bbox": [ 190.32000732421875, 75.873291015625, 200.0041046142578, 83.760009765625 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 190.32000732421875, 75.873291015625, 200.0041046142578, 83.760009765625 ] }, { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "C ", "origin": [ 303.1199951171875, 83.760009765625 ], "bbox": [ 303.1199951171875, 75.767822265625, 313.86480712890625, 83.865478515625 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 303.1199951171875, 75.767822265625, 313.86480712890625, 83.865478515625 ] }, { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "D ", "origin": [ 415.67999267578125, 83.760009765625 ], "bbox": [ 415.67999267578125, 75.873291015625, 426.34820556640625, 83.760009765625 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 415.67999267578125, 75.873291015625, 426.34820556640625, 83.760009765625 ] } ] }, { "type": 0, "number": 11, "flags": 0, "bbox": [ 77.76000213623047, 90.8878173828125, 432.7583923339844, 98.9854736328125 ], "lines": [ { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "A1 ", "origin": [ 77.76000213623047, 98.8800048828125 ], "bbox": [ 77.76000213623047, 90.9932861328125, 93.67839813232422, 98.8800048828125 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 77.76000213623047, 90.9932861328125, 93.67839813232422, 98.8800048828125 ] }, { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "B1 ", "origin": [ 190.32000732421875, 98.8800048828125 ], "bbox": [ 190.32000732421875, 90.9932861328125, 206.414306640625, 98.8800048828125 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 190.32000732421875, 90.9932861328125, 206.414306640625, 98.8800048828125 ] }, { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "C1 ", "origin": [ 303.1199951171875, 98.8800048828125 ], "bbox": [ 303.1199951171875, 90.8878173828125, 320.2749938964844, 98.9854736328125 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 303.1199951171875, 90.8878173828125, 320.2749938964844, 98.9854736328125 ] }, { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "D1 ", "origin": [ 415.67999267578125, 98.8800048828125 ], "bbox": [ 415.67999267578125, 90.9932861328125, 432.7583923339844, 98.8800048828125 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 415.67999267578125, 90.9932861328125, 432.7583923339844, 98.8800048828125 ] } ] }, { "type": 0, "number": 22, "flags": 0, "bbox": [ 77.76000213623047, 106.0078125, 432.7583923339844, 114.10546875 ], "lines": [ { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "A2 ", "origin": [ 77.76000213623047, 114 ], "bbox": [ 77.76000213623047, 106.11328125, 93.67839813232422, 114 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 77.76000213623047, 106.11328125, 93.67839813232422, 114 ] }, { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "B2 ", "origin": [ 190.32000732421875, 114 ], "bbox": [ 190.32000732421875, 106.11328125, 206.414306640625, 114 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 190.32000732421875, 106.11328125, 206.414306640625, 114 ] }, { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "C2 ", "origin": [ 303.1199951171875, 114 ], "bbox": [ 303.1199951171875, 106.0078125, 320.2749938964844, 114.10546875 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 303.1199951171875, 106.0078125, 320.2749938964844, 114.10546875 ] }, { "spans": [ { "size": 12, "flags": 4, "bidi": 0, "char_flags": 16, "font": "Aptos", "color": 0, "alpha": 255, "ascender": 0.800000011920929, "descender": -0.20000000298023224, "text": "D2 ", "origin": [ 415.67999267578125, 114 ], "bbox": [ 415.67999267578125, 106.11328125, 432.7583923339844, 114 ] } ], "wmode": 0, "dir": [ 1, 0 ], "bbox": [ 415.67999267578125, 106.11328125, 432.7583923339844, 114 ] } ] } ], "words": [], "links": [] } ], "metadata": { "format": "PDF 1.6", "title": "", "author": "", "subject": "", "keywords": "", "creator": "", "producer": "", "creationDate": "D:20240722172345Z", "modDate": "D:20260318153118Z", "trapped": "", "encryption": null } } ``` The extraction response is a single JSON object describing a parsed PDF — its pages, text content, tables, images, and metadata. This page documents every object and field in that structure with positional data. Positional coordinates are in PDF points (1 point = 1/72 inch). The origin `(0, 0)` is the **top-left** corner of the page. ## Root object The top-level object returned for every extraction. ```json theme={null} { "filename": "hello-world.pdf", "page_count": 2, "toc": [], "pages": [...], "metadata": {...} } ``` The name of the source PDF file that was parsed. Total number of pages in the PDF. Table of contents entries extracted from the PDF. Each entry is a tuple of `[page_index, title, page_number]`. Empty when the PDF has no bookmarks or outline. Array of [page objects](#page-object), one per page in the PDF. PDF document metadata. See [metadata object](#metadata-object). *** ## Page object Represents a single page of the PDF. Found in `pages[]`. ```json theme={null} { "page_number": 1, "width": 595.2, "height": 841.92, "boxes": [...], "fulltext": [...], "full_ocred": false, "text_ocred": false, "words": [], "links": [] } ``` 1-based index of this page within the document. Page width in PDF user units (points). A standard A4 page is 595.28 pt wide. Page height in PDF user units (points). A standard A4 page is 841.89 pt tall. Detected content regions on the page. Each entry is a [box object](#box-object). Boxes may be classified as `text`, `picture`, or `table`. Raw text blocks extracted directly from the PDF's content stream, independent of the box layout. Each entry is a [fulltext block](#fulltext-block). This mirrors the logical reading order as encoded in the PDF. `true` if the entire page was processed through OCR because no native text layer was found. `true` if individual text regions were OCR'd (as opposed to full-page OCR). Word-level bounding boxes. Empty in this format variant. Hyperlinks found on the page. Empty when no links are present. *** ## Box object A detected content region on a page. Found in `pages[].boxes[]`. Boxes are the primary layout unit. Each box covers a rectangular area and is classified into one of these types: ```text theme={null} text picture table caption title section-header page-header page-footer list-item footnote formula ``` ```json theme={null} { "x0": 70.69, "y0": 295.88, "x1": 197.28, "y1": 304.63, "boxclass": "text", "image": null, "table": null, "textlines": [...] } ``` ```json theme={null} { "x0": 72, "y0": 72, "x1": 334.47, "y1": 273.38, "boxclass": "picture", "image": "images/hello-world.pdf-0001-00.png", "table": null, "textlines": [] } ``` ```json theme={null} { "x0": 72, "y0": 72, "x1": 524, "y1": 118, "boxclass": "table", "image": null, "table": {...}, "textlines": null } ``` Left edge of the box in PDF points, measured from the left of the page. Top edge of the box in PDF points, measured from the top of the page. Right edge of the box in PDF points. Bottom edge of the box in PDF points. Classification of the content region. One of: * `"text"` — contains text lines and spans * `"picture"` — contains an embedded image * `"table"` — contains a detected table structure Relative path to the extracted image file when `boxclass` is `"picture"`. `null` for all other box types. A [table object](#table-object) when `boxclass` is `"table"`. `null` for all other box types. Array of [textline objects](#textline-object) when `boxclass` is `"text"`. Empty array `[]` for picture boxes. `null` for table boxes. *** ## Table object Structured data for a detected table. Found in `boxes[].table` when `boxclass` is `"table"`. ```json theme={null} { "bbox": [71.15, 72.19, 523.22, 117.68], "row_count": 3, "col_count": 4, "cells": [ [[71.15, 72.19, 184.6, 87.36], [184.6, 72.19, 297.16, 87.36], ...], ... ], "extract": [ ["A", "B", "C", "D"], ["A1", "B1", "C1", "D1"], ["A2", "B2", "C2", "D2"] ], "markdown": "|A|B|C|D|\n|---|---|---|---|\n|A1|B1|C1|D1|\n|A2|B2|C2|D2|\n\n" } ``` Bounding box of the entire table as `[x0, y0, x1, y1]` in PDF points. Number of rows in the table, including any header row. Number of columns in the table. A 3D array of cell bounding boxes: `cells[row][col]` gives `[x0, y0, x1, y1]` for that cell in PDF points. Useful for mapping extracted text back to exact cell positions on the page. A 2D array of the cell text values: `extract[row][col]` gives the string content of that cell. The first row is typically the header row. The table rendered as a Markdown pipe table string, ready for display or further processing. *** ## Textline object A single line of text within a box. Found in `boxes[].textlines[]`. ```json theme={null} { "bbox": [70.69, 295.88, 197.28, 304.63], "spans": [...] } ``` Bounding box of this text line as `[x0, y0, x1, y1]` in PDF points. Array of [span objects](#span-object). A single line is typically split into multiple spans wherever the font, size, or style changes. *** ## Span object The smallest unit of text, sharing a single consistent style. Found in `textlines[].spans[]` and `fulltext[].lines[].spans[]`. A span break occurs at any change of font, size, weight, colour, or style — so a line reading "Hello World! **This is bold**" would produce two separate spans. See [Font Flags Reference](/python/guides/extract-JSON#font-flags-reference) for how to interpret the `flags` field. ```json theme={null} { "size": 12, "flags": 0, "bidi": 0, "char_flags": 16, "font": "Arial", "color": 0, "alpha": 255, "ascender": 0.8, "descender": -0.2, "text": "Hello World!", "origin": [70.69, 304.47], "bbox": [70.69, 295.88, 136.09, 304.61], "line": 0, "block": 0, "dir": [1, 0] } ``` ```json theme={null} { "size": 12, "flags": 16, "bidi": 0, "char_flags": 24, "font": "MinionPro-Bold", "color": 0, "alpha": 255, "ascender": 0.8, "descender": -0.2, "text": "This is bold", "origin": [138.83, 304.47], "bbox": [138.83, 296.03, 197.28, 304.63], "line": 0, "block": 0, "dir": [1, 0] } ``` The actual text content of this span. Full PostScript font name, e.g. `"Arial"`, `"MinionPro-Bold"`, `"Aptos"`. The font name often encodes weight and style (e.g. `-Bold`, `-It`). Font size in points. Bitmask of font style flags from the PDF spec. Common values: * `0` — regular * `4` — italic (bit 2) * `16` — bold (bit 4) * `20` — bold + italic (bits 2 and 4) Additional character flags - please refer to [this enumeration](https://github.com/ArtifexSoftware/mupdf/blob/66ef5879c18bc7cc0831fd9b915b257ab717b79e/include/mupdf/fitz/structured-text.h#L489) for details. Text colour as a packed RGB integer. `0` is black (`#000000`). Opacity of the text, from `0` (transparent) to `255` (fully opaque). Font ascender as a fraction of the font size. Typically `0.8`, meaning the ascender reaches 80% of the em above the baseline. Font descender as a fraction of the font size. Typically `-0.2`, meaning the descender extends 20% of the em below the baseline. Tight bounding box of the rendered glyphs as `[x0, y0, x1, y1]` in PDF points. The text origin point `[x, y]` — the position of the baseline at the start of the span, in PDF points. Unicode bidirectional level. `0` for left-to-right text. Index of the line this span belongs to within its parent block. Index of the block this span belongs to within the page's content stream. Text direction as a unit vector `[x, y]`. `[1, 0]` is standard left-to-right horizontal text. `[0, -1]` would indicate top-to-bottom vertical text. *** ## Fulltext block A raw text block from the PDF content stream, independent of visual layout. Found in `pages[].fulltext[]`. The `fulltext` array captures text in the order it appears in the PDF's internal stream, which may differ from the visual reading order. Each block contains one or more lines, and each line contains spans. ```json theme={null} { "type": 0, "number": 0, "flags": 0, "bbox": [70.69, 295.88, 197.28, 304.63], "lines": [ { "spans": [...], "wmode": 0, "dir": [1, 0], "bbox": [70.69, 295.88, 197.28, 304.63] } ] } ``` Block type from the PDF spec. `0` indicates a text block. Sequential index of this block within the page's content stream. Block-level flags. `0` for standard text blocks. Bounding box of the entire block as `[x0, y0, x1, y1]` in PDF points. Array of line objects within this block. Each line has: * `spans` — array of [span objects](#span-object) * `wmode` — writing mode (`0` = horizontal, `1` = vertical) * `dir` — line direction vector, e.g. `[1, 0]` for left-to-right * `bbox` — bounding box of the line as `[x0, y0, x1, y1]` *** ## Metadata object PDF document-level metadata. Found at the root as `metadata`. ```json theme={null} { "format": "PDF 1.6", "title": "", "author": "", "subject": "", "keywords": "", "creator": "", "producer": "", "creationDate": "D:20240722172345Z", "modDate": "D:20260318153118Z", "trapped": "", "encryption": null } ``` PDF version string, e.g. `"PDF 1.4"` or `"PDF 1.6"`. Document title as set in the PDF's document properties. Empty string if not set. Document author as set in the PDF's document properties. Empty string if not set. Document subject. Empty string if not set. Keywords associated with the document. Empty string if not set. The application that originally created the document (before any PDF conversion), e.g. `"Microsoft Word"`. Empty string if not set. The application that produced or last saved the PDF file, e.g. `"macOS Quartz PDFContext"`. Empty string if not set. Creation timestamp in PDF date format: `D:YYYYMMDDHHmmSSOHH'mm'`. Example: `"D:20240722172345Z"` = 22 July 2024, 17:23:45 UTC. Last modification timestamp in the same PDF date format. PDF trapping status. Rarely set in practice; empty string if not applicable. Encryption details if the PDF is encrypted. `null` for unencrypted documents. ## See Also Schema for `page_chunks=True` output from `to_markdown()`. Working walkthrough with filtering and pipeline examples. Full API reference for to\_json(). Extracting and working with table blocks. # Changelog Source: https://docs.pdf4llm.com/python/reference/changelog Version history and release notes for PyMuPDF4LLM.
## 1.27.2.2 Major rework of OCR support: * Tesseract-OCR is now supported as a plugin in the ocr installation folder. * OCR support has been reworked to automatically choose the most appropriate OCR engine combination, depending on the availability of Python package `rapidocr_onnxruntime` and Tesseract's language support files ("tessdata"). * Parameter `force_ocr=True` does no longer require to specify `ocr_function`. If no OCR function is given, the best available plugin is chosen. An exception is raised only if none of the plugins is usable. ## 1.27.2.1 PyMuPDF4LLM now automatically installs and uses `pymupdf_layout`. * Installing `pymupdf4llm` automatically installs `pymupdf_layout`. Exact versions of both `pymupdf` and `pymupdf_layout` are now pinned (previously `pymupdf>=1.27.1` was used). * `import pymupdf4llm` automatically initialises layout support. * Layout can be disabled by calling `pymupdf4llm.use_layout(False)`. *** ## 0.3.4 * [#356](https://github.com/pymupdf/pymupdf4llm/discussions/356) — Page chunk output under `to_text()` may fail for erroneous layout bboxes. * Added support for RapidOCR via a callable plugin. * Added support for improved OCR via a combination of RapidOCR and Tesseract-OCR. * Changed default DPI for OCR to `300` (was `400`). * Added new parameter `ocr_function=None`. When not `None`, must be a callable that OCRs the page by giving it a text layer. * Added new parameter `force_ocr=False` to all extraction functions. Requires `ocr_function` to be set. When `True`, `ocr_function` is called for every page, bypassing the standard OCR worthiness check. *** ## 0.2.9 * [#356](https://github.com/pymupdf/pymupdf4llm/discussions/356) — Page chunk output under `to_text()` may fail for erroneous layout bboxes. * [#355](https://github.com/pymupdf/pymupdf4llm/issues/355) — Image saving fails if the document filename contains folder specifications. * Added new top-level function `get_key_values()` to extract field names and values from Form PDFs. Always available regardless of whether PyMuPDF-Layout is active. * **Removed** OpenCV dependency. Previously used to determine whether a page is worthwhile OCR'ing — replaced with NumPy for these checks. *** ## 0.2.8 * [#349](https://github.com/pymupdf/pymupdf4llm/discussions/349) — Is it possible to change the OCR language when using `-layout`? * [#352](https://github.com/pymupdf/pymupdf4llm/issues/352) — Does not respect the `image_path` keyword argument when `write_images=True`. * [#353](https://github.com/pymupdf/pymupdf4llm/issues/353) — How do I filter out pixmaps with non-empty size but empty value? * Added new parameter `ocr_language`. A string passed directly to Tesseract-OCR — the user is responsible for correct Tesseract language code formatting. * Changed the format of the `"page_boxes"` key in page chunk dictionaries (layout mode). Now a **list of dictionaries** (was a list of lists). Each dictionary contains: * `"index"` — 0-based integer enumerating layout boxes in reading order * `"class"` — string denoting the bbox class (`"table"`, `"list-item"`, `"section-header"`, etc.) * `"bbox"` — `pymupdf.IRect` of the layout boundary box * `"pos"` — `(start, stop)` tuple for slicing the bbox text from `chunk["text"]` * Multiple performance improvements, primarily around rectangle containment checks. *** ## 0.2.7 * [#323](https://github.com/pymupdf/pymupdf4llm/issues/323) — `page_chunks=True` parameter was ignored in PyMuPDF-Layout mode. * `to_markdown()` and `to_text()` now both support page chunk output via `page_chunks=True`. *** ## 0.2.6 * [Forum](https://forum.mupdf.com/t/bug-pymupdf4llm-list-index-out-of-range-in-document-layout-py-2/216) — List index out of range in `document_layout.py`. *** ## 0.2.5 * [#341](https://github.com/pymupdf/RAG/issues/341) — Broken Markdown parsing for a new line directly followed by `'o'`. * New parameter `table_format` in `to_text()` (PyMuPDF-Layout only). Controls the appearance of tables in plain text output. Possible values are defined in `tabulate.tabulate_formats`. Default is `"grid"`. * Optional dependencies can now be installed together: `pip install pymupdf4llm[ocr,layout]`. The `"ocr"` extra installs `opencv-python` for automatic OCR support in PyMuPDF-Layout mode. * Major rework of the heuristics that determine whether a page should be OCR'd. *** ## 0.2.4 * [#335](https://github.com/pymupdf/RAG/issues/335) — `KeyError: "has_ocr_text"`. *** ## 0.2.3 * [#332](https://github.com/pymupdf/RAG/issues/332) — `TypeError: to_markdown() got an unexpected keyword argument 'header'`. * Output methods now accept a new parameter `ocr_dpi=400` which sets the OCR resolution for full-page OCR. * The OCR detection heuristics are more fine-grained and now detect more OCR-worthy situations. * Resolved multiple performance issues, specifically for documents with very many images and extremely large `StructTreeRoot` objects. * Reflected layout-specific API changes in legacy code — `NotImplementedError` is now raised when layout-only features are used outside of layout mode. * Information messages during document parsing are now written to stdout collectively at the end of the parsing phase. * Added support for the `page_separators` parameter in legacy mode. *** ## 0.2.1 * [#320](https://github.com/pymupdf/RAG/issues/320) — `ValueError: min() iterable argument is empty`. * [#319](https://github.com/pymupdf/RAG/issues/319) — `ValueError: min() arg is an empty sequence`. * OCR invocation now differentiates between full-page OCR and text-only OCR. If a page contains text but the percentage of unreadable characters exceeds 90%, only the affected text span bounding boxes are OCR'd and replaced — rather than the whole page. *** ## 0.2.0 This release introduces full support for the [PyMuPDF-Layout](https://pypi.org/project/pymupdf-layout/) package — a radically new AI-based approach for detecting document page layouts. **Highlights:** * Greatly improved table detection * Support for list item hierarchy levels * Detection of page headers and footers * Improved detection of text paragraphs, titles, and section headers * New output options beyond Markdown: plain text (`to_text()`) and structured JSON (`to_json()`) * Automatic OCR detection — invokes Tesseract when the page has little or no readable text, is mostly covered by images, or contains many character-sized vector graphics (requires Tesseract and `opencv-python`) PyMuPDF-Layout is not open-source and carries its own licence. It also requires additional packages including `onnxruntime`, `numpy`, `sympy`, and `opencv-python`. Layout support remains opt-in. To activate it, import `pymupdf_layout` **before** importing `pymupdf4llm`: ```python theme={null} import pymupdf_layout import pymupdf4llm ``` * When `show_progress=True`, the [`tqdm`](https://pypi.org/project/tqdm/) package is used automatically if installed. Falls back to a built-in text-based progress bar if not available. *** ## 0.0.27 * [#296](https://github.com/pymupdf/RAG/issues/296) — A specific diagram incorrectly recognised as significant. * [#294](https://github.com/pymupdf/RAG/issues/294) — Unable to extract images from page. * [#272](https://github.com/pymupdf/RAG/issues/272) — Disappeared page breaks. * New parameter `page_separators=False` in `to_markdown()`. When `True` and `page_chunks=False`, a line `--- end of page=nnn ---` is appended to each page's Markdown text. Page number is 0-based. Intended for debugging purposes. *** ## 0.0.26 * [#289](https://github.com/pymupdf/RAG/issues/289) — Content duplication with the latest version. * [#275](https://github.com/pymupdf/RAG/issues/275) — Text with background missing from output. * [#262](https://github.com/pymupdf/RAG/issues/262) — Markdown error parsing. * The PyMuPDF table module's `to_markdown()` now outputs Markdown-styled cell text. Previously, table cells were extracted as plain text only. * `TocHeaders` is now a top-level import and can be used directly. * New parameter `detect_bg_color=True` in `to_markdown()`. Guesses the page background colour and ignores fill-only vectors matching it. Set to `False` to always consider fill vectors. * Text written with a `Type 3` font is now always included. Previously it was treated as invisible and suppressed. * Package now includes the GNU AGPL 3.0 licence file. PyMuPDF4LLM is dual-licensed under GNU AGPL 3.0 and individual commercial licences. * Added `versions_file.py` to enforce a minimum PyMuPDF version at import time. *** ## 0.0.25 * [#282](https://github.com/pymupdf/RAG/issues/282) — Content duplication with the latest version. * [#281](https://github.com/pymupdf/RAG/issues/281) — Latest version returns empty text for some PDFs. * [#280](https://github.com/pymupdf/RAG/issues/280) — Cannot extract text when `ignore_images=False`. * [#278](https://github.com/pymupdf/RAG/issues/278) — Title words are fragmented. * [#249](https://github.com/pymupdf/RAG/issues/249) — Title duplication in Markdown format. * [#202](https://github.com/pymupdf/RAG/issues/202) — Bad rect issue. * Table module `to_markdown()` now outputs Markdown-styled cell text. * `TocHeaders` is now a top-level import. * Text written with a `Type 3` font is now always included. *** ## 0.0.24 * Fixed `UnboundLocalError`. *** ## 0.0.23 * [#265](https://github.com/pymupdf/RAG/issues/265) — Code error correction. * [#263](https://github.com/pymupdf/RAG/issues/263) — `table_strategy=None` raises an error. * [#261](https://github.com/pymupdf/RAG/issues/261) — Wrong Markdown output in latest PyMuPDF versions. * High-speed vector graphics count: when `graphics_limit` is set, drawings are no longer extracted just for counting purposes. *** ## 0.0.22 * [#251](https://github.com/pymupdf/RAG/issues/251) — Images slightly larger than the page size are being ignored. * [#255](https://github.com/pymupdf/RAG/issues/255) — Single-row or single-column tables are skipped. * [#258](https://github.com/pymupdf/RAG/issues/258) — `to_markdown()` crashes on some documents. * Added class `TocHeaders` as an alternative way to identify headers. *** ## 0.0.21 * [#116](https://github.com/pymupdf/RAG/issues/116) — Handling graphical images and superscripts. *** ## 0.0.20 * [#171](https://github.com/pymupdf/RAG/issues/171) — Text rects overlap with tables and images that should be excluded. * [#189](https://github.com/pymupdf/RAG/issues/189) — The position of the extracted image is incorrect. * [#238](https://github.com/pymupdf/RAG/issues/238) — Text extraction missing when text is laid out around a picture. * New parameter `ignore_images` (bool). When `True`, images are not considered in any way. Useful for pages dense with images that prevent meaningful layout analysis (e.g. PowerPoint slides). * New parameter `ignore_graphics` (bool). When `True`, vector graphics are not considered except for table detection. Useful for pages dense with vector graphics (e.g. PowerPoint slides). * New parameter `max_levels` on `IdentifyHeaders`. Limits the number of header tag levels generated. Example: `IdentifyHeaders(doc, max_levels=3)` ensures at most three header levels are produced. * `table_strategy=None` now disables table detection entirely, which can significantly speed up processing on documents without tables. *** ## 0.0.19 Includes fixes from v0.0.18. * [#158](https://github.com/pymupdf/RAG/issues/158) — Very long titles when converting to Markdown. * [#155](https://github.com/pymupdf/RAG/issues/155) — Inconsistent image extraction from image-only PDFs. * [#161](https://github.com/pymupdf/RAG/issues/161) — `force_text` parameter ignored. * [#162](https://github.com/pymupdf/RAG/issues/162) — `to_markdown()` not outputting all pages. * [#173](https://github.com/pymupdf/RAG/issues/173) — First column of table repeated before the actual table. * [#187](https://github.com/pymupdf/RAG/issues/187) — Unsolicited text particles. * [#188](https://github.com/pymupdf/RAG/issues/188) — Slow conversion to Markdown. * [#191](https://github.com/pymupdf/RAG/issues/191) — Text extraction stops mid-document. * [#212](https://github.com/pymupdf/RAG/issues/212) — Only one image extracted per page when multiple exist. * [#213](https://github.com/pymupdf/RAG/issues/213) — Replacement characters (�) appear after conversion. * [#215](https://github.com/pymupdf/RAG/issues/215) — Excessive time spent identifying text bboxes. * [#218](https://github.com/pymupdf/RAG/issues/218) — `IndexError` in `get_raw_lines` when processing PDFs with formulas. * [#225](https://github.com/pymupdf/RAG/issues/225) — Text with background missing from output. * [#229](https://github.com/pymupdf/RAG/issues/229) — Duplicated table content. * New parameter `filename` (str). Overwrites or sets the filename for saved images. Useful when the document is opened from memory. * New parameter `use_glyphs` (bool). When `True`, uses the glyph number of a character for fonts without a Unicode back-translation. Default `False` renders `�` in these cases. * Added **strikethrough support** — striked-out text is now detected and rendered as `~~text~~`. * Improved **background colour detection** — if all four page corners share the same colour, that colour is assumed to be the background. Text and vectors in that colour are ignored. * Improved **invisible text detection** — text with an alpha value of `0` is now ignored. * Improved **fake-bold detection** — text mimicking bold appearance is now treated as standard bold in most cases. * Header detection now uses the **largest font size** on the line. All spans in a header line are rendered with uniform appearance. * Changed `graphics_limit` behaviour: previously, exceeding the limit caused the entire page to be skipped. Now only vector graphics **outside table bounding boxes** are ignored — images, text, and table content remain extractable. * Changed default for `margins` to `0`. The previous default `(0, 50, 0, 50)` caused confusion by silently ignoring 50pt at the top and bottom of pages. *** ## 0.0.17 * [#147](https://github.com/pymupdf/RAG/issues/147) — Error when page contains nothing but a table. * [#81](https://github.com/pymupdf/RAG/issues/81) — Issues with bullet points in PDFs. * [#78](https://github.com/pymupdf/RAG/issues/78) — Multi-column PDF text extraction. *** ## 0.0.15 * [#138](https://github.com/pymupdf/RAG/issues/138) — Table not extracted and some text order incorrect. * [#135](https://github.com/pymupdf/RAG/issues/135) — Problem with multiple columns in simple text. * [#134](https://github.com/pymupdf/RAG/issues/134) — Exclude images based on size threshold parameter. * [#132](https://github.com/pymupdf/RAG/issues/132) — Optionally embed images as base64 string. * [#128](https://github.com/pymupdf/RAG/issues/128) — Enhanced image embedding format. * New parameter `embed_images` (bool). Embeds images and vector graphics in the Markdown text as base64-encoded strings. Ignores `write_images` and `image_path`. * New parameter `image_size_limit` (float, default `0.05`). Images are ignored if their width or height is smaller than the corresponding 5% fraction of the page dimensions. * Improved algorithm for determining text rectangle sequence on multi-column pages. * Header identification change: if more than six header levels are needed, all text larger than body text is treated as level 6 (`######`). *** ## 0.0.13 * [#112](https://github.com/pymupdf/RAG/issues/112) — Invalid bandwriter header dimensions/setup. * New parameter `ignore_code`. Suppresses special formatting of monospaced text — no code blocks are generated. * New parameter `extract_words`. Enforces `page_chunks=True` and adds a `"words"` list to each page dictionary. *** ## 0.0.11 * [#90](https://github.com/pymupdf/RAG/issues/90) — `'Quad' object has no attribute 'tl'`. * [#88](https://github.com/pymupdf/RAG/issues/88) — Bug in `is_significant` function. * Extended the list of recognised bullet point characters. *** ## 0.0.10 * [#73](https://github.com/pymupdf/RAG/issues/73) — Bug in `to_markdown` internal function. * [#74](https://github.com/pymupdf/RAG/issues/74) — Minimum area for images and vector graphics. * [#75](https://github.com/pymupdf/RAG/issues/75) — Poor Markdown generation for a particular PDF. * [#76](https://github.com/pymupdf/RAG/issues/76) — Suggestion on useful API parameters. * Improved recognition of insignificant vector graphics — highlights and borders are now ignored. * New parameter `image_format` to control the format of saved images. * New parameter `image_path` to store images in a specific folder. * Images are not stored if they are contained within another image on the same page. * Images are not stored if their width or height is less than 5% of the corresponding page dimension. * All text is always written. When `write_images=True`, text on images or graphics can be suppressed by setting `force_text=False`. *** ## 0.0.9 * [#71](https://github.com/pymupdf/RAG/issues/71) — Unexpected results in `pymupdf4llm` when `pymupdf` works correctly. * [#68](https://github.com/pymupdf/RAG/issues/68) — Issue with text extraction near page footer. * Improved identification of scattered text span particles, addressing most out-of-sequence issues. * Rotated pages are now correctly processed. *** ## 0.0.8 * [#65](https://github.com/pymupdf/RAG/issues/65) — Fixed typo in `pymupdf_rag.py`. *** ## 0.0.7 * [#54](https://github.com/pymupdf/RAG/issues/54) — Mistakes in orchestrating sentences. Text extraction no longer uses the `TEXT_DEHYPHENATE` flag. * Improved vector graphics algorithm. Graphics with strokes only near the boundary box border (common in code snippets) are now more reliably classified as irrelevant. *** ## 0.0.6 * [#55](https://github.com/pymupdf/RAG/issues/55) — `IndexError: list index out of range` in `helpers/multi_column.py`. * [#54](https://github.com/pymupdf/RAG/issues/54) — Mistakes in orchestrating sentences. * [#52](https://github.com/pymupdf/RAG/issues/52) — Chunking of text files. * [#41](https://github.com/pymupdf/RAG/issues/41) / [#40](https://github.com/pymupdf/RAG/issues/40) — Improved page column detection (partial fix; complex layouts remain a challenge). * New parameter `dpi` to specify the resolution of extracted images. * New parameters `page_width` and `page_height` for processing reflowable documents (text files, Office, e-books). * New parameter `graphics_limit` to avoid spending runtime on low-value vector graphics content. * New parameter `table_strategy` to directly control the table detection strategy. # Chunk Schema Source: https://docs.pdf4llm.com/python/reference/chunk-schema Full dictionary schema for each page chunk returned when `page_chunks=True`.
## Overview When `page_chunks=True` is passed to [to\_markdown()](../api/to_markdown) or [to\_text()](../api/to_text), the return value is a list of dictionaries — one per page — rather than a single concatenated string. Each dictionary follows the schema described on this page. PyMuPDF4LLM Chunk Schema Diagram ### Iterating over chunks To quickly see the structure of each chunk, you can iterate over the list and print the keys of each dictionary: ```python theme={null} chunks = pymupdf4llm.to_markdown("document.pdf", page_chunks=True) for chunk in chunks: for key in chunk: print(key) print("----") print (chunk[key]) ``` ### Why use page chunks? Page chunking is the recommended approach for any pipeline that needs to process, search, or embed a PDF's content — rather than working with one giant string, you get a structured list where each page is a self-contained unit carrying both its text and the metadata needed to make that text useful. This matters most in RAG applications, where you need to attach source information (file path, page number, document title) to every embedded chunk so that retrieved passages can be traced back to their origin. The layout data in `page_boxes` adds another layer of utility — you can filter out headers, footers, and captions before embedding, or treat tables and body text differently depending on your retrieval strategy. Rather than post-processing a flat markdown string and trying to guess where page boundaries or section headings fall, chunking gives you that structure for free, directly from the PDF's own layout engine. **Example: Extracting page numbers and first 100 characters of text from each chunk** ```python theme={null} import pymupdf4llm chunks = pymupdf4llm.to_markdown("document.pdf", page_chunks=True) for chunk in chunks: print(chunk["metadata"]["page_number"], chunk["text"][:100]) ``` This is the recommended approach for RAG pipelines, as it lets you attach rich metadata to each piece of content before embedding or indexing it. *** ## Chunk schema Each item in the returned list is a dictionary with four top-level keys: ```python theme={null} { "metadata": { ... }, # Document and page-level info "toc_items": [ ... ], # Table of contents entries for this page "page_boxes": [ ... ], # Layout elements detected on this page "text": "..." # Full markdown text for this page } ``` *** ### `metadata` Contains both document-level properties (consistent across all chunks) and page-level properties (unique per chunk). ```python theme={null} chunk["metadata"] = { # Document-level "format": "PDF 1.7", "title": "My Document", "author": "Jane Smith", "subject": "", "keywords": "", "creator": "pdf-lib", "producer": "pdf-lib", "creationDate": "D:20260206183204Z", "modDate": "D:20260206183204Z", "trapped": "", "encryption": None, # Page-level "file_path": "document.pdf", "page_count": 19, "page_number": 1 # 1-based } ``` The PDF version string, e.g. `"PDF 1.7"`. Document title from PDF metadata. Empty string if not set. Document author from PDF metadata. Empty string if not set. The application that originally created the PDF. The application that produced or converted the PDF. PDF date string in `D:YYYYMMDDHHmmSSZ` format. Date the PDF was last modified, same format as `creationDate`. Encryption method if the document is encrypted, otherwise `None`. The file path of the source document as provided to `to_markdown()`. Total number of pages in the document. The 1-based page number this chunk represents. #### Usage example ```python theme={null} for chunk in chunks: meta = chunk["metadata"] print(f"Page {meta['page_number']} of {meta['page_count']} — {meta['file_path']}") ``` *** ### `toc_items` A list of Table of Contents entries that fall on this page. Each entry is a list in the format `[level, title, page_number]`. ```python theme={null} chunk["toc_items"] = [ [1, "Introduction", 3], [2, "Background", 3], [2, "Problem Statement", 3], ] ``` Heading hierarchy depth. `1` = top-level chapter, `2` = section, `3` = subsection, etc. The heading text as it appears in the Table of Contents. The page number the TOC entry points to (1-based). `toc_items` is an empty list `[]` for pages that have no TOC entries, or for documents without a Table of Contents. Always check before iterating. #### Usage example ```python theme={null} for chunk in chunks: for level, title, page in chunk["toc_items"]: indent = " " * (level - 1) print(f"{indent}{title} (p.{page})") ``` *** ### `page_boxes` A list of layout elements detected on the page by the layout analysis engine. Each element describes a discrete visual block — a paragraph, heading, image, table, list item, and so on — along with its position on the page. ```python theme={null} chunk["page_boxes"] = [ { "index": 0, "class": "section-header", "bbox": (58, 55, 560, 108), "pos": (0, 88) }, { "index": 1, "class": "text", "bbox": (36, 125, 574, 209), "pos": (88, 524) }, ... ] ``` Zero-based position of this box in the page's layout order (reading order, top to bottom). The type of layout element detected. See the [box classes](#box-classes) table below. Bounding box of the element in PDF page coordinates: `(x0, y0, x1, y1)`. Origin is the top-left of the page. Units are PDF points (1 point = 1/72 inch). Character offsets into the page's `text` string: `(start, end)`. Use these to slice the exact text that corresponds to this layout element. ##### Box classes | Class | Description | | ---------------- | ---------------------------------------- | | `text` | Body paragraph or general prose | | `section-header` | A heading or section title | | `list-item` | A bullet or numbered list entry | | `table` | A detected table | | `picture` | An image or figure | | `caption` | A caption beneath a figure or table | | `page-footer` | Footer content at the bottom of the page | | `page-header` | Header content at the top of the page | #### Usage example — extract only headings ```python theme={null} for chunk in chunks: boxes = chunk["page_boxes"] text = chunk["text"] for box in boxes: if box["class"] == "section-header": start, end = box["pos"] heading_text = text[start:end].strip() print(heading_text) ``` #### Usage example — get bounding boxes for all images ```python theme={null} for chunk in chunks: page = chunk["metadata"]["page_number"] for box in chunk["page_boxes"]: if box["class"] == "picture": print(f"Page {page}: image at {box['bbox']}") ``` *** ### `text` The full markdown-formatted text content of the page as a single string. Headings, bold text, tables, and list items are represented using standard markdown syntax. ```python theme={null} chunk["text"] = """## Introduction We highlight four promising research opportunities to improve _Large Language Model_ inference for datacenter AI... ## **BACKGROUND** ... """ ``` Markdown string for the entire page. Newlines separate logical blocks. Images that cannot be extracted are replaced with a placeholder like `==> picture [535 x 193] intentionally omitted <==`. The character offsets in each `page_boxes[n]["pos"]` correspond directly to positions within this string, so you can use them to precisely extract the text for any layout element. #### Usage example — slice text by layout element ```python theme={null} chunk = chunks[0] text = chunk["text"] for box in chunk["page_boxes"]: start, end = box["pos"] print(f"[{box['class']}]", text[start:end].strip()[:80]) ``` *** ## Full iteration example ```python theme={null} import pymupdf4llm chunks = pymupdf4llm.to_markdown("document.pdf", page_chunks=True) for chunk in chunks: meta = chunk["metadata"] toc = chunk["toc_items"] boxes = chunk["page_boxes"] text = chunk["text"] print(f"\n--- Page {meta['page_number']} of {meta['page_count']} ---") # TOC entries on this page for level, title, page in toc: print(f" TOC [{level}]: {title}") # Layout elements for box in boxes: start, end = box["pos"] snippet = text[start:end].strip()[:60].replace("\n", " ") print(f" [{box['class']}] {snippet}") ``` *** ## Related | Method | Description | | ------------------------------------------------ | --------------------------------------------------------- | | [`to_markdown()`](/python/api/to_markdown) | The method that produces chunks when `page_chunks=True` | | [`to_json()`](/python/api/to_json) | Alternative export with full bounding box and layout data | | [`get_key_values()`](/python/api/get_key_values) | Extract form field data from a PDF | The JSON schema reference for the full output of to\_json(), including text, image, table, and drawing blocks with bounding boxes and metadata. Working walkthrough with filtering, DataFrame export, and pipeline examples. Full API reference for to\_json(). Extracting form data from PDF as key value pairs.