Return to project, refactoring
This commit is contained in:
158
src/render/font.cpp
Normal file
158
src/render/font.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 = GetDefaultFont(scratch);
|
||||
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);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user