166 lines
5.7 KiB
C++
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;
|
|
}
|