Skip to content

Commit 9844e9f

Browse files
authored
Merge pull request matplotlib#22271 from anntzer/tc
Rework/fix Text layout cache.
2 parents 9a7bb1b + a5efd24 commit 9844e9f

File tree

2 files changed

+44
-23
lines changed

2 files changed

+44
-23
lines changed

lib/matplotlib/tests/test_text.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,3 +764,27 @@ def test_pdf_chars_beyond_bmp():
764764
plt.rcParams['mathtext.fontset'] = 'stixsans'
765765
plt.figure()
766766
plt.figtext(0.1, 0.5, "Mass $m$ \U00010308", size=30)
767+
768+
769+
@needs_usetex
770+
def test_metrics_cache():
771+
fig = plt.figure()
772+
fig.text(.3, .5, "foo\nbar")
773+
fig.text(.3, .5, "foo\nbar", usetex=True)
774+
fig.text(.5, .5, "foo\nbar", usetex=True)
775+
fig.canvas.draw()
776+
renderer = fig._cachedRenderer
777+
ys = {} # mapping of strings to where they were drawn in y with draw_tex.
778+
779+
def call(*args, **kwargs):
780+
renderer, x, y, s, *_ = args
781+
ys.setdefault(s, set()).add(y)
782+
783+
renderer.draw_tex = call
784+
fig.canvas.draw()
785+
assert [*ys] == ["foo", "bar"]
786+
# Check that both TeX strings were drawn with the same y-position for both
787+
# single-line substrings. Previously, there used to be an incorrect cache
788+
# collision with the non-TeX string (drawn first here) whose metrics would
789+
# get incorrectly reused by the first TeX string.
790+
assert len(ys["foo"]) == len(ys["bar"]) == 1

lib/matplotlib/text.py

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -273,30 +273,29 @@ def update_from(self, other):
273273
self._linespacing = other._linespacing
274274
self.stale = True
275275

276-
def _get_layout_cache_key(self, renderer):
277-
"""
278-
Return a hashable tuple of properties that lets `_get_layout` know
279-
whether a previously computed layout can be reused.
280-
"""
281-
return (
282-
self.get_unitless_position(), self.get_text(),
283-
hash(self._fontproperties),
284-
self._verticalalignment, self._horizontalalignment,
285-
self._linespacing,
286-
self._rotation, self._rotation_mode, self._transform_rotates_text,
287-
self.figure.dpi, weakref.ref(renderer),
276+
def _get_text_metrics_with_cache(
277+
self, renderer, text, fontproperties, ismath):
278+
"""
279+
Call ``renderer.get_text_width_height_descent``, caching the results.
280+
"""
281+
cache_key = (
282+
weakref.ref(renderer),
283+
text,
284+
hash(fontproperties),
285+
ismath,
286+
self.figure.dpi,
288287
)
288+
if cache_key not in self._cached:
289+
self._cached[cache_key] = renderer.get_text_width_height_descent(
290+
text, fontproperties, ismath)
291+
return self._cached[cache_key]
289292

290293
def _get_layout(self, renderer):
291294
"""
292295
Return the extent (bbox) of the text together with
293296
multiple-alignment information. Note that it returns an extent
294297
of a rotated text when necessary.
295298
"""
296-
key = self._get_layout_cache_key(renderer=renderer)
297-
if key in self._cached:
298-
return self._cached[key]
299-
300299
thisx, thisy = 0.0, 0.0
301300
lines = self.get_text().split("\n") # Ensures lines is not empty.
302301

@@ -306,16 +305,16 @@ def _get_layout(self, renderer):
306305
ys = []
307306

308307
# Full vertical extent of font, including ascenders and descenders:
309-
_, lp_h, lp_d = renderer.get_text_width_height_descent(
310-
"lp", self._fontproperties,
308+
_, lp_h, lp_d = self._get_text_metrics_with_cache(
309+
renderer, "lp", self._fontproperties,
311310
ismath="TeX" if self.get_usetex() else False)
312311
min_dy = (lp_h - lp_d) * self._linespacing
313312

314313
for i, line in enumerate(lines):
315314
clean_line, ismath = self._preprocess_math(line)
316315
if clean_line:
317-
w, h, d = renderer.get_text_width_height_descent(
318-
clean_line, self._fontproperties, ismath=ismath)
316+
w, h, d = self._get_text_metrics_with_cache(
317+
renderer, clean_line, self._fontproperties, ismath)
319318
else:
320319
w = h = d = 0
321320

@@ -439,9 +438,7 @@ def _get_layout(self, renderer):
439438
# now rotate the positions around the first (x, y) position
440439
xys = M.transform(offset_layout) - (offsetx, offsety)
441440

442-
ret = bbox, list(zip(lines, zip(ws, hs), *xys.T)), descent
443-
self._cached[key] = ret
444-
return ret
441+
return bbox, list(zip(lines, zip(ws, hs), *xys.T)), descent
445442

446443
def set_bbox(self, rectprops):
447444
"""

0 commit comments

Comments
 (0)