Files
text_editor/src/render/font.cpp
2026-01-05 10:15:26 +01:00

166 lines
5.7 KiB
C++

struct Glyph {
Vec2 size;
Vec2 offset;
float xadvance;
float left_side_bearing;
Rect2 atlas_bounding_box;
};
struct Font {
Array<Glyph> 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;
}