Repository: matplotlib/matplotlib

Issue:
[ENH]: Switch text wrapping boundary to subfigure
### Problem

xref https://github.com/matplotlib/matplotlib/issues/28358#issuecomment-2162342459

### Proposed solution

When https://github.com/matplotlib/matplotlib/pull/28177 is in, we could switch the wrapping box to subfigure.

Actions:
- [ ] change the `get_figure()` call in `Text._get_wrap_line_width`
- [ ] update `Text.set_wrap` documentation to explicitly mention subfigure.


Discussion:
I'm confused why this doesn't already work, since `Text.get_figure()` currently returns the parent figure rather than the root.

```python
In [11]: fig = plt.figure()

In [12]: sfig = fig.subfigures()

In [13]: ax = sfig.subplots()

In [14]: txt = ax.text(0, 0, "foo")

In [15]: txt
Out[15]: Text(0, 0, 'foo')

In [16]: txt.get_figure()
Out[16]: <matplotlib.figure.SubFigure at 0x7f2eff1b6ed0>

```
Should `figure_box` be involved in this line?
https://github.com/matplotlib/matplotlib/blob/14a04a02789e5deab2325531b46d570f16a29986/lib/matplotlib/text.py#L665

Edit: and some of the lines above it.  I guess when this was written `figure_box.x0` and `figure_box.y0` were always zero so didn't need accounting for.
> I'm confused why this doesn't already work

I have just checked that the code in https://github.com/matplotlib/matplotlib/issues/28358#issuecomment-2162342459 does not wrap - assuming it was due to the root figure, but it may well be that there is a calculation issue instead.
Titles do not have a wrapping problem... :confused: 

```python
fig = plt.figure()
sfig1, sfig2 = fig.subfigures(ncols=2)
ax1, ax2 = sfig2.subplots(nrows=2)
ax2.set_title("Another title that won't fit in this amount of space", wrap=True)
sfig2.suptitle("Long title that won't fit in this amount of space", wrap=True)
plt.show()
```
![image](https://github.com/matplotlib/matplotlib/assets/10599679/4c127c4e-f115-4714-8807-d599dad3f728)

I can confirm both that tick labels wrap incorrectly and titles wrap correctly and that the tick labels have the correct (sub) figure attached to them.

Very odd.



Patch:
diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py
index 7fc19c042a1f..af990ec1bf9f 100644
--- a/lib/matplotlib/text.py
+++ b/lib/matplotlib/text.py
@@ -606,9 +606,8 @@ def set_wrap(self, wrap):
         """
         Set whether the text can be wrapped.
 
-        Wrapping makes sure the text is completely within the figure box, i.e.
-        it does not extend beyond the drawing area. It does not take into
-        account any other artists.
+        Wrapping makes sure the text is confined to the (sub)figure box. It
+        does not take into account any other artists.
 
         Parameters
         ----------
@@ -657,16 +656,16 @@ def _get_dist_to_box(self, rotation, x0, y0, figure_box):
         """
         if rotation > 270:
             quad = rotation - 270
-            h1 = y0 / math.cos(math.radians(quad))
+            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 / math.cos(math.radians(quad))
-            h2 = y0 / math.cos(math.radians(90 - quad))
+            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 / math.cos(math.radians(90 - 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))


Test patch:
diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py
index f8837d8a5f1b..8904337f68ba 100644
--- a/lib/matplotlib/tests/test_text.py
+++ b/lib/matplotlib/tests/test_text.py
@@ -15,6 +15,7 @@
 from matplotlib.font_manager import FontProperties
 import matplotlib.patches as mpatches
 import matplotlib.pyplot as plt
+from matplotlib.gridspec import GridSpec
 import matplotlib.transforms as mtransforms
 from matplotlib.testing.decorators import check_figures_equal, image_comparison
 from matplotlib.testing._markers import needs_usetex
@@ -707,9 +708,13 @@ def test_large_subscript_title():
      (0.3, 0, 'right'),
      (0.3, 185, 'left')])
 def test_wrap(x, rotation, halign):
-    fig = plt.figure(figsize=(6, 6))
+    fig = plt.figure(figsize=(18, 18))
+    gs = GridSpec(nrows=3, ncols=3, figure=fig)
+    subfig = fig.add_subfigure(gs[1, 1])
+    # we only use the central subfigure, which does not align with any
+    # figure boundary, to ensure only subfigure boundaries are relevant
     s = 'This is a very long text that should be wrapped multiple times.'
-    text = fig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign)
+    text = subfig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign)
     fig.canvas.draw()
     assert text._get_wrapped_text() == ('This is a very long\n'
                                         'text that should be\n'