diff --git a/examples/misc/logos2.py b/examples/misc/logos2.py index 528f09e92c18..bae838be6ad0 100644 --- a/examples/misc/logos2.py +++ b/examples/misc/logos2.py @@ -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(): 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', diff --git a/examples/text_labels_and_annotations/font_table.py b/examples/text_labels_and_annotations/font_table.py index e9296430ac13..0ef5877c0d42 100644 --- a/examples/text_labels_and_annotations/font_table.py +++ b/examples/text_labels_and_annotations/font_table.py @@ -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) @@ -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 diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 3cc90e1f7501..e9306606d1e6 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -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) diff --git a/lib/matplotlib/backends/_backend_pdf_ps.py b/lib/matplotlib/backends/_backend_pdf_ps.py index 780e79bf71b8..a66c3d675972 100644 --- a/lib/matplotlib/backends/_backend_pdf_ps.py +++ b/lib/matplotlib/backends/_backend_pdf_ps.py @@ -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): diff --git a/lib/matplotlib/backends/backend_pdf.py b/lib/matplotlib/backends/backend_pdf.py index 10063bd9a7b3..7b60257f90c3 100644 --- a/lib/matplotlib/backends/backend_pdf.py +++ b/lib/matplotlib/backends/backend_pdf.py @@ -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: diff --git a/lib/matplotlib/backends/backend_pgf.py b/lib/matplotlib/backends/backend_pgf.py index 575d2d263496..457c3fa1a9d5 100644 --- a/lib/matplotlib/backends/backend_pgf.py +++ b/lib/matplotlib/backends/backend_pgf.py @@ -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() + "/")) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 9d45575eb13d..2234bf29e37d 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -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 @@ -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): @@ -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 @@ -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) @@ -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()) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 4cad797b3757..d5c9be39c47a 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -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) @@ -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() diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 22079ccf9874..63d020963f6e 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -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) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index dccb74ba0038..cb1c5d64e288 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -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( @@ -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) diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 2831069ab222..6e11a5dda818 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -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"