module dlib.fonts; import dlibincludes; import dlib.aliases; import dlib.util; import dlib.alloc; import dlib.math; import std.math.traits : isNaN; import std.math : Floor = floor; import std.algorithm.comparison : clamp; import std.algorithm.sorting; const u64 FONT_MAX_BANDS = 8; const u64 FONT_TEX_WIDTH = 4096; struct SlugCurveRef { u32 index; f32 sort_key; } struct SlugBandEntry { u32[] indices; u32 count; } struct SlugGlyphBandData { SlugBandEntry[FONT_MAX_BANDS][2] bands; U8Vec2 band_counts; } struct SlugCurve { Vec2 p1, p2, p3; } struct SlugRect { Vec2 min, max; } struct SlugGlyph { SlugRect rect; IVec2 bearing; IVec2 size; Vec2 band_scale; Vec2 band_offset; i32 glyph_index; i32 advance; i32 curve_start; i32 curve_count; U16Vec2 coords; U8Vec2 max_bands; } struct SlugFontInfo { SlugCurve[] curves; f32[] curve_texel_data; u32[] band_texel_data; SlugGlyphBandData[] glyph_bands; SlugFont base; alias base this; } struct SlugFont { SlugGlyph[128] glyphs; f32 scale; f32 line_height; i32 ascent; i32 descent; i32 line_gap; } struct SlugVertex { union { Vec4 pos; struct { Vec2 vertex_coords; Vec2 normal; }; }; union { Vec4 tex; struct { Vec2 em_space_pos; U16Vec2 glyph_data_coords; struct max_bands { u8 x, pad, y, e; }; } }; union { Vec4 jac; struct { f32 p00, p01, p10, p11; }; }; union { Vec4 band; struct { Vec4 scale; Vec4 offset; }; }; Vec4 color; } 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 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; 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; } 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; } void BuildFontGlyph(Arena* arena, u32 index, u32 glyph_index, SlugFontInfo* font_info, u32* curve_index, stbtt_fontinfo* stb_font_info) { SlugGlyph* glyph = &font_info.glyphs[index]; glyph.glyph_index = glyph_index; glyph.curve_start = *curve_index; stbtt_vertex* vertices; u32 vertex_count = stbtt_GetGlyphShape(stb_font_info, glyph_index, &vertices); if(vertex_count > 0) { f32 cx = 0.0, cy = 0.0; foreach(i; 0 .. vertex_count) { stbtt_vertex* vtx = &vertices[i]; f32 x = cast(f32)(vtx.x); f32 y = cast(f32)(vtx.y); switch(vtx.type) { case STBTT_vmove: { cx = x; cy = y; } break; case STBTT_vline: { f32 dx = x-cx; f32 dy = y-cy; if(fabs(dx) < 0.1 && fabs(dy) < 0.1) { cx = x; cy = y; break; } f32 length = sqrt(dx*dx + dy*dy); f32 nx = -dy/length*0.05; f32 ny = -dx/length*0.05; SlugCurve* curve = &font_info.curves[*curve_index]; *curve = SlugCurve( Vec2(cx, cy), Vec2((cx+x) * 0.5 + nx, (cy+y) * 0.5 + ny), Vec2(x, y), ); cx = x; cy = y; *curve_index += 1; } break; case STBTT_vcurve: { SlugCurve* curve = &font_info.curves[*curve_index]; *curve = SlugCurve( Vec2(cx, cy), Vec2(vtx.cx, vtx.cy), Vec2(x, y), ); *curve_index += 1; } break; case STBTT_vcubic: { f32 cx1 = cast(f32)(vtx.cx ); f32 cy1 = cast(f32)(vtx.cy ); f32 cx2 = cast(f32)(vtx.cx1); f32 cy2 = cast(f32)(vtx.cy1); Vec2 m01 = Vec2(cx+cx1, cy+cy1 ) * 0.5; Vec2 m12 = Vec2(cx1+cx2, cy1+cy2) * 0.5; Vec2 m23 = Vec2(cx2+x, cy2+y ) * 0.5; Vec2 m012 = (m01+m12) * 0.5; Vec2 m123 = (m12+m23) * 0.5; Vec2 mid = (m012+m123) * 0.5; font_info.curves[(*curve_index)+0] = SlugCurve( Vec2(cx, cy), m01, mid ); font_info.curves[(*curve_index)+1] = SlugCurve( mid, m123, Vec2(x, y) ); cx = x; cy = y; *curve_index += 2; } break; default: break; } } stbtt_FreeShape(stb_font_info, vertices); } glyph.curve_count = *curve_index - glyph.curve_start; i32 advance_width, left_side_bearing; stbtt_GetGlyphHMetrics(stb_font_info, glyph_index, &advance_width, &left_side_bearing); glyph.advance = advance_width; IVec2 p0, p1; if(!stbtt_GetGlyphBox(stb_font_info, glyph_index, &p0.x, &p0.y, &p1.x, &p1.y)) { p0 = p1 = 0; } glyph.bearing = p0; glyph.size = p1-p0; glyph.rect.min = Vec2(p0.x, p0.y); glyph.rect.max = Vec2(p1.x, p1.y); glyph.coords = 0; glyph.max_bands = 0; glyph.band_scale = 0; glyph.band_offset = 0; SlugBuildBands(arena, glyph_index, font_info, FONT_MAX_BANDS); } bool LoadFontCurves(Arena* arena, SlugFontInfo* font_info, u8[] font_file_data) { bool result = true; u32 curve_count; stbtt_fontinfo stb_font_info; if(!stbtt_InitFont(&stb_font_info, font_file_data.ptr, stbtt_GetFontOffsetForIndex(font_file_data.ptr, 0))) { Errf("Failed to load font data"); result = false; } if(result) { foreach(i; 0 .. font_info.glyphs.length) { curve_count += SlugCountCurvesForGlyph(stbtt_FindGlyphIndex(&stb_font_info, cast(i32)i), &stb_font_info); } font_info.curves = Alloc!(SlugCurve)(arena, curve_count+1); font_info.glyph_bands = Alloc!(SlugGlyphBandData)(arena, font_info.glyphs.length); f32 font_scale = stbtt_ScaleForPixelHeight(&stb_font_info, 200.0); stbtt_GetFontVMetrics(&stb_font_info, &font_info.ascent, &font_info.descent, &font_info.line_gap); font_info.scale = font_scale; font_info.line_height = cast(f32)(font_info.ascent-font_info.descent); u32 curve_index = 1; u32 curve_texel_count; foreach(i; 0 .. font_info.glyphs.length) { BuildFontGlyph(arena, cast(u32)i, stbtt_FindGlyphIndex(&stb_font_info, cast(int)i), font_info, &curve_index, &stb_font_info); curve_texel_count += font_info.glyphs[i].curve_count*2; } u32 curve_texel_height = clamp(cast(u32)((curve_texel_count+FONT_TEX_WIDTH-1) / FONT_TEX_WIDTH), 1, -1); f32[] curve_texel_data = Alloc!(f32)(arena, FONT_TEX_WIDTH*curve_texel_height*4); u32[] curve_start_indices = Alloc!(u32)(arena, font_info.glyphs.length); curve_index = 1; foreach(i; 0 .. font_info.glyphs.length) { SlugGlyph* glyph = &font_info.glyphs[i]; curve_start_indices[i] = curve_index; SlugCurve[] glyph_curves = font_info.curves[glyph.curve_start .. glyph.curve_start+glyph.curve_count]; foreach(j; 0 .. glyph.curve_count) { SlugCurve* curve = &glyph_curves[j]; UVec2 t0 = UVec2(curve_index%FONT_TEX_WIDTH, curve_index/FONT_TEX_WIDTH); u32 offset = cast(u32)((t0.y*FONT_TEX_WIDTH + t0.x) * 4); curve_texel_data[offset .. offset+4] = [curve.p1.x, curve.p1.y, curve.p2.x, curve.p2.y]; curve_index += 1; UVec2 t1 = UVec2(curve_index%FONT_TEX_WIDTH, curve_index/FONT_TEX_WIDTH); offset = cast(u32)((t1.y*FONT_TEX_WIDTH + t1.x) * 4); curve_texel_data[offset .. offset+4] = [curve.p3.x, curve.p3.y, 0.0, 0.0]; curve_index += 1; } } u32 band_total_count; foreach(i; 0 .. font_info.glyphs.length) { SlugGlyph* glyph = &font_info.glyphs[i]; SlugGlyphBandData* band_data = &font_info.glyph_bands[i]; u32 band_total = band_data.band_counts[0] + band_data.band_counts[1]; u32 padding = cast(u32)(FONT_TEX_WIDTH - (band_total_count%FONT_TEX_WIDTH)); if(padding < band_total && padding < FONT_TEX_WIDTH) { band_total_count += padding; } band_total_count += band_total; foreach(axis; 0 .. 2) { foreach(j; 0 .. band_data.band_counts[axis]) { band_total_count += band_data.bands[axis][j].count; } } } u32 band_texel_height = clamp(cast(u32)((band_total_count+FONT_TEX_WIDTH-1) / FONT_TEX_WIDTH), 1U, u32.max); u32[] band_texel_data = Alloc!(u32)(arena, FONT_TEX_WIDTH*band_texel_height*4); u32 band_index; foreach(i; 0 .. font_info.glyphs.length) { SlugGlyph* glyph = &font_info.glyphs[i]; SlugGlyphBandData* band_data = &font_info.glyph_bands[i]; u32 glyph_curve_start = curve_start_indices[i]; u32 band_total = cast(u32)(band_data.band_counts.x + band_data.band_counts.y); u32 next_band_index = band_index%FONT_TEX_WIDTH; if(next_band_index+band_total > FONT_TEX_WIDTH) { band_index = cast(u32)((band_index/FONT_TEX_WIDTH+1) * FONT_TEX_WIDTH); } glyph.coords = U16Vec2(band_index%FONT_TEX_WIDTH, band_index/FONT_TEX_WIDTH); u32 glyph_start = band_index; SlugCurve[] curves = font_info.curves[glyph.curve_start .. glyph.curve_start+glyph.curve_count]; foreach(axis; 0 .. 2) { foreach(j; 0 .. band_data.band_counts[axis]) { SlugSortBand(arena, &band_data.bands[axis][j], curves, axis); } } u32 band_offset = band_total; u32[] offsets = Alloc!(u32)(arena, band_total); foreach(j; 0 .. band_data.band_counts.x) { offsets[j] = band_offset; band_offset += band_data.bands[0][j].count; } foreach(j; 0 .. band_data.band_counts.y) { offsets[band_data.band_counts.x+j] = band_offset; band_offset += band_data.bands[1][j].count; } foreach(j; 0 .. band_total) { u32 tl = glyph_start+j; u32 di = cast(u32)((tl/FONT_TEX_WIDTH*FONT_TEX_WIDTH + tl%FONT_TEX_WIDTH) * 4); u32 index = j; u32 count = 0; if(index < cast(u32)band_data.band_counts.x) { count = band_data.bands[0][index].count; } else { index -= band_data.band_counts.x; count = band_data.bands[1][index].count; } band_texel_data[di .. di+2] = [count, offsets[j]]; } foreach(j; 0 .. band_total) { u32 axis = j < band_data.band_counts.x ? 0 : 1; u32 index = j < band_data.band_counts.x ? j : j - band_data.band_counts.x; SlugBandEntry* band = &band_data.bands[axis][index]; u32 ls = glyph_start+offsets[j]; foreach(k; 0 .. band.count) { u32 ct = curve_start_indices[i] + band.indices[k]*2; u32 tl = ls+k; u32 di = cast(u32)((tl/FONT_TEX_WIDTH*FONT_TEX_WIDTH + tl%FONT_TEX_WIDTH) * 4); band_texel_data[di .. di+2] = [ct%FONT_TEX_WIDTH, ct/FONT_TEX_WIDTH]; } } band_index = glyph_start+band_offset; } } return result; } void SlugSortBand(Arena* arena, SlugBandEntry* entry, SlugCurve[] curves, u32 axis) { if(entry.count <= 1) return; SlugCurveRef[] refs = Alloc!(SlugCurveRef)(arena, entry.count); foreach(i; 0 .. entry.count) { SlugCurve* curve = &curves[entry.indices[i]]; f32 mx = Max(curve.p1[axis], curve.p2[axis], curve.p3[axis]); refs[i] = SlugCurveRef(entry.indices[i], mx); } sort!("a.sort_key < b.sort_key")(refs); foreach(i; 0 .. entry.count) { entry.indices[i] = refs[i].index; } } u32 SlugCountCurvesForGlyph(u32 glyph_index, stbtt_fontinfo* stb_font_info) { u32 result; stbtt_vertex* vertices; u32 vertex_count = stbtt_GetGlyphShape(stb_font_info, glyph_index, &vertices); foreach(i; 0 .. vertex_count) { switch(vertices[i].type) { case STBTT_vline, STBTT_vcurve: result += 1; break; case STBTT_vcubic: result += 2; break; default: break; } } if(vertex_count > 0) { stbtt_FreeShape(stb_font_info, vertices); } return result; } void SlugBuildBands(Arena* arena, u32 glyph_index, SlugFontInfo* font_info, u32 band_count) { SlugGlyphBandData* band_data = &font_info.glyph_bands[glyph_index]; SlugGlyph* glyph = &font_info.glyphs[glyph_index]; Vec2 size = glyph.rect.max - glyph.rect.min; band_data.band_counts = cast(u8)band_count; for(u32 i = 0; i < band_count; i += 1) { band_data.bands[0][i].indices = Alloc!(u32)(arena, glyph.curve_count); band_data.bands[1][i].indices = Alloc!(u32)(arena, glyph.curve_count); } glyph.max_bands = cast(u8)band_count; glyph.band_scale = Vec2( size.x > 0.0 ? cast(f32)(band_count)/size.x : 0.0, size.y > 0.0 ? cast(f32)(band_count)/size.y : 0.0, ); glyph.band_offset = -glyph.rect.min * glyph.band_scale; for(u32 i = 0; i < glyph.curve_count; i += 1) { SlugCurve* curve = &font_info.curves[glyph.curve_start+i]; Vec2 curve_min = Vec2( Min(curve.p1.x, curve.p2.x, curve.p3.x), Min(curve.p1.y, curve.p2.y, curve.p3.y), ); Vec2 curve_max = Vec2( Max(curve.p1.x, curve.p2.x, curve.p3.x), Min(curve.p1.y, curve.p2.y, curve.p3.y), ); if(size.y > 0.0) { u32 b0 = cast(u32)clamp(floor(curve_min.y * glyph.band_scale.y + glyph.band_offset.y), 0.0, cast(f32)(band_count-1)); u32 b1 = cast(u32)clamp(floor(curve_max.y * glyph.band_scale.y + glyph.band_offset.y), 0.0, cast(f32)(band_count-1)); for(u32 j = b0; j <= b1; j += 1) { SlugBandEntry* entry = &band_data.bands[0][j]; entry.indices[entry.count++] = i; } } if(size.x > 0.0) { u32 b0 = cast(u32)clamp(Floor(curve_min.x * glyph.band_scale.x + glyph.band_offset.x), 0.0, cast(f32)(band_count-1)); u32 b1 = cast(u32)clamp(Floor(curve_max.x * glyph.band_scale.x + glyph.band_offset.x), 0.0, cast(f32)(band_count-1)); for(u32 j = b0; j <= b1; j += 1) { SlugBandEntry* entry = &band_data.bands[1][j]; entry.indices[entry.count++] = i; } } } }