Merge remote-tracking branch 'radarhere/type-annotations' into type-annotations

This commit is contained in:
Andrew Murray 2024-06-06 12:11:53 +10:00
commit f3ec2770e6
6 changed files with 75 additions and 58 deletions

View File

@ -506,7 +506,7 @@ def _getscaleoffset(expr):
class SupportsGetData(Protocol): class SupportsGetData(Protocol):
def getdata( def getdata(
self, self,
) -> tuple[Transform, Sequence[Any]]: ... ) -> tuple[Transform, Sequence[int]]: ...
class Image: class Image:
@ -1295,7 +1295,7 @@ class Image:
return im.crop((x0, y0, x1, y1)) return im.crop((x0, y0, x1, y1))
def draft( 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: ) -> tuple[str, tuple[int, int, float, float]] | None:
""" """
Configures the image file loader so it returns a version of the Configures the image file loader so it returns a version of the
@ -1719,7 +1719,7 @@ class Image:
def paste( def paste(
self, self,
im: Image | str | int | tuple[int, ...], im: Image | str | float | tuple[int, ...],
box: tuple[int, int, int, int] | tuple[int, int] | None = None, box: tuple[int, int, int, int] | tuple[int, int] | None = None,
mask: Image | None = None, mask: Image | None = None,
) -> None: ) -> None:
@ -1750,7 +1750,7 @@ class Image:
See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to See :py:meth:`~PIL.Image.Image.alpha_composite` if you want to
combine images with respect to their alpha channels. 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. :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 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 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" msg = "reducing_gap must be 1.0 or greater"
raise ValueError(msg) raise ValueError(msg)
size = cast("tuple[int, int]", tuple(size))
self.load() self.load()
if box is None: if box is None:
box = (0, 0) + self.size 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: if self.size == size and box == (0, 0) + self.size:
return self.copy() return self.copy()
@ -2291,8 +2287,6 @@ class Image:
if box is None: if box is None:
box = (0, 0) + self.size 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: if factor == (1, 1) and box == (0, 0) + self.size:
return self.copy() return self.copy()
@ -2692,7 +2686,9 @@ class Image:
return return
size = preserved_size 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: if res is not None:
box = res[1] box = res[1]
if box is None: if box is None:
@ -2799,7 +2795,7 @@ class Image:
im.info = self.info.copy() im.info = self.info.copy()
if method == Transform.MESH: if method == Transform.MESH:
# list of quads # list of quads
for box, quad in cast("Sequence[tuple[float, float]]", data): for box, quad in data:
im.__transformer( im.__transformer(
box, self, Transform.QUAD, quad, resample, fillcolor is None box, self, Transform.QUAD, quad, resample, fillcolor is None
) )
@ -2957,7 +2953,7 @@ class ImageTransformHandler:
self, self,
size: tuple[int, int], size: tuple[int, int],
image: Image, image: Image,
**options: dict[str, str | int | tuple[int, ...] | list[int]] | int, **options: str | int | tuple[int, ...] | list[int],
) -> Image: ) -> Image:
pass pass

View File

