#ifndef AMR_FONTS_H #define AMR_FONTS_H #define LATIN_BASIC_START_INDEX 32 #define LATIN_BASIC_END_INDEX 126 typedef struct amr_letter_details_debug { i32 advance; i32 lsb; i32 x0, y0, x1, y1; i32 byte_offset; // @TODO: check if offsets are in PX f32 StartingYOffset; f32 StartingXOffset; } amr_letter_details_debug; typedef struct amr_font_state_debug { stbtt_fontinfo font_info; amr_letter_details_debug font_details; // font metrics f32 font_size; f32 scale_factor; i32 ascent; i32 descent; i32 line_gap; // glyphs u32 charset_start_index; u32 charset_end_index; // @TODO: make this array u32 *glyph_index_array; u32 glyph_count; // text specific metrics // @TODO: these should probably be moved to an array or remain out of this struct f32 Baseline; f32 MaxLineWidthPx; // font bitmap info unsigned char *FontBitmap; u32 FontBitmapWidth; u32 FontBitmapHeight; } amr_font_state; // initialise a font and loads the font in the fontbuffer // input: // - font path // - font buffer, must be initialised by caller // - size of the font file, must be known beforehand // output: // - error code u32 amr_InitFont(const u8 *FontPath, u8 *FontBuffer, u32 FontFz); void amr_LoadFontMetrics(amr_font_state font, u32 FontSize); void amr_LoadGlyphsFromFont(amr_DebugArena *MemoryArena, amr_font_state *font); u32 amr_GetGlyphIdFromCodepoint(amr_font_state font, u32 Codepoint); ////////////////////////////////////////////////////////////////////////////// /// Naive Font Bitmap generation /// This approach is fast but usese more memory /// We make a single draw call, but load the entire text in a single bitmap ////////////////////////////////////////////////////////////////////////////// void amr_MakeFontBitmapNaive(GameState *State, amr_font_state *Font, char *Text, u32 TextSize); u32 amr_InitFont(stbtt_fontinfo *font_info, const char *FontPath, u8 *FontBuffer, u32 FontSz) { PlatformDebugFileRead(FontPath, FontBuffer, FontSz); if (!stbtt_InitFont(font_info, FontBuffer, 0)) { printf("ERROR: failed to read arial font"); assert(1 == 0); } return 0; } void amr_LoadFontMetrics(amr_font_state *Font, f32 FontSize) { // get a SF (scale factor) Font->scale_factor = stbtt_ScaleForPixelHeight(&(Font->font_info), FontSize); // get vertical metrics stbtt_GetFontVMetrics(&(Font->font_info), &(Font->ascent), &(Font->descent), &(Font->line_gap)); } void amr_LoadGlyphsFromFont(amr_DebugArena *MemoryArena, amr_font_state *Font) { Font->glyph_count = Font->charset_end_index - Font->charset_start_index; Font->glyph_index_array = (u32 *)amr_ArenaAlloc(MemoryArena, sizeof(Font->glyph_count) * Font->glyph_count); u32 *array_iter = Font->glyph_index_array; for (u32 i = 0; i < Font->glyph_count; i++) { u32 offset_codepoint = Font->charset_start_index + i; *array_iter = stbtt_FindGlyphIndex(&(Font->font_info), offset_codepoint); array_iter++; } } u32 amr_GetGlyphIdFromCodepoint(amr_font_state Font, u32 Codepoint) { u32 glyph_id = *(Font.glyph_index_array + (Codepoint - Font.charset_start_index)); return glyph_id; } void amr_MakeFontBitmapNaive(GameState *State, amr_font_state *Font, char *Text, u32 TextSize) { Font->MaxLineWidthPx = 0.0f; Font->Baseline = roundf(Font->ascent * Font->scale_factor); u32 fd_sz = sizeof(amr_letter_details_debug)*TextSize; amr_letter_details_debug *fd_arr = (amr_letter_details_debug *)amr_ArenaAlloc(&(State->Memory.Arena), fd_sz); amr_letter_details_debug *_fi = fd_arr; f32 RunningXOffset = 0.0f; // convert str to font for (i32 i = 0; i < TextSize; ++i) { _fi->StartingXOffset = RunningXOffset; if (Text[i] == '\n') { // if new line, move baseline down Font->Baseline += (i32)roundf((Font->ascent - Font->descent + Font->line_gap) * Font->scale_factor); RunningXOffset = 0; ++_fi; continue; } u32 GlyphId = amr_GetGlyphIdFromCodepoint(*Font, Text[i]); stbtt_GetGlyphHMetrics(&(Font->font_info), GlyphId, &_fi->advance, &_fi->lsb); stbtt_GetGlyphBitmapBox(&(Font->font_info), GlyphId, Font->scale_factor, Font->scale_factor, &_fi->x0, &_fi->y0, &_fi->x1, &_fi->y1); _fi->StartingYOffset = (f32)(Font->Baseline + _fi->y0); RunningXOffset += (_fi->advance * Font->scale_factor); if (i < TextSize - 1 && Text[i+1] != '\n') { i32 kern = stbtt_GetGlyphKernAdvance(&(Font->font_info), GlyphId, amr_GetGlyphIdFromCodepoint(*Font, Text[i+1])); RunningXOffset += roundf(kern * Font->scale_factor); } if (RunningXOffset > Font->MaxLineWidthPx) { Font->MaxLineWidthPx = RunningXOffset; } ++_fi; } // make all the glyphs into bitmaps. _fi = fd_arr; Font->FontBitmapWidth = (u32)Font->MaxLineWidthPx; Font->FontBitmapHeight = (u32)(Font->Baseline - roundf(Font->scale_factor * Font->descent)); u32 bitmap_size = sizeof(unsigned char)*Font->FontBitmapWidth*Font->FontBitmapHeight; Font->FontBitmap = (unsigned char *)amr_ArenaAlloc(&(State->Memory.Arena), bitmap_size); _fi = fd_arr; for (int i = 0; i < TextSize; i++) { if (Text[i] == '\n') { ++_fi; continue; } _fi->byte_offset = (i32)(_fi->StartingXOffset + roundf(_fi->lsb * Font->scale_factor) + (_fi->StartingYOffset * Font->FontBitmapWidth)) ; // store image data in bitmap stbtt_MakeGlyphBitmap(&(Font->font_info), Font->FontBitmap + _fi->byte_offset, _fi->x1 - _fi->x0, _fi->y1 - _fi->y0, Font->FontBitmapWidth, Font->scale_factor, Font->scale_factor, amr_GetGlyphIdFromCodepoint(*Font, Text[i])); _fi++; } } #endif