Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix text wrap width on figure edges (#31537)
Text with wrap=True at an axis-aligned rotation on the matching figure
edge collapsed to zero wrap width. In _get_dist_to_box the trig formula
divides by cos(radians(90)) (~6e-17): normally that term's large value
loses the min() to the sensible other side, but when the position sits
exactly on the edge its numerator is also zero and the returned
distance is zero.

Short-circuit cardinal rotations. Also normalize rotation to [0, 360)
with a small absolute tolerance so near-cardinal angles coming out of
transform_angles (e.g. -90.00000000000003 via transform_rotates_text)
still take the fast path instead of blowing up in the trig formula.

Regression tests cover each cardinal rotation on its broken edge plus
a transform_rotates_text case.
  • Loading branch information
FazeelUsmani committed Apr 22, 2026
commit a8560a99572798712d59d09569e480aec910f764
41 changes: 41 additions & 0 deletions lib/matplotlib/tests/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,47 @@ def test_wrap_no_wrap():
assert text._get_wrapped_text() == 'non wrapped text'


@pytest.mark.parametrize(
"x, y, rotation",
[(0.0, 1.0, 0),
(1.0, 0.5, 90),
(0.5, 1.0, 180),
(0.0, 0.5, 270)])
def test_wrap_on_figure_edge(x, y, rotation):
# Regression test for #31537 - wrap collapsed to zero on figure edges.
s = 'This is a very long text that should be wrapped multiple times.'
fig = plt.figure(figsize=(6, 4))
t = fig.text(x, y, s, wrap=True, rotation=rotation)
fig.canvas.draw()

# Compare to a nudged-off-the-edge reference that should wrap the same.
nudge = 1e-4
x_ref = x - nudge if x == 1.0 else x + nudge if x == 0.0 else x
y_ref = y - nudge if y == 1.0 else y + nudge if y == 0.0 else y
fig_ref = plt.figure(figsize=(6, 4))
t_ref = fig_ref.text(x_ref, y_ref, s, wrap=True, rotation=rotation)
fig_ref.canvas.draw()

assert t._get_wrapped_text() == t_ref._get_wrapped_text()


def test_wrap_on_figure_edge_transform_rotates_text():
# With transform_rotates_text=True, get_rotation() goes through
# transform_angles() and can return near-cardinal angles with float
# noise (e.g. -90.00000000000003 for a 270 degree rotation), which
# used to skip the cardinal-angle short-circuit in _get_dist_to_box.
from matplotlib.transforms import Affine2D
fig = plt.figure(figsize=(6, 4))
s = 'This is a very long text that should be wrapped multiple times.'
# rotate_deg(90).rotate_deg(-90) is identity geometrically, but carries
# enough numerical slop to perturb transform_angles.
transform = Affine2D().rotate_deg(90).rotate_deg(-90) + fig.transFigure
t = fig.text(0.0, 0.5, s, transform=transform, wrap=True, rotation=270,
transform_rotates_text=True)
fig.canvas.draw()
assert any(' ' in line for line in t._get_wrapped_text().split('\n'))


@check_figures_equal()
def test_buffer_size(fig_test, fig_ref):
# On old versions of the Agg renderer, large non-ascii single-character
Expand Down
15 changes: 15 additions & 0 deletions lib/matplotlib/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,21 @@ def _get_dist_to_box(self, rotation, x0, y0, figure_box):
Return the distance from the given points to the boundaries of a
rotated box, in pixels.
"""
# Normalize rotation; transform_angles can return values outside
# [0, 360) with tiny float noise.
rotation = rotation % 360

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AFAICS this is already done within set_rotation or get_rotation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, missed that. Dropping the % 360 line.

# Short-circuit cardinal angles, otherwise cos(radians(90)) makes
# the trig formula below blow up when the text is on the edge.
tol = 1e-10
Comment thread
timhoffm marked this conversation as resolved.
Outdated
if rotation < tol or rotation > 360 - tol:
return figure_box.x1 - x0
if abs(rotation - 90) < tol:
return figure_box.y1 - y0
if abs(rotation - 180) < tol:
return x0 - figure_box.x0
if abs(rotation - 270) < tol:
return y0 - figure_box.y0

if rotation > 270:
quad = rotation - 270
h1 = (y0 - figure_box.y0) / math.cos(math.radians(quad))
Expand Down
Loading