DOCX Image Extraction#
The DOCX image extraction logic lives in MsWordDocumentBackend (docling/backend/msword_backend.py). It handles three distinct image types — DrawingML blip pictures, VML legacy images, and blip-less DrawingML shapes (SmartArt, charts) — dispatched from the central _walk_linear() method.
Three Extraction Paths#
The dispatcher in _walk_linear classifies each XML element and routes it accordingly :
| Element type | Detected via | Handler |
|---|---|---|
DrawingML picture (a:blip) | blip_xpath_expr XPath match | _handle_pictures() |
VML image (v:imagedata) | vml_imagedata_xpath_expr XPath match | _handle_vml_pictures() |
| Blip-less DrawingML (shapes, SmartArt) | drawingml_els XPath match | _handle_drawingml() |
DrawingML blip pictures (_handle_pictures)#
For each a:blip element, the backend calls _get_image_from_relationship() using the r:embed relationship attribute to retrieve raw image bytes. It then attempts direct PIL loading via Image.open() and validates the image by round-tripping it to PNG . On UnidentifiedImageError or OSError (e.g., WMF/EMF formats PIL can't decode), it falls back to _convert_elements_via_docx().
VML images (_handle_vml_pictures)#
Mirrors the DrawingML path but uses r:id as the relationship attribute (not r:embed) and searches for parent object or pict tags during DOCX conversion fallback . VML is the legacy format for embedded Visio drawings and similar objects.
Blip-less DrawingML shapes (_handle_drawingml)#
Shapes and SmartArt have no embedded image to extract directly. These are always rendered via _convert_elements_via_docx(), which requires LibreOffice. If LibreOffice is unavailable, a one-time warning is logged and the element is added as a picture placeholder . Native charts (graphicFrame elements referencing word/charts/chartN.xml) are handled separately by _handle_chart() without LibreOffice .
Relationship Resolution#
_get_image_from_relationship() is the single function responsible for resolving a relationship ID to binary image data. It uses self.current_part (which may be overridden for headers/footers) to look up the relationship target, skips external references with a warning, and returns the blob bytes from image_part.blob .
DOCX→PDF→PNG Rendering Pipeline#
_convert_elements_via_docx() handles all cases where PIL cannot directly decode an image:
- Loads a fresh copy of the source DOCX, strips its body, and inserts a deep-copy of the target element(s) into a new paragraph run.
- Calls
get_pil_from_dml_docx()(indocling/backend/docx/drawingml/utils.py), which converts the scratch DOCX to PDF via LibreOffice, renders page 0 at 2× scale using pypdfium2, then crops whitespace. - Returns a PIL
ImageorNoneon failure .
The LibreOffice converter is lazily initialized once per document via the docx_to_pdf_converter_init flag . See the LibreOffice Integration knowledge base article for discovery and availability details.
Text Preservation#
A critical invariant: text in the same paragraph as an image must not be lost. All three dispatch branches in _walk_linear check for w:t nodes after handling the image and call _handle_text_elements() if any exist . For the blip-less DrawingML branch, text is always processed with skip_empty_text=True to filter spurious empty runs .
This was hardened across three PRs:
- PR #3394 — added image + text extraction for textbox containers, preventing double-extraction via XPath ancestry checks.
- PR #3484 — fixed text loss when LibreOffice is absent and DrawingML conversion is skipped.
- PR #3490 — made text extraction unconditional (regardless of LibreOffice availability), introducing
skip_empty_text=Trueto avoid spurious empty items.
Spacer Filtering#
After any image is decoded, _is_invisible_spacer() filters out layout artifacts using three heuristics: area ≤ 25 px², fully transparent RGBA/LA images, and pure-white RGB images. Spacers are added to the document under ContentLayer.INVISIBLE rather than being dropped , preserving document structure without polluting main content.
Key Source Files#
| File | Role |
|---|---|
docling/backend/msword_backend.py | All image extraction handlers and dispatch logic |
docling/backend/docx/drawingml/utils.py | LibreOffice DOCX→PDF converter, PIL rendering, whitespace crop |