/*
See: https://github.com/Ed94/VEFontCache-Odin
*/
package vefontcache

// See: mappings.odin for profiling hookup
DISABLE_PROFILING              :: true
ENABLE_OVERSIZED_GLYPHS        :: true
// White: Cached Hit, Red: Cache Miss, Yellow: Oversized (Will override user's colors enabled)
ENABLE_DRAW_TYPE_VISUALIZATION :: false

Font_ID :: distinct i16
Glyph   :: distinct i32

Load_Font_Error :: enum(i32) {
	None,
	Parser_Failed,
}

Entry :: struct {
	parser_info   : Parser_Font_Info,
	shaper_info   : Shaper_Info,
	id            : Font_ID,
	used          : b32,
	curve_quality : f32,

	// TODO(Ed): Move over settings related to (snapping) hinting, oversample, px_scalar, etc; 
	// to here as there is no need to restrict the user to have one option for all fonts.

	ascent   : f32,
	descent  : f32,
	line_gap : f32,
}

Entry_Default :: Entry {
	id            = 0,
	used          = false,
	curve_quality = 3,
}

// Ease of use encapsulation of common fields for a canvas space
VPZ_Transform :: struct {
	view     : Vec2,
	position : Vec2,
	zoom     : f32,
}

Scope_Stack :: struct {
	font      : [dynamic]Font_ID,
	font_size : [dynamic]f32,
	colour    : [dynamic]RGBAN,
	view      : [dynamic]Vec2,
	position  : [dynamic]Vec2,
	scale     : [dynamic]Vec2,
	zoom      : [dynamic]f32,
}

Context :: struct {
	backing : Allocator,

	parser_ctx  : Parser_Context, // Glyph parser state
	shaper_ctx  : Shaper_Context, // Text shaper state

	// The managed font instances
	entries : [dynamic]Entry,

	// TODO(Ed): Review these when preparing to handle lifting of working context to a thread context.
	glyph_buffer : Glyph_Draw_Buffer, // -> draw.odin
	atlas        : Atlas,             // -> atlas.odin
	shape_cache  : Shaped_Text_Cache, // -> shaper.doin
	draw_list    : Draw_List,         // -> draw.odin

	batch_shapes_buffer : [dynamic]Shaped_Text, // Used for the procs that batch a layer of text.

	// Tracks the offsets for the current layer in a draw_list
	draw_layer : struct { 
		vertices_offset : int,
		indices_offset  : int,
		calls_offset    : int,
	},
	// See: get_draw_list_layer & flush_draw_list_layer

	// Note(Ed): Not really used anymore.
	// debug_print         : b32,
	// debug_print_verbose : b32,

	// Whether or not to snap positioning to the pixel of the view
	// Helps with hinting
	snap_to_view_extent : b32,

	stack : Scope_Stack,

	cursor_pos : Vec2,
	// Will apply a boost scalar (1.0 + alpha sharpen) to the colour's alpha which provides some sharpening of the edges.
	// Has a boldening side-effect. If overblown will look smeared.
	alpha_sharpen : f32,
	// Used by draw interface to super-scale the text by 
	// upscaling px_size with px_scalar and then down-scaling
	// the draw_list result by the same amount.
	px_scalar        : f32,   // Improves hinting, positioning, etc. Can make zoomed out text too jagged.
	zoom_px_interval : f32,   // When using zoom, the size can be locked to to this interval (fixes text width jitter)

	default_curve_quality : i32,
}

//#region("Init Params")

Init_Atlas_Params :: struct {
	size_multiplier : u32, // How much to scale the the atlas size to. (Affects everything, the base is 4096 x 2048 and everything follows from there)
	glyph_padding   : u32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region.
}

Init_Atlas_Params_Default :: Init_Atlas_Params {
	size_multiplier = 1,
	glyph_padding   = 1,
}

Init_Glyph_Draw_Params :: struct {
	// During the draw list generation stage when blitting to atlas, the quad's width will be ceil()'d to the closest pixel.
	// Generally its not recommend todo this and is disabled on most renderers, but on rare occasion some fonts benefit from this.
	snap_glyph_width          : b32,
	// During the draw list generation stage when blitting to atlas, the quad's height be ceil()'d to the closest pixel.
	snap_glyph_height         : b32,
	// Intended to be x16 (4x4) super-sampling from the glyph buffer to the atlas.
	// Oversized glyphs don't use this and instead do 2x or 1x depending on how massive they are.
	over_sample               : u32,
	// Best to just keep this the same as glyph_padding for the atlas..
	draw_padding              : u32,
	shape_gen_scratch_reserve : u32,
	// How many region.D glyphs can be drawn to the glyph render target buffer at once (worst case scenario)
	buffer_glyph_limit        : u32,
	// How many glyphs can at maximimum be proccessed at once by batch_generate_glyphs_draw_list
	batch_glyph_limit         : u32,
}

Init_Glyph_Draw_Params_Default :: Init_Glyph_Draw_Params {
	snap_glyph_width          = false,
	snap_glyph_height         = true,
	over_sample               = 4,
	draw_padding              = Init_Atlas_Params_Default.glyph_padding,
	shape_gen_scratch_reserve = 512,
	buffer_glyph_limit        = 16,
	batch_glyph_limit         = 256,
}

Init_Shaper_Params :: struct {
	// Forces a glyph position to align to a pixel (make sure to use snap_to_view_extent with this or else it won't be preserveds)
	snap_glyph_position           : b32,
	// Will use more signficant advance during shaping for fonts 
	// Note(Ed): Thinking of removing, doesn't look good often and its an extra condition in the hot-loop.
	adv_snap_small_font_threshold : u32,
}

Init_Shaper_Params_Default :: Init_Shaper_Params {
	snap_glyph_position           = true,
	adv_snap_small_font_threshold = 0,
}

Init_Shape_Cache_Params :: struct {
	// Note(Ed): This should mostly just be given the worst-case capacity and reserve at the same time.
	// If memory is a concern it can easily be 256 - 2k if not much text is going to be rendered often.
	// Shapes should really not exceed 1024 glyphs..
	capacity : u32,
	reserve  : u32,
}

Init_Shape_Cache_Params_Default :: Init_Shape_Cache_Params {
	capacity = 10 * 1024,
	reserve  = 128,
}

//#endregion("Init Params")

//#region("lifetime")

