From eed59263eb4dedb917de444717c3c154908f2582 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 26 Jan 2025 15:16:51 +1100 Subject: [PATCH] Moved Anchor and Direction into ImageFont --- Tests/test_imagefont.py | 8 ++++---- Tests/test_imagefontctl.py | 15 ++++++++++----- docs/example/anchors.py | 5 ++--- docs/reference/ImageFont.rst | 5 +++++ src/PIL/ImageDraw.py | 20 ++++++++++---------- src/PIL/ImageFont.py | 33 +++++++++++++++++++++++++++++++-- src/PIL/_imagingft.pyi | 11 +++++------ src/PIL/_typing.py | 31 +------------------------------ 8 files changed, 68 insertions(+), 60 deletions(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index f195e9aaf..75413aed8 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 Anchor, StrOrBytesPath +from PIL._typing import StrOrBytesPath from .helper import ( assert_image_equal, @@ -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: Anchor, left: int, top: int + layout_engine: ImageFont.Layout, anchor: ImageFont.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: Anchor, align: ImageDraw.Align + layout_engine: ImageFont.Layout, anchor: ImageFont.Anchor, align: ImageDraw.Align ) -> None: target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png" text = "a\nlong\ntext sample" @@ -880,7 +880,7 @@ def test_anchor_invalid(font: ImageFont.FreeTypeFont) -> None: with pytest.raises(ValueError): d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor) # type: ignore[arg-type] - anchors: list[Anchor] = ["lt", "lb"] + anchors: list[ImageFont.Anchor] = ["lt", "lb"] for anchor2 in anchors: with pytest.raises(ValueError): d.multiline_text((0, 0), "foo\nbar", anchor=anchor2) diff --git a/Tests/test_imagefontctl.py b/Tests/test_imagefontctl.py index f6a551381..a48cafbbf 100644 --- a/Tests/test_imagefontctl.py +++ b/Tests/test_imagefontctl.py @@ -3,7 +3,6 @@ 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 @@ -233,7 +232,9 @@ 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: Direction, text: str) -> None: +def test_getlength_combine( + mode: str, direction: ImageFont.Direction, text: str +) -> None: if text == "i\u0305i" and direction == "ttb": pytest.skip("fails with this font") @@ -253,7 +254,7 @@ def test_getlength_combine(mode: str, direction: Direction, text: str) -> None: @pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) -def test_anchor_ttb(anchor: Anchor) -> None: +def test_anchor_ttb(anchor: ImageFont.Anchor) -> None: text = "f" path = f"Tests/images/test_anchor_ttb_{text}_{anchor}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 120) @@ -310,7 +311,11 @@ combine_tests = ( "name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests] ) def test_combine( - name: str, text: str, anchor: Anchor | None, dir: Direction | None, epsilon: float + name: str, + text: str, + anchor: ImageFont.Anchor | None, + dir: ImageFont.Direction | None, + epsilon: float, ) -> None: path = f"Tests/images/test_combine_{name}.png" f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) @@ -342,7 +347,7 @@ def test_combine( ("rm", "right"), # pass with getsize ), ) -def test_combine_multiline(anchor: Anchor, align: ImageDraw.Align) -> None: +def test_combine_multiline(anchor: ImageFont.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" diff --git a/docs/example/anchors.py b/docs/example/anchors.py index 23a6e933a..d4d4f3c8f 100644 --- a/docs/example/anchors.py +++ b/docs/example/anchors.py @@ -1,12 +1,11 @@ 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: Anchor) -> Image.Image: +def test(anchor: ImageFont.Anchor) -> Image.Image: im = Image.new("RGBA", (200, 100), "white") d = ImageDraw.Draw(im) d.line(((100, 0), (100, 100)), "gray") @@ -18,7 +17,7 @@ def test(anchor: Anchor) -> Image.Image: if __name__ == "__main__": im = Image.new("RGBA", (600, 300), "white") d = ImageDraw.Draw(im) - anchors: list[list[Anchor]] = [ + anchors: list[list[ImageFont.Anchor]] = [ ["ma", "mt", "mm"], ["ms", "mb", "md"], ["ls", "ms", "rs"], diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index d9d9cac6e..98c9a2386 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -92,6 +92,11 @@ Constants 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``. +.. class:: Anchor + + Type hint literal with the possible anchor values. See :ref:`text-anchors` for + details. + Dictionaries ------------ diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index d574ce2ea..e86ddd7b8 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 Anchor, Coords, Direction +from ._typing import Coords # experimental access to the outline API Outline: Callable[[], Image.core._Outline] | None @@ -585,10 +585,10 @@ class ImageDraw: | ImageFont.TransposedFont | None ) = None, - anchor: Anchor | None = None, + anchor: ImageFont.Anchor | None = None, spacing: float = 4, align: Align = "left", - direction: Direction | None = None, + direction: ImageFont.Direction | None = None, features: list[str] | None = None, language: str | None = None, stroke_width: float = 0, @@ -710,10 +710,10 @@ class ImageDraw: | ImageFont.TransposedFont | None ) = None, - anchor: Anchor | None = None, + anchor: ImageFont.Anchor | None = None, spacing: float = 4, align: Align = "left", - direction: Direction | None = None, + direction: ImageFont.Direction | None = None, features: list[str] | None = None, language: str | None = None, stroke_width: float = 0, @@ -800,7 +800,7 @@ class ImageDraw: | ImageFont.TransposedFont | None ) = None, - direction: Direction | None = None, + direction: ImageFont.Direction | None = None, features: list[str] | None = None, language: str | None = None, embedded_color: bool = False, @@ -830,10 +830,10 @@ class ImageDraw: | ImageFont.TransposedFont | None ) = None, - anchor: Anchor | None = None, + anchor: ImageFont.Anchor | None = None, spacing: float = 4, align: Align = "left", - direction: Direction | None = None, + direction: ImageFont.Direction | None = None, features: list[str] | None = None, language: str | None = None, stroke_width: float = 0, @@ -880,10 +880,10 @@ class ImageDraw: | ImageFont.TransposedFont | None ) = None, - anchor: Anchor | None = None, + anchor: ImageFont.Anchor | None = None, spacing: float = 4, align: Align = "left", - direction: Direction | None = None, + direction: ImageFont.Direction | None = None, features: list[str] | None = None, language: str | None = None, stroke_width: float = 0, diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 46a620271..48462e2c1 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -34,10 +34,10 @@ import warnings from enum import IntEnum from io import BytesIO 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 ._typing import Anchor, Direction, StrOrBytesPath +from ._typing import StrOrBytesPath from ._util import DeferredError, is_path if TYPE_CHECKING: @@ -45,6 +45,35 @@ if TYPE_CHECKING: from ._imaging import ImagingFont 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): minimum: int | None diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi index a33df7621..e32deec2e 100644 --- a/src/PIL/_imagingft.pyi +++ b/src/PIL/_imagingft.pyi @@ -1,7 +1,6 @@ from typing import Any, Callable from . import ImageFont, _imaging -from ._typing import Anchor, Direction class Font: @property @@ -25,11 +24,11 @@ class Font: string: str | bytes, fill: Callable[[int, int], _imaging.ImagingCore], mode: str, - dir: Direction | None, + dir: ImageFont.Direction | None, features: list[str] | None, lang: str | None, stroke_width: float, - anchor: Anchor | None, + anchor: ImageFont.Anchor | None, foreground_ink_long: int, x_start: float, y_start: float, @@ -39,17 +38,17 @@ class Font: self, string: str | bytes | bytearray, mode: str, - dir: Direction | None, + dir: ImageFont.Direction | None, features: list[str] | None, lang: str | None, - anchor: Anchor | None, + anchor: ImageFont.Anchor | None, /, ) -> tuple[tuple[int, int], tuple[int, int]]: ... def getlength( self, string: str | bytes, mode: str, - dir: Direction | None, + dir: ImageFont.Direction | None, features: list[str] | None, lang: str | None, /, diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 55af3819b..d5955ac52 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -3,7 +3,7 @@ from __future__ import annotations import os import sys 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: from numbers import _IntegralLike as IntegralLike @@ -49,33 +49,4 @@ class SupportsRead(Protocol[_T_co]): 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", - "sm", - "ss", - "sb", - "sd", -] - -Direction = Literal["rtl", "ltr", "ttb"] - __all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]