Moved Anchor and Direction into ImageFont

This commit is contained in:
Andrew Murray 2025-01-26 15:16:51 +11:00
parent 0e0515ce95
commit eed59263eb
8 changed files with 68 additions and 60 deletions

View File

@ -14,7 +14,7 @@ import pytest
from packaging.version import parse as parse_version from packaging.version import parse as parse_version
from PIL import Image, ImageDraw, ImageFont, features from PIL import Image, ImageDraw, ImageFont, features
from PIL._typing import Anchor, StrOrBytesPath from PIL._typing import StrOrBytesPath
from .helper import ( from .helper import (
assert_image_equal, 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"), ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"),
) )
def test_anchor( 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: ) -> 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: Anchor, align: ImageDraw.Align 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"
@ -880,7 +880,7 @@ def test_anchor_invalid(font: ImageFont.FreeTypeFont) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.multiline_textbbox((0, 0), "foo\nbar", anchor=anchor) # type: ignore[arg-type] 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: for anchor2 in anchors:
with pytest.raises(ValueError): with pytest.raises(ValueError):
d.multiline_text((0, 0), "foo\nbar", anchor=anchor2) d.multiline_text((0, 0), "foo\nbar", anchor=anchor2)

View File

@ -3,7 +3,6 @@ from __future__ import annotations
import pytest import pytest
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from PIL._typing import Anchor, Direction
from .helper import assert_image_similar_tofile, skip_unless_feature 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"), ("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: Direction, 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")
@ -253,7 +254,7 @@ def test_getlength_combine(mode: str, direction: Direction, text: str) -> None:
@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm")) @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" 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)
@ -310,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, anchor: Anchor | None, dir: Direction | 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)
@ -342,7 +347,7 @@ def test_combine(
("rm", "right"), # pass with getsize ("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 # 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"

View File

@ -1,12 +1,11 @@
from __future__ import annotations from __future__ import annotations
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from PIL._typing import Anchor
font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 16) 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") 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")
@ -18,7 +17,7 @@ def test(anchor: Anchor) -> 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)
anchors: list[list[Anchor]] = [ anchors: list[list[ImageFont.Anchor]] = [
["ma", "mt", "mm"], ["ma", "mt", "mm"],
["ms", "mb", "md"], ["ms", "mb", "md"],
["ls", "ms", "rs"], ["ls", "ms", "rs"],

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 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
@ -585,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,
@ -710,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,
@ -800,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,
@ -830,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,
@ -880,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,10 +34,10 @@ 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 Anchor, Direction, StrOrBytesPath from ._typing import StrOrBytesPath
from ._util import DeferredError, is_path from ._util import DeferredError, is_path
if TYPE_CHECKING: if TYPE_CHECKING:
@ -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

View File

@ -1,7 +1,6 @@
from typing import Any, Callable from typing import Any, Callable
from . import ImageFont, _imaging from . import ImageFont, _imaging
from ._typing import Anchor, Direction
class Font: class Font:
@property @property
@ -25,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: Direction | 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: Anchor | 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,
@ -39,17 +38,17 @@ class Font:
self, self,
string: str | bytes | bytearray, string: str | bytes | bytearray,
mode: str, mode: str,
dir: Direction | None, dir: ImageFont.Direction | None,
features: list[str] | None, features: list[str] | None,
lang: str | None, lang: str | None,
anchor: Anchor | 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: Direction | 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,33 +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",
"sm",
"ss",
"sb",
"sd",
]
Direction = Literal["rtl", "ltr", "ttb"]
__all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"] __all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"]