---
title: "Get words and their positions"
url: https://recursiveai.org/onepdf/cookbook/word-positions/
description: "Read every word on a PDF page with its bounding box in C#, group words into rows, and find the value printed next to a label, with OnePDF."
version: "1.11.0"
---

# Get words and their positions

Read every word on a PDF page with its bounding box in C#, group words into rows, and find the value printed next to a label, with OnePDF.

OnePDF has no table-extraction API. Words with positions are the building block: this recipe reads an invoice’s rows and its total from them.

```csharp
using OnePdf;
using OnePdf.Text;

using PdfDocument doc = PdfDocument.Open("invoice.pdf");
PdfPageText text = doc.Pages[0].ExtractText();

// Rows of the page: words grouped by baseline, top to bottom, each row left to right.
var rows = text.Words
    .GroupBy(w => Math.Round(w.Bounds.Bottom))
    .OrderByDescending(row => row.Key);                    // y grows upward
foreach (var row in rows)
    Console.WriteLine($"y={row.Key,4}  " + string.Join("  ", row.OrderBy(w => w.Bounds.Left).Select(w => $"{w.Text}@{w.Bounds.Left:F0}")));

// The value printed to the right of a label, on the same baseline.
PdfTextWord label = text.Words.First(w => w.Text == "Total");
PdfTextWord value = text.Words
    .Where(w => w.Bounds.Left > label.Bounds.Right && Math.Abs(w.Bounds.Bottom - label.Bounds.Bottom) < 2)
    .OrderBy(w => w.Bounds.Left)
    .First();
Console.WriteLine($"Total: {value.Text} at ({value.Bounds.Left:F1}, {value.Bounds.Bottom:F1})");
```

```text
y= 725  No.@491  2026-104@512
y= 712  October@473  26,@515  2026@534
y= 708  Invoice@54
y= 680  Bill@54  to:@72  Example@87  Co.,@133  100@156  Main@178  Street,@204  Springfield@240
y= 647  Item@60  Qty@354  Price@436  Amount@511
y= 624  Design@60  review@97  2@367  $450.00@423  $900.00@513
y= 600  Implementation,@60  hours@141  12@361  $120.00@423  $1,440.00@504
y= 576  Hosting,@60  October@103  1@367  $80.00@429  $80.00@519
y= 553  Total@437  $2,420.00@504
y=  54  Payment@248  due@286  within@304  30@330  days.@342
Total: $2,420.00 at (503.6, 552.7)
```

- `Bounds` is a `PdfRectangle` (`Left`, `Bottom`, `Right`, `Top`, `Width`, `Height`) in points, with the origin at the lower left of the page and y going up. The page’s `/Rotate` is already applied, so the coordinates are those of the page as a viewer shows it.
- A word is a run of glyphs without a space between them, so punctuation stays attached (“Co.,”).
- `text.Words` lists every word of the page. `text.Blocks`, each block’s `Lines` and each line’s `Words` give the same words grouped in reading order, which suits running text better than tables.
- Rounding the baseline to the nearest point is enough for a generated document. For scans with an OCR layer or slightly skewed text, group words whose baselines are within a fraction of the font size instead.
- `word.IsVisible` is false for invisible text, such as an OCR layer under a scanned image.

To black out the words you found, see [Redact text](https://recursiveai.org/onepdf/cookbook/redact/). How words, lines and blocks are formed is in [Text layout and reading order](https://recursiveai.org/onepdf/docs/text-layout/).

For the whole extraction workflow, see [Extract text from PDF in C#](https://recursiveai.org/onepdf/articles/extract-text-from-pdf-csharp/).

Coming from PdfPig’s word API: [Migrate from PdfPig](https://recursiveai.org/onepdf/articles/migrate-from-pdfpig/).
