struct Glyph { Vec2 size; Vec2 offset; float xadvance; float left_side_bearing; Rect2 atlas_bounding_box; }; struct Font { Array glyphs; uint32_t first_char, last_char; uint32_t texture_id; float size; float descent; float ascent; float line_gap; Int line_spacing; Int char_spacing; Rect2 white_texture_bounding_box; }; struct Atlas { uint8_t *bitmap; Vec2I size; Vec2 inverse_size; int xcursor; int ycursor; int32_t biggest_height; Rect2 white_texture_bounding_box; uint32_t texture_id; }; Atlas CreateAtlas(Allocator allocator, Vec2I size) { Atlas result = {}; result.size = size; result.inverse_size.x = 1.f / (float)result.size.x; result.inverse_size.y = 1.f / (float)result.size.y; result.bitmap = AllocArray(allocator, uint8_t, size.x * size.y); // Add a whitebox first for rectangle rendering for (int y = 0; y < 16; y++) { for (int x = 0; x < 16; x++) { uint8_t *dst = result.bitmap + x + y * result.size.x; *dst = 0xff; } } result.white_texture_bounding_box = { { 2.f * result.inverse_size.x, 2.f / (float)result.size.y}, {14.f * result.inverse_size.x, 14.f / (float)result.size.y} }; result.xcursor += 16; result.biggest_height += 16; return result; } Rect2 PackBitmap(Atlas *atlas, uint8_t *bitmap, int width, int 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. int 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 { SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Font loading error!", "Error while packing a font into atlas. Atlas size for this font scale is a bit too small", NULL); } } uint8_t *src = bitmap; for (int y = atlas->ycursor; y < atlas->ycursor + height; y += 1) { for (int x = atlas->xcursor; x < atlas->xcursor + width; x += 1) { uint8_t *dst = atlas->bitmap + x + y * atlas->size.x; *dst = *src++; } } Vec2 size = {(float)width * atlas->inverse_size.x, (float)height * atlas->inverse_size.y}; Vec2 pos = {(float)atlas->xcursor * atlas->inverse_size.x, (float)atlas->ycursor * atlas->inverse_size.y}; Rect2 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; } Glyph *GetGlyph(Font *font, uint32_t codepoint) { bool is_in_range = codepoint >= font->first_char && codepoint <= font->last_char; if (is_in_range) { uint32_t index = codepoint - font->first_char; return &font->glyphs[index]; } else { uint32_t index = '?' - font->first_char; return &font->glyphs[index]; } } Font CreateFont(Atlas *atlas, int32_t size, String path) { Allocator allocator = GetSystemAllocator(); Scratch scratch; Font result = {}; result.glyphs.allocator = allocator; String file = ReadFile(scratch, path); if (file.len == 0) { file = BakedInFont; Assert(file.len != 0); } stbtt_fontinfo stb_font; int success = stbtt_InitFont(&stb_font, (const unsigned char *)file.data, 0); if (!success) { return result; } float scale = stbtt_ScaleForPixelHeight(&stb_font, (float)size); // float em_scale = stbtt_ScaleForMappingEmToPixels(&stb_font, (float)size); int ascent, descent, line_gap; stbtt_GetFontVMetrics(&stb_font, &ascent, &descent, &line_gap); result.ascent = (float)ascent * scale; result.descent = (float)descent * scale; result.line_gap = (float)line_gap * scale; result.size = (float)size; result.first_char = ' '; result.last_char = '~'; result.white_texture_bounding_box = atlas->white_texture_bounding_box; int glyph_count = result.last_char - result.first_char + 1; // + 1 because it's '<=' not '<' Reserve(&result.glyphs, glyph_count); for (uint32_t ascii_symbol = result.first_char; ascii_symbol <= result.last_char; ascii_symbol++) { int width, height, xoff, yoff; uint8_t *bitmap = (uint8_t *)stbtt_GetCodepointBitmap(&stb_font, 0, scale, ascii_symbol, &width, &height, &xoff, &yoff); defer { stbtt_FreeBitmap(bitmap, 0); }; int xadvance, left_side_bearing; stbtt_GetCodepointHMetrics(&stb_font, ascii_symbol, &xadvance, &left_side_bearing); Glyph glyph = {}; glyph.atlas_bounding_box = PackBitmap(atlas, bitmap, width, height); glyph.size = {(float)width, (float)height}; glyph.offset = {(float)xoff, (float)yoff}; glyph.xadvance = (float)xadvance * scale; glyph.left_side_bearing = (float)left_side_bearing * scale; Add(&result.glyphs, glyph); } Glyph *g = GetGlyph(&result, '_'); result.char_spacing = (Int)g->xadvance ? (Int)g->xadvance : (Int)g->size.x; result.line_spacing = (Int)(result.ascent - result.descent + result.line_gap); return result; }