|
28 | 28 | #else |
29 | 29 | #define UNUSED_ON_NON_WINDOWS Py_UNUSED |
30 | 30 | #endif |
| 31 | +#ifdef __APPLE__ |
| 32 | +#include <CoreFoundation/CoreFoundation.h> |
| 33 | +#include <CoreText/CoreText.h> |
| 34 | +#endif |
31 | 35 |
|
32 | 36 | namespace py = pybind11; |
33 | 37 | using namespace pybind11::literals; |
@@ -95,6 +99,61 @@ mpl_display_is_valid(void) |
95 | 99 | #endif |
96 | 100 | } |
97 | 101 |
|
| 102 | +static py::object |
| 103 | +mpl_get_available_fonts(void) |
| 104 | +{ |
| 105 | +#if defined(__APPLE__) |
| 106 | + py::set fonts; |
| 107 | + |
| 108 | + auto cfStringToPyStr = [](CFStringRef str) -> py::str { |
| 109 | + auto cstr = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); |
| 110 | + if (cstr) { |
| 111 | + return py::str(cstr); |
| 112 | + } |
| 113 | + auto length = CFStringGetLength(str); |
| 114 | + auto maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; |
| 115 | + auto buffer = std::make_unique<char[]>(maxSize); |
| 116 | + py::str result; |
| 117 | + if (CFStringGetCString(str, buffer.get(), maxSize, kCFStringEncodingUTF8)) { |
| 118 | + result = py::str(buffer.get()); |
| 119 | + } |
| 120 | + return result; |
| 121 | + }; |
| 122 | + |
| 123 | + auto collection = CTFontCollectionCreateFromAvailableFonts(NULL); |
| 124 | + auto descriptors = collection ? |
| 125 | + CTFontCollectionCreateMatchingFontDescriptors(collection) : NULL; |
| 126 | + auto count = descriptors ? CFArrayGetCount(descriptors) : 0; |
| 127 | + for (CFIndex i = 0; i < count; i++) { |
| 128 | + auto descriptor = static_cast<CTFontDescriptorRef>( |
| 129 | + CFArrayGetValueAtIndex(descriptors, i)); |
| 130 | + auto url = static_cast<CFURLRef>( |
| 131 | + CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute)); |
| 132 | + CFStringRef path = nullptr; |
| 133 | + if (url) { |
| 134 | + path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); |
| 135 | + CFRelease(url); |
| 136 | + } |
| 137 | + if (path) { |
| 138 | + auto pyStr = cfStringToPyStr(path); |
| 139 | + if (pyStr) { |
| 140 | + fonts.add(pyStr); |
| 141 | + } |
| 142 | + CFRelease(path); |
| 143 | + } |
| 144 | + } |
| 145 | + if (descriptors) { |
| 146 | + CFRelease(descriptors); |
| 147 | + } |
| 148 | + if (collection) { |
| 149 | + CFRelease(collection); |
| 150 | + } |
| 151 | + return fonts; |
| 152 | +#else |
| 153 | + return py::none(); |
| 154 | +#endif |
| 155 | +} |
| 156 | + |
98 | 157 | static py::object |
99 | 158 | mpl_GetCurrentProcessExplicitAppUserModelID(void) |
100 | 159 | { |
@@ -210,6 +269,13 @@ PYBIND11_MODULE(_c_internal_utils, m, py::mod_gil_not_used()) |
210 | 269 | only (e.g., for Tkinter). |
211 | 270 |
|
212 | 271 | On other platforms, always returns True.)"""); |
| 272 | + m.def( |
| 273 | + "get_available_fonts", &mpl_get_available_fonts, |
| 274 | + R"""( -- |
| 275 | + On macOS, uses CoreText to find all fonts available to the current |
| 276 | + process and returns the paths as a set of strings. |
| 277 | +
|
| 278 | + On other platforms, always returns None.)"""); |
213 | 279 | m.def( |
214 | 280 | "Win32_GetCurrentProcessExplicitAppUserModelID", |
215 | 281 | &mpl_GetCurrentProcessExplicitAppUserModelID, |
|
0 commit comments