mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 09:14:27 +03:00
Added type hints
This commit is contained in:
parent
6dd4b3c826
commit
726cdf5eed
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import AnyStr
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None:
|
||||||
|
|
||||||
|
|
||||||
def _test_high_characters(
|
def _test_high_characters(
|
||||||
request: pytest.FixtureRequest, tmp_path: Path, message: str | bytes
|
request: pytest.FixtureRequest, tmp_path: Path, message: AnyStr
|
||||||
) -> None:
|
) -> None:
|
||||||
tempname = save_font(request, tmp_path)
|
tempname = save_font(request, tmp_path)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
|
|
|
@ -717,14 +717,14 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
|
||||||
|
|
||||||
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
|
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
|
||||||
_check_text(font, "Tests/images/variation_adobe.png", 11)
|
_check_text(font, "Tests/images/variation_adobe.png", 11)
|
||||||
for name in ["Bold", b"Bold"]:
|
for name in ("Bold", b"Bold"):
|
||||||
font.set_variation_by_name(name)
|
font.set_variation_by_name(name)
|
||||||
assert font.getname()[1] == "Bold"
|
assert font.getname()[1] == "Bold"
|
||||||
_check_text(font, "Tests/images/variation_adobe_name.png", 16)
|
_check_text(font, "Tests/images/variation_adobe_name.png", 16)
|
||||||
|
|
||||||
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
|
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
|
||||||
_check_text(font, "Tests/images/variation_tiny.png", 40)
|
_check_text(font, "Tests/images/variation_tiny.png", 40)
|
||||||
for name in ["200", b"200"]:
|
for name in ("200", b"200"):
|
||||||
font.set_variation_by_name(name)
|
font.set_variation_by_name(name)
|
||||||
assert font.getname()[1] == "200"
|
assert font.getname()[1] == "200"
|
||||||
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
|
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
|
||||||
|
|
|
@ -91,3 +91,11 @@ Constants
|
||||||
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
|
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
|
||||||
raise a :py:exc:`ValueError` if the number of characters is over this limit. The
|
raise a :py:exc:`ValueError` if the number of characters is over this limit. The
|
||||||
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
||||||
|
|
||||||
|
Dictionaries
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. autoclass:: Axis
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
|
@ -78,3 +78,7 @@ on some Python versions.
|
||||||
|
|
||||||
An internal interface module previously known as :mod:`~PIL._imaging`,
|
An internal interface module previously known as :mod:`~PIL._imaging`,
|
||||||
implemented in :file:`_imaging.c`.
|
implemented in :file:`_imaging.c`.
|
||||||
|
|
||||||
|
.. py:class:: ImagingCore
|
||||||
|
|
||||||
|
A representation of the image data.
|
||||||
|
|
|
@ -159,6 +159,4 @@ exclude = [
|
||||||
'^Tests/oss-fuzz/fuzz_font.py$',
|
'^Tests/oss-fuzz/fuzz_font.py$',
|
||||||
'^Tests/oss-fuzz/fuzz_pillow.py$',
|
'^Tests/oss-fuzz/fuzz_pillow.py$',
|
||||||
'^Tests/test_qt_image_qapplication.py$',
|
'^Tests/test_qt_image_qapplication.py$',
|
||||||
'^Tests/test_font_pcf_charsets.py$',
|
|
||||||
'^Tests/test_font_pcf.py$',
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -218,6 +218,8 @@ if hasattr(core, "DEFAULT_STRATEGY"):
|
||||||
# Registries
|
# Registries
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
from . import ImageFile, ImagePalette
|
from . import ImageFile, ImagePalette
|
||||||
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
|
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
|
||||||
ID: list[str] = []
|
ID: list[str] = []
|
||||||
|
@ -241,9 +243,9 @@ ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {}
|
||||||
_ENDIAN = "<" if sys.byteorder == "little" else ">"
|
_ENDIAN = "<" if sys.byteorder == "little" else ">"
|
||||||
|
|
||||||
|
|
||||||
def _conv_type_shape(im):
|
def _conv_type_shape(im: Image) -> tuple[tuple[int, ...], str]:
|
||||||
m = ImageMode.getmode(im.mode)
|
m = ImageMode.getmode(im.mode)
|
||||||
shape = (im.height, im.width)
|
shape: tuple[int, ...] = (im.height, im.width)
|
||||||
extra = len(m.bands)
|
extra = len(m.bands)
|
||||||
if extra != 1:
|
if extra != 1:
|
||||||
shape += (extra,)
|
shape += (extra,)
|
||||||
|
@ -470,10 +472,10 @@ class _E:
|
||||||
self.scale = scale
|
self.scale = scale
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
|
||||||
def __neg__(self):
|
def __neg__(self) -> _E:
|
||||||
return _E(-self.scale, -self.offset)
|
return _E(-self.scale, -self.offset)
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other) -> _E:
|
||||||
if isinstance(other, _E):
|
if isinstance(other, _E):
|
||||||
return _E(self.scale + other.scale, self.offset + other.offset)
|
return _E(self.scale + other.scale, self.offset + other.offset)
|
||||||
return _E(self.scale, self.offset + other)
|
return _E(self.scale, self.offset + other)
|
||||||
|
@ -486,14 +488,14 @@ class _E:
|
||||||
def __rsub__(self, other):
|
def __rsub__(self, other):
|
||||||
return other + -self
|
return other + -self
|
||||||
|
|
||||||
def __mul__(self, other):
|
def __mul__(self, other) -> _E:
|
||||||
if isinstance(other, _E):
|
if isinstance(other, _E):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return _E(self.scale * other, self.offset * other)
|
return _E(self.scale * other, self.offset * other)
|
||||||
|
|
||||||
__rmul__ = __mul__
|
__rmul__ = __mul__
|
||||||
|
|
||||||
def __truediv__(self, other):
|
def __truediv__(self, other) -> _E:
|
||||||
if isinstance(other, _E):
|
if isinstance(other, _E):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return _E(self.scale / other, self.offset / other)
|
return _E(self.scale / other, self.offset / other)
|
||||||
|
@ -718,9 +720,9 @@ class Image:
|
||||||
return self._repr_image("JPEG")
|
return self._repr_image("JPEG")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __array_interface__(self):
|
def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]:
|
||||||
# numpy array interface support
|
# numpy array interface support
|
||||||
new = {"version": 3}
|
new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3}
|
||||||
try:
|
try:
|
||||||
if self.mode == "1":
|
if self.mode == "1":
|
||||||
# Binary images need to be extended from bits to bytes
|
# Binary images need to be extended from bits to bytes
|
||||||
|
@ -1418,7 +1420,7 @@ class Image:
|
||||||
return out
|
return out
|
||||||
return self.im.getcolors(maxcolors)
|
return self.im.getcolors(maxcolors)
|
||||||
|
|
||||||
def getdata(self, band: int | None = None):
|
def getdata(self, band: int | None = None) -> core.ImagingCore:
|
||||||
"""
|
"""
|
||||||
Returns the contents of this image as a sequence object
|
Returns the contents of this image as a sequence object
|
||||||
containing pixel values. The sequence object is flattened, so
|
containing pixel values. The sequence object is flattened, so
|
||||||
|
@ -1467,8 +1469,8 @@ class Image:
|
||||||
def get_name(tag: str) -> str:
|
def get_name(tag: str) -> str:
|
||||||
return re.sub("^{[^}]+}", "", tag)
|
return re.sub("^{[^}]+}", "", tag)
|
||||||
|
|
||||||
def get_value(element):
|
def get_value(element: Element) -> str | dict[str, Any] | None:
|
||||||
value = {get_name(k): v for k, v in element.attrib.items()}
|
value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()}
|
||||||
children = list(element)
|
children = list(element)
|
||||||
if children:
|
if children:
|
||||||
for child in children:
|
for child in children:
|
||||||
|
@ -1712,7 +1714,7 @@ class Image:
|
||||||
return self.im.histogram(extrema)
|
return self.im.histogram(extrema)
|
||||||
return self.im.histogram()
|
return self.im.histogram()
|
||||||
|
|
||||||
def entropy(self, mask=None, extrema=None):
|
def entropy(self, mask: Image | None = None, extrema=None):
|
||||||
"""
|
"""
|
||||||
Calculates and returns the entropy for the image.
|
Calculates and returns the entropy for the image.
|
||||||
|
|
||||||
|
@ -1996,7 +1998,7 @@ class Image:
|
||||||
|
|
||||||
def putdata(
|
def putdata(
|
||||||
self,
|
self,
|
||||||
data: Sequence[float] | Sequence[Sequence[int]] | NumpyArray,
|
data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray,
|
||||||
scale: float = 1.0,
|
scale: float = 1.0,
|
||||||
offset: float = 0.0,
|
offset: float = 0.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -2184,7 +2186,12 @@ class Image:
|
||||||
|
|
||||||
return m_im
|
return m_im
|
||||||
|
|
||||||
def _get_safe_box(self, size, resample, box):
|
def _get_safe_box(
|
||||||
|
self,
|
||||||
|
size: tuple[int, int],
|
||||||
|
resample: Resampling,
|
||||||
|
box: tuple[float, float, float, float],
|
||||||
|
) -> tuple[int, int, int, int]:
|
||||||
"""Expands the box so it includes adjacent pixels
|
"""Expands the box so it includes adjacent pixels
|
||||||
that may be used by resampling with the given resampling filter.
|
that may be used by resampling with the given resampling filter.
|
||||||
"""
|
"""
|
||||||
|
@ -2294,7 +2301,7 @@ class Image:
|
||||||
factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1
|
factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1
|
||||||
factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
|
factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
|
||||||
if factor_x > 1 or factor_y > 1:
|
if factor_x > 1 or factor_y > 1:
|
||||||
reduce_box = self._get_safe_box(size, resample, box)
|
reduce_box = self._get_safe_box(size, cast(Resampling, resample), box)
|
||||||
factor = (factor_x, factor_y)
|
factor = (factor_x, factor_y)
|
||||||
self = (
|
self = (
|
||||||
self.reduce(factor, box=reduce_box)
|
self.reduce(factor, box=reduce_box)
|
||||||
|
@ -2430,7 +2437,7 @@ class Image:
|
||||||
0.0,
|
0.0,
|
||||||
]
|
]
|
||||||
|
|
||||||
def transform(x, y, matrix):
|
def transform(x: float, y: float, matrix: list[float]) -> tuple[float, float]:
|
||||||
(a, b, c, d, e, f) = matrix
|
(a, b, c, d, e, f) = matrix
|
||||||
return a * x + b * y + c, d * x + e * y + f
|
return a * x + b * y + c, d * x + e * y + f
|
||||||
|
|
||||||
|
@ -2445,9 +2452,9 @@ class Image:
|
||||||
xx = []
|
xx = []
|
||||||
yy = []
|
yy = []
|
||||||
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
|
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
|
||||||
x, y = transform(x, y, matrix)
|
transformed_x, transformed_y = transform(x, y, matrix)
|
||||||
xx.append(x)
|
xx.append(transformed_x)
|
||||||
yy.append(y)
|
yy.append(transformed_y)
|
||||||
nw = math.ceil(max(xx)) - math.floor(min(xx))
|
nw = math.ceil(max(xx)) - math.floor(min(xx))
|
||||||
nh = math.ceil(max(yy)) - math.floor(min(yy))
|
nh = math.ceil(max(yy)) - math.floor(min(yy))
|
||||||
|
|
||||||
|
@ -2705,7 +2712,7 @@ class Image:
|
||||||
provided_size = tuple(map(math.floor, size))
|
provided_size = tuple(map(math.floor, size))
|
||||||
|
|
||||||
def preserve_aspect_ratio() -> tuple[int, int] | None:
|
def preserve_aspect_ratio() -> tuple[int, int] | None:
|
||||||
def round_aspect(number, key):
|
def round_aspect(number: float, key: Callable[[int], float]) -> int:
|
||||||
return max(min(math.floor(number), math.ceil(number), key=key), 1)
|
return max(min(math.floor(number), math.ceil(number), key=key), 1)
|
||||||
|
|
||||||
x, y = provided_size
|
x, y = provided_size
|
||||||
|
@ -2849,7 +2856,13 @@ class Image:
|
||||||
return im
|
return im
|
||||||
|
|
||||||
def __transformer(
|
def __transformer(
|
||||||
self, box, image, method, data, resample=Resampling.NEAREST, fill=1
|
self,
|
||||||
|
box: tuple[int, int, int, int],
|
||||||
|
image: Image,
|
||||||
|
method,
|
||||||
|
data,
|
||||||
|
resample: int = Resampling.NEAREST,
|
||||||
|
fill: bool = True,
|
||||||
):
|
):
|
||||||
w = box[2] - box[0]
|
w = box[2] - box[0]
|
||||||
h = box[3] - box[1]
|
h = box[3] - box[1]
|
||||||
|
@ -2899,11 +2912,12 @@ class Image:
|
||||||
Resampling.BICUBIC,
|
Resampling.BICUBIC,
|
||||||
):
|
):
|
||||||
if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
|
if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
|
||||||
msg = {
|
unusable: dict[int, str] = {
|
||||||
Resampling.BOX: "Image.Resampling.BOX",
|
Resampling.BOX: "Image.Resampling.BOX",
|
||||||
Resampling.HAMMING: "Image.Resampling.HAMMING",
|
Resampling.HAMMING: "Image.Resampling.HAMMING",
|
||||||
Resampling.LANCZOS: "Image.Resampling.LANCZOS",
|
Resampling.LANCZOS: "Image.Resampling.LANCZOS",
|
||||||
}[resample] + f" ({resample}) cannot be used."
|
}
|
||||||
|
msg = unusable[resample] + f" ({resample}) cannot be used."
|
||||||
else:
|
else:
|
||||||
msg = f"Unknown resampling filter ({resample})."
|
msg = f"Unknown resampling filter ({resample})."
|
||||||
|
|
||||||
|
@ -3843,7 +3857,7 @@ class Exif(_ExifBase):
|
||||||
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
|
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
|
||||||
"""
|
"""
|
||||||
|
|
||||||
endian = None
|
endian: str | None = None
|
||||||
bigtiff = False
|
bigtiff = False
|
||||||
_loaded = False
|
_loaded = False
|
||||||
|
|
||||||
|
@ -3892,7 +3906,7 @@ class Exif(_ExifBase):
|
||||||
head += b"\x00\x00\x00\x00"
|
head += b"\x00\x00\x00\x00"
|
||||||
return head
|
return head
|
||||||
|
|
||||||
def load(self, data):
|
def load(self, data: bytes) -> None:
|
||||||
# Extract EXIF information. This is highly experimental,
|
# Extract EXIF information. This is highly experimental,
|
||||||
# and is likely to be replaced with something better in a future
|
# and is likely to be replaced with something better in a future
|
||||||
# version.
|
# version.
|
||||||
|
@ -3911,7 +3925,7 @@ class Exif(_ExifBase):
|
||||||
self._info = None
|
self._info = None
|
||||||
return
|
return
|
||||||
|
|
||||||
self.fp = io.BytesIO(data)
|
self.fp: IO[bytes] = io.BytesIO(data)
|
||||||
self.head = self.fp.read(8)
|
self.head = self.fp.read(8)
|
||||||
# process dictionary
|
# process dictionary
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
|
@ -3921,7 +3935,7 @@ class Exif(_ExifBase):
|
||||||
self.fp.seek(self._info.next)
|
self.fp.seek(self._info.next)
|
||||||
self._info.load(self.fp)
|
self._info.load(self.fp)
|
||||||
|
|
||||||
def load_from_fp(self, fp, offset=None):
|
def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None:
|
||||||
self._loaded_exif = None
|
self._loaded_exif = None
|
||||||
self._data.clear()
|
self._data.clear()
|
||||||
self._hidden_data.clear()
|
self._hidden_data.clear()
|
||||||
|
|
|
@ -36,7 +36,7 @@ import numbers
|
||||||
import struct
|
import struct
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import TYPE_CHECKING, AnyStr, Callable, Union, cast
|
from typing import TYPE_CHECKING, Any, AnyStr, Callable, Union, cast
|
||||||
|
|
||||||
from . import Image, ImageColor
|
from . import Image, ImageColor
|
||||||
from ._deprecate import deprecate
|
from ._deprecate import deprecate
|
||||||
|
@ -561,7 +561,12 @@ class ImageDraw:
|
||||||
def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
|
def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
|
||||||
return text.split("\n" if isinstance(text, str) else b"\n")
|
return text.split("\n" if isinstance(text, str) else b"\n")
|
||||||
|
|
||||||
def _multiline_spacing(self, font, spacing, stroke_width):
|
def _multiline_spacing(
|
||||||
|
self,
|
||||||
|
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
|
||||||
|
spacing: float,
|
||||||
|
stroke_width: float,
|
||||||
|
) -> float:
|
||||||
return (
|
return (
|
||||||
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
|
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
|
||||||
+ stroke_width
|
+ stroke_width
|
||||||
|
@ -571,25 +576,25 @@ class ImageDraw:
|
||||||
def text(
|
def text(
|
||||||
self,
|
self,
|
||||||
xy: tuple[float, float],
|
xy: tuple[float, float],
|
||||||
text: str,
|
text: AnyStr,
|
||||||
fill=None,
|
fill: _Ink | None = None,
|
||||||
font: (
|
font: (
|
||||||
ImageFont.ImageFont
|
ImageFont.ImageFont
|
||||||
| ImageFont.FreeTypeFont
|
| ImageFont.FreeTypeFont
|
||||||
| ImageFont.TransposedFont
|
| ImageFont.TransposedFont
|
||||||
| None
|
| None
|
||||||
) = None,
|
) = None,
|
||||||
anchor=None,
|
anchor: str | None = None,
|
||||||
spacing=4,
|
spacing: float = 4,
|
||||||
align="left",
|
align: str = "left",
|
||||||
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,
|
||||||
stroke_fill=None,
|
stroke_fill: _Ink | None = None,
|
||||||
embedded_color=False,
|
embedded_color: bool = False,
|
||||||
*args,
|
*args: Any,
|
||||||
**kwargs,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw text."""
|
"""Draw text."""
|
||||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||||
|
@ -623,15 +628,14 @@ class ImageDraw:
|
||||||
return fill_ink
|
return fill_ink
|
||||||
return ink
|
return ink
|
||||||
|
|
||||||
def draw_text(ink, stroke_width=0) -> None:
|
def draw_text(ink: int, stroke_width: float = 0) -> None:
|
||||||
mode = self.fontmode
|
mode = self.fontmode
|
||||||
if stroke_width == 0 and embedded_color:
|
if stroke_width == 0 and embedded_color:
|
||||||
mode = "RGBA"
|
mode = "RGBA"
|
||||||
coord = []
|
coord = []
|
||||||
start = []
|
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
coord.append(int(xy[i]))
|
coord.append(int(xy[i]))
|
||||||
start.append(math.modf(xy[i])[0])
|
start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
|
||||||
try:
|
try:
|
||||||
mask, offset = font.getmask2( # type: ignore[union-attr,misc]
|
mask, offset = font.getmask2( # type: ignore[union-attr,misc]
|
||||||
text,
|
text,
|
||||||
|
@ -697,25 +701,25 @@ class ImageDraw:
|
||||||
def multiline_text(
|
def multiline_text(
|
||||||
self,
|
self,
|
||||||
xy: tuple[float, float],
|
xy: tuple[float, float],
|
||||||
text: str,
|
text: AnyStr,
|
||||||
fill=None,
|
fill: _Ink | None = None,
|
||||||
font: (
|
font: (
|
||||||
ImageFont.ImageFont
|
ImageFont.ImageFont
|
||||||
| ImageFont.FreeTypeFont
|
| ImageFont.FreeTypeFont
|
||||||
| ImageFont.TransposedFont
|
| ImageFont.TransposedFont
|
||||||
| None
|
| None
|
||||||
) = None,
|
) = None,
|
||||||
anchor=None,
|
anchor: str | None = None,
|
||||||
spacing=4,
|
spacing: float = 4,
|
||||||
align="left",
|
align: str = "left",
|
||||||
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,
|
||||||
stroke_fill=None,
|
stroke_fill: _Ink | None = None,
|
||||||
embedded_color=False,
|
embedded_color: bool = False,
|
||||||
*,
|
*,
|
||||||
font_size=None,
|
font_size: float | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if direction == "ttb":
|
if direction == "ttb":
|
||||||
msg = "ttb direction is unsupported for multiline text"
|
msg = "ttb direction is unsupported for multiline text"
|
||||||
|
@ -788,19 +792,19 @@ class ImageDraw:
|
||||||
|
|
||||||
def textlength(
|
def textlength(
|
||||||
self,
|
self,
|
||||||
text: str,
|
text: AnyStr,
|
||||||
font: (
|
font: (
|
||||||
ImageFont.ImageFont
|
ImageFont.ImageFont
|
||||||
| ImageFont.FreeTypeFont
|
| ImageFont.FreeTypeFont
|
||||||
| ImageFont.TransposedFont
|
| ImageFont.TransposedFont
|
||||||
| None
|
| None
|
||||||
) = None,
|
) = None,
|
||||||
direction=None,
|
direction: str | None = None,
|
||||||
features=None,
|
features: list[str] | None = None,
|
||||||
language=None,
|
language: str | None = None,
|
||||||
embedded_color=False,
|
embedded_color: bool = False,
|
||||||
*,
|
*,
|
||||||
font_size=None,
|
font_size: float | None = None,
|
||||||
) -> float:
|
) -> 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):
|
||||||
|
@ -817,20 +821,25 @@ class ImageDraw:
|
||||||
|
|
||||||
def textbbox(
|
def textbbox(
|
||||||
self,
|
self,
|
||||||
xy,
|
xy: tuple[float, float],
|
||||||
text,
|
text: AnyStr,
|
||||||
font=None,
|
font: (
|
||||||
anchor=None,
|
ImageFont.ImageFont
|
||||||
spacing=4,
|
| ImageFont.FreeTypeFont
|
||||||
align="left",
|
| ImageFont.TransposedFont
|
||||||
direction=None,
|
| None
|
||||||
features=None,
|
) = None,
|
||||||
language=None,
|
anchor: str | None = None,
|
||||||
stroke_width=0,
|
spacing: float = 4,
|
||||||
embedded_color=False,
|
align: str = "left",
|
||||||
|
direction: str | None = None,
|
||||||
|
features: list[str] | None = None,
|
||||||
|
language: str | None = None,
|
||||||
|
stroke_width: float = 0,
|
||||||
|
embedded_color: bool = False,
|
||||||
*,
|
*,
|
||||||
font_size=None,
|
font_size: float | None = None,
|
||||||
) -> tuple[int, int, int, int]:
|
) -> tuple[float, float, float, float]:
|
||||||
"""Get the bounding box of a given string, in pixels."""
|
"""Get the bounding box of a given string, in pixels."""
|
||||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||||
msg = "Embedded color supported only in RGB and RGBA modes"
|
msg = "Embedded color supported only in RGB and RGBA modes"
|
||||||
|
@ -862,20 +871,25 @@ class ImageDraw:
|
||||||
|
|
||||||
def multiline_textbbox(
|
def multiline_textbbox(
|
||||||
self,
|
self,
|
||||||
xy,
|
xy: tuple[float, float],
|
||||||
text,
|
text: AnyStr,
|
||||||
font=None,
|
font: (
|
||||||
anchor=None,
|
ImageFont.ImageFont
|
||||||
spacing=4,
|
| ImageFont.FreeTypeFont
|
||||||
align="left",
|
| ImageFont.TransposedFont
|
||||||
direction=None,
|
| None
|
||||||
features=None,
|
) = None,
|
||||||
language=None,
|
anchor: str | None = None,
|
||||||
stroke_width=0,
|
spacing: float = 4,
|
||||||
embedded_color=False,
|
align: str = "left",
|
||||||
|
direction: str | None = None,
|
||||||
|
features: list[str] | None = None,
|
||||||
|
language: str | None = None,
|
||||||
|
stroke_width: float = 0,
|
||||||
|
embedded_color: bool = False,
|
||||||
*,
|
*,
|
||||||
font_size=None,
|
font_size: float | None = None,
|
||||||
) -> tuple[int, int, int, int]:
|
) -> tuple[float, float, float, float]:
|
||||||
if direction == "ttb":
|
if direction == "ttb":
|
||||||
msg = "ttb direction is unsupported for multiline text"
|
msg = "ttb direction is unsupported for multiline text"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
@ -914,7 +928,7 @@ class ImageDraw:
|
||||||
elif anchor[1] == "d":
|
elif anchor[1] == "d":
|
||||||
top -= (len(lines) - 1) * line_spacing
|
top -= (len(lines) - 1) * line_spacing
|
||||||
|
|
||||||
bbox: tuple[int, int, int, int] | None = None
|
bbox: tuple[float, float, float, float] | None = None
|
||||||
|
|
||||||
for idx, line in enumerate(lines):
|
for idx, line in enumerate(lines):
|
||||||
left = xy[0]
|
left = xy[0]
|
||||||
|
|
|
@ -34,7 +34,7 @@ import warnings
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import IO, TYPE_CHECKING, Any, BinaryIO
|
from typing import IO, TYPE_CHECKING, Any, BinaryIO, TypedDict
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._typing import StrOrBytesPath
|
from ._typing import StrOrBytesPath
|
||||||
|
@ -46,6 +46,13 @@ if TYPE_CHECKING:
|
||||||
from ._imagingft import Font
|
from ._imagingft import Font
|
||||||
|
|
||||||
|
|
||||||
|
class Axis(TypedDict):
|
||||||
|
minimum: int | None
|
||||||
|
default: int | None
|
||||||
|
maximum: int | None
|
||||||
|
name: bytes | None
|
||||||
|
|
||||||
|
|
||||||
class Layout(IntEnum):
|
class Layout(IntEnum):
|
||||||
BASIC = 0
|
BASIC = 0
|
||||||
RAQM = 1
|
RAQM = 1
|
||||||
|
@ -138,7 +145,9 @@ class ImageFont:
|
||||||
|
|
||||||
self.font = Image.core.font(image.im, data)
|
self.font = Image.core.font(image.im, data)
|
||||||
|
|
||||||
def getmask(self, text, mode="", *args, **kwargs):
|
def getmask(
|
||||||
|
self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
|
||||||
|
) -> Image.core.ImagingCore:
|
||||||
"""
|
"""
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
|
@ -236,7 +245,7 @@ class FreeTypeFont:
|
||||||
|
|
||||||
self.layout_engine = layout_engine
|
self.layout_engine = layout_engine
|
||||||
|
|
||||||
def load_from_bytes(f):
|
def load_from_bytes(f) -> None:
|
||||||
self.font_bytes = f.read()
|
self.font_bytes = f.read()
|
||||||
self.font = core.getfont(
|
self.font = core.getfont(
|
||||||
"", size, index, encoding, self.font_bytes, layout_engine
|
"", size, index, encoding, self.font_bytes, layout_engine
|
||||||
|
@ -283,7 +292,12 @@ class FreeTypeFont:
|
||||||
return self.font.ascent, self.font.descent
|
return self.font.ascent, self.font.descent
|
||||||
|
|
||||||
def getlength(
|
def getlength(
|
||||||
self, text: str | bytes, mode="", direction=None, features=None, language=None
|
self,
|
||||||
|
text: str | bytes,
|
||||||
|
mode: str = "",
|
||||||
|
direction: str | None = None,
|
||||||
|
features: list[str] | None = None,
|
||||||
|
language: str | None = None,
|
||||||
) -> float:
|
) -> 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
|
||||||
|
@ -424,16 +438,16 @@ class FreeTypeFont:
|
||||||
|
|
||||||
def getmask(
|
def getmask(
|
||||||
self,
|
self,
|
||||||
text,
|
text: str | bytes,
|
||||||
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,
|
||||||
ink=0,
|
ink: int = 0,
|
||||||
start=None,
|
start: tuple[float, float] | None = None,
|
||||||
):
|
) -> Image.core.ImagingCore:
|
||||||
"""
|
"""
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
|
@ -516,17 +530,17 @@ class FreeTypeFont:
|
||||||
def getmask2(
|
def getmask2(
|
||||||
self,
|
self,
|
||||||
text: str | bytes,
|
text: str | bytes,
|
||||||
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,
|
||||||
ink=0,
|
ink: int = 0,
|
||||||
start=None,
|
start: tuple[float, float] | None = None,
|
||||||
*args,
|
*args: Any,
|
||||||
**kwargs,
|
**kwargs: Any,
|
||||||
):
|
) -> tuple[Image.core.ImagingCore, tuple[int, int]]:
|
||||||
"""
|
"""
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
|
@ -599,7 +613,7 @@ class FreeTypeFont:
|
||||||
if start is None:
|
if start is None:
|
||||||
start = (0, 0)
|
start = (0, 0)
|
||||||
|
|
||||||
def fill(width, height):
|
def fill(width: int, height: int) -> Image.core.ImagingCore:
|
||||||
size = (width, height)
|
size = (width, height)
|
||||||
Image._decompression_bomb_check(size)
|
Image._decompression_bomb_check(size)
|
||||||
return Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
|
return Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
|
||||||
|
@ -619,8 +633,13 @@ class FreeTypeFont:
|
||||||
)
|
)
|
||||||
|
|
||||||
def font_variant(
|
def font_variant(
|
||||||
self, font=None, size=None, index=None, encoding=None, layout_engine=None
|
self,
|
||||||
):
|
font: StrOrBytesPath | BinaryIO | None = None,
|
||||||
|
size: float | None = None,
|
||||||
|
index: int | None = None,
|
||||||
|
encoding: str | None = None,
|
||||||
|
layout_engine: Layout | None = None,
|
||||||
|
) -> FreeTypeFont:
|
||||||
"""
|
"""
|
||||||
Create a copy of this FreeTypeFont object,
|
Create a copy of this FreeTypeFont object,
|
||||||
using any specified arguments to override the settings.
|
using any specified arguments to override the settings.
|
||||||
|
@ -655,7 +674,7 @@ class FreeTypeFont:
|
||||||
raise NotImplementedError(msg) from e
|
raise NotImplementedError(msg) from e
|
||||||
return [name.replace(b"\x00", b"") for name in names]
|
return [name.replace(b"\x00", b"") for name in names]
|
||||||
|
|
||||||
def set_variation_by_name(self, name):
|
def set_variation_by_name(self, name: str | bytes) -> None:
|
||||||
"""
|
"""
|
||||||
:param name: The name of the style.
|
:param name: The name of the style.
|
||||||
:exception OSError: If the font is not a variation font.
|
:exception OSError: If the font is not a variation font.
|
||||||
|
@ -674,7 +693,7 @@ class FreeTypeFont:
|
||||||
|
|
||||||
self.font.setvarname(index)
|
self.font.setvarname(index)
|
||||||
|
|
||||||
def get_variation_axes(self):
|
def get_variation_axes(self) -> list[Axis]:
|
||||||
"""
|
"""
|
||||||
:returns: A list of the axes in a variation font.
|
:returns: A list of the axes in a variation font.
|
||||||
:exception OSError: If the font is not a variation font.
|
:exception OSError: If the font is not a variation font.
|
||||||
|
@ -704,7 +723,9 @@ class FreeTypeFont:
|
||||||
class TransposedFont:
|
class TransposedFont:
|
||||||
"""Wrapper for writing rotated or mirrored text"""
|
"""Wrapper for writing rotated or mirrored text"""
|
||||||
|
|
||||||
def __init__(self, font, orientation=None):
|
def __init__(
|
||||||
|
self, font: ImageFont | FreeTypeFont, orientation: Image.Transpose | None = None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Wrapper that creates a transposed font from any existing font
|
Wrapper that creates a transposed font from any existing font
|
||||||
object.
|
object.
|
||||||
|
@ -718,13 +739,17 @@ class TransposedFont:
|
||||||
self.font = font
|
self.font = font
|
||||||
self.orientation = orientation # any 'transpose' argument, or None
|
self.orientation = orientation # any 'transpose' argument, or None
|
||||||
|
|
||||||
def getmask(self, text, mode="", *args, **kwargs):
|
def getmask(
|
||||||
|
self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
|
||||||
|
) -> Image.core.ImagingCore:
|
||||||
im = self.font.getmask(text, mode, *args, **kwargs)
|
im = self.font.getmask(text, mode, *args, **kwargs)
|
||||||
if self.orientation is not None:
|
if self.orientation is not None:
|
||||||
return im.transpose(self.orientation)
|
return im.transpose(self.orientation)
|
||||||
return im
|
return im
|
||||||
|
|
||||||
def getbbox(self, text, *args, **kwargs):
|
def getbbox(
|
||||||
|
self, text: str | bytes, *args: Any, **kwargs: Any
|
||||||
|
) -> tuple[int, int, float, float]:
|
||||||
# TransposedFont doesn't support getmask2, move top-left point to (0, 0)
|
# TransposedFont doesn't support getmask2, move top-left point to (0, 0)
|
||||||
# this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
|
# this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
|
||||||
left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
|
left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
|
||||||
|
@ -734,7 +759,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: str | bytes, *args, **kwargs) -> float:
|
def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> 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)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
class ImagingCore:
|
class ImagingCore:
|
||||||
|
def __getitem__(self, index: int) -> float: ...
|
||||||
def __getattr__(self, name: str) -> Any: ...
|
def __getattr__(self, name: str) -> Any: ...
|
||||||
|
|
||||||
class ImagingFont:
|
class ImagingFont:
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
from typing import Any, TypedDict
|
from typing import Any, Callable
|
||||||
|
|
||||||
from . import _imaging
|
from . import ImageFont, _imaging
|
||||||
|
|
||||||
class _Axis(TypedDict):
|
|
||||||
minimum: int | None
|
|
||||||
default: int | None
|
|
||||||
maximum: int | None
|
|
||||||
name: bytes | None
|
|
||||||
|
|
||||||
class Font:
|
class Font:
|
||||||
@property
|
@property
|
||||||
|
@ -28,42 +22,48 @@ class Font:
|
||||||
def render(
|
def render(
|
||||||
self,
|
self,
|
||||||
string: str | bytes,
|
string: str | bytes,
|
||||||
fill,
|
fill: Callable[[int, int], _imaging.ImagingCore],
|
||||||
mode=...,
|
mode: str,
|
||||||
dir=...,
|
dir: str | None,
|
||||||
features=...,
|
features: list[str] | None,
|
||||||
lang=...,
|
lang: str | None,
|
||||||
stroke_width=...,
|
stroke_width: float,
|
||||||
anchor=...,
|
anchor: str | None,
|
||||||
foreground_ink_long=...,
|
foreground_ink_long: int,
|
||||||
x_start=...,
|
x_start: float,
|
||||||
y_start=...,
|
y_start: float,
|
||||||
/,
|
/,
|
||||||
) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
|
) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
|
||||||
def getsize(
|
def getsize(
|
||||||
self,
|
self,
|
||||||
string: str | bytes | bytearray,
|
string: str | bytes | bytearray,
|
||||||
mode=...,
|
mode: str,
|
||||||
dir=...,
|
dir: str | None,
|
||||||
features=...,
|
features: list[str] | None,
|
||||||
lang=...,
|
lang: str | None,
|
||||||
anchor=...,
|
anchor: str | None,
|
||||||
/,
|
/,
|
||||||
) -> tuple[tuple[int, int], tuple[int, int]]: ...
|
) -> tuple[tuple[int, int], tuple[int, int]]: ...
|
||||||
def getlength(
|
def getlength(
|
||||||
self, string: str | bytes, mode=..., dir=..., features=..., lang=..., /
|
self,
|
||||||
|
string: str | bytes,
|
||||||
|
mode: str,
|
||||||
|
dir: str | None,
|
||||||
|
features: list[str] | None,
|
||||||
|
lang: str | None,
|
||||||
|
/,
|
||||||
) -> float: ...
|
) -> float: ...
|
||||||
def getvarnames(self) -> list[bytes]: ...
|
def getvarnames(self) -> list[bytes]: ...
|
||||||
def getvaraxes(self) -> list[_Axis] | None: ...
|
def getvaraxes(self) -> list[ImageFont.Axis]: ...
|
||||||
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,
|
filename: str | bytes,
|
||||||
size: float,
|
size: float,
|
||||||
index=...,
|
index: int,
|
||||||
encoding=...,
|
encoding: str,
|
||||||
font_bytes=...,
|
font_bytes: bytes,
|
||||||
layout_engine=...,
|
layout_engine: int,
|
||||||
) -> Font: ...
|
) -> Font: ...
|
||||||
def __getattr__(name: str) -> Any: ...
|
def __getattr__(name: str) -> Any: ...
|
||||||
|
|
Loading…
Reference in New Issue
Block a user