Read the text of every page of a PDF in C# in reading order, leave out running headers and footers, and keep the page layout when you need it, with OnePDF.
dotnet add package RecursiveAI.OnePdf --version 1.11.0using OnePdf; using OnePdf.Text; using PdfDocument doc = PdfDocument.Open("report.pdf"); // Whole document: pages are read in parallel, and running headers and footers are found by their repetition. var options = new PdfTextOptions { Mode = PdfTextMode.ReadingOrder, // the default; also ContentOrder and Layout ExcludeHeadersAndFooters = true, // leave repeated headers and footers out of the text }; IReadOnlyList<PdfPageText> pages = doc.ExtractText(options); using (StreamWriter output = File.CreateText("report.txt")) { for (int i = 0; i < pages.Count; i++) { output.WriteLine($"--- page {i + 1}"); output.WriteLine(pages[i].Text); } } // One page, keeping the arrangement on the page, like pdftotext -layout. PdfPageText page = doc.Pages[0].ExtractText(new PdfTextOptions { Mode = PdfTextMode.Layout }); Console.WriteLine(page.Text);
report.txt gets each page’s text, columns read one after the other. The console gets the first page in layout mode:
Week 1 Prepared by Jane Doe, account 4471-2290. The harbor trial ran for six weeks. Each crew logged its ... Left column: crews on the north quay. Left column: Right column: crews on the south quay. Right column: crews on the north quay. Left column: crews on the crews on the south quay. Right column: crews on the
The three modes
ReadingOrder(the default) orders blocks by the structure tree when the file is tagged and the tree covers the page, and by the page’s geometry otherwise. Blocks are separated by a blank line and lines by a line feed.ContentOrderkeeps the order in which the content stream draws the glyphs.Layoutputs words on a monospace grid that keeps the page’s arrangement, likepdftotext -layout.
pageText.GetText(PdfTextMode.ContentOrder) gives a page’s text in another mode, worked out from the same page while the document is still open.
Headers and footers
Whole-document extraction flags a block as a header or footer when it sits in the top or bottom 12% of the page and the same text appears there on at least half of the pages. Digits are ignored when comparing, so “Page 3 of 6” counts, and so does a “Week 3” title that opens every page: in the run above, ExcludeHeadersAndFooters dropped the “Week” titles along with the running header and the page numbers. Check the result before you exclude. Each block’s Role tells you what was flagged. Single-page page.ExtractText() doesn’t flag anything, because it has only one page to compare.
Good to know
- Ligatures are split into letters, and Arabic and Hebrew presentation forms are folded to their base characters. Text marked with
/ActualTextreplaces what it covers. - Invisible text, such as the text layer an OCR tool adds to a scan, is included and flagged:
word.IsVisibleis false. JoinHyphenatedWords = truejoins a word split by a hyphen at the end of a line.- Long documents stay small in memory: whole-document extraction doesn’t keep each page’s glyphs unless you set
RetainGlyphs. - OnePDF does no OCR. A scanned page without a text layer gives no text.
For word boxes, see Get words and their positions. Every threshold of the layout analysis is in Text layout and reading order, and the versioned JSON output is in Extraction JSON.
For a longer walk-through with an ASP.NET Core upload endpoint and a comparison with PdfPig and iText, see Extract text from PDF in C#.
To pull out the embedded pictures as well: Extract images from a PDF in C#.