vtgl

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

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 }