Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/users/next_whats_new/2020-02-24-mathtext-fallback.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Add generalized "mathtext.fallback" rcParam
------------------------------------------------------------------------
New "mathtext.fallback" rcParam. Takes "cm", "stix", "stixsans"
or "none" to turn fallback off. "mathtext.fallback_to_cm" is
deprecated, but if used, will override new fallback.
1 change: 1 addition & 0 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ def gen_candidates():
_deprecated_remain_as_none = {
'animation.avconv_path': ('3.3',),
'animation.avconv_args': ('3.3',),
'mathtext.fallback_to_cm': ('3.3',),
'keymap.all_axes': ('3.3',),
}

Expand Down
53 changes: 39 additions & 14 deletions lib/matplotlib/mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,10 +769,17 @@ class UnicodeFonts(TruetypeFonts):

def __init__(self, *args, **kwargs):
# This must come first so the backend's owner is set correctly
Comment thread
timhoffm marked this conversation as resolved.
Resolved
if rcParams['mathtext.fallback_to_cm']:
self.cm_fallback = BakomaFonts(*args, **kwargs)
else:
self.cm_fallback = None
fallback_rc = rcParams['mathtext.fallback']
Comment thread
andrzejnovak marked this conversation as resolved.
Resolved
if rcParams['mathtext.fallback_to_cm'] is not None:
fallback_rc = ('cm' if rcParams['mathtext.fallback_to_cm']
else None)

font_class = {'stix': StixFonts,
'stixsans': StixSansFonts,
'cm': BakomaFonts
}.get(fallback_rc)
self.cm_fallback = font_class(*args, **kwargs) if font_class else None

TruetypeFonts.__init__(self, *args, **kwargs)
self.fontmap = {}
for texfont in "cal rm tt it bf sf".split():
Expand All @@ -783,6 +790,21 @@ def __init__(self, *args, **kwargs):
font = findfont(prop)
self.fontmap['ex'] = font

# include STIX sized alternatives for glyphs if fallback is STIX
if isinstance(self.cm_fallback, StixFonts):
stixsizedaltfonts = {
0: 'STIXGeneral',
1: 'STIXSizeOneSym',
2: 'STIXSizeTwoSym',
3: 'STIXSizeThreeSym',
4: 'STIXSizeFourSym',
5: 'STIXSizeFiveSym'}

for size, name in stixsizedaltfonts.items():
fullpath = findfont(name)
self.fontmap[size] = fullpath
self.fontmap[name] = fullpath

_slanted_symbols = set(r"\int \oint".split())

def _map_virtual_font(self, fontname, font_class, uniindex):
Expand Down Expand Up @@ -830,16 +852,19 @@ def _get_glyph(self, fontname, font_class, sym, fontsize, math=True):

if not found_symbol:
if self.cm_fallback:
if isinstance(self.cm_fallback, BakomaFonts):
_log.warning(
"Substituting with a symbol from Computer Modern.")
if (fontname in ('it', 'regular') and
isinstance(self.cm_fallback, StixFonts)):
return self.cm_fallback._get_glyph(
'rm', font_class, sym, fontsize)
else:
return self.cm_fallback._get_glyph(
fontname, font_class, sym, fontsize)
if (fontname in ('it', 'regular')
and isinstance(self.cm_fallback, StixFonts)):
fontname = 'rm'

g = self.cm_fallback._get_glyph(fontname, font_class,
sym, fontsize)
fname = g[0].family_name
if fname in list(BakomaFonts._fontmap.values()):
fname = "Computer Modern"
_log.warning("Substituting symbol {} "
"from {}".format(sym, fname))
return g

else:
if (fontname in ('it', 'regular')
and isinstance(self, StixFonts)):
Expand Down
7 changes: 4 additions & 3 deletions lib/matplotlib/mpl-data/stylelib/classic.mplstyle
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,10 @@ mathtext.bf : serif:bold
mathtext.sf : sans\-serif
mathtext.fontset : cm # Should be 'cm' (Computer Modern), 'stix',
# 'stixsans' or 'custom'
mathtext.fallback_to_cm : True # When True, use symbols from the Computer Modern
# fonts when a symbol can not be found in one of
# the custom math fonts.
mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix'
# 'stixsans'] when a symbol can not be found in one of the
# custom math fonts. Select 'None' to not perform fallback
# and replace the missing character by a dummy.

mathtext.default : it # The default font to use for math.
# Can be any of the LaTeX font names, including
Expand Down
34 changes: 33 additions & 1 deletion lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,37 @@ def validate_font_properties(s):
return s


def _validate_mathtext_fallback_to_cm(b):
"""
Temporary validate for fallback_to_cm, while deprecated

"""
if isinstance(b, str):
b = b.lower()
if b is None or b == 'none':
return None
else:
cbook.warn_deprecated(
"3.3", message="Support for setting the 'mathtext.fallback_to_cm' rcParam "
"is deprecated since %(since)s and will be removed "
"%(removal)s; use 'mathtext.fallback : 'cm' instead.")
return validate_bool_maybe_none(b)


