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 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. 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" import "core:c" import "core:math" import "core:slice" import stbtt "vendor:stb/truetype" // import freetype "thirdparty:freetype" Parser_Kind :: enum u32 { STB_TrueType, Freetype, } Parser_Font_Info :: struct { label : string, kind : Parser_Kind, using _ : struct #raw_union { stbtt_info : stbtt.fontinfo, // freetype_info : freetype.Face }, data : []byte, } Glyph_Vert_Type :: enum u8 { None, Move = 1, Line, Curve, Cubic, } // Based directly off of stb_truetype's vertex Parser_Glyph_Vertex :: struct { x, y : i16, contour_x0, contour_y0 : i16, contour_x1, contour_y1 : i16, type : Glyph_Vert_Type, padding : u8, } // A shape can be a dynamic array free_type or an opaque set of data handled by stb_truetype Parser_Glyph_Shape :: [dynamic]Parser_Glyph_Vertex Parser_Context :: struct { kind : Parser_Kind, // 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" ) // case .STB_TrueType: // Do nothing intentional // } ctx.kind = kind } parser_shutdown :: proc( ctx : ^Parser_Context ) { // TODO(Ed): Implement } 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 font.kind = ctx.kind return } 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 // } } 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) } 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) ) // } } 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 ) // } 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 } 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 ) // } return } parser_get_bounds :: #force_inline proc "contextless" ( font : Parser_Font_Info, glyph_index : Glyph ) -> (bounds : Range2) { // 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 } ) // 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 = { 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 // } 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 } 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 } 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 }