GeezerVison is showing github.com — Add support for loading all fonts from collections by QuLogic · Pull Request #30334 · matplotlib/matplotlib
Conversation
QuLogic
commented
Jul 19, 2025
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
PR summary
This turned out to be more straightforward than I expected, but it will probably need a few API decisions to be fully complete.
From bottom to top of the API:
FT2Font accepts a face_index parameter to specify which face to load in a collection, and a corresponding face_index property to check what's loaded.
- For backwards-compatibility,
FontManager.findfont returns a str-like class FontPath (name up for debate) which has a face_index attribute and is accepted by get_font. If anyone uses them as strings though, it should act pretty much the same.
For example, now I can see all variants of Noto Sans CJK:
>>> import matplotlib.font_manager
>>> for fe in matplotlib.font_manager.fontManager.ttflist:
... if fe.name.startswith('Noto Sans Mono CJK'):
... print(fe)
...
FontEntry(fname='/usr/share/fonts/google-noto-sans-mono-cjk-vf-fonts/NotoSansMonoCJK-VF.ttc', index=0, name='Noto Sans Mono CJK JP', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-mono-cjk-vf-fonts/NotoSansMonoCJK-VF.ttc', index=1, name='Noto Sans Mono CJK KR', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-mono-cjk-vf-fonts/NotoSansMonoCJK-VF.ttc', index=2, name='Noto Sans Mono CJK SC', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-mono-cjk-vf-fonts/NotoSansMonoCJK-VF.ttc', index=3, name='Noto Sans Mono CJK TC', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-mono-cjk-vf-fonts/NotoSansMonoCJK-VF.ttc', index=4, name='Noto Sans Mono CJK HK', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc', index=5, name='Noto Sans Mono CJK JP', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc', index=6, name='Noto Sans Mono CJK KR', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc', index=7, name='Noto Sans Mono CJK SC', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc', index=8, name='Noto Sans Mono CJK TC', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc', index=9, name='Noto Sans Mono CJK HK', style='normal', variant='normal', weight=400, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Bold.ttc', index=5, name='Noto Sans Mono CJK JP', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Bold.ttc', index=6, name='Noto Sans Mono CJK KR', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Bold.ttc', index=7, name='Noto Sans Mono CJK SC', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Bold.ttc', index=8, name='Noto Sans Mono CJK TC', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Bold.ttc', index=9, name='Noto Sans Mono CJK HK', style='normal', variant='normal', weight=700, stretch='normal', size='scalable')
or all variants of WenQuanYi that we use for tests:
>>> for fe in matplotlib.font_manager.fontManager.ttflist:
... if fe.name.startswith('WenQuan'):
... print(fe)
...
FontEntry(fname='/usr/share/fonts/wqy-zenhei-fonts/wqy-zenhei.ttc', index=0, name='WenQuanYi Zen Hei', style='normal', variant='normal', weight=500, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/wqy-zenhei-fonts/wqy-zenhei.ttc', index=1, name='WenQuanYi Zen Hei Mono', style='normal', variant='normal', weight=500, stretch='normal', size='scalable')
FontEntry(fname='/usr/share/fonts/wqy-zenhei-fonts/wqy-zenhei.ttc', index=2, name='WenQuanYi Zen Hei Sharp', style='normal', variant='normal', weight=500, stretch='normal', size='scalable')
Fixes #3135
TODO
- Font subsetting in vector formats still hard-codes face index 0. This is mostly because it passes around filenames everywhere, so that will have to be changed to include the face index.
- As you can see above, there are two versions of Noto Sans CJK that are found: one for each weight and a variable font. We don't seem to correctly read any differentiating metadata for the variable font, so whether it or the separate Regular version gets picked is a bit up in the air, I think.
API questions
- Considering the typing is quite long with
str | bytes | Path, I wonder if we should change to os.PathLike?
- I originally started with the path-index tuple as an end goal, but after creating the backwards-compatible
FontPath, I'm thinking maybe that's redundant and we should just stick with the FontPath class only. Do we want to accept the tuple form as well, or should I drop it?
- Currently,
FontPath is a subclass of str which allows using it as a str as one normally would. That was the minimum implementation needed, but we probably want to flesh that out a bit. At minimum, I think we should implement __eq__ and __hash__ so that you can use it as a dictionary key without clashing with an equivalent str. But then do we want to add a deprecation warning when making those comparisons? And after thinking about it a bit more, would a namedtuple with __eq__ instead be a better choice?
- Do we want to figure out a way to group these somehow (in a way something like Add font.superfamily support with genre-aware resolution #30155)? Unfortunately, I think this is actually non-trivial. On Fedora for example, the Noto Sans package installs a fontconfig file that groups them and specifies which language corresponds to which font. It is likely not something embedded in the font that we can read ourselves, and we don't use fontconfig either.
PR checklist
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
YDX-2147483647 reacted with thumbs up emoji
|
We had a discussion about this on the call earlier.
- I originally started with the path-index tuple as an end goal, but after creating the backwards-compatible
FontPath, I'm thinking maybe that's redundant and we should just stick with the FontPath class only. Do we want to accept the tuple form as well, or should I drop it?
We'll drop the tuple for now, as it seems the str-ish type should be fine.
- Currently,
FontPath is a subclass of str which allows using it as a str as one normally would. That was the minimum implementation needed, but we probably want to flesh that out a bit. At minimum, I think we should implement __eq__ and __hash__ so that you can use it as a dictionary key without clashing with an equivalent str. But then do we want to add a deprecation warning when making those comparisons? And after thinking about it a bit more, would a namedtuple with __eq__ instead be a better choice?
We'll not deprecate anything for now, and see how this works out. Since FontPath is fairly equivalent to str, there likely won't be much in the way of breakage. For methods, we'll implement at least __eq__, __hash__ and __repr__.
- Do we want to figure out a way to group these somehow
Will leave this for later, if necessary.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
This should work almost everywhere now, I think. The only thing that doesn't support the face index is LaTeX, via PGF or DviFont (which just hard-codes index 0). I think the latter can be covered by the refactors in #29939
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Also, tests use the WenQuanYi Zen Hei that we already conditionally use, but maybe we should generate a minimal .ttc file for testing instead.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Sep 17, 2025
|
I've rebased now that I think there should not be any other PRs that might cause conficts.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
|
Just realized I didn't have any What's new docs here, so I've added a note.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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 have one minor quibble about c++ function signatures, not critical.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This enables loading a non-initial font from collections (`.ttc` files).
Currently exposed for `FT2Font`, only.
This should allow listing the metadata from the whole collection, which
will also pick the right one if specified, though it will not load the
specific index yet.
For backwards-compatibility, the path+index is passed around in a
lightweight subclass of `str`.
Note, this only has an effect if set as the global font. Otherwise, just
the font name is recorded, and the TeX engine's normal lookup is
performed.
Uh oh!
There was an error while loading. Please reload this page.
wavebyrd
pushed a commit
to wavebyrd/matplotlib
that referenced
this pull request
Mar 13, 2026
Add support for loading all fonts from collections
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|