Extract text from a PDF in C#
PdfToText reads the text stored in an existing PDF. Load the file, call GetText for a string or SaveText for a UTF-8 text file, and choose a page range or a layout before you extract.
Compiled and run: Linux ARM64 · Linux x64 · macOS ARM64 · Windows x64
Build it step by step.
-
1Install the NuGet packages
SelectPdf.Universalis the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.win-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-arm64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.osx-arm64
-
2Create a console app
Any .NET 8 or .NET 10 project works. To follow along, create a console app, run the install commands above inside its folder, then replace
Program.cswith the code below.dotnet new console -n PdfSample cd PdfSample
-
3Import the namespace
using SelectPdf.Universal;
PdfToTextand the rest of the classes used here are inSelectPdf.Universal. -
4Create a PDF to read
// 1. Make a three-page report to read back. HtmlToPdf converter = new HtmlToPdf(); PdfDocument report = converter.ConvertHtmlString(""" <h1>Quarterly report</h1><p>Revenue grew 12% to 4.2 million.</p> <h2 style="page-break-before: always">Costs</h2><p>Operating costs stayed at 3.1 million.</p> <h2 style="page-break-before: always">Outlook</h2><p>Growth is expected to continue.</p> """); report.Save("report.pdf"); report.Close();To keep the sample self-contained,
HtmlToPdfconverts a three-page report intoreport.pdf;page-break-beforestarts the second and third pages. In your application this is the PDF you already have. -
5Load it and get the text
// 2. Load the PDF and get all of its text as a string. PdfToText pdfToText = new PdfToText(); pdfToText.Load("report.pdf"); string text = pdfToText.GetText(); Console.WriteLine(text);Loadopens the file; other overloads take a stream, a byte array, aPdfDocumentor a password.GetTextreturns the text of every page as one string, in the defaultTextLayout.Originallayout. -
6Save it to a text file
// 3. Write the same text to a UTF-8 file and release the PDF. pdfToText.SaveText("report.txt"); pdfToText.Close();SaveTextwrites the same text straight toreport.txtas UTF-8, without going through a string.Closereleases the loaded document. -
7Run it
dotnet run
The program writes
report.pdfto the working folder. Without a license key, every page carries a trial watermark; setGlobalProperties.LicenseKeybefore the first conversion to remove it.
The complete Program.cs.
This is the exact file our samples pipeline compiled and ran on Linux ARM64 and Linux x64 and macOS ARM64 and Windows x64 to produce the output on this page.
using SelectPdf.Universal;
// 1. Make a three-page report to read back.
HtmlToPdf converter = new HtmlToPdf();
PdfDocument report = converter.ConvertHtmlString("""
<h1>Quarterly report</h1><p>Revenue grew 12% to 4.2 million.</p>
<h2 style="page-break-before: always">Costs</h2><p>Operating costs stayed at 3.1 million.</p>
<h2 style="page-break-before: always">Outlook</h2><p>Growth is expected to continue.</p>
""");
report.Save("report.pdf");
report.Close();
// 2. Load the PDF and get all of its text as a string.
PdfToText pdfToText = new PdfToText();
pdfToText.Load("report.pdf");
string text = pdfToText.GetText();
Console.WriteLine(text);
// 3. Write the same text to a UTF-8 file and release the PDF.
pdfToText.SaveText("report.txt");
pdfToText.Close();
Open the project on GitHub → this program and the variations below, ready to dotnet run
The PDF it produces.
Download report.pdf ↓ the file this sample produced, unedited
Variations you will need next.
Each variation is a separate file in the same project, compiled and run with the program above.
Extract one page or a range of pages
GetPageCount returns the number of pages and GetText(1) the text of the first page only. For a range, set StartPageNumber and EndPageNumber before extracting; both are 1-based. MarkPageBreaks = true puts a form feed character at the end of each page, so the text can be split back into pages.
using SelectPdf.Universal;
static class PageRange
{
public static void Run()
{
HtmlToPdf converter = new HtmlToPdf();
PdfDocument book = converter.ConvertHtmlString("""
<h1>Chapter 1</h1><p>The first chapter.</p>
<h1 style="page-break-before: always">Chapter 2</h1><p>The second chapter.</p>
<h1 style="page-break-before: always">Chapter 3</h1><p>The third chapter.</p>
<h1 style="page-break-before: always">Chapter 4</h1><p>The fourth chapter.</p>
""");
book.Save("book.pdf");
book.Close();
PdfToText pdfToText = new PdfToText();
pdfToText.Load("book.pdf");
Console.WriteLine($"{pdfToText.GetPageCount()} pages");
// one page: page numbers start at 1
Console.WriteLine(pdfToText.GetText(1));
// pages 2 to 3, with a form feed at the end of each page
pdfToText.StartPageNumber = 2;
pdfToText.EndPageNumber = 3;
pdfToText.MarkPageBreaks = true;
pdfToText.SaveText("book-pages-2-3.txt");
pdfToText.Close();
}
}
Text in reading order instead of the page layout
With the default TextLayout.Original, the two columns come out side by side, line by line, padded so that they stay aligned. TextLayout.Reading returns the left column, then the right one, which suits a search index or any processing of the prose.
using SelectPdf.Universal;
static class ReadingOrder
{
public static void Run()
{
HtmlToPdf converter = new HtmlToPdf();
PdfDocument article = converter.ConvertHtmlString("""
<div style="display: flex; gap: 40px; width: 500px">
<p style="flex: 1">The left column starts the story. It runs over several lines.</p>
<p style="flex: 1">The right column ends the story. It should come second.</p>
</div>
""");
article.Save("article.pdf");
article.Close();
PdfToText pdfToText = new PdfToText();
pdfToText.Load("article.pdf");
// Original (the default) keeps the columns side by side
Console.WriteLine(pdfToText.GetText());
// Reading returns one column after the other
pdfToText.Layout = TextLayout.Reading;
pdfToText.SaveText("article.txt");
pdfToText.Close();
}
}
Find text and highlight it
Search(text, caseSensitive, wholeWordsOnly) returns a TextPosition for each match: the page number and the rectangle of the text, in points. Here a case-insensitive search for "total" finds Subtotal and Total, and a semi-transparent yellow PdfRectangleElement is drawn over each one on a copy of the PDF. PageNumber starts at 1 and Pages at 0, hence the - 1.
using SelectPdf.Universal;
static class SearchText
{
public static void Run()
{
HtmlToPdf converter = new HtmlToPdf();
PdfDocument invoice = converter.ConvertHtmlString("""
<h1>Invoice 1042</h1>
<p>Subtotal: 400.00</p><p>Tax: 76.00</p><p>Total: 476.00</p>
""");
invoice.Save("invoice.pdf");
invoice.Close();
PdfToText pdfToText = new PdfToText();
pdfToText.Load("invoice.pdf");
// case-insensitive, not limited to whole words: matches "Subtotal" and "Total"
TextPosition[] hits = pdfToText.Search("total", false, false);
pdfToText.Close();
// highlight every match on a copy of the PDF
PdfDocument doc = new PdfDocument("invoice.pdf");
foreach (TextPosition hit in hits)
{
Console.WriteLine($"page {hit.PageNumber} at {hit.X}, {hit.Y}");
PdfRectangleElement box = new PdfRectangleElement(hit.X, hit.Y, hit.Width, hit.Height);
box.BackColor = new PdfColor(240, 240, 0);
box.Transparency = 30;
doc.Pages[hit.PageNumber - 1].Add(box);
}
doc.Save("invoice-highlighted.pdf");
doc.Close();
}
}
Common problems and fixes.
The text of the columns is mixed together
The default TextLayout.Original keeps the page layout, so lines of side-by-side columns are written next to each other. Set Layout = TextLayout.Reading to get one column after the other.
Accented or non-Latin characters are garbled in the file
GetText returns a .NET string; when you write it yourself, choose the encoding explicitly, for example Encoding.UTF8. SaveText writes UTF-8, and SaveText(fileName, encoding) takes another encoding.
The PDF is password-protected
Load it with Load(fileName, password), or set UserPassword before you extract. The stream and byte array overloads of Load take a password too.
A scanned PDF returns little or no text
PdfToText extracts the text stored in the PDF. A page that was scanned to an image has no stored text to return.
Good to know.
- The text can be extracted from a PDF as a plain text string with GetText, or written directly into a file with SaveText. Source
- SaveText(fileName) writes the text as UTF-8; the SaveText(fileName, encoding) overload writes it with another encoding. Source
- Page numbers on PdfToText start at 1. StartPageNumber defaults to 1, and an EndPageNumber of 0 means the operation runs to the last page. Source
- TextLayout.Original, the default, preserves the layout of the page with columns kept aligned; TextLayout.Reading produces the text in reading order. Source
- Search returns the page number and bounding rectangle of every occurrence of the text, and an empty array when there are none. Source
Questions, answered.
Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.