Merge pull request #8046 from srittau/type-annotations

Add various type annotations
This commit is contained in:
Andrew Murray 2024-06-08 18:38:21 +10:00 committed by GitHub
commit 5bacce9dc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 249 additions and 97 deletions

View File

@ -124,8 +124,8 @@ def test_fastpath_translate() -> None:
def test_center() -> None: def test_center() -> None:
im = hopper() im = hopper()
rotate(im, im.mode, 45, center=(0, 0)) rotate(im, im.mode, 45, center=(0, 0))
rotate(im, im.mode, 45, translate=(im.size[0] / 2, 0)) rotate(im, im.mode, 45, translate=(im.size[0] // 2, 0))
rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0] / 2, 0)) rotate(im, im.mode, 45, center=(0, 0), translate=(im.size[0] // 2, 0))
def test_rotate_no_fill() -> None: def test_rotate_no_fill() -> None:

View File

@ -144,10 +144,12 @@ pixel, the Python Imaging Library provides different resampling *filters*.
.. py:currentmodule:: PIL.Image .. py:currentmodule:: PIL.Image
.. data:: Resampling.NEAREST .. data:: Resampling.NEAREST
:noindex:
Pick one nearest pixel from the input image. Ignore all other input pixels. Pick one nearest pixel from the input image. Ignore all other input pixels.
.. data:: Resampling.BOX .. data:: Resampling.BOX
:noindex:
Each pixel of source image contributes to one pixel of the Each pixel of source image contributes to one pixel of the
destination image with identical weights. destination image with identical weights.
@ -158,6 +160,7 @@ pixel, the Python Imaging Library provides different resampling *filters*.
.. versionadded:: 3.4.0 .. versionadded:: 3.4.0
.. data:: Resampling.BILINEAR .. data:: Resampling.BILINEAR
:noindex:
For resize calculate the output pixel value using linear interpolation For resize calculate the output pixel value using linear interpolation
on all pixels that may contribute to the output value. on all pixels that may contribute to the output value.
@ -165,6 +168,7 @@ pixel, the Python Imaging Library provides different resampling *filters*.
in the input image is used. in the input image is used.
.. data:: Resampling.HAMMING .. data:: Resampling.HAMMING
:noindex:
Produces a sharper image than :data:`Resampling.BILINEAR`, doesn't have Produces a sharper image than :data:`Resampling.BILINEAR`, doesn't have
dislocations on local level like with :data:`Resampling.BOX`. dislocations on local level like with :data:`Resampling.BOX`.
@ -174,6 +178,7 @@ pixel, the Python Imaging Library provides different resampling *filters*.
.. versionadded:: 3.4.0 .. versionadded:: 3.4.0
.. data:: Resampling.BICUBIC .. data:: Resampling.BICUBIC
:noindex:
For resize calculate the output pixel value using cubic interpolation For resize calculate the output pixel value using cubic interpolation
on all pixels that may contribute to the output value. on all pixels that may contribute to the output value.
@ -181,6 +186,7 @@ pixel, the Python Imaging Library provides different resampling *filters*.
in the input image is used. in the input image is used.
.. data:: Resampling.LANCZOS .. data:: Resampling.LANCZOS
:noindex:
Calculate the output pixel value using a high-quality Lanczos filter (a Calculate the output pixel value using a high-quality Lanczos filter (a
truncated sinc) on all pixels that may contribute to the output value. truncated sinc) on all pixels that may contribute to the output value.

View File

@ -78,8 +78,6 @@ Constructing images
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
.. autofunction:: new .. autofunction:: new
.. autoclass:: SupportsArrayInterface
:show-inheritance:
.. autofunction:: fromarray .. autofunction:: fromarray
.. autofunction:: frombytes .. autofunction:: frombytes
.. autofunction:: frombuffer .. autofunction:: frombuffer
@ -365,6 +363,14 @@ Classes
.. autoclass:: PIL.Image.ImagePointHandler .. autoclass:: PIL.Image.ImagePointHandler
.. autoclass:: PIL.Image.ImageTransformHandler .. autoclass:: PIL.Image.ImageTransformHandler
Protocols
---------
.. autoclass:: SupportsArrayInterface
:show-inheritance:
.. autoclass:: SupportsGetData
:show-inheritance:
Constants Constants
--------- ---------
@ -418,7 +424,6 @@ See :ref:`concept-filters` for details.
.. autoclass:: Resampling .. autoclass:: Resampling
:members: :members:
:undoc-members: :undoc-members:
:noindex:
Dither modes Dither modes
^^^^^^^^^^^^ ^^^^^^^^^^^^

View File

@ -503,6 +503,12 @@ def _getscaleoffset(expr):
# Implementation wrapper # Implementation wrapper
class SupportsGetData(Protocol):
def getdata(
self,
) -> tuple[Transform, Sequence[int]]: ...
class Image: class Image:
""" """
This class represents an image object. To create This class represents an image object. To create
@ -1289,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
@ -1709,7 +1715,12 @@ class Image:
return self.im.entropy(extrema) return self.im.entropy(extrema)
return self.im.entropy() return self.im.entropy()
def paste(self, im, box=None, mask=None) -> None: def paste(
self,
im: Image | str | float | tuple[float, ...],
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 Pastes another image into this image. The box argument is either
a 2-tuple giving the upper left corner, a 4-tuple defining the a 2-tuple giving the upper left corner, a 4-tuple defining the
@ -1737,7 +1748,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
@ -2146,7 +2157,13 @@ class Image:
min(self.size[1], math.ceil(box[3] + support_y)), 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: int | None = None,
box: tuple[float, float, float, float] | None = None,
reducing_gap: float | None = None,
) -> Image:
""" """
Returns a resized copy of this image. Returns a resized copy of this image.
@ -2211,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 = 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 = 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()
@ -2252,7 +2265,11 @@ class Image:
return self._new(self.im.resize(size, resample, box)) 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. Returns a copy of the image reduced ``factor`` times.
If the size of the image is not dividable by ``factor``, If the size of the image is not dividable by ``factor``,
@ -2270,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 = 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()
@ -2287,13 +2302,13 @@ class Image:
def rotate( def rotate(
self, self,
angle, angle: float,
resample=Resampling.NEAREST, resample: Resampling = Resampling.NEAREST,
expand=0, expand: int | bool = False,
center=None, center: tuple[int, int] | None = None,
translate=None, translate: tuple[int, int] | None = None,
fillcolor=None, fillcolor: float | tuple[float, ...] | str | None = None,
): ) -> Image:
""" """
Returns a rotated copy of this image. This method returns a Returns a rotated copy of this image. This method returns a
copy of this image, rotated the given number of degrees counter copy of this image, rotated the given number of degrees counter
@ -2600,7 +2615,12 @@ class Image:
""" """
return 0 return 0
def thumbnail(self, size, resample=Resampling.BICUBIC, reducing_gap=2.0): def thumbnail(
self,
size: tuple[float, float],
resample: Resampling = Resampling.BICUBIC,
reducing_gap: float = 2.0,
) -> None:
""" """
Make this image into a thumbnail. This method modifies the Make this image into a thumbnail. This method modifies the
image to contain a thumbnail version of itself, no larger than image to contain a thumbnail version of itself, no larger than
@ -2661,20 +2681,24 @@ class Image:
box = None box = None
if reducing_gap is not None: if reducing_gap is not None:
size = preserve_aspect_ratio() preserved_size = preserve_aspect_ratio()
if size is None: if preserved_size is None:
return return
size = preserved_size
res = self.draft(None, (size[0] * reducing_gap, size[1] * reducing_gap)) 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:
self.load() self.load()
# load() may have changed the size of the image # load() may have changed the size of the image
size = preserve_aspect_ratio() preserved_size = preserve_aspect_ratio()
if size is None: if preserved_size is None:
return return
size = preserved_size
if self.size != size: if self.size != size:
im = self.resize(size, resample, box=box, reducing_gap=reducing_gap) im = self.resize(size, resample, box=box, reducing_gap=reducing_gap)
@ -2690,12 +2714,12 @@ class Image:
# instead of bloating the method docs, add a separate chapter. # instead of bloating the method docs, add a separate chapter.
def transform( def transform(
self, self,
size, size: tuple[int, int],
method, method: Transform | ImageTransformHandler | SupportsGetData,
data=None, data: Sequence[Any] | None = None,
resample=Resampling.NEAREST, resample: int = Resampling.NEAREST,
fill=1, fill: int = 1,
fillcolor=None, fillcolor: float | tuple[float, ...] | str | None = None,
) -> Image: ) -> Image:
""" """
Transforms this image. This method creates a new image with the Transforms this image. This method creates a new image with the
@ -2929,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]], **options: Any,
) -> Image: ) -> Image:
pass pass

