diff --git a/kitty/freetype.c b/kitty/freetype.c index c47902679..4e259206a 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -12,10 +12,6 @@ #include #include -#if HB_VERSION_MAJOR > 1 || (HB_VERSION_MAJOR == 1 && (HB_VERSION_MINOR > 6 || (HB_VERSION_MINOR == 6 && HB_VERSION_MICRO >= 3))) -#define HARFBUZZ_HAS_CHANGE_FONT -#endif - #if FREETYPE_MAJOR == 2 && FREETYPE_MINOR < 7 #define FT_Bitmap_Init FT_Bitmap_New #endif @@ -149,17 +145,7 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt return set_font_size(self, 0, h, xdpi, ydpi, 0, cell_height); } self->char_width = char_width; self->char_height = char_height; self->xdpi = xdpi; self->ydpi = ydpi; - if (self->harfbuzz_font != NULL) { -#ifdef HARFBUZZ_HAS_CHANGE_FONT - hb_ft_font_changed(self->harfbuzz_font); -#else - hb_font_set_scale( - self->harfbuzz_font, - (int) (((uint64_t) self->face->size->metrics.x_scale * (uint64_t) self->face->units_per_EM + (1u<<15)) >> 16), - (int) (((uint64_t) self->face->size->metrics.y_scale * (uint64_t) self->face->units_per_EM + (1u<<15)) >> 16) - ); -#endif - } + if (self->harfbuzz_font != NULL) hb_ft_font_changed(self->harfbuzz_font); } else { if (!self->is_scalable && self->face->num_fixed_sizes > 0) { int32_t min_diff = INT32_MAX; diff --git a/kitty/freetype_render_ui_text.c b/kitty/freetype_render_ui_text.c index c03f34b1f..3037da8d2 100644 --- a/kitty/freetype_render_ui_text.c +++ b/kitty/freetype_render_ui_text.c @@ -8,43 +8,189 @@ #include "freetype_render_ui_text.h" #include #include +#include "charsets.h" typedef struct FamilyInformation { char *name; bool bold, italic; } FamilyInformation; -FT_Face main_face = NULL; +typedef struct Face { + FT_Face freetype; + hb_font_t *hb; + FT_UInt pixel_size; + struct Face *fallbacks; + size_t count, capacity; +} Face; + +Face main_face = {0}; FontConfigFace main_face_information = {0}; FamilyInformation main_face_family = {0}; +hb_buffer_t *hb_buffer = NULL; static inline FT_UInt -glyph_id_for_codepoint(FT_Face face, char_type cp) { - return FT_Get_Char_Index(face, cp); +glyph_id_for_codepoint(Face *face, char_type cp) { + return FT_Get_Char_Index(face->freetype, cp); +} + +static void +free_face(Face *face) { + if (face->freetype) FT_Done_Face(face->freetype); + if (face->hb) hb_font_destroy(face->hb); + for (size_t i = 0; i < face->count; i++) free_face(face->fallbacks + i); + memset(face, 0, sizeof(Face)); } static void cleanup(void) { - if (main_face) FT_Done_Face(main_face); - main_face = NULL; + free_face(&main_face); free(main_face_information.path); main_face_information.path = NULL; free(main_face_family.name); memset(&main_face_family, 0, sizeof(FamilyInformation)); + if (hb_buffer) hb_buffer_destroy(hb_buffer); + hb_buffer = NULL; } void set_main_face_family(const char *family, bool bold, bool italic) { + if (family == main_face_family.name || (main_face_family.name && strcmp(family, main_face_family.name) == 0)) return; cleanup(); main_face_family.name = strdup(family); main_face_family.bold = bold; main_face_family.italic = italic; } -static inline bool +static bool +load_font(FontConfigFace *info, Face *ans) { + ans->freetype = native_face_from_path(info->path, info->index); + if (!ans->freetype) return false; + ans->hb = hb_ft_font_create(ans->freetype, NULL); + if (!ans->hb) { PyErr_NoMemory(); return false; } + return true; +} + +static bool ensure_state(void) { - if (main_face) return false; + if (main_face.freetype && main_face.hb) return false; if (!information_for_font_family(main_face_family.name, main_face_family.bold, main_face_family.italic, &main_face_information)) return false; - main_face = native_face_from_path(main_face_information.path, main_face_information.index); - return !!main_face; + if (!load_font(&main_face_information, &main_face)) return false; + hb_buffer = hb_buffer_create(); + if (!hb_buffer) { PyErr_NoMemory(); return false; } + return true; +} + +static void +set_pixel_size(Face *face, FT_UInt sz) { + if (sz != face->pixel_size) { + FT_Set_Pixel_Sizes(face->freetype, sz, sz); // TODO: check for and handle failures + hb_ft_font_changed(face->hb); + face->pixel_size = sz; + } +} + +typedef struct RenderState { + uint32_t pending_in_buffer, fg, bg; + uint8_t *output; + bool alpha_first; + size_t output_width, output_height; + Face *current_face; +} RenderState; + + +bool +render_run(RenderState *rs) { + hb_buffer_guess_segment_properties(hb_buffer); + if (!HB_DIRECTION_IS_HORIZONTAL (hb_buffer_get_direction(hb_buffer))) { + PyErr_SetString(PyExc_ValueError, "Vertical text is not supported"); + return false; + } + FT_UInt pixel_size = 2 * rs->output_height / 3; + set_pixel_size(rs->current_face, pixel_size); + hb_shape(rs->current_face->hb, hb_buffer, NULL, 0); + unsigned int len = hb_buffer_get_length(hb_buffer); + hb_glyph_info_t *info = hb_buffer_get_glyph_infos(hb_buffer, NULL); + hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(hb_buffer, NULL); + + (void)len; (void)info; (void)pos; + + return true; +} + +static bool +current_font_has_codepoint(RenderState *rs, char_type codep) { + if (rs->current_face != &main_face && glyph_id_for_codepoint(&main_face, codep) > 0) { + rs->current_face = &main_face; + return true; + } + return glyph_id_for_codepoint(rs->current_face, codep); +} + +static bool +find_fallback_font_for(RenderState *rs, char_type codep) { + if (glyph_id_for_codepoint(&main_face, codep) > 0) { + rs->current_face = &main_face; + return true; + } + for (size_t i = 0; i < main_face.count; i++) { + if (glyph_id_for_codepoint(main_face.fallbacks + i, codep) > 0) { + rs->current_face = main_face.fallbacks + i; + return true; + } + } + FontConfigFace q; + if (!fallback_font(codep, main_face_family.name, main_face_family.bold, main_face_family.italic, &q)) return false; + ensure_space_for(&main_face, fallbacks, Face, main_face.count + 1, capacity, 8, true); + Face *ans = main_face.fallbacks + main_face.count; + if (!load_font(&q, ans)) return false; + main_face.count++; + rs->current_face = ans; + return true; +} + + +bool +render_single_line(const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, bool alpha_first) { + if (!ensure_state()) return false; + for (uint32_t *px = (uint32_t*)output_buf, *end = ((uint32_t*)output_buf) + width * height; px < end; px++) *px = bg; + if (!text || !text[0]) return true; + (void)fg; (void)alpha_first; + hb_buffer_clear_contents(hb_buffer); + if (!hb_buffer_pre_allocate(hb_buffer, 512)) { PyErr_NoMemory(); return false; } + RenderState rs = { + .current_face = &main_face, .fg = fg, .bg = bg, .output_width = width, .output_height = height, + .output = output_buf, .alpha_first = alpha_first + }; + + for (uint32_t i = 0, codep = 0, state = 0, prev = UTF8_ACCEPT; text[i] > 0; i++) { + switch(decode_utf8(&state, &codep, text[i])) { + case UTF8_ACCEPT: + if (current_font_has_codepoint(&rs, codep)) { + hb_buffer_add_utf32(hb_buffer, &codep, 1, 0, 1); + rs.pending_in_buffer += 1; + } else { + if (rs.pending_in_buffer) { + if (!render_run(&rs)) return false; + rs.pending_in_buffer = 0; + hb_buffer_clear_contents(hb_buffer); + } + if (!find_fallback_font_for(&rs, codep)) { + hb_buffer_add_utf32(hb_buffer, &codep, 1, 0, 1); + rs.pending_in_buffer += 1; + } + } + break; + case UTF8_REJECT: + state = UTF8_ACCEPT; + if (prev != UTF8_ACCEPT && i > 0) i--; + break; + } + prev = state; + } + if (rs.pending_in_buffer) { + if (!render_run(&rs)) return false; + rs.pending_in_buffer = 0; + hb_buffer_clear_contents(hb_buffer); + } + return true; } static PyObject* diff --git a/kitty/freetype_render_ui_text.h b/kitty/freetype_render_ui_text.h index 4c49eba45..2f432f789 100644 --- a/kitty/freetype_render_ui_text.h +++ b/kitty/freetype_render_ui_text.h @@ -9,6 +9,8 @@ #include "data-types.h" #include +bool render_single_line(const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, bool alpha_first); + typedef struct FontConfigFace { char *path; int index;