Skip to content

Add ImageText use_max_line_height()#9667

Open
radarhere wants to merge 2 commits into
python-pillow:mainfrom
radarhere:use_max_line_height
Open

Add ImageText use_max_line_height()#9667
radarhere wants to merge 2 commits into
python-pillow:mainfrom
radarhere:use_max_line_height

Conversation

@radarhere

@radarhere radarhere commented Jun 11, 2026

Copy link
Copy Markdown
Member

Resolves #1646. Alternative to #9581

Currently, the bottom of the bounding box of the text "A" is used to determine text line height.

#1646 found that other characters may have larger text height, leading to overlap between lines of text. While this is hard to see with most fonts, I found Great Vibes.

from PIL import Image, ImageDraw, ImageFont, ImageText
font = ImageFont.truetype("Tests/fonts/GreatVibes-Regular.ttf", 120)
text = ImageText.Text("adjust\nYellow", font)

im = Image.new("RGB", (309, 306))
draw = ImageDraw.Draw(im)
draw.text((0, 0), text, "#ff0")
im.save("before.png")

gives
before

This PR adds use_max_line_height() to ImageText. If I call that, the largest bounding box from all of the characters in the given text is used (not all possible characters of the font). Using that method, I get
after

@mergify

This comment was marked as outdated.

@hugovk

hugovk commented Jun 28, 2026

Copy link
Copy Markdown
Member

API:

I'm not sure about adding a new public function to opt in to different calculations. Should this instead be the default?


Tests:

This is failing for me, with RAQM not installed:

Details
❯ tox -e py314 -- Tests/test_imagetext.py
.pkg: _optional_hooks> python /Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/site-packages/pyproject_api/_backend.py True backend
.pkg: get_requires_for_build_sdist> python /Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/site-packages/pyproject_api/_backend.py True backend
.pkg: build_sdist> python /Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/site-packages/pyproject_api/_backend.py True backend
py314: install_package> python -I -m pip install --force-reinstall --no-deps /Users/hugo/github/Pillow/.tox/.tmp/package/6/pillow-12.3.0.dev0.tar.gz
py314: commands[0]> .tox/py314/bin/python selftest.py
--------------------------------------------------------------------
Pillow 12.3.0.dev0
Python 3.14.5 (v3.14.5:5607950ef23, May 10 2026, 07:38:09) [Clang 21.0.0 (clang-2100.0.123.102)]
--------------------------------------------------------------------
Python executable is /Users/hugo/github/Pillow/.tox/py314/bin/python
Environment Python files loaded from /Users/hugo/github/Pillow/.tox/py314
System Python files loaded from /Library/Frameworks/Python.framework/Versions/3.14
--------------------------------------------------------------------
Python Pillow modules loaded from /Users/hugo/github/Pillow/.tox/py314/lib/python3.14/site-packages/PIL
Binary Pillow modules loaded from /Users/hugo/github/Pillow/.tox/py314/lib/python3.14/site-packages/PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 12.3.0.dev0
--- TKINTER support ok, loaded 9.0
--- FREETYPE2 support ok, loaded 2.14.3
--- LITTLECMS2 support ok, loaded 2.19
--- WEBP support ok, loaded 1.6.0
--- AVIF support ok, loaded 1.4.2
--- JPEG support ok, compiled for libjpeg-turbo 3.1.4.1
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.4
--- ZLIB (PNG/ZIP) support ok, loaded 1.2.12
--- LIBTIFF support ok, loaded 4.7.1
*** RAQM (Bidirectional Text) support not installed
*** LIBIMAGEQUANT (Quantization method) support not installed
--- XCB (X protocol) support ok
--------------------------------------------------------------------
Running selftest:
--- 59 tests passed.
py314: commands[1]> .tox/py314/bin/python -m pytest --dist worksteal --numprocesses auto -W always Tests/test_imagetext.py
Test session starts (platform: darwin, Python 3.14.5, pytest 9.1.1, pytest-sugar 1.1.1)
cachedir: .tox/py314/.pytest_cache
--------------------------------------------------------------------
Pillow 12.3.0.dev0
Python 3.14.5 (v3.14.5:5607950ef23, May 10 2026, 07:38:09) [Clang 21.0.0 (clang-2100.0.123.102)]
--------------------------------------------------------------------
Python executable is /Users/hugo/github/Pillow/.tox/py314/bin/python
Environment Python files loaded from /Users/hugo/github/Pillow/.tox/py314
System Python files loaded from /Library/Frameworks/Python.framework/Versions/3.14
--------------------------------------------------------------------
Python Pillow modules loaded from /Users/hugo/github/Pillow/.tox/py314/lib/python3.14/site-packages/PIL
Binary Pillow modules loaded from /Users/hugo/github/Pillow/.tox/py314/lib/python3.14/site-packages/PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 12.3.0.dev0
--- TKINTER support ok, loaded 9.0
--- FREETYPE2 support ok, loaded 2.14.3
--- LITTLECMS2 support ok, loaded 2.19
--- WEBP support ok, loaded 1.6.0
--- AVIF support ok, loaded 1.4.2
--- JPEG support ok, compiled for libjpeg-turbo 3.1.4.1
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.4
--- ZLIB (PNG/ZIP) support ok, loaded 1.2.12
--- LIBTIFF support ok, loaded 4.7.1
*** RAQM (Bidirectional Text) support not installed
*** LIBIMAGEQUANT (Quantization method) support not installed
--- XCB (X protocol) support ok
--------------------------------------------------------------------