@ -456,14 +456,12 @@ class ImageDraw:
self.draw.draw_rectangle(right, ink, 1) self.draw.draw_rectangle(right, ink, 1)
def _multiline_check(self, text: AnyStr) -> bool: 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 return split_character in text
def _multiline_split(self, text: AnyStr) -> list[AnyStr]: def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
split_character = cast(AnyStr, "\n" if isinstance(text, str) else b"\n") return text.split("\n" if isinstance(text, str) else b"\n")
return text.split(split_character)
def _multiline_spacing(self, font, spacing, stroke_width): def _multiline_spacing(self, font, spacing, stroke_width):
return ( return (
@ -477,7 +475,12 @@ class ImageDraw:
xy: tuple[float, float], xy: tuple[float, float],
text: str, text: str,
fill=None, fill=None,
font: ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None, font: (
ImageFont.ImageFont
| ImageFont.FreeTypeFont
| ImageFont.TransposedFont
| None
) = None,
anchor=None, anchor=None,
spacing=4, spacing=4,
align="left", align="left",
@ -597,9 +600,14 @@ class ImageDraw:
def multiline_text( def multiline_text(
self, self,
xy: tuple[float, float], xy: tuple[float, float],
text, text: str,
fill=None, fill=None,
font=None, font: (
ImageFont.ImageFont
| ImageFont.FreeTypeFont
| ImageFont.TransposedFont
| None
) = None,
anchor=None, anchor=None,
spacing=4, spacing=4,
align="left", align="left",
@ -684,7 +692,12 @@ class ImageDraw:
def textlength( def textlength(
self, self,
text: str, text: str,
font: ImageFont.FreeTypeFont | ImageFont.ImageFont | None = None, font: (
ImageFont.ImageFont
| ImageFont.FreeTypeFont
| ImageFont.TransposedFont
| None
) = None,
direction=None, direction=None,
features=None, features=None,
language=None, language=None,

View File

@ -33,11 +33,11 @@ import sys
import warnings import warnings
from enum import IntEnum from enum import IntEnum
from io import BytesIO from io import BytesIO
from typing import TYPE_CHECKING, BinaryIO from typing import IO, TYPE_CHECKING, Any, BinaryIO
from . import Image from . import Image
from ._typing import StrOrBytesPath from ._typing import StrOrBytesPath
from ._util import is_directory, is_path from ._util import is_path
if TYPE_CHECKING: if TYPE_CHECKING:
from . import ImageFile from . import ImageFile
@ -61,7 +61,7 @@ except ImportError as ex:
core = DeferredError.new(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: if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH:
msg = "too many characters in string" msg = "too many characters in string"
raise ValueError(msg) raise ValueError(msg)
@ -113,7 +113,7 @@ class ImageFont:
self._load_pilfont_data(fp, image) self._load_pilfont_data(fp, image)
image.close() image.close()
def _load_pilfont_data(self, file, image): def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
# read PILfont header # read PILfont header
if file.readline() != b"PILfont\n": if file.readline() != b"PILfont\n":
msg = "Not a PILfont file" msg = "Not a PILfont file"
@ -161,7 +161,7 @@ class ImageFont:
return self.font.getmask(text, mode) return self.font.getmask(text, mode)
def getbbox( def getbbox(
self, text: str, *args: object, **kwargs: object self, text: str | bytes | bytearray, *args: Any, **kwargs: Any
) -> tuple[int, int, int, int]: ) -> tuple[int, int, int, int]:
""" """
Returns bounding box (in pixels) of given text. Returns bounding box (in pixels) of given text.
@ -180,7 +180,9 @@ class ImageFont:
width, height = self.font.getsize(text) width, height = self.font.getsize(text)
return 0, 0, width, height 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. Returns length (in pixels) of given text.
This is the amount by which following text should be offset. This is the amount by which following text should be offset.
@ -357,13 +359,13 @@ class FreeTypeFont:
def getbbox( def getbbox(
self, self,
text: str, text: str,
mode="", mode: str = "",
direction=None, direction: str | None = None,
features=None, features: str | None = None,
language=None, language: str | None = None,
stroke_width=0, stroke_width: float = 0,
anchor=None, anchor: str | None = None,
) -> tuple[int, int, int, int]: ) -> tuple[float, float, float, float]:
""" """
Returns bounding box (in pixels) of given text relative to given anchor Returns bounding box (in pixels) of given text relative to given anchor
when rendered in font with provided direction, features, and language. when rendered in font with provided direction, features, and language.
@ -513,7 +515,7 @@ class FreeTypeFont:
def getmask2( def getmask2(
self, self,
text, text: str,
mode="", mode="",
direction=None, direction=None,
features=None, features=None,
@ -641,7 +643,7 @@ class FreeTypeFont:
layout_engine=layout_engine or self.layout_engine, 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. :returns: A list of the named styles in a variation font.
:exception OSError: If the font is not 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" msg = "FreeType 2.9.1 or greater is required"
raise NotImplementedError(msg) from e raise NotImplementedError(msg) from e
for axis in axes: for axis in axes:
if axis["name"]:
axis["name"] = axis["name"].replace(b"\x00", b"") axis["name"] = axis["name"].replace(b"\x00", b"")
return axes 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. :param axes: A list of values for each axis.
:exception OSError: If the font is not a variation font. :exception OSError: If the font is not a variation font.
@ -731,7 +734,7 @@ class TransposedFont:
return 0, 0, height, width return 0, 0, height, width
return 0, 0, width, height 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): 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" msg = "text length is undefined for text rotated by 90 or 270 degrees"
raise ValueError(msg) raise ValueError(msg)
@ -878,11 +881,9 @@ def load_path(filename: str | bytes) -> ImageFont:
:return: A font object. :return: A font object.
:exception OSError: If the file could not be read. :exception OSError: If the file could not be read.
""" """
for directory in sys.path:
if is_directory(directory):
assert isinstance(directory, str)
if not isinstance(filename, str): if not isinstance(filename, str):
filename = filename.decode("utf-8") filename = filename.decode("utf-8")
for directory in sys.path:
try: try:
return load(os.path.join(directory, filename)) return load(os.path.join(directory, filename))
except OSError: except OSError:

View File

@ -425,7 +425,7 @@ class JpegImageFile(ImageFile.ImageFile):
return s return s
def draft( 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: ) -> tuple[str, tuple[int, int, float, float]] | None:
if len(self.tile) != 1: if len(self.tile) != 1:
return None return None

View File

@ -1,7 +1,5 @@
from typing import Any from typing import Any
from typing_extensions import Buffer
class ImagingCore: class ImagingCore:
def __getattr__(self, name: str) -> Any: ... def __getattr__(self, name: str) -> Any: ...
@ -14,5 +12,5 @@ class ImagingDraw:
class PixelAccess: class PixelAccess:
def __getattr__(self, name: str) -> Any: ... def __getattr__(self, name: str) -> Any: ...
def font(image, glyphdata: Buffer) -> ImagingFont: ... def font(image, glyphdata: bytes) -> ImagingFont: ...
def __getattr__(name: str) -> Any: ... def __getattr__(name: str) -> Any: ...

View File

@ -1,5 +1,7 @@
from typing import Any, TypedDict from typing import Any, TypedDict
from . import _imaging
class _Axis(TypedDict): class _Axis(TypedDict):
minimum: int | None minimum: int | None
default: int | None default: int | None
@ -37,21 +39,28 @@ class Font:
x_start=..., x_start=...,
y_start=..., y_start=...,
/, /,
) -> tuple[Any, tuple[int, int]]: ... ) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
def getsize( 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]]: ... ) -> tuple[tuple[int, int], tuple[int, int]]: ...
def getlength( def getlength(
self, string: str, mode=..., dir=..., features=..., lang=..., / self, string: str, mode=..., dir=..., features=..., lang=..., /
) -> int: ... ) -> float: ...
def getvarnames(self) -> list[str]: ... def getvarnames(self) -> list[bytes]: ...
def getvaraxes(self) -> list[_Axis]: ... def getvaraxes(self) -> list[_Axis] | None: ...
def setvarname(self, instance_index: int, /) -> None: ... def setvarname(self, instance_index: int, /) -> None: ...
def setvaraxes(self, axes: list[float], /) -> None: ... def setvaraxes(self, axes: list[float], /) -> None: ...
def getfont( def getfont(
filename: str | bytes | bytearray, filename: str | bytes,
size, size: float,
index=..., index=...,
encoding=..., encoding=...,
font_bytes=..., font_bytes=...,