Trouble with custom fonts

Hello,

I am a matplotlib user of several years who is now getting ready to publish an open-source analysis project. We have been leveraging the Python data ecosystem for this project, with matplotlib as our go-to library for producing visualizations. However, in trying to use my company’s particular font in the titles and labels of my MPL charts, I’ve run into issues, which I’ll try to do a decent job of summarizing below.

The below points fall into some combination of: question, comment, and ahh!? and I’m wondering whether I’m being an idiot or whether there are actually some issues within this layer of matplotlib. I would really like to make deeper use of MPL but am hitting my head against the wall on this font stuff - any feedback, helpful pointers, emotional support etc. would be greatly appreciated.

Happy new year,

Abe

System

  • Ubuntu 18.04
  • Python 3.6.8
  • Matplotlib 3.1.2

Technical Overview, in More Detail

  • I am using a .mplstyle file to try to use a custom font(s) in my plots
  • I am using the font.family parameter in the file
  • By custom, I mean that they are not provided in matplotlib's default collection
  • the font(s) I have tried are ttf
  • the font(s) I have tried are installed, i.e. I can see them in different programs
  • I am running matplotlib from within jupyter

1. font.family

Based on the sample file that I found, it seems like font.family should be a key into a variety of different fonts within a particular font family, right? Well, it appears that that is only the case when making use of the included fonts, as there seems to be a set of hard-coded sub-keys for indexing into a family’s available fonts.

Is it just me, or does that seem silly? I have seen people hacking this, e.g. something like setting the "sans-serif" key in rcParams to equal a list of fonts from a custom family, but this seems like a bad approach. Aside from the fact that this is far from idiomatic, is misleading, etc., I have a number of different notebooks that I’m producing plots in and I’d much rather set the font once in a config file than copy and paste the same, misleading rcParams hack in a number of different places.

2. Font Priority

Considering 1. above, I cannot pick a particular font from the family that I want, and am instead at the whim of MPL’s internal prioritization, which seems to be just grabbing the last item from the list of available .ttf files in the family’s directory, yielding ultra-thin, as opposed to regular, the latter of which I would presume would be the default selection.

The way that I dealt with this was to just only put the regular font in the family’s folder in /usr/share/fonts/truetype/, which for obvious reasons is not what I’d like to be doing.

3. Valid Font Not Being Picked Up

This is the issue that has left me the most stumped, i.e. this is what left me so desperate as to leave a complain-y mess of semi-nonsense on a public discussion board on a Monday night!

I’ve actually been experimenting with two different font families, each of which comes in its own directory. The first came from Google Fonts, and I can get it into matplotlib using what I mentioned above in 2., i.e. putting just the regular version into the family directory. The second, which I’ll refer to as F, came from my graphic designer, and I for the life of me cannot figure out why MPL can’t see it.

Here are some steps that I’ve taken:

  • ensured that F is in the same folder as the font family that is working
  • run chmod 777 on the F family folder
  • confirmed that my OS is picking F up, which should mean that I installed it correctly

To be sure, I ran fc-cache -fv and verified that the the new family was cached, and I’ve been resetting my MPL cache with rm -rf ~/.cache/matplotlib after refreshing my system’s font cache. I even ran a fc-cache --really-force trial to be sure the chmod-ing was picked up, and I even even restarted my notebook kernel, and even my machine, each multiple times, to make sure all was kosher.

On top of that, I can see the font in findSystemFonts (assuming name is the font name I’m after):

import matplotlib.pyplot as plt
from matplotlib import font_manager as fm, rcParams

sum([
        name in f for f in fm.findSystemFonts(fontpaths=None, fontext='ttf') 
    ]) # this produces 18

And MPL can see this collection of files, too - the following works fine (FYI this code comes from an official example):

prop = fm.FontProperties(fname=path_to_my_font)

fig, ax = plt.subplots()
ax.set_title('This is a special font', fontproperties=prop)

But, I am getting issues when I try to access via the family name as such - passing the following to fontproperties produces a warning about the font family not being found:

prop = fm.FontProperties(family=name)

So, I am very confused about why MPL:

  • can see the font family’s folder
  • can access a ttf file within that folder, if a file path is passed
  • can not access the font family via the font family’s name

If anyone has any ideas, I’d be grateful to hear them.

