Tools,

PDF text comes out as gibberish? The font map is broken.

0

words right of 7,197, in all 5 extractors

For printed scans: $0.006 a page

Same page, three kinds of junk

Each page still looked the same, pixel for pixel.

What you see Why
%HQQHW ZDV for "Bennet was" No ToUnicode map. The reader prints glyph numbers as letters.
(cid:37)(cid:72) No map, and no cmap table in the font either.
Oeddej fgr for "Bennet was" A wrong map: real letters, the wrong ones.

In these files each glyph number was its letter's code minus 29, so B came out as %.

Five extractors, the same 40 pages

I made 40 PDF pages from known text, then broke their font maps. Words right, of 7,197. With the map in place, each one got 7,192.

Extractor No map No map, no cmap
pypdf 6.20.1 0 0
pdfplumber 0.11.10 7,168 0
PyMuPDF 1.26.6 0 0
pypdfium2 5.14.0 0 0
Poppler pdftotext 0 0

pdfplumber is the one survivor, because pdfminer.six falls back on the font's own cmap table. With a wrong map, all five got the same 82 words.

How I tested it
  • 20 book pages from Pride and Prejudice (Project Gutenberg) and 20 made-up invoices, in Times, Arial, Georgia and Courier New. Chrome printed each one to a PDF.
  • pikepdf took out each font's ToUnicode map. For the second column, fontTools also took the cmap table out of each font. The wrong map swapped letters with a fixed shuffle.
  • A word counts as right when the page has it and the output has it, in any order. The 5 misses on good pages were words split by a line break at a hyphen.

Check each page with a word list

Bad text is not empty, so count real words, then OCR:

import re

import pypdf
import pypdfium2 as pdfium
import pytesseract

with open("/usr/share/dict/words") as f:
    WORDS = {w.strip().lower() for w in f}


def looks_broken(text):
    if "(cid:" in text or "\ufffd" in text:
        return True
    words = re.findall(r"[^\W\d_]{2,}", text)
    if len(words) < 20:
        return True
    return sum(w.lower() in WORDS for w in words) / len(words) < 0.5


def page_texts(path):
    pdf = pdfium.PdfDocument(path)
    for i, page in enumerate(pypdf.PdfReader(path).pages):
        text = page.extract_text()
        if looks_broken(text):
            image = pdf[i].render(scale=300 / 72).to_pil()
            text = pytesseract.image_to_string(image, config="--dpi 300")
        yield text

0 of 40 good pages sent to OCR

120 of 120 broken pages caught

Which word list, and why 0.5?

On good pages, 75% or more of the words were in the list. On broken pages, 11% or less. The list is /usr/share/dict/words on a Mac. On Ubuntu, install wamerican. So 0.5 sits well inside the gap.

Keep --dpi 300, or tables fall apart

Broken pages, after the check and OCR Words right
20 book pages, of 5,376 5,336
20 invoices, of 1,821 1,708
20 invoices, no --dpi 300 357

Without it, Tesseract guessed the size of the rendered page and broke the invoice tables apart. Each broken page took 0.52 s (median), check and OCR together.

OCRmyPDF: only --force-ocr fixed it

One 40-page file with no map and no cmap, 7,197 words, 1.3 MB.

Flag Words right Output
--skip-text 0 1.3 MB
--redo-ocr 9 1.3 MB
--force-ocr 7,015 10.4 MB

All three exited with code 0. The first two keep the page's own text, so the junk stays. To OCR only the pages the check flags, list them:

ocrmypdf --force-ocr --pages 21-40 in.pdf out.pdf

On a file of 20 good and 20 bad pages, that OCRed only the 20 bad ones and took 4.6 s, not 8.6 s.

Limits, and what I did not test
  • Real files break in more ways: bitmap fonts, text drawn as shapes, maps scrambled on purpose. Mine were broken by me.
  • Other languages. The check needs a word list in the page's language.
  • Windows and Linux. One Mac, Tesseract 5.5.4, English model.
Sources

Our Actor catches 2 of the 3 kinds

Our Apify Actor reads a page's own text with pdfplumber, so the missing map came back right: 7,168 words. It OCRs pages of (cid:x). A wrong map passes its check, and those 40 pages came back as junk. Run the check above first.

Two ways to skip the work

Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.

Try the Actor

Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.

Get one page free

Questions people ask

Why does text copied from a PDF come out as gibberish?

The font's letter map, called ToUnicode, is missing or wrong. The reader gets glyph numbers, not letters. The page still looks right, because the glyphs are fine.

What does (cid:x) mean in pdfminer or pdfplumber output?

A glyph number with no letter. The font has no ToUnicode map and no cmap table, so nothing in the file says what the letter is. OCR the page.

Does OCRmyPDF --redo-ocr fix gibberish text?

No. It keeps the page's own text. In my test it got 9 of 7,197 words right, and --force-ocr got 7,015.