Merge pull request #6381 from nulano/deprecate-getsize

Deprecate ImageFont.getsize and related functions
This commit is contained in:
mergify[bot] 2022-07-01 13:10:14 +00:00 committed by GitHub
commit 488589b4b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 408 additions and 100 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -33,9 +33,9 @@ def fuzz_font(data):
# different font objects. # different font objects.
return return
font.getsize_multiline("ABC\nAaaa") font.getbbox("ABC")
font.getmask("test text") font.getmask("test text")
with Image.new(mode="RGBA", size=(200, 200)) as im: with Image.new(mode="RGBA", size=(200, 200)) as im:
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2)
draw.text((10, 10), "Test Text", font=font, fill="#000") draw.text((10, 10), "Test Text", font=font, fill="#000")

View File

@ -76,12 +76,19 @@ def test_textsize(request, tmp_path):
tempname = save_font(request, tmp_path) tempname = save_font(request, tmp_path)
font = ImageFont.load(tempname) font = ImageFont.load(tempname)
for i in range(255): for i in range(255):
(dx, dy) = font.getsize(chr(i)) (ox, oy, dx, dy) = font.getbbox(chr(i))
assert ox == 0
assert oy == 0
assert dy == 20 assert dy == 20
assert dx in (0, 10) assert dx in (0, 10)
assert font.getlength(chr(i)) == dx
with pytest.warns(DeprecationWarning) as log:
assert font.getsize(chr(i)) == (dx, dy)
assert len(log) == 1
for i in range(len(message)): for i in range(len(message)):
msg = message[: i + 1] msg = message[: i + 1]
assert font.getsize(msg) == (len(msg) * 10, 20) assert font.getlength(msg) == len(msg) * 10
assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20)
def _test_high_characters(request, tmp_path, message): def _test_high_characters(request, tmp_path, message):

View File

@ -101,13 +101,17 @@ def _test_textsize(request, tmp_path, encoding):
tempname = save_font(request, tmp_path, encoding) tempname = save_font(request, tmp_path, encoding)
font = ImageFont.load(tempname) font = ImageFont.load(tempname)
for i in range(255): for i in range(255):
(dx, dy) = font.getsize(bytearray([i])) (ox, oy, dx, dy) = font.getbbox(bytearray([i]))
assert ox == 0
assert oy == 0
assert dy == 20 assert dy == 20
assert dx in (0, 10) assert dx in (0, 10)
assert font.getlength(bytearray([i])) == dx
message = charsets[encoding]["message"].encode(encoding) message = charsets[encoding]["message"].encode(encoding)
for i in range(len(message)): for i in range(len(message)):
msg = message[: i + 1] msg = message[: i + 1]
assert font.getsize(msg) == (len(msg) * 10, 20) assert font.getlength(msg) == len(msg) * 10
assert font.getbbox(msg) == (0, 0, len(msg) * 10, 20)
def test_textsize_iso8859_1(request, tmp_path): def test_textsize_iso8859_1(request, tmp_path):

View File

@ -1232,21 +1232,39 @@ def test_textsize_empty_string():
# Act # Act
# Should not cause 'SystemError: <built-in method getsize of # Should not cause 'SystemError: <built-in method getsize of
# ImagingFont object at 0x...> returned NULL without setting an error' # ImagingFont object at 0x...> returned NULL without setting an error'
draw.textsize("") draw.textbbox((0, 0), "")
draw.textsize("\n") draw.textbbox((0, 0), "\n")
draw.textsize("test\n") draw.textbbox((0, 0), "test\n")
draw.textlength("")
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")
def test_textsize_stroke(): def test_textbbox_stroke():
# Arrange # Arrange
im = Image.new("RGB", (W, H)) im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20) font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 20)
# Act / Assert # Act / Assert
assert draw.textsize("A", font, stroke_width=2) == (16, 20) assert draw.textbbox((2, 2), "A", font, stroke_width=2) == (0, 4, 16, 20)
assert draw.multiline_textsize("ABC\nAaaa", font, stroke_width=2) == (52, 44) assert draw.textbbox((2, 2), "A", font, stroke_width=4) == (-2, 2, 18, 22)
assert draw.textbbox((2, 2), "ABC\nAaaa", font, stroke_width=2) == (0, 4, 52, 44)
assert draw.textbbox((2, 2), "ABC\nAaaa", font, stroke_width=4) == (-2, 2, 54, 50)
def test_textsize_deprecation():
im = Image.new("RGB", (W, H))
draw = ImageDraw.Draw(im)
with pytest.warns(DeprecationWarning) as log:
draw.textsize("Hello")
assert len(log) == 1
with pytest.warns(DeprecationWarning) as log:
draw.textsize("Hello\nWorld")
assert len(log) == 1
with pytest.warns(DeprecationWarning) as log:
draw.multiline_textsize("Hello\nWorld")
assert len(log) == 1
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")

