From 969e4687497d447581e950ed26043a988f50e21b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 21 Jul 2025 13:07:38 +1000 Subject: [PATCH] Allow ImageDraw text() to use ImageText --- Tests/test_imagetext.py | 35 +++++++++++++++++++++++++++++++++-- docs/reference/ImageText.rst | 6 +++++- src/PIL/ImageDraw.py | 23 +++++++++++++---------- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Tests/test_imagetext.py b/Tests/test_imagetext.py index 3a3a58975..b58d048b5 100644 --- a/Tests/test_imagetext.py +++ b/Tests/test_imagetext.py @@ -2,9 +2,9 @@ from __future__ import annotations import pytest -from PIL import ImageFont, ImageText +from PIL import Image, ImageDraw, ImageFont, ImageText -from .helper import skip_unless_feature +from .helper import assert_image_similar_tofile, skip_unless_feature FONT_PATH = "Tests/fonts/FreeMono.ttf" @@ -39,3 +39,34 @@ def test_get_bbox(font: ImageFont.FreeTypeFont) -> None: assert ImageText.ImageText("M", font).get_bbox() == (0, 4, 12, 16) assert ImageText.ImageText("y", font).get_bbox() == (0, 7, 12, 20) assert ImageText.ImageText("a", font).get_bbox() == (0, 7, 12, 16) + + +def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None: + font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine) + text = ImageText.ImageText("Hello World!", font) + text.embed_color() + + im = Image.new("RGB", (300, 64), "white") + draw = ImageDraw.Draw(im) + draw.text((10, 10), text, "#fa6") + + assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1) + + +@skip_unless_feature("freetype2") +def test_stroke() -> None: + for suffix, stroke_fill in {"same": None, "different": "#0f0"}.items(): + # Arrange + im = Image.new("RGB", (120, 130)) + draw = ImageDraw.Draw(im) + font = ImageFont.truetype(FONT_PATH, 120) + text = ImageText.ImageText("A", font) + text.stroke(2, stroke_fill) + + # Act + draw.text((12, 12), text, "#f00") + + # Assert + assert_image_similar_tofile( + im, "Tests/images/imagedraw_stroke_" + suffix + ".png", 3.1 + ) diff --git a/docs/reference/ImageText.rst b/docs/reference/ImageText.rst index ad5439751..fa55b4f30 100644 --- a/docs/reference/ImageText.rst +++ b/docs/reference/ImageText.rst @@ -6,7 +6,7 @@ The :py:mod:`~PIL.ImageText` module defines a class with the same name. Instances of this class provide a way to use fonts with text strings or bytes. The result is a -simple API to apply styling to pieces of text and measure them. +simple API to apply styling to pieces of text and measure or draw them. Example ------- @@ -23,6 +23,10 @@ Example print(text.get_length()) # 154.0 print(text.get_bbox()) # (-2, 3, 156, 22) + im = Image.new("RGB", text.get_bbox()[2:]) + d = ImageDraw.Draw(im) + d.text((0, 0), text, "#f00") + Methods ------- diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 35ecbfb78..852e02698 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -537,7 +537,7 @@ class ImageDraw: def text( self, xy: tuple[float, float], - text: AnyStr, + text: AnyStr | ImageText.ImageText, fill: _Ink | None = None, font: ( ImageFont.ImageFont @@ -558,15 +558,18 @@ class ImageDraw: **kwargs: Any, ) -> None: """Draw text.""" - if font is None: - font = self._getfont(kwargs.get("font_size")) - imagetext = ImageText.ImageText( - text, font, self.mode, spacing, direction, features, language - ) - if embedded_color: - imagetext.embed_color() - if stroke_width: - imagetext.stroke(stroke_width, stroke_fill) + if isinstance(text, ImageText.ImageText): + imagetext = text + else: + if font is None: + font = self._getfont(kwargs.get("font_size")) + imagetext = ImageText.ImageText( + text, font, self.mode, spacing, direction, features, language + ) + if embedded_color: + imagetext.embed_color() + if stroke_width: + imagetext.stroke(stroke_width, stroke_fill) def getink(fill: _Ink | None) -> int: ink, fill_ink = self._getink(fill)