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]; } }