Skip to content

fix: correct CID TrueType text extraction for Identity-H fonts#75

Merged
kolkov merged 2 commits into
mainfrom
fix/cid-font-text-extraction
May 21, 2026
Merged

fix: correct CID TrueType text extraction for Identity-H fonts#75
kolkov merged 2 commits into
mainfrom
fix/cid-font-text-extraction

Conversation

@kolkov

@kolkov kolkov commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix CID TrueType font text extraction that returned raw glyph IDs (\x01 \x02 \x03) or empty strings instead of Unicode text for PDFs with Identity-H encoding and ToUnicode CMap.

Five bugs fixed in the CMap/font decoder pipeline:

  1. beginbfrange array format — previously silently skipped, now fully parsed
  2. Identity-H 2-byte detection — all loadFontDecoder fallback paths now honor 2-byte mode
  3. begincodespacerange parsing — byte width determined from PDF spec signal
  4. UTF-16BE surrogate pairs — CMap destinations for U+10000+ now correctly decoded
  5. Composite font guardlooksLikeGarbage heuristic no longer downgrades Type0 fonts

Fixes #74

Test plan

  • go fmt ./... clean
  • go vet ./... clean
  • go test ./... — 23 packages, all passing
  • golangci-lint run — no new issues
  • 33 new tests: bfrange array parsing, codespacerange detection, surrogate pair decoding, composite font guard, end-to-end integration with synthetic Type0/CIDFontType2 PDF
  • All existing CMap and font decoder tests still pass
  • Zero breaking changes
  • CHANGELOG, ROADMAP updated

kolkov added 2 commits May 21, 2026 12:02
…oUnicode CMap

Five root causes of raw glyph IDs being returned instead of Unicode text:

1. parseBfRange array format [<dst0> <dst1>...] was skipped with 'continue'.
   Now fully parsed: each source code maps to its explicit destination rune.

2. No-ToUnicode early-return paths hardcoded use2ByteGlyphs=false even when
   encoding was Identity-H/V. Now detects Identity encoding and Type0 subtype
   and sets use2ByteGlyphs=true on all early-return paths.

3. begincodespacerange was not parsed. CMapTable gains CodeBytes field (default 1).
   Parser reads the hex-string length from the range to determine 1-byte vs
   2-byte codes and stores it in CodeBytes. loadFontDecoder promotes
   use2ByteGlyphs when CodeBytes==2, even without an Identity encoding name.

4. UTF-16BE surrogate pairs in bfchar/bfrange destinations were silently
   truncated to a single uint16. New decodeUTF16BEHex helper detects 4-byte
   (8-hex-char) sequences, checks for high-surrogate (0xD800-0xDBFF), and
   decodes via unicode/utf16.DecodeRune. Both parseBfChar and parseBfRange
   now use this helper for all destination parsing.

5. Garbage-detection fallback in DecodeString downgrades 2-byte composite
   font decoding to 1-byte, silently corrupting all CID glyph IDs above 0xFF.
   FontDecoder gains isCompositeFont bool field. When true the fallback is
   suppressed entirely. loadFontDecoder sets the flag for Type0 fonts.

Tests: 33 new tests covering all five bugs plus integration test with a
synthetic Type0/CIDFontType2 PDF using bfrange array format.
@kolkov kolkov merged commit 899eaa3 into main May 21, 2026
7 checks passed
@kolkov kolkov deleted the fix/cid-font-text-extraction branch May 21, 2026 09:16
@codecov

codecov Bot commented May 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.42857% with 38 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/extractor/cmap_parser.go 75.70% 16 Missing and 10 partials ⚠️
internal/extractor/text_extractor.go 52.00% 10 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExtractTextFromPage returns CID glyph IDs (or empty) for PDFs with embedded CID TrueType fonts and ToUnicode CMap

1 participant