vtgl

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

font.c (13817B)


      1 /* See LICENSE for copyright details */
      2 /* NOTE: https://en.wikipedia.org/wiki/DEC_Special_Graphics */
      3 /*                                          | 0x60 - 0x7e
      4  *  "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", | ` - g
      5  *  "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", | h - o
      6  *  "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", | p - w
      7  *  "│", "≤", "≥", "π", "≠", "£", "·",      | x - ~
      8  */
      9 static u16 graphic_0[31] = {
     10         0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1,
     11         0x2424, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0x23BA,
     12         0x23BB, 0x2500, 0x23BC, 0x23BD, 0x251C, 0x2524, 0x2534, 0x252C,
     13         0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7,
     14 };
     15 
     16 #define STB_TRUETYPE_IMPLEMENTATION
     17 #define STB_STATIC
     18 #include "extern/stb_truetype.h"
     19 
     20 static b32
     21 init_font(Font *f, char *font_path, i32 font_size)
     22 {
     23 	b32 result = 0;
     24 	os_mapped_file map = os_map_file(font_path, OS_MAP_READ, OS_MAP_PRIVATE);
     25 
     26 	if (map.len) {
     27 		result = stbtt_InitFont(f->font_info, map.data, 0);
     28 		if (result) {
     29 			f->bufsize     = map.len;
     30 			f->buf         = map.data;
     31 			f->stbtt_scale = stbtt_ScaleForMappingEmToPixels(f->font_info, font_size);
     32 		} else {
     33 			/* TODO: leak */
     34 			os_write_err_msg(s8("init_font: failed on: "));
     35 			os_write_err_msg(c_str_to_s8(font_path));
     36 			os_write_err_msg(s8("\n"));
     37 		}
     38 	}
     39 
     40 	return result;
     41 }
     42 
     43 static u32
     44 compute_glyph_hash(GlyphCache *gc, u32 cp)
     45 {
     46 	/* TODO: better hash function! */
     47 	u32 result = (((u64)cp * 1111111111111111111) >> 32) & (gc->cache_len - 1);
     48 	return result;
     49 }
     50 
     51 static uv2
     52 unpack_gpu_tile_coord(u32 gpu_index)
     53 {
     54 	uv2 result;
     55 	result.x = gpu_index & 0xFFFF;
     56 	result.y = gpu_index >> 16;
     57 	return result;
     58 }
     59 
     60 static void
     61 cached_glyph_to_uv(FontAtlas *fa, CachedGlyph *cg, v2 *start, v2 *end, v2 scale)
     62 {
     63 	v2 cs = {.w = fa->info.w, .h = fa->info.h};
     64 	uv2 tile_coord = unpack_gpu_tile_coord(cg->gpu_tile_index);
     65 	f32 min_x = tile_coord.x * cs.w + cg->x0;
     66 	f32 max_x = min_x + cg->width;
     67 	f32 min_y = tile_coord.y * cs.h + fa->info.h + cg->y0 - fa->info.baseline;
     68 	f32 max_y = min_y + cg->height;
     69 
     70 	*start = (v2){.x = min_x * scale.x, .y = max_y * scale.y};
     71 	*end   = (v2){.x = max_x * scale.x, .y = min_y * scale.y};
     72 }
     73 
     74 
     75 static void
     76 recycle_cache(GlyphCache *gc)
     77 {
     78 	CachedGlyph *sentinel = gc->glyphs + 0;
     79 
     80 	ASSERT(sentinel->prev);
     81 
     82 	u32 last_index    = sentinel->prev;
     83 	CachedGlyph *cg   = gc->glyphs + last_index;
     84 	CachedGlyph *prev = gc->glyphs + cg->prev;
     85 	prev->next        = 0;
     86 	sentinel->prev    = cg->prev;
     87 
     88 	u32 hash_slot   = compute_glyph_hash(gc, cg->cp);
     89 	u32 *next_index = gc->hash_table + hash_slot;
     90 	while (*next_index != last_index) {
     91 		ASSERT(*next_index);
     92 		next_index = &gc->glyphs[*next_index].next_with_same_hash;
     93 	}
     94 	ASSERT(*next_index == last_index);
     95 	*next_index                   = cg->next_with_same_hash;
     96 	cg->next_with_same_hash       = sentinel->next_with_same_hash;
     97 	sentinel->next_with_same_hash = last_index;
     98 
     99 	gc->stats.recycle_count++;
    100 }
    101 
    102 static i32
    103 pop_free_glyph_entry(GlyphCache *gc)
    104 {
    105 	CachedGlyph *sentinel = gc->glyphs + 0;
    106 
    107 	if (!sentinel->next_with_same_hash)
    108 		recycle_cache(gc);
    109 
    110 	u32 result = sentinel->next_with_same_hash;
    111 	ASSERT(result);
    112 
    113 	CachedGlyph *cg = gc->glyphs + result;
    114 	sentinel->next_with_same_hash = cg->next_with_same_hash;
    115 	cg->next_with_same_hash       = 0;
    116 	cg->uploaded_to_gpu           = 0;
    117 
    118 	uv2 tile_coord      = unpack_gpu_tile_coord(cg->gpu_tile_index);
    119 	u32 base_tile_index = tile_coord.y * gc->tiles_in_x + tile_coord.x;
    120 	for (u32 i = 0; i < cg->tile_count; i++)
    121 		gc->occupied_tiles[base_tile_index + i] = 0;
    122 	cg->tile_count = 0;
    123 
    124 	return result;
    125 }
    126 
    127 static u32
    128 get_glyph_entry_index(GlyphCache *gc, u32 cp)
    129 {
    130 	u32 result    = 0;
    131 	u32 hash_slot = compute_glyph_hash(gc, cp);
    132 	u32 entry_idx = gc->hash_table[hash_slot];
    133 
    134 	CachedGlyph *cg;
    135 	while (entry_idx) {
    136 		cg = gc->glyphs + entry_idx;
    137 		if (cp == cg->cp) {
    138 			result = entry_idx;
    139 			break;
    140 		}
    141 		entry_idx = cg->next_with_same_hash;
    142 	}
    143 
    144 	if (result) {
    145 		CachedGlyph *prev = gc->glyphs + cg->prev;
    146 		CachedGlyph *next = gc->glyphs + cg->next;
    147 		prev->next = cg->next;
    148 		next->prev = cg->prev;
    149 		gc->stats.hit_count++;
    150 	} else {
    151 		result = pop_free_glyph_entry(gc);
    152 		cg     = gc->glyphs + result;
    153 		cg->cp = cp;
    154 		cg->next_with_same_hash   = gc->hash_table[hash_slot];
    155 		gc->hash_table[hash_slot] = result;
    156 		gc->stats.miss_count++;
    157 	}
    158 
    159 	ASSERT(result);
    160 
    161 	CachedGlyph *sentinel  = gc->glyphs + 0;
    162 	CachedGlyph *last_next = gc->glyphs + sentinel->next;
    163 	cg->next               = sentinel->next;
    164 	cg->prev               = 0;
    165 	sentinel->next         = result;
    166 	last_next->prev        = result;
    167 
    168 	return result;
    169 }
    170 
    171 static GlyphCacheStats
    172 get_and_clear_glyph_cache_stats(GlyphCache *gc)
    173 {
    174 	GlyphCacheStats	result = gc->stats;
    175 	gc->stats              = (GlyphCacheStats){0};
    176 	return result;
    177 }
    178 
    179 static u32 *
    180 render_glyph(Arena *a, FontAtlas *fa, u32 cp, u32 font_id, enum face_style style, CachedGlyph **out_glyph)
    181 {
    182 	BEGIN_TIMED_BLOCK();
    183 	GlyphCache *gc   = &fa->glyph_cache;
    184 	u32 *rgba_bitmap = NULL;
    185 
    186 	/* NOTE: first check if glyph is in the cache and valid */
    187 	/* NOTE: 8 MSB are not used for UTF8 so we can use that to store some extra info:
    188 	 * bits 25,26: the style of the glyph
    189 	 * bits 26-31: requested font_id (limited to first 32 fonts */
    190 	u32 packed_cp   = cp | ((u32)style << 24) | (font_id << 26);
    191 	u32 idx         = get_glyph_entry_index(&fa->glyph_cache, packed_cp);
    192 	CachedGlyph *cg = fa->glyph_cache.glyphs + idx;
    193 
    194 	*out_glyph = cg;
    195 	if (cg->uploaded_to_gpu)
    196 		goto end;
    197 
    198 	ASSERT(cg->tile_count == 0);
    199 
    200 	i32 glyph_idx = 0;
    201 	i32 font_idx  = 0;
    202 	/* NOTE: first try the requested font id */
    203 	if (font_id < fa->nfonts && fa->fonts[font_id][style].buf) {
    204 		glyph_idx = stbtt_FindGlyphIndex(fa->fonts[font_id][style].font_info, cp);
    205 		if (glyph_idx) font_idx = font_id;
    206 	}
    207 
    208 	for (u32 i = 0; !glyph_idx && i < fa->nfonts; i++) {
    209 		u32 test_style = style;
    210 		if (!fa->fonts[i][test_style].buf)
    211 			test_style = FS_NORMAL;
    212 		glyph_idx = stbtt_FindGlyphIndex(fa->fonts[i][test_style].font_info, cp);
    213 		if (!glyph_idx) {
    214 			test_style = FS_NORMAL;
    215 			glyph_idx  = stbtt_FindGlyphIndex(fa->fonts[i][test_style].font_info, cp);
    216 		}
    217 		if (glyph_idx) {
    218 			font_idx = i;
    219 			style    = test_style;
    220 			break;
    221 		}
    222 	}
    223 
    224 	Font *f   = &fa->fonts[font_idx][style];
    225 	f32 scale = f->stbtt_scale;
    226 	i32 x0, y0, x1, y1, advance, left_bearing;
    227 	stbtt_GetGlyphBitmapBoxSubpixel(f->font_info, glyph_idx, scale, scale, 0, 0,
    228 	                                &x0, &y0, &x1, &y1);
    229 
    230 	/* NOTE: this looks weird but some 'wide' glyphs are not actually wide but still
    231 	 * need to be displayed as such (eg. ト). x1 can be used to determine this. */
    232 	cg->tile_count = ((x1 - 1) / (i32)fa->info.w) + 1;
    233 	if (x0 < 0 && x1 + x0 <= fa->info.w)
    234 		cg->tile_count = 1;
    235 	CLAMP(cg->tile_count, 1, 2);
    236 
    237 	i32 height  = y1 - y0;
    238 	i32 width   = x1 - x0;
    239 
    240 	stbtt_GetGlyphHMetrics(f->font_info, glyph_idx, &advance, &left_bearing);
    241 	cg->advance = scale * advance;
    242 	cg->height  = MIN(height, fa->info.h);
    243 	cg->width   = MIN(width, fa->info.w * cg->tile_count);
    244 	cg->x0      = x0;
    245 	cg->y0      = y0;
    246 
    247 	ASSERT(cg->height <= fa->info.h);
    248 	ASSERT(cg->width  <= 2 * fa->info.w);
    249 
    250 	u8 *render_buf = alloc(a, u8, width * height);
    251 	stbtt_MakeGlyphBitmapSubpixel(*a, f->font_info, render_buf, width, height,
    252 	                              width, scale, scale, 0, 0, glyph_idx);
    253 
    254 	uv2 tile_coord = unpack_gpu_tile_coord(cg->gpu_tile_index);
    255 	u32 tile_index = tile_coord.y * gc->tiles_in_x + tile_coord.x;
    256 	if (tile_index != 0 && ((i32)tile_coord.x + cg->tile_count < gc->tiles_in_x)) {
    257 		/* NOTE: try to use the old tile directly */
    258 		if (gc->occupied_tiles[tile_index] ||
    259 		    (cg->tile_count == 2 && gc->occupied_tiles[tile_index + 1]))
    260 			tile_index = 0;
    261 	}
    262 
    263 	if (!tile_index) {
    264 		/* NOTE: there may be a fancier way to do this but we will see if this causes
    265 		 * any performance issue in practice */
    266 		i32 x = 1, y = 1;
    267 		u8 *occupied = gc->occupied_tiles;
    268 		for (y = 0; !tile_index && y < gc->tiles_in_y; y++) {
    269 			for (x = 0; !tile_index && x < gc->tiles_in_x; x++) {
    270 				if (!occupied[0]) {
    271 					tile_index = y * gc->tiles_in_x + x;
    272 					if ((cg->tile_count == 2) && occupied[1])
    273 						tile_index = 0;
    274 				}
    275 				occupied++;
    276 			}
    277 		}
    278 		tile_coord.x = x - 1;
    279 		tile_coord.y = y - 1;
    280 		cg->gpu_tile_index = (tile_coord.y << 16) | (tile_coord.x & 0xFFFF);
    281 	}
    282 
    283 	/* NOTE: tile index 0,0 is reserved */
    284 	ASSERT(cg->gpu_tile_index);
    285 	ASSERT(tile_index);
    286 
    287 	for (u32 i = 0; i < cg->tile_count; i++)
    288 		gc->occupied_tiles[tile_index + i] = 1;
    289 
    290 	i32 out_width = fa->info.w * cg->tile_count;
    291 	i32 out_size  = fa->info.h * out_width;
    292 	ASSERT(out_width >= cg->width);
    293 
    294 	i32 out_y = (fa->info.h + y0 - fa->info.baseline) * out_width;
    295 	out_y = MAX(out_y, 0);
    296 
    297 	height     = cg->height;
    298 	width      = cg->width;
    299 	i32 stride = x1 - x0;
    300 
    301 	i32 xskip = 0;
    302 	if (cg->x0 < 0) xskip = -cg->x0;
    303 
    304 	rgba_bitmap = alloc(a, u32, out_size);
    305 	for (i32 i = 0; i < height; i++) {
    306 		i32 out_x = MAX(cg->x0, 0);
    307 		for (i32 j = xskip; j < width; j++) {
    308 			/* TODO: handled coloured glyphs */
    309 			u32 pixel = 0;
    310 			if (0 /* COLOURED */) {
    311 			} else {
    312 				pixel = (u32)render_buf[i * stride + j] << 24u | 0x00FFFFFFu;
    313 			}
    314 			rgba_bitmap[out_y + out_x] = pixel;
    315 			out_x++;
    316 		}
    317 		out_y += out_width;
    318 	}
    319 
    320 end:
    321 	END_TIMED_BLOCK();
    322 
    323 	return rgba_bitmap;
    324 }
    325 
    326 static FontInfo
    327 compute_font_info(Font *font, u32 font_size)
    328 {
    329 	FontInfo result = {0};
    330 
    331 	static s8 ascii = s8(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"\
    332 	                     "`abcdefghijklmnopqrstuvwxyz{|}~");
    333 
    334 	f32 scale      = stbtt_ScaleForMappingEmToPixels(font->font_info, font_size);
    335 	i32 min_y      = 0;
    336 	i32 max_height = 0;
    337 	f32 width      = 0;
    338 	for (size i = 0; i < ascii.len; i++) {
    339 		u32 glyph_idx = stbtt_FindGlyphIndex(font->font_info, ascii.data[i]);
    340 		i32 x0, y0, x1, y1, advance, left_bearing;
    341 		stbtt_GetGlyphBitmapBoxSubpixel(font->font_info, glyph_idx, scale, scale, 0, 0,
    342 		                                &x0, &y0, &x1, &y1);
    343 		stbtt_GetGlyphHMetrics(font->font_info, glyph_idx, &advance, &left_bearing);
    344 
    345 		width      += scale * advance;
    346 		max_height  = MAX(max_height, y1 - y0);
    347 		min_y       = MIN(min_y, y0);
    348 	}
    349 
    350 	u32 graphic_0_count = 0;
    351 	for (size i = 0; i < ARRAY_COUNT(graphic_0); i++) {
    352 		if (graphic_0[i] == 0)
    353 			continue;
    354 		graphic_0_count++;
    355 
    356 		u32 glyph_idx = stbtt_FindGlyphIndex(font->font_info, graphic_0[i]);
    357 		i32 x0, y0, x1, y1, advance, left_bearing;
    358 		stbtt_GetGlyphBitmapBoxSubpixel(font->font_info, glyph_idx, scale, scale, 0, 0,
    359 		                                &x0, &y0, &x1, &y1);
    360 		stbtt_GetGlyphHMetrics(font->font_info, glyph_idx, &advance, &left_bearing);
    361 
    362 		width      += scale * advance;
    363 		max_height  = MAX(max_height, y1 - y0);
    364 		min_y       = MIN(min_y, y0);
    365 	}
    366 
    367 	result.h        = max_height;
    368 	result.w        = (i32)(width / (ascii.len + graphic_0_count) + 1);
    369 	result.baseline = (i32)(result.h + min_y);
    370 
    371 	return result;
    372 }
    373 
    374 static void
    375 font_atlas_update(FontAtlas *fa, iv2 glyph_bitmap_dim)
    376 {
    377 	GlyphCache *gc = &fa->glyph_cache;
    378 	mem_clear(gc->glyphs,         0, sizeof(*gc->glyphs) * gc->cache_len);
    379 	mem_clear(gc->hash_table,     0, sizeof(*gc->hash_table) * gc->cache_len);
    380 	mem_clear(gc->occupied_tiles, 0, sizeof(*gc->occupied_tiles) * gc->cache_len);
    381 	get_and_clear_glyph_cache_stats(gc);
    382 
    383 	/* NOTE: reserve tile 0,0 */
    384 	gc->occupied_tiles[0] = 1;
    385 
    386 	Font *font = &fa->fonts[0][FS_NORMAL];
    387 	fa->info   = compute_font_info(font, g_font_size);
    388 
    389 	gc->tiles_in_x = (glyph_bitmap_dim.x - 1) / fa->info.w - 1;
    390 	gc->tiles_in_y = (glyph_bitmap_dim.y - 1) / fa->info.h - 1;
    391 
    392 	ASSERT(gc->tiles_in_x && gc->tiles_in_y);
    393 	ASSERT(gc->tiles_in_x * fa->info.w <= glyph_bitmap_dim.x);
    394 
    395 	for (u32 i = 0; i < gc->cache_len - 1; i++)
    396 		gc->glyphs[i].next_with_same_hash = i + 1;
    397 }
    398 
    399 static void
    400 shift_font_sizes(FontAtlas *fa, i32 size_delta)
    401 {
    402 	g_font_size += size_delta;
    403 	if (g_font_size < MIN_FONT_SIZE)
    404 		g_font_size = MIN_FONT_SIZE;
    405 	for (u32 i = 0; i < fa->nfonts; i++) {
    406 		/* TODO: better multi font size support */
    407 		i32 font_size;
    408 		if (i == g_ui_debug_font_id) font_size = g_ui_font_size;
    409 		else                         font_size = g_font_size;
    410 		font_size = MIN(font_size, g_font_size);
    411 		for (u32 j = 0; j < FS_COUNT; j++) {
    412 			Font *f = &fa->fonts[i][j];
    413 			if (!f->buf) continue;
    414 			f->stbtt_scale = stbtt_ScaleForMappingEmToPixels(f->font_info, font_size);
    415 		}
    416 	}
    417 }
    418 
    419 static void
    420 init_fonts(FontAtlas *fa, Arena *a, iv2 glyph_bitmap_dim)
    421 {
    422 	fa->nfonts = 0;
    423 	fa->fonts  = alloc(a, typeof(*fa->fonts), ARRAY_COUNT(g_fonts));
    424 	for (u32 j = 0; j < ARRAY_COUNT(g_fonts); j++) {
    425 		i32 font_size;
    426 		if (j == g_ui_debug_font_id) font_size = g_ui_font_size;
    427 		else                         font_size = g_font_size;
    428 		font_size = MIN(font_size, g_font_size);
    429 		b32 result = 0;
    430 		for (u32 i = 0; i < FS_COUNT; i++) {
    431 			if (!g_fonts[j][i])
    432 				continue;
    433 			Font *f = fa->fonts[fa->nfonts] + i;
    434 			f->font_info = alloc(a, typeof(*f->font_info), 1);
    435 			if (init_font(f, g_fonts[j][i], font_size)) {
    436 				result = 1;
    437 			} else {
    438 				os_write_err_msg(s8("failed to load font: "));
    439 				os_write_err_msg(c_str_to_s8(g_fonts[j][i]));
    440 				os_write_err_msg(s8("\n"));
    441 			}
    442 		}
    443 		fa->nfonts += result;
    444 	}
    445 
    446 	if (!fa->nfonts)
    447 		os_fatal(s8("failed to load any fonts!\n"));
    448 
    449 	Font *f = &fa->fonts[0][FS_NORMAL];
    450 	FontInfo info   = compute_font_info(f, MIN_FONT_SIZE);
    451 	i32 max_tiles_x = glyph_bitmap_dim.x / info.w;
    452 	i32 max_tiles_y = glyph_bitmap_dim.y / info.h;
    453 
    454 	GlyphCache *gc     = &fa->glyph_cache;
    455 	gc->cache_len      = round_down_power_of_2(max_tiles_x * max_tiles_y);
    456 	gc->occupied_tiles = alloc(a, typeof(*gc->occupied_tiles), gc->cache_len);
    457 	gc->glyphs         = alloc(a, typeof(*gc->glyphs),         gc->cache_len);
    458 	gc->hash_table     = alloc(a, typeof(*gc->hash_table),     gc->cache_len);
    459 
    460 	font_atlas_update(fa, glyph_bitmap_dim);
    461 }