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)