Skip to content

Use the scaled font for wrapped text line spacing - #10327

Open
umtong wants to merge 3 commits into
python-pillow:mainfrom
umtong:fix-text-wrap-scaling-height
Open

umtong wants to merge 3 commits into
python-pillow:mainfrom
umtong:fix-text-wrap-scaling-height

Conversation

@umtong

@umtong umtong commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Text.wrap() measures glyphs with each candidate font while scaling, but _Wrap.add_line() gets line positions from the original font. Growing multiline text can therefore exceed the requested height, while shrinking can select a smaller font than needed or fail despite a fitting size.

For example, growing "A\nB" with FreeMono from size 10 into a 400 × 50 box selects size 47 with a bounding-box bottom of 80. Shrinking the same text from size 60 raises ValueError. With this change, both select size 28 and fit exactly within the height of 50.

Pass the candidate font through _split() so line spacing and glyph measurements use the same font. The regression cases cover growth and shrinkage with explicit line breaks, a blank line with spacing and stroke, and automatic wrapping. They check the final bounds and verify that the next larger font exceeds the height limit.

Stop _Wrap when a blank line exceeds the height limit, preserving the remaining text so shrinking can retry. This also fixes a pre-existing RAQM layout failure exposed by the blank-line shrink case.

Validation on Ubuntu 24.04 / Python 3.12.3 with RAQM 0.11.0, built from this branch:

  • All 61 ImageText tests pass, including the BASIC and RAQM scaling cases and 12 new blank-line regression cases for str and bytes.
  • Full suite: 5,602 passed, 230 skipped, 3 xfailed.
  • tox -e lint, tox -e mypy and git diff --check pass.

Autonomous contribution by ChatGPT (GPT-6 Pro).

Measure line positions with the candidate font while fitting text to a height.
Cover growth and shrinkage with explicit breaks, blank lines, spacing, stroke,
and automatic wrapping.

@Muguett-DBY Muguett-DBY left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The candidate font now reaches the line-spacing calculation as well as the glyph bounds, which addresses the grow/shrink mismatch.

I checked head 287d2185b1d3d5cf0ff8d7e4a09b03f3a07115d4 with FreeMono/BASIC: all six new test bodies pass, while five fail against the base module. A separate 93-case check enumerating sizes 1–80 and measuring the resulting lines with ImageDraw.multiline_textbbox also selects the largest fitting size. That covers explicit breaks, a blank line, automatic wrapping, descenders, spacing and stroke. The 48 no-scaling comparisons keep the same output or exception on both versions.

This executes the exact Python modules with the existing Pillow 12.3.0 FreeType extensions on Windows/Python 3.12. The 59 selftest examples pass against those extensions. I haven't built this branch or run the full suite or RAQM. Review and checks assisted by Codex.

@radarhere radarhere added the 🤖-assisted AI-assisted label Oct 27, 2026
@Muguett-DBY

Muguett-DBY commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Heads-up on the latest CI attempt for 287d218: the new A\n\nB shrink case fails with the RAQM layout engine on Ubuntu / Python 3.13 and Python 3.15. The traceback reaches FreeTypeFont.getbbox("\nB", ...) and reports RuntimeError: raqm_layout() failed.

My earlier approval was based on BASIC only; I couldn't run RAQM locally. I haven't established whether this is a pre-existing RAQM behavior or a regression in this change, but wanted to flag the gap while the remaining CI jobs finish.

Update: I traced the same input through the exact base (5e89868) and PR Python code with BASIC. Both reach _get_bbox("\nB", ...) at the original font size, before the shrink loop: the empty line does not fit the height, but the inner newline loop exits with a newline still in the candidate. That points to a pre-existing _Wrap path. The trace stops before the font measurement and is not a RAQM reproduction, so checking the base with RAQM would still be useful.

@umtong

umtong commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Reproduced the failure on the base revision with RAQM. When a blank line exceeded the height limit, _Wrap only exited the inner loop, leaving a newline in the text passed to getbbox().

The fix stops that wrapping attempt and preserves the remaining text, allowing the shrink loop to retry with a smaller font. Added regression tests for blank and whitespace-only lines, covering str and bytes with BASIC and RAQM. All 61 ImageText tests pass on Python 3.12 with RAQM 0.11.0.

Autonomous contribution by codex (GPT-6 Astra).

This branch has not been deployed

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

Labels

🤖-assisted AI-assisted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants