From e988dd4fde4db0d97ed34676e1b4153f87e03936 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 4 Sep 2026 22:56:30 -0400 Subject: [PATCH 1/2] TST: Remove redundant test_mathtext.py::test_fontinfo This test just checks `get_sfnt_table('head')` for DejaVu Sans, but there is a more comprehensive test of all SFNT tables in `test_ft2font.py::test_ft2font_get_sfnt_table`. --- lib/matplotlib/tests/test_mathtext.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 1e1bf793cdae..170e61c3ab61 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -292,14 +292,6 @@ def test_short_long_accents(fig_test, fig_ref): 0, .5, "$" + "".join(fr"\{l} a" for l in corresponding_long_accs) + "$") -def test_fontinfo(): - fontpath = mpl.font_manager.findfont("DejaVu Sans") - font = mpl.ft2font.FT2Font(fontpath) - table = font.get_sfnt_table("head") - assert table is not None - assert table['version'] == (1, 0) - - # See gh-26152 for more context on this xfail @pytest.mark.xfail(pyparsing_version.release == (3, 1, 0), reason="Error messages are incorrect for this version") From d0a969a725e7a17fe5366b7fa0d5bded7995879a Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 5 Sep 2026 02:22:58 -0400 Subject: [PATCH 2/2] Use more specific patterns for bundled mathtext fonts Because we previously only asked for a font family (and sometimes a weight/style), mathtext fonts were at the whim of rcParams for all other settings. This could mean that we would look for an non-existent weight, or even set the normal font to italic unintentionally. The fonts that are available for mathtext are (for now) fixed and bundled, and we only intend for them to work with the exact fonts we have. So use more specific fontconfig patterns that should always match the fonts that we have. While it could be possible to simply hard-code these paths relative to our data path, it seems that way back in 157a0598a02f613c06245de0b3bc81d62765eac7, that implementation was changed to `findfont` intentionally to avoid duplicate embeddings. --- lib/matplotlib/_mathtext.py | 28 +++++++++++++++---------- lib/matplotlib/tests/test_mathtext.py | 30 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py index 319d6f065389..62473c1d1f00 100644 --- a/lib/matplotlib/_mathtext.py +++ b/lib/matplotlib/_mathtext.py @@ -364,6 +364,12 @@ def __init__(self, default_font_prop: FontProperties, load_glyph_flags: LoadFlag self._fonts['default'] = default_font self._fonts['regular'] = default_font + def _normalize_font_pattern(self, pattern: str) -> str: + for option in ['stretch', 'style', 'variant', 'weight']: + if f':{option}=' not in pattern: + pattern = f'{pattern}:{option}=normal' + return pattern + def _get_font(self, font: str) -> FT2Font: basename = self.fontmap.get(font, font) cached_font = self._fonts.get(basename) @@ -492,7 +498,7 @@ def __init__(self, default_font_prop: FontProperties, load_glyph_flags: LoadFlag super().__init__(default_font_prop, load_glyph_flags) for key, val in self._fontmap.items(): - fullpath = findfont(val) + fullpath = findfont(self._normalize_font_pattern(val)) self.fontmap[key] = fullpath self.fontmap[val] = fullpath @@ -617,7 +623,7 @@ def __init__(self, default_font_prop: FontProperties, load_glyph_flags: LoadFlag } for size, name in stixsizedaltfonts.items(): - fullpath = findfont(name) + fullpath = findfont(self._normalize_font_pattern(name)) self.fontmap[size] = fullpath self.fontmap[name] = fullpath @@ -719,7 +725,7 @@ def __init__(self, default_font_prop: FontProperties, load_glyph_flags: LoadFlag '5': 'STIXSizeFiveSym', }) for key, name in self._fontmap.items(): - fullpath = findfont(name) + fullpath = findfont(self._normalize_font_pattern(name)) self.fontmap[key] = fullpath self.fontmap[name] = fullpath @@ -748,9 +754,9 @@ class DejaVuSerifFonts(DejaVuFonts): """ _fontmap = { 'rm': 'DejaVu Serif', - 'it': 'DejaVu Serif:italic', + 'it': 'DejaVu Serif:style=italic', 'bf': 'DejaVu Serif:weight=bold', - 'bfit': 'DejaVu Serif:italic:bold', + 'bfit': 'DejaVu Serif:style=italic:weight=bold', 'sf': 'DejaVu Sans', 'tt': 'DejaVu Sans Mono', 'ex': 'DejaVu Serif Display', @@ -769,9 +775,9 @@ class DejaVuSansFonts(DejaVuFonts): """ _fontmap = { 'rm': 'DejaVu Sans', - 'it': 'DejaVu Sans:italic', + 'it': 'DejaVu Sans:style=italic', 'bf': 'DejaVu Sans:weight=bold', - 'bfit': 'DejaVu Sans:italic:bold', + 'bfit': 'DejaVu Sans:style=italic:weight=bold', 'sf': 'DejaVu Sans', 'tt': 'DejaVu Sans Mono', 'ex': 'DejaVu Sans Display', @@ -796,11 +802,11 @@ class StixFonts(UnicodeFonts): """ _fontmap = { 'rm': 'STIXGeneral', - 'it': 'STIXGeneral:italic', + 'it': 'STIXGeneral:style=italic', 'bf': 'STIXGeneral:weight=bold', - 'bfit': 'STIXGeneral:italic:bold', + 'bfit': 'STIXGeneral:style=italic:weight=bold', 'nonunirm': 'STIXNonUnicode', - 'nonuniit': 'STIXNonUnicode:italic', + 'nonuniit': 'STIXNonUnicode:style=italic', 'nonunibf': 'STIXNonUnicode:weight=bold', '0': 'STIXGeneral', '1': 'STIXSizeOneSym', @@ -815,7 +821,7 @@ class StixFonts(UnicodeFonts): def __init__(self, default_font_prop: FontProperties, load_glyph_flags: LoadFlags): TruetypeFonts.__init__(self, default_font_prop, load_glyph_flags) for key, name in self._fontmap.items(): - fullpath = findfont(name) + fullpath = findfont(self._normalize_font_pattern(name)) self.fontmap[key] = fullpath self.fontmap[name] = fullpath diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index 170e61c3ab61..4c6169f20083 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -1,6 +1,7 @@ from __future__ import annotations import io +import logging from pathlib import Path import platform import re @@ -371,6 +372,35 @@ def test_mathtext_exceptions(math, msg): parser.parse(math) +def test_mathtext_fonts(caplog): + # Changing these `font.*` rcParams should not affect mathtext, as that uses bundled + # fonts with a specific mapping to style/weight. TODO: font.stretch and font.variant + # are difficult to test as we don't have anything that might accidentally be + # substituted there. + mpl.rcParams['font.style'] = 'italic' + mpl.rcParams['font.weight'] = 100 + # Use explicitly available default so that it doesn't warn. + default_font = fm.FontProperties('DejaVu Sans:style=normal:weight=normal') + + _mathtext.BakomaFonts(default_font, LoadFlags.DEFAULT) + dejavusans = _mathtext.DejaVuSansFonts(default_font, LoadFlags.DEFAULT) + dejavuserif = _mathtext.DejaVuSerifFonts(default_font, LoadFlags.DEFAULT) + stix = _mathtext.StixFonts(default_font, LoadFlags.DEFAULT) + stixsans = _mathtext.StixSansFonts(default_font, LoadFlags.DEFAULT) + + # Setting font.style should not have accidentally changed any roman font to italic. + assert dejavusans.fontmap['rm'].endswith('DejaVuSans.ttf') + assert dejavuserif.fontmap['rm'].endswith('DejaVuSerif.ttf') + assert stix.fontmap['rm'].endswith('STIXGeneral.ttf') + assert stixsans.fontmap['rm'].endswith('STIXGeneral.ttf') + # No bakoma font is actually italic, so that setting won't affect it. + + # If `font.weight` leaked into the mathtext setup, then it should cause a warning to + # be logged from the font manager for a missing font weight. + records = [record for record in caplog.records if record.levelno == logging.WARNING] + assert records == [] + + def test_get_unicode_index_exception(): with pytest.raises(ValueError): _mathtext.get_unicode_index(r'\foo')