Merge pull request #1 from radarhere/typing

Move type hints and repair tests
This commit is contained in:
AsfhtgkDavid 2025-01-26 15:57:59 +02:00 committed by GitHub
commit 09a9ee83cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 99 additions and 82 deletions

View File

@ -257,7 +257,7 @@ def test_render_multiline_text(font: ImageFont.FreeTypeFont) -> None:
"align, ext", (("left", ""), ("center", "_center"), ("right", "_right")) "align, ext", (("left", ""), ("center", "_center"), ("right", "_right"))
) )
def test_render_multiline_text_align( def test_render_multiline_text_align(
font: ImageFont.FreeTypeFont, align: str, ext: str font: ImageFont.FreeTypeFont, align: ImageDraw.Align, ext: str
) -> None: ) -> None:
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
@ -272,7 +272,7 @@ def test_unknown_align(font: ImageFont.FreeTypeFont) -> None:
# Act/Assert # Act/Assert
with pytest.raises(ValueError): with pytest.raises(ValueError):
draw.multiline_text((0, 0), TEST_TEXT, font=font, align="unknown") draw.multiline_text((0, 0), TEST_TEXT, font=font, align="unknown") # type: ignore[arg-type]
def test_draw_align(font: ImageFont.FreeTypeFont) -> None: def test_draw_align(font: ImageFont.FreeTypeFont) -> None:
@ -795,7 +795,7 @@ def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None:
ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"), ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"),
) )
def test_anchor( def test_anchor(
layout_engine: ImageFont.Layout, anchor: str, left: int, top: int layout_engine: ImageFont.Layout, anchor: ImageFont.Anchor, left: int, top: int
) -> None: ) -> None:
name, text = "quick", "Quick" name, text = "quick", "Quick"
path = f"Tests/images/test_anchor_{name}_{anchor}.png" path = f"Tests/images/test_anchor_{name}_{anchor}.png"
@ -842,7 +842,7 @@ def test_anchor(
), ),
) )
def test_anchor_multiline( def test_anchor_multiline(
layout_engine: ImageFont.Layout, anchor: str, align: str layout_engine: ImageFont.Layout, anchor: ImageFont.Anchor, align: ImageDraw.Align
) -> None: ) -> None:
target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png" target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png"
text = "a\nlong\ntext sample" text = "a\nlong\ntext sample"
@ -868,22 +868,24 @@ def test_anchor_invalid(font: ImageFont.FreeTypeFont) -> None:
for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]: for anchor in ["", "l", "a", "lax", "sa", "xa", "lx"]:
with pytest.raises(ValueError): with pytest.raises(ValueError):
font.getmask2("hello", anchor=anchor) font.getmask2("hello", anchor=anchor) # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
font.getbbox("hello", anchor=anchor) font.getbbox("hello", anchor=anchor) # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.text((0, 0), "hello", anchor=anchor) d.text((0, 0), "hello", anchor=anchor) # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.textbbox((0, 0), "hello", anchor=anchor) d.textbbox((0, 0), "hello", anchor=anchor) # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor) d.multiline_text((0, 0), "foo\nbar", anchor=anchor) # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor) d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor) # type: ignore[arg-type]
for anchor in ["lt", "lb"]:
anchors: list[ImageFont.Anchor] = ["lt", "lb"]
for anchor2 in anchors:
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor) d.multiline_text((0, 0), "foo\nbar", anchor=anchor2)
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor) d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor2)
@pytest.mark.parametrize("bpp", (1, 2, 4, 8)) @pytest.mark.parametrize("bpp", (1, 2, 4, 8))

View File