rootdir: /Users/hugo/github/Pillow
configfile: pyproject.toml
plugins: cov-7.1.0, xdist-3.8.0, timeout-2.4.0, sugar-1.1.1
8 workers [38 items]    collecting ...

 Tests/test_imagetext.py ✓✓s✓sss✓✓✓✓✓✓✓✓sss✓✓✓✓✓✓✓✓✓✓✓✓✓✓s✓✓✓                95% █████████▌

―――――――――――――――――――――――――――――――― test_use_max_line_height ―――――――――――――――――――――――――――――――――
[gw4] darwin -- Python 3.14.5 /Users/hugo/github/Pillow/.tox/py314/bin/python

    def test_use_max_line_height() -> None:
        font = ImageFont.truetype("Tests/fonts/GreatVibes-Regular.ttf", 120)
        text = ImageText.Text("adjust\nYellow", font)
        text.use_max_line_height()

        im = Image.new("RGB", (309, 306))
        draw = ImageDraw.Draw(im)
        draw.text((0, 0), text, "#ff0")
        expected = "Tests/images/use_max_line_height.png"
        if sysconfig.get_platform() in ("win32", "win-amd64"):
            assert_image_similar_tofile(im, expected, 12.28)
        else:
>           assert_image_equal_tofile(im, expected)

Tests/test_imagetext.py:92:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Tests/helper.py:109: in assert_image_equal_tofile
    assert_image_equal(a, converted_im, msg)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

a = <PIL.Image.Image image mode=RGB size=309x306 at 0x110501940>
b = <PIL.PngImagePlugin.PngImageFile image mode=RGB size=309x306 at 0x107FAE270>
msg = None

    def assert_image_equal(a: Image.Image, b: Image.Image, msg: str | None = None) -> None:
        assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
        assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
        if a.tobytes() != b.tobytes():
            try:
                url = upload(a, b)
                if url:
                    logger.error("URL for test images: %s", url)
            except Exception:
                pass

>           pytest.fail(msg or "got different content")
E           Failed: got different content

Tests/helper.py:98: Failed

 Tests/test_imagetext.py ⨯✓                                                 100% ██████████
================================= short test summary info =================================
SKIPPED [1] Tests/test_imagetext.py:95: raqm not available
SKIPPED [5] Tests/test_imagetext.py:60: raqm not available
SKIPPED [1] Tests/test_imagetext.py:162: raqm not available
SKIPPED [1] Tests/test_imagetext.py:47: raqm not available
FAILED Tests/test_imagetext.py::test_use_max_line_height - Failed: got different content

Results (2.22s):
      29 passed
       1 failed
         - Tests/test_imagetext.py:80 test_use_max_line_height
       8 skipped
py314: exit 1 (2.45 seconds) /Users/hugo/github/Pillow> .tox/py314/bin/python -m pytest --dist worksteal --numprocesses auto -W always Tests/test_imagetext.py pid=827
.pkg: _exit> python /Library/Frameworks/Python.framework/Versions/3.15/lib/python3.15/site-packages/pyproject_api/_backend.py True backend
  py314: FAIL code 1 (19.76=setup[15.52]+cmd[1.79,2.45] seconds)
  evaluation failed :( (19.80 seconds)

It passes after brew install libraqm. Should the test be made more tolerant, or skip without RAQM?

@radarhere

Copy link
Copy Markdown
Member Author

Should the test be made more tolerant, or skip without RAQM?

I've corrected that test so that it is more tolerant for RAQM. It turns out it wasn't Windows that was causing the problem, it was RAQM.

I'm not sure about adding a new public function to opt in to different calculations. Should this instead be the default?

Making it the default behaviour at the moment would cause 48 tests to fail, which I believe would result in a number of complaints from users about the change.

An alternative is just to close #1646, and say that if users want to draw text like this, they can't use our multiline functionality. They have to draw individual lines of text and calculate the next position themselves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multiline text line height not being calculated correctly

2 participants