From 6d7c54bcb23e4d7a19919be8db33de743fb451ca Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 25 Apr 2024 20:43:35 +0530 Subject: [PATCH] Make CoreText signatures for some font finding methods the same as their equivalents in fontconfig --- kitty/core_text.m | 36 ++++++++++++++++++++++++++---------- kitty/fast_data_types.pyi | 2 +- kitty/fonts/core_text.py | 20 ++++++++++---------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/kitty/core_text.m b/kitty/core_text.m index 5a2ccde65..524c6d9d8 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -137,7 +137,7 @@ font_descriptor_to_python(CTFontDescriptorRef descriptor) { "bold", (symbolic_traits & kCTFontBoldTrait) != 0 ? Py_True : Py_False, "italic", (symbolic_traits & kCTFontItalicTrait) != 0 ? Py_True : Py_False, - "monospace", (symbolic_traits & kCTFontMonoSpaceTrait) != 0 ? Py_True : Py_False, + "monospace", (symbolic_traits & kCTFontTraitMonoSpace) != 0 ? Py_True : Py_False, "expanded", (symbolic_traits & kCTFontExpandedTrait) != 0 ? Py_True : Py_False, "condensed", (symbolic_traits & kCTFontCondensedTrait) != 0 ? Py_True : Py_False, "color_glyphs", (symbolic_traits & kCTFontColorGlyphsTrait) != 0 ? Py_True : Py_False, @@ -185,17 +185,33 @@ all_fonts_collection(void) { } static PyObject* -coretext_all_fonts(PyObject UNUSED *_self) { - CFArrayRef matches = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection()); +coretext_all_fonts(PyObject UNUSED *_self, PyObject *monospaced_only_) { + int monospaced_only = PyObject_IsTrue(monospaced_only_); + RAII_CoreFoundation(CFArrayRef, matches, CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection())); const CFIndex count = CFArrayGetCount(matches); - PyObject *ans = PyTuple_New(count), *temp; - if (ans == NULL) { CFRelease(matches); return PyErr_NoMemory(); } + RAII_PyObject(ans, PyTuple_New(count)); + if (ans == NULL) return NULL; + PyObject *temp; + Py_ssize_t num = 0; for (CFIndex i = 0; i < count; i++) { - temp = font_descriptor_to_python((CTFontDescriptorRef) CFArrayGetValueAtIndex(matches, i)); - if (temp == NULL) { CFRelease(matches); Py_DECREF(ans); return NULL; } - PyTuple_SET_ITEM(ans, i, temp); temp = NULL; + CTFontDescriptorRef desc = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matches, i); + if (monospaced_only) { + RAII_CoreFoundation(CFDictionaryRef, traits, CTFontDescriptorCopyAttribute(desc, kCTFontTraitsAttribute)); + if (traits) { + unsigned long symbolic_traits; + CFNumberRef value = (CFNumberRef)CFDictionaryGetValue(traits, kCTFontSymbolicTrait); + if (value) { + CFNumberGetValue(value, kCFNumberLongType, &symbolic_traits); + if (!(symbolic_traits & kCTFontTraitMonoSpace)) continue; + } + } + } + temp = font_descriptor_to_python(desc); + if (temp == NULL) return NULL; + PyTuple_SET_ITEM(ans, num++, temp); temp = NULL; } - CFRelease(matches); + if (_PyTuple_Resize(&ans, num) == -1) return NULL; + Py_INCREF(ans); return ans; } @@ -855,7 +871,7 @@ repr(CTFace *self) { static PyMethodDef module_methods[] = { - METHODB(coretext_all_fonts, METH_NOARGS), + METHODB(coretext_all_fonts, METH_O), {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 11c61372e..f0eedf7b0 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -455,7 +455,7 @@ class CTFace: def display_name(self) -> str: ... -def coretext_all_fonts() -> Tuple[CoreTextFont, ...]: +def coretext_all_fonts(monospaced_only: bool) -> Tuple[CoreTextFont, ...]: pass diff --git a/kitty/fonts/core_text.py b/kitty/fonts/core_text.py index 7cf920a0c..ecde53dbb 100644 --- a/kitty/fonts/core_text.py +++ b/kitty/fonts/core_text.py @@ -2,6 +2,7 @@ # License: GPL v3 Copyright: 2017, Kovid Goyal import re +from functools import lru_cache from typing import Dict, Generator, Iterable, List, Optional, Tuple from kitty.fast_data_types import coretext_all_fonts @@ -33,16 +34,13 @@ def create_font_map(all_fonts: Iterable[CoreTextFont]) -> FontMap: return ans -def all_fonts_map() -> FontMap: - ans: Optional[FontMap] = getattr(all_fonts_map, 'ans', None) - if ans is None: - ans = create_font_map(coretext_all_fonts()) - setattr(all_fonts_map, 'ans', ans) - return ans +@lru_cache(maxsize=2) +def all_fonts_map(monospaced: bool = True) -> FontMap: + return create_font_map(coretext_all_fonts(monospaced)) def list_fonts() -> Generator[ListedFont, None, None]: - for fd in coretext_all_fonts(): + for fd in coretext_all_fonts(False): f = fd['family'] if f: fn = fd['display_name'] @@ -52,9 +50,11 @@ def list_fonts() -> Generator[ListedFont, None, None]: 'is_variable': fd['variable'], 'descriptor': fd} -def find_best_match(family: str, bold: bool = False, italic: bool = False, ignore_face: Optional[CoreTextFont] = None) -> CoreTextFont: +def find_best_match( + family: str, bold: bool = False, italic: bool = False, monospaced: bool = True, ignore_face: Optional[CoreTextFont] = None +) -> CoreTextFont: q = re.sub(r'\s+', ' ', family.lower()) - font_map = all_fonts_map() + font_map = all_fonts_map(monospaced) def score(candidate: CoreTextFont) -> Tuple[int, int, int, float]: style_match = 1 if candidate['bold'] == bold and candidate[ @@ -115,7 +115,7 @@ def get_font_files(opts: Options) -> Dict[str, CoreTextFont]: def font_for_family(family: str) -> Tuple[CoreTextFont, bool, bool]: - ans = find_best_match(family) + ans = find_best_match(family, monospaced=False) return ans, ans['bold'], ans['italic']