PDF Text Extraction#
PDF text extraction in Docling is handled by two backend classes in docling/backend/docling_parse_backend.py: DoclingParseDocumentBackend (serial, default) and ThreadedDoclingParseDocumentBackend. Both delegate all parsing to the C++ docling-parse library, which performs font decoding, character extraction, and layout geometry.
Architecture#
The flow per page:
DoclingParseDocumentBackendloads the document into aPdfDocumentviaDoclingPdfParser. The threaded variant usesDoclingThreadedPdfParserand streamsPageParseResultobjects viaiter_pages().- On first access,
_ensure_parsed()callsdp_doc.get_page()and receives aSegmentedPdfPagecontainingtextline_cells,word_cells,char_cells, andbitmap_resources. - Coordinate origin flip: docling-parse emits coordinates in bottom-left origin. Every cell list is immediately converted to top-left origin via
to_top_left_origin(page_height). get_text_cells()returnstextline_cellsfor the layout pipeline ;get_text_in_rect()queries cells by bounding-box overlap (≥50% intersection-over-self) for table cell matching .
The ContentConfig object controls which cell levels are computed vs. skipped — word/line cells are materialized, raw char cells are computed but not materialized unless keep_chars=True . Bitmap rectangles are extracted for OCR region detection; bitmap bytes are never loaded .
After layout detection, PageAssembleModel maps clusters to text by consuming textline_cells via sanitize_text().
Backend Options#
PdfBackendOptions is the base config for all docling-parse backends. Key fields:
| Option | Default | Purpose |
|---|---|---|
enforce_same_font | True | Split text cells at font boundaries. Set False for PDFs where base glyphs and diacritics use separate fonts (see Diacritics). |
password | None | Decrypt password-protected PDFs. |
ThreadedDoclingParseBackendOptions extends the above:
| Option | Default | Purpose |
|---|---|---|
parser_threads | None (falls back to AcceleratorOptions.num_threads) | Parallelism inside docling-parse. |
release_native_memory_every_n_pages | 128 | Flush C++ allocations every N pages. Set 0 to disable. |
Pass options via PdfFormatOption(backend_options=PdfBackendOptions(...)) to DocumentConverter.
Text Sanitization#
PageAssembleModel.sanitize_text() is applied to every assembled text cluster. Transformations run in order:
- Dehyphenation — removes end-of-line hyphens when the joined result forms a valid alphanumeric word .
- Typographic character normalization — replaces fraction slash, curly quotes, and bullets with ASCII equivalents .
- Ligature expansion —
_LIGATURE_REmatches ligature codepoints (optionally followed by a spurious space before a word character) and replaces them via_LIGATURE_MAP:
| Codepoint | Expands to | Notes |
|---|---|---|
| U+FB00–FB06 | ff, fi, fl, ffi, ffl, st, st | Spurious trailing space absorbed |
| U+0132 / U+0133 | IJ / ij | Dutch ligatures; trailing space preserved |
| U+F0A0 | (empty) | Private-Use Area glyph; discarded |
The ligature map was introduced in PR #3057 and extended for Dutch IJ and U+F0A0 in PR #3254.
Limitation: this normalization only catches ligatures mapped to Unicode codepoints. PDFs that encode ligatures as raw glyph names (e.g., /uniFB01-style references never resolved to Unicode) will not be normalized by this path.
Known Encoding Challenges#
Diacritics / Font Splitting#
Some PDFs render base glyphs and diacritics using different embedded fonts. With enforce_same_font=True (default), docling-parse splits such glyph sequences into separate text cells, producing space-separated fragments (e.g., Zwierząt → Zwierz ą t).
Workaround: set enforce_same_font=False in PdfBackendOptions. This was exposed as a user option in PR #3737 .
Ligatures#
PDF fonts often encode typographic ligatures as single Unicode codepoints (U+FB00–FB06). Extractors may also insert a spurious space between the ligature glyph and the following characters, breaking words like verification into veri fi cation. The sanitize_text() pipeline handles these cases. See PR #3057 and the open tracking issue .
CID-Keyed Fonts / Missing ToUnicode CMap#
CJK PDFs (e.g., Chinese GB/T standards) frequently use CID-keyed fonts with a ToUnicode CMap. When the embedded font subset omits glyph mappings, extraction produces garbled characters (犌犅/犜 instead of correct Chinese text). This is an encoding-level failure, not a rendering failure — force_full_page_ocr cannot recover the correct text because OCR re-reads pixels that look fine visually . There is currently no automatic workaround; affected pages require a correctly-encoded source PDF.
GLYPH Artifacts from Type3 / Missing-CMap Fonts#
Before PR #2738, enabling force_full_page_ocr=True replaced textline_cells with OCR output but left word_cells and char_cells intact. Downstream components (especially TableStructureModel) consumed those stale cells, producing GLYPH<c=1,font=/AAAAAH+font000000002ed64673> strings in table content. The fix in BaseOcrModel.post_process_cells() now filters out all non-OCR (from_ocr=False) word and char cells when full-page OCR is active , reducing corrupted chunks from 1359 to ~50 in the reported test set.
Key Source Files#
| File | Purpose |
|---|---|
docling/backend/docling_parse_backend.py | DoclingParseDocumentBackend, ThreadedDoclingParseDocumentBackend, coordinate-origin flip, get_text_cells(), get_text_in_rect() |
docling/datamodel/backend_options.py | PdfBackendOptions (enforce_same_font, password), ThreadedDoclingParseBackendOptions |
docling/models/stages/page_assemble/page_assemble_model.py | sanitize_text(), _LIGATURE_MAP, _LIGATURE_RE |
docling/models/base_ocr_model.py | post_process_cells() — filters stale word/char cells when force_full_page_ocr=True |
Related issues and PRs:
- PR #3057 — Unicode ligature normalization (U+FB00–FB06)
- PR #3254 — Extended ligature map (Dutch IJ, U+F0A0)
- PR #2738 — Clear word/char cells on
force_full_page_ocr - PR #3737 —
enforce_same_fontuser option - Issue #3582 — CID-font garbled text with GB/T PDFs
- Issue #3420 — Ligature glyphs generating spaces