module dlib.fonts; import dlib.aliases; import dlib.includes; import dlib.util; import dlib.alloc; enum AtlasType { None = 0, SoftMask, } enum YOrigin { None = 0, Bottom, } struct FontAtlas { AtlasType type; f32 size; u32 width; u32 height; YOrigin y_origin; f32 em_size; f32 line_height; f32 ascender; f32 descender; f32 underline_y; f32 underline_thickness; Glyph[128] glyphs; } struct Glyph { dchar ch; f32 advance; f32 plane_left; f32 plane_bottom; f32 plane_right; f32 plane_top; f32 atlas_left; f32 atlas_bottom; f32 atlas_right; f32 atlas_top; } FT_Library FT_LIB; alias FontFace = FT_Face; struct FontAtlasBuf { u8[] data; FontAtlas atlas; } void InitFreeType() { FT_Init_FreeType(&FT_LIB); } void CloseFreeType() { if (FT_LIB) { 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); } } FontAtlasBuf CreateAtlas(Arena* arena, FontFace font, f32 size, u32 dimension) { assert(dimension >= 128, "Dimension must be at least 128"); FontAtlasBuf atlas = { data: AllocArray!(u8)(arena, dimension * dimension * 4), atlas: { size: size, width: dimension, height: dimension, }, }; // TODO: proper packing algorithm if (font != null) { FT_Set_Pixel_Sizes(font, 0, cast(FT_UInt)((96.0/72.0) * size)); i64 f_ascent = cast(i64)(font.size.metrics.ascender >> 6); i64 f_descent = cast(i64)(font.size.metrics.descender >> 6); i64 f_height = cast(i64)(font.size.metrics.height >> 6); u32 max_w = 0; u32 max_h = 0; u32 current_h = 0; u32 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; } u32 bmp_w = font.glyph.bitmap.width; u32 bmp_h = font.glyph.bitmap.rows; if (max_w + bmp_w > dimension) { max_h += current_h; max_w = 0; } assert(max_h < dimension, "Unable to pack atlas within dimensions"); max_w += bmp_w; current_h = bmp_h > current_h ? bmp_h : current_h; count += 1; } max_w = 0; max_h = 0; current_h = 0; count = 0; u32 font_w = font.size.metrics.x_ppem; u32 font_h = font.size.metrics.y_ppem; 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; i32 top = font.glyph.bitmap_top; i32 left = font.glyph.bitmap_left; if (max_w + bmp.rows > dimension) { max_h += current_h; max_w = 0; } i32 x, y; foreach(r; 0 .. bmp.rows) { y = cast(i32)(max_h + r); foreach(c; 0 .. bmp.width) { x = max_w + c; u64 offset = (y*dimension + x) * 4; atlas.data[offset+0] = bmp.buffer[r*bmp.pitch + c]; atlas.data[offset+1] = bmp.buffer[r*bmp.pitch + c]; atlas.data[offset+2] = bmp.buffer[r*bmp.pitch + c]; atlas.data[offset+3] = 255; } } Glyph* g = atlas.atlas.glyphs.ptr + char_code; g.ch = cast(dchar)char_code; g.advance = cast(f32)(glyph.advance.x >> 6); g.plane_left = cast(f32)left; g.plane_right = g.plane_left + bmp.width; g.plane_top = cast(f32)top; g.plane_bottom = g.plane_top + bmp.rows; g.atlas_top = max_h; g.atlas_left = max_w; g.atlas_bottom = max_h + bmp.rows; g.atlas_right = max_w + bmp.width; max_w += bmp.width; current_h = bmp.rows > current_h ? bmp.rows : current_h; count += 1; } } return atlas; }