-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add custom math fallback #16509
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add custom math fallback #16509
Conversation
@anntzer This should about do it. Seems mpl ships only with DejaVu Sans/STIX so I couldn't test a real figure against an incomplete font that would have to get substituted. Unsure what's failing the docs though |
@geib7592 Can you please comment on this PR? Thanks! |
Looking a bit below, it appears that DejaVuFonts (the actual default) already implements its own fallback solution, which goes to stix, not not cm. If you set mathtext.fallback, does that also override the fallback for dejavu? I guess this suggests that the proper solution may instead to make mathtext.fontset accept a list of fonts, falling back from one to the next (so e.g. |
IIUC I agree it would be nicer to have a fallback list, but that seems like it would require major code changes, meanwhile the functionality in this PR (or the original PR indeed) is good enough. Though it could be easily extended to also so |
Hello, it's me from #8619. I'm excited to see someone working on this issue. It seems that since matplotlib ships with stix and dejavu sans, adding a fallback to dajavu sans (and stix sans) could be another (very similar) PR, and in my opinion, it would be worthwhile for flexibility in using custom sans-serif fonts. Allowing a custom fallback font is likely more work, but would be useful for cases I pointed out in #8619. (@anntzer, your suggestion for having I think @anntzer also suggested I add my two cents about why this PR is important. Since posting the feature request in 2017, I've thought a fair amount about how to typeset publication-quality scientific figures. Currently, I don't think any free (or even non-free) software does it perfectly, and it's an area where, with a few well chosen tweaks, matplotlib could really excel. For example, major scientific publishing companies like Nature and Science explicitly prefer Helvetica for the text of their figures. For Greek letters and mathematical symbols, they use a similarly weighted serif font used in their main text. This PR as it stands really helps move in the right direction; the example in #11644 shows how to use Helvetica with a fallback to stix, which gets closer to Nature's guideline. I'd love to see more development toward industry standard design principles in the future with similar tweaks to the mathtext and latex engines. If the matplotlib devs are interested, I'd be happy to discuss and get more involved. Thanks for all your work! |
I'm fine with not doing the most general approach; I just want the various fallback options and their interaction to be properly documented (e.g. whether mathtext.fallback affects dejavu fallback as well).
why? |
Maybe I misunderstood, but right now custom mathfont requires |
Ah yes, you want to fallback from one custom font to another one... |
If you look a bit below, class DejaVuFonts(UnicodeFonts):
use_cmex = False
def __init__(self, *args, **kwargs):
# This must come first so the backend's owner is set correctly
if isinstance(self, DejaVuSerifFonts):
self.cm_fallback = StixFonts(*args, **kwargs)
else:
self.cm_fallback = StixSansFonts(*args, **kwargs)
self.bakoma = BakomaFonts(*args, **kwargs)
TruetypeFonts.__init__(self, *args, **kwargs) (the point I was mentioning above) I'm actually now a bit confused by why DejaVuFonts inherits from UnicodeFonts but calls |
I think we all agree that what we want is a fallback list for math as well. However, since that's major surgery, especially since the default fonts have a bunch of no-warnings/no-docs fallback which should likely be overhauled, we should open this as a new issue for a new PR. This PR bring major improvement in just being able to have a sans-serif math fallback for custom fonts. Fulfilling most of what was requested in the 3 separate issues #8619, #11644 and #16492 Re some details, I added the option to explicitly ask for Otherwise, if we can agree on this functionality I'll update the tut/whats_new etc... |
I think you can add a test by picking some math strings that include a character that's not present in e.g. dejavusans but present in cm/stix/stixsans, and then saving a svg file with that string and |
Ok, I did the SVG check and the fallbacks work fine, the issue is just that some of the math characters are the same for both stix/stixsans e.g.("#STIXGeneral-Regular-10938") In reality it looks like it won't matter if the math fallback is stix or stixsans |
It's fine if the test doesn't cover all cases, it would still be an improvement over the current situation. |
lib/matplotlib/mathtext.py
Outdated
@@ -660,6 +660,7 @@ class BakomaFonts(TruetypeFonts): | |||
|
|||
def __init__(self, *args, **kwargs): | |||
self._stix_fallback = StixFonts(*args, **kwargs) | |||
self.name = "Computer Modern" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the hope was that you would not have to hardcode this but could instead infer it from the FT2Font object, but I guess that somehow didn't work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm doing it wrong, but the only reasonable way I found was self.cm_fallback.default_font_prop.get_family()
but that just returns ['sans-serif']
which doesn't help here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or fontmap which returns sth like {'cal': '/home/anovak/matplotlib/lib/matplotlib/mpl-data/fonts/ttf/cmsy10.ttf', ... 'cmsy10': '/home/anovak/matplotlib/lib/matplotlib/mpl- data/fonts/ttf/cmex10.ttf', 'cmex10': '/home/anovak/matplotlib/lib/matplotlib/mpl-data/fonts/ttf/cmex10.ttf'}
which would need some extra matching overhead to spit out a useful message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was basically considering something like
if (fontname in ('it', 'regular')
and isinstance(self.cm_fallback, StixFonts)):
fontname = 'rm'
g = self.cm_fallback._get_glyph(
fontname, font_class, sym, fontsize)
_log.warning("Substituting with a symbol from {}".format(g[0].family_name))
# or .postscript_name
return g
As it turns out, while this would work for stix fallback and most other fonts (I guess), this would fail specifically for computer modern fallback because cmmi10.ttf has poor metadata (it lists its family as "cmmi10"...), but I guess you can always special-case cm; at least this should work for "all" other fallbacks...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah nice, that's quite fun, because you can report per symbol. Checkout:
import matplotlib as mpl
test_str = r'a$b\AA\Finv\breve\gimel\succnapprox$'
mpl.rcParams['mathtext.fontset'] = 'custom'
mpl.rcParams['mathtext.rm'] = 'Courier New'
mpl.rcParams['mathtext.it'] = 'Courier New:italic'
mpl.rcParams['mathtext.bf'] = 'Courier New:bold'
fig, ax = plt.subplots(figsize=(8, 8))
mpl.rcParams['mathtext.fallback'] = 'cm'
txt = fig.text(.5, .5, test_str, fontsize=40, ha='center')
Output:
Substituting with a \Finv from STIXGeneral
Substituting with a \gimel from STIXGeneral
Substituting with a \combiningbreve from Computer Modern
Substituting with a \succnapprox from STIXGeneral
Substituting with a \Finv from STIXGeneral
Substituting with a \gimel from STIXGeneral
Substituting with a \combiningbreve from Computer Modern
Substituting with a \succnapprox from STIXGeneral
Can't figure out why is it doing two loops though. It will do yet another loop if a giure is saved, which is probably too many warnings to show.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@anntzer yeah, warnings fail tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use recwarn or pytest.warns (https://docs.pytest.org/en/5.3.5/warnings.html#assertwarnings) to catch them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point was that you actually want to emit a test here, but in the tests you can check that the warning is correctly thrown using pytest.warns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
better just go back to logging, than changing all those tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's fine with me too, I don't think the extra logging is too bad(?)
@anntzer that should be it |
A test (per #16509 (comment)) would be nice. |
lgtm, can you please squash? |
4ccc0d4
to
ee790a3
Compare
phew I'm just gonna drop this here when I inevitably need it again https://blog.appsignal.com/2016/09/27/git-rebasing-strategies.html |
644957d
to
a5b8b42
Compare
looks like you got it to work :) |
what's failing the codecov? |
It's not failing? |
flake8 is unhappy though
|
a5b8b42
to
fe162b7
Compare
Now it's back. |
8c6734f
to
5424ff5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
conditional on CI
@anntzer what about the codecov |
it's fine. |
PR Summary
Follow-up on #11644
I've switched the logic so it's more transparent when
mathtext.fallback
is by default 'cm' . The originalmathtext.fallback_to_cm
defaults toNone
, but eitherTrue
orFalse
will raise a warning and override themathtext.fallback
setting.PR Checklist