module dlib.fonts;

import dlib.externdecl;
import dlib.aliases;
import dlib.util;
import dlib.alloc;
import dlib.math;

import core.math : fabs;

import std.traits;

import stb_truetype;

alias FontFace = stbtt_fontinfo;

const u32 FONT_MAX_BANDS = 8;
const u32 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
			{
				u8 max_bands_x, pad, max_bands_y, max_bands_e;
			};
		}
	};
	union
	{
		Vec4 jac;
		struct
		{
			f32 p00, p01, p10, p11;
		};
	};
	union
	{
		Vec4 band;
		struct
		{
			Vec2 band_scale;
			Vec2 band_offset;
		};
	};
	Vec4 color;
}

struct SlugBuffer
{
	SlugVertex[] vertices;
	u32[]        indices;
	u32          quad_offset;
}

struct FontAtlas
{
	Glyph[128]  glyphs;
	u32         pixel_size;
	f32         baseline    = 0.0; // All unscaled
	f32         ascent      = 0.0; 	
	f32         descent     = 0.0;
	f32         line_gap    = 0.0;
	f32         line_height = 0.0;
	f32         max_advance = 0.0;
}

struct GlyphBounds
{
	f32 left = 0.0, bottom = 0.0, right = 0.0, top = 0.0;
}

struct Glyph
{
	dchar       ch;
	f32		      advance;
	GlyphBounds plane;
	GlyphBounds atlas;
}

enum MSDFType
{
	None,
	HardMask,
	SoftMask,
	SDF,
	PSDF,
	MSDF,
	MTSDF,
}

enum MSDFYOrigin
{
	None,
	Left,
	Bottom,
	Right,
	Top,
}

struct MSDFInfo
{
	MSDFGlyph[] glyphs;
	MSDFType    type;
	Vec2        aem_range;
	u32         distance_field_range;
	u32         distance_field_range_middle;
	f32         pixels_per_em;
	u32         texture_width;
	u32         texture_height;
	MSDFYOrigin y_origin;
	f32         line_height;         // EM
	f32         ascender;            // EM
	f32         descender;           // EM
	f32         underline_y;         // EM
	f32         underline_thickness; // EM
}

struct MSDFGlyph
{
	dchar       glyph;
	f32         advance = 0.0;
	GlyphBounds plane; // EM
	GlyphBounds atlas; // Pixels
}

