font.c (13961B)
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 global 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 function 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 function 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 function 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 function 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 function void 75 recycle_cache(GlyphCache *gc) 76 { 77 CachedGlyph *sentinel = gc->glyphs + 0; 78 79 ASSERT(sentinel->prev); 80 81 u32 last_index = sentinel->prev; 82 CachedGlyph *cg = gc->glyphs + last_index; 83 CachedGlyph *prev = gc->glyphs + cg->prev; 84 prev->next = 0; 85 sentinel->prev = cg->prev; 86 87 u32 hash_slot = compute_glyph_hash(gc, cg->cp); 88 u32 *next_index = gc->hash_table + hash_slot; 89 while (*next_index != last_index) { 90 ASSERT(*next_index); 91 next_index = &gc->glyphs[*next_index].next_with_same_hash; 92 } 93 ASSERT(*next_index == last_index); 94 *next_index = cg->next_with_same_hash; 95 cg->next_with_same_hash = sentinel->next_with_same_hash; 96 sentinel->next_with_same_hash = last_index; 97 98 gc->stats.recycle_count++; 99 } 100 101 function i32 102 pop_free_glyph_entry(GlyphCache *gc) 103 { 104 CachedGlyph *sentinel = gc->glyphs + 0; 105 106 if (!sentinel->next_with_same_hash) 107 recycle_cache(gc); 108 109 u32 result = sentinel->next_with_same_hash; 110 ASSERT(result); 111 112 CachedGlyph *cg = gc->glyphs + result; 113 sentinel->next_with_same_hash = cg->next_with_same_hash; 114 cg->next_with_same_hash = 0; 115 cg->uploaded_to_gpu = 0; 116 117 uv2 tile_coord = unpack_gpu_tile_coord(cg->gpu_tile_index); 118 u32 base_tile_index = tile_coord.y * gc->tiles_in_x + tile_coord.x; 119 for (u32 i = 0; i < cg->tile_count; i++) 120 gc->occupied_tiles[base_tile_index + i] = 0; 121 cg->tile_count = 0; 122 123 return result; 124 } 125 126 function u32 127 get_glyph_entry_index(GlyphCache *gc, u32 cp) 128 { 129 u32 result = 0; 130 u32 hash_slot = compute_glyph_hash(gc, cp); 131 u32 entry_idx = gc->hash_table[hash_slot]; 132 133 CachedGlyph *cg; 134 while (entry_idx) { 135 cg = gc->glyphs + entry_idx; 136 if (cp == cg->cp) { 137 result = entry_idx; 138 break; 139 } 140 entry_idx = cg->next_with_same_hash; 141 } 142 143 if (result) { 144 CachedGlyph *prev = gc->glyphs + cg->prev; 145 CachedGlyph *next = gc->glyphs + cg->next; 146 prev->next = cg->next; 147 next->prev = cg->prev; 148 gc->stats.hit_count++; 149 } else { 150 result = pop_free_glyph_entry(gc); 151 cg = gc->glyphs + result; 152 cg->cp = cp; 153 cg->next_with_same_hash = gc->hash_table[hash_slot]; 154 gc->hash_table[hash_slot] = result; 155 gc->stats.miss_count++; 156 } 157 158 ASSERT(result); 159 160 CachedGlyph *sentinel = gc->glyphs + 0; 161 CachedGlyph *last_next = gc->glyphs + sentinel->next; 162 cg->next = sentinel->next; 163 cg->prev = 0; 164 sentinel->next = result; 165 last_next->prev = result; 166 167 return result; 168 } 169 170 function GlyphCacheStats 171 get_and_clear_glyph_cache_stats(GlyphCache *gc) 172 { 173 GlyphCacheStats result = gc->stats; 174 gc->stats = (GlyphCacheStats){0}; 175 return result; 176 } 177 178 function u32 * 179 render_glyph(Arena *a, FontAtlas *fa, u32 cp, u32 font_id, enum face_style style, CachedGlyph **out_glyph) 180 { 181 BEGIN_TIMED_BLOCK(); 182 GlyphCache *gc = &fa->glyph_cache; 183 u32 *rgba_bitmap = NULL; 184 185 /* NOTE: first check if glyph is in the cache and valid */ 186 /* NOTE: 8 MSB are not used for UTF8 so we can use that to store some extra info: 187 * bits 25,26: the style of the glyph 188 * bits 26-31: requested font_id (limited to first 32 fonts */ 189 u32 packed_cp = cp | ((u32)style << 24) | (font_id << 26); 190 u32 idx = get_glyph_entry_index(&fa->glyph_cache, packed_cp); 191 CachedGlyph *cg = fa->glyph_cache.glyphs + idx; 192 193 *out_glyph = cg; 194 if (cg->uploaded_to_gpu) 195 goto end; 196 197 ASSERT(cg->tile_count == 0); 198 199 i32 glyph_idx = 0; 200 i32 font_idx = 0; 201 /* NOTE: first try the requested font id */ 202 if (font_id < fa->nfonts && fa->fonts[font_id][style].buf) { 203 glyph_idx = stbtt_FindGlyphIndex(fa->fonts[font_id][style].font_info, cp); 204 if (glyph_idx) font_idx = font_id; 205 } 206 207 for (u32 i = 0; !glyph_idx && i < fa->nfonts; i++) { 208 u32 test_style = style; 209 if (!fa->fonts[i][test_style].buf) 210 test_style = FS_NORMAL; 211 glyph_idx = stbtt_FindGlyphIndex(fa->fonts[i][test_style].font_info, cp); 212 if (!glyph_idx) { 213 test_style = FS_NORMAL; 214 glyph_idx = stbtt_FindGlyphIndex(fa->fonts[i][test_style].font_info, cp); 215 } 216 if (glyph_idx) { 217 font_idx = i; 218 style = test_style; 219 break; 220 } 221 } 222 223 Font *f = &fa->fonts[font_idx][style]; 224 f32 scale = f->stbtt_scale; 225 i32 x0, y0, x1, y1, advance, left_bearing; 226 stbtt_GetGlyphBitmapBoxSubpixel(f->font_info, glyph_idx, scale, scale, 0, 0, 227 &x0, &y0, &x1, &y1); 228 229 /* NOTE: this looks weird but some 'wide' glyphs are not actually wide but still 230 * need to be displayed as such (eg. ト). x1 can be used to determine this. */ 231 cg->tile_count = ((x1 - 1) / (i32)fa->info.w) + 1; 232 if (x0 < 0 && x1 + x0 <= fa->info.w) 233 cg->tile_count = 1; 234 CLAMP(cg->tile_count, 1, 2); 235 236 i32 height = y1 - y0; 237 i32 width = x1 - x0; 238 239 stbtt_GetGlyphHMetrics(f->font_info, glyph_idx, &advance, &left_bearing); 240 cg->advance = scale * advance; 241 cg->height = MIN(height, fa->info.h); 242 cg->width = MIN(width, fa->info.w * cg->tile_count); 243 cg->x0 = x0; 244 cg->y0 = y0; 245 246 ASSERT(cg->height <= fa->info.h); 247 ASSERT(cg->width <= 2 * fa->info.w); 248 249 u8 *render_buf = alloc(a, u8, width * height); 250 stbtt_MakeGlyphBitmapSubpixel(*a, f->font_info, render_buf, width, height, 251 width, scale, scale, 0, 0, glyph_idx); 252 253 uv2 tile_coord = unpack_gpu_tile_coord(cg->gpu_tile_index); 254 u32 tile_index = tile_coord.y * gc->tiles_in_x + tile_coord.x; 255 if (tile_index != 0 && ((i32)tile_coord.x + cg->tile_count < gc->tiles_in_x)) { 256 /* NOTE: try to use the old tile directly */ 257 if (gc->occupied_tiles[tile_index] || 258 (cg->tile_count == 2 && gc->occupied_tiles[tile_index + 1])) 259 tile_index = 0; 260 } 261 262 if (!tile_index) { 263 /* NOTE: there may be a fancier way to do this but we will see if this causes 264 * any performance issue in practice */ 265 i32 x = 1, y = 1; 266 u8 *occupied = gc->occupied_tiles; 267 for (y = 0; !tile_index && y < gc->tiles_in_y; y++) { 268 for (x = 0; !tile_index && x < gc->tiles_in_x; x++) { 269 if (!occupied[0]) { 270 tile_index = y * gc->tiles_in_x + x; 271 if ((cg->tile_count == 2) && occupied[1]) 272 tile_index = 0; 273 } 274 occupied++; 275 } 276 } 277 tile_coord.x = x - 1; 278 tile_coord.y = y - 1; 279 cg->gpu_tile_index = (tile_coord.y << 16) | (tile_coord.x & 0xFFFF); 280 } 281 282 /* NOTE: tile index 0,0 is reserved */ 283 ASSERT(cg->gpu_tile_index); 284 ASSERT(tile_index); 285 286 for (u32 i = 0; i < cg->tile_count; i++) 287 gc->occupied_tiles[tile_index + i] = 1; 288 289 i32 out_width = fa->info.w * cg->tile_count; 290 i32 out_size = fa->info.h * out_width; 291 ASSERT(out_width >= cg->width); 292 293 i32 out_y = (fa->info.h + y0 - fa->info.baseline) * out_width; 294 out_y = MAX(out_y, 0); 295 296 height = cg->height; 297 width = cg->width; 298 i32 stride = x1 - x0; 299 300 i32 xskip = 0; 301 if (cg->x0 < 0) xskip = -cg->x0; 302 303 rgba_bitmap = alloc(a, u32, out_size); 304 for (i32 i = 0; i < height; i++) { 305 i32 out_x = MAX(cg->x0, 0); 306 for (i32 j = xskip; j < width; j++) { 307 /* TODO: handled coloured glyphs */ 308 u32 pixel = 0; 309 if (0 /* COLOURED */) { 310 } else { 311 pixel = (u32)render_buf[i * stride + j] << 24u | 0x00FFFFFFu; 312 } 313 rgba_bitmap[out_y + out_x] = pixel; 314 out_x++; 315 } 316 out_y += out_width; 317 } 318 319 end: 320 END_TIMED_BLOCK(); 321 322 return rgba_bitmap; 323 } 324 325 function FontInfo 326 compute_font_info(Font *font, u32 font_size) 327 { 328 FontInfo result = {0}; 329 330 local_persist s8 ascii = s8(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" 331 "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"); 332 333 f32 scale = stbtt_ScaleForMappingEmToPixels(font->font_info, font_size); 334 i32 min_y = 0; 335 i32 max_height = 0; 336 f32 width = 0; 337 for (iz i = 0; i < ascii.len; i++) { 338 u32 glyph_idx = stbtt_FindGlyphIndex(font->font_info, ascii.data[i]); 339 i32 x0, y0, x1, y1, advance, left_bearing; 340 stbtt_GetGlyphBitmapBoxSubpixel(font->font_info, glyph_idx, scale, scale, 0, 0, 341 &x0, &y0, &x1, &y1); 342 stbtt_GetGlyphHMetrics(font->font_info, glyph_idx, &advance, &left_bearing); 343 344 width += scale * advance; 345 max_height = MAX(max_height, y1 - y0); 346 min_y = MIN(min_y, y0); 347 } 348 349 u32 graphic_0_count = 0; 350 for (iz i = 0; i < ARRAY_COUNT(graphic_0); i++) { 351 if (graphic_0[i] == 0) 352 continue; 353 graphic_0_count++; 354 355 u32 glyph_idx = stbtt_FindGlyphIndex(font->font_info, graphic_0[i]); 356 i32 x0, y0, x1, y1, advance, left_bearing; 357 stbtt_GetGlyphBitmapBoxSubpixel(font->font_info, glyph_idx, scale, scale, 0, 0, 358 &x0, &y0, &x1, &y1); 359 stbtt_GetGlyphHMetrics(font->font_info, glyph_idx, &advance, &left_bearing); 360 361 width += scale * advance; 362 max_height = MAX(max_height, y1 - y0); 363 min_y = MIN(min_y, y0); 364 } 365 366 result.h = max_height; 367 result.w = (i32)(width / (ascii.len + graphic_0_count) + 1); 368 result.baseline = (i32)(result.h + min_y); 369 370 return result; 371 } 372 373 function void 374 font_atlas_update(FontAtlas *fa, iv2 glyph_bitmap_dim) 375 { 376 GlyphCache *gc = &fa->glyph_cache; 377 mem_clear(gc->glyphs, 0, sizeof(*gc->glyphs) * gc->cache_len); 378 mem_clear(gc->hash_table, 0, sizeof(*gc->hash_table) * gc->cache_len); 379 mem_clear(gc->occupied_tiles, 0, sizeof(*gc->occupied_tiles) * gc->cache_len); 380 get_and_clear_glyph_cache_stats(gc); 381 382 /* NOTE: reserve tile 0,0 */ 383 gc->occupied_tiles[0] = 1; 384 385 Font *font = &fa->fonts[0][FS_NORMAL]; 386 fa->info = compute_font_info(font, g_font_size); 387 388 gc->tiles_in_x = (glyph_bitmap_dim.x - 1) / fa->info.w - 1; 389 gc->tiles_in_y = (glyph_bitmap_dim.y - 1) / fa->info.h - 1; 390 391 ASSERT(gc->tiles_in_x && gc->tiles_in_y); 392 ASSERT(gc->tiles_in_x * fa->info.w <= glyph_bitmap_dim.x); 393 394 for (u32 i = 0; i < gc->cache_len - 1; i++) 395 gc->glyphs[i].next_with_same_hash = i + 1; 396 } 397 398 function v2 399 fa_cell_size(FontAtlas *fa) 400 { 401 v2 result = {.w = fa->info.w, .h = fa->info.h}; 402 return result; 403 } 404 405 function void 406 shift_font_sizes(FontAtlas *fa, i32 size_delta) 407 { 408 g_font_size += size_delta; 409 if (g_font_size < MIN_FONT_SIZE) 410 g_font_size = MIN_FONT_SIZE; 411 for (u32 i = 0; i < fa->nfonts; i++) { 412 /* TODO: better multi font size support */ 413 i32 font_size; 414 if (i == g_ui_debug_font_id) font_size = g_ui_font_size; 415 else font_size = g_font_size; 416 font_size = MIN(font_size, g_font_size); 417 for (u32 j = 0; j < FS_COUNT; j++) { 418 Font *f = &fa->fonts[i][j]; 419 if (!f->buf) continue; 420 f->stbtt_scale = stbtt_ScaleForMappingEmToPixels(f->font_info, font_size); 421 } 422 } 423 } 424 425 function void 426 init_fonts(FontAtlas *fa, Arena *a, iv2 glyph_bitmap_dim) 427 { 428 fa->nfonts = 0; 429 fa->fonts = alloc(a, typeof(*fa->fonts), ARRAY_COUNT(g_fonts)); 430 for (u32 j = 0; j < ARRAY_COUNT(g_fonts); j++) { 431 i32 font_size; 432 if (j == g_ui_debug_font_id) font_size = g_ui_font_size; 433 else font_size = g_font_size; 434 font_size = MIN(font_size, g_font_size); 435 b32 result = 0; 436 for (u32 i = 0; i < FS_COUNT; i++) { 437 if (!g_fonts[j][i]) 438 continue; 439 Font *f = fa->fonts[fa->nfonts] + i; 440 f->font_info = alloc(a, typeof(*f->font_info), 1); 441 if (init_font(f, g_fonts[j][i], font_size)) { 442 result = 1; 443 } else { 444 os_write_err_msg(s8("failed to load font: ")); 445 os_write_err_msg(c_str_to_s8(g_fonts[j][i])); 446 os_write_err_msg(s8("\n")); 447 } 448 } 449 fa->nfonts += result; 450 } 451 452 if (!fa->nfonts) 453 os_fatal(s8("failed to load any fonts!\n")); 454 455 Font *f = &fa->fonts[0][FS_NORMAL]; 456 FontInfo info = compute_font_info(f, MIN_FONT_SIZE); 457 i32 max_tiles_x = glyph_bitmap_dim.x / info.w; 458 i32 max_tiles_y = glyph_bitmap_dim.y / info.h; 459 460 GlyphCache *gc = &fa->glyph_cache; 461 gc->cache_len = round_down_power_of_2(max_tiles_x * max_tiles_y); 462 gc->occupied_tiles = alloc(a, typeof(*gc->occupied_tiles), gc->cache_len); 463 gc->glyphs = alloc(a, typeof(*gc->glyphs), gc->cache_len); 464 gc->hash_table = alloc(a, typeof(*gc->hash_table), gc->cache_len); 465 466 font_atlas_update(fa, glyph_bitmap_dim); 467 }