Add various type annotations

This commit is contained in:
Sebastian Rittau 2024-05-07 14:30:34 +02:00
parent 93ca52fbe0
commit c92f59d758
4 changed files with 104 additions and 41 deletions

View File

@ -41,7 +41,7 @@ import warnings
from collections.abc import Callable, MutableMapping
from enum import IntEnum
from types import ModuleType
from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, cast
from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, cast, overload
# VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION was removed in Pillow 9.0.0.
@ -481,6 +481,8 @@ def _getscaleoffset(expr):
# --------------------------------------------------------------------
# Implementation wrapper
class _GetDataTransform(Protocol):
def getdata(self) -> tuple[Transform, Sequence[int]]: ...
class Image:
"""
@ -1687,7 +1689,7 @@ class Image:
return self.im.entropy(extrema)
return self.im.entropy()
def paste(self, im, box=None, mask=None) -> None:
def paste(self, im: Image | str | int | tuple[int, ...], box: tuple[int, int, int, int] | tuple[int, int] | None = None, mask: Image | None = None) -> None:
"""
Pastes another image into this image. The box argument is either
a 2-tuple giving the upper left corner, a 4-tuple defining the
@ -2122,7 +2124,7 @@ class Image:
min(self.size[1], math.ceil(box[3] + support_y)),
)
def resize(self, size, resample=None, box=None, reducing_gap=None) -> Image:
def resize(self, size: tuple[int, int], resample: Resampling | None = None, box: tuple[float, float, float, float] | None = None, reducing_gap: float | None = None) -> Image:
"""
Returns a resized copy of this image.
@ -2228,7 +2230,7 @@ class Image:
return self._new(self.im.resize(size, resample, box))
def reduce(self, factor, box=None):
def reduce(self, factor: int | tuple[int, int], box: tuple[int, int, int, int] | None = None) -> Image:
"""
Returns a copy of the image reduced ``factor`` times.
If the size of the image is not dividable by ``factor``,
@ -2263,13 +2265,13 @@ class Image:
def rotate(
self,
angle,
resample=Resampling.NEAREST,
expand=0,
center=None,
translate=None,
fillcolor=None,
):
angle: float,
resample: Resampling = Resampling.NEAREST,
expand: bool = False,
center: tuple[int, int] | None = None,
translate: tuple[int, int] | None = None,
fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image:
"""
Returns a rotated copy of this image. This method returns a
copy of this image, rotated the given number of degrees counter
@ -2576,7 +2578,7 @@ class Image:
"""
return 0
def thumbnail(self, size, resample=Resampling.BICUBIC, reducing_gap=2.0):
def thumbnail(self, size: tuple[int, int], resample: Resampling = Resampling.BICUBIC, reducing_gap: float = 2.0) -> None:
"""
Make this image into a thumbnail. This method modifies the
image to contain a thumbnail version of itself, no larger than
@ -2664,14 +2666,34 @@ class Image:
# FIXME: the different transform methods need further explanation
# instead of bloating the method docs, add a separate chapter.
@overload
def transform(
self,
size,
method,
data=None,
resample=Resampling.NEAREST,
fill=1,
fillcolor=None,
size: tuple[int, int],
method: Transform | ImageTransformHandler,
data: Sequence[int],
resample: Resampling = Resampling.NEAREST,
fill: int = 1,
fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image: ...
@overload
def transform(
self,
size: tuple[int, int],
method: _GetDataTransform,
data: None = None,
resample: Resampling = Resampling.NEAREST,
fill: int = 1,
fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image: ...
def transform(
self,
size: tuple[int, int],
method: Transform | ImageTransformHandler | _GetDataTransform,
data: Sequence[int] | None = None,
resample: Resampling = Resampling.NEAREST,
fill: int = 1,
fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image:
"""
Transforms this image. This method creates a new image with the

View File

