diff --git a/doc/api/next_api_changes/behaviour.rst b/doc/api/next_api_changes/behaviour.rst index ffc902b5ef50..0d8d3c386168 100644 --- a/doc/api/next_api_changes/behaviour.rst +++ b/doc/api/next_api_changes/behaviour.rst @@ -36,3 +36,8 @@ did nothing, when passed an unsupported value. It now raises a ``ValueError``. `.Axis.set_tick_params` (and the higher level `.axes.Axes.tick_params` and `.pyplot.tick_params`) used to accept any value for ``which`` and silently did nothing, when passed an unsupported value. It now raises a ``ValueError``. + +``font_manager.findfont`` now warns of +-100 weight difference of selected font vs found font +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Fonts are mapped from string to number. If the best-matched font is over 100 less than or +greater than the chosen font, a warning will be logged. diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 6d56ed5954ab..7d9f8aed3894 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -86,6 +86,10 @@ 'extra bold': 800, 'black': 900, } + +def map_weight_name_to_score(weight): + return weight if isinstance(weight, Number) else weight_dict[weight] + font_family_aliases = { 'serif', 'sans-serif', @@ -1147,8 +1151,8 @@ def score_weight(self, weight1, weight2): # exact match of the weight names, e.g. weight1 == weight2 == "regular" if cbook._str_equal(weight1, weight2): return 0.0 - w1 = weight1 if isinstance(weight1, Number) else weight_dict[weight1] - w2 = weight2 if isinstance(weight2, Number) else weight_dict[weight2] + w1 = map_weight_name_to_score(weight1) + w2 = map_weight_name_to_score(weight2) return 0.95 * (abs(w1 - w2) / 1000) + 0.05 def score_size(self, size1, size2): @@ -1275,6 +1279,11 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default, best_font = font if score == 0: break + if best_font is not None: + if abs(map_weight_name_to_score(prop.get_weight()) + - map_weight_name_to_score(best_font.weight)) > 100: + _log.warning('findfont: Failed to find font weight %s, ' + + 'now using %s.', prop.get_weight(), best_font.weight) if best_font is None or best_score >= 10.0: if fallback_to_default: diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 1597a3aa9c42..e52b7df1d659 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -13,7 +13,7 @@ from matplotlib.font_manager import ( findfont, findSystemFonts, FontProperties, fontManager, json_dump, json_load, get_font, get_fontconfig_fonts, is_opentype_cff_font, - MSUserFontDirectories, _call_fc_list) + MSUserFontDirectories, _call_fc_list, map_weight_name_to_score, weight_dict) from matplotlib import pyplot as plt, rc_context has_fclist = shutil.which('fc-list') is not None @@ -199,3 +199,25 @@ def test_fork(): _model_handler(0) # Make sure the font cache is filled. ctx = multiprocessing.get_context("fork") ctx.Pool(processes=2).map(_model_handler, range(2)) + +def test_map_weights(): + assert map_weight_name_to_score('ultralight') == 100 + assert map_weight_name_to_score('light') == 200 + assert map_weight_name_to_score('normal') == 400 + assert map_weight_name_to_score('regular') == 400 + assert map_weight_name_to_score('book') == 400 + assert map_weight_name_to_score('medium') == 500 + assert map_weight_name_to_score('roman') == 500 + assert map_weight_name_to_score('semibold') == 600 + assert map_weight_name_to_score('demibold') == 600 + assert map_weight_name_to_score('demi') == 600 + assert map_weight_name_to_score('bold') == 700 + assert map_weight_name_to_score('heavy') == 800 + assert map_weight_name_to_score('extra bold') == 800 + assert map_weight_name_to_score('black') == 900 + +def test_font_match_warning(): + with warnings.catch_warnings(record=True) as warns: + font = findfont(FontProperties(family=["PingFang SC"], weight=900)) + assert len(warns) == 2 +