@ -216,7 +216,7 @@ def test_getlength(
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
try: try:
assert d.textlength(text, ttf, direction) == expected assert d.textlength(text, ttf, direction) == expected # type: ignore[arg-type]
except ValueError as ex: except ValueError as ex:
if ( if (
direction == "ttb" direction == "ttb"
@ -232,7 +232,9 @@ def test_getlength(
("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"), ("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"),
ids=("caron-above", "caron-below", "double-breve", "overline"), ids=("caron-above", "caron-below", "double-breve", "overline"),
) )
def test_getlength_combine(mode: str, direction: str, text: str) -> None: def test_getlength_combine(
mode: str, direction: ImageFont.Direction, text: str
) -> None:
if text == "i\u0305i" and direction == "ttb": if text == "i\u0305i" and direction == "ttb":
pytest.skip("fails with this font") pytest.skip("fails with this font")
@ -252,7 +254,7 @@ def test_getlength_combine(mode: str, direction: str, text: str) -> None:
@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) @pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm"))
def test_anchor_ttb(anchor: str) -> None: def test_anchor_ttb(anchor: ImageFont.Anchor) -> None:
text = "f" text = "f"
path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png" path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png"
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120) f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120)
@ -309,7 +311,11 @@ combine_tests = (
"name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests] "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests]
) )
def test_combine( def test_combine(
name: str, text: str, dir: str | None, anchor: str | None, epsilon: float name: str,
text: str,
anchor: ImageFont.Anchor | None,
dir: ImageFont.Direction | None,
epsilon: float,
) -> None: ) -> None:
path = f"Tests/images/test_combine_{name}.png" path = f"Tests/images/test_combine_{name}.png"
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
@ -341,7 +347,7 @@ def test_combine(
("rm", "right"), # pass with getsize ("rm", "right"), # pass with getsize
), ),
) )
def test_combine_multiline(anchor: str, align: str) -> None: def test_combine_multiline(anchor: ImageFont.Anchor, align: ImageDraw.Align) -> None:
# test that multiline text uses getlength, not getsize or getbbox # test that multiline text uses getlength, not getsize or getbbox
path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png" path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png"
@ -367,17 +373,17 @@ def test_anchor_invalid_ttb() -> None:
for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]: for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]:
with pytest.raises(ValueError): with pytest.raises(ValueError):
font.getmask2("hello", anchor=anchor, direction="ttb") font.getmask2("hello", anchor=anchor, direction="ttb") # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
font.getbbox("hello", anchor=anchor, direction="ttb") font.getbbox("hello", anchor=anchor, direction="ttb") # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.text((0, 0), "hello", anchor=anchor, direction="ttb") d.text((0, 0), "hello", anchor=anchor, direction="ttb") # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb") d.textbbox((0, 0), "hello", anchor=anchor, direction="ttb") # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb") d.multiline_text((0, 0), "foo\nbar", anchor=anchor, direction="ttb") # type: ignore[arg-type]
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb") d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor, direction="ttb") # type: ignore[arg-type]
# ttb multiline text does not support anchors at all # ttb multiline text does not support anchors at all
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb") d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb")

View File

@ -5,7 +5,7 @@ from PIL import Image, ImageDraw, ImageFont
font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 16) font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 16)
def test(anchor: str) -> Image.Image: def test(anchor: ImageFont.Anchor) -> Image.Image:
im = Image.new("RGBA", (200, 100), "white") im = Image.new("RGBA", (200, 100), "white")
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
d.line(((100, 0), (100, 100)), "gray") d.line(((100, 0), (100, 100)), "gray")
@ -17,9 +17,12 @@ def test(anchor: str) -> Image.Image:
if __name__ == "__main__": if __name__ == "__main__":
im = Image.new("RGBA", (600, 300), "white") im = Image.new("RGBA", (600, 300), "white")
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
for y, row in enumerate( anchors: list[list[ImageFont.Anchor]] = [
(("ma", "mt", "mm"), ("ms", "mb", "md"), ("ls", "ms", "rs")) ["ma", "mt", "mm"],
): ["ms", "mb", "md"],
["ls", "ms", "rs"],
]
for y, row in enumerate(anchors):
for x, anchor in enumerate(row): for x, anchor in enumerate(row):
im.paste(test(anchor), (x * 200, y * 100)) im.paste(test(anchor), (x * 200, y * 100))
if x != 0: if x != 0:

View File

@ -92,6 +92,11 @@ Constants
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``.
.. class:: Anchor
Type hint literal with the possible anchor values. See :ref:`text-anchors` for
details.
Dictionaries Dictionaries
------------ ------------

View File

