fix: emit glyph marker instead of fabricated encoding text for symbolic-font cmap misses#299
Open
wittjeff wants to merge 1 commit into
Open
Conversation
…ic-font cmap misses When a character code is missing from both /Encoding/Differences and an explicit /ToUnicode (or predefined CID) CMap, get_correct_character() fell back to the declared base encoding and then StandardEncoding. For symbolic fonts — e.g. subsetted Arabic fonts, whose codes bear no relation to the Latin encodings — this fabricates unrelated ASCII text: an Arabic ligature glyph at code 0x23 whose ToUnicode entry the generator omitted comes out as a literal '#' (docling-project/docling#3802). Route the unmapped-code fallbacks through resolve_unmapped_character(): if an explicit cmap exists but misses the code and the font is symbolic (/FontDescriptor /Flags bit 3) or composite (Type0), emit the existing GLYPH<code> marker so downstream consumers can detect the unresolved glyph. Authoritative sources (Differences, cmaps, known base-font tables) stay ahead of the guard; non-symbolic simple fonts and fonts without any ToUnicode keep the current behavior. Also hoist the /FontDescriptor /Flags parsing into init_font_flags(), reused by build_embedded_font_blob() which previously parsed it inline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jeff Witt <152964771+witt3rd@users.noreply.github.com>
Contributor
|
✅ DCO Check Passed Thanks @wittjeff, all your commits are properly signed off. 🎉 |
Contributor
Merge Protections🟢 Merge protection satisfied — ready to merge. Show 1 satisfied protection🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
|
wittjeff
marked this pull request as ready for review
July 14, 2026 18:13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the text-extraction corruption reported in docling-project/docling#3802 (Arabic في coming out as
#). Full root-cause analysis: docling-project/docling#3802 (comment)Problem
When a character code is not covered by
/Encoding/Differencesor the font's/ToUnicodeCMap,get_correct_character()falls through toget_character_from_encoding(), which resolves the code through the declared base encoding and then StandardEncoding. For symbolic fonts (e.g. subsetted Arabic fonts, whose codes are assigned sequentially from 0x21 and bear no relation to Latin encodings) this fabricates ASCII text: an Arabic ligature glyph subsetted at code0x23whose ToUnicode entry the generator omitted — a common generator bug for one-glyph-to-many-codepoints ligatures — comes out as a literal#.Because the fabricated character looks like plausible text, downstream consumers cannot detect the corruption (docling's
rate_text_quality()gate specifically watches forGLYPH<...>markers, which never appear on this path).Fix
The unmapped-code fallback call sites in
get_correct_character()are routed through a newresolve_unmapped_character(): when an explicit/ToUnicode(or predefined CID) CMap exists but does not cover the code, and the code cannot meaningfully resolve through the standard Latin encodings — the font is symbolic (/FontDescriptor /Flagsbit 3) or composite (Type0, whose codes are CIDs) — it emits the existingGLYPH<code>marker instead of fabricating text from the encoding tables./Differences, the cmaps, and the known base-font tables (fm.has(c)— e.g. TeX math fonts resolving∑).0x23genuinely is#).keep_glyphs=False) the marker is already rendered as a space, so consumers see whitespace instead of a wrong character; withkeep_glyphs=Truethe marker is visible and can trigger OCR fallback logic./FontDescriptor /Flagsparsing is hoisted intoinit_font_flags()and reused bybuild_embedded_font_blob(), which previously parsed it inline.Tests
tests/test_tounicode_fallback.pybuilds the PDFs in memory (same pattern astest_standard_font_widths.py): a simple TrueType font, text bytes0x21 0x22 0x23, ToUnicode covering0x21/0x22:#(fabricated)GLYPH<35><23> <0641064A>##(unchanged)The reference regression suite (
test_reference_documents_from_filenames) passes unchanged, and a real-world Arabic PDF with complete ToUnicode (UAE labor law, ADJD mirror) extracts identically before/after.🤖 Generated with Claude Code