View File

@ -1,5 +1,7 @@
import os.path import os.path
import pytest
from PIL import Image, ImageDraw, ImageDraw2 from PIL import Image, ImageDraw, ImageDraw2
from .helper import ( from .helper import (
@ -205,7 +207,9 @@ def test_textsize():
font = ImageDraw2.Font("white", FONT_PATH) font = ImageDraw2.Font("white", FONT_PATH)
# Act # Act
with pytest.warns(DeprecationWarning) as log:
size = draw.textsize("ImageDraw2", font) size = draw.textsize("ImageDraw2", font)
assert len(log) == 1
# Assert # Assert
assert size[1] == 12 assert size[1] == 12
@ -221,9 +225,10 @@ def test_textsize_empty_string():
# Act # Act
# Should not cause 'SystemError: <built-in method getsize of # Should not cause 'SystemError: <built-in method getsize of
# ImagingFont object at 0x...> returned NULL without setting an error' # ImagingFont object at 0x...> returned NULL without setting an error'
draw.textsize("", font) draw.textbbox((0, 0), "", font)
draw.textsize("\n", font) draw.textbbox((0, 0), "\n", font)
draw.textsize("test\n", font) draw.textbbox((0, 0), "test\n", font)
draw.textlength("", font)
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")

View File

@ -94,7 +94,7 @@ class TestImageFont:
def _render(self, font): def _render(self, font):
txt = "Hello World!" txt = "Hello World!"
ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE) ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE)
ttf.getsize(txt) ttf.getbbox(txt)
img = Image.new("RGB", (256, 64), "white") img = Image.new("RGB", (256, 64), "white")
d = ImageDraw.Draw(img) d = ImageDraw.Draw(img)
@ -135,15 +135,15 @@ class TestImageFont:
target = "Tests/images/transparent_background_text_L.png" target = "Tests/images/transparent_background_text_L.png"
assert_image_similar_tofile(im.convert("L"), target, 0.01) assert_image_similar_tofile(im.convert("L"), target, 0.01)
def test_textsize_equal(self): def test_textbbox_equal(self):
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
ttf = self.get_font() ttf = self.get_font()
txt = "Hello World!" txt = "Hello World!"
size = draw.textsize(txt, ttf) bbox = draw.textbbox((10, 10), txt, ttf)
draw.text((10, 10), txt, font=ttf) draw.text((10, 10), txt, font=ttf)
draw.rectangle((10, 10, 10 + size[0], 10 + size[1])) draw.rectangle(bbox)
assert_image_similar_tofile( assert_image_similar_tofile(
im, "Tests/images/rectangle_surrounding_text.png", 2.5 im, "Tests/images/rectangle_surrounding_text.png", 2.5
@ -184,7 +184,7 @@ class TestImageFont:
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
ttf = self.get_font() ttf = self.get_font()
line_spacing = draw.textsize("A", font=ttf)[1] + 4 line_spacing = ttf.getbbox("A")[3] + 4
lines = TEST_TEXT.split("\n") lines = TEST_TEXT.split("\n")
y = 0 y = 0
for line in lines: for line in lines:
@ -245,6 +245,7 @@ class TestImageFont:
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
with pytest.warns(DeprecationWarning) as log:
# Test that textsize() correctly connects to multiline_textsize() # Test that textsize() correctly connects to multiline_textsize()
assert draw.textsize(TEST_TEXT, font=ttf) == draw.multiline_textsize( assert draw.textsize(TEST_TEXT, font=ttf) == draw.multiline_textsize(
TEST_TEXT, font=ttf TEST_TEXT, font=ttf
@ -258,16 +259,41 @@ class TestImageFont:
# to multiline_textsize() # to multiline_textsize()
draw.textsize(TEST_TEXT, font=ttf, spacing=4) draw.textsize(TEST_TEXT, font=ttf, spacing=4)
draw.textsize(TEST_TEXT, ttf, 4) draw.textsize(TEST_TEXT, ttf, 4)
assert len(log) == 6
def test_multiline_bbox(self):
ttf = self.get_font()
im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im)
# Test that textbbox() correctly connects to multiline_textbbox()
assert draw.textbbox((0, 0), TEST_TEXT, font=ttf) == draw.multiline_textbbox(
(0, 0), TEST_TEXT, font=ttf
)
# Test that multiline_textbbox corresponds to ImageFont.textbbox()
# for single line text
assert ttf.getbbox("A") == draw.multiline_textbbox((0, 0), "A", font=ttf)
# Test that textbbox() can pass on additional arguments
# to multiline_textbbox()
draw.textbbox((0, 0), TEST_TEXT, font=ttf, spacing=4)
def test_multiline_width(self): def test_multiline_width(self):
ttf = self.get_font() ttf = self.get_font()
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
assert (
draw.textbbox((0, 0), "longest line", font=ttf)[2]
== draw.multiline_textbbox((0, 0), "longest line\nline", font=ttf)[2]
)
with pytest.warns(DeprecationWarning) as log:
assert ( assert (
draw.textsize("longest line", font=ttf)[0] draw.textsize("longest line", font=ttf)[0]
== draw.multiline_textsize("longest line\nline", font=ttf)[0] == draw.multiline_textsize("longest line\nline", font=ttf)[0]
) )
assert len(log) == 2
def test_multiline_spacing(self): def test_multiline_spacing(self):
ttf = self.get_font() ttf = self.get_font()
@ -289,16 +315,33 @@ class TestImageFont:
# Original font # Original font
draw.font = font draw.font = font
with pytest.warns(DeprecationWarning) as log:
box_size_a = draw.textsize(word) box_size_a = draw.textsize(word)
assert box_size_a == font.getsize(word)
assert len(log) == 2
bbox_a = draw.textbbox((10, 10), word)
# Rotated font # Rotated font
draw.font = transposed_font draw.font = transposed_font
with pytest.warns(DeprecationWarning) as log:
box_size_b = draw.textsize(word) box_size_b = draw.textsize(word)
assert box_size_b == transposed_font.getsize(word)
assert len(log) == 2
bbox_b = draw.textbbox((20, 20), word)
# Check (w,h) of box a is (h,w) of box b # Check (w,h) of box a is (h,w) of box b
assert box_size_a[0] == box_size_b[1] assert box_size_a[0] == box_size_b[1]
assert box_size_a[1] == box_size_b[0] assert box_size_a[1] == box_size_b[0]
# Check bbox b is (20, 20, 20 + h, 20 + w)
assert bbox_b[0] == 20
assert bbox_b[1] == 20
assert bbox_b[2] == 20 + bbox_a[3] - bbox_a[1]
assert bbox_b[3] == 20 + bbox_a[2] - bbox_a[0]
# text length is undefined for vertical text
pytest.raises(ValueError, draw.textlength, word)
def test_unrotated_transposed_font(self): def test_unrotated_transposed_font(self):
img_grey = Image.new("L", (100, 100)) img_grey = Image.new("L", (100, 100))
draw = ImageDraw.Draw(img_grey) draw = ImageDraw.Draw(img_grey)
@ -310,15 +353,31 @@ class TestImageFont:
# Original font # Original font
draw.font = font draw.font = font
with pytest.warns(DeprecationWarning) as log:
box_size_a = draw.textsize(word) box_size_a = draw.textsize(word)
assert len(log) == 1
bbox_a = draw.textbbox((10, 10), word)
length_a = draw.textlength(word)
# Rotated font # Rotated font
draw.font = transposed_font draw.font = transposed_font
with pytest.warns(DeprecationWarning) as log:
box_size_b = draw.textsize(word) box_size_b = draw.textsize(word)
assert len(log) == 1
bbox_b = draw.textbbox((20, 20), word)
length_b = draw.textlength(word)
# Check boxes a and b are same size # Check boxes a and b are same size
assert box_size_a == box_size_b assert box_size_a == box_size_b
# Check bbox b is (20, 20, 20 + w, 20 + h)
assert bbox_b[0] == 20
assert bbox_b[1] == 20
assert bbox_b[2] == 20 + bbox_a[2] - bbox_a[0]
assert bbox_b[3] == 20 + bbox_a[3] - bbox_a[1]
assert length_a == length_b
def test_rotated_transposed_font_get_mask(self): def test_rotated_transposed_font_get_mask(self):
# Arrange # Arrange
text = "mask this" text = "mask this"
@ -373,9 +432,11 @@ class TestImageFont:
text = "offset this" text = "offset this"
# Act # Act
with pytest.warns(DeprecationWarning) as log:
offset = font.getoffset(text) offset = font.getoffset(text)
# Assert # Assert
assert len(log) == 1
assert offset == (0, 3) assert offset == (0, 3)
def test_free_type_font_get_mask(self): def test_free_type_font_get_mask(self):
@ -417,11 +478,11 @@ class TestImageFont:
# Assert # Assert
assert_image_equal_tofile(im, "Tests/images/default_font.png") assert_image_equal_tofile(im, "Tests/images/default_font.png")
def test_getsize_empty(self): def test_getbbox_empty(self):
# issue #2614 # issue #2614
font = self.get_font() font = self.get_font()
# should not crash. # should not crash.
assert (0, 0) == font.getsize("") assert (0, 0, 0, 0) == font.getbbox("")
def test_render_empty(self): def test_render_empty(self):
# issue 2666 # issue 2666
@ -438,7 +499,7 @@ class TestImageFont:
# issue #2826 # issue #2826
font = ImageFont.load_default() font = ImageFont.load_default()
with pytest.raises(UnicodeEncodeError): with pytest.raises(UnicodeEncodeError):
font.getsize("") font.getbbox("")
def test_unicode_extended(self): def test_unicode_extended(self):
# issue #3777 # issue #3777
@ -563,6 +624,17 @@ class TestImageFont:
assert t.font.x_ppem == 20 assert t.font.x_ppem == 20
assert t.font.y_ppem == 20 assert t.font.y_ppem == 20
assert t.font.glyphs == 4177 assert t.font.glyphs == 4177
assert t.getbbox("A") == (0, 4, 12, 16)
assert t.getbbox("AB") == (0, 4, 24, 16)
assert t.getbbox("M") == (0, 4, 12, 16)
assert t.getbbox("y") == (0, 7, 12, 20)
assert t.getbbox("a") == (0, 7, 12, 16)
assert t.getlength("A") == 12
assert t.getlength("AB") == 24
assert t.getlength("M") == 12
assert t.getlength("y") == 12
assert t.getlength("a") == 12
with pytest.warns(DeprecationWarning) as log:
assert t.getsize("A") == (12, 16) assert t.getsize("A") == (12, 16)
assert t.getsize("AB") == (24, 16) assert t.getsize("AB") == (24, 16)
assert t.getsize("M") == (12, 16) assert t.getsize("M") == (12, 16)
@ -574,6 +646,7 @@ class TestImageFont:
assert t.getsize_multiline("ABC\n") == (36, 36) assert t.getsize_multiline("ABC\n") == (36, 36)
assert t.getsize_multiline("ABC\nA") == (36, 36) assert t.getsize_multiline("ABC\nA") == (36, 36)
assert t.getsize_multiline("ABC\nAaaa") == (48, 36) assert t.getsize_multiline("ABC\nAaaa") == (48, 36)
assert len(log) == 11
def test_getsize_stroke(self): def test_getsize_stroke(self):
# Arrange # Arrange
@ -581,6 +654,13 @@ class TestImageFont:
# Act / Assert # Act / Assert
for stroke_width in [0, 2]: for stroke_width in [0, 2]:
assert t.getbbox("A", stroke_width=stroke_width) == (
0 - stroke_width,
4 - stroke_width,
12 + stroke_width,
16 + stroke_width,
)
with pytest.warns(DeprecationWarning) as log:
assert t.getsize("A", stroke_width=stroke_width) == ( assert t.getsize("A", stroke_width=stroke_width) == (
12 + stroke_width * 2, 12 + stroke_width * 2,
16 + stroke_width * 2, 16 + stroke_width * 2,
@ -589,6 +669,7 @@ class TestImageFont:
48 + stroke_width * 2, 48 + stroke_width * 2,
36 + stroke_width * 4, 36 + stroke_width * 4,
) )
assert len(log) == 2
def test_complex_font_settings(self): def test_complex_font_settings(self):
# Arrange # Arrange
@ -720,8 +801,11 @@ class TestImageFont:
im = Image.new("RGB", (200, 200)) im = Image.new("RGB", (200, 200))
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
default_font = ImageFont.load_default() default_font = ImageFont.load_default()
with pytest.raises(ValueError): with pytest.warns(DeprecationWarning) as log:
d.textbbox((0, 0), "test", font=default_font) width, height = d.textsize("test", font=default_font)
assert len(log) == 1
assert d.textlength("test", font=default_font) == width
assert d.textbbox((0, 0), "test", font=default_font) == (0, 0, width, height)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"anchor, left, top", "anchor, left, top",
@ -868,7 +952,7 @@ class TestImageFont:
def test_standard_embedded_color(self): def test_standard_embedded_color(self):
txt = "Hello World!" txt = "Hello World!"
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=self.LAYOUT_ENGINE) ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=self.LAYOUT_ENGINE)
ttf.getsize(txt) ttf.getbbox(txt)
im = Image.new("RGB", (300, 64), "white") im = Image.new("RGB", (300, 64), "white")
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)

