To extract text from a PDF in C#, add the RecursiveAI.OnePdf package, open the file with PdfDocument.Open and call ExtractText(). You get each page’s text in reading order, so two-column pages come out one column after the other, plus every word with its position on the page. OnePDF is pure C# with no native binaries, licensed MIT OR Apache-2.0. This page covers whole documents, multi-column layouts, text from one area of a page, uploads in ASP.NET Core, passwords, and what to do about scans and tables. Every sample was compiled and run against OnePDF 1.11.0 on .NET 10.
Before you start
dotnet new console -o PdfText cd PdfText dotnet add package RecursiveAI.OnePdf --version 1.11.0
OnePDF targets .NET 10 (net10.0). The samples use IRS Form W-9 (Rev. March 2024), a six-page US Letter PDF with two-column instruction pages, so you can run them on the same file. Save it as fw9.pdf next to the program.
Extract the text of every page
using OnePdf; using OnePdf.Text; using PdfDocument doc = PdfDocument.Open("fw9.pdf"); // The text of every page, in reading order. Pages are read in parallel. IReadOnlyList<PdfPageText> pages = doc.ExtractText(); using StreamWriter output = File.CreateText("fw9.txt"); for (int i = 0; i < pages.Count; i++) { output.WriteLine($"--- page {i + 1}"); output.WriteLine(pages[i].Text); } Console.WriteLine($"{pages.Count} pages, {pages.Sum(p => p.Text.Length):N0} characters");
6 pages, 37,973 charactersfw9.txt starts with “Form W-9”, “(Rev. March 2024)” and the form’s title, each block separated by a blank line.
PdfDocumentholds the file open and reads it as needed, so dispose it. It opens a path, aStreamor aReadOnlyMemory<byte>.doc.ExtractText()reads the pages in parallel, on up to four threads by default. Reading order comes from the structure tree when the file is tagged (the W-9 is) and from the geometry of the page otherwise.- Ligatures are split into letters, Arabic and Hebrew presentation forms are folded to their base letters, and right-to-left text comes out in logical order.
- Whole-document extraction also flags running headers and footers by their repetition. Set
ExcludeHeadersAndFooters = trueinPdfTextOptionsto leave them out. The extract-text recipe shows the options.
Two-column pages: reading order or layout
Page 2 of the W-9 is set in two columns. A plain left-to-right sweep would mix them line by line. OnePDF has three plain-text modes for a page:
using OnePdf; using OnePdf.Text; using PdfDocument doc = PdfDocument.Open("fw9.pdf"); // Page 2 of the W-9 is set in two columns. Page indexes are zero-based. PdfPageText page = doc.Pages[1].ExtractText(); File.WriteAllText("page2-reading-order.txt", page.Text); // left column, then right column File.WriteAllText("page2-layout.txt", page.GetText(PdfTextMode.Layout)); // the page's arrangement, like pdftotext -layout File.WriteAllText("page2-content-order.txt", page.GetText(PdfTextMode.ContentOrder));
The reading-order file keeps the left column together. It starts like this, and the right column (“Foreign person. If you are a foreign person…”) follows on line 104:
Form W-9 (Rev. 3-2024) Page 2 must obtain your correct taxpayer identification number (TIN), which may be your social security number (SSN), individual taxpayer identification number (ITIN), adoption taxpayer identification number (ATIN), or employer identification number (EIN), to report on an information return the amount paid to you, or other amount reportable on an information return. Examples of information returns include, but are not limited to, the following. • Form 1099-INT (interest earned or paid).
The layout file keeps the page’s arrangement on a monospace grid instead, like pdftotext -layout:
Form W-9 (Rev. 3-2024) Page 2 must obtain your correct taxpayer identification number (TIN), which Foreign person. If you are a foreign person or the U.S. branch of a may be your social security number (SSN), individual taxpayer foreign bank that has elected to be treated as a U.S. person (under identification number (ITIN), adoption taxpayer identification number Regulations section 1.1441-1(b)(2)(iv) or other applicable section for (ATIN), or employer identification number (EIN), to report on an chapter 3 or 4 purposes), do not use Form W-9. Instead, use the
ReadingOrder(the default) is what you want for search, indexing and feeding text to other tools. Blocks are separated by a blank line, lines by a line feed and table cells by a tab.Layoutsuits fixed-format documents where position carries meaning, such as statements and forms.ContentOrderkeeps the order in which the file draws its text, which is how a simple extractor reads.
page.Text is the text in the mode you asked for (reading order by default), and page.GetText(mode) gives another mode from the same page. How lines, columns and blocks are found, and every threshold involved, is in text layout and reading order.
Get words with their positions (text from one area)
Every word comes with its bounding box, so you can read the text inside a region of the page, the way you would pick a field out of a fixed form:
using OnePdf; using OnePdf.Text; using PdfDocument doc = PdfDocument.Open("fw9.pdf"); PdfPageText text = doc.Pages[0].ExtractText(); // An area of the page in points (1/72 inch) with the origin at the lower left: // the box in the top right corner of the W-9. var area = new PdfRectangle(490, 710, 612, 760); List<PdfTextWord> words = text.Words .Where(w => w.Bounds.Left >= area.Left && w.Bounds.Right <= area.Right && w.Bounds.Bottom >= area.Bottom && w.Bounds.Top <= area.Top) .ToList(); Console.WriteLine(string.Join(" ", words.Select(w => w.Text))); foreach (PdfTextWord w in words.Take(3)) Console.WriteLine($"{w.Text,-10} left={w.Bounds.Left:F1} bottom={w.Bounds.Bottom:F1} width={w.Bounds.Width:F1}");
Give form to the requester. Do not send to the IRS. Give left=496.6 bottom=738.0 width=19.0 form left=518.1 bottom=738.0 width=20.2 to left=540.8 bottom=738.0 width=8.7
Boundsis in points (1/72 inch) with the origin at the lower left of the page and y going up, after the page’s rotation is applied. A US Letter page is 612 by 792 points.text.Wordslists the page’s words in reading order.text.Blocks, each block’sLinesand each line’sWordsgive the same words grouped.- To find the coordinates of an area, print the words of the page with their boxes once, as in get words and their positions. That recipe also groups words into table rows and finds the value printed next to a label.
- For machine processing, the whole result (glyphs, words, lines and blocks) is available as versioned JSON: see extraction JSON.
Read text from an uploaded PDF in ASP.NET Core
In a web app the PDF usually arrives as an upload. Open it from memory with TryOpen, which returns an error instead of throwing when the file isn’t a PDF it can read. Create the project with dotnet new web and add the package as above. Run it with dotnet run, then post a file to the address it prints:
using OnePdf; using OnePdf.Text; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); // POST a PDF as multipart/form-data (field "file"); get back the text of each page. app.MapPost("/pdf/text", async (IFormFile file) => { using var buffer = new MemoryStream(); await file.CopyToAsync(buffer); if (!PdfDocument.TryOpen(buffer.ToArray(), out PdfDocument? doc, out PdfException? error)) return Results.BadRequest(error?.Message); using (doc) { IReadOnlyList<PdfPageText> pages = doc.ExtractText(); return Results.Ok(pages.Select((p, i) => new { page = i + 1, hasText = !string.IsNullOrWhiteSpace(p.Text), // false for a scan without a text layer text = p.Text, })); } }).DisableAntiforgery(); app.Run();
# APP_URL is the address that dotnet run prints curl -F [email protected] "$APP_URL/pdf/text"
For the W-9 this returns six objects, from {"page":1,"hasText":true,"text":"Form W-9\n\n(Rev. March 2024)… to page 6. A file that isn’t a PDF gets a 400 response with the reason, here “The file has no usable cross-reference data, trailer or document catalog.” A PDF that is only a scanned image returns "hasText":false.
OnePDF is designed for files you didn’t write: it has no unsafe code, it never runs a document’s scripts or fetches anything, and limits on decoded size, nesting and object count are on by default and checked before the work they guard. You can tighten them with PdfOpenOptions.Limits. Wall-clock limits are yours to set, by passing a CancellationToken to ExtractText.
Password-protected PDFs
using OnePdf; using OnePdf.Text; try { using PdfDocument doc = PdfDocument.Open("statement.pdf", new PdfOpenOptions { Password = "open sesame" }); Console.WriteLine($"{doc.Encryption?.Cipher}: {doc.Pages[0].ExtractText().Text}"); } catch (PdfPasswordException) { Console.WriteLine("Wrong or missing password."); }
Aes256: Statement for October 2026. Balance due: $2,420.00Either the user password or the owner password opens the file. OnePDF reads the standard security handler from RC4 40-bit to AES-256. With a wrong or missing password, Open throws PdfPasswordException. Files encrypted for specific recipients’ certificates (public-key encryption) aren’t supported. To remove the password and save an unencrypted copy, see decrypt or encrypt a PDF.
Scanned PDFs, OCR and tables
- Scans: OnePDF does no OCR. A scanned page without a text layer gives empty text, which is how the
hasTextcheck above works. Run those pages through an OCR tool first. - Scans that were already OCRed carry an invisible text layer. OnePDF includes that text and flags it:
word.IsVisibleisfalse. - Tables: there is no table-extraction API. When a region of the page looks like a table, reading order keeps its cells on their rows, separated by tabs, and the word boxes let you rebuild rows and columns yourself, as the word-positions recipe does for an invoice.
The same job with PdfPig or iText
If you are comparing libraries, here is the same whole-document extraction with the two most common alternatives, written from their own documentation and run on the same W-9. PdfPig 0.1.16 (Apache-2.0), following its README, which recommends its text extractors over page.Text:
using UglyToad.PdfPig; using UglyToad.PdfPig.Content; using UglyToad.PdfPig.DocumentLayoutAnalysis.TextExtractor; using PdfDocument document = PdfDocument.Open("fw9.pdf"); foreach (Page page in document.GetPages()) { Console.WriteLine($"--- page {page.Number}"); Console.WriteLine(ContentOrderTextExtractor.GetText(page)); }
iText 9.8.0 (AGPL or commercial), following the LocationTextExtractionStrategy example in its knowledge base:
using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas.Parser; using iText.Kernel.Pdf.Canvas.Parser.Listener; using PdfDocument pdf = new PdfDocument(new PdfReader("fw9.pdf")); for (int page = 1; page <= pdf.GetNumberOfPages(); page++) { ITextExtractionStrategy strategy = new LocationTextExtractionStrategy(); Console.WriteLine($"--- page {page}"); Console.WriteLine(PdfTextExtractor.GetTextFromPage(pdf.GetPage(page), strategy)); }
Both read every page. On page 2, iText’s location strategy joins the two columns line by line (“must obtain your correct taxpayer identification number (TIN), which Foreign person. If you are a foreign person…”), while PdfPig’s content-order extractor keeps the columns apart on this file, because the W-9 draws one column after the other. Content order only works when the producer draws in reading order; OnePDF’s reading order comes from the document’s tags or the page geometry instead.
On the project’s published text-quality benchmark (62 generated files with exact reference text), OnePDF’s character error on content was 0.3% and its reading-order error 1.2%, against 5.6% and 10.7% for PdfPig 0.1.16’s layout pipeline and 21.9% and 22.7% for its default Page.Text. On the speed benchmark, full text of a 300-page born-digital document took 211 ms against PdfPig’s 238 ms on one thread, and 68.9 ms against 123 ms at the default degree of parallelism. Both ran on an AMD Ryzen 9 7950X3D under Linux with .NET 10.0.13, and the times are BenchmarkDotNet means. Moving code across is covered in migrating from PdfPig. For licensing differences between these libraries, see choosing a C# PDF library.
Where next
- Getting started with OnePDF: install, read, edit, fill a form, sign, generate.
- Extract text from a PDF and get words and their positions, the cookbook recipes.
- Redact text once you have found it, or create a PDF in C#.
- To turn pages into images instead of text, see convert a PDF to an image in C#.