// ve_fontcache_init
startup :: proc( ctx : ^Context, parser_kind : Parser_Kind = .STB_TrueType, // Note(Ed): Only sbt_truetype supported for now.
	allocator                   := context.allocator,
	atlas_params                := Init_Atlas_Params_Default,
	glyph_draw_params           := Init_Glyph_Draw_Params_Default,
	shape_cache_params          := Init_Shape_Cache_Params_Default,
	shaper_params               := Init_Shaper_Params_Default,
	alpha_sharpen               : f32 = 0.1,
	px_scalar                   : f32 = 1.4,
	zoom_px_interval            : i32 = 2,
	
	// Curve quality to use for a font when unspecified,
	// Affects step size for bezier curve passes in generate_glyph_pass_draw_list
	default_curve_quality       : u32 = 3,
	entires_reserve             : u32 = 256,
	scope_stack_reserve         : u32 = 32,
)
{
	assert( ctx != nil, "Must provide a valid context" )

	ctx.backing       = allocator
	context.allocator = ctx.backing

	ctx.alpha_sharpen    = alpha_sharpen
	ctx.px_scalar        = px_scalar
	ctx.zoom_px_interval = f32(zoom_px_interval)

	shaper_ctx := & ctx.shaper_ctx
	shaper_ctx.adv_snap_small_font_threshold = f32(shaper_params.adv_snap_small_font_threshold)
	shaper_ctx.snap_glyph_position           = shaper_params.snap_glyph_position

	ctx.default_curve_quality = default_curve_quality == 0 ? 3 : i32(default_curve_quality)

	error : Allocator_Error
	ctx.entries, error = make( [dynamic]Entry, len = 0, cap = entires_reserve )
	assert(error == .None, "VEFontCache.init : Failed to allocate entries")

	ctx.draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 8 * Kilobyte )
	assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.vertices")

	ctx.draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 16 * Kilobyte )
	assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.indices")

	ctx.draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = Kilobyte )
	assert(error == .None, "VEFontCache.init : Failed to allocate draw_list.calls")
	
	atlas := & ctx.atlas
	Atlas_Setup:
	{
		atlas.size_multiplier = f32(atlas_params.size_multiplier)

		atlas_size    := Vec2i { 4096, 2048 } * i32(atlas.size_multiplier)
		slot_region_a := Vec2i {  32,  32 }   * i32(atlas.size_multiplier)
		slot_region_b := Vec2i {  32,  64 }   * i32(atlas.size_multiplier)
		slot_region_c := Vec2i {  64,  64 }   * i32(atlas.size_multiplier)
		slot_region_d := Vec2i { 128, 128 }   * i32(atlas.size_multiplier)
		
		init_atlas_region :: proc( region : ^Atlas_Region, atlas_size, slot_size : Vec2i, factor : Vec2i )
		{
			region.next_idx  = 0;
			region.slot_size = slot_size
			region.size      =  atlas_size / factor
			region.capacity  = region.size / region.slot_size

			error : Allocator_Error
			lru_init( & region.state, region.capacity.x * region.capacity.y )
		}
		init_atlas_region( & atlas.region_a, atlas_size, slot_region_a, { 4, 2})
		init_atlas_region( & atlas.region_b, atlas_size, slot_region_b, { 4, 2})
		init_atlas_region( & atlas.region_c, atlas_size, slot_region_c, { 4, 1})
		init_atlas_region( & atlas.region_d, atlas_size, slot_region_d, { 2, 1})

		atlas.size          = atlas_size
		atlas.glyph_padding = f32(atlas_params.glyph_padding)

		atlas.region_a.offset   = {0, 0}
		atlas.region_b.offset.x = 0
		atlas.region_b.offset.y = atlas.region_a.size.y
		atlas.region_c.offset.x = atlas.region_a.size.x
		atlas.region_c.offset.y = 0
		atlas.region_d.offset.x = atlas.size.x / 2
		atlas.region_d.offset.y = 0

		atlas.regions = {
			nil,
			& atlas.region_a,
			& atlas.region_b,
			& atlas.region_c,
			& atlas.region_d,
		}
	}

	Shape_Cache_Setup:
	{
		shape_cache := & ctx.shape_cache
		lru_init( & shape_cache.state, i32(shape_cache_params.capacity) )

		shape_cache.storage, error = make( [dynamic]Shaped_Text, shape_cache_params.capacity )
		assert(error == .None, "VEFontCache.init : Failed to allocate shape_cache.storage")

		for idx : u32 = 0; idx < shape_cache_params.capacity; idx += 1
		{
			storage_entry := & shape_cache.storage[idx]

			storage_entry.glyph, error = make( [dynamic]Glyph, len = 0, cap = shape_cache_params.reserve )
			assert( error == .None, "VEFontCache.init : Failed to allocate glyphs array for shape cache storage" )

			storage_entry.position, error = make( [dynamic]Vec2, len = 0, cap = shape_cache_params.reserve )
			assert( error == .None, "VEFontCache.init : Failed to allocate positions array for shape cache storage" )

			storage_entry.visible, error = make( [dynamic]i32, len = 0, cap = shape_cache_params.reserve )
			assert( error == .None, "VEFontCache.init : Failed to allocate visible array for shape cache storage" )

			storage_entry.atlas_lru_code, error = make( [dynamic]Atlas_Key, len = 0, cap = shape_cache_params.reserve )
			assert( error == .None, "VEFontCache.init : Failed to allocate atlas_lru_code array for shape cache storage" )

			storage_entry.region_kind, error = make( [dynamic]Atlas_Region_Kind, len = 0, cap = shape_cache_params.reserve )
			assert( error == .None, "VEFontCache.init : Failed to allocate region_kind array for shape cache storage" )

			storage_entry.bounds, error = make( [dynamic]Range2, len = 0, cap = shape_cache_params.reserve )
			assert( error == .None, "VEFontCache.init : Failed to allocate bounds array for shape cache storage" )
		}
	}

	Glyph_Buffer_Setup:
	{
		glyph_buffer := & ctx.glyph_buffer
		glyph_buffer.snap_glyph_width  = cast(f32) i32(glyph_draw_params.snap_glyph_width)
		glyph_buffer.snap_glyph_height = cast(f32) i32(glyph_draw_params.snap_glyph_height)
		glyph_buffer.over_sample       = { f32(glyph_draw_params.over_sample), f32(glyph_draw_params.over_sample) }
		glyph_buffer.size.x            = atlas.region_d.slot_size.x * i32(glyph_buffer.over_sample.x) * i32(glyph_draw_params.buffer_glyph_limit)
		glyph_buffer.size.y            = atlas.region_d.slot_size.y * i32(glyph_buffer.over_sample.y)
		glyph_buffer.draw_padding      = cast(f32) glyph_draw_params.draw_padding

		glyph_buffer.draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 8 * Kilobyte )
		assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for glyph_buffer.draw_list" )

		glyph_buffer.draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 16 * Kilobyte )
		assert( error == .None, "VEFontCache.init : Failed to allocate indices for glyph_buffer.draw_list" )

		glyph_buffer.draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = Kilobyte )
		assert( error == .None, "VEFontCache.init : Failed to allocate calls for glyph_buffer.draw_list" )

		glyph_buffer.clear_draw_list.vertices, error = make( [dynamic]Vertex, len = 0, cap = 2 * Kilobyte )
		assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for clear_draw_list" )

		glyph_buffer.clear_draw_list.indices, error = make( [dynamic]u32, len = 0, cap = 4 * Kilobyte )
		assert( error == .None, "VEFontCache.init : Failed to allocate calls for indices array for clear_draw_list" )

		glyph_buffer.clear_draw_list.calls, error = make( [dynamic]Draw_Call, len = 0, cap = Kilobyte )
		assert( error == .None, "VEFontCache.init : Failed to allocate calls for calls for clear_draw_list" )

		glyph_buffer.shape_gen_scratch, error = make( [dynamic]Vertex, len = 0, cap = glyph_draw_params.shape_gen_scratch_reserve )
		assert(error == .None, "VEFontCache.init : Failed to allocate shape_gen_scratch")

		batch_cache    := & glyph_buffer.batch_cache
		batch_cache.cap = i32(glyph_draw_params.batch_glyph_limit)
		batch_cache.num = 0
		batch_cache.table, error = make( map[Atlas_Key]b8, uint(glyph_draw_params.batch_glyph_limit) )
		assert(error == .None, "VEFontCache.init : Failed to allocate batch_cache")

		glyph_buffer.glyph_pack,error = make_soa( #soa[dynamic]Glyph_Pack_Entry, length = 0, capacity = uint(shape_cache_params.reserve) )
		glyph_buffer.oversized, error = make( [dynamic]i32, len = 0, cap = uint(shape_cache_params.reserve) )
		glyph_buffer.to_cache,  error = make( [dynamic]i32, len = 0, cap = uint(shape_cache_params.reserve) )
		glyph_buffer.cached,    error = make( [dynamic]i32, len = 0, cap = uint(shape_cache_params.reserve) )
	}

	parser_init( & ctx.parser_ctx, parser_kind )
	shaper_init( & ctx.shaper_ctx )

	// Scoping Stack
	{
		stack := &  ctx.stack

		error : Allocator_Error
		stack.font, error = make([dynamic]Font_ID, len = 0, cap = scope_stack_reserve)
		assert(error == .None, "VEFontCache.init : Failed to allocate stack.font")

		stack.font_size, error = make([dynamic]f32, len = 0, cap = scope_stack_reserve)
		assert(error == .None, "VEFontCache.init : Failed to allocate stack.font_size")

		stack.font_size, error = make([dynamic]f32, len = 0, cap = scope_stack_reserve)
		assert(error == .None, "VEFontCache.init : Failed to allocate stack.font_size")

		stack.colour, error = make([dynamic]RGBAN, len = 0, cap = scope_stack_reserve)
		assert(error == .None, "VEFontCache.init : Failed to allocate stack.colour")

		stack.view, error = make([dynamic]Vec2, len = 0, cap = scope_stack_reserve)
		assert(error == .None, "VEFontCache.init : Failed to allocate stack.view")

		stack.position, error = make([dynamic]Vec2, len = 0, cap = scope_stack_reserve)
		assert(error == .None, "VEFontCache.init : Failed to allocate stack.position")

		stack.scale, error = make([dynamic]Vec2, len = 0, cap = scope_stack_reserve)
		assert(error == .None, "VEFontCache.init : Failed to allocate stack.scale")

		stack.zoom, error = make([dynamic]f32, len = 0, cap = scope_stack_reserve)
		assert(error == .None, "VEFontCache.init : Failed to allocate stack.zoom")
	}

	// Set the default stack values
	// Will be popped on shutdown
	push_colour(ctx, {1, 1, 1, 1})
	push_font_size(ctx, 36)
	push_view(ctx, { 0, 0 })
	push_position(ctx, {0, 0})
	push_scale(ctx, 1.0)
	push_zoom(ctx, 1.0)
}

