diff --git a/docs/changelog.rst b/docs/changelog.rst index 84195243a..dd6cf8fa7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -40,7 +40,9 @@ Detailed list of changes - Reduce startup latency by ~50 milliseconds when running kittens via key-bindings or remote control (:iss:`5159`) -- A new option :opt:`modify_font` to adjust various font metrics like underline thickness, etc. (:pull:`5265`) +- A new option :opt:`modify_font` to adjust various font metrics like underlines, cell sizes etc. (:pull:`5265`) + +- Deprecate the ``adjust_baseline``, ``adjust_line_height`` and ``adjust_column_width`` options in favor of :opt:`modify_font` - Wayland: Fix a regression in the previous release that caused mouse cursor animation and keyboard repeat to stop working when switching seats (:iss:`5188`) @@ -724,7 +726,7 @@ Detailed list of changes 0.21.2 [2021-06-28] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- A new :opt:`adjust_baseline` option to adjust the vertical alignment of text +- A new ``adjust_baseline`` option to adjust the vertical alignment of text inside a line (:pull:`3734`) - A new :opt:`url_excluded_characters` option to exclude additional characters diff --git a/kitty/core_text.m b/kitty/core_text.m index 984c6f383..31fbc2497 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -324,13 +324,6 @@ harfbuzz_font_for_face(PyObject* s) { return self->hb_font; } -static unsigned int -adjust_ypos(unsigned int pos, unsigned int cell_height, int adjustment) { - if (adjustment >= 0) adjustment = MIN(adjustment, (int)pos - 1); - else adjustment = MAX(adjustment, (int)pos - (int)cell_height + 1); - return pos - adjustment; -} - void cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness, unsigned int* strikethrough_position, unsigned int* strikethrough_thickness) { // See https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/TypoFeatures/TextSystemFeatures.html @@ -376,9 +369,6 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u CTLineGetTypographicBounds(line, &typographic_ascent, &typographic_descent, &typographic_leading); *cell_height = MAX(4u, (unsigned int)ceilf(line_height)); CGFloat bounds_ascent = bounds_without_leading.size.height + bounds_without_leading.origin.y; - int baseline_offset = 0; - if (OPT(adjust_baseline_px) != 0) baseline_offset = OPT(adjust_baseline_px); - else if (OPT(adjust_baseline_frac) != 0) baseline_offset = (int)(*cell_height * OPT(adjust_baseline_frac)); *baseline = (unsigned int)floor(bounds_ascent + 0.5); // Not sure if we should add this to bounds ascent and then round it or add // it to already rounded baseline and round again. @@ -393,12 +383,6 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u debug("\tline metrics: ascent: %f descent: %f leading: %f\n", typographic_ascent, typographic_descent, typographic_leading); debug("\tfont metrics: ascent: %f descent: %f leading: %f underline_position: %f\n", self->ascent, self->descent, self->leading, self->underline_position); debug("\tcell_height: %u baseline: %u underline_position: %u strikethrough_position: %u\n", *cell_height, *baseline, *underline_position, *strikethrough_position); - if (baseline_offset) { - *baseline = adjust_ypos(*baseline, *cell_height, baseline_offset); - *underline_position = adjust_ypos(*underline_position, *cell_height, baseline_offset); - *strikethrough_position = adjust_ypos(*strikethrough_position, *cell_height, baseline_offset); - } - CFRelease(test_frame); CFRelease(path); CFRelease(framesetter); #undef count diff --git a/kitty/fonts.c b/kitty/fonts.c index dc5b295f1..0d26718a0 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -346,24 +346,29 @@ adjust_metric(unsigned int *metric, float adj, AdjustmentUnit unit, double dpi) *metric = (a < 0 && -a > (int)*metric) ? 0 : *metric + a; } +static unsigned int +adjust_ypos(unsigned int pos, unsigned int cell_height, int adjustment) { + if (adjustment >= 0) adjustment = MIN(adjustment, (int)pos - 1); + else adjustment = MAX(adjustment, (int)pos - (int)cell_height + 1); + return pos - adjustment; +} + static void calc_cell_metrics(FontGroup *fg) { unsigned int cell_height, cell_width, baseline, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness; cell_metrics(fg->fonts[fg->medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness, &strikethrough_position, &strikethrough_thickness); if (!cell_width) fatal("Failed to calculate cell width for the specified font"); unsigned int before_cell_height = cell_height; - int cw = cell_width, ch = cell_height; - if (OPT(adjust_line_height_px) != 0) ch += OPT(adjust_line_height_px); - if (OPT(adjust_line_height_frac) != 0.f) ch = (int)(ch * OPT(adjust_line_height_frac)); - if (OPT(adjust_column_width_px != 0)) cw += OPT(adjust_column_width_px); - if (OPT(adjust_column_width_frac) != 0.f) cw = (int)(cw * OPT(adjust_column_width_frac)); + unsigned int cw = cell_width, ch = cell_height; + adjust_metric(&cw, OPT(cell_width).val, OPT(cell_width).unit, fg->logical_dpi_x); + adjust_metric(&ch, OPT(cell_height).val, OPT(cell_height).unit, fg->logical_dpi_y); #define MAX_DIM 1000 #define MIN_WIDTH 2 #define MIN_HEIGHT 4 if (cw >= MIN_WIDTH && cw <= MAX_DIM) cell_width = cw; - else log_error("Cell width invalid after adjustment, ignoring adjust_column_width"); + else log_error("Cell width invalid after adjustment, ignoring modify_font cell_width"); if (ch >= MIN_HEIGHT && ch <= MAX_DIM) cell_height = ch; - else log_error("Cell height invalid after adjustment, ignoring adjust_line_height"); + else log_error("Cell height invalid after adjustment, ignoring modify_font cell_height"); int line_height_adjustment = cell_height - before_cell_height; if (cell_height < MIN_HEIGHT) fatal("Line height too small: %u", cell_height); if (cell_height > MAX_DIM) fatal("Line height too large: %u", cell_height); @@ -373,10 +378,18 @@ calc_cell_metrics(FontGroup *fg) { #undef MIN_HEIGHT #undef MAX_DIM + unsigned int baseline_before = baseline; #define A(which, dpi) adjust_metric(&which, OPT(which).val, OPT(which).unit, fg->logical_dpi_##dpi); - A(underline_thickness, y); A(underline_position, y); A(strikethrough_thickness, y); A(strikethrough_position, y); + A(underline_thickness, y); A(underline_position, y); A(strikethrough_thickness, y); A(strikethrough_position, y); A(baseline, y); #undef A + if (baseline_before != baseline) { + int adjustment = baseline - baseline_before; + baseline = adjust_ypos(baseline_before, cell_height, adjustment); + underline_position = adjust_ypos(underline_position, cell_height, adjustment); + strikethrough_position = adjust_ypos(underline_position, cell_height, adjustment); + } + underline_position = MIN(cell_height - 1, underline_position); // ensure there is at least a couple of pixels available to render styled underlines while (underline_position > baseline + 1 && cell_height - underline_position < 2) underline_position--; diff --git a/kitty/fonts/__init__.py b/kitty/fonts/__init__.py index 97f44ecdd..7a5af5ed6 100644 --- a/kitty/fonts/__init__.py +++ b/kitty/fonts/__init__.py @@ -29,6 +29,9 @@ class ModificationType(Enum): underline_thickness = auto() strikethrough_position = auto() strikethrough_thickness = auto() + cell_width = auto() + cell_height = auto() + baseline = auto() size = auto() diff --git a/kitty/freetype.c b/kitty/freetype.c index f25fb7dd5..bd028202c 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -306,21 +306,11 @@ calc_cell_width(Face *self) { } -static unsigned int -adjust_ypos(unsigned int pos, unsigned int cell_height, int adjustment) { - if (adjustment >= 0) adjustment = MIN(adjustment, (int)pos - 1); - else adjustment = MAX(adjustment, (int)pos - (int)cell_height + 1); - return pos - adjustment; -} - void cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness, unsigned int* strikethrough_position, unsigned int* strikethrough_thickness) { Face *self = (Face*)s; *cell_width = calc_cell_width(self); *cell_height = calc_cell_height(self, true); - int baseline_offset = 0; - if (OPT(adjust_baseline_px) != 0) baseline_offset = OPT(adjust_baseline_px); - else if (OPT(adjust_baseline_frac) != 0) baseline_offset = (int)(*cell_height * OPT(adjust_baseline_frac)); *baseline = font_units_to_pixels_y(self, self->ascender); *underline_position = MIN(*cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->ascender - self->underline_position))); *underline_thickness = MAX(1, font_units_to_pixels_y(self, self->underline_thickness)); @@ -335,11 +325,6 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u } else { *strikethrough_thickness = *underline_thickness; } - if (baseline_offset) { - *baseline = adjust_ypos(*baseline, *cell_height, baseline_offset); - *underline_position = adjust_ypos(*underline_position, *cell_height, baseline_offset); - *strikethrough_position = adjust_ypos(*strikethrough_position, *cell_height, baseline_offset); - } } unsigned int diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 01624e29d..3f663d64c 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -18,6 +18,7 @@ definition = Definition( definition.add_deprecation('deprecated_hide_window_decorations_aliases', 'x11_hide_window_decorations', 'macos_hide_titlebar') definition.add_deprecation('deprecated_macos_show_window_title_in_menubar_alias', 'macos_show_window_title_in_menubar') definition.add_deprecation('deprecated_send_text', 'send_text') +definition.add_deprecation('deprecated_adjust_line_height', 'adjust_line_height', 'adjust_column_width', 'adjust_baseline') agr = definition.add_group egr = definition.end_group @@ -80,34 +81,6 @@ terminals. ''' ) -opt('adjust_line_height', '0', - option_type='adjust_line_height', ctype='!adjust_line_height', - long_text=''' -Change the size of each character cell kitty renders. You can use either -numbers, which are interpreted as pixels or percentages (number followed by %), -which are interpreted as percentages of the unmodified values. You can use -negative pixels or percentages less than 100% to reduce sizes (but this might -cause rendering artifacts). -''' - ) - -opt('adjust_column_width', '0', - option_type='adjust_line_height', ctype='!adjust_column_width', - ) - -opt('adjust_baseline', '0', - option_type='adjust_baseline', ctype='!adjust_baseline', - add_to_default=False, - long_text=''' -Adjust the vertical alignment of text (the height in the cell at which text is -positioned). You can use either numbers, which are interpreted as pixels or -percentages (number followed by %), which are interpreted as the percentage of -the line height. A positive value moves the baseline up, and a negative value -moves them down. The underline and strikethrough positions are adjusted -accordingly. -''' - ) - opt('+symbol_map', 'U+E0A0-U+E0A3,U+E0C0-U+E0C7 PowerlineSymbols', option_type='symbol_map', add_to_default=False, @@ -227,6 +200,17 @@ No suffix means use pts. For example:: modify_font underline_position -2 modify_font underline_thickness 150% modify_font strikethrough_position 2px + +Additionally, you can modify the size of the cell in which each font glyph is rendered and the baseline +at which the glyph is placed in the cell. For example:: + + modify_font cell_width 80% + modify_font cell_height -2px + modify_font baseline 3 + +Note that modifying the baseline will automatically adjust the underline and strikethrough positions +by the same amount. Increasing the baseline raises glyphs inside the cell and decreasing it lowers them. +Decreasing the cell size might cause rendering artifacts, so use with care. ''') opt('box_drawing_scale', '0.001, 1, 1.5, 2', diff --git a/kitty/options/parse.py b/kitty/options/parse.py index c6d500270..a46de39fe 100644 --- a/kitty/options/parse.py +++ b/kitty/options/parse.py @@ -6,10 +6,10 @@ from kitty.conf.utils import ( unit_float ) from kitty.options.utils import ( - action_alias, active_tab_title_template, adjust_baseline, adjust_line_height, allow_hyperlinks, - allow_remote_control, bell_on_tab, box_drawing_scale, clear_all_mouse_actions, clear_all_shortcuts, - clipboard_control, clone_source_strategies, config_or_absolute_path, copy_on_select, - cursor_text_color, deprecated_hide_window_decorations_aliases, + action_alias, active_tab_title_template, allow_hyperlinks, allow_remote_control, bell_on_tab, + box_drawing_scale, clear_all_mouse_actions, clear_all_shortcuts, clipboard_control, + clone_source_strategies, config_or_absolute_path, copy_on_select, cursor_text_color, + deprecated_adjust_line_height, 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, modify_font, narrow_symbols, optional_edge_width, parse_map, parse_mouse_map, paste_actions, @@ -42,15 +42,6 @@ class Parser: def active_tab_title_template(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: ans['active_tab_title_template'] = active_tab_title_template(val) - def adjust_baseline(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: - ans['adjust_baseline'] = adjust_baseline(val) - - def adjust_column_width(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: - ans['adjust_column_width'] = adjust_line_height(val) - - def adjust_line_height(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: - ans['adjust_line_height'] = adjust_line_height(val) - def allow_cloning(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: val = val.lower() if val not in self.choices_for_allow_cloning: @@ -1352,6 +1343,15 @@ class Parser: def send_text(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: deprecated_send_text('send_text', val, ans) + def adjust_line_height(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + deprecated_adjust_line_height('adjust_line_height', val, ans) + + def adjust_column_width(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + deprecated_adjust_line_height('adjust_column_width', val, ans) + + def adjust_baseline(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: + deprecated_adjust_line_height('adjust_baseline', val, ans) + def map(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: for k in parse_map(val): ans['map'].append(k) diff --git a/kitty/options/to-c-generated.h b/kitty/options/to-c-generated.h index d601a269b..60a6d1d45 100644 --- a/kitty/options/to-c-generated.h +++ b/kitty/options/to-c-generated.h @@ -31,45 +31,6 @@ convert_from_opts_force_ltr(PyObject *py_opts, Options *opts) { Py_DECREF(ret); } -static void -convert_from_python_adjust_line_height(PyObject *val, Options *opts) { - adjust_line_height(val, opts); -} - -static void -convert_from_opts_adjust_line_height(PyObject *py_opts, Options *opts) { - PyObject *ret = PyObject_GetAttrString(py_opts, "adjust_line_height"); - if (ret == NULL) return; - convert_from_python_adjust_line_height(ret, opts); - Py_DECREF(ret); -} - -static void -convert_from_python_adjust_column_width(PyObject *val, Options *opts) { - adjust_column_width(val, opts); -} - -static void -convert_from_opts_adjust_column_width(PyObject *py_opts, Options *opts) { - PyObject *ret = PyObject_GetAttrString(py_opts, "adjust_column_width"); - if (ret == NULL) return; - convert_from_python_adjust_column_width(ret, opts); - Py_DECREF(ret); -} - -static void -convert_from_python_adjust_baseline(PyObject *val, Options *opts) { - adjust_baseline(val, opts); -} - -static void -convert_from_opts_adjust_baseline(PyObject *py_opts, Options *opts) { - PyObject *ret = PyObject_GetAttrString(py_opts, "adjust_baseline"); - if (ret == NULL) return; - convert_from_python_adjust_baseline(ret, opts); - Py_DECREF(ret); -} - static void convert_from_python_disable_ligatures(PyObject *val, Options *opts) { opts->disable_ligatures = PyLong_AsLong(val); @@ -1051,12 +1012,6 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) { if (PyErr_Occurred()) return false; convert_from_opts_force_ltr(py_opts, opts); if (PyErr_Occurred()) return false; - convert_from_opts_adjust_line_height(py_opts, opts); - if (PyErr_Occurred()) return false; - convert_from_opts_adjust_column_width(py_opts, opts); - if (PyErr_Occurred()) return false; - convert_from_opts_adjust_baseline(py_opts, opts); - if (PyErr_Occurred()) return false; convert_from_opts_disable_ligatures(py_opts, opts); if (PyErr_Occurred()) return false; convert_from_opts_modify_font(py_opts, opts); diff --git a/kitty/options/to-c.h b/kitty/options/to-c.h index ba2ed1820..0696429a2 100644 --- a/kitty/options/to-c.h +++ b/kitty/options/to-c.h @@ -129,6 +129,7 @@ static void modify_font(PyObject *mf, Options *opts) { #define S(which) { PyObject *v = PyDict_GetItemString(mf, #which); if (v) parse_font_mod_size(v, &opts->which.val, &opts->which.unit); } S(underline_position); S(underline_thickness); S(strikethrough_thickness); S(strikethrough_position); + S(cell_height); S(cell_width); S(baseline); #undef S } @@ -224,21 +225,3 @@ tab_bar_margin_height(PyObject *val, Options *opts) { opts->tab_bar_margin_height.outer = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 0)); opts->tab_bar_margin_height.inner = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 1)); } - -#define read_adjust(name) { \ - if (PyFloat_Check(al)) { \ - opts->name##_frac = (float)PyFloat_AsDouble(al); \ - opts->name##_px = 0; \ - } else { \ - opts->name##_frac = 0; \ - opts->name##_px = (int)PyLong_AsLong(al); \ - } \ -} - -static void -adjust_line_height(PyObject *al, Options *opts) { read_adjust(adjust_line_height); } -static void -adjust_column_width(PyObject *al, Options *opts) { read_adjust(adjust_column_width); } -static void -adjust_baseline(PyObject *al, Options *opts) { read_adjust(adjust_baseline); } -#undef read_adjust diff --git a/kitty/options/types.py b/kitty/options/types.py index 698276c1a..639e137e5 100644 --- a/kitty/options/types.py +++ b/kitty/options/types.py @@ -52,9 +52,6 @@ option_names = ( # {{{ 'active_tab_font_style', 'active_tab_foreground', 'active_tab_title_template', - 'adjust_baseline', - 'adjust_column_width', - 'adjust_line_height', 'allow_cloning', 'allow_hyperlinks', 'allow_remote_control', @@ -467,9 +464,6 @@ class Options: active_tab_font_style: typing.Tuple[bool, bool] = (True, True) active_tab_foreground: Color = Color(0, 0, 0) active_tab_title_template: typing.Optional[str] = None - adjust_baseline: typing.Union[int, float] = 0 - adjust_column_width: typing.Union[int, float] = 0 - adjust_line_height: typing.Union[int, float] = 0 allow_cloning: choices_for_allow_cloning = 'ask' allow_hyperlinks: int = 1 allow_remote_control: str = 'n' diff --git a/kitty/options/utils.py b/kitty/options/utils.py index d214ef536..9f205fad6 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -449,26 +449,6 @@ def parse_shortcut(sc: str) -> SingleKey: return SingleKey(mods, is_native, key or 0) -def adjust_line_height(x: str) -> Union[int, float]: - if x.endswith('%'): - ans = float(x[:-1].strip()) / 100.0 - if ans < 0: - log_error('Percentage adjustments of cell sizes must be positive numbers') - return 0 - return ans - return int(x) - - -def adjust_baseline(x: str) -> Union[int, float]: - if x.endswith('%'): - ans = float(x[:-1].strip()) / 100.0 - if abs(ans) > 1: - log_error('Percentage adjustments of the baseline cannot exceed 100%') - return 0 - return ans - return int(x) - - def to_font_size(x: str) -> float: return max(MINIMUM_FONT_SIZE, float(x)) @@ -1203,3 +1183,16 @@ def deprecated_send_text(key: str, val: str, ans: Dict[str, Any]) -> None: key_str = f'{sc} send_text {mode} {text}' for k in parse_map(key_str): ans['map'].append(k) + + +def deprecated_adjust_line_height(key: str, x: str, opts_dict: Dict[str, Any]) -> None: + fm = {'adjust_line_height': 'cell_height', 'adjust_baseline': 'baseline', 'adjust_column_width': 'cell_width'}[key] + mtype = getattr(ModificationType, fm) + if x.endswith('%'): + ans = float(x[:-1].strip()) + if ans < 0: + log_error(f'Percentage adjustments of {key} must be positive numbers') + return + opts_dict['modify_font'][fm] = FontModification(mtype, ModificationValue(ans, ModificationUnit.percent)) + else: + opts_dict['modify_font'][fm] = FontModification(mtype, ModificationValue(int(x), ModificationUnit.pixel)) diff --git a/kitty/state.h b/kitty/state.h index e7dbb8019..1acd20b9d 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -46,8 +46,6 @@ typedef struct { float macos_thicken_font; WindowTitleIn macos_show_window_title_in; char *bell_path; - int adjust_line_height_px, adjust_column_width_px, adjust_baseline_px; - float adjust_line_height_frac, adjust_column_width_frac, adjust_baseline_frac; float background_opacity, dim_opacity; char *background_image, *default_window_logo; @@ -88,7 +86,7 @@ typedef struct { int macos_colorspace; struct { float val; AdjustmentUnit unit; - } underline_position, underline_thickness, strikethrough_position, strikethrough_thickness; + } underline_position, underline_thickness, strikethrough_position, strikethrough_thickness, cell_width, cell_height, baseline; } Options; typedef struct WindowLogoRenderData {