From df7b5bb1fc4b68f5292d9fcc79b3b7d6e24d3e98 Mon Sep 17 00:00:00 2001 From: Thiago Cunha Date: Tue, 8 Jul 2025 19:02:13 -0300 Subject: [PATCH] fix(image): fallback to 72 dpi when header dpi is 0 (closes #1494) --- HISTORY.rst | 8 ++++++ src/docx/image/image.py | 48 ++++++++++++++++++++++------------ tests/test_files/zero_dpi.jpg | Bin 0 -> 9042 bytes tests/test_zero_dpi.py | 17 ++++++++++++ 4 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 tests/test_files/zero_dpi.jpg create mode 100644 tests/test_zero_dpi.py diff --git a/HISTORY.rst b/HISTORY.rst index 69bba4161..d04cfefae 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,14 @@ Release History --------------- +Unreleased +++++++++++ + +* **Fixed** – Issues #1497 / #1494: adding an image whose header reports + `dpi = 0` now falls back to `72 dpi` instead of raising + `ZeroDivisionError` when using `Document.add_picture()`. + + 1.2.0 (2025-06-16) ++++++++++++++++++ diff --git a/src/docx/image/image.py b/src/docx/image/image.py index e5e7f8a13..c81e6e1b0 100644 --- a/src/docx/image/image.py +++ b/src/docx/image/image.py @@ -87,19 +87,27 @@ def px_height(self) -> int: @property def horz_dpi(self) -> int: - """Integer dots per inch for the width of this image. - - Defaults to 72 when not present in the file, as is often the case. """ - return self._image_header.horz_dpi + Horizontal DPI reported for this image. Returns the header value + unless it is `None` **or** `0`, in which case it defaults to + 72 dpi, matching Word's internal assumption. + """ + dpi = self._image_header.horz_dpi + if dpi in (None, 0): + return 72 + return dpi @property def vert_dpi(self) -> int: - """Integer dots per inch for the height of this image. - - Defaults to 72 when not present in the file, as is often the case. """ - return self._image_header.vert_dpi + Vertical DPI reported for this image. Returns the header value + unless it is `None` **or** `0`, in which case it defaults to + 72 dpi, matching Word's internal assumption. + """ + dpi = self._image_header.vert_dpi + if dpi in (None, 0): + return 72 + return dpi @property def width(self) -> Inches: @@ -219,16 +227,24 @@ def px_height(self): @property def horz_dpi(self): - """Integer dots per inch for the width of this image. - - Defaults to 72 when not present in the file, as is often the case. """ - return self._horz_dpi + Horizontal DPI reported for this image. Returns the header value + unless it is `None` **or** `0`, in which case it defaults to + 72 dpi, matching Word's internal assumption. + """ + dpi = self._horz_dpi + if dpi in (None, 0): + return 72 + return dpi @property def vert_dpi(self): - """Integer dots per inch for the height of this image. - - Defaults to 72 when not present in the file, as is often the case. """ - return self._vert_dpi + Vertical DPI reported for this image. Returns the header value + unless it is `None` **or** `0`, in which case it defaults to + 72 dpi, matching Word's internal assumption. + """ + dpi = self._vert_dpi + if dpi in (None, 0): + return 72 + return dpi diff --git a/tests/test_files/zero_dpi.jpg b/tests/test_files/zero_dpi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6dab8de8cd481cb72623b83fe6c063fb4f574a8a GIT binary patch literal 9042 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