mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-13 04:03:16 +02:00
Wire up parsing of font specs
This commit is contained in:
@@ -80,3 +80,22 @@ class FontModification(NamedTuple):
|
||||
def __repr__(self) -> str:
|
||||
fn = f' {self.font_name}' if self.font_name else ''
|
||||
return f'{self.mod_type.name}{fn} {self.mod_value}'
|
||||
|
||||
|
||||
class FontSpec(NamedTuple):
|
||||
family: str = ''
|
||||
style: str = ''
|
||||
postscript_name: str = ''
|
||||
full_name: str = ''
|
||||
system: str = ''
|
||||
axes: Tuple[Tuple[str, float], ...] = ()
|
||||
|
||||
@property
|
||||
def is_system(self) -> bool:
|
||||
return bool(self.system)
|
||||
|
||||
@property
|
||||
def is_auto(self) -> bool:
|
||||
return self.system == 'auto'
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import re
|
||||
from typing import Dict, Generator, Iterable, List, Optional, Tuple
|
||||
|
||||
from kitty.fast_data_types import CTFace, coretext_all_fonts
|
||||
from kitty.fonts import FontFeature, VariableData
|
||||
from kitty.fonts import FontFeature, FontSpec, VariableData
|
||||
from kitty.options.types import Options
|
||||
from kitty.typing import CoreTextFont
|
||||
from kitty.utils import log_error
|
||||
@@ -89,32 +89,38 @@ def find_best_match(family: str, bold: bool = False, italic: bool = False, ignor
|
||||
return sorted(candidates, key=score)[-1]
|
||||
|
||||
|
||||
def resolve_family(f: str, main_family: str, bold: bool = False, italic: bool = False) -> str:
|
||||
if (bold or italic) and f == 'auto':
|
||||
f = main_family
|
||||
if f.lower() == 'monospace':
|
||||
f = 'Menlo'
|
||||
return f
|
||||
def get_font_from_spec(
|
||||
spec: FontSpec, bold: bool = False, italic: bool = False, medium_font_spec: FontSpec = FontSpec(),
|
||||
resolved_medium_font: Optional[CoreTextFont] = None
|
||||
) -> CoreTextFont:
|
||||
if not spec.is_system:
|
||||
raise NotImplementedError('TODO: Implement me')
|
||||
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, ignore_face=resolved_medium_font)
|
||||
|
||||
|
||||
def get_font_files(opts: Options) -> Dict[str, CoreTextFont]:
|
||||
medium_font = get_font_from_spec(opts.font_family)
|
||||
ans: Dict[str, CoreTextFont] = {}
|
||||
kd = {(False, False): 'medium', (True, False): 'bold', (False, True): 'italic', (True, True): 'bi'}
|
||||
for (bold, italic) in sorted(attr_map):
|
||||
attr = attr_map[(bold, italic)]
|
||||
key = {(False, False): 'medium',
|
||||
(True, False): 'bold',
|
||||
(False, True): 'italic',
|
||||
(True, True): 'bi'}[(bold, italic)]
|
||||
ignore_face = None if key == 'medium' else ans['medium']
|
||||
face = find_best_match(resolve_family(getattr(opts, attr), opts.font_family, bold, italic), bold, italic, ignore_face=ignore_face)
|
||||
ans[key] = face
|
||||
key = kd[(bold, italic)]
|
||||
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
|
||||
ans[key] = font
|
||||
if key == 'medium':
|
||||
setattr(get_font_files, 'medium_family', face['family'])
|
||||
setattr(get_font_files, 'medium_family', font['family'])
|
||||
return ans
|
||||
|
||||
|
||||
def font_for_family(family: str) -> Tuple[CoreTextFont, bool, bool]:
|
||||
ans = find_best_match(resolve_family(family, getattr(get_font_files, 'medium_family')))
|
||||
ans = find_best_match(family)
|
||||
return ans, ans['bold'], ans['italic']
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ from kitty.options.types import Options
|
||||
from kitty.typing import FontConfigPattern
|
||||
from kitty.utils import log_error
|
||||
|
||||
from . import FontFeature, ListedFont, VariableData
|
||||
from . import FontFeature, FontSpec, ListedFont, VariableData
|
||||
|
||||
attr_map = {(False, False): 'font_family',
|
||||
(True, False): 'bold_font',
|
||||
@@ -153,21 +153,29 @@ def find_best_match(family: str, bold: bool = False, italic: bool = False, monos
|
||||
return fc_match(family, bold, italic)
|
||||
|
||||
|
||||
def resolve_family(f: str, main_family: str, bold: bool, italic: bool) -> str:
|
||||
if (bold or italic) and f == 'auto':
|
||||
f = main_family
|
||||
return f
|
||||
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:
|
||||
raise NotImplementedError('TODO: Implement me')
|
||||
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():
|
||||
rf = resolve_family(getattr(opts, attr), opts.font_family, bold, italic)
|
||||
font = find_best_match(rf, bold, italic)
|
||||
key = {(False, False): 'medium',
|
||||
(True, False): 'bold',
|
||||
(False, True): 'italic',
|
||||
(True, True): 'bi'}[(bold, italic)]
|
||||
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
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ from kitty.fast_data_types import (
|
||||
)
|
||||
from kitty.fonts.box_drawing import BufType, distribute_dots, render_box_char, render_missing_glyph
|
||||
from kitty.options.types import Options, defaults
|
||||
from kitty.options.utils import parse_font_spec
|
||||
from kitty.types import _T
|
||||
from kitty.typing import CoreTextFont, FontConfigPattern
|
||||
from kitty.utils import log_error
|
||||
@@ -431,7 +432,7 @@ class setup_for_testing:
|
||||
self.family, self.size, self.dpi = family, size, dpi
|
||||
|
||||
def __enter__(self) -> Tuple[Dict[Tuple[int, int, int], bytes], int, int]:
|
||||
opts = defaults._replace(font_family=self.family, font_size=self.size)
|
||||
opts = defaults._replace(font_family=parse_font_spec(self.family), font_size=self.size)
|
||||
set_options(opts)
|
||||
sprites = {}
|
||||
|
||||
|
||||
@@ -32,15 +32,15 @@ kitty has very powerful font management. You can configure individual font faces
|
||||
and even specify special fonts for particular characters.
|
||||
''')
|
||||
|
||||
opt('font_family', 'monospace',
|
||||
opt('font_family', 'monospace', option_type='parse_font_spec',
|
||||
long_text='''
|
||||
You can specify different fonts for the bold/italic/bold-italic variants.
|
||||
To get a full list of supported fonts use the ``kitty +list-fonts`` command.
|
||||
By default they are derived automatically, by the OSes font system. When
|
||||
:opt:`bold_font` or :opt:`bold_italic_font` is set to :code:`auto` on macOS, the
|
||||
priority of bold fonts is semi-bold, bold, heavy. Setting them manually is
|
||||
useful for font families that have many weight variants like Book, Medium,
|
||||
Thick, etc.
|
||||
By default, they are derived automatically, via the Operating System's font
|
||||
management. When :opt:`bold_font` or :opt:`bold_italic_font` is set to
|
||||
:code:`auto` on macOS, the priority of bold fonts is semi-bold, bold, heavy.
|
||||
Setting them manually is useful for font families that have many weight variants
|
||||
like Book, Medium, Thick, etc.
|
||||
For example::
|
||||
|
||||
font_family Operator Mono Book
|
||||
@@ -50,11 +50,11 @@ For example::
|
||||
'''
|
||||
)
|
||||
|
||||
opt('bold_font', 'auto')
|
||||
opt('bold_font', 'auto', option_type='parse_font_spec')
|
||||
|
||||
opt('italic_font', 'auto')
|
||||
opt('italic_font', 'auto', option_type='parse_font_spec')
|
||||
|
||||
opt('bold_italic_font', 'auto')
|
||||
opt('bold_italic_font', 'auto', option_type='parse_font_spec')
|
||||
|
||||
opt('font_size', '11.0',
|
||||
option_type='to_font_size', ctype='double',
|
||||
|
||||
21
kitty/options/parse.py
generated
21
kitty/options/parse.py
generated
@@ -13,13 +13,12 @@ from kitty.options.utils import (
|
||||
deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias,
|
||||
deprecated_send_text, disable_ligatures, edge_width, env, font_features, hide_window_decorations,
|
||||
macos_option_as_alt, macos_titlebar_color, menu_map, modify_font, narrow_symbols,
|
||||
notify_on_cmd_finish, optional_edge_width, parse_map, parse_mouse_map, paste_actions,
|
||||
remote_control_password, resize_debounce_time, scrollback_lines, scrollback_pager_history_size,
|
||||
shell_integration, store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge,
|
||||
tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator,
|
||||
tab_title_template, titlebar_color, to_cursor_shape, to_cursor_unfocused_shape, to_font_size,
|
||||
to_layout_names, to_modifiers, url_prefixes, url_style, visual_window_select_characters,
|
||||
window_border_width, window_logo_scale, window_size
|
||||
notify_on_cmd_finish, optional_edge_width, parse_font_spec, parse_map, parse_mouse_map,
|
||||
paste_actions, remote_control_password, resize_debounce_time, scrollback_lines,
|
||||
scrollback_pager_history_size, shell_integration, store_multiple, symbol_map, tab_activity_symbol,
|
||||
tab_bar_edge, tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator,
|
||||
tab_title_template, titlebar_color, to_cursor_shape, to_font_size, to_layout_names, to_modifiers,
|
||||
url_prefixes, url_style, visual_window_select_characters, window_border_width, window_size
|
||||
)
|
||||
|
||||
|
||||
@@ -102,10 +101,10 @@ class Parser:
|
||||
ans['bell_path'] = config_or_absolute_path(val)
|
||||
|
||||
def bold_font(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['bold_font'] = str(val)
|
||||
ans['bold_font'] = parse_font_spec(val)
|
||||
|
||||
def bold_italic_font(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['bold_italic_font'] = str(val)
|
||||
ans['bold_italic_font'] = parse_font_spec(val)
|
||||
|
||||
def box_drawing_scale(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['box_drawing_scale'] = box_drawing_scale(val)
|
||||
@@ -979,7 +978,7 @@ class Parser:
|
||||
ans['focus_follows_mouse'] = to_bool(val)
|
||||
|
||||
def font_family(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['font_family'] = str(val)
|
||||
ans['font_family'] = parse_font_spec(val)
|
||||
|
||||
def font_features(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
for k, v in font_features(val):
|
||||
@@ -1025,7 +1024,7 @@ class Parser:
|
||||
ans['input_delay'] = positive_int(val)
|
||||
|
||||
def italic_font(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['italic_font'] = str(val)
|
||||
ans['italic_font'] = parse_font_spec(val)
|
||||
|
||||
def kitten_alias(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
for k, v in action_alias(val):
|
||||
|
||||
9
kitty/options/types.py
generated
9
kitty/options/types.py
generated
@@ -7,6 +7,7 @@ from kitty.constants import is_macos
|
||||
import kitty.constants
|
||||
from kitty.fast_data_types import Color, SingleKey
|
||||
import kitty.fast_data_types
|
||||
from kitty.fonts import FontSpec
|
||||
import kitty.fonts
|
||||
from kitty.options.utils import (
|
||||
AliasMap, KeyDefinition, KeyboardModeMap, MouseMap, MouseMapping, NotifyOnCmdFinish,
|
||||
@@ -487,8 +488,8 @@ class Options:
|
||||
bell_border_color: Color = Color(255, 90, 0)
|
||||
bell_on_tab: str = '🔔 '
|
||||
bell_path: typing.Optional[str] = None
|
||||
bold_font: str = 'auto'
|
||||
bold_italic_font: str = 'auto'
|
||||
bold_font: FontSpec = FontSpec(family='', style='', postscript_name='', full_name='', system='auto', axes=())
|
||||
bold_italic_font: FontSpec = FontSpec(family='', style='', postscript_name='', full_name='', system='auto', axes=())
|
||||
box_drawing_scale: typing.Tuple[float, float, float, float] = (0.001, 1.0, 1.5, 2.0)
|
||||
clear_all_mouse_actions: bool = False
|
||||
clear_all_shortcuts: bool = False
|
||||
@@ -519,7 +520,7 @@ class Options:
|
||||
enabled_layouts: typing.List[str] = ['fat', 'grid', 'horizontal', 'splits', 'stack', 'tall', 'vertical']
|
||||
file_transfer_confirmation_bypass: str = ''
|
||||
focus_follows_mouse: bool = False
|
||||
font_family: str = 'monospace'
|
||||
font_family: FontSpec = FontSpec(family='', style='', postscript_name='', full_name='', system='monospace', axes=())
|
||||
font_size: float = 11.0
|
||||
force_ltr: bool = False
|
||||
foreground: Color = Color(221, 221, 221)
|
||||
@@ -533,7 +534,7 @@ class Options:
|
||||
initial_window_height: typing.Tuple[int, str] = (400, 'px')
|
||||
initial_window_width: typing.Tuple[int, str] = (640, 'px')
|
||||
input_delay: int = 3
|
||||
italic_font: str = 'auto'
|
||||
italic_font: FontSpec = FontSpec(family='', style='', postscript_name='', full_name='', system='auto', axes=())
|
||||
kitty_mod: int = 5
|
||||
linux_bell_theme: str = '__custom'
|
||||
linux_display_server: choices_for_linux_display_server = 'auto'
|
||||
|
||||
@@ -45,7 +45,7 @@ from kitty.conf.utils import (
|
||||
)
|
||||
from kitty.constants import is_macos
|
||||
from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, NO_CURSOR_SHAPE, Color, Shlex, SingleKey
|
||||
from kitty.fonts import FontFeature, FontModification, ModificationType, ModificationUnit, ModificationValue
|
||||
from kitty.fonts import FontFeature, FontModification, FontSpec, ModificationType, ModificationUnit, ModificationValue
|
||||
from kitty.key_names import character_key_name_aliases, functional_key_name_aliases, get_key_name_lookup
|
||||
from kitty.rgb import color_as_int
|
||||
from kitty.types import FloatEdges, MouseEvent
|
||||
@@ -1390,6 +1390,26 @@ def parse_mouse_map(val: str) -> Iterable[MouseMapping]:
|
||||
yield MouseMapping(button, mods, count, mode == 'grabbed', definition=action)
|
||||
|
||||
|
||||
def parse_font_spec(spec: str) -> FontSpec:
|
||||
if spec == 'auto':
|
||||
return FontSpec(system='auto')
|
||||
items = tuple(shlex_split(spec))
|
||||
if '=' not in items[0]:
|
||||
return FontSpec(system=spec)
|
||||
axes = {}
|
||||
defined = {}
|
||||
for item in items:
|
||||
k, sep, v = item.partition('=')
|
||||
if sep != '=':
|
||||
raise ValueError(f'The font specification: {spec} is not valid as {item} does not contain an =')
|
||||
if k in ('family', 'style', 'full_name', 'postscript_name'):
|
||||
defined[k] = v
|
||||
else:
|
||||
try:
|
||||
axes[k] = float(v)
|
||||
except Exception:
|
||||
raise ValueError(f'The font specification: {spec} is not valid as {v} is not a number')
|
||||
return FontSpec(axes=tuple(axes.items()), **defined)
|
||||
def deprecated_hide_window_decorations_aliases(key: str, val: str, ans: Dict[str, Any]) -> None:
|
||||
if not hasattr(deprecated_hide_window_decorations_aliases, key):
|
||||
setattr(deprecated_hide_window_decorations_aliases, key, True)
|
||||
|
||||
Reference in New Issue
Block a user