hot_reload :: proc( ctx : ^Context, allocator : Allocator )
{
	assert( ctx != nil )
	ctx.backing       = allocator
	context.allocator = ctx.backing

	atlas        := & ctx.atlas
	glyph_buffer := & ctx.glyph_buffer
	shape_cache  := & ctx.shape_cache
	draw_list    := & ctx.draw_list

	reload_array( & ctx.entries, allocator )

	reload_array( & glyph_buffer.draw_list.calls,    allocator )
	reload_array( & glyph_buffer.draw_list.indices,  allocator )
	reload_array( & glyph_buffer.draw_list.vertices, allocator )

	reload_array( & glyph_buffer.clear_draw_list.calls,    allocator )
	reload_array( & glyph_buffer.clear_draw_list.indices,  allocator )
	reload_array( & glyph_buffer.clear_draw_list.vertices, allocator )

	reload_map(   & glyph_buffer.batch_cache.table, allocator )
	reload_array( & glyph_buffer.shape_gen_scratch, allocator )

	reload_array_soa( & glyph_buffer.glyph_pack, allocator )
	reload_array(     & glyph_buffer.oversized,  allocator )
	reload_array(     & glyph_buffer.to_cache,   allocator )
	reload_array(     & glyph_buffer.cached,     allocator )

	lru_reload( & atlas.region_a.state, allocator)
	lru_reload( & atlas.region_b.state, allocator)
	lru_reload( & atlas.region_c.state, allocator)
	lru_reload( & atlas.region_d.state, allocator)

	lru_reload( & shape_cache.state, allocator )
	for idx : i32 = 0; idx < i32(len(shape_cache.storage)); idx += 1 {
		storage_entry := & shape_cache.storage[idx]
		reload_array( & storage_entry.glyph,       allocator)
		reload_array( & storage_entry.position,       allocator)
		reload_array( & storage_entry.visible,        allocator)
		reload_array( & storage_entry.atlas_lru_code, allocator)
		reload_array( & storage_entry.region_kind,    allocator)
		reload_array( & storage_entry.bounds,         allocator)
	}
	reload_array( & shape_cache.storage, allocator )
	
	reload_array( & draw_list.vertices, allocator)
	reload_array( & draw_list.indices,  allocator)
	reload_array( & draw_list.calls,    allocator)

	parser_reload(& ctx.parser_ctx, allocator)

	// Scope Stack
	{
		stack := & ctx.stack
		reload_array(& stack.font,      allocator)
		reload_array(& stack.font_size, allocator)
		reload_array(& stack.colour,    allocator)
		reload_array(& stack.view,      allocator)
		reload_array(& stack.position,  allocator)
		reload_array(& stack.scale,     allocator)
		reload_array(& stack.zoom,      allocator)
	}
}

