diff --git a/docs/changelog.rst b/docs/changelog.rst index 47d469349..9fe4b9683 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,6 +7,9 @@ To update |kitty|, :doc:`follow the instructions `. 0.21.2 [future] ---------------------- +- A new :opt:`adjust_baseline` option to adjust the vertical alignment of text + inside a line (:pull:`3734`) + - Fix a regression in 0.21.0 that broke rendering of private use unicode symbols followed by spaces, when they also exist not followed by spaces (:iss:`3729`) diff --git a/kitty/core_text.m b/kitty/core_text.m index ff1be554f..733bf2d13 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -323,6 +323,13 @@ 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 @@ -368,8 +375,9 @@ 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; - if (OPT(adjust_baseline_px) != 0) bounds_ascent -= OPT(adjust_baseline_px); - if (OPT(adjust_baseline_frac) != 0) bounds_ascent -= *cell_height * OPT(adjust_baseline_frac); + 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 = *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. @@ -384,6 +392,11 @@ 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); diff --git a/kitty/freetype.c b/kitty/freetype.c index 95d83c3a1..296375ec0 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -302,6 +302,14 @@ calc_cell_width(Face *self) { return ans; } + +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; @@ -309,13 +317,13 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u *cell_height = calc_cell_height(self, true); int baseline_offset = 0; if (OPT(adjust_baseline_px) != 0) baseline_offset = OPT(adjust_baseline_px); - if (OPT(adjust_baseline_frac) != 0) baseline_offset = (int)(*cell_height * OPT(adjust_baseline_frac)); - *baseline = font_units_to_pixels_y(self, self->ascender) - baseline_offset; - *underline_position = MIN(*cell_height - 1, (unsigned int)MAX(0, font_units_to_pixels_y(self, MAX(0, self->ascender - self->underline_position)) - baseline_offset)); + 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)MAX(0, 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)); if (self->strikethrough_position != 0) { - *strikethrough_position = MIN(*cell_height - 1, (unsigned int)MAX(0, font_units_to_pixels_y(self, MAX(0, self->ascender - self->strikethrough_position)) - baseline_offset)); + *strikethrough_position = MIN(*cell_height - 1, (unsigned int)MAX(0, font_units_to_pixels_y(self, MAX(0, self->ascender - self->strikethrough_position)))); } else { *strikethrough_position = (unsigned int)floor(*baseline * 0.65); } @@ -324,6 +332,11 @@ 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 007be789a..b5bccc8d4 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -98,11 +98,12 @@ opt('adjust_baseline', '0', option_type='adjust_baseline', ctype='!adjust_baseline', add_to_default=False, long_text=''' -Adjust the baseline position of each character. You can use either -a number, which is interpreted as pixels or a percentage (number followed by %), -which is interpreted as the percentage of the line height. A positive -value moves all characters up, and a negative value moves them down. -The underline and strikethrough positions are adjusted accordingly. +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 a +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. ''' )