diff --git a/doc/api/font_manager_api.rst b/doc/api/font_manager_api.rst index ba1d785ca939..1a1b06da1fa9 100644 --- a/doc/api/font_manager_api.rst +++ b/doc/api/font_manager_api.rst @@ -4,6 +4,7 @@ .. automodule:: matplotlib.font_manager :members: + :exclude-members: FontEntry :undoc-members: :show-inheritance: diff --git a/galleries/examples/widgets/menu.py b/galleries/examples/widgets/menu.py index 8d3db3d1b9c3..e948d5e00863 100644 --- a/galleries/examples/widgets/menu.py +++ b/galleries/examples/widgets/menu.py @@ -5,19 +5,22 @@ Using texts to construct a simple menu. """ + +from dataclasses import dataclass + import matplotlib.pyplot as plt import matplotlib.artist as artist import matplotlib.patches as patches +from matplotlib.typing import ColorType +@dataclass class ItemProperties: - def __init__(self, fontsize=14, labelcolor='black', bgcolor='yellow', - alpha=1.0): - self.fontsize = fontsize - self.labelcolor = labelcolor - self.bgcolor = bgcolor - self.alpha = alpha + fontsize: float = 14 + labelcolor: ColorType = 'black' + bgcolor: ColorType = 'yellow' + alpha: float = 1.0 class MenuItem(artist.Artist): @@ -130,7 +133,7 @@ def on_move(self, event): menuitems = [] for label in ('open', 'close', 'save', 'save as', 'quit'): def on_select(item): - print('you selected %s' % item.labelstr) + print(f'you selected {item.labelstr}') item = MenuItem(fig, label, props=props, hoverprops=hoverprops, on_select=on_select) menuitems.append(item) diff --git a/lib/matplotlib/_text_helpers.py b/lib/matplotlib/_text_helpers.py index 8625186ba8ec..dc0540ea14e4 100644 --- a/lib/matplotlib/_text_helpers.py +++ b/lib/matplotlib/_text_helpers.py @@ -2,14 +2,21 @@ Low-level text helper utilities. """ +from __future__ import annotations + import dataclasses from . import _api -from .ft2font import KERNING_DEFAULT, LOAD_NO_HINTING +from .ft2font import KERNING_DEFAULT, LOAD_NO_HINTING, FT2Font -LayoutItem = dataclasses.make_dataclass( - "LayoutItem", ["ft_object", "char", "glyph_idx", "x", "prev_kern"]) +@dataclasses.dataclass(frozen=True) +class LayoutItem: + ft_object: FT2Font + char: str + glyph_idx: int + x: float + prev_kern: float def warn_on_missing_glyph(codepoint, fontnames): @@ -38,9 +45,10 @@ def warn_on_missing_glyph(codepoint, fontnames): def layout(string, font, *, kern_mode=KERNING_DEFAULT): """ - Render *string* with *font*. For each character in *string*, yield a - (glyph-index, x-position) pair. When such a pair is yielded, the font's - glyph is set to the corresponding character. + Render *string* with *font*. + + For each character in *string*, yield a LayoutItem instance. When such an instance + is yielded, the font's glyph is set to the corresponding character. Parameters ---------- @@ -53,8 +61,7 @@ def layout(string, font, *, kern_mode=KERNING_DEFAULT): Yields ------ - glyph_index : int - x_position : float + LayoutItem """ x = 0 prev_glyph_idx = None diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index d66e199b25b2..da105878b575 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -2378,8 +2378,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): multibyte_glyphs = [] prev_was_multibyte = True prev_font = font - for item in _text_helpers.layout( - s, font, kern_mode=KERNING_UNFITTED): + for item in _text_helpers.layout(s, font, kern_mode=KERNING_UNFITTED): if _font_supports_glyph(fonttype, ord(item.char)): if prev_was_multibyte or item.ft_object != prev_font: singlebyte_chunks.append((item.ft_object, item.x, [])) @@ -2389,9 +2388,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None): singlebyte_chunks[-1][2].append(item.char) prev_was_multibyte = False else: - multibyte_glyphs.append( - (item.ft_object, item.x, item.glyph_idx) - ) + multibyte_glyphs.append((item.ft_object, item.x, item.glyph_idx)) prev_was_multibyte = True # Do the rotation and global translation as a single matrix # concatenation up front diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 52e6207c63ec..73da3c418dd7 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -25,6 +25,8 @@ # - setWeights function needs improvement # - 'light' is an invalid weight value, remove it. +from __future__ import annotations + from base64 import b64encode from collections import namedtuple import copy @@ -41,7 +43,6 @@ import subprocess import sys import threading -from typing import Union import matplotlib as mpl from matplotlib import _api, _afm, cbook, ft2font @@ -304,42 +305,35 @@ def findSystemFonts(fontpaths=None, fontext='ttf'): return [fname for fname in fontfiles if os.path.exists(fname)] -def _fontentry_helper_repr_png(fontent): - from matplotlib.figure import Figure # Circular import. - fig = Figure() - font_path = Path(fontent.fname) if fontent.fname != '' else None - fig.text(0, 0, fontent.name, font=font_path) - with BytesIO() as buf: - fig.savefig(buf, bbox_inches='tight', transparent=True) - return buf.getvalue() - - -def _fontentry_helper_repr_html(fontent): - png_stream = _fontentry_helper_repr_png(fontent) - png_b64 = b64encode(png_stream).decode() - return f"" - - -FontEntry = dataclasses.make_dataclass( - 'FontEntry', [ - ('fname', str, dataclasses.field(default='')), - ('name', str, dataclasses.field(default='')), - ('style', str, dataclasses.field(default='normal')), - ('variant', str, dataclasses.field(default='normal')), - ('weight', Union[str, int], dataclasses.field(default='normal')), - ('stretch', str, dataclasses.field(default='normal')), - ('size', str, dataclasses.field(default='medium')), - ], - namespace={ - '__doc__': """ +@dataclasses.dataclass(frozen=True) +class FontEntry: + """ A class for storing Font properties. It is used when populating the font lookup dictionary. - """, - '_repr_html_': lambda self: _fontentry_helper_repr_html(self), - '_repr_png_': lambda self: _fontentry_helper_repr_png(self), - } -) + """ + + fname: str = '' + name: str = '' + style: str = 'normal' + variant: str = 'normal' + weight: str | int = 'normal' + stretch: str = 'normal' + size: str = 'medium' + + def _repr_html_(self) -> str: + png_stream = self._repr_png_() + png_b64 = b64encode(png_stream).decode() + return f"" + + def _repr_png_(self) -> bytes: + from matplotlib.figure import Figure # Circular import. + fig = Figure() + font_path = Path(self.fname) if self.fname != '' else None + fig.text(0, 0, self.name, font=font_path) + with BytesIO() as buf: + fig.savefig(buf, bbox_inches='tight', transparent=True) + return buf.getvalue() def ttfFontProperty(font): @@ -926,8 +920,7 @@ def default(self, o): try: # Cache paths of fonts shipped with Matplotlib relative to the # Matplotlib data path, which helps in the presence of venvs. - d["fname"] = str( - Path(d["fname"]).relative_to(mpl.get_data_path())) + d["fname"] = str(Path(d["fname"]).relative_to(mpl.get_data_path())) except ValueError: pass return d @@ -944,10 +937,9 @@ def _json_decode(o): r.__dict__.update(o) return r elif cls == 'FontEntry': - r = FontEntry.__new__(FontEntry) - r.__dict__.update(o) - if not os.path.isabs(r.fname): - r.fname = os.path.join(mpl.get_data_path(), r.fname) + if not os.path.isabs(o['fname']): + o['fname'] = os.path.join(mpl.get_data_path(), o['fname']) + r = FontEntry(**o) return r else: raise ValueError("Don't know how to deserialize __class__=%s" % cls)