My PDF renders fine but extracts as gibberish.

The page looks perfect on screen. The glyph shapes are all there. Reading the text back out uses a separate table entirely. When it's missing, extraction has nothing reliable to translate character codes into.

looks-like

Q3 (cid:415)(cid:286)(cid:448)(cid:286)(cid:374)(cid:437)(cid:286) grew 34% year over year

Poppler-based tools (pdftotext) surface this literally as (cid:N) glyph-index tokens when they give up. Tika/PDFBox, what txtfetch runs, more often substitutes a replacement character or drops the run silently instead. Different libraries fail differently, but the root cause is the same.

why-it-happens

The page renders perfectly because rendering only needs the font's outline data, not a mapping back to what character each glyph represents. Reading the text back out needs a second table entirely: /ToUnicode, a CMap recording which Unicode codepoint each glyph stands for. When it's missing or wrong, an extractor has nothing reliable to translate character codes into.

It matters most for composite /Type0 fonts using /Identity-H encoding. These map character codes straight to glyph indexes inside the embedded font program. There's no encoding-derived fallback meaning, only whatever /ToUnicode says. That's the one font shape this is certain about. Other font types generally aren't affected the same way.

Ordinary simple fonts (/Type1, /TrueType) without a /ToUnicode map are usually fine. Their own encoding already establishes what each code means: a standard name like /WinAnsiEncoding, or the base-14 fonts' built-in StandardEncoding. This is specific to the /Type0 + /Identity-H + no-/ToUnicode combination. That's why it hits some PDFs from a producer and not others.

A common source is a font subsetted down to only the glyphs actually used, without regenerating the CMap alongside it. Or a producer optimized for print fidelity and never considered anyone would try to read the text back out.

confirm-it

  • The PDF text-layer checker's structural scan flags exactly this font shape: Type0/Identity-H with no ToUnicode. It's the one case it can be certain about from structure alone. Check the PDF's font encoding
  • Paste a sample of the extracted text into the chunk previewer. Mojibake and stray replacement characters both surface there as named, counted signals if that's the shape your garbling takes. Scan your extracted text

fix-it-yourself

Regenerate the PDF with a font that embeds ToUnicode

There's no reading-side fix. A missing CMap can't be reconstructed from glyph shapes alone. If you control the source, such as a report generator or a print-to-PDF step: switch to a library or driver that writes ToUnicode. Or re-export from the original document.

OCR the affected pages instead of extracting their text

Render the page to an image and OCR the rendering directly. OCR reads glyph shapes, and never touches the broken CMap that trips up text extraction.

bash

pdftoppm -png -r 300 input.pdf page
tesseract page-1.png output

Detect the failure before you trust the text

A cheap heuristic: count literal (cid:N)-shaped tokens or out-of-range characters in the extraction. A spike is a strong signal this page hit the CMap problem.

python

import re
suspicious = len(re.findall(r"\(cid:\d+\)", text))
if suspicious > 5:
    print("likely missing ToUnicode CMap — consider OCR for this page")

what-txtfetch-does

txtfetch runs Apache Tika (PDFBox underneath), which resolves /ToUnicode the same way any PDFBox-based reader does. When the CMap is missing on a Type0/Identity-H font, that run of text either won't decode to meaningful Unicode or comes back as replacement characters. It does this honestly, with no silent guessing at what it might have said.

Automatic Tesseract escalation won't rescue this on its own. It only fires when the whole document's first pass comes back blank, and a broken-CMap page returns text, just wrong text (see /fixes/pdf-extracts-no-text). Since the page isn't blank, nothing about the default path detects the problem.

What does work is ?quality=premium. It routes every page through a vision-language model reading the rendered page, which never touches the broken /ToUnicode table that defeats text extraction. That's the same reason the render-then-OCR route above works, without you running it yourself. Premium always routes async: a 202 response plus a job_id to poll. It falls back to the Tika baseline with metadata.tier_downgraded: true if a guardrail trips. Keep the DIY route above as your check on a document where the text matters and you want to compare.

what-it-costs-you-downstream

Garbled runs break sentence- and paragraph-boundary detection right where they occur, so a chunker draws boundaries blind through the damage. Every embedding computed over gibberish tokens pollutes the vector space around genuinely related, cleanly-extracted content nearby.

faq

Why does my PDF render perfectly but extract as garbage?
Rendering only needs the font's glyph outlines. Reading text back out needs a separate table, /ToUnicode, mapping each glyph to a Unicode character. That table is often missing or broken on a subsetted Type0/Identity-H font. When it is, extraction has nothing to translate character codes into, even though the page looks completely normal.
Is this the same thing as mojibake?
No. Mojibake is a decoding bug: the right bytes, the wrong encoding applied. It's reversible by re-decoding correctly. A missing ToUnicode CMap means there's no mapping to reverse. Reading the rendered page instead of its character codes is the practical fix, not a re-decode. See /fixes/mojibake-wrong-encoding for the encoding case.
Can I fix this after the fact, in the text I already extracted?
No. Once the character codes have been read without a valid ToUnicode map, the original glyph identity generally can't be recovered from the output text alone. You have to re-read the page rather than repair the string. Use either ?quality=premium, which routes the page through a vision-language model, or the DIY render-to-image-then-OCR route above.

related-reading

Fix the text you already have.

The free cleaner repairs this damage in your browser. Nothing leaves the page.

Clean up your text →

Get an API key →