diff --git a/doc/api/next_api_changes/behavior/30272-ES.rst b/doc/api/next_api_changes/behavior/30272-ES.rst new file mode 100644 index 000000000000..5a03f9bc7972 --- /dev/null +++ b/doc/api/next_api_changes/behavior/30272-ES.rst @@ -0,0 +1,2 @@ +``font_manager.findfont`` logs if selected font weight does not match requested +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index 2db98b75ab2e..ab6b495631de 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -35,7 +35,7 @@ from io import BytesIO import json import logging -from numbers import Number +from numbers import Integral import os from pathlib import Path import plistlib @@ -172,6 +172,10 @@ ] +def _normalize_weight(weight): + return weight if isinstance(weight, Integral) else weight_dict[weight] + + def get_fontext_synonyms(fontext): """ Return a list of file extensions that are synonyms for @@ -1256,8 +1260,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 = _normalize_weight(weight1) + w2 = _normalize_weight(weight2) return 0.95 * (abs(w1 - w2) / 1000) + 0.05 def score_size(self, size1, size2): @@ -1480,6 +1484,10 @@ def _findfont_cached(self, prop, fontext, directory, fallback_to_default, best_font = font if score == 0: break + if best_font is not None and (_normalize_weight(prop.get_weight()) != + _normalize_weight(best_font.weight)): + _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/font_manager.pyi b/lib/matplotlib/font_manager.pyi index c64ddea3e073..e865f67384cd 100644 --- a/lib/matplotlib/font_manager.pyi +++ b/lib/matplotlib/font_manager.pyi @@ -1,14 +1,13 @@ +from collections.abc import Iterable from dataclasses import dataclass +from numbers import Integral import os +from pathlib import Path +from typing import Any, Literal from matplotlib._afm import AFM from matplotlib import ft2font -from pathlib import Path - -from collections.abc import Iterable -from typing import Any, Literal - font_scalings: dict[str | None, float] stretch_dict: dict[str, int] weight_dict: dict[str, int] @@ -19,6 +18,7 @@ MSUserFontDirectories: list[str] X11FontDirectories: list[str] OSXFontDirectories: list[str] +def _normalize_weight(weight: str | Integral) -> Integral: ... def get_fontext_synonyms(fontext: str) -> list[str]: ... def list_fonts(directory: str, extensions: Iterable[str]) -> list[str]: ... def win32FontDirectory() -> str: ... diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 97ee8672b1d4..24421b8e30b3 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -15,7 +15,8 @@ from matplotlib.font_manager import ( findfont, findSystemFonts, FontEntry, FontProperties, fontManager, json_dump, json_load, get_font, is_opentype_cff_font, - MSUserFontDirectories, _get_fontconfig_fonts, ttfFontProperty) + MSUserFontDirectories, ttfFontProperty, + _get_fontconfig_fonts, _normalize_weight) from matplotlib import cbook, ft2font, pyplot as plt, rc_context, figure as mfigure from matplotlib.testing import subprocess_run_helper, subprocess_run_for_testing @@ -407,3 +408,29 @@ def test_fontproperties_init_deprecation(): # Since this case is not covered by docs, I've refrained from jumping # extra hoops to detect this possible API misuse. FontProperties(family="serif-24:style=oblique:weight=bold") + + +def test_normalize_weights(): + assert _normalize_weight(300) == 300 # passthrough + assert _normalize_weight('ultralight') == 100 + assert _normalize_weight('light') == 200 + assert _normalize_weight('normal') == 400 + assert _normalize_weight('regular') == 400 + assert _normalize_weight('book') == 400 + assert _normalize_weight('medium') == 500 + assert _normalize_weight('roman') == 500 + assert _normalize_weight('semibold') == 600 + assert _normalize_weight('demibold') == 600 + assert _normalize_weight('demi') == 600 + assert _normalize_weight('bold') == 700 + assert _normalize_weight('heavy') == 800 + assert _normalize_weight('extra bold') == 800 + assert _normalize_weight('black') == 900 + with pytest.raises(KeyError): + _normalize_weight('invalid') + + +def test_font_match_warning(caplog): + findfont(FontProperties(family=["DejaVu Sans"], weight=750)) + logs = [rec.message for rec in caplog.records] + assert 'findfont: Failed to find font weight 750, now using 700.' in logs