# Text Extraction

> Get the text of PDF files in WPF: open it as plain text in the TextControl, read lines by page with the Lines class, match regular expressions and read areas.

- **Product:** TX Text Control .NET for WPF
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET for WPF > PDF Processing > Text Extraction
- **4394 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/text-extraction/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/text-extraction/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/text-extraction/llms-full.txt

---

Which invoice number does this PDF contain, and on which page is the termination clause? Answering such questions takes the text of the PDF. TX Text Control .NET for WPF gets it in two forms: as a plain-text document in the WPF `TextControl`, where users read and search it, and as a list of text lines with page numbers and coordinates, which your code filters by text, regular expression or area. The examples extend a window with a `TextControl` named `textControl1` and a `ListBox` named `resultsListBox` that shows the results.

To turn a PDF into an editable document that keeps its layout, see [PDF Import and Its Limitations](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/import/llms-full.txt) instead.

### How Text Extraction Works

TX Text Control extracts the text it can identify through the character mapping of the PDF, inserts missing spaces and sorts the text blocks into their logical order, as [What Happens When a PDF Is Loaded](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/import/#what-happens-when-a-pdf-is-loaded) explains; scanned pages need OCR first.

| Way | Returns | Use it for |
|---|---|---|
| `Load` with [PDFImportSettings](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol/pdfimportsettings/enumeration/llms-full.txt) `GenerateLines` | The text of the PDF as a document, without images and paragraph formatting | Reading and searching the text in the editor, full-text indexes |
| The [Lines](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-lines/llms-full.txt) class of the `TXTextControl.DocumentServer.PDF.Contents` namespace | Each text line with its page number and position | Text per page, searches, values at a position |

A `Lines` object reads every text line of a PDF file or byte array in its constructor and needs no `TextControl`. [ContentLines](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-lines/contentlines-property/llms-full.txt) is the list of [ContentLine](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-contentline/llms-full.txt) objects, and [Find](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-lines/find-method/llms-full.txt) returns a filtered part of it. Read a document once and call `Find` on the same object as often as needed.

| Property of `ContentLine` | Contains |
|---|---|
| [Page](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-contentline/page-property/llms-full.txt) | The page number of the line |
| [Text](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-contentline/text-property/llms-full.txt) | The text of the line |
| [X](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-contentline/x-property/llms-full.txt), [Y](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-contentline/y-property/llms-full.txt), [Width](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-contentline/width-property/llms-full.txt), [Height](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-contentline/height-property/llms-full.txt) | Position and size of the line on the page, in points |
| [Rectangle](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-contentline/rectangle-property/llms-full.txt) | The bounds of the line as a `System.Drawing.RectangleF` |

### Prepare the Window

Add the list box to the `DockPanel` of *MainWindow.xaml*, before the `TextControl` element, so that it docks at the right of the editor:

```xml
<ListBox x:Name="resultsListBox" DockPanel.Dock="Right" Width="300" />
```

The handlers below go into *MainWindow.xaml.cs* and need these `using` directives. `System.Drawing` provides the `RectangleF` structure the coordinates use, and `Microsoft.Win32` the file dialogs of WPF:

```csharp
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using Microsoft.Win32;
using TXTextControl;
using TXTextControl.DocumentServer.PDF.Contents;
```

### Open the Text of a PDF in the Editor

`PDFImportSettings.GenerateLines` loads a PDF as text only and discards images and paragraph formatting, the setting best suited for searching PDF files. The following handler shows the **Open** dialog with the PDF filter and loads the chosen file as text:

```csharp
private void openPdfAsTextButton_Click(object sender, RoutedEventArgs e)
{
    LoadSettings loadSettings = new LoadSettings()
    {
        // Text only: images and paragraph formatting are left out
        PDFImportSettings = PDFImportSettings.GenerateLines
    };

    // Opens the Open dialog with the filter for PDF documents
    textControl1.Load(StreamType.AdobePDF, loadSettings);
}
```

Users read the text in the editor and search it with the **Find** dialog, which `textControl1.Find()` opens. Your code gets the complete text as a string from [Text](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-wpf/textcontrol/text-property/llms-full.txt), and [Search and Replace](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/document-editor/editing-text/search-replace/llms-full.txt) searches it in code. A protected PDF needs its password in `LoadSettings`; see [Passwords and Access Permissions](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/passwords/#open-a-password-protected-pdf).

### Read the Text Page by Page

A search hit or a summary often refers to a page. The following handlers let the user pick a PDF file and fill the list box with its lines, page by page and in reading order:

```csharp
private string ChoosePdf()
{
    OpenFileDialog openDialog = new OpenFileDialog() { Filter = "PDF documents (*.pdf)|*.pdf" };
    return openDialog.ShowDialog(this) == true ? openDialog.FileName : null;
}

private void showLinesButton_Click(object sender, RoutedEventArgs e)
{
    string path = ChoosePdf();

    if (path == null)
    {
        return;
    }

    Lines lines = new Lines(path);

    resultsListBox.Items.Clear();

    foreach (var page in lines.ContentLines.GroupBy(line => line.Page).OrderBy(group => group.Key))
    {
        resultsListBox.Items.Add($"--- Page {page.Key} ---");

        // From top to bottom, then from left to right
        foreach (ContentLine line in page.OrderBy(line => line.Y).ThenBy(line => line.X))
        {
            resultsListBox.Items.Add(line.Text);
        }
    }
}
```

The [Lines constructor](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol-documentserver-pdf/contents-lines/constructor/llms-full.txt) accepts a file path or a byte array. Sorting the lines of a page by `Y` and then by `X` gives the reading order, the approach of the blog article [Mining PDFs with Regex in C#: Practical Patterns, Tips, and Ideas](https://www.textcontrol.com/blog/2025/08/12/mining-pdfs-with-regex-in-csharp-practical-patterns-tips-and-ideas/). The WPF `OpenFileDialog` returns a `bool?`, which is `true` when the user chose a file.

### Search With Regular Expressions

Given a regular expression, `Find` returns each line with a match. The following handler lists every invoice number of the form INV-2025-001 together with its page:

```csharp
private void findInvoiceNumbersButton_Click(object sender, RoutedEventArgs e)
{
    string path = ChoosePdf();

    if (path == null)
    {
        return;
    }

    // Invoice numbers such as INV-2025-001
    string pattern = @"\bINV-\d{4}-\d{3}\b";

    Lines lines = new Lines(path);
    List<ContentLine> found = lines.Find(pattern, RegexOptions.IgnoreCase);

    resultsListBox.Items.Clear();

    foreach (ContentLine line in found)
    {
        Match match = Regex.Match(line.Text, pattern, RegexOptions.IgnoreCase);
        resultsListBox.Items.Add($"Page {line.Page}: {match.Value}");
    }
}
```

The code works as follows:

- `Find(string regex, RegexOptions options)` applies a .NET regular expression with its options to every line.
- The result contains whole lines, and `Regex.Match` takes the matched value out of each one.
- `Find(string text)` returns the lines that contain a plain text.

A single-line expression misses a value that wraps to the next line. The blog article above has patterns for dates, amounts and clause numbers and looks at neighboring lines for context.

### Read a Value From an Area of the Page

In documents of one layout, for example the invoices of one supplier, a value sits at the same place on every page. `Find` with a rectangle returns what is printed there. The following handler takes the area of the invoice number from a sample invoice and reads that area in each PDF of a folder:

```csharp
private void readInvoiceNumbersButton_Click(object sender, RoutedEventArgs e)
{
    // Find the area of a known value in a sample document
    Lines sample = new Lines("invoice-sample.pdf");
    ContentLine known = sample.Find("INV-2025-001").FirstOrDefault(line => line.Page == 1);

    if (known == null)
    {
        MessageBox.Show(this, "The sample invoice doesn't contain the invoice number.");
        return;
    }

    RectangleF area = known.Rectangle;

    OpenFolderDialog folderDialog = new OpenFolderDialog();

    if (folderDialog.ShowDialog(this) != true)
    {
        return;
    }

    resultsListBox.Items.Clear();

    // Read the same area of other documents of the same layout
    foreach (string file in Directory.GetFiles(folderDialog.FolderName, "*.pdf"))
    {
        Lines lines = new Lines(file);

        // true: lines that intersect the area, not only lines completely inside it
        ContentLine value = lines.Find(area, true).FirstOrDefault(line => line.Page == 1);

        resultsListBox.Items.Add($"{System.IO.Path.GetFileName(file)}: {value?.Text ?? "(no value)"}");
    }
}
```

The code works as follows:

- `Find(RectangleF rectangle, bool partially)` returns the lines in a rectangle: with `false` only lines completely inside it, with `true` also lines that intersect it, so a longer value is found too.
- Taking the rectangle from a line of the sample uses the coordinates of the PDF files themselves, not values measured by hand.
- `Find(PointF position, float radius, bool partially)` returns the lines around a point. `OpenFolderDialog` is part of WPF since .NET 8.

Several fields of flattened forms are read this way in the blog article [Template-Based Text Extraction from PDF Documents in .NET C#](https://www.textcontrol.com/blog/2025/02/12/template-based-text-extraction-from-pdf-documents-in-net-c-sharp/).

### Get the Text and the Coordinates in One Load

Text that the user doesn't need to see can be extracted with a [ServerTextControl](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol/servertextcontrol/llms-full.txt), the TX Text Control without a user interface, which leaves the document in the window untouched. Adding `LoadEmbeddedData` to the import settings fills [LoadSettings.EmbeddedData](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol/loadsettings/embeddeddata-property/llms-full.txt) with the text coordinates as XML, one element per line with its page and position:

```csharp
private void textAndCoordinatesButton_Click(object sender, RoutedEventArgs e)
{
    using (ServerTextControl tx = new ServerTextControl())
    {
        // Initialize the new instance before using it
        tx.Create();

        LoadSettings loadSettings = new LoadSettings()
        {
            PDFImportSettings = PDFImportSettings.GenerateLines | PDFImportSettings.LoadEmbeddedData
        };

        tx.Load("invoice.pdf", StreamType.AdobePDF, loadSettings);

        File.WriteAllText("invoice.txt", tx.Text);

        if (loadSettings.EmbeddedData != null &&
            loadSettings.EmbeddedData.TryGetValue(EmbeddedDataFormat.TextCoordinates, out object coordinates))
        {
            File.WriteAllText("coordinates.xml", coordinates.ToString());
        }
    }
}
```

[ServerTextControl.Text](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol/servertextcontrol/text-property/llms-full.txt) is the text of the loaded document as one string. `EmbeddedData` is keyed by [EmbeddedDataFormat](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/reference/txtextcontrol/embeddeddataformat/enumeration/llms-full.txt), with the values `TextCoordinates`, `FormFields` and `MetaData`; it is filled only with `LoadEmbeddedData` and only with the formats the PDF provides.

### Next Steps

- [PDF Import and Its Limitations](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/import/llms-full.txt): load a PDF as an editable document with its layout.
- [Reading Form Fields and Values](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/forms/reading-fields/llms-full.txt): get the values of PDF form fields.
- [Document Metadata](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/metadata/llms-full.txt): read title, author and keywords of a PDF.
- [Attachments](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/attachments/llms-full.txt): extract embedded files from a PDF.

---

## Nearby

- **Previous:** [Attachments](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/attachments/llms-full.txt)
- **Next:** [Document Metadata](https://docs.textcontrol.com/tx-text-control-dotnet-wpf/pdf/metadata/llms-full.txt)
