aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/docx/image/image.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/docx/image/image.py')
-rw-r--r--.venv/lib/python3.12/site-packages/docx/image/image.py234
1 files changed, 234 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/docx/image/image.py b/.venv/lib/python3.12/site-packages/docx/image/image.py
new file mode 100644
index 00000000..0022b5b4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docx/image/image.py
@@ -0,0 +1,234 @@
+"""Provides objects that can characterize image streams.
+
+That characterization is as to content type and size, as a required step in including
+them in a document.
+"""
+
+from __future__ import annotations
+
+import hashlib
+import io
+import os
+from typing import IO, Tuple
+
+from docx.image.exceptions import UnrecognizedImageError
+from docx.shared import Emu, Inches, Length, lazyproperty
+
+
+class Image:
+ """Graphical image stream such as JPEG, PNG, or GIF with properties and methods
+ required by ImagePart."""
+
+ def __init__(self, blob: bytes, filename: str, image_header: BaseImageHeader):
+ super(Image, self).__init__()
+ self._blob = blob
+ self._filename = filename
+ self._image_header = image_header
+
+ @classmethod
+ def from_blob(cls, blob: bytes) -> Image:
+ """Return a new |Image| subclass instance parsed from the image binary contained
+ in `blob`."""
+ stream = io.BytesIO(blob)
+ return cls._from_stream(stream, blob)
+
+ @classmethod
+ def from_file(cls, image_descriptor: 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
+ with open(path, "rb") as f:
+ blob = f.read()
+ stream = io.BytesIO(blob)
+ filename = os.path.basename(path)
+ else:
+ stream = image_descriptor
+ stream.seek(0)
+ blob = stream.read()
+ filename = None
+ return cls._from_stream(stream, blob, filename)
+
+ @property
+ def blob(self):
+ """The bytes of the image 'file'."""
+ return self._blob
+
+ @property
+ def content_type(self) -> str:
+ """MIME content type for this image, e.g. ``'image/jpeg'`` for a JPEG image."""
+ return self._image_header.content_type
+
+ @lazyproperty
+ def ext(self):
+ """The file extension for the image.
+
+ If an actual one is available from a load filename it is used. Otherwise a
+ canonical extension is assigned based on the content type. Does not contain the
+ leading period, e.g. 'jpg', not '.jpg'.
+ """
+ return os.path.splitext(self._filename)[1][1:]
+
+ @property
+ def filename(self):
+ """Original image file name, if loaded from disk, or a generic filename if
+ loaded from an anonymous stream."""
+ return self._filename
+
+ @property
+ def px_width(self) -> int:
+ """The horizontal pixel dimension of the image."""
+ return self._image_header.px_width
+
+ @property
+ def px_height(self) -> int:
+ """The vertical pixel dimension of the image."""
+ return self._image_header.px_height
+
+ @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
+
+ @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
+
+ @property
+ def width(self) -> Inches:
+ """A |Length| value representing the native width of the image, calculated from
+ the values of `px_width` and `horz_dpi`."""
+ return Inches(self.px_width / self.horz_dpi)
+
+ @property
+ def height(self) -> Inches:
+ """A |Length| value representing the native height of the image, calculated from
+ the values of `px_height` and `vert_dpi`."""
+ return Inches(self.px_height / self.vert_dpi)
+
+ def scaled_dimensions(
+ self, width: int | Length | None = None, height: int | Length | None = None
+ ) -> Tuple[Length, Length]:
+ """(cx, cy) pair representing scaled dimensions of this image.
+
+ The native dimensions of the image are scaled by applying the following rules to
+ the `width` and `height` arguments.
+
+ * If both `width` and `height` are specified, the return value is (`width`,
+ `height`); no scaling is performed.
+ * 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.
+ * If both `width` and `height` are |None|, the native dimensions are returned.
+
+ The native dimensions are calculated using the dots-per-inch (dpi) value
+ embedded in the image, defaulting to 72 dpi if no value is specified, as is
+ often the case. The returned values are both |Length| objects.
+ """
+ if width is None and height is None:
+ return self.width, self.height
+
+ if width is None:
+ assert height is not None
+ scaling_factor = float(height) / float(self.height)
+ width = round(self.width * scaling_factor)
+
+ if height is None:
+ scaling_factor = float(width) / float(self.width)
+ height = round(self.height * scaling_factor)
+
+ return Emu(width), Emu(height)
+
+ @lazyproperty
+ def sha1(self):
+ """SHA1 hash digest of the image blob."""
+ return hashlib.sha1(self._blob).hexdigest()
+
+ @classmethod
+ def _from_stream(
+ cls,
+ stream: IO[bytes],
+ blob: bytes,
+ filename: str | None = None,
+ ) -> Image:
+ """Return an instance of the |Image| subclass corresponding to the format of the
+ image in `stream`."""
+ image_header = _ImageHeaderFactory(stream)
+ if filename is None:
+ filename = "image.%s" % image_header.default_ext
+ return cls(blob, filename, image_header)
+
+
+def _ImageHeaderFactory(stream: IO[bytes]):
+ """A |BaseImageHeader| subclass instance that can parse headers of image in `stream`."""
+ from docx.image import SIGNATURES
+
+ def read_32(stream: IO[bytes]):
+ stream.seek(0)
+ return stream.read(32)
+
+ header = read_32(stream)
+ for cls, offset, signature_bytes in SIGNATURES:
+ end = offset + len(signature_bytes)
+ found_bytes = header[offset:end]
+ if found_bytes == signature_bytes:
+ return cls.from_stream(stream)
+ raise UnrecognizedImageError
+
+
+class BaseImageHeader:
+ """Base class for image header subclasses like |Jpeg| and |Tiff|."""
+
+ def __init__(self, px_width: int, px_height: int, horz_dpi: int, vert_dpi: int):
+ self._px_width = px_width
+ self._px_height = px_height
+ self._horz_dpi = horz_dpi
+ self._vert_dpi = vert_dpi
+
+ @property
+ def content_type(self) -> str:
+ """Abstract property definition, must be implemented by all subclasses."""
+ msg = "content_type property must be implemented by all subclasses of " "BaseImageHeader"
+ raise NotImplementedError(msg)
+
+ @property
+ def default_ext(self) -> str:
+ """Default filename extension for images of this type.
+
+ An abstract property definition, must be implemented by all subclasses.
+ """
+ raise NotImplementedError(
+ "default_ext property must be implemented by all subclasses of " "BaseImageHeader"
+ )
+
+ @property
+ def px_width(self):
+ """The horizontal pixel dimension of the image."""
+ return self._px_width
+
+ @property
+ def px_height(self):
+ """The vertical pixel dimension of the image."""
+ return self._px_height
+
+ @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
+
+ @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