Recursive AI

Get words and their positions

All onepdf recipes

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.

C#
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. How words, lines and blocks are formed is in Text layout and reading order.

For the whole extraction workflow, see Extract text from PDF in C#.

Coming from PdfPig’s word API: Migrate from PdfPig.