Cracking the Code: PDF Text Extraction with Modern C++
Extracting text from a PDF is rarely as simple as 'give me the words.' Different use cases need different things: a full-text search index needs all words in reading order, a document analysis pipeline needs font names and bounding boxes alongside the text, a compliance scanner needs to find specific patterns like phone numbers or account numbers, and a forms processing system needs to pull field values from completed AcroForms.
This tutorial covers all four of these scenarios using the Datalogics Modern C++ API, which uses standard C++17 conventions including RAII resource management, range-based for loops, and standard exception handling. All samples are available in the Modern C++ samples repository on GitHub.
Who Is This For?
This tutorial is for C++ developers building document processing systems that need to work with PDF text at the content level. That includes search and indexing engines, document intelligence and AI/ML data preparation pipelines, compliance and regulatory scanning tools, forms processing backends, and data migration workflows that extract structured content from legacy PDF archives.
The Modern C++ API: What Is Different
The classic APDFL C++ interface uses C-style handles, macro-based error handling (DURING/HANDLER blocks), and raw pointer management. The Modern C++ API replaces all of this with proper C++ classes. Library initialization is an RAII object. Documents and pages are value types. Error handling uses standard try/catch. This is what the initialization looks like:
#include <datalogics_interface/datalogics_interface.hpp>
using namespace datalogics_interface;
Library lib; // Initializes the library; destructor cleans up automatically
Document doc(input_path); // Opens the PDF
There are no explicit teardown calls, no error code checks after every line, and no raw memory management. The API is composable in the way modern C++ code is expected to be.
Sample 1: Extracting Page Text
When to Use This
Use TextExtract when you need the raw text content of a PDF for full-text search indexing, NLP preprocessing, accessibility processing, or any downstream system that works with plain text. The sample handles both tagged and untagged PDFs using separate code paths, which matters because the two document types have different internal text structures.
Setting Up the WordFinder
The WordFinder is the engine that converts a PDF's content stream into a sequence of words. WordFinderConfig controls its behavior through named setter methods, which is much more readable than the classic C++ approach of filling a struct by field index:
WordFinderConfig word_config;
word_config.set_ignore_char_gaps(false);
word_config.set_ignore_line_gaps(false);
word_config.set_no_annotations(false);
word_config.set_no_hyphen_detection(false);
word_config.set_no_style_info(false);
word_config.set_no_xy_sort(true);
WordFinder word_finder(doc, WordFinderVersion::Latest, word_config);
Setting no_xy_sort to true preserves the internal content stream order rather than sorting by position. For most extraction purposes, sorting by position (the default) produces better reading-order output. Disable it only if you specifically need content stream order.
Extracting Words Per Page
The WordFinder provides a word list per page. Each word object exposes its text and a set of attribute flags that describe its position in the line. The sample uses these flags to reconstruct whitespace and line breaks:
for (int i = 0; i < n_pages; ++i) {
auto page_words = word_finder.get_word_list(i);
std::string text_to_extract;
for (const auto& word : page_words) {
text_to_extract += word.get_text();
if ((word.get_attributes() & WordAttributeFlags::AdjacentToSpace)
== WordAttributeFlags::AdjacentToSpace)
text_to_extract += " ";
if ((word.get_attributes() & WordAttributeFlags::LastWordOnLine)
== WordAttributeFlags::LastWordOnLine)
text_to_extract += "\n";
}
logfile << "<page " << (i + 1) << ">\n" << text_to_extract << "\n";
}
The sample also handles hyphenated words that break across lines by detecting the HasSoftHyphen and LastWordOnLine flags together, then joining the two parts without the hyphen. This produces clean extracted text without mid-word hyphens in the output.
Tagged vs. Untagged PDFs
Tagged PDFs have explicit logical structure that identifies reading regions, so the end-of-region detection uses get_is_last_word_in_region() rather than the LastWordOnLine flag. The sample auto-detects whether the document is tagged by examining the MarkInfo dictionary in the document root, then dispatches to the appropriate extraction function.
Expected Output
Two plain text files: TextExtract-untagged-out.txt and TextExtract-tagged-out.txt. Each contains one section per page, with words separated by spaces and lines separated by newlines.
Sample: text_extract.cpp on GitHub
Sample 2: Extracting Text with Style and Position
When to Use This
Use ExtractTextPreservingStyleAndPositionInfo when you need more than just the words. Document analysis, layout reconstruction, AI training data preparation, and accessibility tools often need to know where each word appears on the page, in what font, at what size, and in what color. This sample captures all of that information and writes it to JSON.
What the Code Does
The sample calls dl_common::get_text_and_details for each page, which returns a vector of TextAndDetailsObject structs. Each object contains the word text, its bounding quad (four corner coordinates), and a list of style entries covering the characters in the word:
for (int page_num = 0; page_num < doc.get_num_pages(); ++page_num) {
auto page_result = dl_common::get_text_and_details(doc, page_num);
all_results.insert(all_results.end(),
std::make_move_iterator(page_result.begin()),
std::make_move_iterator(page_result.end()));
}
The results are then serialized to JSON. Each word entry looks like this in the output:
{
"text": "Invoice",
"quads": [{
"top-left": "(72.00, 720.50)",
"top-right": "(144.20, 720.50)",
"bottom-left": "(72.00, 708.30)",
"bottom-right": "(144.20, 708.30)"
}],
"styles": [{
"char-index": "0",
"font-name": "Arial-BoldMT",
"font-size": "14.00",
"color-space": "DeviceGray",
"color-values": ["0.000"]
}]
}
The quad coordinates are in PDF points (1/72 inch), with the origin at the bottom-left of the page. The color-values array contains one value per channel in the color space: one value for DeviceGray, three for DeviceRGB, four for DeviceCMYK.
Expected Output
A JSON file named ExtractTextPreservingStyleAndPositionInfo-out.json containing one object per word across all pages, with full position and style metadata. This output is well-suited for loading into document analysis tools, vector databases for semantic search, or training pipelines for layout-aware machine learning models.
Sample: extract_text_preserving_style_and_position_info.cpp on GitHub
Sample 3: Extracting Text by Pattern Match
When to Use This
Use ExtractTextByPatternMatch when you need to find and extract specific structured data from a PDF, such as phone numbers, email addresses, dates, invoice numbers, or any other pattern that can be described with a regular expression. This approach is faster than extracting all text first and then searching it, because the search happens at the word-finder level.
What the Code Does
The sample uses DocTextFinder, a higher-level class that wraps the WordFinder and adds pattern matching. The pattern is a standard C++ regex string. The get_match_list call searches all specified pages and returns every match:
// Phone number pattern
std::string pattern = "((1-)?\\(?\\d{3}\\)?(\\s)?(-)?\\d{3}-\\d{4})";
WordFinderConfig wf_config;
wf_config.set_no_hyphen_detection(true); // Preserve hyphens in phone numbers
DocTextFinder doc_text_finder(doc, wf_config);
auto doc_matches = doc_text_finder.get_match_list(0, n_pages - 1, pattern);
for (const auto& match : doc_matches) {
outfile << match.get_match_string() << "\n";
}
Note that set_no_hyphen_detection(true) is set here, which is the opposite of the TextExtract sample. This is intentional: phone numbers contain hyphens that are part of the data and should not be treated as line-break hyphenation. Always review the WordFinderConfig settings relative to your specific content type.
The pattern string uses standard C++ regex syntax. The phone number pattern handles US numbers with and without country code, with and without parentheses around the area code, and with and without spaces after the area code.
Expected Output
A plain text file named ExtractTextByPatternMatch-out.txt containing one match per line. Each line is the matched text exactly as it appeared in the PDF.
Sample: extract_text_by_pattern_match.cpp on GitHub
Sample 4: Extracting AcroForm Field Data
When to Use This
Use ExtractAcroFormFieldData when you are processing completed PDF forms and need the submitted field values. This is the right approach for any forms intake workflow: insurance applications, government filings, HR onboarding forms, customer registration forms, or any other fillable PDF that users complete and submit. The output is structured JSON that maps directly to the form's field names and their values.
What the Code Does
The sample calls dl_common::get_acro_form_field_data, which returns a vector of field structs containing the field name and the submitted text value. The results are printed to the console and written to JSON:
Document doc(input_path);
auto result = dl_common::get_acro_form_field_data(doc);
for (const auto& field : result) {
std::cout << "AcroForm Field Name > " << field.field_name << std::endl;
std::cout << "AcroForm Field Text > " << field.field_text << std::endl;
}
The JSON output uses a simple array of objects with AcroFormFieldName and AcroFormFieldText keys:
[
{ "AcroFormFieldName": "FirstName", "AcroFormFieldText": "Jane" },
{ "AcroFormFieldName": "LastName", "AcroFormFieldText": "Smith" },
{ "AcroFormFieldName": "PolicyNumber", "AcroFormFieldText": "A-12345" }
]
The field names in the output match the internal field names defined in the PDF form, which may differ from the visible labels shown on screen. If field names are not meaningful, consult the form's design documentation or examine the raw field names in the output to establish the mapping.
Expected Output
A JSON file named ExtractAcroFormFieldData-out.json containing one object per form field. This output is suitable for direct ingestion into a database, REST API, or data processing pipeline without any further parsing.
Sample: extract_acro_form_field_data.cpp on GitHub
Additional Text Extraction Capabilities
The Modern C++ Text folder includes additional samples beyond the four covered here. ExtractTextByRegion and ExtractTextFromMultiRegions extract text from defined rectangular zones on the page, which is useful for invoice processing and zonal extraction workflows. RegexExtractText and RegexTextSearch offer true regex-based extraction and search with more flexibility than DocTextFinder. ExtractTextFromAnnotations pulls text from comments, highlights, and other annotation types. These are all available in the Modern C++ samples repository.
Get Started
Check out more Modern C++ samples and a free trial of Adobe PDF Library SDK today.