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 }