@ -34,10 +34,11 @@ from __future__ import annotations
import math
import numbers
import struct
from typing import Sequence, cast
from typing import AnyStr, Sequence, cast
from . import Image, ImageColor
from ._typing import Coords
from .ImageFont import FreeTypeFont, ImageFont
"""
A simple 2D drawing interface for PIL images.
@ -92,7 +93,7 @@ class ImageDraw:
self.fontmode = "L" # aliasing is okay for other modes
self.fill = False
def getfont(self):
def getfont(self) -> FreeTypeFont | ImageFont:
"""
Get the current default font.
@ -450,12 +451,12 @@ class ImageDraw:
right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 1)
def _multiline_check(self, text) -> bool:
def _multiline_check(self, text: str | bytes) -> bool:
split_character = "\n" if isinstance(text, str) else b"\n"
return split_character in text
def _multiline_split(self, text) -> list[str | bytes]:
def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
split_character = "\n" if isinstance(text, str) else b"\n"
return text.split(split_character)
@ -469,7 +470,7 @@ class ImageDraw:
def text(
self,
xy,
xy: tuple[int, int],
text,
fill=None,
font=None,
@ -591,7 +592,7 @@ class ImageDraw:
def multiline_text(
self,
xy,
xy: tuple[int, int],
text,
fill=None,
font=None,
@ -678,15 +679,15 @@ class ImageDraw:
def textlength(
self,
text,
font=None,
text: str,
font: FreeTypeFont | ImageFont | None = None,
direction=None,
features=None,
language=None,
embedded_color=False,
*,
font_size=None,
):
) -> float:
"""Get the length of a given string, in pixels with 1/64 precision."""
if self._multiline_check(text):
msg = "can't measure length of multiline text"

View File

@ -33,12 +33,15 @@ import sys
import warnings
from enum import IntEnum
from io import BytesIO
from typing import BinaryIO
from typing import TYPE_CHECKING, BinaryIO
from . import Image
from ._typing import StrOrBytesPath
from ._util import is_directory, is_path
if TYPE_CHECKING:
from _imagingft import Font
class Layout(IntEnum):
BASIC = 0
@ -56,7 +59,7 @@ except ImportError as ex:
core = DeferredError.new(ex)
def _string_length_check(text):
def _string_length_check(text: str | bytes) -> None:
if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH:
msg = "too many characters in string"
raise ValueError(msg)
@ -81,7 +84,9 @@ def _string_length_check(text):
class ImageFont:
"""PIL font wrapper"""
def _load_pilfont(self, filename):
font: Font
def _load_pilfont(self, filename: str) -> None:
with open(filename, "rb") as fp:
image = None
for ext in (".png", ".gif", ".pbm"):
@ -153,7 +158,7 @@ class ImageFont:
Image._decompression_bomb_check(self.font.getsize(text))
return self.font.getmask(text, mode)
def getbbox(self, text, *args, **kwargs):
def getbbox(self, text: str, *args: object, **kwargs: object) -> tuple[int, int, int, int]:
"""
Returns bounding box (in pixels) of given text.
@ -171,7 +176,7 @@ class ImageFont:
width, height = self.font.getsize(text)
return 0, 0, width, height
def getlength(self, text, *args, **kwargs):
def getlength(self, text: str, *args: object, **kwargs: object) -> int:
"""
Returns length (in pixels) of given text.
This is the amount by which following text should be offset.
@ -254,7 +259,7 @@ class FreeTypeFont:
path, size, index, encoding, layout_engine = state
self.__init__(path, size, index, encoding, layout_engine)
def getname(self):
def getname(self) -> tuple[str, str]:
"""
:return: A tuple of the font family (e.g. Helvetica) and the font style
(e.g. Bold)
@ -269,7 +274,7 @@ class FreeTypeFont:
"""
return self.font.ascent, self.font.descent
def getlength(self, text, mode="", direction=None, features=None, language=None):
def getlength(self, text: str, mode="", direction=None, features=None, language=None) -> float:
"""
Returns length (in pixels with 1/64 precision) of given text when rendered
in font with provided direction, features, and language.
@ -343,14 +348,14 @@ class FreeTypeFont:
def getbbox(
self,
text,
text: str,
mode="",
direction=None,
features=None,
language=None,
stroke_width=0,
anchor=None,
):
) -> tuple[int, int, int, int]:
"""
Returns bounding box (in pixels) of given text relative to given anchor
when rendered in font with provided direction, features, and language.
@ -725,7 +730,7 @@ class TransposedFont:
return self.font.getlength(text, *args, **kwargs)
def load(filename):
def load(filename: str) -> ImageFont:
"""
Load a font file. This function loads a font object from the given
bitmap font file, and returns the corresponding font object.
@ -739,7 +744,7 @@ def load(filename):
return f
def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
def truetype(font: StrOrBytesPath | BinaryIO | None = None, size: float = 10, index: int = 0, encoding: str = "", layout_engine: Layout | None = None) -> FreeTypeFont:
"""
Load a TrueType or OpenType font from a file or file-like object,
and create a font object.
@ -800,7 +805,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
:exception ValueError: If the font size is not greater than zero.
"""
def freetype(font):
def freetype(font: StrOrBytesPath | BinaryIO | None) -> FreeTypeFont:
return FreeTypeFont(font, size, index, encoding, layout_engine)
try:
@ -850,7 +855,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
raise
def load_path(filename):
def load_path(filename: str | bytes) -> ImageFont:
"""
Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a
bitmap font along the Python path.

View File

@ -1,3 +1,38 @@
from typing import Any
from typing import Any, TypedDict
class _Axis(TypedDict):
minimum: int | None
default: int | None
maximum: int | None
name: str | None
class Font:
@property
def family(self) -> str | None: ...
@property
def style(self) -> str | None: ...
@property
def ascent(self) -> int: ...
@property
def descent(self) -> int: ...
@property
def height(self) -> int: ...
@property
def x_ppem(self) -> int: ...
@property
def y_ppem(self) -> int: ...
@property
def glyphs(self) -> int: ...
def render(self, string: str, fill, mode = ..., dir = ..., features = ..., lang = ..., stroke_width = ..., anchor = ..., foreground_ink_long = ..., x_start = ..., y_start = ..., /) -> tuple[Any, tuple[int, int]]: ...
def getsize(self, string: str, 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]: ...
def setvarname(self, instance_index: int, /) -> None: ...
def setvaraxes(self, axes: list[float], /) -> None: ...
def getfont(filename: str | bytes | bytearray, size, index = ..., encoding = ..., font_bytes = ..., layout_engine = ...) -> Font: ...
def __getattr__(name: str) -> Any: ...