static MSDFInfo
GenerateMSDF(string file_path)()
{
	import std.json;

	string msdf_json = import(file_path);
	
	JSONValue jsonv = parseJSON(msdf_json);

	static T GetJSONValue(T)(JSONValue jsonv, string name)
	{
		static if(is(T == string))
		{
			T result = null;
		}
		else
		{
			T result = cast(T)0;
		}

		if(const(JSONValue)* value = name in jsonv)
		{
			switch(value.type())
			{
				static if(is(T == u32) || is(T == u64) || is(T == f32))
				{
					case JSONType.float_:   result = cast(T)value.floating; break;
					case JSONType.uinteger: result = cast(T)value.uinteger; break;
					case JSONType.integer:  result = cast(T)value.integer;  break;
				}
				static if(is(T == string))
				{
					case JSONType.string: result = cast(T)value.str; break;
				}
				default: assert(false, "Unable to retrieve value");
			}
		}

		return result;
	}

	MSDFInfo result = {
		distance_field_range:        GetJSONValue!(u32)(jsonv["atlas"],   "distanceRange"),
		distance_field_range_middle: GetJSONValue!(u32)(jsonv["atlas"],   "distanceRangeMiddle"),
		pixels_per_em:               GetJSONValue!(f32)(jsonv["atlas"],   "size"),
		texture_width:               GetJSONValue!(u32)(jsonv["atlas"],   "width"),
		texture_height:              GetJSONValue!(u32)(jsonv["atlas"],   "height"),
		line_height:                 GetJSONValue!(f32)(jsonv["metrics"], "lineHeight"),
		ascender:                    GetJSONValue!(f32)(jsonv["metrics"], "ascender"),
		descender:                   GetJSONValue!(f32)(jsonv["metrics"], "descender"),
		underline_y:                 GetJSONValue!(f32)(jsonv["metrics"], "underlineY"),
		underline_thickness:         GetJSONValue!(f32)(jsonv["metrics"], "underlineThickness"),
	};

	result.aem_range = Vec2(
		(result.distance_field_range_middle - result.distance_field_range/2) / result.pixels_per_em,
		(result.distance_field_range_middle + result.distance_field_range/2) / result.pixels_per_em
	);

	string type_string     = GetJSONValue!(string)(jsonv["atlas"], "type");
	string y_origin_string = GetJSONValue!(string)(jsonv["atlas"], "yOrigin");

	assert(type_string.length);
	assert(y_origin_string.length);

	final switch(type_string) with(MSDFType)
	{
		case "hardmask": result.type = HardMask; break;
		case "softmask": result.type = SoftMask; break;
		case "sdf":      result.type = SDF;      break;
		case "psdf":     result.type = PSDF;     break;
		case "msdf":     result.type = MSDF;     break;
		case "mtsdf":    result.type = MTSDF;    break;	 
	}

	final switch(y_origin_string) with(MSDFYOrigin)
	{
		case "top":    result.y_origin = Top;    break;
		case "left":   result.y_origin = Left;   break;
		case "right":  result.y_origin = Right;  break;
		case "bottom": result.y_origin = Bottom; break;
	}

	MSDFGlyph[] glyphs = new MSDFGlyph[128];

	foreach(i; 0 .. glyphs.length)
	{
		glyphs[i].glyph = cast(dchar)i;
	}

	JSONValue[] glyph_json = jsonv["glyphs"].array;

	foreach(i; 0 .. glyph_json.length)
	{
		u64 ch          = cast(u64)glyph_json[i]["unicode"].integer;
		MSDFGlyph glyph = { glyph: cast(dchar)ch };
		glyph.advance = GetJSONValue!(f32)(glyph_json[i], "advance");
		if("planeBounds" in glyph_json[i])
		{
			glyph.plane.left   = GetJSONValue!(f32)(glyph_json[i]["planeBounds"], "left");
			glyph.plane.right  = GetJSONValue!(f32)(glyph_json[i]["planeBounds"], "right");
			glyph.plane.bottom = GetJSONValue!(f32)(glyph_json[i]["planeBounds"], "bottom");
			glyph.plane.top    = GetJSONValue!(f32)(glyph_json[i]["planeBounds"], "top");

			glyph.atlas.left   = GetJSONValue!(f32)(glyph_json[i]["atlasBounds"], "left");
			glyph.atlas.right  = GetJSONValue!(f32)(glyph_json[i]["atlasBounds"], "right");
			glyph.atlas.bottom = GetJSONValue!(f32)(glyph_json[i]["atlasBounds"], "bottom");
			glyph.atlas.top    = GetJSONValue!(f32)(glyph_json[i]["atlasBounds"], "top");
		}

		glyphs[ch] = glyph;
	}

	foreach(i; 0 .. glyphs.length)
	{
		if(glyphs[i].glyph == 0xFFFF)
		{
			glyphs[i].glyph = 0;
			glyphs[i].plane = GlyphBounds(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0);
			glyphs[i].atlas = GlyphBounds(left: 0.0, right: 0.0, top: 0.0, bottom: 0.0);
		}
	}

	result.glyphs = glyphs;

	return result;
}

