diff --git a/src/docx/document.py b/src/docx/document.py index 73757b46d..3c94de4ee 100644 --- a/src/docx/document.py +++ b/src/docx/document.py @@ -123,6 +123,8 @@ def add_picture( image_path_or_stream: str | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, + title: str | None = None, + descr: str | None = None, ): """Return new picture shape added in its own paragraph at end of the document. @@ -133,9 +135,12 @@ def add_picture( aspect ratio of the image. The native size of the picture is calculated using the dots-per-inch (dpi) value specified in the image file, defaulting to 72 dpi if no value is specified, as is often the case. + + `title` sets the image's alternative-text title. + `descr` sets the alternative-text description shown in Word's Alt Text pane. """ run = self.add_paragraph().add_run() - return run.add_picture(image_path_or_stream, width, height) + return run.add_picture(image_path_or_stream, width, height, title, descr) def add_section(self, start_type: WD_SECTION = WD_SECTION.NEW_PAGE): """Return a |Section| object newly added at the end of the document. diff --git a/src/docx/oxml/shape.py b/src/docx/oxml/shape.py index c6df8e7b8..6754694bd 100644 --- a/src/docx/oxml/shape.py +++ b/src/docx/oxml/shape.py @@ -77,7 +77,7 @@ class CT_Inline(BaseOxmlElement): ) @classmethod - def new(cls, cx: Length, cy: Length, shape_id: int, pic: CT_Picture) -> CT_Inline: + def new(cls, cx: Length, cy: Length, shape_id: int, pic: CT_Picture, title=None, descr=None) -> CT_Inline: """Return a new ```` element populated with the values passed as parameters.""" inline = cast(CT_Inline, parse_xml(cls._inline_xml())) @@ -85,13 +85,15 @@ def new(cls, cx: Length, cy: Length, shape_id: int, pic: CT_Picture) -> CT_Inlin inline.extent.cy = cy inline.docPr.id = shape_id inline.docPr.name = "Picture %d" % shape_id + if title: inline.docPr.title = title + if descr: inline.docPr.descr = descr inline.graphic.graphicData.uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" inline.graphic.graphicData._insert_pic(pic) return inline @classmethod def new_pic_inline( - cls, shape_id: int, rId: str, filename: str, cx: Length, cy: Length + cls, shape_id: int, rId: str, filename: str, cx: Length, cy: Length, title=None, descr=None ) -> CT_Inline: """Create `wp:inline` element containing a `pic:pic` element. @@ -99,7 +101,7 @@ def new_pic_inline( """ pic_id = 0 # Word doesn't seem to use this, but does not omit it pic = CT_Picture.new(pic_id, filename, rId, cx, cy) - inline = cls.new(cx, cy, shape_id, pic) + inline = cls.new(cx, cy, shape_id, pic, title=title, descr=descr) return inline @classmethod @@ -126,6 +128,8 @@ class CT_NonVisualDrawingProps(BaseOxmlElement): id = RequiredAttribute("id", ST_DrawingElementId) name = RequiredAttribute("name", XsdString) + title = OptionalAttribute('title', XsdString) + descr = OptionalAttribute('descr', XsdString) class CT_NonVisualPictureProperties(BaseOxmlElement): diff --git a/src/docx/parts/story.py b/src/docx/parts/story.py index 7482c91a8..186529126 100644 --- a/src/docx/parts/story.py +++ b/src/docx/parts/story.py @@ -62,6 +62,8 @@ def new_pic_inline( image_descriptor: str | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, + title: str | None = None, + descr: str | None = None, ) -> CT_Inline: """Return a newly-created `w:inline` element. @@ -71,7 +73,7 @@ def new_pic_inline( rId, image = self.get_or_add_image(image_descriptor) cx, cy = image.scaled_dimensions(width, height) shape_id, filename = self.next_id, image.filename - return CT_Inline.new_pic_inline(shape_id, rId, filename, cx, cy) + return CT_Inline.new_pic_inline(shape_id, rId, filename, cx, cy, title, descr) @property def next_id(self) -> int: diff --git a/src/docx/shape.py b/src/docx/shape.py index cd35deb35..6435e142f 100644 --- a/src/docx/shape.py +++ b/src/docx/shape.py @@ -101,3 +101,42 @@ def width(self): def width(self, cx: Length): self._inline.extent.cx = cx self._inline.graphic.graphicData.pic.spPr.cx = cx + + @property + def alt_text(self) -> str | None: + """Read/write alternative-text description for this inline shape. + + This corresponds to the 'Description' field in Word's Alt Text pane and is + stored on the ``wp:docPr`` ``descr`` attribute. + """ + docPr = self._inline.docPr + if docPr is None: + return None + return docPr.descr + + @alt_text.setter + def alt_text(self, value: str | None) -> None: + docPr = self._inline.docPr + if docPr is None: + return + # Setting to empty/None clears the attribute. + docPr.descr = value if value else None + + @property + def alt_title(self) -> str | None: + """Read/write alternative-text title for this inline shape. + + This corresponds to the 'Title' field in Word's Alt Text pane and is + stored on the ``wp:docPr`` ``title`` attribute. + """ + docPr = self._inline.docPr + if docPr is None: + return None + return docPr.title + + @alt_title.setter + def alt_title(self, value: str | None) -> None: + docPr = self._inline.docPr + if docPr is None: + return + docPr.title = value if value else None diff --git a/src/docx/text/run.py b/src/docx/text/run.py index 57ea31fa4..fc699deb4 100644 --- a/src/docx/text/run.py +++ b/src/docx/text/run.py @@ -61,6 +61,8 @@ def add_picture( image_path_or_stream: str | IO[bytes], width: int | Length | None = None, height: int | Length | None = None, + title: str | None = None, + descr: str | None = None, ) -> InlineShape: """Return |InlineShape| containing image identified by `image_path_or_stream`. @@ -75,8 +77,11 @@ def add_picture( ratio of the image. The native size of the picture is calculated using the dots- per-inch (dpi) value specified in the image file, defaulting to 72 dpi if no value is specified, as is often the case. + + `title` sets the image's alternative-text title. `descr` sets the alternative-text + description shown in Word's Alt Text pane. """ - inline = self.part.new_pic_inline(image_path_or_stream, width, height) + inline = self.part.new_pic_inline(image_path_or_stream, width, height, title, descr) self._r.add_drawing(inline) return InlineShape(inline)