Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Use class form of data classes #27415

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/font_manager_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

.. automodule:: matplotlib.font_manager
:members:
:exclude-members: FontEntry
:undoc-members:
:show-inheritance:

Expand Down
17 changes: 10 additions & 7 deletions galleries/examples/widgets/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
23 changes: 15 additions & 8 deletions lib/matplotlib/_text_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
----------
Expand All @@ -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
Expand Down
7 changes: 2 additions & 5 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, []))
Expand All @@ -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
Expand Down
72 changes: 32 additions & 40 deletions lib/matplotlib/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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"<img src=\"data:image/png;base64, {png_b64}\" />"


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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making this frozen breaks JSON decoding of FontManager. Either you have to leave this unfrozen, or you have to adapt FontManager._json_decode():

r = FontEntry.__new__(FontEntry)
r.__dict__.update(o)

Maybe FontEntry(**o) will do? Or you have to go with object.__setattr__ similar to https://github.com/jsonpickle/jsonpickle/pull/397/files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just need to avoid changing the element after creation, which we can do by modifying the input dictionary beforehand.

I'm not sure why we were doing __new__ and __dict__.update instead of FontEntry(**o), but I've moved to the latter as suggested.

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"<img src=\"data:image/png;base64, {png_b64}\" />"

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):
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down