From 58be99a27eeae5b7f4194c98f638bd619f93f6b4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 4 Nov 2017 11:46:26 +0530 Subject: [PATCH] Work on rendering ligatures correctly --- kitty/core_text.m | 2 +- kitty/fonts.c | 122 +++++++++++++++++++++++++++------------------- kitty/fonts.h | 2 +- kitty/freetype.c | 2 +- 4 files changed, 75 insertions(+), 53 deletions(-) diff --git a/kitty/core_text.m b/kitty/core_text.m index b389bf06e..ec8875040 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -291,7 +291,7 @@ set_size_for_face(PyObject *self, float pt_sz, float xdpi, float ydpi) { bool -render_glyphs_in_cell(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) { +render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) { // TODO: Implement this (void)(f); (void)(bold); (void)italic; (void)info; (void)positions; (void)num_glyphs; (void)num_glyphs; (void)canvas; (void)cell_width; (void)cell_height; (void)num_cells; (void)baseline; return true; diff --git a/kitty/fonts.c b/kitty/fonts.c index d15c8b3e4..fc7b53c11 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -20,8 +20,9 @@ typedef struct SpritePosition SpritePosition; struct SpritePosition { SpritePosition *next; - bool filled, rendered, is_second; + bool filled, rendered; sprite_index x, y, z; + uint8_t ligature_index; glyph_index glyph; uint64_t extra_glyphs; }; @@ -80,14 +81,14 @@ do_increment(int *error) { static SpritePosition* -sprite_position_for(Font *font, glyph_index glyph, uint64_t extra_glyphs, bool is_second, int *error) { +sprite_position_for(Font *font, glyph_index glyph, uint64_t extra_glyphs, uint8_t ligature_index, int *error) { glyph_index idx = glyph & 0x3ff; SpritePosition *s = font->sprite_map + idx; // Optimize for the common case of glyph under 1024 already in the cache - if (LIKELY(s->glyph == glyph && s->filled && s->extra_glyphs == extra_glyphs && s->is_second == is_second)) return s; // Cache hit + if (LIKELY(s->glyph == glyph && s->filled && s->extra_glyphs == extra_glyphs && s->ligature_index == ligature_index)) return s; // Cache hit while(true) { if (s->filled) { - if (s->glyph == glyph && s->extra_glyphs == extra_glyphs && s->is_second == is_second) return s; // Cache hit + if (s->glyph == glyph && s->extra_glyphs == extra_glyphs && s->ligature_index == ligature_index) return s; // Cache hit } else { break; } @@ -99,7 +100,7 @@ sprite_position_for(Font *font, glyph_index glyph, uint64_t extra_glyphs, bool i } s->glyph = glyph; s->extra_glyphs = extra_glyphs; - s->is_second = is_second; + s->ligature_index = ligature_index; s->filled = true; s->rendered = false; s->x = sprite_tracker.x; s->y = sprite_tracker.y; s->z = sprite_tracker.z; @@ -128,7 +129,7 @@ sprite_map_free(Font *font) { void clear_sprite_map(Font *font) { -#define CLEAR(s) s->filled = false; s->rendered = false; s->glyph = 0; s->extra_glyphs = 0; s->x = 0; s->y = 0; s->z = 0; s->is_second = false; +#define CLEAR(s) s->filled = false; s->rendered = false; s->glyph = 0; s->extra_glyphs = 0; s->x = 0; s->y = 0; s->z = 0; s->ligature_index = 0; SpritePosition *s; for (size_t i = 0; i < sizeof(font->sprite_map)/sizeof(font->sprite_map[0]); i++) { s = font->sprite_map + i; @@ -190,8 +191,9 @@ static size_t symbol_maps_count = 0, symbol_map_fonts_count = 0; static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0; static uint8_t *canvas = NULL; +#define CELLS_IN_CANVAS 16 static inline void -clear_canvas(void) { memset(canvas, 0, 4 * cell_width * cell_height); } +clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height); } static void python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, uint8_t* buf) { @@ -222,7 +224,7 @@ update_cell_metrics(float pt_sz, float xdpi, float ydpi) { if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; } underline_position = MIN(cell_height - 1, underline_position); sprite_tracker_set_layout(cell_width, cell_height); - free(canvas); canvas = malloc(4 * cell_width * cell_height); + free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height); if (canvas == NULL) return PyErr_NoMemory(); return Py_BuildValue("IIIII", cell_width, cell_height, baseline, underline_position, underline_thickness); } @@ -371,21 +373,26 @@ load_hb_buffer(Cell *first_cell, index_type num_cells) { hb_buffer_guess_segment_properties(harfbuzz_buffer); } + static inline void -split_cell(uint8_t *src, uint8_t *d1, uint8_t *d2) { - for (size_t r = 0; r < cell_height; r++, d1 += cell_width, d2 += cell_width, src += 2 * cell_width) { - memcpy(d1, src, cell_width); memcpy(d2, src + cell_width, cell_width); - } +set_cell_sprite(Cell *cell, SpritePosition *sp) { + cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z; } -static inline unsigned int -shape_cell(Cell *cell, Cell *second_cell, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int length, Font *font) { - uint32_t cluster = info[0].cluster; - unsigned int num_glyphs = 1; - while (num_glyphs < length && info[num_glyphs].cluster == cluster) num_glyphs++; +static inline uint8_t* +extract_cell_from_canvas(unsigned int i) { + uint8_t *ans = canvas + (cell_width * cell_height * (CELLS_IN_CANVAS - 1)), *dest = ans; + uint8_t *src = canvas + (cell_width * cell_height * i); + for (unsigned int r = 0; r < cell_height; r++, dest += cell_width, src += cell_width) memcpy(dest, src, cell_width); + return ans; +} + +static inline void +render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font) { uint64_t extra_glyphs; #define G(n) ((uint64_t)(info[n].codepoint & 0xffff)) glyph_index glyph = G(0); + SpritePosition* sprite_position[5]; switch(num_glyphs) { case 1: extra_glyphs = 0; @@ -405,34 +412,44 @@ shape_cell(Cell *cell, Cell *second_cell, hb_glyph_info_t *info, hb_glyph_positi } #undef G int error = 0; - SpritePosition *sp = sprite_position_for(font, glyph, extra_glyphs, false, &error), *sp2 = NULL; - if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return num_glyphs; } - if (second_cell) { - sp2 = sprite_position_for(font, glyph, extra_glyphs, true, &error); - if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return num_glyphs; } + for (unsigned int i = 0; i < num_cells; i++) { + sprite_position[i] = sprite_position_for(font, glyph, extra_glyphs, (uint8_t)i, &error); + if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return; } } - if (sp->rendered && (!sp2 || sp2->rendered)) goto end; + if (sprite_position[0]->rendered) { + for (unsigned int i = 0; i < num_cells; i++) { set_cell_sprite(cells + i, sprite_position[i]); } + return; + } + clear_canvas(); - if (!render_glyphs_in_cell(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, second_cell == NULL ? 1 : 2, baseline)) { - PyErr_Print(); return num_glyphs; + render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline); + + for (unsigned int i = 0; i < num_cells; i++) { + sprite_position[i]->rendered = true; + set_cell_sprite(cells + i, sprite_position[i]); + uint8_t *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i); + current_send_sprite_to_gpu(sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf); } - if (second_cell) { - uint8_t *d1 = canvas + (2 * cell_width * cell_height); - uint8_t *d2 = canvas + (3 * cell_width * cell_height); - split_cell(canvas, d1, d2); - current_send_sprite_to_gpu(sp->x, sp->y, sp->z, d1); - current_send_sprite_to_gpu(sp2->x, sp2->y, sp2->z, d2); - sp2->rendered = true; - } else { - current_send_sprite_to_gpu(sp->x, sp->y, sp->z, canvas); - } - sp->rendered = true; -end: - cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z; - if (second_cell) { second_cell->sprite_x = sp2->x; second_cell->sprite_y = sp2->y; second_cell->sprite_z = sp2->z; } - return num_glyphs; + } +static inline void +next_group(unsigned int *num_group_cells, unsigned int *num_group_glyphs, Cell *cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, unsigned int num_cells) { + num_glyphs = MIN(num_glyphs, 5); // we only support groupes of upto 5 glyphs + *num_group_cells = 0, *num_group_glyphs = 0; + bool unsafe_to_break; + do { + // If the glyph has no advance, then it is a combing char + if (positions[*num_group_glyphs].x_advance != 0) *num_group_cells += ((cells[*num_group_cells].attrs & WIDTH_MASK) == 2) ? 2 : 1; + + // check if the next glyph can be broken at + *num_group_glyphs += 1; + unsafe_to_break = *num_group_glyphs < num_glyphs && info[*num_group_glyphs].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK; + + } while (unsafe_to_break && *num_group_cells < num_cells && *num_group_glyphs < MIN(num_glyphs, 6)); + *num_group_cells = MAX(1, MIN(*num_group_cells, num_cells)); + *num_group_glyphs = MAX(1, MIN(*num_group_glyphs, num_glyphs)); +} static inline void shape_run(Cell *first_cell, index_type num_cells, Font *font) { @@ -440,17 +457,21 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) { // for a discussion of glyph clustering in harfbuzz load_hb_buffer(first_cell, num_cells); hb_shape(font->hb_font, harfbuzz_buffer, NULL, 0); - unsigned int info_length, positions_length, length; + unsigned int info_length, positions_length, num_glyphs; hb_glyph_info_t *info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length); hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(harfbuzz_buffer, &positions_length); - length = MIN(info_length, positions_length); - unsigned int run_pos = 0; - Cell *cell, *second_cell; - while(length > run_pos && num_cells) { - cell = first_cell++; num_cells--; - if ((cell->attrs & WIDTH_MASK) == 2 && num_cells) { second_cell = first_cell++; num_cells--; } - else second_cell = NULL; - run_pos += shape_cell(cell, second_cell, info + run_pos, positions + run_pos, length - run_pos, font); + num_glyphs = MIN(info_length, positions_length); +#if 0 + // You can also generate this easily using hb-shape --show-flags --show-extents --cluster-level=1 /path/to/font/file text + hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, num_glyphs, (char*)canvas, 4 * cell_width * cell_height, NULL, font->hb_font, HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS | HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS); + printf("\n%s\n", canvas); + clear_canvas(); +#endif + unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells; + while(run_pos < num_glyphs && cell_pos < num_cells) { + next_group(&num_group_cells, &num_group_glyphs, first_cell + cell_pos, info + run_pos, positions + run_pos, num_glyphs - run_pos, num_cells - cell_pos); + render_group(num_group_cells, num_group_glyphs, first_cell + cell_pos, info + run_pos, positions + run_pos, font); + run_pos += num_group_glyphs; cell_pos += num_group_cells; } } @@ -568,7 +589,7 @@ test_sprite_position_for(PyObject UNUSED *self, PyObject *args) { uint64_t extra_glyphs = 0; if (!PyArg_ParseTuple(args, "H|I", &glyph, &extra_glyphs)) return NULL; int error; - SpritePosition *pos = sprite_position_for(&medium_font, glyph, extra_glyphs, false, &error); + SpritePosition *pos = sprite_position_for(&medium_font, glyph, extra_glyphs, 0, &error); if (pos == NULL) { sprite_map_set_error(error); return NULL; } return Py_BuildValue("HHH", pos->x, pos->y, pos->z); } @@ -650,6 +671,7 @@ init_fonts(PyObject *module) { } harfbuzz_buffer = hb_buffer_create(); if (harfbuzz_buffer == NULL || !hb_buffer_allocation_successful(harfbuzz_buffer) || !hb_buffer_pre_allocate(harfbuzz_buffer, 2048)) { PyErr_NoMemory(); return false; } + hb_buffer_set_cluster_level(harfbuzz_buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS); if (PyModule_AddFunctions(module, module_methods) != 0) return false; current_send_sprite_to_gpu = send_sprite_to_gpu; return true; diff --git a/kitty/fonts.h b/kitty/fonts.h index fa1fec2e9..676000c22 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -19,4 +19,4 @@ hb_font_t* harfbuzz_font_for_face(PyObject*); bool set_size_for_face(PyObject*, float, float, float); void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); void sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z); -bool render_glyphs_in_cell(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline); +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, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline); diff --git a/kitty/freetype.c b/kitty/freetype.c index 363c897f0..5fffaeaba 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -290,7 +290,7 @@ place_bitmap_in_cell(unsigned char *cell, ProcessedBitmap *bm, size_t cell_width bool -render_glyphs_in_cell(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) { +render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) { Face *self = (Face*)f; float x = 0.f, y = 0.f; ProcessedBitmap bm;