shutdown :: proc( ctx : ^Context )
{
	assert( ctx != nil )
	context.allocator = ctx.backing

	atlas        := & ctx.atlas
	glyph_buffer := & ctx.glyph_buffer
	shape_cache  := & ctx.shape_cache
	draw_list    := & ctx.draw_list

	pop_colour(ctx)
	pop_font_size(ctx)
	pop_view(ctx)
	pop_position(ctx)
	pop_scale(ctx)
	pop_zoom(ctx)

	for & entry in ctx.entries {
		unload_font( ctx, entry.id )
	}
	delete( ctx.entries )
	
	delete( glyph_buffer.draw_list.vertices )
	delete( glyph_buffer.draw_list.indices )
	delete( glyph_buffer.draw_list.calls )

	delete( glyph_buffer.clear_draw_list.vertices )
	delete( glyph_buffer.clear_draw_list.indices )
	delete( glyph_buffer.clear_draw_list.calls )

	delete( glyph_buffer.batch_cache.table )
	delete( glyph_buffer.shape_gen_scratch )

	delete_soa( glyph_buffer.glyph_pack)
	delete(     glyph_buffer.oversized)
	delete(     glyph_buffer.to_cache)
	delete(     glyph_buffer.cached)

	lru_free( & atlas.region_a.state )
	lru_free( & atlas.region_b.state )
	lru_free( & atlas.region_c.state )
	lru_free( & atlas.region_d.state )

	for idx : i32 = 0; idx < i32(len(shape_cache.storage)); idx += 1 {
		storage_entry := & shape_cache.storage[idx]
		delete( storage_entry.glyph )
		delete( storage_entry.position )
		delete( storage_entry.visible )
		delete( storage_entry.atlas_lru_code)
		delete( storage_entry.region_kind)
		delete( storage_entry.bounds)
	}
	lru_free( & shape_cache.state )
	
	delete( draw_list.vertices )
	delete( draw_list.indices )
	delete( draw_list.calls )

	shaper_shutdown( & ctx.shaper_ctx )
	parser_shutdown( & ctx.parser_ctx )

	// Scope Stack
	{
		stack := & ctx.stack
		delete(stack.font)
		delete(stack.font_size)
		delete(stack.colour)
		delete(stack.view)
		delete(stack.position)
		delete(stack.scale)
		delete(stack.zoom)
	}
}

load_font :: proc( ctx : ^Context, label : string, data : []byte, glyph_curve_quality : u32 = 0 ) -> (font_id : Font_ID, error : Load_Font_Error)
{
	profile(#procedure)
	assert( ctx != nil )
	assert( len(data) > 0 )
	context.allocator = ctx.backing

	entries := & ctx.entries

	id : Font_ID = -1

	for index : i32 = 0; index < i32(len(entries)); index += 1 {
		if entries[index].used do continue
		id = Font_ID(index)
		break
	}
	if id == -1 {
		append_elem( entries, Entry {})
		id = cast(Font_ID) len(entries) - 1
	}
	assert( id >= 0 && id < Font_ID(len(entries)) )

	entry := & entries[ id ]
	{
		entry.used = true

		profile_begin("calling loaders")
		parser_error : b32
		entry.parser_info, parser_error = parser_load_font( & ctx.parser_ctx, label, data )
		if parser_error {
			error = .Parser_Failed
			return
		}
		entry.shaper_info = shaper_load_font( & ctx.shaper_ctx, label, data )
		profile_end()

		ascent, descent, line_gap := parser_get_font_vertical_metrics(entry.parser_info)
		entry.ascent   = f32(ascent)
		entry.descent  = f32(descent)
		entry.line_gap = f32(line_gap)

		if glyph_curve_quality == 0 {
			entry.curve_quality = f32(ctx.default_curve_quality)
		}
		else {
			entry.curve_quality = f32(glyph_curve_quality)
		}
	}
	entry.id = Font_ID(id)
	ctx.entries[ id ].id = Font_ID(id)

	font_id = Font_ID(id)
	return
}

unload_font :: proc( ctx : ^Context, font : Font_ID )
{
	assert( ctx != nil )
	assert( font >= 0 && int(font) < len(ctx.entries) )
	context.allocator = ctx.backing

	entry     := & ctx.entries[ font ]
	entry.used = false

	shaper_unload_font( & entry.shaper_info )
	parser_unload_font( & entry.parser_info )
}

// Can be used with hot-reload
clear_atlas_region_caches :: proc(ctx : ^Context)
{
	lru_clear(& ctx.atlas.region_a.state)
	lru_clear(& ctx.atlas.region_b.state)
	lru_clear(& ctx.atlas.region_c.state)
	lru_clear(& ctx.atlas.region_d.state)

	ctx.atlas.region_a.next_idx = 0
	ctx.atlas.region_b.next_idx = 0
	ctx.atlas.region_c.next_idx = 0
	ctx.atlas.region_d.next_idx = 0
}

// Can be used with hot-reload
clear_shape_cache :: proc (ctx : ^Context)
{
	lru_clear(& ctx.shape_cache.state)
	for idx : i32 = 0; idx < cast(i32) cap(ctx.shape_cache.storage); idx += 1 {
		storage_entry := & ctx.shape_cache.storage[idx]
		storage_entry.end_cursor_pos = {}
		storage_entry.size           = {}
		clear(& storage_entry.glyph)
		clear(& storage_entry.position)
	}
	ctx.shape_cache.next_cache_id = 0
}

//#endregion("lifetime")

//#region("shaping")

// For high performance, the user should track the shapes and use the draw list interface on shapes. 
// Doing so avoids cache lookups.

shape_text :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string, 
	shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
) -> Shaped_Text
{
	profile(#procedure)
	assert( len(text_utf8) > 0 )
	entry := ctx.entries[ font ]

	target_px_size    := px_size * ctx.px_scalar
	target_font_scale := parser_scale( entry.parser_info, target_px_size )

	return shaper_shape_text_cached( text_utf8, 
		& ctx.shaper_ctx, 
		& ctx.shape_cache,
		ctx.atlas,
		vec2(ctx.glyph_buffer.size),
		font, 
		entry, 
		target_px_size, 
		target_font_scale, 
		shaper_proc
	)
}

// User handled shaped text. Will not be cached
shape_text_uncached :: #force_inline proc( ctx : ^Context, font : Font_ID, px_size: f32, text_utf8 : string, shape : ^Shaped_Text, 
	shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz 
)
{
	profile(#procedure)
	assert( len(text_utf8) > 0 )
	entry := ctx.entries[ font ]

	target_px_size    := px_size * ctx.px_scalar
	target_font_scale := parser_scale( entry.parser_info, target_px_size )

	shaper_proc(& ctx.shaper_ctx, 
		ctx.atlas, 
		vec2(ctx.glyph_buffer.size), 
		font,
		entry, 
		target_px_size, 
		target_font_scale, 
		text_utf8, 
		shape
	) 
	return
}

//#endregion("shaping")

//#region("draw_list generation")

/* The most basic interface-level draw shape procedure.
	Context's stack is not used. Only modifications for alpha sharpen and px_scalar are applied.
	view, position, and scale are expected to be in unsigned normalized space:
                                                          
|            +----------------------------------+ (1.0, 1.0)
|            |                                  |
|            |                                  |
|            |          Glyph Quad              |
|            |          +--------+ < scale.y    |
|            |          |   **   |  *     |     |
|            |          |  *  *  |  ****  |     |
|            |          |  ****  |  *  *  |     |
|            |          |  *  *  |  ****  |     |
|            |          +--------+--------+.... |
|            | position ^        ^ scale.x      |
|            |                                  |
|            |                                  |
|            |                                  |
| (0.0, 0.0) +----------------------------------+

    •   position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned)
    <-> scale   : Scale the glyph beyond its default scaling from its px_size.
*/
@(optimization_mode="favor_size")
draw_shape_normalized_space :: #force_inline proc( ctx : ^Context, 
	colour   : RGBAN,  
	position : Vec2, 
	scale    : Vec2,  
	shape    : Shaped_Text
)
{
	profile(#procedure)
	assert( ctx != nil )

	entry := ctx.entries[ shape.font ]

	should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
	adjusted_colour      := colour
	adjusted_colour.a    += ctx.alpha_sharpen * should_alpha_sharpen

	target_px_size    := shape.px_size
	target_scale      := scale         * (1 / ctx.px_scalar)
	target_font_scale := parser_scale( entry.parser_info, shape.px_size )

	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
		ctx.px_scalar,
		adjusted_colour, 
		entry, 
		target_px_size,
		target_font_scale, 
		position, 
		target_scale, 
	)
}

