WIP: Updating public repo with latest version

This commit is contained in:
2025-01-10 09:07:26 -05:00
parent d329327555
commit 36cc557975
13 changed files with 2724 additions and 1709 deletions

View File

@@ -2,11 +2,17 @@ package vefontcache
/*
Notes:
This is a minimal wrapper I originally did incase something than stb_truetype is introduced in the future.
Otherwise, its essentially 1:1 with it.
Freetype will do memory allocations and has an interface the user can implement.
That interface is not exposed from this parser but could be added to parser_init.
Freetype isn't really supported and its not a high priority (pretty sure its too slow).
~~Freetype will do memory allocations and has an interface the user can implement.~~
~~That interface is not exposed from this parser but could be added to parser_init.~~
STB_Truetype has macros for its allocation unfortuantely
STB_Truetype:
* Has macros for its allocation unfortuantely.
TODO(Ed): Just keep a local version of stb_truetype and modify it to support a sokol/odin compatible allocator.
Already wanted to do so anyway to evaluate the glyph point shape implementation on its side.
*/
import "base:runtime"
@@ -14,7 +20,7 @@ import "core:c"
import "core:math"
import "core:slice"
import stbtt "vendor:stb/truetype"
import freetype "thirdparty:freetype"
// import freetype "thirdparty:freetype"
Parser_Kind :: enum u32 {
STB_TrueType,
@@ -26,7 +32,7 @@ Parser_Font_Info :: struct {
kind : Parser_Kind,
using _ : struct #raw_union {
stbtt_info : stbtt.fontinfo,
freetype_info : freetype.Face
// freetype_info : freetype.Face
},
data : []byte,
}
@@ -52,20 +58,20 @@ Parser_Glyph_Shape :: [dynamic]Parser_Glyph_Vertex
Parser_Context :: struct {
kind : Parser_Kind,
ft_library : freetype.Library,
// ft_library : freetype.Library,
}
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" )
// 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:
// case .STB_TrueType:
// Do nothing intentional
}
// }
ctx.kind = kind
}
@@ -74,24 +80,23 @@ parser_shutdown :: proc( ctx : ^Parser_Context ) {
// TODO(Ed): Implement
}
parser_load_font :: proc( ctx : ^Parser_Context, label : string, data : []byte ) -> (font : Parser_Font_Info)
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 := freetype.new_memory_face( ctx.ft_library, raw_data(data), cast(i32) len(data), 0, & font.freetype_info )
if error != .Ok do return
}
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 != .Ok do return
}
// 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:
success := stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 )
if ! success do return
}
// case .STB_TrueType:
error = ! stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 )
// }
font.label = label
font.data = data
@@ -101,158 +106,163 @@ 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" )
// 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:
// case .STB_TrueType:
// Do Nothing
}
// }
}
parser_find_glyph_index :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph)
parser_find_glyph_index :: #force_inline proc "contextless" ( font : Parser_Font_Info, codepoint : rune ) -> (glyph_index : Glyph)
{
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 )
}
// 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
case .STB_TrueType:
glyph_index = transmute(Glyph) stbtt.FindGlyphIndex( & font.stbtt_info, codepoint )
return
}
return Glyph(-1)
// }
// return Glyph(-1)
}
parser_free_shape :: proc( font : ^Parser_Font_Info, shape : Parser_Glyph_Shape )
parser_free_shape :: #force_inline proc( font : Parser_Font_Info, shape : Parser_Glyph_Shape )
{
switch font.kind
{
case .Freetype:
delete(shape)
// switch font.kind
// {
// case .Freetype:
// delete(shape)
case .STB_TrueType:
stbtt.FreeShape( & font.stbtt_info, transmute( [^]stbtt.vertex) raw_data(shape) )
}
// case .STB_TrueType:
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 )
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 )
}
// 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
}
// 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 )
}
// case .STB_TrueType:
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
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 )
}
// 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 )
}
// 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 )
// case .STB_TrueType:
kern := stbtt.GetCodepointKernAdvance( font.stbtt_info, prev_codepoint, codepoint )
return kern
}
return -1
// }
// return -1
}
parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^Parser_Font_Info ) -> (ascent, descent, line_gap : i32 )
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)
// 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 )
}
// case .STB_TrueType:
stbtt.GetFontVMetrics( font.stbtt_info, & ascent, & descent, & line_gap )
// }
return
}
parser_get_glyph_box :: #force_inline proc ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (bounds_0, bounds_1 : Vec2i)
parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> (bounds : Range2)
{
switch font.kind
{
case .Freetype:
freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } )
// profile(#procedure)
bounds_0, bounds_1 : Vec2i
metrics := font.freetype_info.glyph.metrics
// switch font.kind
// {
// case .Freetype:
// freetype.load_glyph( font.freetype_info, c.uint(glyph_index), { .No_Bitmap, .No_Hinting, .No_Scale } )
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)}
// metrics := font.freetype_info.glyph.metrics
case .STB_TrueType:
// 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 )
success := cast(bool) stbtt.GetGlyphBox( font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
// assert( success )
bounds_0 = { i32(x0), i32(y0) }
bounds_1 = { i32(x1), i32(y1) }
}
bounds_0 = { x0, y0 }
bounds_1 = { x1, y1 }
// }
bounds = { vec2(bounds_0), vec2(bounds_1) }
return
}
parser_get_glyph_shape :: proc( font : ^Parser_Font_Info, glyph_index : Glyph ) -> (shape : Parser_Glyph_Shape, error : Allocator_Error)
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.
// 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:
// case .STB_TrueType:
stb_shape : [^]stbtt.vertex
nverts := stbtt.GetGlyphShape( & font.stbtt_info, cast(i32) glyph_index, & stb_shape )
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
@@ -260,83 +270,82 @@ parser_get_glyph_shape :: proc( font : ^Parser_Font_Info, glyph_index : Glyph )
shape_raw.cap = int(nverts)
shape_raw.allocator = runtime.nil_allocator()
error = Allocator_Error.None
return
}
// return
// }
return
}
parser_is_glyph_empty :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, glyph_index : Glyph ) -> b32
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
// 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
// case .STB_TrueType:
return stbtt.IsGlyphEmpty( font.stbtt_info, cast(c.int) glyph_index )
// }
// return false
}
parser_scale :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32
parser_scale :: #force_inline proc "contextless" ( font : Parser_Font_Info, size : f32 ) -> f32
{
size_scale := size < 0.0 ? \
parser_scale_for_pixel_height( font, -size ) \
: parser_scale_for_mapping_em_to_pixels( font, size )
// size_scale = 1.0
// 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
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
// 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
// case.STB_TrueType:
return stbtt.ScaleForPixelHeight( font.stbtt_info, size )
// }
// return 0
}
parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font : ^Parser_Font_Info, size : f32 ) -> f32
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
// 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
// // 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
// 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
// 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
// case .STB_TrueType:
return stbtt.ScaleForMappingEmToPixels( font.stbtt_info, size )
// }
// return 0
}