§HOW-TO GUIDE · C# · .NET 8 & 10

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

Video walkthrough · 3:15 · Watch on YouTube · Short · 1:01
§STEP BY STEP

Build it step by step.

  1. 1Install the NuGet packages

    SelectPdf.Universal is the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.

    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.win-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-arm64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.osx-arm64
  2. 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.cs with the code below.

    terminal
    dotnet new console -n PdfSample
    cd PdfSample
  3. 3Import the namespace

    Program.cs · lines 1–1
    using SelectPdf.Universal;

    PdfToText and the rest of the classes used here are in SelectPdf.Universal.

  4. 4Create a PDF to read

    Program.cs · lines 3–11
    // 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, HtmlToPdf converts a three-page report into report.pdf; page-break-before starts the second and third pages. In your application this is the PDF you already have.

  5. 5Load it and get the text

    Program.cs · lines 13–17
    // 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);

    Load opens the file; other overloads take a stream, a byte array, a PdfDocument or a password. GetText returns the text of every page as one string, in the default TextLayout.Original layout.

  6. 6Save it to a text file

    Program.cs · lines 19–21
    // 3. Write the same text to a UTF-8 file and release the PDF.
    pdfToText.SaveText("report.txt");
    pdfToText.Close();

    SaveText writes the same text straight to report.txt as UTF-8, without going through a string. Close releases the loaded document.

  7. 7Run it

    terminal
    dotnet run

    The program writes report.pdf to the working folder. Without a license key, every page carries a trial watermark; set GlobalProperties.LicenseKey before the first conversion to remove it.

§COMPLETE PROGRAM

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.

Program.cs
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

§OUTPUT

The PDF it produces.

First page of report.pdf, the PDF this sample produces
report.pdf, page 1

Download report.pdf ↓ the file this sample produced, unedited

§VARIATIONS

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.

PageRange.cs
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.

ReadingOrder.cs
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.

SearchText.cs
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();
    }
}
§TROUBLESHOOTING

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

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
?FAQ

Questions, answered.

Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.

Create a PdfToText object, load the PDF with Load and call GetText to get the text as a string, or SaveText to write it to a UTF-8 file. Call Close when you are done.
Call GetText with the page number; page numbers start at 1. For a range, set StartPageNumber and EndPageNumber, then call GetText or SaveText.
Yes. PdfToText.Search returns a TextPosition for every occurrence, with the page number and the X, Y, width and height of the text in points.
No. The Community Edition is HTML to PDF only. PDF to text is part of the commercial SelectPdf.Universal library, which you can try for free with a watermark.