/* Non-scoping context. The most basic interface-level draw shape procedure (everything else is quality of life warppers).

	Context's stack is not used. Only modifications for alpha sharpen and px_scalar are applied.
	view, position, and scale are expected to be in unsigned normalized space:
                                                          
|            +----------------------------------+ (1.0, 1.0)
|            |                                  |
|            |                                  |
|            |          Glyph Quad              |
|            |          +---------+ < scale.y   |
|            |          |   **   |  *     |     |
|            |          |  *  *  |  ****  |     |
|            |          |  ****  |  *  *  |     |
|            |          |  *  *  |  ****  |     |
|            |          +--------+--------+.... |
|            | position ^        ^ scale.x      |
|            |                                  |
|            |                                  |
|            |                                  |
| (0.0, 0.0) +----------------------------------+

    •   position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned)
    <-> scale   : Scale the glyph beyond its default scaling from its px_size.
*/
@(optimization_mode="favor_size")
draw_text_normalized_space :: proc( ctx : ^Context, 
	font        : Font_ID,
	px_size     : f32,
	colour      : RGBAN,
	position    : Vec2,
	scale       : Vec2, 
	text_utf8   : string,
	shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
)
{
	profile(#procedure)
	assert( ctx != nil )
	assert( font >= 0 && int(font) < len(ctx.entries) )
	assert( len(text_utf8) > 0 )

	ctx.cursor_pos = {}
	entry := ctx.entries[ font ]

	should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
	adjusted_colour      := colour
	adjusted_colour.a    += ctx.alpha_sharpen * should_alpha_sharpen

	// Does nothing when px_scalar is 1.0
	target_px_size    := px_size * ctx.px_scalar
	target_scale      := scale   * (1 / ctx.px_scalar)
	target_font_scale := parser_scale( entry.parser_info, target_px_size )

	shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size),
		font, 
		entry, 
		target_px_size,
		target_font_scale, 
		shaper_proc
	)
	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
		ctx.px_scalar,
		adjusted_colour, 
		entry, 
		target_px_size,
		target_font_scale, 
		position,
		target_scale, 
	)
}

/* Equivalent to draw_text_shape_normalized_space, however the coordinate space is expected to be relative to the view.
	view, position, and scale are expected to be in unsigned view space:
                                                          
|            +----------------------------------+ (view.x, view.y)
|            |                                  |
|            |                                  |
|            |          Glyph Quad              |
|            |          +---------+ < scale.y   |
|            |          |   **   |  *     |     |
|            |          |  *  *  |  ****  |     |
|            |          |  ****  |  *  *  |     |
|            |          |  *  *  |  ****  |     |
|            |          +--------+--------+.... |
|            | position ^        ^ scale.x      |
|            |                                  |
|            |                                  |
|            |                                  |
| (0.0, 0.0) +----------------------------------+

	□   view    : The coordinate space is scaled to the view. Positions will be snapped to it.
    •   position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned)
    <-> scale   : Scale the glyph beyond its default scaling from its px_size.
	    zoom    : Will affect the scale similar to how the zoom on a canvas would behave.
*/
// @(optimization_mode="favor_size")
draw_shape_view_space :: proc( ctx : ^Context,
	colour   : RGBAN, 
	view     : Vec2,
	position : Vec2,
	scale    : Vec2, 
	zoom     : f32,
	shape    : Shaped_Text
)
{
	profile(#procedure)
	assert( ctx != nil )
	// TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape)
	assert( ctx.px_scalar > 0.0 )

	entry := ctx.entries[ shape.font ]

	should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
	adjusted_colour      := colour
	adjusted_colour.a    += ctx.alpha_sharpen * should_alpha_sharpen

	px_scalar_quotient := (1 / ctx.px_scalar)
	px_size             := shape.px_size * px_scalar_quotient

	resolved_size,   zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view )
	target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view )

	target_px_size, 
	target_font_scale,
	target_scale := resolve_px_scalar_size(entry.parser_info, resolved_size, ctx.px_scalar, norm_scale)

	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
		ctx.px_scalar,
		adjusted_colour, 
		entry, 
		target_px_size,
		target_font_scale, 
		target_position, 
		target_scale, 
	)
}

/* Equivalent to draw_text_shape_normalized_space, however the coordinate space is expected to be relative to the view.
	view, position, and scale are expected to be in unsigned view space:
                                                          
|            +----------------------------------+ (view.x, view.y)
|            |                                  |
|            |                                  |
|            |          Glyph Quad              |
|            |          +---------+ < scale.y   |
|            |          |   **   |  *     |     |
|            |          |  *  *  |  ****  |     |
|            |          |  ****  |  *  *  |     |
|            |          |  *  *  |  ****  |     |
|            |          +--------+--------+.... |
|            | position ^        ^ scale.x      |
|            |                                  |
|            |                                  |
|            |                                  |
| (0.0, 0.0) +----------------------------------+

	□   view    : The coordinate space is scaled to the view. Positions will be snapped to it.
    •   position: Anchor point in normalized space (where the bottom-right vertex of the first glyph quad will be positioned)
    <-> scale   : Scale the glyph beyond its default scaling from its px_size.
	    zoom    : Will affect the scale similar to how the zoom on a canvas would behave.
*/
// @(optimization_mode = "favor_size")
draw_text_view_space :: proc(ctx : ^Context,
	font        : Font_ID,
	px_size     : f32,
	colour      : RGBAN,
	view        : Vec2,
	position    : Vec2,
	scale       : Vec2, 
	zoom        : f32,
	text_utf8   : string,
	shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
)
{
	profile(#procedure)
	assert( ctx != nil )
	assert( font >= 0 && int(font) < len(ctx.entries) )
	assert( len(text_utf8) > 0 )
	assert( ctx.px_scalar > 0.0 )

	ctx.cursor_pos = {}
	entry := ctx.entries[ font ]

	should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
	adjusted_colour      := colour
	adjusted_colour.a    += ctx.alpha_sharpen * should_alpha_sharpen

	resolved_size,   zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view )
	target_position, norm_scale := get_normalized_position_scale( position, zoom_scale, view )

	target_px_size, 
	target_font_scale,
	target_scale := resolve_px_scalar_size(entry.parser_info, resolved_size, ctx.px_scalar, norm_scale)

	shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size),
		font, 
		entry, 
		target_px_size,
		target_font_scale, 
		shaper_proc
	)
	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
		ctx.px_scalar,
		adjusted_colour, 
		entry, 
		target_px_size,
		target_font_scale, 
		target_position,
		target_scale, 
	)
}

