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
18 changes: 10 additions & 8 deletions src/docx/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from __future__ import annotations

import os
from typing import IO, TYPE_CHECKING, Iterator, List, Sequence

from docx.blkcntnr import BlockItemContainer
Expand Down Expand Up @@ -120,19 +121,20 @@ def add_paragraph(self, text: str = "", style: str | ParagraphStyle | None = Non

def add_picture(
self,
image_path_or_stream: str | IO[bytes],
image_path_or_stream: str | os.PathLike[str] | IO[bytes],
width: int | Length | None = None,
height: int | Length | None = None,
):
"""Return new picture shape added in its own paragraph at end of the document.

The picture contains the image at `image_path_or_stream`, scaled based on
`width` and `height`. If neither width nor height is specified, the picture
appears at its native size. If only one is specified, it is used to compute a
scaling factor that is then applied to the unspecified dimension, preserving the
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.
The picture contains the image at `image_path_or_stream`, which is a path (a
string or path-like object) or a file-like object containing a binary image. The
picture is scaled based on `width` and `height`. If neither width nor height is
specified, the picture appears at its native size. If only one is specified, it
is used to compute a scaling factor that is then applied to the unspecified
dimension, preserving the 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.
"""
run = self.add_paragraph().add_run()
return run.add_picture(image_path_or_stream, width, height)
Expand Down
8 changes: 4 additions & 4 deletions src/docx/image/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ def from_blob(cls, blob: bytes) -> Image:
return cls._from_stream(stream, blob)

@classmethod
def from_file(cls, image_descriptor: str | IO[bytes]):
def from_file(cls, image_descriptor: str | os.PathLike[str] | IO[bytes]):
"""Return a new |Image| subclass instance loaded from the image file identified
by `image_descriptor`, a path or file-like object."""
if isinstance(image_descriptor, str):
path = image_descriptor
if isinstance(image_descriptor, (str, os.PathLike)):
path = os.fspath(image_descriptor)
with open(path, "rb") as f:
blob = f.read()
stream = io.BytesIO(blob)
filename = os.path.basename(path)
filename = os.path.basename(os.fsdecode(path))
else:
stream = image_descriptor
stream.seek(0)
Expand Down
9 changes: 7 additions & 2 deletions src/docx/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import os
from typing import IO, cast

from docx.image.image import Image
Expand All @@ -22,7 +23,9 @@ def after_unmarshal(self):
"""
self._gather_image_parts()

def get_or_add_image_part(self, image_descriptor: str | IO[bytes]) -> ImagePart:
def get_or_add_image_part(
self, image_descriptor: str | os.PathLike[str] | IO[bytes]
) -> ImagePart:
"""Return |ImagePart| containing image specified by `image_descriptor`.

The image-part is newly created if a matching one is not already present in the
Expand Down Expand Up @@ -65,7 +68,9 @@ def __len__(self):
def append(self, item: ImagePart):
self._image_parts.append(item)

def get_or_add_image_part(self, image_descriptor: str | IO[bytes]) -> ImagePart:
def get_or_add_image_part(
self, image_descriptor: str | os.PathLike[str] | IO[bytes]
) -> ImagePart:
"""Return |ImagePart| object containing image identified by `image_descriptor`.

The image-part is newly created if a matching one is not present in the
Expand Down
7 changes: 5 additions & 2 deletions src/docx/parts/story.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import os
from typing import IO, TYPE_CHECKING, Tuple, cast

from docx.opc.constants import RELATIONSHIP_TYPE as RT
Expand All @@ -24,7 +25,9 @@ class StoryPart(XmlPart):
`.add_paragraph()`, `.add_table()` etc.
"""

def get_or_add_image(self, image_descriptor: str | IO[bytes]) -> Tuple[str, Image]:
def get_or_add_image(
self, image_descriptor: str | os.PathLike[str] | IO[bytes]
) -> Tuple[str, Image]:
"""Return (rId, image) pair for image identified by `image_descriptor`.

`rId` is the str key (often like "rId7") for the relationship between this story
Expand Down Expand Up @@ -59,7 +62,7 @@ def get_style_id(

def new_pic_inline(
self,
image_descriptor: str | IO[bytes],
image_descriptor: str | os.PathLike[str] | IO[bytes],
width: int | Length | None = None,
height: int | Length | None = None,
) -> CT_Inline:
Expand Down
7 changes: 4 additions & 3 deletions src/docx/text/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import os
from typing import IO, TYPE_CHECKING, Iterator, cast

from docx.drawing import Drawing
Expand Down Expand Up @@ -58,16 +59,16 @@ def add_break(self, break_type: WD_BREAK = WD_BREAK.LINE):

def add_picture(
self,
image_path_or_stream: str | IO[bytes],
image_path_or_stream: str | os.PathLike[str] | IO[bytes],
width: int | Length | None = None,
height: int | Length | None = None,
) -> InlineShape:
"""Return |InlineShape| containing image identified by `image_path_or_stream`.

The picture is added to the end of this run.

`image_path_or_stream` can be a path (a string) or a file-like object containing
a binary image.
`image_path_or_stream` can be a path (a string or path-like object) or a
file-like object containing a binary image.

If neither width nor height is specified, the picture appears at
its native size. If only one is specified, it is used to compute a scaling
Expand Down
7 changes: 7 additions & 0 deletions tests/image/test_image.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Unit test suite for docx.image package"""

import io
from pathlib import Path

import pytest

Expand Down Expand Up @@ -40,6 +41,12 @@ def it_can_construct_from_an_image_path(self, from_path_fixture):
_from_stream_.assert_called_once_with(stream_, blob, filename)
assert image is image_

def it_can_construct_from_a_pathlib_path(self, from_path_fixture):
image_path, _from_stream_, stream_, blob, filename, image_ = from_path_fixture
image = Image.from_file(Path(image_path))
_from_stream_.assert_called_once_with(stream_, blob, filename)
assert image is image_

def it_can_construct_from_an_image_file_like(self, from_filelike_fixture):
image_stream, _from_stream_, blob, image_ = from_filelike_fixture
image = Image.from_file(image_stream)
Expand Down