FontFace
OpenFont(u8[] data)
{
	FontFace font;
	stbtt_InitFont(&font, data.ptr, stbtt_GetFontOffsetForIndex(data.ptr, 0));
	return font;
}

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
PopulateAtlasGlyphInfo(FontFace* font, u32 pixel_size, const MSDFInfo* msdf_info)
{
	FontAtlas atlas;

	// TODO: proper packing algorithm
	if(font != null)
	{
		f32 scale = stbtt_ScaleForPixelHeight(font, cast(f32)pixel_size);

		i32 ascent, descent, line_gap;
		stbtt_GetFontVMetrics(font, &ascent, &descent, &line_gap);

		i32 max_advance;
		foreach(i32 ch; 0 .. 128)
		{
			i32 glyph_index = stbtt_FindGlyphIndex(font, ch);
			if(glyph_index)
			{
				i32 advance, x0, y0, x1, y1;
				stbtt_GetGlyphHMetrics(font, glyph_index, &advance, null);
				stbtt_GetGlyphBox(font, glyph_index, &x0, &y0, &x1, &y1);

				max_advance = max_advance < advance ? advance : max_advance;

				MSDFGlyph* msdfg;
				foreach(j; 0 .. msdf_info.glyphs.length)
				{
					if(msdf_info.glyphs[j].glyph == ch)
					{
						msdfg = cast(MSDFGlyph*)msdf_info.glyphs.ptr + j;
						break;
					}	
				}

				Glyph* g = atlas.glyphs.ptr + ch;

				g.ch           = ch;
				g.advance      = cast(f32)advance * scale;
				g.atlas        = msdfg.atlas;
				g.plane.left   = cast(f32)x0 * scale;
				g.plane.top    = cast(f32)y0 * scale;
				g.plane.right  = cast(f32)x1 * scale;
				g.plane.bottom = cast(f32)y1 * scale;
			}
		}

		i32 x0, y0, x1, y1;
		stbtt_GetFontBoundingBox(font, &x0, &y0, &x1, &y1);

		atlas.pixel_size  = pixel_size;
		atlas.ascent      = cast(f32)ascent  * scale;
		atlas.descent     = cast(f32)descent * scale;
		atlas.baseline    = cast(f32)-y0 * scale;
		atlas.line_height = cast(f32)(y1 - y0) * scale; // Maybe add line gap?
		atlas.line_gap    = cast(f32)line_gap * scale;
		atlas.max_advance = cast(f32)max_advance * scale;
	}

	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) with(stbtt_curvetype)
			{
				case vmove:
					{
						cx = x;
						cy = y;
					} break;
				case 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 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 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);

				Assign(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);

				Assign(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;
				}

				Assign(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);

					Assign(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);
	}

	assert(false, "implement sorting!");
	// 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) with(stbtt_curvetype)
		{
			case vline, vcurve: result += 1; break;
			case 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;
			}
		}
	}
}

void
SlugLoadFontVertex(u8 ch, IVec2 pos, SlugFont* font, SlugBuffer* buffer)
{
	ch = ch > 127 ? 0 : ch;

	SlugGlyph* glyph = &font.glyphs[ch];

	if(ch == ' ')
	{
		pos.x += cast(i32)(cast(f32)(glyph.advance) * font.scale);
	}
	else if(glyph.curve_count > 0)
	{
		Vec2 glyph_pos = Vec2(pos.x, pos.y);

		Vec2 p0 = glyph_pos + glyph.rect.min * font.scale;
		Vec2 p1 = glyph_pos + glyph.rect.max * font.scale;

		u32 vertex_start = buffer.quad_offset*4;
		u32 index_start  = buffer.quad_offset*6;

		SlugVertex[] vertices = buffer.vertices[vertex_start .. vertex_start+4];
		u32[]        indices  = buffer.indices[index_start   .. index_start+6];

		const f32 NORMAL = 1.0;

		f32 inv_scale = 1.0f/font.scale;

		foreach(i; 0 .. 4)
		{
			vertices[i].band_scale        = glyph.band_scale;
			vertices[i].band_offset       = glyph.band_offset;
			vertices[i].glyph_data_coords = glyph.coords;
			vertices[i].max_bands_x       = glyph.max_bands.x;
			vertices[i].max_bands_y       = glyph.max_bands.y;
			vertices[i].jac               = Vec4(inv_scale, 0.0, 0.0, inv_scale);

			vertices[i].color = 1.0;
		}

		vertices[0].vertex_coords = p0;
		vertices[0].normal        = Vec2(-NORMAL, +NORMAL);
		vertices[0].em_space_pos  = Vec2(glyph.rect.min.x, glyph.rect.max.y);

		vertices[1].vertex_coords = Vec2(p0.x, p1.y);
		vertices[1].normal        = -NORMAL;
		vertices[1].em_space_pos  = glyph.rect.min;

		vertices[2].vertex_coords = Vec2(p1.x, p0.y);
		vertices[2].normal        = +NORMAL;
		vertices[2].em_space_pos  = glyph.rect.max;

		vertices[3].vertex_coords = p1;
		vertices[3].normal        = Vec2(+NORMAL, -NORMAL);
		vertices[3].em_space_pos  = Vec2(glyph.rect.max.x, glyph.rect.min.y);

		u32 vidx = vertex_start;
		Assign(indices[0 .. 6], vidx+0, vidx+2, vidx+3, vidx+3, vidx+1, vidx+0);

		buffer.quad_offset += 1;

		pos.x += cast(i32)(cast(f32)(glyph.advance) * font.scale);
	}
}