/* Uses the ctx.stack, position and scale are relative to the position and scale on the stack.

absolute_position := peek(stack.position) + position
absolute_scale    := peek(stack.scale   ) * scale

|            +-----------------------------------+ (view.x, view.y)
|            |                                   |
|            |                                   |
|            |          Glyph Quad    absolute   |
|            |          +---------+ < scale.y    |
|            |          |   **    |  *     |     |
|            |          |  *  *   |  ****  |     |
|            |          |  ****   |  *  *  |     |
|            |          |  *  *   |  ****  |     |
|            |          +---------+--------+.... |
|            | absolute ^         ^ absolute     |
|            | position             scale.x      |
|            |                                   |
|            |                                   |
|            |                                   |
| (0.0, 0.0) +-----------------------------------+
*/
// @(optimization_mode = "favor_size")
draw_shape :: proc( ctx : ^Context, position, scale : Vec2, shape : Shaped_Text )
{
	profile(#procedure)
	assert( ctx != nil )
	assert( ctx.px_scalar > 0.0 )

	stack := & ctx.stack
	assert(len(stack.view)      > 0)
	assert(len(stack.colour)    > 0)
	assert(len(stack.position)  > 0)
	assert(len(stack.scale)     > 0)
	assert(len(stack.zoom)      > 0)

	// TODO(Ed): This should be taken from the shape instead (you cannot use a different font with a shape)
	font := peek(stack.font)
	assert( font >= 0 &&int(font) < len(ctx.entries) )

	view := peek(stack.view);

	ctx.cursor_pos = {}
	entry := ctx.entries[ font ]

	colour               := peek(stack.colour)
	should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
	adjusted_colour      := colour
	adjusted_colour.a    += ctx.alpha_sharpen * should_alpha_sharpen

	px_scalar_quotient := 1 / ctx.px_scalar

	px_size := shape.px_size * px_scalar_quotient
	zoom    := peek(stack.zoom)

	resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view )

	absolute_position := peek(stack.position) + position
	absolute_scale    := peek(stack.scale)    * zoom_scale

	target_position, norm_scale := get_normalized_position_scale( absolute_position, absolute_scale, view )
	target_px_size, 
	target_font_scale, 
	target_scale := resolve_px_scalar_size(entry.parser_info,resolved_size, ctx.px_scalar, norm_scale)

	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
		ctx.px_scalar,
		adjusted_colour, 
		entry, 
		target_px_size,
		target_font_scale,
		target_position, 
		target_scale, 
	)
}

/* Uses the ctx.stack, position and scale are relative to the position and scale on the stack.

absolute_position := peek(stack.position) + position
absolute_scale    := peek(stack.scale   ) * scale

|            +-----------------------------------+ (view.x, view.y)
|            |                                   |
|            |                                   |
|            |          Glyph Quad    absolute   |
|            |          +---------+ < scale.y    |
|            |          |   **    |  *     |     |
|            |          |  *  *   |  ****  |     |
|            |          |  ****   |  *  *  |     |
|            |          |  *  *   |  ****  |     |
|            |          +---------+--------+.... |
|            | absolute ^         ^ absolute     |
|            | position             scale.x      |
|            |                                   |
|            |                                   |
|            |                                   |
| (0.0, 0.0) +-----------------------------------+
*/
// @(optimization_mode = "favor_size")
draw_text :: proc( ctx : ^Context, position, scale : Vec2, text_utf8 : string, 
	shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz 
)
{
	profile(#procedure)
	assert( ctx != nil )
	assert( len(text_utf8) > 0 )
	assert( ctx.px_scalar > 0.0 )

	stack := & ctx.stack
	assert(len(stack.font)      > 0)
	assert(len(stack.font_size) > 0)
	assert(len(stack.colour)    > 0)
	assert(len(stack.view)      > 0)
	assert(len(stack.position)  > 0)
	assert(len(stack.scale)     > 0)
	assert(len(stack.zoom)      > 0)

	font := peek(stack.font)
	assert( font >= 0 &&int(font) < len(ctx.entries) )

	view := peek(stack.view);

	ctx.cursor_pos = {}
	entry := ctx.entries[ font ]

	colour               := peek(stack.colour)
	should_alpha_sharpen := cast(f32) cast(i32) (colour.a >= 1.0)
	adjusted_colour      := colour
	adjusted_colour.a    += ctx.alpha_sharpen * should_alpha_sharpen

	px_size := peek(stack.font_size)
	zoom    := peek(stack.zoom)

	resolved_size, zoom_scale := resolve_zoom_size_scale( zoom, px_size, scale, ctx.zoom_px_interval, 2, 999.0, view )

	absolute_position := peek(stack.position) + position
	absolute_scale    := peek(stack.scale)    * scale

	target_position, norm_scale := get_normalized_position_scale( absolute_position, absolute_scale, view )
	target_px_size, 
	target_font_scale, 
	target_scale := resolve_px_scalar_size(entry.parser_info, resolved_size, ctx.px_scalar, norm_scale)

	shape := shaper_shape_text_cached( text_utf8, & ctx.shaper_ctx, & ctx.shape_cache, ctx.atlas, vec2(ctx.glyph_buffer.size),
		font, 
		entry, 
		target_px_size,
		target_font_scale, 
		shaper_proc
	)
	ctx.cursor_pos = generate_shape_draw_list( & ctx.draw_list, shape, & ctx.atlas, & ctx.glyph_buffer,
		ctx.px_scalar,
		adjusted_colour, 
		entry, 
		target_px_size,
		target_font_scale, 
		target_position,
		target_scale, 
	)
}

get_draw_list :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> ^Draw_List {
	assert( ctx != nil )
	if optimize_before_returning do optimize_draw_list( & ctx.draw_list, 0 )
	return & ctx.draw_list
}

get_draw_list_layer :: #force_inline proc( ctx : ^Context, optimize_before_returning := true ) -> (vertices : []Vertex, indices : []u32, calls : []Draw_Call) {
	assert( ctx != nil )
	if optimize_before_returning do optimize_draw_list( & ctx.draw_list, ctx.draw_layer.calls_offset )
	vertices = ctx.draw_list.vertices[ ctx.draw_layer.vertices_offset : ]
	indices  = ctx.draw_list.indices [ ctx.draw_layer.indices_offset  : ]
	calls    = ctx.draw_list.calls   [ ctx.draw_layer.calls_offset    : ]
	return
}

