font.c (6696B)
1 /* See LICENSE for copyright details */ 2 static void 3 init_font(FontAtlas *fa, Font *f, FontDesc *fdesc) 4 { 5 os_mapped_file map = os_map_file(fdesc->path, OS_MAP_READ, OS_MAP_PRIVATE); 6 7 f->bufsize = map.len; 8 f->buf = map.data; 9 10 if (!stbtt_InitFont(&f->font_info, f->buf, 0)) 11 os_die("stbtt_InitFont: failed on %s\n", fdesc->path); 12 f->fontsize = fdesc->height; 13 f->stbtt_scale = stbtt_ScaleForPixelHeight(&f->font_info, f->fontsize); 14 } 15 16 static u32 17 compute_glyph_hash(GlyphCache *gc, u32 cp) 18 { 19 /* TODO: better hash function! */ 20 u32 result = (((u64)cp * 1111111111111111111) >> 32) & (gc->cache_len - 1); 21 return result; 22 } 23 24 static void 25 recycle_cache(GlyphCache *gc) 26 { 27 CachedGlyph *sentinel = gc->glyphs + 0; 28 29 ASSERT(sentinel->prev); 30 31 u32 last_index = sentinel->prev; 32 CachedGlyph *cg = gc->glyphs + last_index; 33 CachedGlyph *prev = gc->glyphs + cg->prev; 34 prev->next = 0; 35 sentinel->prev = cg->prev; 36 37 u32 hash_slot = compute_glyph_hash(gc, cg->cp); 38 u32 *next_index = gc->hash_table + hash_slot; 39 while (*next_index != last_index) { 40 ASSERT(*next_index); 41 next_index = &gc->glyphs[*next_index].next_with_same_hash; 42 } 43 ASSERT(*next_index == last_index); 44 *next_index = cg->next_with_same_hash; 45 cg->next_with_same_hash = sentinel->next_with_same_hash; 46 sentinel->next_with_same_hash = last_index; 47 48 gc->stats.recycle_count++; 49 } 50 51 static i32 52 pop_free_glyph_entry(GlyphCache *gc) 53 { 54 CachedGlyph *sentinel = gc->glyphs + 0; 55 56 if (!sentinel->next_with_same_hash) 57 recycle_cache(gc); 58 59 u32 result = sentinel->next_with_same_hash; 60 ASSERT(result); 61 62 CachedGlyph *cg = gc->glyphs + result; 63 sentinel->next_with_same_hash = cg->next_with_same_hash; 64 cg->next_with_same_hash = 0; 65 cg->uploaded_to_gpu = 0; 66 67 return result; 68 } 69 70 static u32 71 get_glyph_entry_index(GlyphCache *gc, u32 cp) 72 { 73 u32 result = 0; 74 u32 hash_slot = compute_glyph_hash(gc, cp); 75 u32 entry_idx = gc->hash_table[hash_slot]; 76 77 CachedGlyph *cg; 78 while (entry_idx) { 79 cg = gc->glyphs + entry_idx; 80 if (cp == cg->cp) { 81 result = entry_idx; 82 break; 83 } 84 entry_idx = cg->next_with_same_hash; 85 } 86 87 if (result) { 88 CachedGlyph *prev = gc->glyphs + cg->prev; 89 CachedGlyph *next = gc->glyphs + cg->next; 90 prev->next = cg->next; 91 next->prev = cg->prev; 92 gc->stats.hit_count++; 93 } else { 94 result = pop_free_glyph_entry(gc); 95 cg = gc->glyphs + result; 96 cg->cp = cp; 97 cg->next_with_same_hash = gc->hash_table[hash_slot]; 98 gc->hash_table[hash_slot] = result; 99 gc->stats.miss_count++; 100 } 101 102 ASSERT(result); 103 104 CachedGlyph *sentinel = gc->glyphs + 0; 105 CachedGlyph *last_next = gc->glyphs + sentinel->next; 106 cg->next = sentinel->next; 107 cg->prev = 0; 108 sentinel->next = result; 109 last_next->prev = result; 110 111 return result; 112 } 113 114 static GlyphCacheStats 115 get_and_clear_glyph_cache_stats(GlyphCache *gc) 116 { 117 GlyphCacheStats result = gc->stats; 118 gc->stats = (GlyphCacheStats){0}; 119 return result; 120 } 121 122 static u32 * 123 render_glyph(Arena *a, FontAtlas *fa, u32 cp, enum face_style style, CachedGlyph **out_glyph, u32 *out_idx) 124 { 125 /* NOTE: first check if glyph is in the cache and valid */ 126 /* NOTE: 8 MSB are not used for UTF8 so we can use that to store the style of the glyph */ 127 u32 idx = get_glyph_entry_index(&fa->glyph_cache, cp|(1 << (30 - style))); 128 CachedGlyph *cg = fa->glyph_cache.glyphs + idx; 129 130 *out_idx = idx; 131 *out_glyph = cg; 132 if (cg->uploaded_to_gpu) 133 return NULL; 134 135 i32 glyph_idx = 0; 136 i32 font_idx = 0; 137 for (u32 i = 0; i < fa->nfonts; i++) { 138 if (!fa->fonts[i][style].buf) 139 style = FS_NORMAL; 140 glyph_idx = stbtt_FindGlyphIndex(&fa->fonts[i][style].font_info, cp); 141 if (!glyph_idx) 142 glyph_idx = stbtt_FindGlyphIndex(&fa->fonts[i][FS_NORMAL].font_info, cp); 143 if (glyph_idx) { 144 font_idx = i; 145 break; 146 } 147 } 148 149 Font *f = &fa->fonts[font_idx][style]; 150 f32 scale = f->stbtt_scale; 151 i32 x0, y0, x1, y1; 152 stbtt_GetGlyphBitmapBoxSubpixel(&f->font_info, glyph_idx, scale, scale, 0, 0, 153 &x0, &y0, &x1, &y1); 154 155 cg->g.size.w = x1 - x0; 156 cg->g.size.h = y1 - y0; 157 cg->g.delta.x = x0; 158 cg->g.delta.y = -y1; 159 160 u8 *render_buf = alloc(a, u8, cg->g.size.w * cg->g.size.h); 161 stbtt_MakeGlyphBitmapSubpixel(*a, &f->font_info, render_buf, cg->g.size.w, cg->g.size.h, 162 cg->g.size.w, scale, scale, 0, 0, glyph_idx); 163 164 u32 *rgba_bitmap = alloc(a, u32, cg->g.size.w * cg->g.size.h); 165 for (u32 i = 0; i < cg->g.size.h; i++) { 166 for (u32 j = 0; j < cg->g.size.w; j++) { 167 /* TODO: handled coloured glyphs */ 168 u32 pixel = 0; 169 if (0 /* COLOURED */) { 170 } else { 171 pixel = (u32)(render_buf[i * cg->g.size.w + j]) << 24; 172 } 173 rgba_bitmap[i * cg->g.size.w + j] = pixel; 174 } 175 } 176 177 /* NOTE: ' ' has 0 size in freetype but we need it to have a width! */ 178 if (cp == ' ') cg->g.size.w = fa->size.w; 179 180 return rgba_bitmap; 181 } 182 183 static void 184 update_font_metrics(FontAtlas *fa) 185 { 186 i32 x0, x1, y0, y1; 187 stbtt_GetFontBoundingBox(&fa->fonts[0][FS_NORMAL].font_info, &x0, &y0, &x1, &y1); 188 f32 scale = fa->fonts[0][FS_NORMAL].stbtt_scale; 189 fa->size.h = scale * (y1 - y0) + 0.5; 190 fa->size.w = scale * (x1 - x0) + 0.5; 191 fa->baseline = -scale * y0 + 0.5; 192 } 193 194 static void 195 font_atlas_update(FontAtlas *fa) 196 { 197 GlyphCache *gc = &fa->glyph_cache; 198 mem_clear(gc->glyphs, 0, sizeof(*gc->glyphs) * gc->cache_len); 199 mem_clear(gc->hash_table, 0, sizeof(*gc->hash_table) * gc->cache_len); 200 for(u32 i = 0; i < gc->cache_len - 1; i++) 201 gc->glyphs[i].next_with_same_hash = i + 1; 202 get_and_clear_glyph_cache_stats(gc); 203 update_font_metrics(fa); 204 } 205 206 static i32 207 shift_font_sizes(FontAtlas *fa, i32 size_delta) 208 { 209 for (u32 i = 0; i < fa->nfonts; i++) { 210 for (u32 j = 0; j < FS_LAST; j++) { 211 Font *f = &fa->fonts[i][j]; 212 if (!f->buf) continue; 213 214 i32 newsize = f->fontsize + size_delta; 215 CLAMP(newsize, 8, MAX_FONT_SIZE); 216 f->fontsize = newsize; 217 f->stbtt_scale = stbtt_ScaleForPixelHeight(&f->font_info, f->fontsize); 218 } 219 } 220 return 0; 221 } 222 223 static void 224 init_fonts(Term *t, Arena *a) 225 { 226 FontAtlas *fa = &t->fa; 227 u32 n_fonts = ARRAY_COUNT(g_fonts); 228 229 fa->fonts = alloc(a, typeof(*fa->fonts), n_fonts); 230 for (fa->nfonts = 0; fa->nfonts < n_fonts; fa->nfonts++) { 231 for (u32 i = 0; i < FS_LAST; i++) { 232 if (!g_fonts[fa->nfonts][i].path) 233 continue; 234 init_font(fa, &fa->fonts[fa->nfonts][i], &g_fonts[fa->nfonts][i]); 235 } 236 } 237 238 static_assert(ISPOWEROFTWO(GLYPH_CACHE_LEN), "GLYPH_CACHE_LEN must be a power of two!"); 239 GlyphCache *gc = &fa->glyph_cache; 240 gc->cache_len = GLYPH_CACHE_LEN; 241 gc->glyphs = alloc(a, typeof(*gc->glyphs), gc->cache_len); 242 gc->hash_table = alloc(a, typeof(*gc->hash_table), gc->cache_len); 243 244 font_atlas_update(fa); 245 }