Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
43 changes: 43 additions & 0 deletions lib/matplotlib/tests/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,49 @@ 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():
# transform_rotates_text with an axis-aligned transform can make
# get_rotation() float-round to 360.0, breaking the wrap logic.
s = 'This is a very long text that should be wrapped multiple times.'

fig = plt.figure(figsize=(6, 4))
transform = mtransforms.Affine2D().rotate_deg(270) + fig.transFigure
t = fig.text(0.0, 0.0, s, transform=transform, wrap=True,
rotation=90, transform_rotates_text=True)
fig.canvas.draw()

fig_ref = plt.figure(figsize=(6, 4))
t_ref = fig_ref.text(0.0, 0.0, s, wrap=True, rotation=0)
fig_ref.canvas.draw()

assert 0 <= t.get_rotation() < 360
assert t._get_wrapped_text() == t_ref._get_wrapped_text()


@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
42 changes: 25 additions & 17 deletions lib/matplotlib/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,10 @@ def _char_index_at(self, x):
def get_rotation(self):
"""Return the text angle in degrees in the range [0, 360)."""
if self.get_transform_rotates_text():
return self.get_transform().transform_angles(
angle = self.get_transform().transform_angles(
[self._rotation], [self.get_unitless_position()]).item(0) % 360
# `(tiny_negative) % 360` can float-round to exactly 360.0.
return 0.0 if angle == 360 else angle
else:
return self._rotation

Expand Down Expand Up @@ -757,23 +759,29 @@ 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.
"""
if rotation > 270:
quad = rotation - 270
h1 = (y0 - figure_box.y0) / math.cos(math.radians(quad))
h2 = (figure_box.x1 - x0) / math.cos(math.radians(90 - quad))
elif rotation > 180:
quad = rotation - 180
h1 = (x0 - figure_box.x0) / math.cos(math.radians(quad))
h2 = (y0 - figure_box.y0) / math.cos(math.radians(90 - quad))
elif rotation > 90:
quad = rotation - 90
h1 = (figure_box.y1 - y0) / math.cos(math.radians(quad))
h2 = (x0 - figure_box.x0) / math.cos(math.radians(90 - quad))
else:
h1 = (figure_box.x1 - x0) / math.cos(math.radians(rotation))
h2 = (figure_box.y1 - y0) / math.cos(math.radians(90 - rotation))
# Branch bounds are inclusive at the cardinals so each cardinal lands
# in a branch where quad=0 — there cos(0)=1 and sin(0)=0 exactly,
# avoiding the float noise that cos(radians(90)) would introduce.
# sin(radians(x)) replaces cos(radians(90 - x)) so the div-by-0
# propagates to +inf, and fmin discards it (and any 0/0 NaN at corners).
with np.errstate(divide="ignore", invalid="ignore"):
if rotation >= 270:
quad = rotation - 270
h1 = (y0 - figure_box.y0) / np.cos(np.radians(quad))
h2 = (figure_box.x1 - x0) / np.sin(np.radians(quad))
elif rotation >= 180:
quad = rotation - 180
h1 = (x0 - figure_box.x0) / np.cos(np.radians(quad))
h2 = (y0 - figure_box.y0) / np.sin(np.radians(quad))
elif rotation >= 90:
quad = rotation - 90
h1 = (figure_box.y1 - y0) / np.cos(np.radians(quad))
h2 = (x0 - figure_box.x0) / np.sin(np.radians(quad))
else:
h1 = (figure_box.x1 - x0) / np.cos(np.radians(rotation))
h2 = (figure_box.y1 - y0) / np.sin(np.radians(rotation))

return min(h1, h2)
return np.fmin(h1, h2)

def _get_rendered_text_width(self, text):
"""
Expand Down
Loading