Description
Summary
Currently, findSystemFonts look at all the file in the specified folders for macos and windows.
This is not a very good method, because a font in a "Font folder" does not necessarily need to be picked up.
A very good example of that is in this issue: #22859
This is why I propose to use CoreText (for mac) and DirectWrite (for Windows).
These are API provided by Apple and Microsoft.
Proposed fix
CoreText - For MAC
In this example, I use this library: https://pypi.org/project/pyobjc-framework-CoreText/
import CoreText
fontURLs = CoreText.CTFontManagerCopyAvailableFontURLs()
fontPath = set()
for i in range(CoreText.CFArrayGetCount(fontURLs)):
url = CoreText.CFArrayGetValueAtIndex(fontURLs, i)
fontPath.add(str(url.path()))
DirectWrite - For Windows
I don't know much C/C++, so I can't give an good example.
On recent Windows build, it is pretty simple to get the font path with DirectWrite, but since matplotlib seems to support Windows 7, we would maybe need to use the "complex" solution.
But, here is how I understand it (I have no proof that it works):
Logic for to support only Windows 10:
- GetSystemFontCollection
- GetFontSet
- For loop with GetFontCount
Here is an "pseudo-code" of how I see it.
font_collection = GetSystemFontCollection()
font_set = font_collection.GetFontSet()
for i in range(font_set.GetFontCount()):
font_face_reference = font_set.GetFontFaceReference(i)
font_file = font_face_reference.GetFontFile()
reference_key, reference_key_size = font_file.GetReferenceKey()
path = GetFilePathFromKey(reference_key, reference_key_size)
Logic for Windows Vista to this day:
- GetSystemFontCollection
- Do a for loop with GetFontFamilyCount
- GetFontFamily
- GetMatchingFonts - The param weight, stretch, style can be anything. These param seems to only change the order or the return list.
- Do a for loop with GetFontCount
font_collection = GetSystemFontCollection()
for i in range(font_collection.GetFontFamilyCount()):
font_family = font_collection.GetFontFamily(i)
matching_fonts = GetMatchingFonts(ANY_WEIGHT, ANY_STRETCH, ANY_STYLE)
for j in range(matching_fonts.GetFontCount()):
font = matching_fonts.GetFont(j)
font_face = font.CreateFontFace()
for file in font_face.GetFiles():
reference_key, reference_key_size = font_file.GetReferenceKey()
path = GetFilePathFromKey(reference_key, reference_key_size)
I have try to see if the GDI api could help us to get the path of the fonts, but from what I can see, it is not an available feature: https://learn.microsoft.com/en-us/windows/win32/gdi/font-and-text-functions
FontConfig (Bonus) - For Linux
Currently, matplotlib parse the result from subprocess.
It would be an good idea to directly use FontConfig. See this answer from stackoverflow to know how to do it: https://stackoverflow.com/a/14634033
I did not found any FontConfig bindings library that are still maintained, so it would need to be implemented with Cython.