From eb1c75720eaa8686c160e3ffa2559a03b5187e5a Mon Sep 17 00:00:00 2001 From: pommicket Date: Tue, 18 Jul 2023 17:20:11 -0400 Subject: font fallbacks --- buffer.c | 10 ++++- session.c | 8 +++- ted.c | 12 ++++-- text.c | 123 ++++++++++++++++++++++++++++++++++++++------------------------ text.h | 14 ++++++- 5 files changed, 113 insertions(+), 54 deletions(-) diff --git a/buffer.c b/buffer.c index 3ca57f9..5cf1820 100644 --- a/buffer.c +++ b/buffer.c @@ -956,8 +956,14 @@ bool buffer_pixels_to_pos(TextBuffer *buffer, vec2 pixel_coords, BufferPos *pos) x -= buffer->x1; y -= buffer->y1; - x = clampd(x, 0, buffer->x2 - buffer->x1); - y = clampd(y, 0, buffer->y2 - buffer->y1); + double buffer_width = buffer->x2 - buffer->x1; + double buffer_height = buffer->y2 - buffer->y1; + + if (x < 0 || y < 0 || x >= buffer_width || y >= buffer_height) + ret = false; + + x = clampd(x, 0, buffer_width); + y = clampd(y, 0, buffer_height); double xoff = x + buffer->scroll_x * text_font_char_width(font, ' '); diff --git a/session.c b/session.c index 8860cd1..af55eba 100644 --- a/session.c +++ b/session.c @@ -116,7 +116,7 @@ static char read_char(FILE *fp) { } static bool read_bool(FILE *fp) { - return (bool)getc(fp); + return getc(fp) != 0; } static void write_cstr(FILE *fp, const char *cstr) { @@ -244,6 +244,12 @@ static void session_read_buffer(Ted *ted, FILE *fp) { buffer->selection = read_bool(fp); if (buffer->selection) buffer->selection_pos = buffer_pos_read(buffer, fp); + buffer_pos_validate(buffer, &buffer->cursor_pos); + buffer_pos_validate(buffer, &buffer->selection_pos); + if (buffer->selection && buffer_pos_eq(buffer->cursor_pos, buffer->selection_pos)) { + // this could happen if the file was changed on disk + buffer->selection = false; + } } } diff --git a/ted.c b/ted.c index 19d360c..1eec19c 100644 --- a/ted.c +++ b/ted.c @@ -313,7 +313,8 @@ static Font *ted_load_single_font(Ted *ted, const char *filename) { static Font *ted_load_multifont(Ted *ted, const char *filenames) { char filename[TED_PATH_MAX]; - Font *font = NULL; + Font *first_font = NULL; + Font *curr_font = NULL; while (*filenames) { while (*filenames == ',') ++filenames; @@ -321,12 +322,17 @@ static Font *ted_load_multifont(Ted *ted, const char *filenames) { strn_cpy(filename, sizeof filename, filenames, len); str_trim(filename); if (*filename) { - font = ted_load_single_font(ted, filename); + Font *font = ted_load_single_font(ted, filename); + if (!first_font) + first_font = font; + if (curr_font) + text_font_set_fallback(curr_font, font); + curr_font = font; } filenames += len; } - return font; + return first_font; } void ted_load_fonts(Ted *ted) { diff --git a/text.c b/text.c index 9d161c4..29d1831 100644 --- a/text.c +++ b/text.c @@ -26,9 +26,9 @@ typedef struct { typedef struct { char32_t c; + bool defined; u32 texture; stbtt_packedchar data; - bool defined; // whether this character is actually defined in the font file } CharInfo; // characters are split into this many "buckets" according to @@ -56,6 +56,7 @@ struct Font { CharInfo *char_info[CHAR_BUCKET_COUNT]; // each entry is a dynamic array of char info // TTF data (i.e. the contents of the TTF file) u8 *ttf_data; + Font *fallback; }; const TextRenderState text_render_state_default = { @@ -90,6 +91,10 @@ static void text_set_err(const char *fmt, ...) { } } +void text_font_set_fallback(Font *font, Font *fallback) { + font->fallback = fallback; +} + static GLuint text_program; static GLuint text_vbo, text_vao; static GLuint text_v_pos, text_v_color, text_v_tex_coord; @@ -137,7 +142,6 @@ static u32 char_bucket_index(char32_t c) { static FontTexture *font_new_texture(Font *font) { - debug_println("Create new texture for font %p", (void *)font); PROFILE_TIME(start); unsigned char *pixels = calloc(FONT_TEXTURE_WIDTH, FONT_TEXTURE_HEIGHT); if (!pixels) { @@ -183,7 +187,9 @@ static void font_texture_free(FontTexture *texture) { memset(texture, 0, sizeof *texture); } -// on success, *info is filled out +// on success, *info is filled out. +// success includes cases where c is not defined by the font so a substitute character is used. +// failure only indicates something very bad. static Status text_load_char(Font *font, char32_t c, CharInfo *info) { u32 bucket = char_bucket_index(c); arr_foreach_ptr(font->char_info[bucket], CharInfo, i) { @@ -195,51 +201,57 @@ static Status text_load_char(Font *font, char32_t c, CharInfo *info) { } glGetError(); // clear error + memset(info, 0, sizeof *info); if (c != UNICODE_BOX_CHARACTER && stbtt_FindGlyphIndex(&font->stb_info, (int)c) == 0) { - // this code point is not defined by the font — use the box character - printf("Using box character for U+%04X\n",c); + // this code point is not defined by the font + + // use the box character if (!text_load_char(font, UNICODE_BOX_CHARACTER, info)) return false; info->c = c; info->defined = false; - } else { - info->defined = true; - if (!font->textures) { - if (!font_new_texture(font)) - return false; - } - - int success = 0; - FontTexture *texture = arr_lastp(font->textures); - for (int i = 0; i < 2; i++) { - info->c = c; - info->texture = arr_len(font->textures) - 1; - success = stbtt_PackFontRange(&texture->pack_context, font->ttf_data, 0, font->char_height, - (int)c, 1, &info->data); - if (success) break; - // texture is full; create a new one - stbtt_PackEnd(&texture->pack_context); - font_texture_update_if_needed(texture); - free(texture->pixels); - texture->pixels = NULL; - texture = font_new_texture(font); - if (!texture) - return false; - } - - if (!success) { - // a brand new texture couldn't fit the character. - // something has gone horribly wrong. - font_texture_free(texture); - arr_remove_last(font->textures); - text_set_err("Error rasterizing character %lc", (wchar_t)c); + arr_add(font->char_info[bucket], *info); + return true; + } + + info->defined = true; + + if (!font->textures) { + if (!font_new_texture(font)) return false; - } - - texture->needs_update = true; } + int success = 0; + FontTexture *texture = arr_lastp(font->textures); + for (int i = 0; i < 2; i++) { + info->c = c; + info->texture = arr_len(font->textures) - 1; + success = stbtt_PackFontRange(&texture->pack_context, font->ttf_data, 0, font->char_height, + (int)c, 1, &info->data); + if (success) break; + // texture is full; create a new one + stbtt_PackEnd(&texture->pack_context); + font_texture_update_if_needed(texture); + free(texture->pixels); + texture->pixels = NULL; + debug_println("Create new texture for font %p (triggered by U+%04X)", (void *)font, c); + texture = font_new_texture(font); + if (!texture) + return false; + } + + if (!success) { + // a brand new texture couldn't fit the character. + // something has gone horribly wrong. + font_texture_free(texture); + arr_remove_last(font->textures); + text_set_err("Error rasterizing character %lc", (wchar_t)c); + return false; + } + + texture->needs_update = true; + arr_add(font->char_info[bucket], *info); return true; } @@ -306,10 +318,13 @@ float text_font_char_height(Font *font) { float text_font_char_width(Font *font, char32_t c) { CharInfo info = {0}; - if (text_load_char(font, c, &info)) + if (text_load_char(font, c, &info)) { + if (!info.defined && font->fallback) + return text_font_char_width(font->fallback, c); return info.data.xadvance; - else + } else { return 0; + } } void text_render(Font *font) { @@ -336,6 +351,10 @@ void text_render(Font *font) { glDrawArrays(GL_TRIANGLES, 0, (GLsizei)(3 * ntriangles)); arr_clear(texture->triangles); } + + if (font->fallback) { + text_render(font->fallback); + } } void text_char_with_state(Font *font, TextRenderState *state, char32_t c) { @@ -352,6 +371,13 @@ top: if (!text_load_char(font, c, &info)) return; + + if (!info.defined && font->fallback) { + text_char_with_state(font->fallback, state, c); + return; + } + + const float char_height = font->char_height; stbtt_aligned_quad q = {0}; @@ -513,12 +539,6 @@ void text_get_size32(Font *font, const char32_t *text, u64 len, float *width, fl if (height) *height = (float)render_state.y + font->char_height * (2/3.0f); } -static void font_free_textures(Font *font) { - arr_foreach_ptr(font->textures, FontTexture, texture) { - font_texture_free(texture); - } - arr_clear(font->textures); -} static void font_free_char_info(Font *font) { for (u32 i = 0; i < CHAR_BUCKET_COUNT; i++) { @@ -526,10 +546,19 @@ static void font_free_char_info(Font *font) { } } +static void font_free_textures(Font *font) { + arr_foreach_ptr(font->textures, FontTexture, texture) { + font_texture_free(texture); + } + arr_clear(font->textures); +} + void text_font_change_size(Font *font, float new_size) { font_free_textures(font); font_free_char_info(font); font->char_height = new_size; + if (font->fallback) + text_font_change_size(font->fallback, new_size); } void text_font_free(Font *font) { diff --git a/text.h b/text.h index 79f99ad..8d317e8 100644 --- a/text.h +++ b/text.h @@ -68,7 +68,15 @@ const char *text_get_err(void); void text_clear_err(void); /// Load a TTF font found in ttf_filename with the given font size (character pixel height) Font *text_font_load(const char *ttf_filename, float font_size); -/// Change size of font. Avoid calling this function too often, since all font textures are trashed. +/// Set a fallback font to use if a character is not defined by `font`. +/// +/// You can pass `NULL` to clear any previous fallback. +/// Do not create a loop of fallback fonts. +void text_font_set_fallback(Font *font, Font *fallback); +/// Change size of font. +/// +/// Avoid calling this function too often, since all font textures are trashed. +/// Also changes size of fallback fonts. void text_font_change_size(Font *font, float new_size); /// Height of a character of this font in pixels. float text_font_char_height(Font *font); @@ -91,8 +99,12 @@ void text_char_with_state(Font *font, TextRenderState *state, char32_t c); /// Draw some UTF-8 text with a \ref TextRenderState. void text_utf8_with_state(Font *font, TextRenderState *state, const char *str); /// Free memory used by font. +/// +/// Does NOT free the font's fallback. void text_font_free(Font *font); /// Render all text drawn with \ref text_utf8, etc. +/// +/// This will render the fallback font and its fallback, and so on. void text_render(Font *font); /// The "default" text rendering state - everything you need to just render text normally. /// This lets you do stuff like: -- cgit v1.2.3