#define GLYPH_SUBPIXEL_SEGMENTS 3 global read_only s32 Font_Oversample = 2; inline s32 GetSubpixelSegmentAtP(r32 Value) { s32 Result = (s32)(Value - Floor(Value))*GLYPH_SUBPIXEL_SEGMENTS; return(Result); } static void RasterizeGlyph(glyph_atlas *Atlas, font_id Font, glyph *Glyph, u32 Codepoint, r32 Size, s32 Subpixel) { Glyph->Font = Font; Glyph->Codepoint = Codepoint; Glyph->Size = Size; Glyph->Subpixel = Subpixel; Assert(Subpixel < GLYPH_SUBPIXEL_SEGMENTS); loaded_font *LoadedFont = Atlas->Fonts + Font; stbtt_fontinfo *Info = &LoadedFont->Info; r32 Scale = stbtt_ScaleForMappingEmToPixels(Info, Size); s32 InternalIndex = (s32)(Glyph - Atlas->Glyphs); s32 GlyphsPerRow = Atlas->BitmapSize / Atlas->GlyphSize; v2_s32 BaseTextureOffset = V2S32((InternalIndex % GlyphsPerRow)*Atlas->GlyphSize, (InternalIndex / GlyphsPerRow)*Atlas->GlyphSize); int GlyphIndex = stbtt_FindGlyphIndex(Info, Codepoint); stbtt_GetGlyphBitmapBoxSubpixel(Info, GlyphIndex, Scale, Scale, (r32)Subpixel/GLYPH_SUBPIXEL_SEGMENTS, 0, &Glyph->P0.x, &Glyph->P0.y, &Glyph->P1.x, &Glyph->P1.y); Fill(Atlas->BitmapBuffer, 0, Atlas->GlyphSize*Atlas->GlyphSize); stbtt_MakeGlyphBitmapSubpixel(Info, Atlas->BitmapBuffer, Atlas->GlyphSize, Atlas->GlyphSize, Atlas->GlyphSize, Scale, Scale, (r32)Subpixel/GLYPH_SUBPIXEL_SEGMENTS, 0, GlyphIndex); s32 Advance, LeftSideBearing; stbtt_GetGlyphHMetrics(Info, GlyphIndex, &Advance, &LeftSideBearing); Glyph->Advance = Advance*Scale; Glyph->Offset.x = LeftSideBearing*Scale; Glyph->Offset.y = Glyph->P0.y + (LoadedFont->Ascent + LoadedFont->LineGap)*Scale; v2_s32 Dim = Glyph->P1 - Glyph->P0; Glyph->P0 = BaseTextureOffset; Glyph->P1 = BaseTextureOffset + Dim + V2S32(2, 2); Atlas->RenderCommands->FillRegion(Atlas->Texture, BaseTextureOffset, V2S32(Atlas->GlyphSize, Atlas->GlyphSize), Atlas->BitmapBuffer); } static glyph *GetGlyph(glyph_atlas *Atlas, font_id Font, u32 Codepoint, r32 Size, s32 Subpixel) { glyph *Glyph = 0; for(s32 GlyphIndex = 0; GlyphIndex < Atlas->GlyphsUsed; ++GlyphIndex) { glyph *At = Atlas->Glyphs + GlyphIndex; if((At->Font == Font) && (At->Codepoint == Codepoint) && (At->Size == Size) && (At->Subpixel == Subpixel)) { Glyph = At; break; } } if(Glyph) { DLLRemove_NP(Atlas->LRUFirst, Atlas->LRULast, Glyph, LRUNext, LRUPrev); } else { if(Atlas->GlyphsUsed < Atlas->MaxGlyphCount) { Glyph = Atlas->Glyphs + Atlas->GlyphsUsed++; } else { Glyph = Atlas->LRUFirst; Assert(Glyph); DLLRemove_NP(Atlas->LRUFirst, Atlas->LRULast, Glyph, LRUNext, LRUPrev); } RasterizeGlyph(Atlas, Font, Glyph, Codepoint, Size, Subpixel); } DLLInsertLast_NP(Atlas->LRUFirst, Atlas->LRULast, Glyph, LRUNext, LRUPrev); return(Glyph); } static glyph_atlas *CreateGlyphAtlas(vn_render_commands *RenderCommands, s32 BitmapSize = DEFAULT_GLYPH_ATLAS_DIM, s32 GlyphSize = MAX_GLYPH_SIZE) { memory_arena *Arena = ArenaAllocate(Megabytes(128)); glyph_atlas *Atlas = PushStruct(Arena, glyph_atlas); Atlas->Arena = Arena; Atlas->BitmapSize = BitmapSize; Atlas->GlyphSize = GlyphSize; Atlas->MaxGlyphCount = (DEFAULT_GLYPH_ATLAS_DIM / MAX_GLYPH_SIZE)*(DEFAULT_GLYPH_ATLAS_DIM / MAX_GLYPH_SIZE); Atlas->Glyphs = PushArray(Atlas->Arena, glyph, Atlas->MaxGlyphCount); Atlas->RenderCommands = RenderCommands; Atlas->Texture = RenderCommands->AllocateTexture(V2S32(BitmapSize, BitmapSize), Render_TextureFormat_R8, false, 0); Atlas->Fonts[Font_Regular].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/Roboto-Regular.ttf")); Atlas->Fonts[Font_Bold].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/Roboto-Bold.ttf")); Atlas->Fonts[Font_Monospace].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/DejaVuSansMono.ttf")); Atlas->Fonts[Font_MonospaceOblique].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/DejaVuSansMono-Oblique.ttf")); Atlas->Fonts[Font_Hand].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/PatrickHand-Regular.ttf")); Atlas->Fonts[Font_Fancy].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/Merriweather-Regular.ttf")); Atlas->Fonts[Font_Japanese].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/NotoSansJP-Regular.ttf")); Atlas->Fonts[Font_Icons].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/icons.ttf")); for(s32 FontIndex = 0; FontIndex < Font_Count; ++FontIndex) { loaded_font *Font = Atlas->Fonts + FontIndex; stbtt_InitFont(&Font->Info, Font->Data.Data, stbtt_GetFontOffsetForIndex(Font->Data.Data, 0)); stbtt_GetFontVMetrics(&Font->Info, &Font->Ascent, &Font->Descent, &Font->LineGap); } Atlas->BitmapBuffer = PushArray(Atlas->Arena, u8, GlyphSize*GlyphSize); return(Atlas); } static r32 PushText(render_group *Group, glyph_atlas *Atlas, font_id Font, v2 P, r32 Size, v4 Color, string Text) { r32 OffsetX = 0; u8 *TextBegin = Text.Data; u8 *TextEnd = TextBegin+Text.Count; for(u8 *Byte = TextBegin; Byte < TextEnd;) { string_decode Decode = DecodeUTF8Codepoint(Byte, TextEnd-Byte); Byte += Decode.Size; u32 Codepoint = Decode.Codepoint; glyph *Glyph = GetGlyph(Atlas, Font, Codepoint, Size*Font_Oversample, GetSubpixelSegmentAtP(P.x*Font_Oversample)); Assert(Glyph); v2 GlyphP = P + Glyph->Offset*(1.0 / Font_Oversample) + V2(OffsetX, 1); v2 RenderDim = ConvertV2ToR32(Glyph->P1 - Glyph->P0); v2 Dim = RenderDim*(1.0 / Font_Oversample); PushTexturedQuad(Group, Range2R32(GlyphP, GlyphP+Dim), Range2R32(ConvertV2ToR32(Glyph->P0), ConvertV2ToR32(Glyph->P1)), Color, Color, Color, Color, 0, 0, 0, Atlas->Texture); OffsetX += Glyph->Advance/Font_Oversample; } return(OffsetX); } static void PushTextF(render_group *Group, glyph_atlas *Atlas, font_id Font, v2 P, r32 Size, v4 Color, char *Format, ...) { temporary_memory Scratch = GetScratch(0, 0); va_list Arguments; va_start(Arguments, Format); string String = PushFormatVariadic(Scratch.Arena, Format, Arguments); va_end(Arguments); PushText(Group, Atlas, Font, P, Size, Color, String); ReleaseScratch(Scratch); } inline r32 CalculateRasterizedTextWidth(glyph_atlas *Atlas, font_id Font, r32 Size, string Text) { r32 X = 0; u8 *TextBegin = Text.Data; u8 *TextEnd = TextBegin+Text.Count; for(u8 *Byte = TextBegin; Byte < TextEnd;) { string_decode Decode = DecodeUTF8Codepoint(Byte, TextEnd-Byte); Byte += Decode.Size; u32 Codepoint = Decode.Codepoint; glyph *Glyph = GetGlyph(Atlas, Font, Codepoint, Size*Font_Oversample, GetSubpixelSegmentAtP(X*Font_Oversample)); Assert(Glyph); X += Glyph->Advance/Font_Oversample; } return(X); } inline r32 CalculateRasterizedTextHeight(glyph_atlas *Atlas, font_id Font, r32 Size, string Text) { r32 Scale = stbtt_ScaleForMappingEmToPixels(&Atlas->Fonts[Font].Info, Size)/ stbtt_ScaleForPixelHeight(&Atlas->Fonts[Font].Info, Size); r32 Y = Size*Scale; u8 *TextBegin = Text.Data; u8 *TextEnd = TextBegin+Text.Count; for(u8 *Byte = TextBegin; Byte < TextEnd;) { string_decode Decode = DecodeUTF8Codepoint(Byte, TextEnd-Byte); Byte += Decode.Size; u32 Codepoint = Decode.Codepoint; if(Codepoint == '\n') { Y += Size*Scale; } } return(Y); }