Repository: matplotlib/matplotlib
File: lib/matplotlib/text.py (Python)

Code before:

def _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi):
    """
    Call ``renderer.get_text_width_height_descent``, caching the results.

    To get the rendered size of a string we need to know:
      - what renderer we are using
      - the current dpi of the renderer
      - the string
      - the font properties
      - is it math text or not

    We do this as a two-layer cache with the outer layer being tied to a
    renderer instance and the inner layer handling everything else.

    The outer layer is implemented as `.WeakKeyDictionary` keyed on the
    renderer.  As long as someone else is holding a hard ref to the renderer
    we will keep the cache alive, but it will be automatically dropped when
    the renderer is garbage collected.

    The inner layer is a plain dict keyed on (text, fontprop, ismath, dpi).
    As the dpi is mutable on the renderer, we need to explicitly include it as
    part of the cache key even though we do not directly use it (it is used in
    the method call on the renderer).
    """
    inner_cache = _text_metrics_cache.setdefault(renderer, {})

    cache_key = (text, fontprop, ismath, dpi)
    result = inner_cache.get(cache_key)
    if result is None:
        result = renderer.get_text_width_height_descent(text, fontprop, ismath)
        inner_cache[cache_key] = result
    return result


# See _get_text_metrics_with_cache docstring for details on the cache structure.
_text_metrics_cache = weakref.WeakKeyDictionary()


@_docstring.interpd
@_api.define_aliases({
    "color": ["c"],
    "fontproperties": ["font", "font_properties"],
    "fontfamily": ["family"],
    "fontname": ["name"],
    "fontsize": ["size"],
    "fontstretch": ["stretch"],
    "fontstyle": ["style"],
    "fontvariant": ["variant"],
    "fontweight": ["weight"],
    "horizontalalignment": ["ha"],

Review comment (performance):
Unless I am mistaken this makes the inner cache unbounded in size, which is not something we want (see #30400 and linked threads).

Code after:
            (xt, yt - d),
            (xt + w, yt + a),
            (xt + w, yt - d),
        ])
    projected_xs, projected_ys = zip(*projected_xys)

    xt_box, yt_box = min(projected_xs), min(projected_ys)
    w_box, h_box = max(projected_xs) - xt_box, max(projected_ys) - yt_box

    x_box, y_box = _rotate(theta).transform((xt_box, yt_box))

    return x_box, y_box, w_box, h_box


def _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi):
    """Call ``renderer.get_text_width_height_descent``, caching the results."""

    # hit the outer cache layer and get the function to compute the metrics
    # for this renderer instance
    get_text_metrics = _get_text_metrics_function(renderer)
    # call the function to compute the metrics and return
    #
    # We pass a copy of the fontprop because FontProperties is both mutable and
    # has a `__hash__` that depends on that mutable state.  This is not ideal
    # as it means the hash of an object is not stable over time which leads to
    # very confusing behavior when used as keys in dictionaries or hashes.
    return get_text_metrics(text, fontprop.copy(), ismath, dpi)


def _get_text_metrics_function(input_renderer, _cache=weakref.WeakKeyDictionary()):
    """
    Helper function to provide a two-layered cache for font metrics


    To get the rendered size of a size of string we need to know:
      - what renderer we are using
      - the current dpi of the renderer
      - the string
      - the font properties
      - is it math text or not

    We do this as a two-layer cache with the outer layer being tied to a
    renderer instance and the inner layer handling everything else.

    The outer layer is implemented as `.WeakKeyDictionary` keyed on the
    renderer.  As long as someone else is holding a hard ref to the renderer
    we will keep the cache alive, but it will be automatically dropped when
    the renderer is garbage collected.

    The inner layer is provided by an lru_cache with a large maximum size (such
    that we expect very few cache misses in actual use cases).  As the