diff --git a/kitty/core_text.m b/kitty/core_text.m index 79ca2d2fa..fa1ba9c57 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -845,10 +845,13 @@ get_variable_data(CTFace *self) { RAII_CoreFoundation(CFArrayRef, descriptors, CTFontCollectionCreateMatchingFontDescriptors(collection)); RAII_PyObject(named_styles, PyTuple_New(CFArrayGetCount(descriptors))); Py_ssize_t actual_num = 0; + RAII_CoreFoundation(CFURLRef, url, CTFontCopyAttribute(self->ct_font, kCTFontURLAttribute)); for (CFIndex i = 0; i < CFArrayGetCount(descriptors); i++) { CTFontDescriptorRef descriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(descriptors, i); RAII_CoreFoundation(CFDictionaryRef, variation, CTFontDescriptorCopyAttribute(descriptor, kCTFontVariationAttribute)); if (!variation) continue; + RAII_CoreFoundation(CFURLRef, candidate_url, CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute)); + if (!CFEqual(url, candidate_url)) continue; RAII_CoreFoundation(CFStringRef, style, CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute)); RAII_CoreFoundation(CFStringRef, psname, CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute)); RAII_PyObject(axis_values, PyDict_New()); diff --git a/kitty/fonts/core_text.py b/kitty/fonts/core_text.py index 971dacfae..4ba5ebfc5 100644 --- a/kitty/fonts/core_text.py +++ b/kitty/fonts/core_text.py @@ -118,6 +118,26 @@ def font_for_family(family: str) -> Tuple[CoreTextFont, bool, bool]: def get_variable_data_for_descriptor(f: ListedFont) -> VariableData: + return CTFace(descriptor=descriptor(f)).get_variable_data() + + +def descriptor(f: ListedFont) -> CoreTextFont: d = f['descriptor'] assert d['descriptor_type'] == 'core_text' - return CTFace(descriptor=d).get_variable_data() + return d + + +def prune_family_group(g: List[ListedFont]) -> List[ListedFont]: + # CoreText returns a separate font for every style in the variable font, so + # merge them. + variable_paths = {descriptor(f)['path']: False for f in g if f['is_variable']} + if not variable_paths: + return g + def is_ok(d: CoreTextFont) -> bool: + if d['path'] not in variable_paths: + return True + if not variable_paths[d['path']]: + variable_paths[d['path']] = True + return True + return False + return [x for x in g if is_ok(descriptor(x))] diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index 71aed3967..11b7c7fe4 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -177,7 +177,22 @@ def font_for_family(family: str) -> Tuple[FontConfigPattern, bool, bool]: return ans, ans.get('weight', 0) >= FC_WEIGHT_BOLD, ans.get('slant', FC_SLANT_ROMAN) != FC_SLANT_ROMAN -def get_variable_data_for_descriptor(f: ListedFont) -> VariableData: +def descriptor(f: ListedFont) -> FontConfigPattern: d = f['descriptor'] assert d['descriptor_type'] == 'fontconfig' - return Face(descriptor=d).get_variable_data() + return d + + +def get_variable_data_for_descriptor(f: ListedFont) -> VariableData: + return Face(descriptor=descriptor(f)).get_variable_data() + + +def prune_family_group(g: List[ListedFont]) -> List[ListedFont]: + # fontconfig creates dummy entries for named styles in variable fonts, prune them + variable_paths = {descriptor(f)['path'] for f in g if f['is_variable']} + if not variable_paths: + return g + def is_ok(d: FontConfigPattern) -> bool: + return d['variable'] or d['path'] not in variable_paths + + return [x for x in g if is_ok(descriptor(x))] diff --git a/kitty/fonts/list.py b/kitty/fonts/list.py index 9d2c7edac..a0f91ef1d 100644 --- a/kitty/fonts/list.py +++ b/kitty/fonts/list.py @@ -11,9 +11,9 @@ from kitty.types import run_once from . import ListedFont if is_macos: - from .core_text import get_variable_data_for_descriptor, list_fonts + from .core_text import get_variable_data_for_descriptor, list_fonts, prune_family_group else: - from .fontconfig import get_variable_data_for_descriptor, list_fonts + from .fontconfig import get_variable_data_for_descriptor, list_fonts, prune_family_group @run_once @@ -54,14 +54,12 @@ def create_family_groups(monospaced: bool = True) -> Dict[str, List[ListedFont]] for f in list_fonts(): if not monospaced or f['is_monospace']: g.setdefault(f['family'], []).append(f) - return g + return {k: prune_family_group(v) for k, v in g.items()} def show_variable(f: ListedFont, psnames: bool) -> None: vd = get_variable_data_for_descriptor(f) - p = italic(f['full_name']) - if psnames and f['postscript_name']: - p += f' ({f["postscript_name"]})' + p = italic(f['family']) p = f"{p} {variable_font_label('Variable font')}" print(indented(p)) print(indented(variable_font_label('Axes of variation'), level=2))