diff --git a/dlib/dlibincludes.c b/dlib/dlibincludes.c index a523e7c..315eb71 100644 --- a/dlib/dlibincludes.c +++ b/dlib/dlibincludes.c @@ -16,9 +16,9 @@ # include #endif -// # include -// # include FT_FREETYPE_H -// # include FT_GLYPH_H +# include +# include FT_FREETYPE_H +# include FT_GLYPH_H #endif #ifdef _WIN32 diff --git a/dlib/fonts.d b/dlib/fonts.d index 61161f9..9f8b92b 100644 --- a/dlib/fonts.d +++ b/dlib/fonts.d @@ -1,5 +1,7 @@ module dlib.fonts; +import dlibincludes; + import dlib.externdecl; import dlib.aliases; import dlib.util; @@ -12,7 +14,9 @@ import std.traits; import stb_truetype; -alias FontFace = stbtt_fontinfo; +__gshared FT_Library FT_LIB; +alias FontFace = FT_Face; +alias STBFontFace = stbtt_fontinfo; const u32 FONT_MAX_BANDS = 8; const u32 FONT_TEX_WIDTH = 4096; @@ -140,6 +144,19 @@ struct SlugBuffer } struct FontAtlas +{ + Glyph[128] glyphs; + u8[] data; + u32 size; + UVec2 dimensions; + f32 ascent; + f32 descent; + f32 line_gap; + f32 line_height; + f32 max_advance; +} + +struct MSDFFontAtlas { Glyph[128] glyphs; u32 pixel_size; @@ -210,6 +227,225 @@ struct MSDFGlyph GlyphBounds atlas; // Pixels } +shared static this() +{ + FT_Init_FreeType(&FT_LIB); +} + +shared static ~this() +{ + FT_Done_FreeType(FT_LIB); +} + +FontFace +OpenFont(u8[] data) +{ + FontFace font; + FT_New_Memory_Face(FT_LIB, data.ptr, cast(FT_Long)data.length, 0, &font); + return font; +} + +void +CloseFont(FontFace font) +{ + if(font != null) + { + FT_Done_Face(font); + } +} + +i32 +Float26(f32 f) +{ + return cast(i32)round(cast(f32)(1 << 6) * f); +} + +u8 +DeMultiply(u8 color, u8 alpha) +{ + u32 v = cast(u32)(255.0 * cast(f32)(color) / cast(f32)(alpha + f32.epsilon) + 0.5); + return v > 255 ? 255 : cast(u8)v; +} + +u32 +TextSize(f32 s) +{ + return cast(u32)((96.0f/72.0f) * s); +} + +FontAtlas +CreateAtlas(Arena* arena, FontFace font, u32 size, UVec2 atlas_dim) +{ + FontAtlas atlas = { + data: Alloc!(u8)(arena, atlas_dim.x * atlas_dim.y * 4), + size: size, + dimensions: atlas_dim, + }; + + // TODO: proper packing algorithm + if(font != null) + { + FT_Set_Pixel_Sizes(font, 0, TextSize(size)); + + i64 bbox_ymax = font.bbox.yMax >> 6; + i64 bbox_ymin = font.bbox.yMin >> 6; + + u64 baseline = font.size.metrics.ascender >> 6; + u64 line_height = (font.size.metrics.height >> 6)+1; + + atlas.line_height = line_height; + atlas.max_advance = font.size.metrics.max_advance >> 6; + + u32 max_w, max_h, count; + + i32 font_size = Float26(size); + + const u32 PADDING = 2; + foreach(FT_ULong char_code; 0 .. 0x7F) + { + FT_Error res = FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER); + if(res != 0) + { + continue; + } + + u32 bmp_w = font.glyph.bitmap.width; + u32 bmp_h = font.glyph.bitmap.rows; + if(max_w + bmp_w > atlas_dim.x) + { + max_h += line_height+PADDING; + max_w = 0; + } + + if(max_h > atlas_dim.y) + { + Logf("Unable to pack atlas within dimensions provided"); + assert(false); + } + + max_w += bmp_w; + count += 1; + } + + max_w = max_h = count = 0; + + foreach(FT_ULong char_code; 0 .. 0x7F) + { + FT_Error res = FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER); + if(res != 0) + { + continue; + } + + FT_GlyphSlot glyph = font.glyph; + FT_Bitmap* bmp = &font.glyph.bitmap; + + if(max_w + bmp.rows > atlas_dim.x) + { + max_h += line_height+PADDING; + max_w = PADDING; + } + + i64 base = Max(baseline, font.glyph.bitmap_top); + + switch(bmp.pixel_mode) + { + case FT_PIXEL_MODE_BGRA: + { + u64 x, y; + foreach(r; 0 .. bmp.rows) + { + y = (base - font.glyph.bitmap_top + r) + max_h; + foreach(c; 0 .. bmp.width) + { + x = max_w + c; + u64 offset = (y*atlas_dim.y + x) * 4; + + u8 p_b = bmp.buffer[offset+0]; + u8 p_g = bmp.buffer[offset+1]; + u8 p_r = bmp.buffer[offset+2]; + u8 p_a = bmp.buffer[offset+3]; + + atlas.data[offset .. offset+4] = [DeMultiply(p_r, p_a), DeMultiply(p_b, p_a), DeMultiply(p_g, p_a), p_a]; + } + } + } break; + case FT_PIXEL_MODE_MONO: + { + u32 pitch = bmp.pitch; + u8* buf = bmp.buffer; + u64 x, y; + foreach(r; 0 .. bmp.rows) + { + u8 bits = 0; + u8* buf_ptr = buf; + y = (base - font.glyph.bitmap_top + r) + max_h; + foreach(c; 0 .. bmp.width) + { + if((c & 7) == 0) + { + bits = *buf_ptr; + buf_ptr += 1; + } + + x = max_w + c; + u64 offset = (y*atlas_dim.y + x) * 4; + + atlas.data[offset .. offset+4] = [255, 255, 255, bits & 0x80 ? 255 : 0]; + bits <<= 1; + } + + buf += pitch; + } + } break; + case FT_PIXEL_MODE_GRAY: + { + u64 x, y; + foreach(r; 0 .. bmp.rows) + { + y = (base - font.glyph.bitmap_top + r) + max_h; + foreach(c; 0 .. bmp.width) + { + x = max_w + c; + u64 offset = (y*atlas_dim.y + x) * 4; + + atlas.data[offset .. offset+4] = [255, 255, 255, bmp.buffer[r*bmp.pitch + c]]; + } + } + } break; + default: + assert(false, "Unknown pixel_mode value"); + break; + } + + Glyph* g = atlas.glyphs.ptr + char_code; + + f32 height = glyph.metrics.height >> 6; + f32 width = glyph.metrics.width >> 6; + f32 top = font.glyph.bitmap_top; + f32 left = font.glyph.bitmap_left; + + g.ch = cast(dchar)char_code; + g.advance = cast(f32)(glyph.advance.x >> 6); + g.plane.left = left; + g.plane.right = g.plane.left + bmp.width; + g.plane.top = 0.0; + g.plane.bottom = line_height; + + g.atlas.top = max_h; + g.atlas.left = max_w; + g.atlas.bottom = max_h + line_height; + g.atlas.right = max_w + bmp.width; + + max_w += bmp.width + PADDING; + + count += 1; + } + } + + return atlas; +} + static MSDFInfo GenerateMSDF(string file_path)() { @@ -338,31 +574,18 @@ GenerateMSDF(string file_path)() return result; } -FontFace -OpenFont(u8[] data) +STBFontFace +STBOpenFont(u8[] data) { - FontFace font; + STBFontFace font; stbtt_InitFont(&font, data.ptr, stbtt_GetFontOffsetForIndex(data.ptr, 0)); return font; } -u8 -DeMultiply(u8 color, u8 alpha) +MSDFFontAtlas +PopulateAtlasGlyphInfo(STBFontFace* font, u32 pixel_size, const MSDFInfo* msdf_info) { - u32 v = cast(u32)(255.0 * cast(f32)(color) / cast(f32)(alpha + f32.epsilon) + 0.5); - return v > 255 ? 255 : cast(u8)v; -} - -u32 -TextSize(f32 s) -{ - return cast(u32)((96.0f/72.0f) * s); -} - -FontAtlas -PopulateAtlasGlyphInfo(FontFace* font, u32 pixel_size, const MSDFInfo* msdf_info) -{ - FontAtlas atlas; + MSDFFontAtlas atlas; // TODO: proper packing algorithm if(font != null)