Skip to content

Commit

Permalink
Add various type annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
srittau committed May 7, 2024
1 parent 93ca52f commit c92f59d
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 41 deletions.
58 changes: 40 additions & 18 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
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.
Expand Down Expand Up @@ -481,6 +481,8 @@ def _getscaleoffset(expr):
# --------------------------------------------------------------------
# Implementation wrapper

class _GetDataTransform(Protocol):
def getdata(self) -> tuple[Transform, Sequence[int]]: ...

class Image:
"""
Expand Down Expand Up @@ -1687,7 +1689,7 @@ def entropy(self, mask=None, extrema=None):
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
Expand Down Expand Up @@ -2122,7 +2124,7 @@ def _get_safe_box(self, size, resample, box):
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.
Expand Down Expand Up @@ -2228,7 +2230,7 @@ def resize(self, size, resample=None, box=None, reducing_gap=None) -> 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``,
Expand Down Expand Up @@ -2263,13 +2265,13 @@ def reduce(self, factor, box=None):

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
Expand Down Expand Up @@ -2576,7 +2578,7 @@ def tell(self) -> int:
"""
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
Expand Down Expand Up @@ -2664,14 +2666,34 @@ def round_aspect(number, key):

# FIXME: the different transform methods need further explanation
# instead of bloating the method docs, add a separate chapter.
@overload
def transform(
self,
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,
method,
data=None,
resample=Resampling.NEAREST,
fill=1,
fillcolor=None,
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
Expand Down
19 changes: 10 additions & 9 deletions src/PIL/ImageDraw.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@
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.
Expand Down Expand Up @@ -92,7 +93,7 @@ def __init__(self, im: Image.Image, mode: str | None = None) -> None:
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.
Expand Down Expand Up @@ -450,12 +451,12 @@ def draw_corners(pieslice) -> None:
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)
Expand All @@ -469,7 +470,7 @@ def _multiline_spacing(self, font, spacing, stroke_width):

def text(
self,
xy,
xy: tuple[int, int],
text,
fill=None,
font=None,
Expand Down Expand Up @@ -591,7 +592,7 @@ def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:

def multiline_text(
self,
xy,
xy: tuple[int, int],
text,
fill=None,
font=None,
Expand Down Expand Up @@ -678,15 +679,15 @@ def multiline_text(

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"
Expand Down
31 changes: 18 additions & 13 deletions src/PIL/ImageFont.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@
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
Expand All @@ -56,7 +59,7 @@ class Layout(IntEnum):
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)
Expand All @@ -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"):
Expand Down Expand Up @@ -153,7 +158,7 @@ def getmask(self, text, mode="", *args, **kwargs):
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.
Expand All @@ -171,7 +176,7 @@ def getbbox(self, text, *args, **kwargs):
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.
Expand Down Expand Up @@ -254,7 +259,7 @@ def __setstate__(self, state):
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)
Expand All @@ -269,7 +274,7 @@ def getmetrics(self):
"""
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.
Expand Down Expand Up @@ -343,14 +348,14 @@ def getlength(self, text, mode="", direction=None, features=None, language=None)

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.
Expand Down Expand Up @@ -725,7 +730,7 @@ def getlength(self, text, *args, **kwargs):
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.
Expand All @@ -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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -850,7 +855,7 @@ def freetype(font):
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.
Expand Down
37 changes: 36 additions & 1 deletion src/PIL/_imagingft.pyi
Original file line number Diff line number Diff line change
@@ -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: ...

0 comments on commit c92f59d

Please sign in to comment.