Wire up parsing of font specs

This commit is contained in:
Kovid Goyal
2024-04-24 19:21:23 +05:30
parent b385ccbc45
commit 6e3c9856d1
8 changed files with 107 additions and 53 deletions

View File

@@ -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'

View File

@@ -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']

View File

@@ -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

View File

@@ -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 = {}

View File

@@ -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
View File

@@ -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):

View File

@@ -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'

View File

@@ -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)