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
(input_renderer, _cache=weakref.WeakKeyDictionary())
| 60 | |
| 61 | |
| 62 | def _get_text_metrics_function(input_renderer, _cache=weakref.WeakKeyDictionary()): |
| 63 | """ |
| 64 | Helper function to provide a two-layered cache for font metrics |
| 65 | |
| 66 | |
| 67 | To get the rendered size of a size of string we need to know: |
| 68 | - what renderer we are using |
| 69 | - the current dpi of the renderer |
| 70 | - the string |
| 71 | - the font properties |
| 72 | - is it math text or not |
| 73 | |
| 74 | We do this as a two-layer cache with the outer layer being tied to a |
| 75 | renderer instance and the inner layer handling everything else. |
| 76 | |
| 77 | The outer layer is implemented as `.WeakKeyDictionary` keyed on the |
| 78 | renderer. As long as someone else is holding a hard ref to the renderer |
| 79 | we will keep the cache alive, but it will be automatically dropped when |
| 80 | the renderer is garbage collected. |
| 81 | |
| 82 | The inner layer is provided by an lru_cache with a large maximum size (such |
| 83 | that we expect very few cache misses in actual use cases). As the |
| 84 | dpi is mutable on the renderer, we need to explicitly include it as part of |
| 85 | the cache key on the inner layer even though we do not directly use it (it is |
| 86 | used in the method call on the renderer). |
| 87 | |
| 88 | This function takes a renderer and returns a function that can be used to |
| 89 | get the font metrics. |
| 90 | |
| 91 | Parameters |
| 92 | ---------- |
| 93 | input_renderer : maplotlib.backend_bases.RendererBase |
| 94 | The renderer to set the cache up for. |
| 95 | |
| 96 | _cache : dict, optional |
| 97 | We are using the mutable default value to attach the cache to the function. |
| 98 | |
| 99 | In principle you could pass a different dict-like to this function to inject |
| 100 | a different cache, but please don't. This is an internal function not meant to |
| 101 | be reused outside of the narrow context we need it for. |
| 102 | |
| 103 | There is a possible race condition here between threads, we may need to drop the |
| 104 | mutable default and switch to a threadlocal variable in the future. |
| 105 | |
| 106 | """ |
| 107 | if (_text_metrics := _cache.get(input_renderer, None)) is None: |
| 108 | # We are going to include this in the closure we put as values in the |
| 109 | # cache. Closing over a hard-ref would create an unbreakable reference |
| 110 | # cycle. |
| 111 | renderer_ref = weakref.ref(input_renderer) |
| 112 | |
| 113 | # define the function locally to get a new lru_cache per renderer |
| 114 | @functools.lru_cache(4096) |
| 115 | # dpi is unused, but participates in cache invalidation (via the renderer). |
| 116 | def _text_metrics(text, fontprop, ismath, dpi): |
| 117 | # this should never happen under normal use, but this is a better error to |
| 118 | # raise than an AttributeError on `None` |
| 119 | if (local_renderer := renderer_ref()) is None: |
no test coverage detected
searching dependent graphs…