Extracting Text
Pdf::extractTextFromFile() and ::extractTextFromData() pull the text layer back out of an
existing PDF. Extraction is native to Pop PDF — no external binary, no third-party PDF library —
and decodes text against embedded and CID fonts as well as the standard names; see
Embedded and CID Fonts.
Extracting From a File#
extractTextFromFile($file, $pages = null, $pageLimit = null, $password = null) takes a path and
returns one string:
use Pop\Pdf\Pdf;
$text = Pdf::extractTextFromFile(__DIR__ . '/report.pdf');
Given a three-page report.pdf, one line of text on each page, that returns every page's text
joined into a single value:
"Page One text.\n\nPage Two text.\n\nPage Three text."
echoing it prints each page on its own line with a blank line between:
Page One text.
Page Two text.
Page Three text.
How Pages Are Separated covers what produces that blank line, and what a page with no text contributes.
$file has to point at something on disk — a path that does not exist raises
Pop\Pdf\Extract\Exception:
use Pop\Pdf\Pdf;
use Pop\Pdf\Extract\Exception;
try {
Pdf::extractTextFromFile(__DIR__ . '/missing.pdf');
} catch (Exception $exception) {
echo $exception->getMessage();
}
That prints Error: That PDF file does not exist. A path that exists but isn't a valid PDF raises
the same exception with a different message — extractTextFromFile() on a plain text file, or
extractTextFromData() on a string that isn't PDF bytes at all, both fail with Error: Could not resolve the PDF document catalog (Root).
Extracting From Raw Bytes#
extractTextFromData($data, $pages = null, $pageLimit = null) is the same call against a string of
PDF bytes instead of a path — for a document that was uploaded, fetched over HTTP, or generated in
memory and never written to disk:
use Pop\Pdf\Pdf;
$data = file_get_contents(__DIR__ . '/report.pdf');
$text = Pdf::extractTextFromData($data);
Both methods load their input into the same Extract\Document and run the identical walk over it,
so the same file returns the identical string either way — which one to call is a question of what
you already have in hand, a path or a buffer.
Selecting Pages#
$pages is the third argument on both methods — an array of 1-based page numbers, or a single
number on its own; 2 and [2] mean the same thing:
use Pop\Pdf\Pdf;
$second = Pdf::extractTextFromFile(__DIR__ . '/report.pdf', pages: 2);
$firstAndThird = Pdf::extractTextFromFile(__DIR__ . '/report.pdf', pages: [1, 3]);
$second is "Page Two text."; $firstAndThird is "Page One text.\n\nPage Three text.".
- selection follows document order, not list order —
pages: [3, 1]returns page one's text first, exactly whatpages: [1, 3]returns - a page number past the end of the document drops out of the selection rather than raising —
[1, 9]on a three-page document returns page 1's text alone - a selection that matches nothing — every number out of range, or an empty array — returns
''rather than raising, the same string an empty page returns; counting the result against the request is the only way to tell the two apart
use Pop\Pdf\Pdf;
Pdf::extractTextFromFile(__DIR__ . '/report.pdf', pages: [9]); // '' — page 9 doesn't exist
Pdf::extractTextFromFile(__DIR__ . '/report.pdf', pages: []); // '' — nothing was asked for
Limiting How Much Is Walked#
$pageLimit is the fourth argument, and caps extraction to the document's first n pages:
use Pop\Pdf\Pdf;
$firstTwo = Pdf::extractTextFromFile(__DIR__ . '/report.pdf', pageLimit: 2);
That returns "Page One text.\n\nPage Two text.". A limit larger than the document, 99 on a
three-page file, returns every page, the same as no limit at all — and the limit is enforced during
the walk, not by truncating the joined string: pages beyond it are never decompressed or
interpreted, so a low limit on a long document skips real work, not only output.
An explicit $pages list wins outright over $pageLimit, which only applies when $pages is
null:
use Pop\Pdf\Pdf;
$text = Pdf::extractTextFromFile(__DIR__ . '/report.pdf', pages: [3], pageLimit: 1);
// "Page Three text." — the explicit list, not the first page the limit would have picked
How Pages Are Separated#
Pages join with a blank line, "\n\n", and only between pages that actually produced text — a page
with no text at all contributes nothing, not even an empty entry:
use Pop\Pdf\Pdf;
// A three-page document where the middle page carries no text at all
$text = Pdf::extractTextFromFile(__DIR__ . '/mixed-report.pdf');
That returns "First page has text.\n\nThird page has text." — no trace of the blank page between
them. Selecting it on its own, pages: 2, returns '', the same as any other selection matching
nothing.
Embedded and CID Fonts#
Extraction decodes text against whatever font drew it, including a font embedded in the document itself, not only the standard names: an embedded TrueType or OpenType font decodes as two-byte glyph IDs through its own character map, while a standard or embedded Type1 font stays on a single-byte path. A compressed content stream — Flate, LZW, RunLength, ASCII85 or ASCIIHex, in any combination a producer chose to apply — extracts the same as one left uncompressed.
The round trip is the way to see this working rather than take it on faith: write non-ASCII text through an embedded TrueType font and extract it straight back.
use Pop\Pdf\Document;
use Pop\Pdf\Document\Page;
use Pop\Pdf\Document\Page\Text;
use Pop\Pdf\Document\Font;
use Pop\Pdf\Pdf;
$font = new Font(__DIR__ . '/fonts/DejaVuSans.ttf');
$document = new Document(new Page(Page::LETTER));
$document->embedFont($font);
$original = 'Café Müller — €120 naïve';
$document->getPage(1)->addText(new Text($original, size: 24), $font->getName(), x: 72, y: 700);
Pdf::writeToFile($document, filename: __DIR__ . '/embedded.pdf');
$extracted = Pdf::extractTextFromFile(__DIR__ . '/embedded.pdf');
$extracted === $original; // true
The accented letter, umlaut, em dash and euro sign all survive, and the bytes match exactly, not only the visible characters:
bin2hex($extracted); // '436166c3a9204dc3bc6c6c657220e2809420e282ac313230206e61c3af7665'
bin2hex($original); // '436166c3a9204dc3bc6c6c657220e2809420e282ac313230206e61c3af7665'
Identical hex confirms the same UTF-8 sequence went in through addText() and came back out
unchanged, not merely a string that happens to display the same way.
Embedding Fonts covers registering the font and checking its glyph
coverage before you draw with it.
See Also#
- Image-Only Pages — calling
isImageOnlyDocument()to decide whether a document has a text layer to extract from at all - Pages as Images — rasterizing pages that turn out to be scans rather than text
- Opening Encrypted PDFs — the
$passwordargument these methods share - Embedding Fonts — registering a TrueType, OpenType or Type1 font and checking its glyph coverage before drawing text with it