Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions docling_core/types/doc/common/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,14 @@ def _load_local_image(self, base_dir: Path | None = None) -> None:

@property
def pil_image(self) -> PILImage.Image | None:
"""Return the PIL Image."""
"""Return the PIL Image, or None if its data cannot be decoded.

A URI may point at data that Pillow cannot open: an inline
``data:image/svg+xml`` (Pillow has no SVG decoder), a ``data:`` payload
that is not an image, or a local image file that is no longer readable.
Refusal to read a source at all stays an error - a blocked ``file:``
URI and an oversized payload keep raising :class:`ValueError`.
"""
if self._pil is not None:
return self._pil

Expand All @@ -134,15 +141,21 @@ def pil_image(self) -> PILImage.Image | None:
max_size=settings.max_image_decoded_size,
label="Image file",
)
self._pil = PILImage.open(local_path)
try:
self._pil = PILImage.open(local_path)
except PILImage.UnidentifiedImageError:
return None
elif isinstance(self.uri, AnyUrl) and self.uri.scheme == "data":
encoded_img = str(self.uri).split(",")[1]
decoded_img = base64.b64decode(encoded_img)

if len(decoded_img) > settings.max_image_decoded_size:
raise ValueError(f"Decoded image exceeds size limit of {settings.max_image_decoded_size} bytes.")

self._pil = PILImage.open(BytesIO(decoded_img))
try:
self._pil = PILImage.open(BytesIO(decoded_img))
except PILImage.UnidentifiedImageError:
return None
# else: HTTP(S) and other remote schemes are intentionally not fetched.
# If remote fetch is enabled, it must use host allowlists and block
# private, link-local, and cloud-metadata addresses (SSRF controls).
Expand Down
46 changes: 46 additions & 0 deletions tests/test_docling_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,52 @@ def test_pil_image():
assert reloaded_image.tobytes() == fig_image.tobytes()


def test_get_image_of_an_undecodable_ref_returns_none():
"""A reference whose data Pillow cannot open must not raise.

``FloatingItem.get_image`` is typed and documented as returning ``None`` when
no image is available, but it forwarded ``ImageRef.pil_image``, which called
``PILImage.open`` unguarded. An inline ``data:image/svg+xml`` reference (which
a Markdown or HTML backend produces when the inline image is an SVG) made
``get_image`` raise ``UnidentifiedImageError`` instead of returning ``None``,
so callers that skip a picture without an image crashed - the picture
enrichment models through ``prepare_element``, and any consumer that walks
the pictures of such a document.
"""
svg = (
'<svg xmlns="http://www.w3.org/2000/svg" width="120" height="80">'
'<rect width="120" height="80" fill="#1e64c8"/></svg>'
)
svg_data_uri = "data:image/svg+xml;base64," + base64.b64encode(svg.encode()).decode()

doc = DoclingDocument(name="Untitled 1")
doc.add_picture(
image=ImageRef(
dpi=72,
mimetype="image/svg+xml",
size=Size(width=120, height=80),
uri=AnyUrl(svg_data_uri),
)
)

picture = doc.pictures[0]
assert picture.image is not None # the payload is still referenced
assert picture.image.pil_image is None # but there are no pixels to decode
assert picture.get_image(doc=doc) is None


def test_pil_image_of_a_corrupt_data_uri_returns_none():
"""The same holds for a data URI that simply is not image data."""
image_ref = ImageRef(
dpi=72,
mimetype="image/png",
size=Size(width=1, height=1),
uri=AnyUrl("data:image/png;base64," + base64.b64encode(b"not an image").decode()),
)

assert image_ref.pil_image is None


def test_image_ref():
data_uri = {
"dpi": 72,
Expand Down
Loading