@ -39,7 +39,7 @@ from typing import TYPE_CHECKING, Any, AnyStr, Callable, Literal, Union, cast
from . import Image, ImageColor from . import Image, ImageColor
from ._deprecate import deprecate from ._deprecate import deprecate
from ._typing import Align, Anchor, Coords, Direction from ._typing import Coords
# experimental access to the outline API # experimental access to the outline API
Outline: Callable[[], Image.core._Outline] | None Outline: Callable[[], Image.core._Outline] | None
@ -51,6 +51,8 @@ except AttributeError:
if TYPE_CHECKING: if TYPE_CHECKING:
from . import ImageDraw2, ImageFont from . import ImageDraw2, ImageFont
Align = Literal["left", "center", "right"]
_Ink = Union[float, tuple[int, ...], str] _Ink = Union[float, tuple[int, ...], str]
""" """
@ -583,10 +585,10 @@ class ImageDraw:
| ImageFont.TransposedFont | ImageFont.TransposedFont
| None | None
) = None, ) = None,
anchor: Anchor | None = None, anchor: ImageFont.Anchor | None = None,
spacing: float = 4, spacing: float = 4,
align: Align = "left", align: Align = "left",
direction: Direction | None = None, direction: ImageFont.Direction | None = None,
features: list[str] | None = None, features: list[str] | None = None,
language: str | None = None, language: str | None = None,
stroke_width: float = 0, stroke_width: float = 0,
@ -708,10 +710,10 @@ class ImageDraw:
| ImageFont.TransposedFont | ImageFont.TransposedFont
| None | None
) = None, ) = None,
anchor: Anchor | None = None, anchor: ImageFont.Anchor | None = None,
spacing: float = 4, spacing: float = 4,
align: Align = "left", align: Align = "left",
direction: Direction | None = None, direction: ImageFont.Direction | None = None,
features: list[str] | None = None, features: list[str] | None = None,
language: str | None = None, language: str | None = None,
stroke_width: float = 0, stroke_width: float = 0,
@ -798,7 +800,7 @@ class ImageDraw:
| ImageFont.TransposedFont | ImageFont.TransposedFont
| None | None
) = None, ) = None,
direction: Direction | None = None, direction: ImageFont.Direction | None = None,
features: list[str] | None = None, features: list[str] | None = None,
language: str | None = None, language: str | None = None,
embedded_color: bool = False, embedded_color: bool = False,
@ -828,10 +830,10 @@ class ImageDraw:
| ImageFont.TransposedFont | ImageFont.TransposedFont
| None | None
) = None, ) = None,
anchor: Anchor | None = None, anchor: ImageFont.Anchor | None = None,
spacing: float = 4, spacing: float = 4,
align: Align = "left", align: Align = "left",
direction: Direction | None = None, direction: ImageFont.Direction | None = None,
features: list[str] | None = None, features: list[str] | None = None,
language: str | None = None, language: str | None = None,
stroke_width: float = 0, stroke_width: float = 0,
@ -878,10 +880,10 @@ class ImageDraw:
| ImageFont.TransposedFont | ImageFont.TransposedFont
| None | None
) = None, ) = None,
anchor: Anchor | None = None, anchor: ImageFont.Anchor | None = None,
spacing: float = 4, spacing: float = 4,
align: Align = "left", align: Align = "left",
direction: Direction | None = None, direction: ImageFont.Direction | None = None,
features: list[str] | None = None, features: list[str] | None = None,
language: str | None = None, language: str | None = None,
stroke_width: float = 0, stroke_width: float = 0,

View File