flush_draw_list :: #force_inline proc( ctx : ^Context ) {
	assert( ctx != nil )
	clear_draw_list( & ctx.draw_list )
	ctx.draw_layer.vertices_offset = 0
	ctx.draw_layer.indices_offset  = 0
	ctx.draw_layer.calls_offset    = 0
}

flush_draw_list_layer :: #force_inline proc( ctx : ^Context ) {
	assert( ctx != nil )
	ctx.draw_layer.vertices_offset = len(ctx.draw_list.vertices)
	ctx.draw_layer.indices_offset  = len(ctx.draw_list.indices)
	ctx.draw_layer.calls_offset    = len(ctx.draw_list.calls)
}

//#endregion("draw_list generation")

//#region("metrics")

// The metrics follow the convention for providing their values unscaled from ctx.px_scalar
// Where its assumed when utilizing the draw_list generators or shaping procedures that the shape will be affected by it so it must be handled.
// If px_scalar is 1.0 no effect is done and its just redundant ops.

measure_shape_size :: #force_inline proc( ctx : Context, shape : Shaped_Text ) -> (measured : Vec2) {
	measured = shape.size * (1 / ctx.px_scalar)
	return
}

// Don't use this if you already have the shape instead use measure_shape_size
@(optimization_mode="favor_size")
measure_text_size :: proc( ctx : ^Context, font : Font_ID, px_size : f32, text_utf8 : string, 
	shaper_proc : $Shaper_Shape_Text_Uncached_Proc = shaper_shape_harfbuzz
) -> (measured : Vec2)
{
	// profile(#procedure)
	assert( ctx != nil )
	assert( font >= 0 && int(font) < len(ctx.entries) )

	entry := ctx.entries[font]

	target_scale      := 1 / ctx.px_scalar
	target_px_size    := px_size * ctx.px_scalar
	target_font_scale := parser_scale( entry.parser_info, target_px_size )

	shaped := shaper_shape_text_cached( text_utf8, 
		& ctx.shaper_ctx, 
		& ctx.shape_cache, 
		ctx.atlas, 
		vec2(ctx.glyph_buffer.size),
		font, 
		entry, 
		target_px_size, 
		target_font_scale, 
		shaper_proc 
	)
	return shaped.size * target_scale
}

get_font_vertical_metrics :: #force_inline proc ( ctx : Context, font : Font_ID, px_size : f32 ) -> ( ascent, descent, line_gap : f32 )
{
	assert( font >= 0 && int(font) < len(ctx.entries) )

	entry := ctx.entries[ font ]
	
	font_scale := parser_scale( entry.parser_info, px_size )

	ascent   = font_scale * entry.ascent
	descent  = font_scale * entry.descent
	line_gap = font_scale * entry.line_gap
	return
}

//#endregion("metrics")

//#region("miscellaneous")

get_font_entry :: #force_inline proc "contextless" ( ctx : ^Context, font : Font_ID ) -> Entry {
	return ctx.entries[font]
}

get_cursor_pos :: #force_inline proc "contextless" ( ctx : Context ) -> Vec2 { return ctx.cursor_pos }

// Will normalize the value of the position and scale based on the provided view.
// Position will also be snapped to the nearest pixel via ceil.
// (Does nothing if view is 1 or 0)
get_normalized_position_scale :: #force_inline proc "contextless" ( position, scale, view : Vec2 ) -> (position_norm, scale_norm : Vec2)
{
	should_snap   := cast(f32) i32(view.x > 0 && view.y > 0)
	view_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) }

	position_snapped := ceil(position) * view_quotient * should_snap
	position_norm     = position       * view_quotient

	position_norm = max(position_snapped, position_norm)
	scale_norm    = scale * view_quotient
	return
}

// Used to constrain the px_size used in draw calls.
resolve_draw_px_size :: #force_inline proc "contextless" ( px_size, interval, min, max : f32 ) -> (resolved_size : f32) {
	interval_quotient := 1.0 / f32(interval)
	interval_size     := round(px_size * interval_quotient) * interval
	resolved_size      = clamp( interval_size, min, max )
	return
}

// Provides a way to get a "zoom" on the font size and scale, similar conceptually to a canvas UX zoom
// Does nothing when zoom is 1.0
resolve_zoom_size_scale :: #force_inline proc "contextless" ( 
	zoom, px_size : f32, scale : Vec2, interval, min, max : f32, clamp_scale : Vec2 
) -> (resolved_size : f32, zoom_scale : Vec2)
{
	zoom_px_size     := px_size * zoom
	resolved_size     = resolve_draw_px_size( zoom_px_size, interval, min, max )
	zoom_diff_scalar := 1 + (zoom_px_size - resolved_size) * (1 / resolved_size)
	zoom_scale        = zoom_diff_scalar * scale
	zoom_scale.x      = clamp(zoom_scale.x, 0, clamp_scale.x)
	zoom_scale.y      = clamp(zoom_scale.y, 0, clamp_scale.y)
	return
}

// Get the target pixel, font_scale, and scale for the given font pixel size, and scalar multiple to apply. (Normalized space with norm_scale)
// To derived norm_scale use:  get_normalized_position_scale or just do (scale * (1 / view))
resolve_px_scalar_size :: #force_inline proc "contextless" ( parser_info : Parser_Font_Info, px_size, px_scalar : f32, norm_scale : Vec2 
) -> (target_px_size, target_font_scale : f32, target_scale : Vec2 )
{
	// Does nothing when px_scalar is 1.0
	target_px_size    = px_size    * px_scalar
	target_scale      = norm_scale * (1 / px_scalar)
	target_font_scale = parser_scale( parser_info, target_px_size )
	return
}

snap_normalized_position_to_view :: #force_inline proc "contextless" ( position, view : Vec2 ) -> (position_snapped : Vec2)
{
	should_snap   := cast(f32) i32(view.x > 0 && view.y > 0)
	view_quotient := 1 / Vec2 { max(view.x, 1), max(view.y, 1) }

	position_snapped = ceil(position * view) * view_quotient * should_snap
	position_snapped = max(position, position_snapped)
	return
}

