diff --git a/kitty/core_text.m b/kitty/core_text.m index ff76081c2..a12784685 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -290,6 +290,12 @@ 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) { + // TODO: Implement this + return true; +} + static PyObject * repr(Face *self) { char buf[400] = {0}; diff --git a/kitty/fonts.c b/kitty/fonts.c index bd513d13c..eebbc55b9 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -191,7 +191,7 @@ 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; static inline void -clear_canvas(void) { memset(canvas, 0, cell_width * cell_height); } +clear_canvas(void) { memset(canvas, 0, 4 * cell_width * cell_height); } static void python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, uint8_t* buf) { @@ -222,7 +222,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(cell_width * cell_height); + free(canvas); canvas = malloc(4 * cell_width * cell_height); if (canvas == NULL) return PyErr_NoMemory(); return Py_BuildValue("IIIII", cell_width, cell_height, baseline, underline_position, underline_thickness); } @@ -357,7 +357,7 @@ load_hb_buffer(Cell *first_cell, index_type num_cells) { index_type num; hb_buffer_clear_contents(harfbuzz_buffer); while (num_cells) { - for (num = 0; num_cells-- && num < sizeof(shape_buffer)/sizeof(shape_buffer[0]) - 20; first_cell++) { + for (num = 0; num_cells && num < sizeof(shape_buffer)/sizeof(shape_buffer[0]) - 20; first_cell++, num_cells--) { shape_buffer[num++] = first_cell->ch; if (first_cell->cc) { shape_buffer[num++] = first_cell->cc & CC_MASK; @@ -370,10 +370,87 @@ 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); + } +} + +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++; + uint64_t extra_glyphs; +#define G(n) ((uint64_t)(info[n].codepoint & 0xffff)) + glyph_index glyph = G(0); + switch(num_glyphs) { + case 1: + extra_glyphs = 0; + break; + case 2: + extra_glyphs = G(1); + break; + case 3: + extra_glyphs = G(1) | (G(2) << 16); + break; + case 4: + extra_glyphs = G(1) | (G(2) << 16) | (G(3) << 32); + break; + default: // we only support a maximum of four extra glyphs per cell + extra_glyphs = G(1) | (G(2) << 16) | (G(3) << 32) | (G(4) << 48); + break; + } +#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; } + } + if (sp->rendered && (!sp2 || sp2->rendered)) goto end; + 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; + } + 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 shape_run(Cell *first_cell, index_type num_cells, Font *font) { + // See https://www.mail-archive.com/harfbuzz@lists.freedesktop.org/msg04698.html + // 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; + 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); + } } static inline void @@ -425,9 +502,9 @@ set_font(PyObject UNUSED *m, PyObject *args) { Py_INCREF(get_fallback_font); Py_INCREF(box_drawing_function); clear_font(&medium_font); clear_font(&bold_font); clear_font(&italic_font); clear_font(&bi_font); clear_sprite_map(&box_font); if (!alloc_font(&medium_font, medium, false, false)) return PyErr_NoMemory(); - if (bold && !alloc_font(&bold_font, bold, false, false)) return PyErr_NoMemory(); - if (italic && !alloc_font(&italic_font, italic, false, false)) return PyErr_NoMemory(); - if (bi && !alloc_font(&bi_font, bi, false, false)) return PyErr_NoMemory(); + if (bold && !alloc_font(&bold_font, bold, true, false)) return PyErr_NoMemory(); + if (italic && !alloc_font(&italic_font, italic, false, true)) return PyErr_NoMemory(); + if (bi && !alloc_font(&bi_font, bi, true, true)) return PyErr_NoMemory(); for (size_t i = 0; fallback_fonts[i].face != NULL; i++) clear_font(fallback_fonts + i); for (size_t i = 0; symbol_map_fonts_count; i++) free_font(symbol_map_fonts + i); free(symbol_maps); free(symbol_map_fonts); symbol_maps = NULL; symbol_map_fonts = NULL; diff --git a/kitty/fonts.h b/kitty/fonts.h index 5987ee8c3..fa1fec2e9 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -19,3 +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); diff --git a/kitty/freetype.c b/kitty/freetype.c index 69e25da24..363c897f0 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -97,6 +97,17 @@ set_size_for_face(PyObject *self, float pt_sz, float xdpi, float ydpi) { return set_font_size((Face*)self, w, w, (FT_UInt)xdpi, (FT_UInt) ydpi); } +static inline int +get_load_flags(int hinting, int hintstyle, int base) { + int flags = base; + if (hinting) { + if (hintstyle >= 3) flags |= FT_LOAD_TARGET_NORMAL; + else if (0 < hintstyle && hintstyle < 3) flags |= FT_LOAD_TARGET_LIGHT; + } else flags |= FT_LOAD_NO_HINTING; + return flags; +} + + static PyObject* new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { Face *self; @@ -120,6 +131,9 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { if (!set_size_for_face((PyObject*)self, size_in_pts, xdpi, ydpi)) { Py_CLEAR(self); return NULL; } self->harfbuzz_font = hb_ft_font_create(self->face, NULL); if (self->harfbuzz_font == NULL) { Py_CLEAR(self); return PyErr_NoMemory(); } +#ifdef HARBUZZ_HAS_LOAD_FLAGS + hb_ft_font_set_load_flags(self->harfbuzz_font, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT)); +#endif } return (PyObject*)self; } @@ -142,21 +156,11 @@ repr(Face *self) { } -static inline int -get_load_flags(int hinting, int hintstyle, int base) { - int flags = base; - if (hinting) { - if (hintstyle >= 3) flags |= FT_LOAD_TARGET_NORMAL; - else if (0 < hintstyle && hintstyle < 3) flags |= FT_LOAD_TARGET_LIGHT; - } else flags |= FT_LOAD_NO_HINTING; - return flags; -} - static inline bool load_glyph(Face *self, int glyph_index, int load_type) { int flags = get_load_flags(self->hinting, self->hintstyle, load_type); int error = FT_Load_Glyph(self->face, glyph_index, flags); - if (error) { set_freetype_error("Failed to load glyph, with error:", error); Py_CLEAR(self); return false; } + if (error) { set_freetype_error("Failed to load glyph, with error:", error); return false; } return true; } @@ -195,6 +199,112 @@ face_has_codepoint(PyObject *s, char_type cp) { hb_font_t* harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; } + +typedef struct { + unsigned char* buf; + size_t start_x, width, stride; + size_t rows; +} ProcessedBitmap; + + +static inline void +trim_borders(ProcessedBitmap *ans, size_t extra) { + bool column_has_text = false; + + // Trim empty columns from the right side of the bitmap + for (ssize_t x = ans->width - 1; !column_has_text && x > -1 && extra > 0; x--) { + for (size_t y = 0; y < ans->rows && !column_has_text; y++) { + if (ans->buf[x + y * ans->stride] > 200) column_has_text = true; + } + if (!column_has_text) { ans->width--; extra--; } + } + + ans->start_x = extra; + ans->width -= extra; +} + + + +static inline bool +render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int num_cells, bool bold, bool italic, bool rescale) { + if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false; + unsigned int max_width = cell_width * num_cells; + FT_Bitmap *bitmap = &self->face->glyph->bitmap; + ans->buf = bitmap->buffer; + ans->start_x = 0; ans->width = bitmap->width; + ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch; + ans->rows = bitmap->rows; + if (ans->width > max_width) { + size_t extra = bitmap->width - max_width; + if (italic && extra < cell_width / 2) { + trim_borders(ans, extra); + } else if (rescale && self->is_scalable && extra > MAX(2, cell_width / 3)) { + FT_F26Dot6 char_width = self->char_width, char_height = self->char_height; + float ar = (float)max_width / (float)bitmap->width; + if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi)) { + if (!render_bitmap(self, glyph_id, ans, cell_width, num_cells, bold, italic, false)) return false; + if (!set_font_size(self, char_width, char_height, self->xdpi, self->ydpi)) return false; + } else return false; + } + } + return true; +} + +static inline void +place_bitmap_in_cell(unsigned char *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, FT_Glyph_Metrics *metrics, size_t baseline) { + // We want the glyph to be positioned inside the cell based on the bearingX + // and bearingY values, making sure that it does not overflow the cell. + + // Calculate column bounds + ssize_t xoff = (ssize_t)(x_offset + (float)metrics->horiBearingX / 64.f); + size_t src_start_column = bm->start_x, dest_start_column = 0, extra; + if (xoff < 0) src_start_column += -xoff; + else dest_start_column = xoff; + // Move the dest start column back if the width overflows because of it + if (dest_start_column > 0 && dest_start_column + bm->width > cell_width) { + extra = dest_start_column + bm->width - cell_width; + dest_start_column = extra > dest_start_column ? 0 : dest_start_column - extra; + } + + // Calculate row bounds + ssize_t yoff = (ssize_t)(y_offset + (float)metrics->horiBearingY / 64.f); + size_t src_start_row, dest_start_row; + if (yoff > 0 && (size_t)yoff > baseline) { + src_start_row = 0; + dest_start_row = 0; + } else { + src_start_row = 0; + dest_start_row = baseline - yoff; + } + + /* printf("src_start_row: %zu src_start_column: %zu dest_start_row: %zu dest_start_column: %zu\n", src_start_row, src_start_column, dest_start_row, dest_start_column); */ + + for (size_t sr = src_start_row, dr = dest_start_row; sr < bm->rows && dr < cell_height; sr++, dr++) { + for(size_t sc = src_start_column, dc = dest_start_column; sc < bm->width && dc < cell_width; sc++, dc++) { + uint16_t val = cell[dr * cell_width + dc]; + val = (val + bm->buf[sr * bm->stride + sc]) % 256; + cell[dr * cell_width + dc] = val; + } + } +} + + +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) { + Face *self = (Face*)f; + float x = 0.f, y = 0.f; + ProcessedBitmap bm; + for (unsigned int i = 0; i < num_glyphs; i++) { + if (info[i].codepoint == 0) continue; + if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, num_cells, bold, italic, true)) return false; + x += (float)positions[i].x_offset / 64.0f; + y = (float)positions[i].y_offset / 64.0f; + place_bitmap_in_cell(canvas, &bm, cell_width * num_cells, cell_height, x, y, &self->face->glyph->metrics, baseline); + x += (float)positions[i].x_advance / 64.0f; + } + return true; +} + // Boilerplate {{{ static PyMemberDef members[] = {