From d566c04d5b9b2f7587015b110e588b073a24cf2d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Jun 2024 14:20:01 +1000 Subject: [PATCH] Updated type hints --- src/PIL/Image.py | 22 +++++++--------- src/PIL/ImageDraw.py | 29 +++++++++++++++------ src/PIL/ImageFont.py | 53 +++++++++++++++++++------------------- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/_imaging.pyi | 4 +-- src/PIL/_imagingft.pyi | 23 ++++++++++++----- 6 files changed, 75 insertions(+), 58 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c02c7d6b6..2ea26877d 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -506,7 +506,7 @@ def _getscaleoffset(expr): class SupportsGetData(Protocol): def getdata( self, - ) -> tuple[Transform, Sequence[Any]]: ... + ) -> tuple[Transform, Sequence[int]]: ... class Image: @@ -1295,7 +1295,7 @@ class Image: return im.crop((x0, y0, x1, y1)) def draft( - self, mode: str, size: tuple[int, int] + self, mode: str | None, size: tuple[int, int] ) -> tuple[str, tuple[int, int, float, float]] | None: """ Configures the image file loader so it returns a version of the @@ -1719,7 +1719,7 @@ class Image: def paste( self, - im: Image | str | int | tuple[int, ...], + im: Image | str | float | tuple[int, ...], box: tuple[int, int, int, int] | tuple[int, int] | None = None, mask: Image | None = None, ) -> None: @@ -1750,7 +1750,7 @@ class Image: See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to combine images with respect to their alpha channels. - :param im: Source image or pixel value (integer or tuple). + :param im: Source image or pixel value (integer, float or tuple). :param box: An optional 4-tuple giving the region to paste into. If a 2-tuple is used instead, it's treated as the upper left corner. If omitted or None, the source is pasted into the @@ -2228,13 +2228,9 @@ class Image: msg = "reducing_gap must be 1.0 or greater" raise ValueError(msg) - size = cast("tuple[int, int]", tuple(size)) - self.load() if box is None: box = (0, 0) + self.size - else: - box = cast("tuple[float, float, float, float]", tuple(box)) if self.size == size and box == (0, 0) + self.size: return self.copy() @@ -2291,8 +2287,6 @@ class Image: if box is None: box = (0, 0) + self.size - else: - box = cast("tuple[int, int, int, int]", tuple(box)) if factor == (1, 1) and box == (0, 0) + self.size: return self.copy() @@ -2692,7 +2686,9 @@ class Image: return size = preserved_size - res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap)) # type: ignore[arg-type] + res = self.draft( + None, (int(size[0] * reducing_gap), int(size[1] * reducing_gap)) + ) if res is not None: box = res[1] if box is None: @@ -2799,7 +2795,7 @@ class Image: im.info = self.info.copy() if method == Transform.MESH: # list of quads - for box, quad in cast("Sequence[tuple[float, float]]", data): + for box, quad in data: im.__transformer( box, self, Transform.QUAD, quad, resample, fillcolor is None ) @@ -2957,7 +2953,7 @@ class ImageTransformHandler: self, size: tuple[int, int], image: Image, - **options: dict[str, str | int | tuple[int, ...] | list[int]] | int, + **options: str | int | tuple[int, ...] | list[int], ) -> Image: pass diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 1887a3933..0663d9ddf 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -456,14 +456,12 @@ class ImageDraw: self.draw.draw_rectangle(right, ink, 1) def _multiline_check(self, text: AnyStr) -> bool: - split_character = cast(AnyStr, "\n" if isinstance(text, str) else b"\n") + split_character = "\n" if isinstance(text, str) else b"\n" return split_character in text def _multiline_split(self, text: AnyStr) -> list[AnyStr]: - split_character = cast(AnyStr, "\n" if isinstance(text, str) else b"\n") - - return text.split(split_character) + return text.split("\n" if isinstance(text, str) else b"\n") def _multiline_spacing(self, font, spacing, stroke_width): return ( @@ -477,7 +475,12 @@ class ImageDraw: xy: tuple[float, float], text: str, fill=None, - font: ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, anchor=None, spacing=4, align="left", @@ -597,9 +600,14 @@ class ImageDraw: def multiline_text( self, xy: tuple[float, float], - text, + text: str, fill=None, - font=None, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, anchor=None, spacing=4, align="left", @@ -684,7 +692,12 @@ class ImageDraw: def textlength( self, text: str, - font: ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, direction=None, features=None, language=None, diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 747c0c050..a9925483e 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -33,11 +33,11 @@ import sys import warnings from enum import IntEnum from io import BytesIO -from typing import TYPE_CHECKING, BinaryIO +from typing import IO, TYPE_CHECKING, Any, BinaryIO from . import Image from ._typing import StrOrBytesPath -from ._util import is_directory, is_path +from ._util import is_path if TYPE_CHECKING: from . import ImageFile @@ -61,7 +61,7 @@ except ImportError as ex: core = DeferredError.new(ex) -def _string_length_check(text: str) -> None: +def _string_length_check(text: str | bytes | bytearray) -> None: if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH: msg = "too many characters in string" raise ValueError(msg) @@ -113,7 +113,7 @@ class ImageFont: self._load_pilfont_data(fp, image) image.close() - def _load_pilfont_data(self, file, image): + def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None: # read PILfont header if file.readline() != b"PILfont\n": msg = "Not a PILfont file" @@ -161,7 +161,7 @@ class ImageFont: return self.font.getmask(text, mode) def getbbox( - self, text: str, *args: object, **kwargs: object + self, text: str | bytes | bytearray, *args: Any, **kwargs: Any ) -> tuple[int, int, int, int]: """ Returns bounding box (in pixels) of given text. @@ -180,7 +180,9 @@ class ImageFont: width, height = self.font.getsize(text) return 0, 0, width, height - def getlength(self, text: str, *args: object, **kwargs: object) -> int: + def getlength( + self, text: str | bytes | bytearray, *args: Any, **kwargs: Any + ) -> int: """ Returns length (in pixels) of given text. This is the amount by which following text should be offset. @@ -357,13 +359,13 @@ class FreeTypeFont: def getbbox( self, text: str, - mode="", - direction=None, - features=None, - language=None, - stroke_width=0, - anchor=None, - ) -> tuple[int, int, int, int]: + mode: str = "", + direction: str | None = None, + features: str | None = None, + language: str | None = None, + stroke_width: float = 0, + anchor: str | None = None, + ) -> tuple[float, float, float, float]: """ Returns bounding box (in pixels) of given text relative to given anchor when rendered in font with provided direction, features, and language. @@ -513,7 +515,7 @@ class FreeTypeFont: def getmask2( self, - text, + text: str, mode="", direction=None, features=None, @@ -641,7 +643,7 @@ class FreeTypeFont: layout_engine=layout_engine or self.layout_engine, ) - def get_variation_names(self): + def get_variation_names(self) -> list[bytes]: """ :returns: A list of the named styles in a variation font. :exception OSError: If the font is not a variation font. @@ -683,10 +685,11 @@ class FreeTypeFont: msg = "FreeType 2.9.1 or greater is required" raise NotImplementedError(msg) from e for axis in axes: - axis["name"] = axis["name"].replace(b"\x00", b"") + if axis["name"]: + axis["name"] = axis["name"].replace(b"\x00", b"") return axes - def set_variation_by_axes(self, axes): + def set_variation_by_axes(self, axes: list[float]) -> None: """ :param axes: A list of values for each axis. :exception OSError: If the font is not a variation font. @@ -731,7 +734,7 @@ class TransposedFont: return 0, 0, height, width return 0, 0, width, height - def getlength(self, text, *args, **kwargs): + def getlength(self, text: str, *args, **kwargs) -> float: if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): msg = "text length is undefined for text rotated by 90 or 270 degrees" raise ValueError(msg) @@ -878,15 +881,13 @@ def load_path(filename: str | bytes) -> ImageFont: :return: A font object. :exception OSError: If the file could not be read. """ + if not isinstance(filename, str): + filename = filename.decode("utf-8") for directory in sys.path: - if is_directory(directory): - assert isinstance(directory, str) - if not isinstance(filename, str): - filename = filename.decode("utf-8") - try: - return load(os.path.join(directory, filename)) - except OSError: - pass + try: + return load(os.path.join(directory, filename)) + except OSError: + pass msg = "cannot find font file" raise OSError(msg) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 909911dfe..e1c61f991 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -425,7 +425,7 @@ class JpegImageFile(ImageFile.ImageFile): return s def draft( - self, mode: str, size: tuple[int, int] + self, mode: str | None, size: tuple[int, int] ) -> tuple[str, tuple[int, int, float, float]] | None: if len(self.tile) != 1: return None diff --git a/src/PIL/_imaging.pyi b/src/PIL/_imaging.pyi index d85eb84fa..1fe954417 100644 --- a/src/PIL/_imaging.pyi +++ b/src/PIL/_imaging.pyi @@ -1,7 +1,5 @@ from typing import Any -from typing_extensions import Buffer - class ImagingCore: def __getattr__(self, name: str) -> Any: ... @@ -14,5 +12,5 @@ class ImagingDraw: class PixelAccess: def __getattr__(self, name: str) -> Any: ... -def font(image, glyphdata: Buffer) -> ImagingFont: ... +def font(image, glyphdata: bytes) -> ImagingFont: ... def __getattr__(name: str) -> Any: ... diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi index 987e7fd6f..b023efe01 100644 --- a/src/PIL/_imagingft.pyi +++ b/src/PIL/_imagingft.pyi @@ -1,5 +1,7 @@ from typing import Any, TypedDict +from . import _imaging + class _Axis(TypedDict): minimum: int | None default: int | None @@ -37,21 +39,28 @@ class Font: x_start=..., y_start=..., /, - ) -> tuple[Any, tuple[int, int]]: ... + ) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ... def getsize( - self, string: str, mode=..., dir=..., features=..., lang=..., anchor=..., / + self, + string: str | bytes | bytearray, + mode=..., + dir=..., + features=..., + lang=..., + anchor=..., + /, ) -> tuple[tuple[int, int], tuple[int, int]]: ... def getlength( self, string: str, mode=..., dir=..., features=..., lang=..., / - ) -> int: ... - def getvarnames(self) -> list[str]: ... - def getvaraxes(self) -> list[_Axis]: ... + ) -> float: ... + def getvarnames(self) -> list[bytes]: ... + def getvaraxes(self) -> list[_Axis] | None: ... def setvarname(self, instance_index: int, /) -> None: ... def setvaraxes(self, axes: list[float], /) -> None: ... def getfont( - filename: str | bytes | bytearray, - size, + filename: str | bytes, + size: float, index=..., encoding=..., font_bytes=...,