View File

@ -140,8 +140,8 @@ def test_ligature_features():
target = "Tests/images/test_ligature_features.png" target = "Tests/images/test_ligature_features.png"
assert_image_similar_tofile(im, target, 0.5) assert_image_similar_tofile(im, target, 0.5)
liga_size = ttf.getsize("fi", features=["-liga"]) liga_bbox = ttf.getbbox("fi", features=["-liga"])
assert liga_size == (13, 19) assert liga_bbox == (0, 4, 13, 19)
def test_kerning_features(): def test_kerning_features():

View File

@ -178,6 +178,25 @@ Image.coerce_e
This undocumented method has been deprecated and will be removed in Pillow 10 This undocumented method has been deprecated and will be removed in Pillow 10
(2023-07-01). (2023-07-01).
Font size and offset methods
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 9.2.0
Several functions for computing the size and offset of rendered text
have been deprecated and will be removed in Pillow 10 (2023-07-01):
=========================================================================== =============================================================================================================
Deprecated Use instead
=========================================================================== =============================================================================================================
:py:meth:`.FreeTypeFont.getsize` and :py:meth:`.FreeTypeFont.getoffset` :py:meth:`.FreeTypeFont.getbbox` and :py:meth:`.FreeTypeFont.getlength`
:py:meth:`.FreeTypeFont.getsize_multiline` :py:meth:`.ImageDraw.multiline_textbbox`
:py:meth:`.ImageFont.getsize` :py:meth:`.ImageFont.getbbox` and :py:meth:`.ImageFont.getlength`
:py:meth:`.TransposedFont.getsize` :py:meth:`.TransposedFont.getbbox` and :py:meth:`.TransposedFont.getlength`
:py:meth:`.ImageDraw.textsize` and :py:meth:`.ImageDraw.multiline_textsize` :py:meth:`.ImageDraw.textbbox`, :py:meth:`.ImageDraw.textlength` and :py:meth:`.ImageDraw.multiline_textbbox`
:py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength`
=========================================================================== =============================================================================================================
Removed features Removed features
---------------- ----------------

View File

@ -436,12 +436,14 @@ Methods
.. py:method:: ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) .. py:method:: ImageDraw.textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0)
Return the size of the given string, in pixels. .. deprecated:: 9.2.0
Use :py:meth:`textlength()` to measure the offset of following text with Use :py:meth:`textlength()` to measure the offset of following text with
1/64 pixel precision. 1/64 pixel precision.
Use :py:meth:`textbbox()` to get the exact bounding box based on an anchor. Use :py:meth:`textbbox()` to get the exact bounding box based on an anchor.
Return the size of the given string, in pixels.
.. note:: For historical reasons this function measures text height from .. note:: For historical reasons this function measures text height from
the ascender line instead of the top, see :ref:`text-anchors`. the ascender line instead of the top, see :ref:`text-anchors`.
If you wish to measure text height from the top, it is recommended If you wish to measure text height from the top, it is recommended
@ -484,6 +486,10 @@ Methods
.. py:method:: ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0) .. py:method:: ImageDraw.multiline_textsize(text, font=None, spacing=4, direction=None, features=None, language=None, stroke_width=0)
.. deprecated:: 9.2.0
Use :py:meth:`.multiline_textbbox` instead.
Return the size of the given string, in pixels. Return the size of the given string, in pixels.
Use :py:meth:`textlength()` to measure the offset of following text with Use :py:meth:`textlength()` to measure the offset of following text with

View File

@ -56,6 +56,7 @@ Methods
.. autoclass:: PIL.ImageFont.TransposedFont .. autoclass:: PIL.ImageFont.TransposedFont
:members: :members:
:undoc-members:
Constants Constants
--------- ---------

View File

@ -40,6 +40,25 @@ Image.coerce_e
This undocumented method has been deprecated and will be removed in Pillow 10 This undocumented method has been deprecated and will be removed in Pillow 10
(2023-07-01). (2023-07-01).
Font size and offset methods
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 9.2.0
Several functions for computing the size and offset of rendered text
have been deprecated and will be removed in Pillow 10 (2023-07-01):
=========================================================================== =============================================================================================================
Deprecated Use instead
=========================================================================== =============================================================================================================
:py:meth:`.FreeTypeFont.getsize` and :py:meth:`.FreeTypeFont.getoffset` :py:meth:`.FreeTypeFont.getbbox` and :py:meth:`.FreeTypeFont.getlength`
:py:meth:`.FreeTypeFont.getsize_multiline` :py:meth:`.ImageDraw.multiline_textbbox`
:py:meth:`.ImageFont.getsize` :py:meth:`.ImageFont.getbbox` and :py:meth:`.ImageFont.getlength`
:py:meth:`.TransposedFont.getsize` :py:meth:`.TransposedFont.getbbox` and :py:meth:`.TransposedFont.getlength`
:py:meth:`.ImageDraw.textsize` and :py:meth:`.ImageDraw.multiline_textsize` :py:meth:`.ImageDraw.textbbox`, :py:meth:`.ImageDraw.textlength` and :py:meth:`.ImageDraw.multiline_textbbox`
:py:meth:`.ImageDraw2.Draw.textsize` :py:meth:`.ImageDraw2.Draw.textbbox` and :py:meth:`.ImageDraw2.Draw.textlength`
=========================================================================== =============================================================================================================
API Additions API Additions
============= =============

View File

@ -32,8 +32,10 @@
import math import math
import numbers import numbers
import warnings
from . import Image, ImageColor from . import Image, ImageColor
from ._deprecate import deprecate
""" """
A simple 2D drawing interface for PIL images. A simple 2D drawing interface for PIL images.
@ -372,6 +374,19 @@ class ImageDraw:
return text.split(split_character) return text.split(split_character)
def _multiline_spacing(self, font, spacing, stroke_width):
# this can be replaced with self.textbbox(...)[3] when textsize is removed
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
return (
self.textsize(
"A",
font=font,
stroke_width=stroke_width,
)[1]
+ spacing
)
def text( def text(
self, self,
xy, xy,
@ -511,9 +526,7 @@ class ImageDraw:
widths = [] widths = []
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = ( line_spacing = self._multiline_spacing(font, spacing, stroke_width)
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing
)
for line in lines: for line in lines:
line_width = self.textlength( line_width = self.textlength(
line, font, direction=direction, features=features, language=language line, font, direction=direction, features=features, language=language
@ -573,14 +586,31 @@ class ImageDraw:
stroke_width=0, stroke_width=0,
): ):
"""Get the size of a given string, in pixels.""" """Get the size of a given string, in pixels."""
deprecate("textsize", 10, "textbbox or textlength")
if self._multiline_check(text): if self._multiline_check(text):
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
return self.multiline_textsize( return self.multiline_textsize(
text, font, spacing, direction, features, language, stroke_width text,
font,
spacing,
direction,
features,
language,
stroke_width,
) )
if font is None: if font is None:
font = self.getfont() font = self.getfont()
return font.getsize(text, direction, features, language, stroke_width) with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
return font.getsize(
text,
direction,
features,
language,
stroke_width,
)
def multiline_textsize( def multiline_textsize(
self, self,
@ -592,14 +622,21 @@ class ImageDraw:
language=None, language=None,
stroke_width=0, stroke_width=0,
): ):
deprecate("multiline_textsize", 10, "multiline_textbbox")
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = ( line_spacing = self._multiline_spacing(font, spacing, stroke_width)
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing with warnings.catch_warnings():
) warnings.filterwarnings("ignore", category=DeprecationWarning)
for line in lines: for line in lines:
line_width, line_height = self.textsize( line_width, line_height = self.textsize(
line, font, spacing, direction, features, language, stroke_width line,
font,
spacing,
direction,
features,
language,
stroke_width,
) )
max_width = max(max_width, line_width) max_width = max(max_width, line_width)
return max_width, len(lines) * line_spacing - spacing return max_width, len(lines) * line_spacing - spacing
@ -625,8 +662,15 @@ class ImageDraw:
try: try:
return font.getlength(text, mode, direction, features, language) return font.getlength(text, mode, direction, features, language)
except AttributeError: except AttributeError:
deprecate("textlength support for fonts without getlength", 10)
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
size = self.textsize( size = self.textsize(
text, font, direction=direction, features=features, language=language text,
font,
direction=direction,
features=features,
language=language,
) )
if direction == "ttb": if direction == "ttb":
return size[1] return size[1]
@ -667,10 +711,6 @@ class ImageDraw:
if font is None: if font is None:
font = self.getfont() font = self.getfont()
from . import ImageFont
if not isinstance(font, ImageFont.FreeTypeFont):
raise ValueError("Only supported for TrueType fonts")
mode = "RGBA" if embedded_color else self.fontmode mode = "RGBA" if embedded_color else self.fontmode
bbox = font.getbbox( bbox = font.getbbox(
text, mode, direction, features, language, stroke_width, anchor text, mode, direction, features, language, stroke_width, anchor
@ -704,9 +744,7 @@ class ImageDraw:
widths = [] widths = []
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
line_spacing = ( line_spacing = self._multiline_spacing(font, spacing, stroke_width)
self.textsize("A", font=font, stroke_width=stroke_width)[1] + spacing
)
for line in lines: for line in lines:
line_width = self.textlength( line_width = self.textlength(
line, line,

View File

@ -24,7 +24,10 @@
""" """
import warnings
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
from ._deprecate import deprecate
class Pen: class Pen:
@ -172,8 +175,35 @@ class Draw:
def textsize(self, text, font): def textsize(self, text, font):
""" """
.. deprecated:: 9.2.0
Return the size of the given string, in pixels. Return the size of the given string, in pixels.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textsize` .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textsize`
""" """
deprecate("textsize", 10, "textbbox or textlength")
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
return self.draw.textsize(text, font=font.font) return self.draw.textsize(text, font=font.font)
def textbbox(self, xy, text, font):
"""
Returns bounding box (in pixels) of given text.
:return: ``(left, top, right, bottom)`` bounding box
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
"""
if self.transform:
xy = ImagePath.Path(xy)
xy.transform(self.transform)
return self.draw.textbbox(xy, text, font=font.font)
def textlength(self, text, font):
"""
Returns length (in pixels) of given text.
This is the amount by which following text should be offset.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textlength`
"""
return self.draw.textlength(text, font=font.font)

View File

@ -137,12 +137,17 @@ class ImageFont:
def getsize(self, text, *args, **kwargs): def getsize(self, text, *args, **kwargs):
""" """
.. deprecated:: 9.2.0
Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead.
Returns width and height (in pixels) of given text. Returns width and height (in pixels) of given text.
:param text: Text to measure. :param text: Text to measure.
:return: (width, height) :return: (width, height)
""" """
deprecate("getsize", 10, "getbbox or getlength")
return self.font.getsize(text) return self.font.getsize(text)
def getmask(self, text, mode="", *args, **kwargs): def getmask(self, text, mode="", *args, **kwargs):
@ -165,6 +170,33 @@ class ImageFont:
""" """
return self.font.getmask(text, mode) return self.font.getmask(text, mode)
def getbbox(self, text, *args, **kwargs):
"""
Returns bounding box (in pixels) of given text.
.. versionadded:: 9.2.0
:param text: Text to render.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
:return: ``(left, top, right, bottom)`` bounding box
"""
width, height = self.font.getsize(text)
return 0, 0, width, height
def getlength(self, text, *args, **kwargs):
"""
Returns length (in pixels) of given text.
This is the amount by which following text should be offset.
.. versionadded:: 9.2.0
"""
width, height = self.font.getsize(text)
return width
## ##
# Wrapper for FreeType fonts. Application code should use the # Wrapper for FreeType fonts. Application code should use the
@ -386,16 +418,23 @@ class FreeTypeFont:
return left, top, left + width, top + height return left, top, left + width, top + height
def getsize( def getsize(
self, text, direction=None, features=None, language=None, stroke_width=0 self,
text,
direction=None,
features=None,
language=None,
stroke_width=0,
): ):
""" """
Returns width and height (in pixels) of given text if rendered in font with .. deprecated:: 9.2.0
provided direction, features, and language.
Use :py:meth:`getlength()` to measure the offset of following text with Use :py:meth:`getlength()` to measure the offset of following text with
1/64 pixel precision. 1/64 pixel precision.
Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor. Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor.
Returns width and height (in pixels) of given text if rendered in font with
provided direction, features, and language.
.. note:: For historical reasons this function measures text height from .. note:: For historical reasons this function measures text height from
the ascender line instead of the top, see :ref:`text-anchors`. the ascender line instead of the top, see :ref:`text-anchors`.
If you wish to measure text height from the top, it is recommended If you wish to measure text height from the top, it is recommended
@ -438,6 +477,7 @@ class FreeTypeFont:
:return: (width, height) :return: (width, height)
""" """
deprecate("getsize", 10, "getbbox or getlength")
# vertical offset is added for historical reasons # vertical offset is added for historical reasons
# see https://github.com/python-pillow/Pillow/pull/4910#discussion_r486682929 # see https://github.com/python-pillow/Pillow/pull/4910#discussion_r486682929
size, offset = self.font.getsize(text, "L", direction, features, language) size, offset = self.font.getsize(text, "L", direction, features, language)
@ -456,6 +496,10 @@ class FreeTypeFont:
stroke_width=0, stroke_width=0,
): ):
""" """
.. deprecated:: 9.2.0
Use :py:meth:`.ImageDraw.multiline_textbbox` instead.
Returns width and height (in pixels) of given text if rendered in font Returns width and height (in pixels) of given text if rendered in font
with provided direction, features, and language, while respecting with provided direction, features, and language, while respecting
newline characters. newline characters.
@ -495,8 +539,11 @@ class FreeTypeFont:
:return: (width, height) :return: (width, height)
""" """
deprecate("getsize_multiline", 10, "ImageDraw.multiline_textbbox")
max_width = 0 max_width = 0
lines = self._multiline_split(text) lines = self._multiline_split(text)
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
line_spacing = self.getsize("A", stroke_width=stroke_width)[1] + spacing line_spacing = self.getsize("A", stroke_width=stroke_width)[1] + spacing
for line in lines: for line in lines:
line_width, line_height = self.getsize( line_width, line_height = self.getsize(
@ -508,6 +555,10 @@ class FreeTypeFont:
def getoffset(self, text): def getoffset(self, text):
""" """
.. deprecated:: 9.2.0
Use :py:meth:`.getbbox` instead.
Returns the offset of given text. This is the gap between the Returns the offset of given text. This is the gap between the
starting coordinate and the first marking. Note that this gap is starting coordinate and the first marking. Note that this gap is
included in the result of :py:func:`~PIL.ImageFont.FreeTypeFont.getsize`. included in the result of :py:func:`~PIL.ImageFont.FreeTypeFont.getsize`.
@ -516,6 +567,7 @@ class FreeTypeFont:
:return: A tuple of the x and y offset :return: A tuple of the x and y offset
""" """
deprecate("getoffset", 10, "getbbox")
return self.font.getsize(text)[1] return self.font.getsize(text)[1]
def getmask( def getmask(
@ -796,6 +848,14 @@ class TransposedFont:
self.orientation = orientation # any 'transpose' argument, or None self.orientation = orientation # any 'transpose' argument, or None
def getsize(self, text, *args, **kwargs): def getsize(self, text, *args, **kwargs):
"""
.. deprecated:: 9.2.0
Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead.
"""
deprecate("getsize", 10, "getbbox or getlength")
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
w, h = self.font.getsize(text) w, h = self.font.getsize(text)
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
return h, w return h, w
@ -807,6 +867,23 @@ class TransposedFont:
return im.transpose(self.orientation) return im.transpose(self.orientation)
return im return im
def getbbox(self, text, *args, **kwargs):
# TransposedFont doesn't support getmask2, move top-left point to (0, 0)
# this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
width = right - left
height = bottom - top
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
return 0, 0, height, width
return 0, 0, width, height
def getlength(self, text, *args, **kwargs):
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
raise ValueError(
"text length is undefined for text rotated by 90 or 270 degrees"
)
return self.font.getlength(text, *args, **kwargs)
def load(filename): def load(filename):
""" """