Skip to content

Commit e7b72a3

Browse files
authored
Add ImageText (#9098)
2 parents 864d4b6 + d5e1601 commit e7b72a3

File tree

8 files changed

+593
-293
lines changed

8 files changed

+593
-293
lines changed

Tests/test_imagetext.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from PIL import Image, ImageDraw, ImageFont, ImageText
6+
7+
from .helper import assert_image_similar_tofile, skip_unless_feature
8+
9+
FONT_PATH = "Tests/fonts/FreeMono.ttf"
10+
11+
12+
@pytest.fixture(
13+
scope="module",
14+
params=[
15+
pytest.param(ImageFont.Layout.BASIC),
16+
pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")),
17+
],
18+
)
19+
def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout:
20+
return request.param
21+
22+
23+
@pytest.fixture(scope="module")
24+
def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont:
25+
return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine)
26+
27+
28+
def test_get_length(font: ImageFont.FreeTypeFont) -> None:
29+
assert ImageText.Text("A", font).get_length() == 12
30+
assert ImageText.Text("AB", font).get_length() == 24
31+
assert ImageText.Text("M", font).get_length() == 12
32+
assert ImageText.Text("y", font).get_length() == 12
33+
assert ImageText.Text("a", font).get_length() == 12
34+
35+
36+
def test_get_bbox(font: ImageFont.FreeTypeFont) -> None:
37+
assert ImageText.Text("A", font).get_bbox() == (0, 4, 12, 16)
38+
assert ImageText.Text("AB", font).get_bbox() == (0, 4, 24, 16)
39+
assert ImageText.Text("M", font).get_bbox() == (0, 4, 12, 16)
40+
assert ImageText.Text("y", font).get_bbox() == (0, 7, 12, 20)
41+
assert ImageText.Text("a", font).get_bbox() == (0, 7, 12, 16)
42+
43+
44+
def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None:
45+
font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
46+
text = ImageText.Text("Hello World!", font)
47+
text.embed_color()
48+
49+
im = Image.new("RGB", (300, 64), "white")
50+
draw = ImageDraw.Draw(im)
51+
draw.text((10, 10), text, "#fa6")
52+
53+
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
54+
55+
56+
@skip_unless_feature("freetype2")
57+
def test_stroke() -> None:
58+
for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items():
59+
# Arrange
60+
im = Image.new("RGB", (120, 130))
61+
draw = ImageDraw.Draw(im)
62+
font = ImageFont.truetype(FONT_PATH, 120)
63+
text = ImageText.Text("A", font)
64+
text.stroke(2, stroke_fill)
65+
66+
# Act
67+
draw.text((12, 12), text, "#f00")
68+
69+
# Assert
70+
assert_image_similar_tofile(
71+
im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1
72+
)

docs/reference/ImageDraw.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,8 @@ Methods
582582
hello_world = hello + world # kerning is disabled, no need to adjust
583583
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"]) # True
584584

585+
.. seealso:: :py:meth:`PIL.ImageText.Text.get_length`
586+
585587
.. versionadded:: 8.0.0
586588

587589
:param text: Text to be measured. May not contain any newline characters.
@@ -683,6 +685,8 @@ Methods
683685
1/64 pixel precision. The bounding box includes extra margins for
684686
some fonts, e.g. italics or accents.
685687

688+
.. seealso:: :py:meth:`PIL.ImageText.Text.get_bbox`
689+
686690
.. versionadded:: 8.0.0
687691

688692
:param xy: The anchor coordinates of the text.

docs/reference/ImageText.rst

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
.. py:module:: PIL.ImageText
2+
.. py:currentmodule:: PIL.ImageText
3+
4+
:py:mod:`~PIL.ImageText` module
5+
===============================
6+
7+
The :py:mod:`~PIL.ImageText` module defines a :py:class:`~PIL.ImageText.Text` class.
8+
Instances of this class provide a way to use fonts with text strings or bytes. The
9+
result is a simple API to apply styling to pieces of text and measure or draw them.
10+
11+
Example
12+
-------
13+
14+
::
15+
16+
from PIL import Image, ImageDraw, ImageFont, ImageText
17+
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 24)
18+
19+
text = ImageText.Text("Hello world", font)
20+
text.embed_color()
21+
text.stroke(2, "#0f0")
22+
23+
print(text.get_length()) # 154.0
24+
print(text.get_bbox()) # (-2, 3, 156, 22)
25+
26+
im = Image.new("RGB", text.get_bbox()[2:])
27+
d = ImageDraw.Draw(im)
28+
d.text((0, 0), text, "#f00")
29+
30+
Comparison
31+
----------
32+
33+
Without ``ImageText.Text``::
34+
35+
from PIL import Image, ImageDraw
36+
im = Image.new(mode, size)
37+
d = ImageDraw.Draw(im)
38+
39+
d.textlength(text, font, direction, features, language, embedded_color)
40+
d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color)
41+
d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color)
42+
43+
With ``ImageText.Text``::
44+
45+
from PIL import ImageText
46+
text = ImageText.Text(text, font, mode, spacing, direction, features, language)
47+
text.embed_color()
48+
text.stroke(stroke_width, stroke_fill)
49+
50+
text.get_length()
51+
text.get_bbox(xy, anchor, align)
52+
53+
im = Image.new(mode, size)
54+
d = ImageDraw.Draw(im)
55+
d.text(xy, text, fill, anchor=anchor, align=align)
56+
57+
Methods
58+
-------
59+
60+
.. autoclass:: PIL.ImageText.Text
61+
:members:

docs/reference/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Reference
2424
ImageSequence
2525
ImageShow
2626
ImageStat
27+
ImageText
2728
ImageTk
2829
ImageTransform
2930
ImageWin

docs/releasenotes/12.0.0.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,39 @@ Image.alpha_composite: LA images
124124

125125
:py:meth:`~PIL.Image.alpha_composite` can now use LA images as well as RGBA.
126126

127+
API additions
128+
=============
129+
130+
Added ImageText.Text
131+
^^^^^^^^^^^^^^^^^^^^
132+
133+
:py:class:`PIL.ImageText.Text` has been added, as a simpler way to use fonts with text
134+
strings or bytes.
135+
136+
Without ``ImageText.Text``::
137+
138+
from PIL import Image, ImageDraw
139+
im = Image.new(mode, size)
140+
d = ImageDraw.Draw(im)
141+
142+
d.textlength(text, font, direction, features, language, embedded_color)
143+
d.multiline_textbbox(xy, text, font, anchor, spacing, align, direction, features, language, stroke_width, embedded_color)
144+
d.text(xy, text, fill, font, anchor, spacing, align, direction, features, language, stroke_width, stroke_fill, embedded_color)
145+
146+
With ``ImageText.Text``::
147+
148+
from PIL import ImageText
149+
text = ImageText.Text(text, font, mode, spacing, direction, features, language)
150+
text.embed_color()
151+
text.stroke(stroke_width, stroke_fill)
152+
153+
text.get_length()
154+
text.get_bbox(xy, anchor, align)
155+
156+
im = Image.new(mode, size)
157+
d = ImageDraw.Draw(im)
158+
d.text(xy, text, fill, anchor=anchor, align=align)
159+
127160
Other changes
128161
=============
129162

0 commit comments

Comments
 (0)