From 931e91f1a77b0376bc77c21b5221d7309f8525cb Mon Sep 17 00:00:00 2001 From: Trygve Aaberge Date: Sat, 28 Mar 2020 05:38:11 +0100 Subject: [PATCH] Read strikethrough info from font when using FreeType This reads the strikethrough position and thickness from the font so it is rendered correctly. This is only implemented for FreeType, not Core Text, because I didn't find any way to get the info from Core Text, and I don't have a Mac to test it on either. When using Core Text or when the font doesn't provide the info, the same approximation as before is used. --- kitty/core_text.m | 4 +++- kitty/fast_data_types.pyi | 2 +- kitty/fonts.c | 10 +++++----- kitty/fonts.h | 2 +- kitty/fonts/render.py | 12 ++++++++---- kitty/freetype.c | 28 ++++++++++++++++++++++++---- 6 files changed, 42 insertions(+), 16 deletions(-) diff --git a/kitty/core_text.m b/kitty/core_text.m index 23d1bcf5b..15d58c460 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -312,7 +312,7 @@ harfbuzz_font_for_face(PyObject* s) { } void -cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) { +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 CTFace *self = (CTFace*)s; #define count (128 - 32) @@ -332,6 +332,8 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u *underline_position = (unsigned int)floor(self->ascent - self->underline_position + 0.5); *underline_thickness = (unsigned int)ceil(MAX(0.1, self->underline_thickness)); *baseline = (unsigned int)self->ascent; + *strikethrough_position = (unsigned int)floor(*baseline * 0.65); + *strikethrough_thickness = *underline_thickness; // float line_height = MAX(1, floor(self->ascent + self->descent + MAX(0, self->leading) + 0.5)); // Let CoreText's layout engine calculate the line height. Slower, but hopefully more accurate. #define W "AQWMH_gyl " diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 580f29969..814c4179e 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -902,7 +902,7 @@ def set_font_data( box_drawing_func: Callable[[int, int, int, float], Tuple[int, Union[bytearray, bytes, Array]]], prerender_func: Callable[ - [int, int, int, int, int, float, float, float, float], + [int, int, int, int, int, int, int, float, float, float, float], Tuple[Union[Array, int], ...]], descriptor_for_idx: Callable[[int], Tuple[FontObject, bool, bool]], bold: int, italic: int, bold_italic: int, num_symbol_fonts: int, diff --git a/kitty/fonts.c b/kitty/fonts.c index b045587f5..d35dc902a 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -85,7 +85,7 @@ typedef struct { typedef struct { FONTS_DATA_HEAD id_type id; - unsigned int baseline, underline_position, underline_thickness; + unsigned int baseline, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness; size_t fonts_capacity, fonts_count, fallback_fonts_count; ssize_t medium_font_idx, bold_font_idx, italic_font_idx, bi_font_idx, first_symbol_font_idx, first_fallback_font_idx; Font *fonts; @@ -422,8 +422,8 @@ python_send_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigne static inline void calc_cell_metrics(FontGroup *fg) { - unsigned int cell_height, cell_width, baseline, underline_position, underline_thickness; - cell_metrics(fg->fonts[fg->medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness); + 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; @@ -455,7 +455,7 @@ calc_cell_metrics(FontGroup *fg) { } sprite_tracker_set_layout(&fg->sprite_tracker, cell_width, cell_height); fg->cell_width = cell_width; fg->cell_height = cell_height; - fg->baseline = baseline; fg->underline_position = underline_position; fg->underline_thickness = underline_thickness; + fg->baseline = baseline; fg->underline_position = underline_position; fg->underline_thickness = underline_thickness, fg->strikethrough_position = strikethrough_position, fg->strikethrough_thickness = strikethrough_thickness; free(fg->canvas); fg->canvas = calloc(CELLS_IN_CANVAS * fg->cell_width * fg->cell_height, sizeof(pixel)); if (!fg->canvas) fatal("Out of memory allocating canvas for font group"); @@ -1243,7 +1243,7 @@ send_prerendered_sprites(FontGroup *fg) { current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, x, y, z, fg->canvas); do_increment(fg, &error); if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("Failed"); } - PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIffdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, OPT(cursor_beam_thickness), OPT(cursor_underline_thickness), fg->logical_dpi_x, fg->logical_dpi_y); + PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIIIffdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, fg->strikethrough_position, fg->strikethrough_thickness, OPT(cursor_beam_thickness), OPT(cursor_underline_thickness), fg->logical_dpi_x, fg->logical_dpi_y); if (args == NULL) { PyErr_Print(); fatal("Failed to pre-render cells"); } for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) { x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z; diff --git a/kitty/fonts.h b/kitty/fonts.h index 361597cd6..7a9cb6f21 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -25,7 +25,7 @@ int get_glyph_width(PyObject *, glyph_index); bool is_glyph_empty(PyObject *, glyph_index); hb_font_t* harfbuzz_font_for_face(PyObject*); bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE); -void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); +void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, bool center_glyph); PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg); PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE); diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 157834845..bd9d02bff 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -191,7 +191,9 @@ def render_special( cell_width: int = 0, cell_height: int = 0, baseline: int = 0, underline_position: int = 0, - underline_thickness: int = 0 + underline_thickness: int = 0, + strikethrough_position: int = 0, + strikethrough_thickness: int = 0 ) -> ctypes.Array: underline_position = min(underline_position, cell_height - underline_thickness) CharTexture = ctypes.c_ubyte * (cell_width * cell_height) @@ -216,8 +218,7 @@ def render_special( t = max(1, min(cell_height - underline_position - 1, t)) dl([add_line, add_line, add_dline, add_curl][underline], underline_position, t, cell_height) if strikethrough: - pos = int(0.65 * baseline) - dl(add_line, pos, underline_thickness, cell_height) + dl(add_line, strikethrough_position, strikethrough_thickness, cell_height) return ans @@ -268,6 +269,8 @@ def prerender_function( baseline: int, underline_position: int, underline_thickness: int, + strikethrough_position: int, + strikethrough_thickness: int, cursor_beam_thickness: float, cursor_underline_thickness: float, dpi_x: float, @@ -276,7 +279,8 @@ def prerender_function( # Pre-render the special underline, strikethrough and missing and cursor cells f = partial( render_special, cell_width=cell_width, cell_height=cell_height, baseline=baseline, - underline_position=underline_position, underline_thickness=underline_thickness) + underline_position=underline_position, underline_thickness=underline_thickness, + strikethrough_position=strikethrough_position, strikethrough_thickness=strikethrough_thickness) c = partial( render_cursor, cursor_beam_thickness=cursor_beam_thickness, cursor_underline_thickness=cursor_underline_thickness, cell_width=cell_width, diff --git a/kitty/freetype.c b/kitty/freetype.c index 90ee84a9a..741b4229a 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -22,12 +22,13 @@ #include FT_FREETYPE_H #include FT_BITMAP_H +#include FT_TRUETYPE_TABLES_H typedef struct { PyObject_HEAD FT_Face face; unsigned int units_per_EM; - int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness; + int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness; int hinting, hintstyle, index; bool is_scalable, has_color; float size_in_pts; @@ -207,6 +208,12 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_ if (self->harfbuzz_font == NULL) { PyErr_NoMemory(); return false; } hb_ft_font_set_load_flags(self->harfbuzz_font, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT)); + TT_OS2 *os2 = (TT_OS2*)FT_Get_Sfnt_Table(self->face, FT_SFNT_OS2); + if (os2 != NULL) { + self->strikethrough_position = os2->yStrikeoutPosition; + self->strikethrough_thickness = os2->yStrikeoutSize; + } + self->path = path; Py_INCREF(self->path); self->index = self->face->face_index & 0xFFFF; @@ -263,11 +270,11 @@ static PyObject * repr(Face *self) { const char *ps_name = FT_Get_Postscript_Name(self->face); return PyUnicode_FromFormat( - "Face(family=%s, style=%s, ps_name=%s, path=%S, index=%d, is_scalable=%S, has_color=%S, ascender=%i, descender=%i, height=%i, underline_position=%i, underline_thickness=%i)", + "Face(family=%s, style=%s, ps_name=%s, path=%S, index=%d, is_scalable=%S, has_color=%S, ascender=%i, descender=%i, height=%i, underline_position=%i, underline_thickness=%i, strikethrough_position=%i, strikethrough_thickness=%i)", self->face->family_name ? self->face->family_name : "", self->face->style_name ? self->face->style_name : "", ps_name ? ps_name: "", self->path, self->index, self->is_scalable ? Py_True : Py_False, self->has_color ? Py_True : Py_False, - self->ascender, self->descender, self->height, self->underline_position, self->underline_thickness + self->ascender, self->descender, self->height, self->underline_position, self->underline_thickness, self->strikethrough_position, self->strikethrough_thickness ); } @@ -292,13 +299,24 @@ calc_cell_width(Face *self) { } void -cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) { +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); *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)); + + if (self->strikethrough_position != 0) { + *strikethrough_position = MIN(*cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->ascender - self->strikethrough_position))); + } else { + *strikethrough_position = (unsigned int)floor(*baseline * 0.65); + } + if (self->strikethrough_thickness != 0) { + *strikethrough_thickness = MAX(1, font_units_to_pixels_y(self, self->strikethrough_thickness)); + } else { + *strikethrough_thickness = *underline_thickness; + } } unsigned int @@ -659,6 +677,8 @@ static PyMemberDef members[] = { MEM(max_advance_height, T_INT), MEM(underline_position, T_INT), MEM(underline_thickness, T_INT), + MEM(strikethrough_position, T_INT), + MEM(strikethrough_thickness, T_INT), MEM(is_scalable, T_BOOL), MEM(path, T_OBJECT_EX), {NULL} /* Sentinel */