Skip to content

Commit d136509

Browse files
radarherehugovk
andauthored
Add abstract BaseImageFont class (#9595)
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
1 parent 3a621b1 commit d136509

4 files changed

Lines changed: 57 additions & 62 deletions

File tree

docs/reference/ImageFont.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,21 @@ Functions
6666
Methods
6767
-------
6868

69+
.. autoclass:: PIL.ImageFont.BaseImageFont
70+
:members:
71+
6972
.. autoclass:: PIL.ImageFont.ImageFont
7073
:members:
74+
:show-inheritance:
7175

7276
.. autoclass:: PIL.ImageFont.FreeTypeFont
7377
:members:
78+
:show-inheritance:
7479

7580
.. autoclass:: PIL.ImageFont.TransposedFont
7681
:members:
7782
:undoc-members:
83+
:show-inheritance:
7884

7985
Constants
8086
---------

src/PIL/ImageDraw.py

Lines changed: 21 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@
3636
from collections.abc import Sequence
3737
from typing import cast
3838

39-
from . import Image, ImageColor, ImageText
39+
from . import Image, ImageColor, ImageFont, ImageText
4040

4141
TYPE_CHECKING = False
4242
if TYPE_CHECKING:
4343
from collections.abc import Callable
4444
from types import ModuleType
4545
from typing import Any, AnyStr
4646

47-
from . import ImageDraw2, ImageFont
47+
from . import ImageDraw2
4848
from ._typing import Coords, _Ink
4949

5050
# experimental access to the outline API
@@ -59,9 +59,7 @@
5959

6060

6161
class ImageDraw:
62-
font: (
63-
ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont | None
64-
) = None
62+
font: ImageFont.BaseImageFont | None = None
6563

6664
def __init__(self, im: Image.Image, mode: str | None = None) -> None:
6765
"""
@@ -105,7 +103,7 @@ def __init__(self, im: Image.Image, mode: str | None = None) -> None:
105103

106104
def getfont(
107105
self,
108-
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
106+
) -> ImageFont.BaseImageFont:
109107
"""
110108
Get the current default font.
111109
@@ -125,17 +123,11 @@ def getfont(
125123
:returns: An image font."""
126124
if not self.font:
127125
# FIXME: should add a font repository
128-
from . import ImageFont
129-
130126
self.font = ImageFont.load_default()
131127
return self.font
132128

133-
def _getfont(
134-
self, font_size: float | None
135-
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont:
129+
def _getfont(self, font_size: float | None) -> ImageFont.BaseImageFont:
136130
if font_size is not None:
137-
from . import ImageFont
138-
139131
return ImageFont.load_default(font_size)
140132
else:
141133
return self.getfont()
@@ -540,12 +532,7 @@ def text(
540532
xy: tuple[float, float],
541533
text: AnyStr | ImageText.Text[AnyStr],
542534
fill: _Ink | None = None,
543-
font: (
544-
ImageFont.ImageFont
545-
| ImageFont.FreeTypeFont
546-
| ImageFont.TransposedFont
547-
| None
548-
) = None,
535+
font: ImageFont.BaseImageFont | None = None,
549536
anchor: str | None = None,
550537
spacing: float = 4,
551538
align: str = "left",
@@ -600,26 +587,26 @@ def draw_text(ink: int, stroke_width: float = 0) -> None:
600587
x = int(line.x)
601588
y = int(line.y)
602589
start = (math.modf(line.x)[0], math.modf(line.y)[0])
603-
try:
604-
mask, offset = image_text.font.getmask2( # type: ignore[union-attr,misc]
590+
if isinstance(image_text.font, ImageFont.FreeTypeFont):
591+
mask, offset = image_text.font.getmask2(
605592
line.text,
606593
mode,
607-
direction=direction,
608-
features=features,
609-
language=language,
610-
stroke_width=stroke_width,
594+
direction,
595+
features,
596+
language,
597+
stroke_width,
598+
line.anchor,
599+
ink,
600+
start,
611601
stroke_filled=True,
612-
anchor=line.anchor,
613-
ink=ink,
614-
start=start,
615602
*args,
616603
**kwargs,
617604
)
618605
x += offset[0]
619606
y += offset[1]
620-
except AttributeError:
607+
else:
621608
try:
622-
mask = image_text.font.getmask( # type: ignore[misc]
609+
mask = image_text.font.getmask(
623610
line.text,
624611
mode,
625612
direction,
@@ -664,12 +651,7 @@ def multiline_text(
664651
xy: tuple[float, float],
665652
text: AnyStr,
666653
fill: _Ink | None = None,
667-
font: (
668-
ImageFont.ImageFont
669-
| ImageFont.FreeTypeFont
670-
| ImageFont.TransposedFont
671-
| None
672-
) = None,
654+
font: ImageFont.BaseImageFont | None = None,
673655
anchor: str | None = None,
674656
spacing: float = 4,
675657
align: str = "left",
@@ -702,12 +684,7 @@ def multiline_text(
702684
def textlength(
703685
self,
704686
text: AnyStr,
705-
font: (
706-
ImageFont.ImageFont
707-
| ImageFont.FreeTypeFont
708-
| ImageFont.TransposedFont
709-
| None
710-
) = None,
687+
font: ImageFont.BaseImageFont | None = None,
711688
direction: str | None = None,
712689
features: list[str] | None = None,
713690
language: str | None = None,
@@ -734,12 +711,7 @@ def textbbox(
734711
self,
735712
xy: tuple[float, float],
736713
text: AnyStr,
737-
font: (
738-
ImageFont.ImageFont
739-
| ImageFont.FreeTypeFont
740-
| ImageFont.TransposedFont
741-
| None
742-
) = None,
714+
font: ImageFont.BaseImageFont | None = None,
743715
anchor: str | None = None,
744716
spacing: float = 4,
745717
align: str = "left",
@@ -767,12 +739,7 @@ def multiline_textbbox(
767739
self,
768740
xy: tuple[float, float],
769741
text: AnyStr,
770-
font: (
771-
ImageFont.ImageFont
772-
| ImageFont.FreeTypeFont
773-
| ImageFont.TransposedFont
774-
| None
775-
) = None,
742+
font: ImageFont.BaseImageFont | None = None,
776743
anchor: str | None = None,
777744
spacing: float = 4,
778745
align: str = "left",

src/PIL/ImageFont.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
from __future__ import annotations
2929

30+
import abc
3031
import base64
3132
import os
3233
import sys
@@ -91,7 +92,27 @@ def _string_length_check(text: str | bytes | bytearray) -> None:
9192
# --------------------------------------------------------------------
9293

9394

94-
class ImageFont:
95+
class BaseImageFont(abc.ABC):
96+
"""Used by ImageDraw and ImageText"""
97+
98+
@abc.abstractmethod
99+
def getbbox(
100+
self, text: str | bytes | bytearray, *args: Any, **kwargs: Any
101+
) -> tuple[float, float, float, float]:
102+
pass
103+
104+
@abc.abstractmethod
105+
def getmask(
106+
self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
107+
) -> Image.core.ImagingCore:
108+
pass
109+
110+
@abc.abstractmethod
111+
def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> float:
112+
pass
113+
114+
115+
class ImageFont(BaseImageFont):
95116
"""PIL font wrapper"""
96117

97118
font: ImagingFont
@@ -215,7 +236,7 @@ def getlength(
215236
# <b>truetype</b> factory function to create font objects.
216237

217238

218-
class FreeTypeFont:
239+
class FreeTypeFont(BaseImageFont):
219240
"""FreeType font wrapper (requires _imagingft service)"""
220241

221242
font: Font
@@ -723,7 +744,7 @@ def set_variation_by_axes(self, axes: list[float]) -> None:
723744
self.font.setvaraxes(axes)
724745

725746

726-
class TransposedFont:
747+
class TransposedFont(BaseImageFont):
727748
"""Wrapper for writing rotated or mirrored text"""
728749

729750
def __init__(

src/PIL/ImageText.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
from . import ImageFont
88
from ._typing import _Ink
99

10-
Font = ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont
11-
1210

1311
class _Line(NamedTuple):
1412
x: float
@@ -27,7 +25,7 @@ def __init__(
2725
text: Text[AnyStr],
2826
width: int,
2927
height: int | None = None,
30-
font: Font | None = None,
28+
font: ImageFont.BaseImageFont | None = None,
3129
) -> None:
3230
self.text: Text[AnyStr] = text
3331
self.width = width
@@ -97,7 +95,7 @@ class Text(Generic[AnyStr]):
9795
def __init__(
9896
self,
9997
text: AnyStr,
100-
font: Font | None = None,
98+
font: ImageFont.BaseImageFont | None = None,
10199
mode: str = "RGB",
102100
spacing: float = 4,
103101
direction: str | None = None,
@@ -449,7 +447,10 @@ def _split(
449447
return parts
450448

451449
def _get_bbox(
452-
self, text: str | bytes, font: Font | None = None, anchor: str | None = None
450+
self,
451+
text: str | bytes,
452+
font: ImageFont.BaseImageFont | None = None,
453+
anchor: str | None = None,
453454
) -> tuple[float, float, float, float]:
454455
return (font or self.font).getbbox(
455456
text,

0 commit comments

Comments
 (0)