diff --git a/doc/api/font_manager_api.rst b/doc/api/font_manager_api.rst index 8b698bacf0fe..3e043112380b 100644 --- a/doc/api/font_manager_api.rst +++ b/doc/api/font_manager_api.rst @@ -7,5 +7,9 @@ :undoc-members: :show-inheritance: + .. data:: fontManager + + The global instance of `FontManager`. + .. autoclass:: FontEntry :no-undoc-members: diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index e17d847ea87c..d582bc936902 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -1,7 +1,7 @@ """ A module for finding, managing, and using fonts across platforms. -This module provides a single `FontManager` instance that can +This module provides a single `FontManager` instance, ``fontManager``, that can be shared across backends and platforms. The `findfont` function returns the best TrueType (TTF) font file in the local or system font path that matches the specified `FontProperties` @@ -627,32 +627,33 @@ class FontProperties: - family: A list of font names in decreasing order of priority. The items may include a generic font family name, either - 'sans-serif' (default), 'serif', 'cursive', 'fantasy', or 'monospace'. + 'sans-serif', 'serif', 'cursive', 'fantasy', or 'monospace'. In that case, the actual font to be used will be looked up - from the associated rcParam. + from the associated rcParam. Default: :rc:`font.family` - - style: Either 'normal' (default), 'italic' or 'oblique'. + - style: Either 'normal', 'italic' or 'oblique'. + Default: :rc:`font.style` - - variant: Either 'normal' (default) or 'small-caps'. + - variant: Either 'normal' or 'small-caps'. + Default: :rc:`font.variant` - stretch: A numeric value in the range 0-1000 or one of 'ultra-condensed', 'extra-condensed', 'condensed', - 'semi-condensed', 'normal' (default), 'semi-expanded', 'expanded', - 'extra-expanded' or 'ultra-expanded'. + 'semi-condensed', 'normal', 'semi-expanded', 'expanded', + 'extra-expanded' or 'ultra-expanded'. Default: :rc:`font.stretch` - weight: A numeric value in the range 0-1000 or one of - 'ultralight', 'light', 'normal' (default), 'regular', 'book', 'medium', + 'ultralight', 'light', 'normal', 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', - 'extra bold', 'black'. + 'extra bold', 'black'. Default: :rc:`font.weight` - size: Either an relative value of 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' or an - absolute font size, e.g., 10 (default). + absolute font size, e.g., 10. Default: :rc:`font.size` - - math_fontfamily: The family of fonts used to render math text; overrides - :rc:`mathtext.fontset`. Supported values are the same as the ones - supported by :rc:`mathtext.fontset`: 'dejavusans', 'dejavuserif', 'cm', - 'stix', 'stixsans' and 'custom'. + - math_fontfamily: The family of fonts used to render math text. + Supported values are: 'dejavusans', 'dejavuserif', 'cm', + 'stix', 'stixsans' and 'custom'. Default: :rc:`mathtext.fontset` Alternatively, a font may be specified using the absolute path to a font file, by using the *fname* kwarg. However, in this case, it is typically @@ -807,7 +808,7 @@ def set_family(self, family): is CSS parlance), such as: 'serif', 'sans-serif', 'cursive', 'fantasy', or 'monospace', a real font name or a list of real font names. Real font names are not supported when - :rc:`text.usetex` is `True`. + :rc:`text.usetex` is `True`. Default: :rc:`font.family` """ if family is None: family = rcParams['font.family'] @@ -817,7 +818,11 @@ def set_family(self, family): def set_style(self, style): """ - Set the font style. Values are: 'normal', 'italic' or 'oblique'. + Set the font style. + + Parameters + ---------- + style : {'normal', 'italic', 'oblique'}, default: :rc:`font.style` """ if style is None: style = rcParams['font.style'] @@ -826,7 +831,11 @@ def set_style(self, style): def set_variant(self, variant): """ - Set the font variant. Values are: 'normal' or 'small-caps'. + Set the font variant. + + Parameters + ---------- + variant : {'normal', 'small-caps'}, default: :rc:`font.variant` """ if variant is None: variant = rcParams['font.variant'] @@ -835,10 +844,14 @@ def set_variant(self, variant): def set_weight(self, weight): """ - Set the font weight. May be either a numeric value in the - range 0-1000 or one of 'ultralight', 'light', 'normal', - 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', - 'demi', 'bold', 'heavy', 'extra bold', 'black' + Set the font weight. + + Parameters + ---------- + weight : int or {'ultralight', 'light', 'normal', 'regular', 'book', \ +'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', \ +'extra bold', 'black'}, default: :rc:`font.weight` + If int, must be in the range 0-1000. """ if weight is None: weight = rcParams['font.weight'] @@ -853,10 +866,14 @@ def set_weight(self, weight): def set_stretch(self, stretch): """ - Set the font stretch or width. Options are: 'ultra-condensed', - 'extra-condensed', 'condensed', 'semi-condensed', 'normal', - 'semi-expanded', 'expanded', 'extra-expanded' or - 'ultra-expanded', or a numeric value in the range 0-1000. + Set the font stretch or width. + + Parameters + ---------- + stretch : int or {'ultra-condensed', 'extra-condensed', 'condensed', \ +'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded', \ +'ultra-expanded'}, default: :rc:`font.stretch` + If int, must be in the range 0-1000. """ if stretch is None: stretch = rcParams['font.stretch'] @@ -871,9 +888,14 @@ def set_stretch(self, stretch): def set_size(self, size): """ - Set the font size. Either an relative value of 'xx-small', - 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' - or an absolute font size, e.g., 12. + Set the font size. + + Parameters + ---------- + size : float or {'xx-small', 'x-small', 'small', 'medium', \ +'large', 'x-large', 'xx-large'}, default: :rc:`font.size` + If float, the font size in points. The string values denote sizes + relative to the default font size. """ if size is None: size = rcParams['font.size'] @@ -1091,6 +1113,9 @@ def addfont(self, path): ---------- path : str or path-like """ + # Convert to string in case of a path as + # afmFontProperty and FT2Font expect this + path = os.fsdecode(path) if Path(path).suffix.lower() == ".afm": with open(path, "rb") as fh: font = _afm.AFM(fh) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 0eafec792b04..37015bc0d76a 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -387,6 +387,20 @@ def validate_fontweight(s): raise ValueError(f'{s} is not a valid font weight.') from e +def validate_fontstretch(s): + stretchvalues = [ + 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', + 'normal', 'semi-expanded', 'expanded', 'extra-expanded', + 'ultra-expanded'] + # Note: Historically, stretchvalues have been case-sensitive in Matplotlib + if s in stretchvalues: + return s + try: + return int(s) + except (ValueError, TypeError) as e: + raise ValueError(f'{s} is not a valid font stretch.') from e + + def validate_font_properties(s): parse_fontconfig_pattern(s) return s @@ -900,7 +914,7 @@ def _convert_validator_spec(key, conv): "font.family": validate_stringlist, # used by text object "font.style": validate_string, "font.variant": validate_string, - "font.stretch": validate_string, + "font.stretch": validate_fontstretch, "font.weight": validate_fontweight, "font.size": validate_float, # Base font size in points "font.serif": validate_stringlist, diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index fb1119e33489..254b9fdff38b 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -176,6 +176,13 @@ def test_user_fonts_linux(tmpdir, monkeypatch): _get_fontconfig_fonts.cache_clear() +def test_addfont_as_path(): + """Smoke test that addfont() accepts pathlib.Path.""" + font_test_file = 'mpltest.ttf' + path = Path(__file__).parent / font_test_file + fontManager.addfont(path) + + @pytest.mark.skipif(sys.platform != 'win32', reason='Windows only') def test_user_fonts_win32(): if not (os.environ.get('APPVEYOR') or os.environ.get('TF_BUILD')): diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 75b6f727f799..6f0edf3ae1f3 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -20,6 +20,7 @@ _validate_color_or_linecolor, validate_cycler, validate_float, + validate_fontstretch, validate_fontweight, validate_hatch, validate_hist_bins, @@ -469,6 +470,26 @@ def test_validate_fontweight(weight, parsed_weight): assert validate_fontweight(weight) == parsed_weight +@pytest.mark.parametrize('stretch, parsed_stretch', [ + ('expanded', 'expanded'), + ('EXPANDED', ValueError), # stretch is case-sensitive + (100, 100), + ('100', 100), + (np.array(100), 100), + # fractional fontweights are not defined. This should actually raise a + # ValueError, but historically did not. + (20.6, 20), + ('20.6', ValueError), + ([100], ValueError), +]) +def test_validate_fontstretch(stretch, parsed_stretch): + if parsed_stretch is ValueError: + with pytest.raises(ValueError): + validate_fontstretch(stretch) + else: + assert validate_fontstretch(stretch) == parsed_stretch + + def test_keymaps(): key_list = [k for k in mpl.rcParams if 'keymap' in k] for k in key_list:
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: