From f5ae6fe15249e3b60bb6e2a43a3239c410fff25f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 25 Apr 2024 21:40:19 +0530 Subject: [PATCH] Refactor font selection code to share more between fontconfig and CoreText --- kitty/fast_data_types.pyi | 8 +- kitty/fonts/__init__.py | 17 +++- kitty/fonts/common.py | 138 ++++++++++++++++++++++++++++++-- kitty/fonts/core_text.py | 35 +++++--- kitty/fonts/fontconfig.py | 164 +++++--------------------------------- kitty/fonts/render.py | 10 +-- kitty/typing.py | 1 + kitty/typing.pyi | 3 +- 8 files changed, 207 insertions(+), 169 deletions(-) diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index f0eedf7b0..75aee51ec 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1,6 +1,6 @@ import termios from ctypes import Array, c_ubyte -from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, NotRequired, Optional, Tuple, TypedDict, Union, overload +from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, Optional, Tuple, TypedDict, Union, overload from kitty.boss import Boss from kitty.fonts import FontFeature, VariableData @@ -8,7 +8,7 @@ from kitty.fonts.render import FontObject from kitty.marks import MarkerFunc from kitty.options.types import Options from kitty.types import LayerShellConfig, SignalInfo -from kitty.typing import EdgeLiteral +from kitty.typing import EdgeLiteral, NotRequired # Constants {{{ GLFW_LAYER_SHELL_NONE: int @@ -447,6 +447,10 @@ class CoreTextFont(TypedDict): slant: float traits: int + # The following two are used by C code to get a face from the pattern + named_style: NotRequired[int] + axes: NotRequired[List[float]] + class CTFace: def __init__(self, descriptor: Optional[CoreTextFont] = None, path: str = ''): ... diff --git a/kitty/fonts/__init__.py b/kitty/fonts/__init__.py index f07ac2c19..9a4dc9e79 100644 --- a/kitty/fonts/__init__.py +++ b/kitty/fonts/__init__.py @@ -1,5 +1,5 @@ from enum import Enum, IntEnum, auto -from typing import Dict, NamedTuple, Tuple, TypedDict, Union +from typing import Callable, Dict, NamedTuple, Tuple, TypedDict, Union from kitty.typing import CoreTextFont, FontConfigPattern @@ -99,3 +99,18 @@ class FontSpec(NamedTuple): return self.system == 'auto' +class Score(NamedTuple): + variable_score: int + style_score: int + monospace_score: int + width_score: int + weight_distance_from_medium: float = 0 + + +Descriptor = Union[FontConfigPattern, CoreTextFont] +Scorer = Callable[[Descriptor], Score] + + +def family_name_to_key(family: str) -> str: + import re + return re.sub(r'\s+', ' ', family.lower()) diff --git a/kitty/fonts/common.py b/kitty/fonts/common.py index 94ef9074b..538f286ef 100644 --- a/kitty/fonts/common.py +++ b/kitty/fonts/common.py @@ -1,28 +1,41 @@ #!/usr/bin/env python # License: GPLv3 Copyright: 2024, Kovid Goyal -from typing import TYPE_CHECKING, Dict, Union +from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Sequence, Union from kitty.constants import is_macos +from kitty.options.types import Options -from . import VariableData +from . import Descriptor, FontSpec, Scorer, VariableData, family_name_to_key if TYPE_CHECKING: - from kitty.fast_data_types import CoreTextFont, CTFace, FontConfigPattern + from kitty.fast_data_types import CTFace from kitty.fast_data_types import Face as FT_Face - Descriptor = Union[FontConfigPattern, CoreTextFont] + FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map'] + FontMap = Dict[FontCollectionMapType, Dict[str, List[Descriptor]]] def Face(descriptor: Descriptor) -> Union[FT_Face, CTFace]: pass + def all_fonts_map(monospaced: bool) -> FontMap: ... + def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer: ... + def find_best_match( + family: str, bold: bool = False, italic: bool = False, monospaced: bool = True, ignore_face: Optional[Descriptor] = None + ) -> Descriptor: ... + def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> Descriptor: ... else: - Descriptor = object + FontCollectionMapType = FontMap = None if is_macos: from kitty.fast_data_types import CTFace as Face + + from .core_text import all_fonts_map, create_scorer, find_best_match, find_last_resort_text_font else: from kitty.fast_data_types import Face + from .fontconfig import all_fonts_map, create_scorer, find_best_match, find_last_resort_text_font + cache_for_variable_data_by_path: Dict[str, VariableData] = {} +attr_map = {(False, False): 'font_family', (True, False): 'bold_font', (False, True): 'italic_font', (True, True): 'bold_italic_font'} def get_variable_data_for_descriptor(d: Descriptor) -> VariableData: @@ -32,3 +45,118 @@ def get_variable_data_for_descriptor(d: Descriptor) -> VariableData: if ans is None: ans = cache_for_variable_data_by_path[d['path']] = Face(descriptor=d).get_variable_data() return ans + + +def find_best_match_in_candidates( + candidates: Sequence[Descriptor], scorer: Scorer, is_medium_face: bool, ignore_face: Optional[Descriptor] = None +) -> Optional[Descriptor]: + if len(candidates) == 1 and not is_medium_face and candidates[0].get('family') == candidates[0].get('full_name'): + # IBM Plex Mono does this, where the full name of the regular font + # face is the same as its family name + return None + candidates = sorted(candidates, key=scorer) + for x in candidates: + if ignore_face is None or x != ignore_face: + return x + return None + + +def get_fine_grained_font( + spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(), + resolved_medium_font: Optional[Descriptor] = None, monospaced: bool = True +) -> Descriptor: + font_map = all_fonts_map(monospaced) + is_medium_face = resolved_medium_font is None + prefer_variable = bool(spec.axes) or bool(spec.style) + scorer = create_scorer(bold, italic, monospaced, prefer_variable=prefer_variable) + if spec.postscript_name: + q = find_best_match_in_candidates(font_map['ps_map'].get(family_name_to_key(spec.postscript_name), []), scorer, is_medium_face) + if q: + return q + if spec.full_name: + q = find_best_match_in_candidates(font_map['full_map'].get(family_name_to_key(spec.full_name), []), scorer, is_medium_face) + if q: + return q + if spec.family: + candidates = font_map['family_map'].get(family_name_to_key(spec.family), []) + if spec.style: + qs = spec.style.lower() + candidates = [x for x in candidates if x['style'].lower() == qs] + q = find_best_match_in_candidates(candidates, scorer, is_medium_face) + if q: + return q + return find_last_resort_text_font(bold, italic, monospaced) + + +def apply_variation_to_pattern(pat: Descriptor, spec: FontSpec) -> Descriptor: + if not pat['variable']: + return pat + + vd = Face(descriptor=pat).get_variable_data() + if spec.style: + q = spec.style.lower() + for i, ns in enumerate(vd['named_styles']): + if ns.get('psname') and ns['psname'].lower() == q: + pat['named_style'] = i + break + else: + for i, ns in enumerate(vd['named_styles']): + if ns['name'].lower() == q: + pat['named_style'] = i + break + tag_map, name_map = {}, {} + axes = [ax['default'] for ax in vd['axes']] + for i, ax in enumerate(vd['axes']): + tag_map[ax['tag']] = i + if ax['strid']: + name_map[ax['strid'].lower()] = i + changed = False + for axspec in spec.axes: + qname = axspec[0] + axis = tag_map.get(qname) + if axis is None: + axis = name_map.get(qname.lower()) + if axis is not None: + axes[axis] = axspec[1] + changed = True + if changed: + pat['axes'] = axes + return pat + + +def find_bold_italic_variant(medium: Descriptor, bold: bool, italic: bool) -> Optional[Descriptor]: + raise NotImplementedError('TODO: Implement me') + + +def get_font_from_spec( + spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(), + resolved_medium_font: Optional[Descriptor] = None +) -> Descriptor: + if not spec.is_system: + return apply_variation_to_pattern(get_fine_grained_font(spec, bold, italic, medium_font_spec, resolved_medium_font), spec) + family = spec.system + if family == 'auto': + if bold or italic: + assert resolved_medium_font is not None + family = resolved_medium_font['family'] + if resolved_medium_font['variable']: + v = find_bold_italic_variant(resolved_medium_font, bold, italic) + if v is not None: + return v + else: + family = 'monospace' + return find_best_match(family, bold, italic, ignore_face=resolved_medium_font) + + +def get_font_files(opts: Options) -> Dict[str, Descriptor]: + ans: Dict[str, Descriptor] = {} + medium_font = get_font_from_spec(opts.font_family) + kd = {(False, False): 'medium', (True, False): 'bold', (False, True): 'italic', (True, True): 'bi'} + for (bold, italic), attr in attr_map.items(): + if bold or italic: + font = get_font_from_spec(getattr(opts, attr), bold, italic, medium_font_spec=opts.font_family, resolved_medium_font=medium_font) + else: + font = medium_font + key = kd[(bold, italic)] + ans[key] = font + return ans diff --git a/kitty/fonts/core_text.py b/kitty/fonts/core_text.py index ecde53dbb..daaf16c8f 100644 --- a/kitty/fonts/core_text.py +++ b/kitty/fonts/core_text.py @@ -11,7 +11,7 @@ from kitty.options.types import Options from kitty.typing import CoreTextFont from kitty.utils import log_error -from . import ListedFont +from . import Descriptor, ListedFont, Score, Scorer attr_map = {(False, False): 'font_family', (True, False): 'bold_font', @@ -50,13 +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, monospaced: bool = True, ignore_face: Optional[CoreTextFont] = None -) -> CoreTextFont: - q = re.sub(r'\s+', ' ', family.lower()) - font_map = all_fonts_map(monospaced) +def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer: - def score(candidate: CoreTextFont) -> Tuple[int, int, int, float]: + def score(candidate: Descriptor) -> Score: + assert candidate['descriptor_type'] == 'core_text' + variable_score = 0 if prefer_variable and candidate['variable'] else 1 style_match = 1 if candidate['bold'] == bold and candidate[ 'italic' ] == italic else 0 @@ -65,13 +63,30 @@ def find_best_match( # prefer semi-bold to bold to heavy, less bold means less chance of # overflow weight_distance_from_medium = abs(candidate['weight']) - return style_match, monospace_match, 1 if is_regular_width else 0, 1 - weight_distance_from_medium + return Score(variable_score, 1 - style_match, 1 - monospace_match, 1 - is_regular_width, weight_distance_from_medium) + + return score + + +def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> CoreTextFont: + font_map = all_fonts_map(monospaced) + candidates = font_map['family_map']['menlo'] + scorer = create_scorer(bold, italic, monospaced) + return sorted(candidates, key=scorer)[0] + + +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(monospaced) + scorer = create_scorer(bold, italic, monospaced) # First look for an exact match for selector in ('ps_map', 'full_map'): candidates = font_map[selector].get(q) if candidates: - possible = sorted(candidates, key=score)[-1] + possible = sorted(candidates, key=scorer)[0] if possible != ignore_face: return possible @@ -81,7 +96,7 @@ def find_best_match( log_error(f'The font {family} was not found, falling back to Menlo') q = 'menlo' candidates = font_map['family_map'][q] - return sorted(candidates, key=score)[-1] + return sorted(candidates, key=scorer)[0] def get_font_from_spec( diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index a340e4734..9c27210cb 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -1,9 +1,8 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal -import re from functools import lru_cache -from typing import Callable, Dict, Generator, List, Literal, NamedTuple, Optional, Tuple, cast +from typing import Dict, Generator, List, Literal, Optional, Tuple, cast from kitty.fast_data_types import ( FC_DUAL, @@ -13,20 +12,12 @@ from kitty.fast_data_types import ( FC_WEIGHT_BOLD, FC_WEIGHT_REGULAR, FC_WIDTH_NORMAL, - Face, fc_list, ) from kitty.fast_data_types import fc_match as fc_match_impl -from kitty.options.types import Options from kitty.typing import FontConfigPattern -from . import FontSpec, ListedFont - -attr_map = {(False, False): 'font_family', - (True, False): 'bold_font', - (False, True): 'italic_font', - (True, True): 'bold_italic_font'} - +from . import Descriptor, ListedFont, Score, Scorer, family_name_to_key FontCollectionMapType = Literal['family_map', 'ps_map', 'full_map'] FontMap = Dict[FontCollectionMapType, Dict[str, List[FontConfigPattern]]] @@ -46,7 +37,7 @@ def create_font_map(all_fonts: Tuple[FontConfigPattern, ...]) -> FontMap: return ans -@lru_cache() +@lru_cache(maxsize=2) def all_fonts_map(monospaced: bool = True) -> FontMap: if monospaced: ans = fc_list(spacing=FC_DUAL) + fc_list(spacing=FC_MONO) @@ -73,64 +64,50 @@ def list_fonts(only_variable: bool = False) -> Generator[ListedFont, None, None] } -def family_name_to_key(family: str) -> str: - return re.sub(r'\s+', ' ', family.lower()) - - @lru_cache() def fc_match(family: str, bold: bool, italic: bool, spacing: int = FC_MONO) -> FontConfigPattern: return fc_match_impl(family, bold, italic, spacing) -class Score(NamedTuple): - variable_score: int - style_score: int - monospace_score: int - width_score: int - -Scorer = Callable[[FontConfigPattern], Score] - def create_scorer(bold: bool = False, italic: bool = False, monospaced: bool = True, prefer_variable: bool = False) -> Scorer: - def score(candidate: FontConfigPattern) -> Score: + def score(candidate: Descriptor) -> Score: + assert candidate['descriptor_type'] == 'fontconfig' variable_score = 0 if prefer_variable and candidate['variable'] else 1 - bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate.get('weight', 0)) - italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate.get('slant', 0)) + bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate['weight']) + italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate['slant']) monospace_match = 0 if monospaced: monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1 - width_score = abs(candidate.get('width', FC_WIDTH_NORMAL) - FC_WIDTH_NORMAL) - + width_score = abs(candidate['width'] - FC_WIDTH_NORMAL) return Score(variable_score, bold_score + italic_score, monospace_match, width_score) return score -def find_best_match_in_candidates( - candidates: List[FontConfigPattern], scorer: Scorer, is_medium_face: bool -) -> Optional[FontConfigPattern]: - if not candidates: - return None - if len(candidates) == 1 and not is_medium_face and candidates[0].get('family') == candidates[0].get('full_name'): - # IBM Plex Mono does this, where the full name of the regular font - # face is the same as its family name - return None - candidates.sort(key=scorer) - return candidates[0] +def find_last_resort_text_font(bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern: + # Use fc-match with a generic family + family = 'monospace' if monospaced else 'sans-serif' + return fc_match(family, bold, italic) -def find_best_match(family: str, bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern: +def find_best_match( + family: str, bold: bool = False, italic: bool = False, monospaced: bool = True, + ignore_face: Optional[FontConfigPattern] = None +) -> FontConfigPattern: + from .common import find_best_match_in_candidates q = family_name_to_key(family) font_map = all_fonts_map(monospaced) scorer = create_scorer(bold, italic, monospaced) is_medium_face = not bold and not italic # First look for an exact match exact_match = ( - find_best_match_in_candidates(font_map['ps_map'].get(q, []), scorer, is_medium_face) or - find_best_match_in_candidates(font_map['full_map'].get(q, []), scorer, is_medium_face) or - find_best_match_in_candidates(font_map['family_map'].get(q, []), scorer, is_medium_face) + find_best_match_in_candidates(font_map['ps_map'].get(q, []), scorer, is_medium_face, ignore_face=ignore_face) or + find_best_match_in_candidates(font_map['full_map'].get(q, []), scorer, is_medium_face, ignore_face=ignore_face) or + find_best_match_in_candidates(font_map['family_map'].get(q, []), scorer, is_medium_face, ignore_face=ignore_face) ) if exact_match: + assert exact_match['descriptor_type'] == 'fontconfig' return exact_match # Use fc-match to see if we can find a monospaced font that matches family @@ -155,104 +132,7 @@ def find_best_match(family: str, bold: bool = False, italic: bool = False, monos if family_name_candidates and len(family_name_candidates) > 1: candidates = family_name_candidates return sorted(candidates, key=scorer)[0] - - # Use fc-match with a generic family - family = 'monospace' if monospaced else 'sans-serif' - return fc_match(family, bold, italic) - - -def get_fine_grained_font( - spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(), - resolved_medium_font: Optional[FontConfigPattern] = None, monospaced: bool = True -) -> FontConfigPattern: - font_map = all_fonts_map(monospaced) - is_medium_face = resolved_medium_font is None - prefer_variable = bool(spec.axes) or bool(spec.style) - if resolved_medium_font and resolved_medium_font['variable']: - prefer_variable = True - scorer = create_scorer(bold, italic, monospaced, prefer_variable=prefer_variable) - if spec.postscript_name: - q = find_best_match_in_candidates(font_map['ps_map'].get(family_name_to_key(spec.postscript_name), []), scorer, is_medium_face) - if q: - return q - if spec.full_name: - q = find_best_match_in_candidates(font_map['full_map'].get(family_name_to_key(spec.full_name), []), scorer, is_medium_face) - if q: - return q - if spec.family: - candidates = font_map['family_map'].get(family_name_to_key(spec.family), []) - if spec.style: - qs = spec.style.lower() - candidates = [x for x in candidates if x['style'].lower() == qs] - q = find_best_match_in_candidates(candidates, scorer, is_medium_face) - if q: - return q - # Use fc-match with a generic family - family = 'monospace' if monospaced else 'sans-serif' - return fc_match(family, bold, italic) - - -def apply_variation_to_pattern(pat: FontConfigPattern, spec: FontSpec) -> FontConfigPattern: - if not pat['variable']: - return pat - - vd = Face(descriptor=pat).get_variable_data() - if spec.style: - q = spec.style.lower() - for i, ns in enumerate(vd['named_styles']): - if ns.get('psname') and ns['psname'].lower() == q: - pat['named_style'] = i - break - else: - for i, ns in enumerate(vd['named_styles']): - if ns['name'].lower() == q: - pat['named_style'] = i - break - tag_map, name_map = {}, {} - axes = [ax['default'] for ax in vd['axes']] - for i, ax in enumerate(vd['axes']): - tag_map[ax['tag']] = i - if ax['strid']: - name_map[ax['strid'].lower()] = i - changed = False - for axspec in spec.axes: - qname = axspec[0] - axis = tag_map.get(qname) - if axis is None: - axis = name_map.get(qname.lower()) - if axis is not None: - axes[axis] = axspec[1] - changed = True - if changed: - pat['axes'] = axes - return pat - - -def get_font_from_spec( - spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(), - resolved_medium_font: Optional[FontConfigPattern] = None -) -> FontConfigPattern: - if not spec.is_system: - return apply_variation_to_pattern(get_fine_grained_font(spec, bold, italic, medium_font_spec, resolved_medium_font), spec) - family = spec.system - if family == 'auto' and (bold or italic): - assert resolved_medium_font is not None - family = resolved_medium_font['family'] - return find_best_match(family, bold, italic) - - -def get_font_files(opts: Options) -> Dict[str, FontConfigPattern]: - ans: Dict[str, FontConfigPattern] = {} - medium_font = get_font_from_spec(opts.font_family) - kd = {(False, False): 'medium', (True, False): 'bold', (False, True): 'italic', (True, True): 'bi'} - for (bold, italic), attr in attr_map.items(): - if bold or italic: - font = get_font_from_spec(getattr(opts, attr), bold, italic, medium_font_spec=opts.font_family, resolved_medium_font=medium_font) - else: - font = medium_font - key = kd[(bold, italic)] - ans[key] = font - return ans + return find_last_resort_text_font(bold, italic, monospaced) def font_for_family(family: str) -> Tuple[FontConfigPattern, bool, bool]: diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index bc53f8ea8..df4087157 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -29,23 +29,17 @@ from kitty.types import _T from kitty.typing import CoreTextFont, FontConfigPattern from kitty.utils import log_error +from .common import get_font_files + if is_macos: from .core_text import font_for_family as font_for_family_macos - from .core_text import get_font_files as get_font_files_coretext else: from .fontconfig import font_for_family as font_for_family_fontconfig - from .fontconfig import get_font_files as get_font_files_fontconfig FontObject = Union[CoreTextFont, FontConfigPattern] current_faces: List[Tuple[FontObject, bool, bool]] = [] -def get_font_files(opts: Options) -> Dict[str, Any]: - if is_macos: - return get_font_files_coretext(opts) - return get_font_files_fontconfig(opts) - - def font_for_family(family: str) -> Tuple[FontObject, bool, bool]: if is_macos: return font_for_family_macos(family) diff --git a/kitty/typing.py b/kitty/typing.py index e0d0ce0e2..83d7cd81e 100644 --- a/kitty/typing.py +++ b/kitty/typing.py @@ -21,3 +21,4 @@ PowerlineStyle = str MatchType = str Protocol = object OptionsProtocol = object +NotRequired = object diff --git a/kitty/typing.pyi b/kitty/typing.pyi index 7146e78a6..5f9c224b0 100644 --- a/kitty/typing.pyi +++ b/kitty/typing.pyi @@ -4,6 +4,7 @@ from socket import socket as Socket from subprocess import CompletedProcess as CompletedProcess from subprocess import Popen as PopenType from typing import Literal +from typing import NotRequired as NotRequired from typing import Protocol as Protocol from typing import TypedDict as TypedDict @@ -61,7 +62,7 @@ __all__ = ( 'EdgeLiteral', 'MatchType', 'GRT_a', 'GRT_f', 'GRT_t', 'GRT_o', 'GRT_m', 'GRT_d', 'GraphicsCommandType', 'HandlerType', 'AbstractEventLoop', 'AddressFamily', 'Socket', 'CompletedProcess', 'PopenType', 'Protocol', 'TypedDict', 'MarkType', 'ImageManagerType', 'Debug', 'LoopType', 'MouseEvent', - 'TermManagerType', 'BossType', 'ChildType', 'BadLineType', 'MouseButton', + 'TermManagerType', 'BossType', 'ChildType', 'BadLineType', 'MouseButton', 'NotRequired', 'KeyActionType', 'KeyMap', 'KittyCommonOpts', 'AliasMap', 'CoreTextFont', 'WindowSystemMouseEvent', 'FontConfigPattern', 'ScreenType', 'StartupCtx', 'KeyEventType', 'LayoutType', 'PowerlineStyle', 'RemoteCommandType', 'SessionType', 'SessionTab', 'SpecialWindowInstance', 'TabType', 'ScreenSize', 'WindowType'