diff --git a/kitty/fonts/__init__.py b/kitty/fonts/__init__.py index a1fd16270..8c9fd1bcc 100644 --- a/kitty/fonts/__init__.py +++ b/kitty/fonts/__init__.py @@ -1,7 +1,8 @@ try: - from typing import TypedDict + from typing import TypedDict, NamedTuple except ImportError: TypedDict = dict +from enum import Enum, auto class ListedFont(TypedDict): @@ -21,3 +22,27 @@ class FontFeature: def __repr__(self) -> str: return repr(self.name) + + +class ModificationType(Enum): + underline_position = auto() + underline_thickness = auto() + strikethrough_position = auto() + strikethrough_thickness = auto() + size = auto() + + +class ModificationUnit(Enum): + pt = auto() + percent = auto() + + +class ModificationValue(NamedTuple): + val: float + unit: ModificationUnit + + +class FontModification(NamedTuple): + mod_type: ModificationType + mod_value: ModificationValue + font_name: str = '' diff --git a/kitty/options/definition.py b/kitty/options/definition.py index c0d1d74e3..8bd2026f4 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -216,6 +216,20 @@ You can do this with e.g.:: ''' ) +opt('+modify_font', '', + option_type='modify_font', + add_to_default=False, + long_text=''' +Modify font characteristics such as the position or thickness of the underline and strikethrough. +The modifications can be either plain numbers, in which case they are interpreted as +pts, or percentages, in which case they are interpreted as a percentage of the original value. +For example:: + + modify_font underline_position -2 + modify_font underline_thickness 150% + modify_font strikethrough_thickness 50% +''') + opt('box_drawing_scale', '0.001, 1, 1.5, 2', option_type='box_drawing_scale', long_text=''' diff --git a/kitty/options/parse.py b/kitty/options/parse.py index 9037a12a0..c6d500270 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -12,7 +12,7 @@ from kitty.options.utils import ( cursor_text_color, 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, - narrow_symbols, optional_edge_width, parse_map, parse_mouse_map, paste_actions, + modify_font, narrow_symbols, optional_edge_width, parse_map, parse_mouse_map, paste_actions, resize_draw_strategy, 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, @@ -1096,6 +1096,10 @@ class Parser: def mark3_foreground(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['mark3_foreground'] = to_color(val) + def modify_font(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + for k, v in modify_font(val): + ans["modify_font"][k] = v + def mouse_hide_wait(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['mouse_hide_wait'] = float(val) @@ -1364,6 +1368,7 @@ def create_result_dict() -> typing.Dict[str, typing.Any]: 'exe_search_path': {}, 'font_features': {}, 'kitten_alias': {}, + 'modify_font': {}, 'narrow_symbols': {}, 'symbol_map': {}, 'watcher': {}, diff --git a/kitty/options/types.py b/kitty/options/types.py index 01a4bf5a7..698276c1a 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -393,6 +393,7 @@ option_names = ( # {{{ 'mark2_foreground', 'mark3_background', 'mark3_foreground', + 'modify_font', 'mouse_hide_wait', 'mouse_map', 'narrow_symbols', @@ -612,6 +613,7 @@ class Options: exe_search_path: typing.Dict[str, str] = {} font_features: typing.Dict[str, typing.Tuple[kitty.fonts.FontFeature, ...]] = {} kitten_alias: typing.Dict[str, str] = {} + modify_font: typing.Dict[str, kitty.fonts.FontModification] = {} narrow_symbols: typing.Dict[typing.Tuple[int, int], int] = {} symbol_map: typing.Dict[typing.Tuple[int, int], str] = {} watcher: typing.Dict[str, str] = {} @@ -731,6 +733,7 @@ defaults.env = {} defaults.exe_search_path = {} defaults.font_features = {} defaults.kitten_alias = {} +defaults.modify_font = {} defaults.narrow_symbols = {} defaults.symbol_map = {} defaults.watcher = {} diff --git a/kitty/options/utils.py b/kitty/options/utils.py index fdb5b45ee..ecc73c856 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -20,7 +20,10 @@ from kitty.constants import is_macos from kitty.fast_data_types import ( CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, Color ) -from kitty.fonts import FontFeature +from kitty.fonts import ( + FontFeature, FontModification, ModificationType, ModificationUnit, + ModificationValue +) from kitty.key_names import ( character_key_name_aliases, functional_key_name_aliases, get_key_name_lookup @@ -797,6 +800,41 @@ def font_features(val: str) -> Iterable[Tuple[str, Tuple[FontFeature, ...]]]: yield parts[0], tuple(features) +def modify_font(val: str) -> Iterable[Tuple[str, FontModification]]: + parts = val.split() + pos, plen = 0, len(parts) + if plen < 2: + log_error(f"Ignoring invalid modify_font: {val}") + return + mtype: Optional[ModificationType] = getattr(ModificationType, parts[pos], None) + if mtype is None: + log_error(f"Ignoring invalid modify_font with unknown modification type: {parts[pos]}") + return + pos += 1 + font_name = '' + if mtype is ModificationType.size: + font_name = parts[pos] + pos += 1 + if plen - pos < 1: + log_error(f"Ignoring invalid modify_font: {val}") + return + sz = parts[pos] + pos += 1 + munit = ModificationUnit.pt + if sz.endswith('%'): + munit = ModificationUnit.percent + sz = sz[:-1] + try: + mvalue = float(sz) + except Exception: + log_error(f'Ignoring modify_font with invalid size: {sz}') + return + key = mtype.name + if font_name: + key += f':{font_name}' + yield key, FontModification(mtype, ModificationValue(mvalue, munit), font_name) + + def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]: val = val.strip() if val: diff --git a/kitty_tests/options.py b/kitty_tests/options.py index df28d89c9..f7ce7c268 100644 --- a/kitty_tests/options.py +++ b/kitty_tests/options.py @@ -23,6 +23,7 @@ class TestConfParsing(BaseTest): from kitty.config import load_config, defaults from kitty.constants import is_macos from kitty.options.utils import to_modifiers + from kitty.fonts import FontModification, ModificationType, ModificationValue, ModificationUnit bad_lines = [] def p(*lines, bad_line_num=0): @@ -55,6 +56,12 @@ class TestConfParsing(BaseTest): self.assertFalse(bad_lines) opts = p('pointer_shape_when_grabbed XXX', bad_line_num=1) self.ae(opts.pointer_shape_when_grabbed, defaults.pointer_shape_when_grabbed) + opts = p('modify_font underline_position -2', 'modify_font underline_thickness 150%', 'modify_font size Test -1') + self.ae(opts.modify_font, { + 'underline_position': FontModification(ModificationType.underline_position, ModificationValue(-2., ModificationUnit.pt)), + 'underline_thickness': FontModification(ModificationType.underline_thickness, ModificationValue(150, ModificationUnit.percent)), + 'size:Test': FontModification(ModificationType.size, ModificationValue(-1., ModificationUnit.pt), 'Test'), + }) # test the aliasing options opts = p('env A=1', 'env B=x$A', 'env C=', 'env D', 'clear_all_shortcuts y', 'kitten_alias a b --moo', 'map f1 kitten a arg')