how does pdf text extraction work
How Text Extraction From a PDF Works
There are no words in a PDF. There are numbered glyphs at coordinates. Everything you get back from a text extractor is an educated reconstruction, which is why it is sometimes uncannily good and sometimes gibberish.
01
Step one: find the drawing instructions
The extractor decompresses each page's content stream and walks it looking for text operators. Text lives between `BT` and `ET` — begin text, end text — and inside that block a handful of operators do all the work. `Tf` selects a font and size. `Tm` and `Td` set the position. `Tj` and `TJ` draw a string.
So the raw material is a list like: at (72, 720), in font F1 at 12pt, draw these bytes. Repeat a few thousand times. That is genuinely all the file contains.
02
Step two: turn glyph codes into characters — the hard part
The bytes inside a `Tj` are not Unicode. They are indices into a font's internal glyph table. Byte 0x44 means "draw glyph 68 of this font", and glyph 68 is only the letter D if the font says so.
There are two ways to find out. Simple fonts carry an encoding — often a standard one like WinAnsi, sometimes a custom Differences array that remaps individual slots. Composite fonts, which is most modern embedded ones, need a `ToUnicode` CMap: an explicit table in the file mapping each glyph code to a Unicode character.
When that CMap is present and correct, extraction is exact. When it is missing, the extractor is guessing — usually by falling back to the glyph names in the font, and failing that, to the raw codes. That is the origin of extracted text that comes out as a stream of accented consonants or Chinese characters from a plainly English document. Nothing is broken; the file simply never said what its glyphs meant.
Subset fonts make this worse. To save space, a writer embeds only the glyphs used and renumbers them, so the mapping is entirely arbitrary and entirely dependent on the CMap the writer may or may not have bothered to include.
- ToUnicode CMap present
- Exact extraction. The file states what each glyph means.
- Standard encoding, no CMap
- Usually correct for plain Latin text. Accents and symbols start to slip.
- Subset font, no CMap
- Mojibake. The glyph numbering is arbitrary and nothing in the file decodes it.
- Text converted to outlines
- No text at all — the letters are vector shapes. Only OCR can read it, exactly as with a scan.
03
Step three: invent the spaces
This surprises people. A PDF does not have to store spaces. Two words are frequently drawn as two separate strings with a position jump between them, or as one `TJ` array with a negative number in the middle — `[(Hel) -20 (lo)] TJ` — where the number is a kerning adjustment in thousandths of an em.
So the extractor measures. It knows the advance width of each glyph from the font, so it knows where a character *should* have ended. If the next character starts noticeably further right than that, it inserts a space. If the gap is small, it does not.
The threshold is a heuristic, and it is why extracted text sometimes arrives as `T h i s i s s p a c e d` or, going the other way, as `wordsrunningtogether`. Justified text is the usual culprit: stretching a line to fit the margin widens the real spaces until intra-word kerning starts to look like one.
04
Step four: rebuild lines, columns and reading order
Now the extractor has characters with coordinates and no concept of a line. It groups by vertical position — glyphs sharing a baseline, within a tolerance, are one line — then sorts each line left to right, then sorts the lines top to bottom.
That works for a single column. It fails badly on two, because content-stream order is drawing order, and coordinate order down the page runs straight across the gutter. Line one of column one, then line one of column two, then line two of column one. The result is readable words in unreadable order.
Better extractors do layout analysis first: find the whitespace gutters, split the page into column blocks, and extract each block in full before moving on. Tables get the same treatment, one dimension up — cells are clustered into rows and columns by alignment, because nothing in the file says "table". A ruled border is four lines drawn on the page, and it means nothing to the extractor.
This is also where paragraph reconstruction happens, and where hyphenation has to be undone. A word broken across a line break is two glyph runs and a hyphen; joining them back needs a rule about when a trailing hyphen is a line break and when it is part of a compound word.
05
The failure modes, and what each one means
Because you can now read the symptom backwards to the cause:
- Nothing comes out at all
- There is no text layer. The page is an image, or the text was converted to outlines. Run OCR.
- Unreadable characters
- A missing or wrong ToUnicode CMap. The glyphs drew fine; nothing decodes them.
- Text in the wrong order
- Multi-column layout, or a drawing order that does not match reading order.
- Missing or extra spaces
- The gap heuristic. Common in justified text and in headings with wide tracking.
- "fi" and "fl" in the output
- Ligatures. Two letters drawn as one glyph, and the CMap mapped it to the single ligature character rather than to the pair.
06
Why this is the same problem as PDF to Word
Converting to Word is text extraction plus one more layer of inference. Having reconstructed characters, spaces, lines and columns, the converter has to guess semantics: this run is 18 point and bold and sits alone above a paragraph, so it is probably a heading; these lines all start at the same indent with a bullet glyph, so this is probably a list.
Every one of those is a guess made from visual evidence, because the file records appearance and nothing else. It is why conversion quality varies so much with the source. A PDF exported from Word, which may still carry tagging, converts well. A PDF produced by a typesetting engine carries no structure at all, and the converter is reading it exactly as you would from a printout.