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

Skip to content

[with findfont diff] Parsing all families in font_manager #20496

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

Closed
wants to merge 15 commits into from
Closed
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
4 changes: 2 additions & 2 deletions examples/misc/logos2.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
def get_font_properties():
# The original font is Calibri, if that is not installed, we fall back
# to Carlito, which is metrically equivalent.
if 'Calibri' in matplotlib.font_manager.findfont('Calibri:bold'):
if 'Calibri' in matplotlib.font_manager.findfont('Calibri:bold').keys():
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you need .keys()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah that's right, I was probably searching for findfont usages within mpl and happen to change this eagerly.

return matplotlib.font_manager.FontProperties(family='Calibri',
weight='bold')
if 'Carlito' in matplotlib.font_manager.findfont('Carlito:bold'):
if 'Carlito' in matplotlib.font_manager.findfont('Carlito:bold').keys():
print('Original font not found. Falling back to Carlito. '
'The logo text will not be in the correct font.')
return matplotlib.font_manager.FontProperties(family='Carlito',
Expand Down
2 changes: 2 additions & 0 deletions examples/text_labels_and_annotations/font_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def print_glyphs(path):
"""
if path is None:
path = fm.findfont(fm.FontProperties()) # The default font.
path = next(iter(path.values())) # Get the first filepath

font = FT2Font(path)

Expand All @@ -60,6 +61,7 @@ def draw_font_table(path):
"""
if path is None:
path = fm.findfont(fm.FontProperties()) # The default font.
path = next(iter(path.values())) # Get the first filepath

font = FT2Font(path)
# A charmap is a mapping of "character codes" (in the sense of a character
Expand Down
3 changes: 3 additions & 0 deletions lib/matplotlib/_mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ def destroy(self):
def _get_font(self, font):
if font in self.fontmap:
basename = self.fontmap[font]
# TODO: allow multiple fonts
# for now settle with the first element
basename = next(iter(basename.values()))
else:
basename = font
cached_font = self._fonts.get(basename)
Expand Down
4 changes: 4 additions & 0 deletions lib/matplotlib/backends/_backend_pdf_ps.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ def get_text_width_height_descent(self, s, prop, ismath):
def _get_font_afm(self, prop):
fname = font_manager.findfont(
prop, fontext="afm", directory=self._afm_font_dir)

# TODO: allow multiple font caching
# for now pass the first font
fname = next(iter(fname.values()))
return _cached_get_afm_from_fname(fname)

def _get_font_ttf(self, prop):
Expand Down
13 changes: 9 additions & 4 deletions lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,11 +829,16 @@ def fontName(self, fontprop):

if isinstance(fontprop, str):
filename = fontprop
elif mpl.rcParams['pdf.use14corefonts']:
filename = findfont(
fontprop, fontext='afm', directory=RendererPdf._afm_font_dir)
else:
filename = findfont(fontprop)
if mpl.rcParams["pdf.use14corefonts"]:
filename = findfont(fontprop, fontext="afm",
directory=RendererPdf._afm_font_dir)
else:
filename = findfont(fontprop)

# TODO: allow multiple fonts for PDF backend
# for now settle with the first element
filename = next(iter(filename.values()))

Fx = self.fontNames.get(filename)
if Fx is None:
Expand Down
8 changes: 7 additions & 1 deletion lib/matplotlib/backends/backend_pgf.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ def get_fontspec():
for family, command in zip(families, commands):
# 1) Forward slashes also work on Windows, so don't mess with
# backslashes. 2) The dirname needs to include a separator.
path = pathlib.Path(fm.findfont(family))
path = fm.findfont(family)

# TODO: Allow multiple fonts
# for now stick with the first font
path = next(iter(path.values()))

path = pathlib.Path(path)
latex_fontspec.append(r"\%s{%s}[Path=\detokenize{%s}]" % (
command, path.name, path.parent.as_posix() + "/"))

Expand Down
55 changes: 46 additions & 9 deletions lib/matplotlib/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
# - setWeights function needs improvement
# - 'light' is an invalid weight value, remove it.

from collections import OrderedDict
import dataclasses
from functools import lru_cache
import json
Expand Down Expand Up @@ -1097,7 +1098,9 @@ def addfont(self, path):
def defaultFont(self):
# Lazily evaluated (findfont then caches the result) to avoid including
# the venv path in the json serialization.
return {ext: self.findfont(family, fontext=ext)

# TODO: allow embedding multiple fonts
return {ext: next(iter(self.findfont(family, fontext=ext).values()))
for ext, family in self.defaultFamily.items()}

def get_default_weight(self):
Expand Down Expand Up @@ -1304,16 +1307,38 @@ def findfont(self, prop, fontext='ttf', directory=None,
rc_params = tuple(tuple(rcParams[key]) for key in [
"font.serif", "font.sans-serif", "font.cursive", "font.fantasy",
"font.monospace"])
return self._findfont_cached(
prop, fontext, directory, fallback_to_default, rebuild_if_missing,
rc_params)

prop = FontProperties._from_any(prop)
ffamily = prop.get_family()

# maintain two dicts, one for available paths,
# the other for fallback paths
fpaths, fbpaths = OrderedDict(), OrderedDict()
for fidx in range(len(ffamily)):
cprop = prop.copy()

# set current prop's family
cprop.set_family(ffamily[fidx])

fpath = self._findfont_cached(
FontProperties._from_any(cprop), fontext, directory,
fallback_to_default, rebuild_if_missing, rc_params)

# if fontfile isn't found, fpath will be an OrderedDict
if isinstance(fpath, OrderedDict):
fbpaths.update(fpath)
else:
fpaths[ffamily[fidx]] = fpath

# append fallback font(s) to the very end
fpaths.update(fbpaths)

return fpaths

@lru_cache()
def _findfont_cached(self, prop, fontext, directory, fallback_to_default,
rebuild_if_missing, rc_params):

prop = FontProperties._from_any(prop)

fname = prop.get_file()
if fname is not None:
return fname
Expand Down Expand Up @@ -1401,7 +1426,10 @@ def is_opentype_cff_font(filename):


@lru_cache(64)
def _get_font(filename, hinting_factor, *, _kerning_factor, thread_id):
def _get_font(filenames, hinting_factor, *, _kerning_factor, thread_id):
# TODO: allow multiple files (future PR)
# for now just pass the first element
filename = filenames[0]
return ft2font.FT2Font(
filename, hinting_factor, _kerning_factor=_kerning_factor)

Expand All @@ -1417,11 +1445,20 @@ def _get_font(filename, hinting_factor, *, _kerning_factor, thread_id):
def get_font(filename, hinting_factor=None):
# Resolving the path avoids embedding the font twice in pdf/ps output if a
# single font is selected using two different relative paths.
filename = _cached_realpath(filename)
if isinstance(filename, OrderedDict):
filenames = []
for fname in filename.values():
filenames.append(_cached_realpath(fname))
else:
filenames = [_cached_realpath(filename)]
if hinting_factor is None:
hinting_factor = rcParams['text.hinting_factor']

# convert to tuple so its hashable
filenames = tuple(filenames)

# also key on the thread ID to prevent segfaults with multi-threading
return _get_font(filename, hinting_factor,
return _get_font(filenames, hinting_factor,
_kerning_factor=rcParams['text.kerning_factor'],
thread_id=threading.get_ident())

Expand Down
5 changes: 3 additions & 2 deletions lib/matplotlib/tests/test_font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def test_font_priority():
'font.sans-serif':
['cmmi10', 'Bitstream Vera Sans']}):
font = findfont(FontProperties(family=["sans-serif"]))
assert Path(font).name == 'cmmi10.ttf'
# first font should be cmmi10.ttf
assert Path(next(iter(font.values()))).name == 'cmmi10.ttf'

# Smoketest get_charmap, which isn't used internally anymore
font = get_font(font)
Expand Down Expand Up @@ -110,7 +111,7 @@ def test_utf16m_sfnt():

def test_find_ttc():
fp = FontProperties(family=["WenQuanYi Zen Hei"])
if Path(findfont(fp)).name != "wqy-zenhei.ttc":
if "wqy-zenhei.ttc" not in map(lambda x: Path(x).name, findfont(fp)):
pytest.skip("Font may be missing")

fig, ax = plt.subplots()
Expand Down
2 changes: 2 additions & 0 deletions lib/matplotlib/tests/test_mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ def test_mathfont_rendering(baseline_images, fontset, index, text):

def test_fontinfo():
fontpath = mpl.font_manager.findfont("DejaVu Sans")
# get the first element
fontpath = next(iter(fontpath.values()))
font = mpl.ft2font.FT2Font(fontpath)
table = font.get_sfnt_table("head")
assert table['version'] == (1, 0)
Expand Down
3 changes: 2 additions & 1 deletion lib/matplotlib/tests/test_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_font_styles():
def find_matplotlib_font(**kw):
prop = FontProperties(**kw)
path = findfont(prop, directory=mpl.get_data_path())
return FontProperties(fname=path)
return FontProperties(fname=next(iter(path.values())))

from matplotlib.font_manager import FontProperties, findfont
warnings.filterwarnings(
Expand Down Expand Up @@ -198,6 +198,7 @@ def test_antialiasing():

def test_afm_kerning():
fn = mpl.font_manager.findfont("Helvetica", fontext="afm")
fn = next(iter(fn.values()))
with open(fn, 'rb') as fh:
afm = mpl.afm.AFM(fh)
assert afm.string_width_height('VAVAVAVAVAVA') == (7174.0, 718)
Expand Down
7 changes: 6 additions & 1 deletion lib/matplotlib/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,10 +483,15 @@ def __init__(self, useOffset=None, useMathText=None, useLocale=None):
),
fallback_to_default=False,
)
# visit all values
ufont = ufont.values()
except ValueError:
ufont = None

if ufont == str(cbook._get_data_path("fonts/ttf/cmr10.ttf")):
if (
ufont is not None and
str(cbook._get_data_path("fonts/ttf/cmr10.ttf")) in ufont
):
_api.warn_external(
"cmr10 font should ideally be used with "
"mathtext, set axes.formatter.use_mathtext to True"
Expand Down