View File

@ -34,7 +34,7 @@ from __future__ import annotations
import math import math
import numbers import numbers
import struct import struct
from typing import TYPE_CHECKING, Sequence, cast from typing import TYPE_CHECKING, AnyStr, Sequence, cast
from . import Image, ImageColor from . import Image, ImageColor
from ._typing import Coords from ._typing import Coords
@ -95,7 +95,9 @@ class ImageDraw:
if TYPE_CHECKING: if TYPE_CHECKING:
from . import ImageFont from . import ImageFont
def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont: def getfont(
self,
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
""" """
Get the current default font. Get the current default font.
@ -120,14 +122,15 @@ class ImageDraw:
self.font = ImageFont.load_default() self.font = ImageFont.load_default()
return self.font return self.font
def _getfont(self, font_size: float | None): def _getfont(
self, font_size: float | None
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
if font_size is not None: if font_size is not None:
from . import ImageFont from . import ImageFont
font = ImageFont.load_default(font_size) return ImageFont.load_default(font_size)
else: else:
font = self.getfont() return self.getfont()
return font
def _getink(self, ink, fill=None) -> tuple[int | None, int | None]: def _getink(self, ink, fill=None) -> tuple[int | None, int | None]:
if ink is None and fill is None: if ink is None and fill is None:
@ -460,15 +463,13 @@ class ImageDraw:
right[3] -= r + 1 right[3] -= r + 1
self.draw.draw_rectangle(right, ink, 1) self.draw.draw_rectangle(right, ink, 1)
def _multiline_check(self, text) -> bool: def _multiline_check(self, text: AnyStr) -> bool:
split_character = "\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) -> list[str | bytes]: def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
split_character = "\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 (
@ -479,10 +480,15 @@ class ImageDraw:
def text( def text(
self, self,
xy, 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",
@ -536,7 +542,7 @@ class ImageDraw:
coord.append(int(xy[i])) coord.append(int(xy[i]))
start.append(math.modf(xy[i])[0]) start.append(math.modf(xy[i])[0])
try: try:
mask, offset = font.getmask2( mask, offset = font.getmask2( # type: ignore[union-attr,misc]
text, text,
mode, mode,
direction=direction, direction=direction,
@ -552,7 +558,7 @@ class ImageDraw:
coord = [coord[0] + offset[0], coord[1] + offset[1]] coord = [coord[0] + offset[0], coord[1] + offset[1]]
except AttributeError: except AttributeError:
try: try:
mask = font.getmask( mask = font.getmask( # type: ignore[misc]
text, text,
mode, mode,
direction, direction,
@ -601,10 +607,15 @@ class ImageDraw:
def multiline_text( def multiline_text(
self, self,
xy, 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",
@ -634,7 +645,7 @@ class ImageDraw:
font = self._getfont(font_size) font = self._getfont(font_size)
widths = [] widths = []
max_width = 0 max_width: float = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self._multiline_spacing(font, spacing, stroke_width) line_spacing = self._multiline_spacing(font, spacing, stroke_width)
for line in lines: for line in lines:
@ -688,15 +699,20 @@ class ImageDraw:
def textlength( def textlength(
self, self,
text, text: str,
font=None, font: (
ImageFont.ImageFont
| ImageFont.FreeTypeFont
| ImageFont.TransposedFont
| None
) = None,
direction=None, direction=None,
features=None, features=None,
language=None, language=None,
embedded_color=False, embedded_color=False,
*, *,
font_size=None, font_size=None,
): ) -> float:
"""Get the length of a given string, in pixels with 1/64 precision.""" """Get the length of a given string, in pixels with 1/64 precision."""
if self._multiline_check(text): if self._multiline_check(text):
msg = "can't measure length of multiline text" msg = "can't measure length of multiline text"
@ -788,7 +804,7 @@ class ImageDraw:
font = self._getfont(font_size) font = self._getfont(font_size)
widths = [] widths = []
max_width = 0 max_width: float = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = self._multiline_spacing(font, spacing, stroke_width) line_spacing = self._multiline_spacing(font, spacing, stroke_width)
for line in lines: for line in lines:

View File

@ -33,11 +33,16 @@ 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 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:
from . import ImageFile
from ._imaging import ImagingFont
from ._imagingft import Font
class Layout(IntEnum): class Layout(IntEnum):
@ -56,7 +61,7 @@ except ImportError as ex:
core = DeferredError.new(ex) core = DeferredError.new(ex)
def _string_length_check(text): 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)
@ -81,9 +86,11 @@ def _string_length_check(text):
class ImageFont: class ImageFont:
"""PIL font wrapper""" """PIL font wrapper"""
def _load_pilfont(self, filename): font: ImagingFont
def _load_pilfont(self, filename: str) -> None:
with open(filename, "rb") as fp: with open(filename, "rb") as fp:
image = None image: ImageFile.ImageFile | None = None
for ext in (".png", ".gif", ".pbm"): for ext in (".png", ".gif", ".pbm"):
if image: if image:
image.close() image.close()
@ -106,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"
@ -153,7 +160,9 @@ class ImageFont:
Image._decompression_bomb_check(self.font.getsize(text)) Image._decompression_bomb_check(self.font.getsize(text))
return self.font.getmask(text, mode) return self.font.getmask(text, mode)
def getbbox(self, text, *args, **kwargs): def getbbox(
self, text: str | bytes | bytearray, *args: Any, **kwargs: Any
) -> tuple[int, int, int, int]:
""" """
Returns bounding box (in pixels) of given text. Returns bounding box (in pixels) of given text.
@ -167,7 +176,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, *args, **kwargs): 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.
@ -187,6 +198,8 @@ class ImageFont:
class FreeTypeFont: class FreeTypeFont:
"""FreeType font wrapper (requires _imagingft service)""" """FreeType font wrapper (requires _imagingft service)"""
font: Font
def __init__( def __init__(
self, self,
font: StrOrBytesPath | BinaryIO | None = None, font: StrOrBytesPath | BinaryIO | None = None,
@ -250,7 +263,7 @@ class FreeTypeFont:
path, size, index, encoding, layout_engine = state path, size, index, encoding, layout_engine = state
self.__init__(path, size, index, encoding, layout_engine) self.__init__(path, size, index, encoding, layout_engine)
def getname(self): def getname(self) -> tuple[str | None, str | None]:
""" """
:return: A tuple of the font family (e.g. Helvetica) and the font style :return: A tuple of the font family (e.g. Helvetica) and the font style
(e.g. Bold) (e.g. Bold)
@ -265,7 +278,9 @@ class FreeTypeFont:
""" """
return self.font.ascent, self.font.descent 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 Returns length (in pixels with 1/64 precision) of given text when rendered
in font with provided direction, features, and language. in font with provided direction, features, and language.
@ -339,14 +354,14 @@ class FreeTypeFont:
def getbbox( def getbbox(
self, self,
text, text: str,
mode="", mode: str = "",
direction=None, direction: str | None = None,
features=None, features: list[str] | None = None,
language=None, language: str | None = None,
stroke_width=0, stroke_width: float = 0,
anchor=None, anchor: str | None = None,
): ) -> 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.
@ -496,7 +511,7 @@ class FreeTypeFont:
def getmask2( def getmask2(
self, self,
text, text: str,
mode="", mode="",
direction=None, direction=None,
features=None, features=None,
@ -666,10 +681,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:
axis["name"] = axis["name"].replace(b"\x00", b"") if axis["name"]:
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.
@ -714,14 +730,14 @@ 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)
return self.font.getlength(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 Load a font file. This function loads a font object from the given
bitmap font file, and returns the corresponding font object. bitmap font file, and returns the corresponding font object.
@ -735,7 +751,13 @@ def load(filename):
return f 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, Load a TrueType or OpenType font from a file or file-like object,
and create a font object. and create a font object.
@ -796,7 +818,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
:exception ValueError: If the font size is not greater than zero. :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) return FreeTypeFont(font, size, index, encoding, layout_engine)
try: try:
@ -846,7 +868,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
raise 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 Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a
bitmap font along the Python path. bitmap font along the Python path.
@ -855,14 +877,13 @@ def load_path(filename):
: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.
""" """
if not isinstance(filename, str):
filename = filename.decode("utf-8")
for directory in sys.path: for directory in sys.path:
if is_directory(directory): try:
if not isinstance(filename, str): return load(os.path.join(directory, filename))
filename = filename.decode("utf-8") except OSError:
try: pass
return load(os.path.join(directory, filename))
except OSError:
pass
msg = "cannot find font file" msg = "cannot find font file"
raise OSError(msg) raise OSError(msg)
@ -881,6 +902,7 @@ def load_default(size: float | None = None) -> FreeTypeFont | ImageFont:
:return: A font object. :return: A font object.
""" """
f: FreeTypeFont | ImageFont
if core.__class__.__name__ == "module" or size is not None: if core.__class__.__name__ == "module" or size is not None:
f = truetype( f = truetype(
BytesIO( BytesIO(

View File

@ -14,7 +14,7 @@
# #
from __future__ import annotations from __future__ import annotations
from typing import Sequence from typing import Any, Sequence
from . import Image from . import Image
@ -34,7 +34,7 @@ class Transform(Image.ImageTransformHandler):
self, self,
size: tuple[int, int], size: tuple[int, int],
image: Image.Image, image: Image.Image,
**options: dict[str, str | int | tuple[int, ...] | list[int]], **options: Any,
) -> Image.Image: ) -> Image.Image:
"""Perform the transform. Called from :py:meth:`.Image.transform`.""" """Perform the transform. Called from :py:meth:`.Image.transform`."""
# can be overridden # can be overridden

View File

@ -426,7 +426,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,3 +1,16 @@
from typing import Any from typing import Any
class ImagingCore:
def __getattr__(self, name: str) -> Any: ...
class ImagingFont:
def __getattr__(self, name: str) -> Any: ...
class ImagingDraw:
def __getattr__(self, name: str) -> Any: ...
class PixelAccess:
def __getattr__(self, name: str) -> Any: ...
def font(image, glyphdata: bytes) -> ImagingFont: ...
def __getattr__(name: str) -> Any: ... def __getattr__(name: str) -> Any: ...

View File

@ -1,3 +1,69 @@
from typing import Any from typing import Any, TypedDict
from . import _imaging
class _Axis(TypedDict):
minimum: int | None
default: int | None
maximum: int | None
name: bytes | 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[_imaging.ImagingCore, tuple[int, int]]: ...
def getsize(
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=..., /
) -> 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,
size: float,
index=...,
encoding=...,
font_bytes=...,
layout_engine=...,
) -> Font: ...
def __getattr__(name: str) -> Any: ... def __getattr__(name: str) -> Any: ...