set_alpha_scalar     :: #force_inline proc( ctx : ^Context, scalar   : f32 ) { assert(ctx != nil); ctx.alpha_sharpen    = scalar }
set_px_scalar        :: #force_inline proc( ctx : ^Context, scalar   : f32 ) { assert(ctx != nil); ctx.px_scalar        = scalar }
set_zoom_px_interval :: #force_inline proc( ctx : ^Context, interval : i32 ) { assert(ctx != nil); ctx.zoom_px_interval = f32(interval) }

// During a shaping pass on text, will snap each glyph's position via ceil.
set_snap_glyph_shape_position :: #force_inline proc( ctx : ^Context, should_snap : b32 ) {
	assert(ctx != nil)
	ctx.shaper_ctx.snap_glyph_position = should_snap
}

// During to_cache pass within batch_generate_glyphs_draw_list, will snap the quad's size using ceil.
set_snap_glyph_render_height :: #force_inline proc( ctx : ^Context, should_snap : b32 ) { 
	assert(ctx != nil)
	ctx.glyph_buffer.snap_glyph_height = cast(f32) i32(should_snap)
}

//#endregion("miscellaneous")

//#region("scope stack")

/* Scope stacking ease of use interface.

View: Extents in 2D for the relative space the the text is being drawn within.
Used with snap_to_view_extent to enforce position snapping.

Position: Used with a draw procedure that uses relative positioning will offset the incoming position by the given amount.
Scale   : Used with a draw procedure that uses relative scaling, will scale the procedures incoming scale by the given amount.
Zoom    : Used with a draw procedure that uses scaling via zoom, will scale the procedure's incoming font size & scale based on an 'canvas' camera's notion of it.
*/

@(deferred_in = auto_pop_font)
scope_font         :: #force_inline proc( ctx : ^Context, font     : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) }
push_font          :: #force_inline proc( ctx : ^Context, font     : Font_ID ) { assert(ctx != nil); append(& ctx.stack.font, font ) }
pop_font           :: #force_inline proc( ctx : ^Context                     ) { assert(ctx != nil); pop(& ctx.stack.font) }
auto_pop_font      :: #force_inline proc( ctx : ^Context, font     : Font_ID ) { assert(ctx != nil); pop(& ctx.stack.font) }

@(deferred_in = auto_pop_font_size)
scope_font_size    :: #force_inline proc( ctx : ^Context, px_size  : f32     ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) }
push_font_size     :: #force_inline proc( ctx : ^Context, px_size  : f32     ) { assert(ctx != nil); append(& ctx.stack.font_size, px_size) }
pop_font_size      :: #force_inline proc( ctx : ^Context                     ) { assert(ctx != nil); pop(& ctx.stack.font_size) }
auto_pop_font_size :: #force_inline proc( ctx : ^Context, px_size  : f32     ) { assert(ctx != nil); pop(& ctx.stack.font_size) }

@(deferred_in = auto_pop_colour )
scope_colour       :: #force_inline proc( ctx : ^Context, colour   : RGBAN   ) { assert(ctx != nil); append(& ctx.stack.colour, colour) }
push_colour        :: #force_inline proc( ctx : ^Context, colour   : RGBAN   ) { assert(ctx != nil); append(& ctx.stack.colour, colour) }
pop_colour         :: #force_inline proc( ctx : ^Context                     ) { assert(ctx != nil); pop(& ctx.stack.colour) }
auto_pop_colour    :: #force_inline proc( ctx : ^Context, colour   : RGBAN   ) { assert(ctx != nil); pop(& ctx.stack.colour) }

@(deferred_in = auto_pop_view)
scope_view         :: #force_inline proc( ctx : ^Context, view     : Vec2    ) { assert(ctx != nil); append(& ctx.stack.view, view) }
push_view          :: #force_inline proc( ctx : ^Context, view     : Vec2    ) { assert(ctx != nil); append(& ctx.stack.view, view) }
pop_view           :: #force_inline proc( ctx : ^Context                     ) { assert(ctx != nil); pop(& ctx.stack.view) }
auto_pop_view      :: #force_inline proc( ctx : ^Context, view     : Vec2    ) { assert(ctx != nil); pop(& ctx.stack.view) }

@(deferred_in = auto_pop_position)
scope_position     :: #force_inline proc( ctx : ^Context, position : Vec2    ) { assert(ctx != nil); append(& ctx.stack.position, position ) }
push_position      :: #force_inline proc( ctx : ^Context, position : Vec2    ) { assert(ctx != nil); append(& ctx.stack.position, position ) }
pop_position       :: #force_inline proc( ctx : ^Context                     ) { assert(ctx != nil); pop( & ctx.stack.position) }
auto_pop_position  :: #force_inline proc( ctx : ^Context, view     : Vec2    ) { assert(ctx != nil); pop( & ctx.stack.position) }

@(deferred_in = auto_pop_scale)
scope_scale        :: #force_inline proc( ctx : ^Context, scale    : Vec2    ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) }
push_scale         :: #force_inline proc( ctx : ^Context, scale    : Vec2    ) { assert(ctx != nil); append(& ctx.stack.scale, scale ) }
pop_scale          :: #force_inline proc( ctx : ^Context,                    ) { assert(ctx != nil); pop(& ctx.stack.scale) }
auto_pop_scale     :: #force_inline proc( ctx : ^Context, scale    : Vec2    ) { assert(ctx != nil); pop(& ctx.stack.scale) }

@(deferred_in = auto_pop_zoom )
scope_zoom         :: #force_inline proc( ctx : ^Context, zoom     : f32     ) { append(& ctx.stack.zoom, zoom ) }
push_zoom          :: #force_inline proc( ctx : ^Context, zoom     : f32     ) { append(& ctx.stack.zoom, zoom)  }
pop_zoom           :: #force_inline proc( ctx : ^Context                     ) { pop(& ctx.stack.zoom) }
auto_pop_zoom      :: #force_inline proc( ctx : ^Context, zoom     : f32     ) { pop(& ctx.stack.zoom) }

@(deferred_in = auto_pop_vpz)
scope_vpz    :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform  ) { 
	assert(ctx != nil)
	append(& ctx.stack.view,     camera.view     )
	append(& ctx.stack.position, camera.position )
	append(& ctx.stack.zoom,     camera.zoom     )
}
push_vpz     :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform  ) { 
	assert(ctx != nil)
	append(& ctx.stack.view,     camera.view     )
	append(& ctx.stack.position, camera.position )
	append(& ctx.stack.zoom,     camera.zoom     )
}
pop_vpz      :: #force_inline proc( ctx : ^Context ) {
	assert(ctx != nil)
	pop(& ctx.stack.view    )
	pop(& ctx.stack.position)
	pop(& ctx.stack.zoom    )
}
auto_pop_vpz :: #force_inline proc( ctx : ^Context, camera : VPZ_Transform ) { 
	assert(ctx != nil)
	pop(& ctx.stack.view    )
	pop(& ctx.stack.position)
	pop(& ctx.stack.zoom    )
}

//#endregion("scope stack")
