diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 6a0a940b9..f195e9aaf 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -14,7 +14,7 @@ import pytest from packaging.version import parse as parse_version from PIL import Image, ImageDraw, ImageFont, features -from PIL._typing import StrOrBytesPath +from PIL._typing import Anchor, StrOrBytesPath from .helper import ( assert_image_equal, @@ -257,7 +257,7 @@ def test_render_multiline_text(font: ImageFont.FreeTypeFont) -> None: "align, ext", (("left", ""), ("center", "_center"), ("right", "_right")) ) def test_render_multiline_text_align( - font: ImageFont.FreeTypeFont, align: str, ext: str + font: ImageFont.FreeTypeFont, align: ImageDraw.Align, ext: str ) -> None: im = Image.new(mode="RGB", size=(300, 100)) draw = ImageDraw.Draw(im) @@ -272,7 +272,7 @@ def test_unknown_align(font: ImageFont.FreeTypeFont) -> None: # Act/Assert 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: @@ -795,7 +795,7 @@ def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None: ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"), ) def test_anchor( - layout_engine: ImageFont.Layout, anchor: str, left: int, top: int + layout_engine: ImageFont.Layout, anchor: Anchor, left: int, top: int ) -> None: name, text = "quick", "Quick" path = f"Tests/images/test_anchor_{name}_{anchor}.png" @@ -842,7 +842,7 @@ def test_anchor( ), ) def test_anchor_multiline( - layout_engine: ImageFont.Layout, anchor: str, align: str + layout_engine: ImageFont.Layout, anchor: Anchor, align: ImageDraw.Align ) -> None: target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png" 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"]: with pytest.raises(ValueError): - font.getmask2("hello", anchor=anchor) + font.getmask2("hello", anchor=anchor) # type: ignore[arg-type] with pytest.raises(ValueError): - font.getbbox("hello", anchor=anchor) + font.getbbox("hello", anchor=anchor) # type: ignore[arg-type] 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): - d.textbbox((0, 0), "hello", anchor=anchor) + d.textbbox((0, 0), "hello", anchor=anchor) # type: ignore[arg-type] 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): - d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor) - for anchor in ["lt", "lb"]: + d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor) # type: ignore[arg-type] + + anchors: list[Anchor] = ["lt", "lb"] + for anchor2 in anchors: 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): - 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)) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index 24c7b871a..f6a551381 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -3,6 +3,7 @@ from __future__ import annotations import pytest from PIL import Image, ImageDraw, ImageFont +from PIL._typing import Anchor, Direction from .helper import assert_image_similar_tofile, skip_unless_feature @@ -216,7 +217,7 @@ def test_getlength( d = ImageDraw.Draw(im) try: - assert d.textlength(text, ttf, direction) == expected + assert d.textlength(text, ttf, direction) == expected # type: ignore[arg-type] except ValueError as ex: if ( direction == "ttb" @@ -232,7 +233,7 @@ def test_getlength( ("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"), 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: Direction, text: str) -> None: if text == "i\u0305i" and direction == "ttb": pytest.skip("fails with this font") @@ -252,7 +253,7 @@ def test_getlength_combine(mode: str, direction: str, text: str) -> None: @pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) -def test_anchor_ttb(anchor: str) -> None: +def test_anchor_ttb(anchor: Anchor) -> None: text = "f" path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120) @@ -309,7 +310,7 @@ combine_tests = ( "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests] ) def test_combine( - name: str, text: str, dir: str | None, anchor: str | None, epsilon: float + name: str, text: str, anchor: Anchor | None, dir: Direction | None, epsilon: float ) -> None: path = f"Tests/images/test_combine_{name}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) @@ -341,7 +342,7 @@ def test_combine( ("rm", "right"), # pass with getsize ), ) -def test_combine_multiline(anchor: str, align: str) -> None: +def test_combine_multiline(anchor: Anchor, align: ImageDraw.Align) -> None: # test that multiline text uses getlength, not getsize or getbbox path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png" @@ -367,17 +368,17 @@ def test_anchor_invalid_ttb() -> None: for anchor in ["", "l", "a", "lax", "xa", "la", "ls", "ld", "lx"]: 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): - font.getbbox("hello", anchor=anchor, direction="ttb") + font.getbbox("hello", anchor=anchor, direction="ttb") # type: ignore[arg-type] 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): - 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): - 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): - 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 with pytest.raises(ValueError): d.multiline_text((0, 0), "foo\nbar", anchor="mm", direction="ttb") diff --git a/docs/example/anchors.py b/docs/example/anchors.py index 2ee11103f..23a6e933a 100644 --- a/docs/example/anchors.py +++ b/docs/example/anchors.py @@ -1,11 +1,12 @@ from __future__ import annotations from PIL import Image, ImageDraw, ImageFont +from PIL._typing import Anchor font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 16) -def test(anchor: str) -> Image.Image: +def test(anchor: Anchor) -> Image.Image: im = Image.new("RGBA", (200, 100), "white") d = ImageDraw.Draw(im) d.line(((100, 0), (100, 100)), "gray") @@ -17,9 +18,12 @@ def test(anchor: str) -> Image.Image: if __name__ == "__main__": im = Image.new("RGBA", (600, 300), "white") d = ImageDraw.Draw(im) - for y, row in enumerate( - (("ma", "mt", "mm"), ("ms", "mb", "md"), ("ls", "ms", "rs")) - ): + anchors: list[list[Anchor]] = [ + ["ma", "mt", "mm"], + ["ms", "mb", "md"], + ["ls", "ms", "rs"], + ] + for y, row in enumerate(anchors): for x, anchor in enumerate(row): im.paste(test(anchor), (x * 200, y * 100)) if x != 0: diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 1012dcbc0..d574ce2ea 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -39,7 +39,7 @@ from typing import TYPE_CHECKING, Any, AnyStr, Callable, Literal, Union, cast from . import Image, ImageColor from ._deprecate import deprecate -from ._typing import Align, Anchor, Coords, Direction +from ._typing import Anchor, Coords, Direction # experimental access to the outline API Outline: Callable[[], Image.core._Outline] | None @@ -51,6 +51,8 @@ except AttributeError: if TYPE_CHECKING: from . import ImageDraw2, ImageFont + Align = Literal["left", "center", "right"] + _Ink = Union[float, tuple[int, ...], str] """ diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index f1889f07e..55af3819b 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -76,8 +76,6 @@ Anchor = Literal[ "sd", ] -Align = Literal["left", "center", "right"] - Direction = Literal["rtl", "ltr", "ttb"] __all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]