module dlib.fonts; import dlibincludes; import dlib.aliases; import dlib.util; import dlib.alloc; struct FontAtlas { f32 size; f32 units_per_em; f32 ascent; f32 descent; f32 line_gap; f32 max_advance; f32 line_height; u32 width; u32 height; 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; } __gshared FT_Library FT_LIB; alias FontFace = FT_Face; struct FontAtlasBuf { u8[] data; FontAtlas atlas; } shared static this() { 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); } } 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; } FontAtlasBuf CreateAtlas(Arena* arena, FontFace font, f32 size, u32 dimension, bool y_origin_top = true) { assert(dimension >= 128, "Dimension must be at least 128"); FontAtlasBuf abuf = { data: Alloc!(u8)(arena, dimension * dimension * 4), atlas: { size: size, width: dimension, height: dimension, }, }; // TODO: proper packing algorithm if(font != null) { i64 bbox_ymax = font.bbox.yMax >> 6; i64 bbox_ymin = font.bbox.yMin >> 6; abuf.atlas.line_height = cast(f32)(bbox_ymax - bbox_ymin); abuf.atlas.units_per_em = cast(f32)font.units_per_EM; abuf.atlas.ascent = cast(f32)font.ascender; abuf.atlas.descent = cast(f32)font.descender; abuf.atlas.line_gap = cast(f32)(font.height - font.ascender + font.descender); abuf.atlas.max_advance = cast(f32)(font.max_advance_width>>6); u32 max_w, max_h, current_h, count; i32 font_size = Float26(size); foreach(FT_ULong char_code; 0 .. 0x7F) { FT_Set_Char_Size(font, font_size, 0, 0, 0); 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; } const u32 PADDING = 2; max_w = PADDING; max_h = PADDING; current_h = 0; count = 0; foreach(FT_ULong char_code; 0 .. 0x7F) { FT_Set_Char_Size(font, font_size, 0, 0, 0); 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 > dimension) { max_h += cast(u32)(size) + PADDING; max_w = PADDING; } switch(bmp.pixel_mode) { case FT_PIXEL_MODE_BGRA: { 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; 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]; abuf.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; i32 x, y; foreach(r; 0 .. bmp.rows) { u8 bits = 0; u8* buf_ptr = buf; y = cast(i32)(max_h + r); foreach(c; 0 .. bmp.width) { if((c & 7) == 0) { bits = *buf_ptr; buf_ptr += 1; } x = max_w + c; u64 offset = (y*dimension + x) * 4; abuf.data[offset .. offset+4] = [255, 255, 255, bits & 0x80 ? 255 : 0]; bits <<= 1; } buf += pitch; } } break; case FT_PIXEL_MODE_GRAY: { 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; abuf.data[offset .. offset+4] = [255, 255, 255, bmp.buffer[r*bmp.pitch + c]]; } } } break; default: assert(false, "Unknown pixel_mode value"); break; } Glyph* g = abuf.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; assert(y_origin_top, "only y_origin_top is currently supported"); g.ch = cast(dchar)char_code; g.advance = cast(f32)(glyph.advance.x >> 6); g.plane_left = left; g.plane_right = g.plane_left + width; g.plane_top = cast(f32)(bbox_ymax) - top; g.plane_bottom = g.plane_top + height; 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 + PADDING; current_h = bmp.rows > current_h ? bmp.rows : current_h; count += 1; } } return abuf; }