dlib/fonts.d
2025-12-26 17:19:23 +11:00

268 lines
5.2 KiB
D

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;
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)
{
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)
{
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);
Logf("%f %f %f", font.height, font.ascender, font.descender);
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;
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 = 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;
}
unittest
{
}