More cleanup, preparing VEFontCache for public repo

This commit is contained in:
Edward R. Gonzalez 2025-01-10 09:32:19 -05:00
parent 50dd6130c8
commit e23935db5b
15 changed files with 56 additions and 238 deletions

View File

@ -1,4 +1,4 @@
package vetext
package vefontcache
/* Note(Ed):
Original implementation has been changed moderately.

View File

@ -19,7 +19,7 @@ Features:
* Robust quality of life features:
* Tracks text layers!
* Push and pop stack for font, font_size, colour, view, position, scale and zoom!
* Enforce even only font-sizing [TODO]
* Enforce even only font-sizing (useful for linear-zoom) [TODO]
* Snap-positining to view for better hinting
* Basic or advanced text shaping via Harfbuzz
* All rendering is real-time, triangulation done on the CPU, vertex rendering and texture blitting on the gpu.

View File

@ -1,4 +1,4 @@
package vetext
package vefontcache
// There are only 4 actual regions of the atlas. E represents the atlas_decide_region detecting an oversized glyph.
// Note(Ed): None should never really occur anymore. So its safe to most likely add an assert when its detected.

View File

@ -1,4 +1,4 @@
package vetext
package vefontcache
/*
Note(Ed): This may be seperated in the future into another file dedending on how much is involved with supportin ear-clipping triangulation.

View File

@ -1,9 +1,9 @@
package vetext
package vefontcache
when false {
// TODO(Ed): Freetype support
// TODO(Ed): glyph caching cannot be handled in a 'font parser' abstraction. Just going to have explicit procedures to grab info neatly...
// TODO(Ed): glyph triangulation cannot be handled in a 'font parser' abstraction. Just going to have explicit procedures to grab info neatly...
cache_glyph_freetype :: proc(ctx: ^Context, font: Font_ID, glyph_index: Glyph, entry: ^Entry, bounds_0, bounds_1: Vec2, scale, translate: Vec2) -> b32
{
draw_filled_path_freetype :: proc( draw_list : ^Draw_List, outside_point : Vec2, path : []Vertex,

View File

@ -1,4 +1,4 @@
package vetext
package vefontcache
/*
Didn't want to splinter this into more files..

View File

@ -1,4 +1,4 @@
package vetext
package vefontcache
/*
Notes:
@ -24,7 +24,7 @@ import stbtt "vendor:stb/truetype"
Parser_Kind :: enum u32 {
STB_TrueType,
Freetype,
Freetype, // Currently not implemented.
}
Parser_Font_Info :: struct {
@ -63,40 +63,16 @@ Parser_Context :: struct {
parser_init :: proc( ctx : ^Parser_Context, kind : Parser_Kind )
{
// switch kind
// {
// case .Freetype:
// result := freetype.init_free_type( & ctx.ft_library )
// assert( result == freetype.Error.Ok, "VEFontCache.parser_init: Failed to initialize freetype" )
// case .STB_TrueType:
// Do nothing intentional
// }
ctx.kind = kind
}
parser_shutdown :: proc( ctx : ^Parser_Context ) {
// TODO(Ed): Implement
// Note: Not necesssary for stb_truetype
}
parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte ) -> (font : Parser_Font_Info, error : b32)
{
// switch ctx.kind
// {
// case .Freetype:
// when ODIN_OS == .Windows {
// error_status := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i32) len(data), 0, & font.freetype_info )
// if error != .Ok do error = true
// }
// else when ODIN_OS == .Linux {
// error := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i64) len(data), 0, & font.freetype_info )
// if error_status != .Ok do error = true
// }
// case .STB_TrueType:
error = ! stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 )
// }
font.label = label
font.data = data
@ -106,122 +82,36 @@ parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte )
parser_unload_font :: proc( font : ^Parser_Font_Info )
{
// switch font.kind {
// case .Freetype:
// error := freetype.done_face( font.freetype_info )
// assert( error == .Ok, "VEFontCache.parser_unload_font: Failed to unload freetype face" )
// case .STB_TrueType:
// Do Nothing
// }
// case .STB_TrueType:
// Do Nothing
}
parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph)
{
// profile(#procedure)
// switch font.kind
// {
// case .Freetype:
// when ODIN_OS == .Windows {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
// }
// else when ODIN_OS == .Linux {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
// }
// return
// case .STB_TrueType:
glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( font.stbtt_info, codepoint )
return
// }
// return Glyph(-1)
glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( font.stbtt_info, codepoint )
return
}
parser_free_shape :: #force_inline proc( font : Parser_Font_Info, shape : Parser_Glyph_Shape )
{
// switch font.kind
// {
// case .Freetype:
// delete(shape)
// case .STB_TrueType:
stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) )
// }
stbtt.FreeShape( font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) )
}
parser_get_codepoint_horizontal_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> ( advance, to_left_side_glyph : i32 )
{
// switch font.kind
// {
// case .Freetype:
// glyph_index : Glyph
// when ODIN_OS == .Windows {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
// }
// else when ODIN_OS == .Linux {
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
// }
// if glyph_index != 0
// {
// freetype.load_glyph( font.freetype_info, c.uint(codepoint), { .No_Bitmap, .No_Hinting, .No_Scale } )
// advance = i32(font.freetype_info.glyph.advance.x) >> 6
// to_left_side_glyph = i32(font.freetype_info.glyph.metrics.hori_bearing_x) >> 6
// }
// else
// {
// advance = 0
// to_left_side_glyph = 0
// }
// case .STB_TrueType:
stbtt.GetCodepointHMetrics( font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
// }
stbtt.GetCodepointHMetrics( font.stbtt_info, codepoint, & advance, & to_left_side_glyph )
return
}
parser_get_codepoint_kern_advance :: #force_inline proc "contextless" ( font : Parser_Font_Info, prev_codepoint, codepoint : rune ) -> i32
{
// switch font.kind
// {
// case .Freetype:
// prev_glyph_index : Glyph
// glyph_index : Glyph
// when ODIN_OS == .Windows {
// prev_glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) prev_codepoint )
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, transmute(u32) codepoint )
// }
// else when ODIN_OS == .Linux {
// prev_glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) prev_codepoint )
// glyph_index = transmute(Glyph) freetype.get_char_index( font.freetype_info, cast(u64) codepoint )
// }
// if prev_glyph_index != 0 && glyph_index != 0
// {
// kerning : freetype.Vector
// font.freetype_info.driver.clazz.get_kerning( font.freetype_info, transmute(u32) prev_codepoint, transmute(u32) codepoint, & kerning )
// }
// case .STB_TrueType:
kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint )
return kern
// }
// return -1
kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint )
return kern
}
parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : Parser_Font_Info ) -> (ascent, descent, line_gap : i32 )
{
// switch font.kind
// {
// case .Freetype:
// info := font.freetype_info
// ascent = i32(info.ascender)
// descent = i32(info.descender)
// line_gap = i32(info.height) - (ascent - descent)
// case .STB_TrueType:
stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap )
// }
stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap )
return
}
@ -230,122 +120,47 @@ parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info,
// profile(#procedure)
bounds_0, bounds_1 : Vec2i
// switch font.kind
// {
// case .Freetype:
// freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } )
x0, y0, x1, y1 : i32
success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
// metrics := font.freetype_info.glyph.metrics
// bounds_0 = {i32(metrics.hori_bearing_x), i32(metrics.hori_bearing_y - metrics.height)}
// bounds_1 = {i32(metrics.hori_bearing_x + metrics.width), i32(metrics.hori_bearing_y)}
// case .STB_TrueType:
x0, y0, x1, y1 : i32
success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
// assert( success )
bounds_0 = { x0, y0 }
bounds_1 = { x1, y1 }
// }
bounds_0 = { x0, y0 }
bounds_1 = { x1, y1 }
bounds = { vec2(bounds_0), vec2(bounds_1) }
return
}
parser_get_glyph_shape :: #force_inline proc ( font : Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error)
{
// switch font.kind
// {
// case .Freetype:
// // TODO(Ed): Don't do this, going a completely different route for handling shapes.
// // This abstraction fails to be time-saving or performant.
// case .STB_TrueType:
stb_shape : [^]stbtt.vertex
nverts := stbtt.GetGlyphShape( font.stbtt_info, cast(i32) glyph_index, & stb_shape )
shape_raw := transmute( ^runtime.Raw_Dynamic_Array) & shape
shape_raw.data = stb_shape
shape_raw.len = int(nverts)
shape_raw.cap = int(nverts)
shape_raw.allocator = runtime.nil_allocator()
error = Allocator_Error.None
// return
// }
stb_shape : [^]stbtt.vertex
nverts := stbtt.GetGlyphShape( font.stbtt_info, cast(i32) glyph_index, & stb_shape )
shape_raw := transmute( ^runtime.Raw_Dynamic_Array) & shape
shape_raw.data = stb_shape
shape_raw.len = int(nverts)
shape_raw.cap = int(nverts)
shape_raw.allocator = runtime.nil_allocator()
error = Allocator_Error.None
return
}
parser_is_glyph_empty :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> b32
{
// switch font.kind
// {
// case .Freetype:
// error := freetype.load_glyph( font.freetype_info, cast(u32) glyph_index, { .No_Bitmap, .No_Hinting, .No_Scale } )
// if error == .Ok
// {
// if font.freetype_info.glyph.format == .Outline {
// return font.freetype_info.glyph.outline.n_points == 0
// }
// else if font.freetype_info.glyph.format == .Bitmap {
// return font.freetype_info.glyph.bitmap.width == 0 && font.freetype_info.glyph.bitmap.rows == 0;
// }
// }
// return false
// case .STB_TrueType:
return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index )
// }
// return false
return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index )
}
parser_scale :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
// profile(#procedure)
// size_scale := size < 0.0 ? parser_scale_for_pixel_height( font, -size ) : parser_scale_for_mapping_em_to_pixels( font, size )
size_scale := size > 0.0 ? parser_scale_for_pixel_height( font, size ) : parser_scale_for_mapping_em_to_pixels( font, -size )
return size_scale
}
parser_scale_for_pixel_height :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
// switch font.kind {
// case .Freetype:
// freetype.set_pixel_sizes( font.freetype_info, 0, cast(u32) size )
// size_scale := size / cast(f32)font.freetype_info.units_per_em
// return size_scale
// case.STB_TrueType:
return stbtt.ScaleForPixelHeight( font.stbtt_info, size )
// }
// return 0
return stbtt.ScaleForPixelHeight( font.stbtt_info, size )
}
parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
// switch font.kind {
// case .Freetype:
// Inches_To_CM :: cast(f32) 2.54
// Points_Per_CM :: cast(f32) 28.3465
// CM_Per_Point :: cast(f32) 1.0 / DPT_DPCM
// CM_Per_Pixel :: cast(f32) 1.0 / DPT_PPCM
// DPT_DPCM :: cast(f32) 72.0 * Inches_To_CM // 182.88 points/dots per cm
// DPT_PPCM :: cast(f32) 96.0 * Inches_To_CM // 243.84 pixels per cm
// DPT_DPI :: cast(f32) 72.0
// // TODO(Ed): Don't assume the dots or pixels per inch.
// system_dpi :: DPT_DPI
// FT_Font_Size_Point_Unit :: 1.0 / 64.0
// FT_Point_10 :: 64.0
// points_per_em := (size / system_dpi ) * DPT_DPI
// freetype.set_char_size( font.freetype_info, 0, cast(freetype.F26Dot6) f32(points_per_em * FT_Point_10), cast(u32) DPT_DPI, cast(u32) DPT_DPI )
// size_scale := f32(f64(size) / cast(f64) font.freetype_info.units_per_em)
// return size_scale
// case .STB_TrueType:
return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size )
// }
// return 0
return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size )
}

View File

@ -1,4 +1,4 @@
package vetext
package vefontcache
import "base:builtin"
resize_soa_non_zero :: non_zero_resize_soa

View File

@ -1,4 +1,4 @@
package vetext
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.
*/
@ -190,8 +190,8 @@ shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry
f32(hb_gposition.x_advance) * font_scale,
f32(hb_gposition.y_advance) * font_scale
}
(position^) += advance
(max_line_width^) = max(max_line_width^, position.x)
(position^) += advance
(max_line_width^) = max(max_line_width^, position.x)
is_empty := parser_is_glyph_empty(entry.parser_info, glyph)
if ! is_empty {

View File

@ -1,7 +1,7 @@
/*
See: https://github.com/Ed94/VEFontCache-Odin
*/
package vetext
package vefontcache
import "base:runtime"
@ -761,6 +761,7 @@ draw_text_normalized_space :: proc( ctx : ^Context,
)
}
// TODO(Ed): Use this in sectr over normalized space
// Equivalent to draw_text_shape_normalized_space, however position's units is scaled to view and must be normalized.
@(optimization_mode="favor_size")
draw_text_shape_view_space :: #force_inline proc( ctx : ^Context,
@ -803,6 +804,7 @@ draw_text_shape_view_space :: #force_inline proc( ctx : ^Context,
)
}
// TODO(Ed): Use this in sectr over normalized space
// Equivalent to draw_text_normalized_space, however position's units is scaled to view and must be normalized.
@(optimization_mode = "favor_size")
draw_text_view_space :: proc(ctx : ^Context,

View File

@ -281,12 +281,13 @@ frametime_delta32 :: #force_inline proc "contextless" () -> f32 {
return cast(f32) get_state().frametime.delta_ms
}
app_config :: #force_inline proc "contextless" () -> AppConfig { return get_state().config }
app_color_theme :: #force_inline proc "contextless" () -> AppColorTheme { return get_state().config.color_theme }
debug_data :: #force_inline proc "contextless" () -> ScratchData { return get_state().debug }
get_frametime :: #force_inline proc "contextless" () -> FrameTime { return get_state().frametime }
get_default_font :: #force_inline proc "contextless" () -> FontID { return get_state().default_font }
get_input_state :: #force_inline proc "contextless" () -> InputState { return (get_state().input ^) }
app_config :: #force_inline proc "contextless" () -> AppConfig { return get_state().config }
app_color_theme :: #force_inline proc "contextless" () -> AppColorTheme { return get_state().config.color_theme }
debug_data :: #force_inline proc "contextless" () -> ScratchData { return get_state().debug }
get_frametime :: #force_inline proc "contextless" () -> FrameTime { return get_state().frametime }
get_default_font :: #force_inline proc "contextless" () -> FontID { return get_state().default_font }
get_input_state :: #force_inline proc "contextless" () -> InputState { return (get_state().input ^) }
get_screen_extent :: #force_inline proc "contextless" () -> Extents2 { return get_state().app_window.extent }
get_ui_context_mut :: #force_inline proc "contextless" () -> ^UI_State { return get_state().ui_context }
set_ui_context :: #force_inline proc "contextless" ( ui : ^UI_State ) { get_state().ui_context = ui }