The short reply would be, by far the most robust solution right now is to just pass an explicit font path (using the slightly cumbersome FontProperties(fname="/path/to/your/font") (in a style file, I think you can do something like font.family: serif (for example – you just need to pick one) and font.serif: :fname=/path/to/your/font (untested)). (As a side point, this two-layer approach basically comes from CSS.)
Correctly inferring the font properties from the font file is a (somewhat) complicated problem; see e.g. https://github.com/matplotlib/matplotlib/pull/16203 for recent work on that…

thanks for the quick reply :slight_smile:! yeah, looks like there is some gnarly, cross-platform stuff you are having to account for there …

so, when I try to do that(edit: “that” being edit the style file to have font.family: serif), I’m getting the following:

findfont: Font family ['serif'] not found. Falling back to DejaVu Sans.

which is obviously weird, as that one’s supposed to be included. as per this thread, I tried rebuilding my cache, but to no avail.

Actually, looking at it again, that’s not going to work, and I don’t actually think there’s a really easy way to do that (I have a grand plan to redo the whole font config system, but it’s a lot of work…).

For thin fonts, I think the PR linked above will fix the issue (this has been reported a few times alread). It’s not merged yet, obviously :slight_smile:

For the valid font not being picked up… do both font files show up in ~/.cache/fontlist-v310.json? (or whatever the latest version is) If so are the font properties listed in the file the same (possibly after applying the PR above)? If not, then you can (again, untested) set in your style file font.family or font.weight or whatnot that differentiates them. If not, well, that’ll need to wait the bigger fix of font configuration to allow passing explicit paths in the style file :confused:

The basis for Matplotlib’s current approach (which predates nearly all current core devs) is derived from CSS: in CSS you can’t(?) say “use the font at this path”, because that’s obviously not portable, you can only(?) specify fonts by families, etc. (in which case you’re not going to control fonts that exist in duplicate form in the user’s computer), or as “serif”/“sans-serif”/etc. (which are actually configured by the user’s OS; e.g. on Linux (which appears to be your case) you can edit your fonts.conf (possibly via a desktop GUI) to say what font to use when “serif” is being requested. I think it’s a reasonable approach, but the execution is, let’s say, not perfect :slight_smile:

Thanks for another quick reply - wanted to get back to you earlier this AM but was tied up.

For thin fonts, I think the PR linked above will fix the issue …

That’s great - yeah, I could see that you reworked some of the weight parsing stuff so that it is (I think? haven’t run yet) more platform specific.

Re: the font that’s not being picked up, to add a little more color I should tell you that it’s from a different family than the one that is being picked up. That said, they’re both in the json file (which, yes, is ~/.cache/matplotlib/fontlist-v310.json, by the way, despite me being on 3.1.12), and they both have "__class__": "FontEntry" in their properties, and the other fields look to be being pulled in fine.

Let me see if I can pull your PR-code down and give that a whirl. Regarding the following:

you can edit your fonts.conf

Could you tell me more what you mean here? Because how I’ve “installed” my fonts is through making them available, system-wide, which means that both of the new families are already available throughout my system.

I.e. … for the one that is being picked up, I can call that family in matplotlib, but I can’t select into regular the right way (which it sounds like your PR should address), so maybe the serif hack would work.

But for the one that I can’t pickup, matplotlib won’t even recognize that family, so I don’t know if putting those ttf files under a new family folder would help, although that’s an interesting idea.

I’m also confused about why putting font.family: serif in my style file isn’t working, but maybe throwing blowing away the cache I’m explicitly telling it to only look at my system (and not my MPL) cache? I guess I’d figured MPL would look to its own cache if it doesn’t find something matching in my system, and it appears to instead be just reverting to a specific family in its own cache if it can’t find what it’s looking for.

Really appreciate your help, and the background intel is of course always good to hear! Let me know if there’s anything I can do to help on this outside of pulling your PR down.

By editing fonts.conf I meant something like https://wiki.archlinux.org/index.php/Font_configuration/Examples#Default_fonts (e.g.

 <match target="pattern">
   <test qual="any" name="family"><string>sans-serif</string></test>
   <!--<test qual="any" name="lang"><string>ja</string></test>-->
   <edit name="family" mode="prepend" binding="same"><string>Noto Sans</string>  </edit>
 </match>

which sets Noto Sans as your default sans-serif font. But that’s irrelevant for matplotlib, which does not depend on fontconfig to select fonts (because fontconfig isn’t available on Windows, after all).

The font.family: serif trick I suggested isn’t working simply because font.serif: :fname=/some/path isn’t working, and that “just”(?) needs to be implemented(?).

1 Like