vtgl

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

font.c (14240B)


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