def _validate_mathtext_fallback(s):
_fallback_fonts = ['cm', 'stix', 'stixsans']
if isinstance(s, str):
s = s.lower()
if s is None or s == 'none':
return None
elif s.lower() in _fallback_fonts:
return s
else:
raise ValueError(f"{s} is not a valid fallback font name. Valid fallback "
f"font names are {','.join(_fallback_fonts)}. Passing "
f"'None' will turn fallback off.")


validate_fontset = ValidateInStrings(
'fontset',
['dejavusans', 'dejavuserif', 'cm', 'stix', 'stixsans', 'custom'],
Expand Down Expand Up @@ -1150,7 +1181,8 @@ def _convert_validator_spec(key, conv):
'mathtext.default': [
'it',
['rm', 'cal', 'it', 'tt', 'sf', 'bf', 'default', 'bb', 'frak', 'scr', 'regular']],
'mathtext.fallback_to_cm': [True, validate_bool],
'mathtext.fallback_to_cm': [None, _validate_mathtext_fallback_to_cm],
'mathtext.fallback': ['cm', _validate_mathtext_fallback],

'image.aspect': ['equal', validate_aspect], # equal, auto, a number
'image.interpolation': ['antialiased', validate_string],
Expand Down
46 changes: 46 additions & 0 deletions lib/matplotlib/tests/test_mathtext.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import io
import os
import re

import numpy as np
Expand Down Expand Up @@ -283,3 +284,48 @@ def test_single_minus_sign():
def test_spaces(fig_test, fig_ref):
fig_test.subplots().set_title(r"$1\,2\>3\ 4$")
fig_ref.subplots().set_title(r"$1\/2\:3~4$")


def test_mathtext_fallback_valid():
for fallback in ['cm', 'stix', 'stixsans', 'None']:
mpl.rcParams['mathtext.fallback'] = fallback


@pytest.mark.xfail
def test_mathtext_fallback_invalid():
for fallback in ['abc', '']:
mpl.rcParams['mathtext.fallback'] = fallback


@pytest.mark.xfail
def test_mathtext_fallback_to_cm_invalid():
for fallback in [True, False]:
mpl.rcParams['mathtext.fallback_to_cm'] = fallback


@pytest.mark.parametrize(
"fallback,fontlist",
[("cm", ['DejaVu Sans', 'mpltest', 'STIXGeneral', 'cmr10', 'STIXGeneral']),
("stix", ['DejaVu Sans', 'mpltest', 'STIXGeneral'])])
def test_mathtext_fallback(fallback, fontlist):
mpl.font_manager.fontManager.addfont(
Comment thread
andrzejnovak marked this conversation as resolved.
Resolved
os.path.join((os.path.dirname(os.path.realpath(__file__))), 'mpltest.ttf'))
mpl.rcParams["svg.fonttype"] = 'none'
mpl.rcParams['mathtext.fontset'] = 'custom'
mpl.rcParams['mathtext.rm'] = 'mpltest'
mpl.rcParams['mathtext.it'] = 'mpltest:italic'
mpl.rcParams['mathtext.bf'] = 'mpltest:bold'
mpl.rcParams['mathtext.fallback'] = fallback

test_str = r'a$A\AA\breve\gimel$'

buff = io.BytesIO()
fig, ax = plt.subplots()
fig.text(.5, .5, test_str, fontsize=40, ha='center')
fig.savefig(buff, format="svg")
char_fonts = [
line.split("font-family:")[-1].split(";")[0]
for line in str(buff.getvalue()).split(r"\n") if "tspan" in line
]
assert char_fonts == fontlist
mpl.font_manager.fontManager.ttflist = mpl.font_manager.fontManager.ttflist[:-1]
7 changes: 4 additions & 3 deletions matplotlibrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,10 @@
#mathtext.rm: sans
#mathtext.sf: sans
#mathtext.tt: monospace
#mathtext.fallback_to_cm: True # When True, use symbols from the Computer Modern
# fonts when a symbol can not be found in one of
# the custom math fonts.
#mathtext.fallback: cm # Select fallback font from ['cm' (Computer Modern), 'stix'
Comment thread
andrzejnovak marked this conversation as resolved.
Resolved
# 'stixsans'] when a symbol can not be found in one of the
# custom math fonts. Select 'None' to not perform fallback
# and replace the missing character by a dummy symbol.
#mathtext.default: it # The default font to use for math.
# Can be any of the LaTeX font names, including
# the special name "regular" for the same font
Expand Down
5 changes: 3 additions & 2 deletions tutorials/text/mathtext.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,9 @@
The fonts used should have a Unicode mapping in order to find any
non-Latin characters, such as Greek. If you want to use a math symbol
that is not contained in your custom fonts, you can set
:rc:`mathtext.fallback_to_cm` to ``True`` which will cause the mathtext system
to use characters from the default Computer Modern fonts whenever a particular
:rc:`mathtext.fallback` to either ``'cm'``, ``'stix'`` or ``'stixsans'``
which will cause the mathtext system to use
characters from an alternative font whenever a particular
character can not be found in the custom font.

Note that the math glyphs specified in Unicode have evolved over time, and many
Expand Down