mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-24 17:06:16 +03:00
Merge pull request #8046 from srittau/type-annotations
Add various type annotations
This commit is contained in:
commit
5bacce9dc2
|
@ -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:
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: ...
|
||||||
|
|
|
@ -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: ...
|
||||||
|
|
Loading…
Reference in New Issue
Block a user