Summary
While investigating #28249, I became curious why system_profiler was taking 7 seconds on my machine to simply list installed fonts. The problem is that it needs to determine the localized name of the font as part of the XML output, which involves loading each font.
If we only care about the path, we should be able to query CoreText directly. The C code would look something like this:
C Program
#include <CoreFoundation/CoreFoundation.h>
#include <CoreText/CoreText.h>
#include <sys/param.h>
int main(int argc, const char *argv[])
{
CTFontCollectionRef collection = CTFontCollectionCreateFromAvailableFonts(NULL);
CFArrayRef descriptors = collection ? CTFontCollectionCreateMatchingFontDescriptors(collection) : NULL;
CFIndex count = descriptors ? CFArrayGetCount(descriptors) : 0;
for (CFIndex i = 0; i < count; i++) {
char UTF8Path[MAXPATHLEN * 4];
CTFontDescriptorRef descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, i);
CFURLRef url = CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute);
CFStringRef path = NULL;
if (url) {
path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
CFRelease(url);
}
if (path) {
CFStringGetCString(path, UTF8Path, sizeof(UTF8Path), kCFStringEncodingUTF8);
printf("%s\n", UTF8Path);
CFRelease(path);
}
}
printf("\n%ld fonts total.\n", (long)count);
if (descriptors) CFRelease(descriptors);
if (collection) CFRelease(collection);
return 0;
}
That returns 1206 fonts in 63ms.
I asked Claude to convert to a Python script using ctypes:
Python Script
import ctypes
import ctypes.util
CoreText = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreText"))
CF = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreFoundation"))
CF.CFArrayGetCount.argtypes = [ctypes.c_void_p]
CF.CFArrayGetCount.restype = ctypes.c_long
CF.CFArrayGetValueAtIndex.argtypes = [ctypes.c_void_p, ctypes.c_long]
CF.CFArrayGetValueAtIndex.restype = ctypes.c_void_p
CF.CFRelease.argtypes = [ctypes.c_void_p]
CF.CFRelease.restype = None
CF.CFStringGetCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_long, ctypes.c_uint32]
CF.CFStringGetCString.restype = ctypes.c_bool
CF.CFURLCopyFileSystemPath.argtypes = [ctypes.c_void_p, ctypes.c_long]
CF.CFURLCopyFileSystemPath.restype = ctypes.c_void_p
CoreText.CTFontCollectionCreateFromAvailableFonts.argtypes = [ctypes.c_void_p]
CoreText.CTFontCollectionCreateFromAvailableFonts.restype = ctypes.c_void_p
CoreText.CTFontCollectionCreateMatchingFontDescriptors.argtypes = [ctypes.c_void_p]
CoreText.CTFontCollectionCreateMatchingFontDescriptors.restype = ctypes.c_void_p
CoreText.CTFontDescriptorCopyAttribute.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
CoreText.CTFontDescriptorCopyAttribute.restype = ctypes.c_void_p
kCTFontURLAttribute = ctypes.c_void_p.in_dll(CoreText, "kCTFontURLAttribute")
kCFURLPOSIXPathStyle = 0
kCFStringEncodingUTF8 = 0x08000100
def cfstring_to_str(cfstr):
buf = ctypes.create_string_buffer(4096)
CF.CFStringGetCString(cfstr, buf, 4096, kCFStringEncodingUTF8)
return buf.value.decode("utf-8")
def list_fonts():
collection = CoreText.CTFontCollectionCreateFromAvailableFonts(None)
descriptors = CoreText.CTFontCollectionCreateMatchingFontDescriptors(collection)
count = CF.CFArrayGetCount(descriptors)
paths = []
for i in range(count):
descriptor = CF.CFArrayGetValueAtIndex(descriptors, i)
url = CoreText.CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute.value)
if url:
cfpath = CF.CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle)
if cfpath:
paths.append(cfstring_to_str(cfpath))
CF.CFRelease(cfpath)
CF.CFRelease(url)
CF.CFRelease(descriptors)
CF.CFRelease(collection)
return paths
print(f"{len(list_fonts())}")
That's a bit slower at ~90ms, but better than the 7 seconds it takes for system_profiler. Is it worth the added complexity of using ctypes in font_manager.py?
Proposed fix
No response
Summary
While investigating #28249, I became curious why
system_profilerwas taking 7 seconds on my machine to simply list installed fonts. The problem is that it needs to determine the localized name of the font as part of the XML output, which involves loading each font.If we only care about the path, we should be able to query CoreText directly. The C code would look something like this:
C Program
That returns 1206 fonts in 63ms.
I asked Claude to convert to a Python script using
ctypes:Python Script
That's a bit slower at ~90ms, but better than the 7 seconds it takes for
system_profiler. Is it worth the added complexity of using ctypes in font_manager.py?Proposed fix
No response