View File

@ -272,20 +272,21 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// Load default font
path_roboto_regular := strings.concatenate( { Path_Assets, "Roboto-Regular.ttf"} )
font_roboto_regular = font_load( path_roboto_regular, "Roboto Regular", 32.0 )
path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" } )
font_firacode = font_load( path_firacode, "FiraCode", 16.0 )
// path_roboto_regular := strings.concatenate( { Path_Assets, "Roboto-Regular.ttf"} )
// font_roboto_regular = font_load( path_roboto_regular, "Roboto Regular", 32.0 )
// path_fira_cousine := strings.concatenate( { Path_Assets, "FiraCousine-Regular.ttf" } )
// font_fira_cousine = font_load( path_fira_cousine, "Fira Cousine", 16.0 )
default_font = font_roboto_regular
default_font = font_firacode
// Aysnc load the others
// path_arial_unicode_ms := strings.concatenate( { Path_Assets, "Arial Unicode MS.ttf" } )
// font_arial_unicode_ms = font_load( path_arial_unicode_ms, "Arial_Unicode_MS", 16.0 )
// path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" } )
// font_firacode = font_load( path_firacode, "FiraCode", 16.0 )
// path_neodgm_code := strings.concatenate( { Path_Assets, "neodgm_code.ttf"} )
// font_neodgm_code = font_load( path_neodgm_code, "NeoDunggeunmo Code", 32.0 )
@ -299,7 +300,6 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// path_rec_mono_linear := strings.concatenate( { Path_Assets, "RecMonoLinear-Regular-1.084.ttf" })
// font_rec_mono_linear = font_load( path_rec_mono_linear, "RecMonoLinear Regular", 32.0 )
// path_roboto_mono_regular := strings.concatenate( { Path_Assets, "RobotoMono-Regular.ttf"} )
// font_roboto_mono_regular = font_load( path_roboto_mono_regular, "Roboto Mono Regular", 32.0 )

View File

@ -212,8 +212,8 @@ screen_to_ws_view_pos :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 {
// Centered screen space to conventional screen space used for rendering
screen_to_render_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 {
screen_extent := transmute(Vec2) get_state().app_window.extent
return pos * {1, 1} + { screen_extent.x, screen_extent.y }
screen_extent := transmute(Vec2) get_screen_extent()
return pos + screen_extent
}
// TODO(Ed): These should assume a cam_context or have the ability to provide it in params