821 lines
18 KiB
D
821 lines
18 KiB
D
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 SlugTexel(T)
|
|
{
|
|
T[] data;
|
|
u32 height;
|
|
u32 width;
|
|
}
|
|
|
|
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;
|
|
SlugTexel!(f32) curve_texel;
|
|
SlugTexel!(u32) band_texel;
|
|
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, 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, u32.max);
|
|
|
|
u32[] curve_start_indices = Alloc!(u32)(arena, font_info.glyphs.length);
|
|
SlugTexel!(f32) curve_texel = {
|
|
data: Alloc!(f32)(arena, FONT_TEX_WIDTH*curve_texel_height*4),
|
|
height: curve_texel_height,
|
|
width: FONT_TEX_WIDTH,
|
|
};
|
|
|
|
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);
|
|
SlugTexel!(u32) band_texel = {
|
|
data: Alloc!(u32)(arena, FONT_TEX_WIDTH*band_texel_height*4),
|
|
width: FONT_TEX_WIDTH,
|
|
height: band_texel_height,
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
font_info.curve_texel = curve_texel;
|
|
font_info.band_texel = band_texel;
|
|
}
|
|
|
|
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 index, SlugFontInfo* font_info, u32 band_count)
|
|
{
|
|
SlugGlyphBandData* band_data = &font_info.glyph_bands[index];
|
|
SlugGlyph* glyph = &font_info.glyphs[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),
|
|
Max(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;
|
|
}
|
|
}
|
|
}
|
|
}
|