vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: c51eafbc9d8d8e3269704ae15d163d96f63d2055
Parent: 40b0580e1fced6c85c299cea320cac4d59948e15
Author: Randy Palamar
Date:   Wed, 20 Nov 2024 23:01:56 -0700

greatly improve font metric/cell size calculations

There is still some jank to work out but this is much much nicer
than what was here. Part of the jank will be fixed by adding our
own wide character table.

Diffstat:
Mconfig.def.h | 19+++++++++++--------
Mfont.c | 164++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mterminal.c | 22----------------------
Mtests/test.c | 1+
Mutil.h | 6------
Mvtgl.c | 2++
6 files changed, 130 insertions(+), 84 deletions(-)

diff --git a/config.def.h b/config.def.h @@ -1,29 +1,32 @@ /* See LICENSE for copyright details */ static FontDesc g_fonts[][FS_COUNT] = { { - [FS_NORMAL] = {.path = "/usr/share/fonts/gofont/Go-Mono.ttf", .height = 24}, - [FS_BOLD] = {.path = "/usr/share/fonts/gofont/Go-Mono-Bold.ttf", .height = 24}, - [FS_ITALIC] = {.path = "/usr/share/fonts/gofont/Go-Mono-Italic.ttf", .height = 24}, - [FS_BOLD_ITALIC] = {.path = "/usr/share/fonts/gofont/Go-Mono-Bold-Italic.ttf", .height = 24}, + [FS_NORMAL] = "/usr/share/fonts/gofont/Go-Mono.ttf", + [FS_BOLD] = "/usr/share/fonts/gofont/Go-Mono-Bold.ttf", + [FS_ITALIC] = "/usr/share/fonts/gofont/Go-Mono-Italic.ttf", + [FS_BOLD_ITALIC] = "/usr/share/fonts/gofont/Go-Mono-Bold-Italic.ttf", }, { - [FS_NORMAL] = {.path = "/usr/share/fonts/dejavu/DejaVuSans.ttf", .height = 28}, + [FS_NORMAL] = "/usr/share/fonts/dejavu/DejaVuSans.ttf", }, { - [FS_NORMAL] = {.path = "/usr/share/fonts/dejavu/DejaVuSans.ttf", .height = 22}, + [FS_NORMAL] = "/usr/share/fonts/dejavu/DejaVuSans.ttf", }, { - [FS_NORMAL] = {.path = "/usr/share/fonts/liberation-fonts/LiberationMono-Regular.ttf", .height = 16}, + [FS_NORMAL] = "/usr/share/fonts/liberation-fonts/LiberationMono-Regular.ttf", }, }; +static i32 g_font_size = 28; +static i32 g_ui_font_size = 20; + /* NOTE: indices into the array above */ static u32 g_ui_small_font_id = 2; static u32 g_ui_large_font_id = 3; static u32 g_ui_debug_font_id = 4; /* NOTE: terminal margin in pixels */ -static v2 g_term_margin = {.w = 8, .h = 8}; +static iv2 g_term_margin = {.w = 8, .h = 8}; static u8 g_tabstop = 8; diff --git a/font.c b/font.c @@ -1,24 +1,42 @@ /* See LICENSE for copyright details */ -#define FONT_H_PADDING 1 -#define FONT_BASELINE_EXTRA 1 +/* NOTE: graphic character rxvt extension */ +/* | 0x41 - 0x7e + * "↑", "↓", "→", "←", "█", "▚", "☃", | A - G + * 0, 0, 0, 0, 0, 0, 0, 0, | H - O + * 0, 0, 0, 0, 0, 0, 0, 0, | P - W + * 0, 0, 0, 0, 0, 0, 0, " ", | X - _ + * "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", | ` - g + * "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", | h - o + * "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", | p - w + * "│", "≤", "≥", "π", "≠", "£", "·", | x - ~ + */ +static u16 graphic_0[62] = { + 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259A, 0x2603, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x25C6, + 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1, 0x2424, + 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0x23BA, 0x23BB, + 0x2500, 0x23BC, 0x23BD, 0x251C, 0x2524, 0x2534, 0x252C, 0x2502, + 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, +}; static b32 -init_font(Font *f, FontDesc *fdesc) +init_font(Font *f, char *font_path, i32 font_size) { b32 result = 0; - os_mapped_file map = os_map_file(fdesc->path, OS_MAP_READ, OS_MAP_PRIVATE); + os_mapped_file map = os_map_file(font_path, OS_MAP_READ, OS_MAP_PRIVATE); if (map.len) { result = stbtt_InitFont(&f->font_info, map.data, 0); if (result) { f->bufsize = map.len; f->buf = map.data; - f->fontsize = fdesc->height; - f->stbtt_scale = stbtt_ScaleForPixelHeight(&f->font_info, f->fontsize); + f->stbtt_scale = stbtt_ScaleForMappingEmToPixels(&f->font_info, font_size); } else { /* TODO: leak */ - os_write_err_msg(s8("stbtt_InitFont: failed on ")); - os_write_err_msg(c_str_to_s8(fdesc->path)); + os_write_err_msg(s8("init_font: failed on: ")); + os_write_err_msg(c_str_to_s8(font_path)); os_write_err_msg(s8("\n")); } } @@ -199,8 +217,10 @@ render_glyph(Arena *a, FontAtlas *fa, u32 cp, u32 font_id, enum face_style style if (!fa->fonts[i][test_style].buf) test_style = FS_NORMAL; glyph_idx = stbtt_FindGlyphIndex(&fa->fonts[i][test_style].font_info, cp); - if (!glyph_idx) - glyph_idx = stbtt_FindGlyphIndex(&fa->fonts[i][FS_NORMAL].font_info, cp); + if (!glyph_idx) { + test_style = FS_NORMAL; + glyph_idx = stbtt_FindGlyphIndex(&fa->fonts[i][test_style].font_info, cp); + } if (glyph_idx) { font_idx = i; style = test_style; @@ -215,31 +235,30 @@ render_glyph(Arena *a, FontAtlas *fa, u32 cp, u32 font_id, enum face_style style stbtt_GetGlyphBitmapBoxSubpixel(&f->font_info, glyph_idx, scale, scale, 0, 0, &x0, &y0, &x1, &y1); + /* NOTE: this looks weird but some 'wide' glyphs are not actually wide but still + * need to be displayed as such (eg. ト). x1 can be used to determine this. */ + cg->tile_count = ((x1 - 1) / (i32)fa->info.w) + 1; + if (x0 < 0 && x1 + x0 <= fa->info.w) + cg->tile_count = 1; + i32 height = y1 - y0; i32 width = x1 - x0; stbtt_GetGlyphHMetrics(&f->font_info, glyph_idx, &advance, &left_bearing); cg->advance = scale * advance; - cg->height = height; - cg->width = width; - cg->x0 = x0 + FONT_H_PADDING; + cg->height = MIN(height, fa->info.h); + cg->width = MIN(width, fa->info.w * cg->tile_count); + cg->x0 = x0; cg->y0 = y0; + ASSERT(cg->height <= fa->info.h); + ASSERT(cg->width <= 2 * fa->info.w); + u8 *render_buf = alloc(a, u8, width * height); stbtt_MakeGlyphBitmapSubpixel(*a, &f->font_info, render_buf, width, height, width, scale, scale, 0, 0, glyph_idx); - /* NOTE: this looks weird but some 'wide' glyphs are not actually wide but still - * need to be displayed as such (eg. ト). x1 can be used to determine this. */ - cg->tile_count = (((i32)x1 - 1) / (i32)fa->info.w) + 1; - - ASSERT(cg->tile_count * fa->info.w >= x1); ASSERT(cg->tile_count >= 1 && cg->tile_count < gc->tiles_in_x); - - i32 out_width = fa->info.w * cg->tile_count; - i32 out_size = fa->info.h * out_width; - ASSERT(out_width >= width); - uv2 tile_coord = unpack_gpu_tile_coord(cg->gpu_tile_index); u32 tile_index = tile_coord.y * gc->tiles_in_x + tile_coord.x; if (tile_index != 0 && (tile_coord.x + cg->tile_count < gc->tiles_in_x)) { @@ -277,19 +296,29 @@ render_glyph(Arena *a, FontAtlas *fa, u32 cp, u32 font_id, enum face_style style for (u32 i = 0; i < cg->tile_count; i++) gc->occupied_tiles[tile_index + i] = 1; + i32 out_width = fa->info.w * cg->tile_count; + i32 out_size = fa->info.h * out_width; + ASSERT(out_width >= cg->width); + i32 out_y = (fa->info.h + y0 - fa->info.baseline) * out_width; - ASSERT(out_y >= 0); - ASSERT(x0 + FONT_H_PADDING >= 0); + out_y = MAX(out_y, 0); + + height = cg->height; + width = cg->width; + i32 stride = x1 - x0; + + i32 xskip = 0; + if (cg->x0 < 0) xskip = -cg->x0; rgba_bitmap = alloc(a, u32, out_size); for (i32 i = 0; i < height; i++) { - i32 out_x = x0 + FONT_H_PADDING; - for (i32 j = 0; j < width; j++) { + i32 out_x = MAX(cg->x0, 0); + for (i32 j = xskip; j < width; j++) { /* TODO: handled coloured glyphs */ u32 pixel = 0; if (0 /* COLOURED */) { } else { - pixel = (u32)render_buf[i * width + j] << 24u | 0x00FFFFFFu; + pixel = (u32)render_buf[i * stride + j] << 24u | 0x00FFFFFFu; } rgba_bitmap[out_y + out_x] = pixel; out_x++; @@ -304,16 +333,49 @@ end: } static FontInfo -compute_font_info(Font *font, f32 scale) +compute_font_info(Font *font, u32 font_size) { - FontInfo result; + FontInfo result = {0}; + + static s8 ascii = s8(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"\ + "`abcdefghijklmnopqrstuvwxyz{|}~"); + + f32 scale = stbtt_ScaleForMappingEmToPixels(&font->font_info, font_size); + i32 min_y = 0; + i32 max_height = 0; + f32 width = 0; + for (size i = 0; i < ascii.len; i++) { + u32 glyph_idx = stbtt_FindGlyphIndex(&font->font_info, ascii.data[i]); + i32 x0, y0, x1, y1, advance, left_bearing; + stbtt_GetGlyphBitmapBoxSubpixel(&font->font_info, glyph_idx, scale, scale, 0, 0, + &x0, &y0, &x1, &y1); + stbtt_GetGlyphHMetrics(&font->font_info, glyph_idx, &advance, &left_bearing); + + width += scale * advance; + max_height = MAX(max_height, y1 - y0); + min_y = MIN(min_y, y0); + } - i32 x0, x1, y0, y1; - stbtt_GetFontBoundingBox(&font->font_info, &x0, &y0, &x1, &y1); + u32 graphic_0_count = 0; + for (size i = 0; i < ARRAY_COUNT(graphic_0); i++) { + if (graphic_0[i] == 0) + continue; + graphic_0_count++; + + u32 glyph_idx = stbtt_FindGlyphIndex(&font->font_info, graphic_0[i]); + i32 x0, y0, x1, y1, advance, left_bearing; + stbtt_GetGlyphBitmapBoxSubpixel(&font->font_info, glyph_idx, scale, scale, 0, 0, + &x0, &y0, &x1, &y1); + stbtt_GetGlyphHMetrics(&font->font_info, glyph_idx, &advance, &left_bearing); + + width += scale * advance; + max_height = MAX(max_height, y1 - y0); + min_y = MIN(min_y, y0); + } - result.h = (u32)(scale * (y1 - y0)) + 1; - result.w = (u32)(scale * (x1 - x0)) + 1 + FONT_H_PADDING; - result.baseline = (u32)(-scale * y0) + 1 + FONT_BASELINE_EXTRA; + result.h = max_height; + result.w = (i32)(width / (ascii.len + graphic_0_count) + 1); + result.baseline = (i32)(result.h + min_y); return result; } @@ -330,9 +392,8 @@ font_atlas_update(FontAtlas *fa, iv2 glyph_bitmap_dim) /* NOTE: reserve tile 0,0 */ gc->occupied_tiles[0] = 1; - Font *font = &fa->fonts[0][FS_BOLD]; - if (!font) font = &fa->fonts[0][FS_NORMAL]; - fa->info = compute_font_info(font, font->stbtt_scale); + Font *font = &fa->fonts[0][FS_NORMAL]; + fa->info = compute_font_info(font, g_font_size); gc->tiles_in_x = (glyph_bitmap_dim.x - 1) / fa->info.w - 1; gc->tiles_in_y = (glyph_bitmap_dim.y - 1) / fa->info.h - 1; @@ -347,15 +408,19 @@ font_atlas_update(FontAtlas *fa, iv2 glyph_bitmap_dim) static void shift_font_sizes(FontAtlas *fa, i32 size_delta) { + g_font_size += size_delta; + if (g_font_size < MIN_FONT_SIZE) + g_font_size = MIN_FONT_SIZE; for (u32 i = 0; i < fa->nfonts; i++) { + /* TODO: better multi font size support */ + i32 font_size; + if (i == g_ui_debug_font_id) font_size = g_ui_font_size; + else font_size = g_font_size; + font_size = MIN(font_size, g_font_size); for (u32 j = 0; j < FS_COUNT; j++) { Font *f = &fa->fonts[i][j]; if (!f->buf) continue; - - i32 newsize = f->fontsize + size_delta; - CLAMP(newsize, MIN_FONT_SIZE, MAX_FONT_SIZE); - f->fontsize = newsize; - f->stbtt_scale = stbtt_ScaleForPixelHeight(&f->font_info, f->fontsize); + f->stbtt_scale = stbtt_ScaleForMappingEmToPixels(&f->font_info, font_size); } } } @@ -366,15 +431,19 @@ init_fonts(FontAtlas *fa, Arena *a, iv2 glyph_bitmap_dim) fa->nfonts = 0; fa->fonts = alloc(a, typeof(*fa->fonts), ARRAY_COUNT(g_fonts)); for (u32 j = 0; j < ARRAY_COUNT(g_fonts); j++) { + i32 font_size; + if (j == g_ui_debug_font_id) font_size = g_ui_font_size; + else font_size = g_font_size; + font_size = MIN(font_size, g_font_size); b32 result = 0; for (u32 i = 0; i < FS_COUNT; i++) { - if (!g_fonts[j][i].path) + if (!g_fonts[j][i]) continue; - if (init_font(&fa->fonts[fa->nfonts][i], &g_fonts[j][i])) { + if (init_font(&fa->fonts[fa->nfonts][i], g_fonts[j][i], font_size)) { result = 1; } else { os_write_err_msg(s8("failed to load font: ")); - os_write_err_msg(c_str_to_s8(g_fonts[j][i].path)); + os_write_err_msg(c_str_to_s8(g_fonts[j][i])); os_write_err_msg(s8("\n")); } } @@ -385,8 +454,7 @@ init_fonts(FontAtlas *fa, Arena *a, iv2 glyph_bitmap_dim) os_fatal(s8("failed to load any fonts!\n")); Font *f = &fa->fonts[0][FS_NORMAL]; - FontInfo info = compute_font_info(f, stbtt_ScaleForPixelHeight(&f->font_info, - MIN_FONT_SIZE)); + FontInfo info = compute_font_info(f, MIN_FONT_SIZE); i32 max_tiles_x = glyph_bitmap_dim.x / info.w; i32 max_tiles_y = glyph_bitmap_dim.y / info.h; diff --git a/terminal.c b/terminal.c @@ -1218,28 +1218,6 @@ push_normal_cp(Term *t, TermView *tv, u32 cp) width = cg->tile_count; } - /* NOTE: graphic character rxvt extension */ - /* | 0x41 - 0x7e - * "↑", "↓", "→", "←", "█", "▚", "☃", | A - G - * 0, 0, 0, 0, 0, 0, 0, 0, | H - O - * 0, 0, 0, 0, 0, 0, 0, 0, | P - W - * 0, 0, 0, 0, 0, 0, 0, " ", | X - _ - * "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", | ` - g - * "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", | h - o - * "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", | p - w - * "│", "≤", "≥", "π", "≠", "£", "·", | x - ~ - */ - static u16 graphic_0[62] = { - 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259A, 0x2603, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, - 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x25C6, - 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1, 0x2424, - 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0x23BA, 0x23BB, - 0x2500, 0x23BC, 0x23BD, 0x251C, 0x2524, 0x2534, 0x252C, 0x2502, - 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, - }; - if (t->cursor.charsets[t->cursor.charset_index] == CS_GRAPHIC0 && BETWEEN(cp, 0x41, 0x7e) && graphic_0[cp - 0x41]) cp = graphic_0[cp - 0x41]; diff --git a/tests/test.c b/tests/test.c @@ -10,6 +10,7 @@ KEYBIND_FN(paste) { return 0; } KEYBIND_FN(scroll) { return 0; } KEYBIND_FN(zoom) { return 0; } +#include "font.c" #include "terminal.c" static void diff --git a/util.h b/util.h @@ -413,11 +413,6 @@ enum face_style { }; typedef struct { - char *path; - i32 height; -} FontDesc; - -typedef struct { i32 h, w; i32 baseline; } FontInfo; @@ -426,7 +421,6 @@ typedef struct { stbtt_fontinfo font_info; u8 *buf; i32 bufsize; - i32 fontsize; f32 stbtt_scale; } Font; diff --git a/vtgl.c b/vtgl.c @@ -709,6 +709,8 @@ KEYBIND_FN(zoom) shift_font_sizes(&t->fa, a.i); font_atlas_update(&t->fa, t->gl.glyph_bitmap_dim); t->gl.flags |= NEEDS_RESIZE; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, t->gl.glyph_bitmap_dim.w, t->gl.glyph_bitmap_dim.h, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); return 1; }