progress on VEFontCache port
This commit is contained in:
		| @@ -15,6 +15,8 @@ Changes: | ||||
| */ | ||||
| package VEFontCache | ||||
|  | ||||
| Advance_Snap_Smallfont_Size :: 12 | ||||
|  | ||||
| FontID  :: distinct i64 | ||||
| Glyph   :: distinct i32 | ||||
|  | ||||
| @@ -53,10 +55,6 @@ Entry :: struct { | ||||
| 	shaper_info : ^ShaperInfo, | ||||
| 	id          : FontID, | ||||
| 	used        : b32, | ||||
|  | ||||
| 	// Note(Ed) : Not sure how I feel about the size specification here | ||||
| 	// I rather have different size glyphs for a font on demand (necessary for the canvas UI) | ||||
| 	// Might be mis-understaning how this cache works... | ||||
| 	size        : f32, | ||||
| 	size_scale  : f32, | ||||
| } | ||||
| @@ -94,7 +92,8 @@ Context :: struct { | ||||
| 	curve_quality  : u32, | ||||
| 	text_shape_adv : b32, | ||||
|  | ||||
| 	debug_print_verbose : b32 | ||||
| 	debug_print         : b32, | ||||
| 	debug_print_verbose : b32, | ||||
| } | ||||
|  | ||||
| get_cursor_pos :: proc( ctx : ^Context                  ) -> Vec2 { return ctx.cursor_pos } | ||||
| @@ -114,6 +113,54 @@ font_key_from_label :: #force_inline proc( label : string ) -> u64 { | ||||
| 	return hash | ||||
| } | ||||
|  | ||||
| // ve_fontcache_configure_snap | ||||
| configure_snap :: proc( ctx : ^Context, snap_width, snap_height : u32 ) { | ||||
| 	assert( ctx != nil ) | ||||
| 	ctx.snap_width  = snap_width | ||||
| 	ctx.snap_height = snap_height | ||||
| } | ||||
|  | ||||
| // For a provided alpha value, | ||||
| // allows the function to calculate the position of a point along the curve at any given fraction of its total length | ||||
| // ve_fontcache_eval_bezier (quadratic) | ||||
| eval_point_on_bezier3 :: proc( p0, p1, p2 : Vec2, alpha : f32 ) -> Vec2 | ||||
| { | ||||
| 	starting_point := p0 * (1 - alpha) * (1 - alpha) | ||||
| 	control_point  := p1 * 2.0 * (1 - alpha) | ||||
| 	end_point      := p2 * alpha * alpha | ||||
|  | ||||
| 	point := starting_point + control_point + end_point | ||||
| 	return point | ||||
| } | ||||
|  | ||||
| // For a provided alpha value, | ||||
| // allows the function to calculate the position of a point along the curve at any given fraction of its total length | ||||
| // ve_fontcache_eval_bezier (cubic) | ||||
| eval_point_on_bezier4 :: proc( p0, p1, p2, p3 : Vec2, alpha : f32 ) -> Vec2 | ||||
| { | ||||
| 	start_point := p0 * (1 - alpha) * (1 - alpha) * (1 - alpha) | ||||
| 	control_a   := p1 * 3 * (1 - alpha) * (1 - alpha) * alpha | ||||
| 	control_b   := p2 * 3 * (1 - alpha) * alpha * alpha | ||||
| 	end_point   := p3 * alpha * alpha * alpha | ||||
|  | ||||
| 	point := start_point + control_a + control_b + end_point | ||||
| 	return point | ||||
| } | ||||
|  | ||||
| screenspace_x_form :: proc( position, scale : ^Vec2, width, height : f32 ) { | ||||
| 	scale.x    = (scale.x / width ) * 2.0 | ||||
| 	scale.y    = (scale.y / height) * 2.0 | ||||
| 	position.x = position.x * (2.0 / width) - 1.0 | ||||
| 	position.y = position.y * (2.0 / width) - 1.0 | ||||
| } | ||||
|  | ||||
| textspace_x_form :: proc( position, scale : ^Vec2, width, height : f32 ) { | ||||
| 	position.x /= width | ||||
| 	position.y /= height | ||||
| 	scale.x    /= width | ||||
| 	scale.y    /= height | ||||
| } | ||||
|  | ||||
| InitAtlasRegionParams :: struct { | ||||
| 	width  : u32, | ||||
| 	height : u32, | ||||
| @@ -182,7 +229,6 @@ init :: proc( ctx : ^Context, | ||||
| 	glyph_draw_params           := InitGlyphDrawParams_Default, | ||||
| 	shape_cache_params          := InitShapeCacheParams_Default, | ||||
| 	curve_quality               : u32 = 6, | ||||
| 	advance_snap_smallfont_size : u32 = 12, | ||||
| 	entires_reserve             : u32 = Kilobyte, | ||||
| 	temp_path_reserve           : u32 = Kilobyte, | ||||
| 	temp_codepoint_seen_reserve : u32 = 512, | ||||
| @@ -358,40 +404,6 @@ unload_font :: proc( ctx : ^Context, font : FontID ) | ||||
| 	shaper_unload_font( entry.shaper_info ) | ||||
| } | ||||
|  | ||||
| // ve_fontcache_configure_snap | ||||
| configure_snap :: proc( ctx : ^Context, snap_width, snap_height : u32 ) { | ||||
| 	assert( ctx != nil ) | ||||
| 	ctx.snap_width  = snap_width | ||||
| 	ctx.snap_height = snap_height | ||||
| } | ||||
|  | ||||
| // For a provided alpha value, | ||||
| // allows the function to calculate the position of a point along the curve at any given fraction of its total length | ||||
| // ve_fontcache_eval_bezier (quadratic) | ||||
| eval_point_on_bezier3 :: proc( p0, p1, p2 : Vec2, alpha : f32 ) -> Vec2 | ||||
| { | ||||
| 	starting_point := p0 * (1 - alpha) * (1 - alpha) | ||||
| 	control_point  := p1 * 2.0 * (1 - alpha) | ||||
| 	end_point      := p2 * alpha * alpha | ||||
|  | ||||
| 	point := starting_point + control_point + end_point | ||||
| 	return point | ||||
| } | ||||
|  | ||||
| // For a provided alpha value, | ||||
| // allows the function to calculate the position of a point along the curve at any given fraction of its total length | ||||
| // ve_fontcache_eval_bezier (cubic) | ||||
| eval_point_on_bezier4 :: proc( p0, p1, p2, p3 : Vec2, alpha : f32 ) -> Vec2 | ||||
| { | ||||
| 	start_point := p0 * (1 - alpha) * (1 - alpha) * (1 - alpha) | ||||
| 	control_a   := p1 * 3 * (1 - alpha) * (1 - alpha) * alpha | ||||
| 	control_b   := p2 * 3 * (1 - alpha) * alpha * alpha | ||||
| 	end_point   := p3 * alpha * alpha * alpha | ||||
|  | ||||
| 	point := start_point + control_a + control_b + end_point | ||||
| 	return point | ||||
| } | ||||
|  | ||||
| cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale, translate : Vec2  ) -> b32 | ||||
| { | ||||
| 	assert( ctx != nil ) | ||||
| @@ -513,20 +525,6 @@ cache_glyph :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph, scale, | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| screenspace_x_form :: proc( position, scale : ^Vec2, width, height : f32 ) { | ||||
| 	scale.x    = (scale.x / width ) * 2.0 | ||||
| 	scale.y    = (scale.y / height) * 2.0 | ||||
| 	position.x = position.x * (2.0 / width) - 1.0 | ||||
| 	position.y = position.y * (2.0 / width) - 1.0 | ||||
| } | ||||
|  | ||||
| textspace_x_form :: proc( position, scale : ^Vec2, width, height : f32 ) { | ||||
| 	position.x /= width | ||||
| 	position.y /= height | ||||
| 	scale.x    /= width | ||||
| 	scale.y    /= height | ||||
| } | ||||
|  | ||||
| cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph ) | ||||
| { | ||||
| 	assert( ctx != nil ) | ||||
| @@ -541,12 +539,83 @@ cache_glyph_to_atlas :: proc( ctx : ^Context, font : FontID, glyph_index : Glyph | ||||
| 	bounds_width  := bounds_1.x - bounds_0.x | ||||
| 	bounds_height := bounds_1.y - bounds_0.y | ||||
|  | ||||
| 	region, state, next_idx, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) | ||||
| 	region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) | ||||
|  | ||||
| 	// E region is special case and not cached to atlas. | ||||
| 	if region == .None || region == .E do return | ||||
| 	if region_kind == .None || region_kind == .E do return | ||||
|  | ||||
| 	// Grab an atlas LRU cache slot. | ||||
| 	lru_code    := font_glyph_lru_code( font, glyph_index ) | ||||
| 	atlas_index := LRU_get( & region.state, lru_code ) | ||||
| 	if atlas_index == -1 | ||||
| 	{ | ||||
| 		if region.next_idx < region.state.capacity | ||||
| 		{ | ||||
| 			evicted         := LRU_put( & region.state, lru_code, i32(region.next_idx) ) | ||||
| 			atlas_index      = i32(region.next_idx) | ||||
| 			region.next_idx += 1 | ||||
| 			assert( evicted == lru_code ) | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			next_evict_codepoint := LRU_get_next_evicted( & region.state ) | ||||
| 			assert( next_evict_codepoint != 0xFFFFFFFFFFFFFFFF ) | ||||
|  | ||||
| 			atlas_index = LRU_peek( & region.state, next_evict_codepoint ) | ||||
| 			assert( atlas_index != -1 ) | ||||
|  | ||||
| 			evicted := LRU_put( & region.state, lru_code, atlas_index ) | ||||
| 			assert( evicted == next_evict_codepoint ) | ||||
| 		} | ||||
|  | ||||
| 		assert( LRU_get( & region.state, lru_code ) != - 1 ) | ||||
| 	} | ||||
|  | ||||
| 	if ctx.debug_print | ||||
| 	{ | ||||
| 		@static debug_total_cached : i32 = 0 | ||||
| 		logf("glyph %v%v( %v ) caching to atlas region %v at idx %d. %d total glyphs cached.\n", i32(glyph_index), rune(glyph_index), cast(rune) region_kind, atlas_index, debug_total_cached) | ||||
| 		debug_total_cached += 1 | ||||
| 	} | ||||
|  | ||||
| 	// Draw oversized glyph to update FBO | ||||
| 	glyph_draw_scale       := over_sample * entry.size_scale | ||||
| 	glyph_draw_translate   := Vec2 { f32(bounds_0.x), f32(bounds_0.y) } * glyph_draw_scale + Vec2{ f32(ctx.atlas.glyph_padding), f32(ctx.atlas.glyph_padding) } | ||||
| 	glyph_draw_translate.x  = cast(f32) (i32(glyph_draw_translate.x + 0.9999999)) | ||||
| 	glyph_draw_translate.y  = cast(f32) (i32(glyph_draw_translate.y + 0.9999999)) | ||||
|  | ||||
| 	// Allocate a glyph_update_FBO region | ||||
| 	// gwidth_scaled_px =  | ||||
|  | ||||
| 	// Calculate the src and destination regions | ||||
|  | ||||
| 	// Advance glyph_update_batch_x and calculate final glyph drawing transform | ||||
|  | ||||
| 	// Queue up clear on target region on atlas | ||||
|  | ||||
| 	// Queue up a blit from glyph_update_FBO to the atlas | ||||
|  | ||||
| 	// Render glyph to glyph_update_FBO | ||||
| 	// cache_glyph(  ) | ||||
| } | ||||
|  | ||||
| directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0 : Vec2i, bounds_width, bounds_height : u32, over_sample, position, scale : Vec2 ) | ||||
| { | ||||
| 	flush_glyph_buffer_to_atlas( ctx ) | ||||
|  | ||||
| 	glyph_draw_scale     := over_sample * entry.size_scale | ||||
| 	glyph_draw_translate := - Vec2{ f32(bounds_0.x), f32(bounds_0.y)} * glyph_draw_scale + Vec2{ f32(ctx.atlas.glyph_padding), f32(ctx.atlas.glyph_padding) } | ||||
| 	screenspace_x_form( & glyph_draw_translate, & glyph_draw_scale, f32(ctx.atlas.buffer_width), f32(ctx.atlas.buffer_height) ) | ||||
|  | ||||
| 	cache_glyph( ctx, entry.id, glyph, glyph_draw_scale, glyph_draw_translate ) | ||||
|  | ||||
| 	// Figure out the source rect. | ||||
|  | ||||
| 	// Figure out the destination rect. | ||||
|  | ||||
| 	// Add the glyph drawcall. | ||||
|  | ||||
| 	// Clear glyph_update_FBO. | ||||
| } | ||||
|  | ||||
| is_empty :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32 | ||||
| @@ -597,12 +666,36 @@ shape_text_cached :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) - | ||||
|  | ||||
| 			LRU_put( state, hash, shape_cache_idx ) | ||||
| 		} | ||||
|  | ||||
| 		shape_text_uncached( ctx, font, & shape_cache.storage.data[ shape_cache_idx ], text_utf8 ) | ||||
| 	} | ||||
|  | ||||
| 	return & shape_cache.storage.data[ shape_cache_idx ] | ||||
| } | ||||
|  | ||||
| shape_text_uncached :: proc() | ||||
| shape_text_uncached :: proc( ctx : ^Context, font : FontID, output : ^ShapedText, text_utf8 : string ) | ||||
| { | ||||
| 	assert( ctx != nil ) | ||||
| 	assert( font >= 0 && font < FontID(ctx.entries.num) ) | ||||
|  | ||||
| 	use_full_text_shape := ctx.text_shape_adv | ||||
| 	entry := & ctx.entries.data[ font ] | ||||
|  | ||||
| 	clear( output.glyphs ) | ||||
| 	clear( output.positions ) | ||||
|  | ||||
| 	ascent, descent, line_gap := parser_get_font_vertical_metrics( entry.parser_info ) | ||||
|  | ||||
| 	if use_full_text_shape | ||||
| 	{ | ||||
| 		assert( entry.shaper_info != nil ) | ||||
|  | ||||
| 		shaper_shape_from_text( & ctx.shaper_ctx, entry.shaper_info, output, text_utf8, ascent, descent, line_gap, entry.size, entry.size_scale ) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// We use our own fallback dumbass text shaping. | ||||
| 	// WARNING: PLEASE USE HARFBUZZ. GOOD TEXT SHAPING IS IMPORTANT FOR INTERNATIONALISATION. | ||||
|  | ||||
| 	 | ||||
| } | ||||
|   | ||||
| @@ -85,22 +85,22 @@ can_batch_glyph :: proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_in | ||||
|  | ||||
| 	// Decide which atlas to target | ||||
| 	assert( glyph_index != -1 ) | ||||
| 	region, state, next_index, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) | ||||
| 	region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) | ||||
|  | ||||
| 	// E region can't batch | ||||
| 	if region == .E || region == .None    do return false | ||||
| 	if ctx.temp_codepoint_seen_num > 1024 do return false | ||||
| 	if region_kind == .E || region_kind == .None do return false | ||||
| 	if ctx.temp_codepoint_seen_num > 1024        do return false | ||||
| 	// Note(Ed): Why 1024? | ||||
|  | ||||
| 	// Is this glyph cached? | ||||
| 	// lru_code    := u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 ) | ||||
| 	lru_code    := font_glyph_lru_code(font, glyph_index) | ||||
| 	atlas_index := LRU_get( state, lru_code ) | ||||
| 	atlas_index := LRU_get( & region.state, lru_code ) | ||||
| 	if atlas_index == - 1 | ||||
| 	{ | ||||
| 		if (next_index^) >= u32(state.capacity) { | ||||
| 		if region.next_idx >= u32( region.state.capacity) { | ||||
| 			// We will evict LRU. We must predict which LRU will get evicted, and if it's something we've seen then we need to take slowpath and flush batch. | ||||
| 			next_evict_codepoint := LRU_get_next_evicted( state ) | ||||
| 			next_evict_codepoint := LRU_get_next_evicted( & region.state ) | ||||
| 			seen := get( ctx.temp_codepoint_seen, next_evict_codepoint ) | ||||
| 			assert(seen != nil) | ||||
|  | ||||
| @@ -112,17 +112,17 @@ can_batch_glyph :: proc( ctx : ^Context, font : FontID, entry : ^Entry, glyph_in | ||||
| 		cache_glyph_to_atlas( ctx, font, glyph_index ) | ||||
| 	} | ||||
|  | ||||
| 	assert( LRU_get( state, lru_code ) != 1 ) | ||||
| 	assert( LRU_get( & region.state, lru_code ) != 1 ) | ||||
| 	set( ctx.temp_codepoint_seen, lru_code, true ) | ||||
| 	ctx.temp_codepoint_seen_num += 1 | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| decide_codepoint_region :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph | ||||
| ) -> (region : AtlasRegionKind, state : ^LRU_Cache, next_idx : ^u32, over_sample : Vec2) | ||||
| ) -> (region_kind : AtlasRegionKind, region : ^AtlasRegion, over_sample : Vec2) | ||||
| { | ||||
| 	if parser_is_glyph_empty( entry.parser_info, glyph_index ) { | ||||
| 		region = .None | ||||
| 		region_kind = .None | ||||
| 	} | ||||
|  | ||||
| 	bounds_0, bounds_1 := parser_get_glyph_box( entry.parser_info, glyph_index ) | ||||
| @@ -137,37 +137,32 @@ decide_codepoint_region :: proc( ctx : ^Context, entry : ^Entry, glyph_index : G | ||||
| 	if bounds_width_scaled <= atlas.region_a.width && bounds_height_scaled <= atlas.region_a.height | ||||
| 	{ | ||||
| 		// Region A for small glyphs. These are good for things such as punctuation. | ||||
| 		region   = .A | ||||
| 		state    = & atlas.region_a.state | ||||
| 		next_idx = & atlas.region_a.next_idx | ||||
| 		region_kind = .A | ||||
| 		region      = & atlas.region_a | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.region_b.width && bounds_height_scaled <= atlas.region_b.height | ||||
| 	{ | ||||
| 		// Region B for tall glyphs. These are good for things such as european alphabets. | ||||
| 		region   = .B | ||||
| 		state    = & atlas.region_b.state | ||||
| 		next_idx = & atlas.region_b.next_idx | ||||
| 		region_kind = .B | ||||
| 		region      = & atlas.region_b | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.region_c.width && bounds_height_scaled <= atlas.region_c.height | ||||
| 	{ | ||||
| 		// Region C for big glyphs. These are good for things such as asian typography. | ||||
| 		region   = .C | ||||
| 		state    = & atlas.region_c.state | ||||
| 		next_idx = & atlas.region_c.next_idx | ||||
| 		region_kind = .C | ||||
| 		region      = & atlas.region_c | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.region_d.width && bounds_height_scaled <= atlas.region_d.height | ||||
| 	{ | ||||
| 		// Region D for huge glyphs. These are good for things such as titles and 4k. | ||||
| 		region   = .D | ||||
| 		state    = & atlas.region_d.state | ||||
| 		next_idx = & atlas.region_d.next_idx | ||||
| 		region_kind = .D | ||||
| 		region      = & atlas.region_d | ||||
| 	} | ||||
| 	else if bounds_width_scaled <= atlas.buffer_width && bounds_height_scaled <= atlas.buffer_height | ||||
| 	{ | ||||
| 		// Region 'E' for massive glyphs. These are rendered uncached and un-oversampled. | ||||
| 		region   = .E | ||||
| 		state    = nil | ||||
| 		next_idx = nil | ||||
| 		region_kind = .E | ||||
| 		region      = nil | ||||
| 		if bounds_width_scaled <= atlas.buffer_width / 2 && bounds_height_scaled <= atlas.buffer_height / 2 { | ||||
| 			over_sample = { 2.0, 2.0 } | ||||
| 		} | ||||
| @@ -177,11 +172,10 @@ decide_codepoint_region :: proc( ctx : ^Context, entry : ^Entry, glyph_index : G | ||||
| 		return | ||||
| 	} | ||||
| 	else { | ||||
| 		region = .None | ||||
| 		region_kind = .None | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	assert(state    != nil) | ||||
| 	assert(next_idx != nil) | ||||
| 	assert(region != nil) | ||||
| 	return | ||||
| } | ||||
|   | ||||
| @@ -89,11 +89,6 @@ clear_draw_list :: proc( draw_list : ^DrawList ) { | ||||
| 	clear( draw_list.vertices ) | ||||
| } | ||||
|  | ||||
| directly_draw_massive_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph : Glyph, bounds_0 : Vec2i, bounds_width, bounds_height : u32, over_sample, position, scale : Vec2 ) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, position, scale : Vec2 ) -> b32 | ||||
| { | ||||
| 	// Glyph not in current font | ||||
| @@ -106,10 +101,10 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, | ||||
| 	bounds_height := bounds_1.y - bounds_0.y | ||||
|  | ||||
| 	// Decide which atlas to target | ||||
| 	region, state, next_idx, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) | ||||
| 	region_kind, region, over_sample := decide_codepoint_region( ctx, entry, glyph_index ) | ||||
|  | ||||
| 	// E region is special case and not cached to atlas | ||||
| 	if region == .E | ||||
| 	if region_kind == .E | ||||
| 	{ | ||||
| 		directly_draw_massive_glyph( ctx, entry, glyph_index, bounds_0, bounds_width, bounds_height, over_sample, position, scale ) | ||||
| 		return true | ||||
| @@ -118,7 +113,7 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, | ||||
| 	// Is this codepoint cached? | ||||
| 	// lru_code    := u64(glyph_index) + ( ( 0x100000000 * u64(entry.id) ) & 0xFFFFFFFF00000000 ) | ||||
| 	lru_code    := font_glyph_lru_code(entry.id, glyph_index) | ||||
| 	atlas_index := LRU_get( state, lru_code ) | ||||
| 	atlas_index := LRU_get( & region.state, lru_code ) | ||||
| 	if atlas_index == - 1 { | ||||
| 		return false | ||||
| 	} | ||||
| @@ -126,7 +121,7 @@ draw_cached_glyph :: proc( ctx : ^Context, entry : ^Entry, glyph_index : Glyph, | ||||
| 	atlas := & ctx.atlas | ||||
|  | ||||
| 	// Figure out the source bounding box in the atlas texture | ||||
| 	position, width, height := atlas_bbox( atlas, region, u32(atlas_index) ) | ||||
| 	position, width, height := atlas_bbox( atlas, region_kind, u32(atlas_index) ) | ||||
|  | ||||
| 	glyph_position := position | ||||
| 	glyph_width    := f32(bounds_width)  * entry.size_scale | ||||
|   | ||||
| @@ -106,6 +106,18 @@ parser_unload_font :: proc( font : ^ParserFontInfo ) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| parser_get_font_vertical_metrics :: proc( font : ^ParserFontInfo ) -> (ascent, descent, line_gap : i32 ) | ||||
| { | ||||
| 	switch font.kind | ||||
| 	{ | ||||
| 		case .Freetype: | ||||
|  | ||||
| 		case .STB_TrueType: | ||||
| 			stbtt.GetFontVMetrics( & font.stbtt_info, & ascent, & descent, & line_gap ) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| parser_scale_for_pixel_height :: #force_inline proc( font : ^ParserFontInfo, size : f32 ) -> f32 | ||||
| { | ||||
| 	switch font.kind { | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| package VEFontCache | ||||
| /* | ||||
| Note(Ed): The only reason I didn't directly use harfbuzz is because hamza exists and seems to be under active development as an alternative. | ||||
| */ | ||||
|  | ||||
| import "core:c" | ||||
| import "core:math" | ||||
| import "thirdparty:harfbuzz" | ||||
|  | ||||
| ShaperKind :: enum { | ||||
| @@ -56,3 +60,100 @@ shaper_unload_font :: proc( ctx : ^ShaperInfo ) | ||||
| 	if face != nil do harfbuzz.face_destroy( face ) | ||||
| 	if blob != nil do harfbuzz.blob_destroy( blob ) | ||||
| } | ||||
|  | ||||
| shaper_shape_from_text :: proc( ctx : ^ShaperContext, info : ^ShaperInfo, output :^ShapedText, text_utf8 : string, | ||||
| 	ascent, descent, line_gap : i32, size, size_scale : f32 ) | ||||
| { | ||||
| 	current_script := harfbuzz.Script.UNKNOWN | ||||
| 	hb_ucfunc      := harfbuzz.unicode_funcs_get_default() | ||||
| 	harfbuzz.buffer_clear_contents( ctx.hb_buffer ) | ||||
| 	assert( info.font != nil ) | ||||
|  | ||||
| 	ascent   := f32(ascent) | ||||
| 	descent  := f32(descent) | ||||
| 	line_gap := f32(line_gap) | ||||
|  | ||||
| 	position, vertical_position : f32 | ||||
| 	shape_run :: proc( buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^ShapedText, | ||||
| 		position, vertical_position : ^f32, | ||||
| 		ascent, descent, line_gap, size, size_scale : f32 ) | ||||
| 	{ | ||||
| 		// Set script and direction. We use the system's default langauge. | ||||
| 		// script = HB_SCRIPT_LATIN | ||||
| 		harfbuzz.buffer_set_script( buffer, script ) | ||||
| 		harfbuzz.buffer_set_direction( buffer, harfbuzz.script_get_horizontal_direction( script )) | ||||
| 		harfbuzz.set_language( buffer, harfbuzz.language_get_default() ) | ||||
|  | ||||
| 		// Perform the actual shaping of this run using HarfBuzz. | ||||
| 		harfbuzz.shape( font, buffer, nil, 0 ) | ||||
|  | ||||
| 		// Loop over glyphs and append to output buffer. | ||||
| 		glyph_count : u32 | ||||
| 		glyph_infos     := harfbuzz.buffer_get_glyph_infos( buffer, & glyph_count ) | ||||
| 		glyph_positions := harfbuzz.buffer_get_glyph_positions( buffer, & glyph_count ) | ||||
|  | ||||
| 		for index : i32; index < i32(glyph_count); index += 1 | ||||
| 		{ | ||||
| 			hb_glyph     := glyph_infos[ index ] | ||||
| 			hb_gposition := glyph_positions[ index ] | ||||
| 			glyph_id     := cast(Glyph) hb_glyph.codepoint | ||||
|  | ||||
| 			if hb_glyph.cluster > 0 | ||||
| 			{ | ||||
| 				(position^)           = 0.0 | ||||
| 				(vertical_position^) -= (ascent - descent + line_gap) * size_scale | ||||
| 				(vertical_position^)  = cast(f32) i32(vertical_position^ + 0.5) | ||||
| 				continue | ||||
| 			} | ||||
| 			if math.abs( size ) <= Advance_Snap_Smallfont_Size | ||||
| 			{ | ||||
| 				(position^) = math.ceil( position^ ) | ||||
| 			} | ||||
|  | ||||
| 			append( & output.glyphs, glyph_id ) | ||||
|  | ||||
| 			pos      := position^ | ||||
| 			v_pos    := vertical_position^ | ||||
| 			offset_x := f32(hb_gposition.x_offset) * size_scale | ||||
| 			offset_y := f32(hb_gposition.y_offset) * size_scale | ||||
| 			append( & output.positions, Vec2 { cast(f32) i32( pos + offset_x + 0.5 ), | ||||
| 				v_pos + offset_y, | ||||
| 			}) | ||||
|  | ||||
| 			(position^)          += f32(hb_gposition.x_advance) * size_scale | ||||
| 			(vertical_position^) += f32(hb_gposition.y_advance) * size_scale | ||||
| 		} | ||||
|  | ||||
| 		output.end_cursor_pos.x = position^ | ||||
| 		output.end_cursor_pos.y = vertical_position^ | ||||
| 		harfbuzz.buffer_clear_contents( buffer ) | ||||
| 	} | ||||
|  | ||||
| 	// Note(Original Author): | ||||
| 	// We first start with simple bidi and run logic. | ||||
| 	// True CTL is pretty hard and we don't fully support that; patches welcome! | ||||
|  | ||||
| 	for codepoint, byte_offset in text_utf8 | ||||
| 	{ | ||||
| 		script := harfbuzz.unicode_script( hb_ucfunc, cast(harfbuzz.Codepoint) codepoint ) | ||||
|  | ||||
| 		// Can we continue the current run? | ||||
| 		ScriptKind :: harfbuzz.Script | ||||
|  | ||||
| 		special_script : b32 = script == ScriptKind.UNKNOWN || script == ScriptKind.INHERITED || script == ScriptKind.COMMON | ||||
| 		if special_script || script == current_script { | ||||
| 			harfbuzz.buffer_add( ctx.hb_buffer, cast(harfbuzz.Codepoint) codepoint, codepoint == '\n' ? 1 : 0 ) | ||||
| 			current_script = special_script ? current_script : script | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// End current run since we've encountered a script change. | ||||
| 		shape_run( ctx.hb_buffer, current_script, info.font, output, & position, & vertical_position, ascent, descent, line_gap, size, size_scale ) | ||||
| 		harfbuzz.buffer_add( ctx.hb_buffer, cast(harfbuzz.Codepoint) codepoint, codepoint == '\n' ? 1 : 0 ) | ||||
| 		current_script = script | ||||
| 	} | ||||
|  | ||||
| 	// End the last run if needed | ||||
| 	shape_run( ctx.hb_buffer, current_script, info.font, output, & position, & vertical_position, ascent, descent, line_gap, size, size_scale ) | ||||
| 	return | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user