156 lines
5.4 KiB
C
156 lines
5.4 KiB
C
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];
|
|
}
|
|
} |