Files
wasm_transcript_browser/src/render/font.c
2025-01-08 09:34:53 +01:00

156 lines
5.4 KiB
C

typedef struct rn_glyph_t rn_glyph_t;
struct rn_glyph_t {
v2f32_t size;
v2f32_t offset;
f32 xadvance;
f32 left_side_bearing;
r2f32_t atlas_bounding_box;
};
typedef struct rn_font_t rn_font_t;
struct rn_font_t {
rn_glyph_t *glyphs;
i32 glyph_count;
u32 first_char, last_char;
u32 texture_id;
f32 size;
f32 descent;
f32 ascent;
f32 line_gap;
r2f32_t white_texture_bounding_box;
};
typedef struct rn_atlas_t rn_atlas_t;
struct rn_atlas_t {
u8 *bitmap;
v2i32_t size;
v2f32_t inverse_size;
i32 xcursor;
i32 ycursor;
i32 biggest_height;
r2f32_t white_texture_bounding_box;
u32 texture_id;
};
rn_atlas_t *rn_create_atlas(ma_arena_t *arena, v2i32_t size) {
rn_atlas_t *result = ma_push_type(arena, rn_atlas_t);
result->size = size;
result->inverse_size.x = 1.f / (f32)result->size.x;
result->inverse_size.y = 1.f / (f32)result->size.y;
result->bitmap = ma_push_array(arena, u8, size.x * size.y);
// Add a whitebox first for rectangle rendering
for (i32 y = 0; y < 16; y++) {
for (i32 x = 0; x < 16; x++) {
u8 *dst = result->bitmap + x + y * result->size.x;
*dst = 0xff;
}
}
result->white_texture_bounding_box = (r2f32_t){
.min = { 2.f * result->inverse_size.x, 2.f / (f32)result->size.y},
.max = {14.f * result->inverse_size.x, 14.f / (f32)result->size.y}
};
result->xcursor += 16;
result->biggest_height += 16;
return result;
}
r2f32_t rn_pack_bitmap(rn_atlas_t *atlas, u8 *bitmap, i32 width, i32 height) {
// Packing into a texture atlas
// @Inefficient The algorithm is a simplest thing I had in mind, first we advance
// through the atlas in X packing consecutive glyphs. After we get to the end of the row
// we advance to the next row by the Y size of the biggest packed glyph. If we get to the
// end of atlas and fail to pack everything the app panics.
i32 spacing = 4;
if (atlas->xcursor + width > atlas->size.x) {
if (atlas->ycursor + height < atlas->size.y) {
atlas->xcursor = 0;
atlas->ycursor += atlas->biggest_height + spacing;
} else {
fatalf("error while packing a font into atlas. rn_atlas_t size for this font scale is a bit too small");
}
}
u8 *src = bitmap;
for (i32 y = atlas->ycursor; y < atlas->ycursor + height; y += 1) {
for (i32 x = atlas->xcursor; x < atlas->xcursor + width; x += 1) {
u8 *dst = atlas->bitmap + x + y * atlas->size.x;
*dst = *src++;
}
}
v2f32_t size = {(f32)width * atlas->inverse_size.x, (f32)height * atlas->inverse_size.y};
v2f32_t pos = {(f32)atlas->xcursor * atlas->inverse_size.x, (f32)atlas->ycursor * atlas->inverse_size.y};
r2f32_t result = {pos.x, pos.y, pos.x + size.x, pos.y + size.y};
atlas->xcursor += width + spacing;
if (height > atlas->biggest_height) {
atlas->biggest_height = height;
}
return result;
}
rn_font_t rn_create_font(ma_arena_t *glyph_arena, s8_t font_data, rn_atlas_t *atlas, i32 size) {
rn_font_t result = {};
stbtt_fontinfo stb_font;
i32 success = stbtt_InitFont(&stb_font, (const unsigned char *)font_data.str, 0);
if (!success) {
return result;
}
f32 scale = stbtt_ScaleForPixelHeight(&stb_font, (f32)size);
// f32 em_scale = stbtt_ScaleForMappingEmToPixels(&stb_font, (f32)size);
i32 ascent, descent, line_gap;
stbtt_GetFontVMetrics(&stb_font, &ascent, &descent, &line_gap);
result.ascent = (f32)ascent * scale;
result.descent = (f32)descent * scale;
result.line_gap = (f32)line_gap * scale;
result.size = (f32)size;
result.first_char = ' ';
result.last_char = '~';
result.white_texture_bounding_box = atlas->white_texture_bounding_box;
i32 glyph_count = result.last_char - result.first_char + 1; // + 1 because it's '<=' not '<'
result.glyphs = ma_push_array(glyph_arena, rn_glyph_t, glyph_count);
for (u32 ascii_symbol = result.first_char; ascii_symbol <= result.last_char; ascii_symbol++) {
i32 width, height, xoff, yoff;
u8 *bitmap = (u8 *)stbtt_GetCodepointBitmap(&stb_font, 0, scale, ascii_symbol, &width, &height, &xoff, &yoff);
i32 xadvance, left_side_bearing;
stbtt_GetCodepointHMetrics(&stb_font, ascii_symbol, &xadvance, &left_side_bearing);
rn_glyph_t glyph = {};
glyph.atlas_bounding_box = rn_pack_bitmap(atlas, bitmap, width, height);
glyph.size = (v2f32_t){(f32)width, (f32)height};
glyph.offset = (v2f32_t){(f32)xoff, (f32)yoff};
glyph.xadvance = (f32)xadvance * scale;
glyph.left_side_bearing = (f32)left_side_bearing * scale;
assert(result.glyph_count + 1 <= glyph_count);
result.glyphs[result.glyph_count++] = glyph;
stbtt_FreeBitmap(bitmap, 0);
}
return result;
}
rn_glyph_t *rn_get_glyph(rn_font_t *font, u32 codepoint) {
b32 is_in_range = codepoint >= font->first_char && codepoint <= font->last_char;
if (is_in_range) {
u32 index = codepoint - font->first_char;
return &font->glyphs[index];
} else {
u32 index = '?' - font->first_char;
return &font->glyphs[index];
}
}