Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a5efd24

Browse filesBrowse files
committed
Rework/fix Text layout cache.
Instead of caching the text layout based on a bunch of properties, only cache the computation of the text's metrics, which 1) should be the most expensive part (everything else in _get_layout is relatively simple iteration and arithmetic) and 2) depends on fewer implicit parameters. In fact, the old cache key was insufficient in that it would conflate usetex and non-usetex strings together, even though they have different metrics; e.g. with the (extremely artificial) example ```python figtext(.1, .5, "foo\nbar", size=32) # (0) figtext(.1, .5, "foo\nbar", usetex=True, size=32, c="r", alpha=.5) # (1) figtext(.3, .5, "foo\nbar", usetex=True, size=32, c="r", alpha=.5) # (2) ``` the linespacing of the first usetex string (1) would be "wrong": it is bigger that the one of the second usetex string (2), because it instead reuses the layout computed for the non-usetex string (0). The motivation is also to in the future let the renderer have better control on cache invalidation (with a yet-to-be-added renderer method), e.g. multiple instances of the same renderer cache could share the same layout info.
1 parent 76012ae commit a5efd24
Copy full SHA for a5efd24

File tree

2 files changed

+44
-23
lines changed
Filter options

2 files changed

+44
-23
lines changed

‎lib/matplotlib/tests/test_text.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_text.py
+24Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/text.py
+20-23Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.