@ -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, TypedDict, cast from typing import IO, TYPE_CHECKING, Any, BinaryIO, Literal, TypedDict, cast
from . import Image, features from . import Image, features
from ._typing import StrOrBytesPath from ._typing import StrOrBytesPath
@ -45,6 +45,35 @@ if TYPE_CHECKING:
from ._imaging import ImagingFont from ._imaging import ImagingFont
from ._imagingft import Font from ._imagingft import Font
Anchor = Literal[
"la",
"lt",
"lm",
"ls",
"lb",
"ld",
"ma",
"mt",
"mm",
"ms",
"mb",
"md",
"ra",
"rt",
"rm",
"rs",
"rb",
"rd",
"sa",
"st",
"sm",
"ss",
"sb",
"sd",
]
Direction = Literal["rtl", "ltr", "ttb"]
class Axis(TypedDict): class Axis(TypedDict):
minimum: int | None minimum: int | None
@ -313,7 +342,7 @@ class FreeTypeFont:
self, self,
text: str | bytes, text: str | bytes,
mode: str = "", mode: str = "",
direction: str | None = None, direction: Direction | None = None,
features: list[str] | None = None, features: list[str] | None = None,
language: str | None = None, language: str | None = None,
) -> float: ) -> float:
@ -392,11 +421,11 @@ class FreeTypeFont:
self, self,
text: str | bytes, text: str | bytes,
mode: str = "", mode: str = "",
direction: str | None = None, direction: Direction | None = None,
features: list[str] | None = None, features: list[str] | None = None,
language: str | None = None, language: str | None = None,
stroke_width: float = 0, stroke_width: float = 0,
anchor: str | None = None, anchor: Anchor | None = None,
) -> tuple[float, float, float, float]: ) -> 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
@ -458,11 +487,11 @@ class FreeTypeFont:
self, self,
text: str | bytes, text: str | bytes,
mode: str = "", mode: str = "",
direction: str | None = None, direction: Direction | None = None,
features: list[str] | None = None, features: list[str] | None = None,
language: str | None = None, language: str | None = None,
stroke_width: float = 0, stroke_width: float = 0,
anchor: str | None = None, anchor: Anchor | None = None,
ink: int = 0, ink: int = 0,
start: tuple[float, float] | None = None, start: tuple[float, float] | None = None,
) -> Image.core.ImagingCore: ) -> Image.core.ImagingCore:
@ -549,11 +578,11 @@ class FreeTypeFont:
self, self,
text: str | bytes, text: str | bytes,
mode: str = "", mode: str = "",
direction: str | None = None, direction: Direction | None = None,
features: list[str] | None = None, features: list[str] | None = None,
language: str | None = None, language: str | None = None,
stroke_width: float = 0, stroke_width: float = 0,
anchor: str | None = None, anchor: Anchor | None = None,
ink: int = 0, ink: int = 0,
start: tuple[float, float] | None = None, start: tuple[float, float] | None = None,
*args: Any, *args: Any,

View File

@ -24,11 +24,11 @@ class Font:
string: str | bytes, string: str | bytes,
fill: Callable[[int, int], _imaging.ImagingCore], fill: Callable[[int, int], _imaging.ImagingCore],
mode: str, mode: str,
dir: str | None, dir: ImageFont.Direction | None,
features: list[str] | None, features: list[str] | None,
lang: str | None, lang: str | None,
stroke_width: float, stroke_width: float,
anchor: str | None, anchor: ImageFont.Anchor | None,
foreground_ink_long: int, foreground_ink_long: int,
x_start: float, x_start: float,
y_start: float, y_start: float,
@ -38,17 +38,17 @@ class Font:
self, self,
string: str | bytes | bytearray, string: str | bytes | bytearray,
mode: str, mode: str,
dir: str | None, dir: ImageFont.Direction | None,
features: list[str] | None, features: list[str] | None,
lang: str | None, lang: str | None,
anchor: str | None, anchor: ImageFont.Anchor | None,
/, /,
) -> tuple[tuple[int, int], tuple[int, int]]: ... ) -> tuple[tuple[int, int], tuple[int, int]]: ...
def getlength( def getlength(
self, self,
string: str | bytes, string: str | bytes,
mode: str, mode: str,
dir: str | None, dir: ImageFont.Direction | None,
features: list[str] | None, features: list[str] | None,
lang: str | None, lang: str | None,
/, /,

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import os import os
import sys import sys
from collections.abc import Sequence from collections.abc import Sequence
from typing import TYPE_CHECKING, Any, Literal, Protocol, TypeVar, Union from typing import TYPE_CHECKING, Any, Protocol, TypeVar, Union
if TYPE_CHECKING: if TYPE_CHECKING:
from numbers import _IntegralLike as IntegralLike from numbers import _IntegralLike as IntegralLike
@ -49,34 +49,4 @@ class SupportsRead(Protocol[_T_co]):
StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]
Anchor = Literal[
"la",
"lt",
"lm",
"ls",
"lb",
"ld",
"ma",
"mt",
"mm",
"ms",
"mb",
"md",
"ra",
"rt",
"rm",
"rs",
"rb",
"rd",
"sa",
"st",
"ss",
"sb",
"sd",
]
Align = Literal["left", "center", "right"]
Direction = Literal["rtl", "ltr", "ttb"]
__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"] __all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]