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 }