287 lines
5.3 KiB
D
287 lines
5.3 KiB
D
module dlib.fonts;
|
|
|
|
import dlibincludes;
|
|
import dlib.aliases;
|
|
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;
|
|
}
|
|
|
|
__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 atlas = {
|
|
data: Alloc!(u8)(arena, dimension * dimension * 4),
|
|
atlas: {
|
|
size: size,
|
|
width: dimension,
|
|
height: dimension,
|
|
},
|
|
};
|
|
|
|
// TODO: proper packing algorithm
|
|
if(font != null)
|
|
{
|
|
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;
|
|
|
|
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;
|
|
i32 top = font.glyph.bitmap_top;
|
|
i32 left = font.glyph.bitmap_left;
|
|
|
|
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];
|
|
|
|
atlas.data[offset+0] = DeMultiply(p_r, p_a);
|
|
atlas.data[offset+1] = DeMultiply(p_b, p_a);
|
|
atlas.data[offset+2] = DeMultiply(p_g, p_a);
|
|
atlas.data[offset+3] = 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;
|
|
|
|
atlas.data[offset+0] = 255;
|
|
atlas.data[offset+1] = 255;
|
|
atlas.data[offset+2] = 255;
|
|
atlas.data[offset+3] = (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;
|
|
|
|
atlas.data[offset+0] = 255;
|
|
atlas.data[offset+1] = 255;
|
|
atlas.data[offset+2] = 255;
|
|
atlas.data[offset+3] = bmp.buffer[r*bmp.pitch + c];
|
|
}
|
|
}
|
|
} break;
|
|
default:
|
|
assert(false, "Unknown pixel_mode value");
|
|
break;
|
|
}
|
|
|
|
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 + PADDING;
|
|
current_h = bmp.rows > current_h ? bmp.rows : current_h;
|
|
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
return atlas;
|
|
}
|
|
|
|
|