From bb6e1f78d3ac3385681026e5d475e2a08cd43903 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Tue, 2 Jul 2024 16:21:07 -0400 Subject: [PATCH] Update vefontcache to latest --- code/font/vefontcache/LRU.odin | 8 +- code/font/vefontcache/Readme.md | 33 ++- code/font/vefontcache/atlas.odin | 5 +- code/font/vefontcache/docs/Readme.md | 6 +- .../vefontcache/docs/draw_text_codepaths.pur | Bin 58941 -> 0 bytes code/font/vefontcache/draw.odin | 172 ++++++++++++++- code/font/vefontcache/mappings.odin | 2 +- code/font/vefontcache/misc.odin | 95 ++------ code/font/vefontcache/parser.odin | 203 ++---------------- code/font/vefontcache/shaped_text.odin | 19 +- code/font/vefontcache/shaper.odin | 51 ++--- code/font/vefontcache/vefontcache.odin | 58 +++-- 12 files changed, 305 insertions(+), 347 deletions(-) delete mode 100644 code/font/vefontcache/docs/draw_text_codepaths.pur diff --git a/code/font/vefontcache/LRU.odin b/code/font/vefontcache/LRU.odin index f04da54..69b2615 100644 --- a/code/font/vefontcache/LRU.odin +++ b/code/font/vefontcache/LRU.odin @@ -1,4 +1,4 @@ -package VEFontCache +package vefontcache /* The choice was made to keep the LRU cache implementation as close to the original as possible. @@ -54,7 +54,8 @@ pool_list_init :: proc( pool : ^PoolList, capacity : i32, dbg_name : string = "" } pool_list_free :: proc( pool : ^PoolList ) { - // TODO(Ed): Implement + delete( pool.items) + delete( pool.free_list) } pool_list_reload :: proc( pool : ^PoolList, allocator : Allocator ) { @@ -174,7 +175,8 @@ LRU_init :: proc( cache : ^LRU_Cache, capacity : i32, dbg_name : string = "" ) { } LRU_free :: proc( cache : ^LRU_Cache ) { - // TODO(Ed): Implement + pool_list_free( & cache.key_queue ) + delete( cache.table ) } LRU_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) { diff --git a/code/font/vefontcache/Readme.md b/code/font/vefontcache/Readme.md index d6583fe..b959d44 100644 --- a/code/font/vefontcache/Readme.md +++ b/code/font/vefontcache/Readme.md @@ -6,43 +6,38 @@ Its original purpose was for use in game engines, however its rendeirng quality See: [docs/Readme.md](docs/Readme.md) for the library's interface +## Building + +See [scripts/Readme.md](scripts/Readme.md) for building examples or utilizing the provided backends. + +Currently the scripts provided & the library itself where developed & tested on Windows. The library itself should not be limited to that OS platform however, just don't have the configuration setup for alternative platforms (yet). + +The library depends on freetype, harfbuzz, & stb_truetype currently to build. +Note: freetype and harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet). + ## Changes from orignal * Font Parser & Glyph shaper are abstracted to their own interface -* Font face parser info encapsulated in parser_info struct. * ve_fontcache_loadfile not ported (ust use core:os or os2, then call load_font) * Macro defines have been coverted (mostly) to runtime parameters * Support for hot_reloading +* Curve quality step granularity for glyph rendering can be set on a per font basis. ## TODOs -### Thirdparty support: - -* Setup freetype, harfbuzz, depedency management within the library - -### Documentation: - -* Pureref outline of draw_text exectuion -* Markdown general documentation - -### Content: - -* Port over the original demo utilizing sokol libraries instead -* Provide a sokol_gfx backend package - ### Additional Features: -* Support for freetype -* Support for harfbuzz +* Support for freetype (WIP, Currently a mess... and slow) +* Add ability to conditionally compile dependencies (so that the user may not need to resolve those packages). * Ability to set a draw transform, viewport and projection * By default the library's position is in unsigned normalized render space * Could implement a similar design to sokol_gp's interface -* Allow curve_quality to be set on a per-font basis ### Optimization: +* Check if its better to store the generated glyph vertices if they need to be re-cached or directly drawn. * Look into setting up multi-threading by giving each thread a context - * There is a heavy performance bottleneck in iterating the text/shape/glyphs on the cpu (single-thread) vs the actual rendering + * There is a heavy performance bottleneck in iterating the text/shape/glyphs on the cpu (single-thread) vs the actual rendering *(if doing thousands of drawing commands)* * draw_text can provide in the context a job list per thread for the user to thenk hookup to their own threading solution to handle. * Context would need to be segregated into staged data structures for each thread to utilize * Each should have their own? diff --git a/code/font/vefontcache/atlas.odin b/code/font/vefontcache/atlas.odin index 600a826..47876b0 100644 --- a/code/font/vefontcache/atlas.odin +++ b/code/font/vefontcache/atlas.odin @@ -1,4 +1,4 @@ -package VEFontCache +package vefontcache AtlasRegionKind :: enum u8 { None = 0x00, @@ -86,8 +86,7 @@ atlas_bbox :: proc( atlas : ^Atlas, region : AtlasRegionKind, local_idx : i32 ) return } -decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Glyph -) -> (region_kind : AtlasRegionKind, region : ^AtlasRegion, over_sample : Vec2) +decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> (region_kind : AtlasRegionKind, region : ^AtlasRegion, over_sample : Vec2) { if parser_is_glyph_empty(&entry.parser_info, glyph_index) { return .None, nil, {} diff --git a/code/font/vefontcache/docs/Readme.md b/code/font/vefontcache/docs/Readme.md index 010f9ce..b0c3aef 100644 --- a/code/font/vefontcache/docs/Readme.md +++ b/code/font/vefontcache/docs/Readme.md @@ -39,11 +39,11 @@ You'll find this used immediately in draw_text it acts as a way to snap the posi If snapping is not desired, set the snap_width and height before calling draw_text to 0. -## get_cursor_pos +### get_cursor_pos Will provide the current cursor_pos for the resulting text drawn. -## set_color +### set_color Sets the color to utilize on `DrawCall`s for FrameBuffer.Target or .Target_Uncached passes @@ -72,6 +72,6 @@ Will update the draw list layer with the latest offset based on the current leng Provides a Vec2 the width and height occupied by the provided text string. The y is measured to be the the largest glyph box bounds height of the text. The width is derived from the `end_cursor_pos` field from a `ShapedText` entry. -## get_font_vertical_metrics +### get_font_vertical_metrics A wrapper for `parser_get_font_vertical_metrics`. Will provide the ascent, descent, and line_gap for a font entry. diff --git a/code/font/vefontcache/docs/draw_text_codepaths.pur b/code/font/vefontcache/docs/draw_text_codepaths.pur deleted file mode 100644 index 801029cb8c7067835a2ac67f482eb4a0bf6a1761..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58941 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 diff --git a/code/font/vefontcache/draw.odin b/code/font/vefontcache/draw.odin index 983871e..5fcd883 100644 --- a/code/font/vefontcache/draw.odin +++ b/code/font/vefontcache/draw.odin @@ -1,4 +1,7 @@ -package VEFontCache +package vefontcache + +import "thirdparty:freetype" +import "core:slice" Vertex :: struct { pos : Vec2, @@ -29,6 +32,7 @@ DrawList :: struct { calls : [dynamic]DrawCall, } +// TODO(Ed): This was a rough translation of the raw values the orignal was using, need to give better names... FrameBufferPass :: enum u32 { None = 0, Glyph = 1, @@ -84,6 +88,162 @@ blit_quad :: proc( draw_list : ^DrawList, p0 : Vec2 = {0, 0}, p1 : Vec2 = {1, 1} return } +// TODO(Ed): glyph caching 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: FontID, glyph_index: Glyph, entry: ^Entry, bounds_0, bounds_1: Vec2, scale, translate: Vec2) -> b32 +{ + draw_filled_path_freetype :: proc( draw_list : ^DrawList, outside_point : Vec2, path : []Vertex, + scale := Vec2 { 1, 1 }, + translate := Vec2 { 0, 0 }, + debug_print_verbose : b32 = false + ) + { + if debug_print_verbose { + log("outline_path:") + for point in path { + vec := point.pos * scale + translate + logf(" %0.2f %0.2f", vec.x, vec.y ) + } + } + + v_offset := cast(u32) len(draw_list.vertices) + for point in path + { + transformed_point := Vertex { + pos = point.pos * scale + translate, + u = 0, + v = 0 + } + append( & draw_list.vertices, transformed_point ) + } + + if len(path) > 2 + { + indices := & draw_list.indices + for index : u32 = 1; index < cast(u32) len(path) - 1; index += 1 { + to_add := [3]u32 { + v_offset, + v_offset + index, + v_offset + index + 1 + } + append( indices, ..to_add[:] ) + } + + // Close the path by connecting the last vertex to the first two + to_add := [3]u32 { + v_offset, + v_offset + cast(u32)(len(path) - 1), + v_offset + 1 + } + append( indices, ..to_add[:] ) + } + } + + if glyph_index == Glyph(0) { + return false + } + + face := entry.parser_info.freetype_info + error := freetype.load_glyph(face, u32(glyph_index), {.No_Bitmap, .No_Scale}) + if error != .Ok { + return false + } + + glyph := face.glyph + if glyph.format != .Outline { + return false + } + + outline := &glyph.outline + if outline.n_points == 0 { + return false + } + + draw := DrawCall_Default + draw.pass = FrameBufferPass.Glyph + draw.start_index = cast(u32) len(ctx.draw_list.indices) + + contours := slice.from_ptr(cast( [^]i16) outline.contours, int(outline.n_contours)) + points := slice.from_ptr(cast( [^]freetype.Vector) outline.points, int(outline.n_points)) + tags := slice.from_ptr(cast( [^]u8) outline.tags, int(outline.n_points)) + + path := &ctx.temp_path + clear(path) + + outside := Vec2{ bounds_0.x - 21, bounds_0.y - 33 } + + start_index: int = 0 + for contour_index in 0 ..< int(outline.n_contours) + { + end_index := int(contours[contour_index]) + 1 + prev_point: Vec2 + first_point: Vec2 + + for idx := start_index; idx < end_index; idx += 1 + { + current_pos := Vec2 { f32( points[idx].x ), f32( points[idx].y ) } + if ( tags[idx] & 1 ) == 0 + { + // If current point is off-curve + if (idx == start_index || (tags[ idx - 1 ] & 1) != 0) + { + // current is the first or following an on-curve point + prev_point = current_pos + } + else + { + // current and previous are off-curve, calculate midpoint + midpoint := (prev_point + current_pos) * 0.5 + append( path, Vertex { pos = midpoint } ) // Add midpoint as on-curve point + if idx < end_index - 1 + { + // perform interp from prev_point to current_pos via midpoint + step := 1.0 / entry.curve_quality + for alpha : f32 = 0.0; alpha <= 1.0; alpha += step + { + bezier_point := eval_point_on_bezier3( prev_point, midpoint, current_pos, alpha ) + append( path, Vertex{ pos = bezier_point } ) + } + } + + prev_point = current_pos + } + } + else + { + if idx == start_index { + first_point = current_pos + } + if prev_point != (Vec2{}) { + // there was an off-curve point before this + append(path, Vertex{ pos = prev_point}) // Ensure previous off-curve is handled + } + append(path, Vertex{ pos = current_pos}) + prev_point = {} + } + } + + // ensure the contour is closed + if path[0].pos != path[ len(path) - 1 ].pos { + append(path, Vertex{pos = path[0].pos}) + } + draw_filled_path(&ctx.draw_list, bounds_0, path[:], scale, translate, ctx.debug_print_verbose) + clear(path) + start_index = end_index + } + + if len(path) > 0 { + draw_filled_path(&ctx.draw_list, outside, path[:], scale, translate, ctx.debug_print_verbose) + } + + draw.end_index = cast(u32) len(ctx.draw_list.indices) + if draw.end_index > draw.start_index { + append( & ctx.draw_list.calls, draw) + } + + return true +} + +// TODO(Ed): Is it better to cache the glyph vertices for when it must be re-drawn (directly or two atlas)? cache_glyph :: proc(ctx : ^Context, font : FontID, glyph_index : Glyph, entry : ^Entry, bounds_0, bounds_1 : Vec2, scale, translate : Vec2) -> b32 { // profile(#procedure) @@ -91,6 +251,12 @@ cache_glyph :: proc(ctx : ^Context, font : FontID, glyph_index : Glyph, entry : return false } + // Glyph shape handling are not abstractable between freetype and stb_truetype + if entry.parser_info.kind == .Freetype { + result := cache_glyph_freetype( ctx, font, glyph_index, entry, bounds_0, bounds_1, scale, translate ) + return result + } + shape, error := parser_get_glyph_shape(&entry.parser_info, glyph_index) assert(error == .None) if len(shape) == 0 { @@ -150,7 +316,7 @@ cache_glyph :: proc(ctx : ^Context, font : FontID, glyph_index : Glyph, entry : draw.end_index = u32(len(ctx.draw_list.indices)) if draw.end_index > draw.start_index { - append(&ctx.draw_list.calls, draw) + append( & ctx.draw_list.calls, draw) } parser_free_shape(&entry.parser_info, shape) @@ -475,7 +641,7 @@ draw_text_batch :: proc(ctx: ^Context, entry: ^Entry, shaped: ^ShapedText, bounds_size := Vec2 { vbounds_1.x - vbounds_0.x, vbounds_1.y - vbounds_0.y } shaped_position := shaped.positions[index] - glyph_translate := position + shaped_position * scale + glyph_translate := position + (shaped_position) * scale if region_kind == .E { diff --git a/code/font/vefontcache/mappings.odin b/code/font/vefontcache/mappings.odin index cc24907..46ea639 100644 --- a/code/font/vefontcache/mappings.odin +++ b/code/font/vefontcache/mappings.odin @@ -1,4 +1,4 @@ -package VEFontCache +package vefontcache import "core:hash" fnv64a :: hash.fnv64a diff --git a/code/font/vefontcache/misc.odin b/code/font/vefontcache/misc.odin index 1b04560..fe591ea 100644 --- a/code/font/vefontcache/misc.odin +++ b/code/font/vefontcache/misc.odin @@ -1,4 +1,4 @@ -package VEFontCache +package vefontcache import "base:runtime" import "core:simd" @@ -20,23 +20,23 @@ vec2i_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2 @(require_results) floor_vec2 :: proc "contextless" ( v : Vec2 ) -> Vec2 { return { floor_f32(v.x), floor_f32(v.y) } } // This buffer is used below excluisvely to prevent any allocator recusion when verbose logging from allocators. -// This means a single line is limited to 32k buffer (increase naturally if this SOMEHOW becomes a bottleneck...) -// Logger_Allocator_Buffer : [32 * Kilobyte]u8 +// This means a single line is limited to 4k buffer +// Logger_Allocator_Buffer : [4 * Kilobyte]u8 // log :: proc( msg : string, level := core_log.Level.Info, loc := #caller_location ) { -// temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) -// context.allocator = arena_allocator(& temp_arena) -// context.temp_allocator = arena_allocator(& temp_arena) + // temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) + // context.allocator = arena_allocator(& temp_arena) + // context.temp_allocator = arena_allocator(& temp_arena) -// core_log.log( level, msg, location = loc ) + // core_log.log( level, msg, location = loc ) // } // logf :: proc( fmt : string, args : ..any, level := core_log.Level.Info, loc := #caller_location ) { -// temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) -// context.allocator = arena_allocator(& temp_arena) -// context.temp_allocator = arena_allocator(& temp_arena) + // temp_arena : Arena; arena_init(& temp_arena, Logger_Allocator_Buffer[:]) + // context.allocator = arena_allocator(& temp_arena) + // context.temp_allocator = arena_allocator(& temp_arena) -// core_log.logf( level, fmt, ..args, location = loc ) + // core_log.logf( level, fmt, ..args, location = loc ) // } reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) { @@ -121,7 +121,7 @@ textspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, } } -Use_SIMD_For_Bezier_Ops :: true +Use_SIMD_For_Bezier_Ops :: false when ! Use_SIMD_For_Bezier_Ops { @@ -130,10 +130,10 @@ when ! Use_SIMD_For_Bezier_Ops // ve_fontcache_eval_bezier (quadratic) eval_point_on_bezier3 :: #force_inline proc "contextless" ( p0, p1, p2 : Vec2, alpha : f32 ) -> Vec2 { - p0 := vec2_64(p0) - p1 := vec2_64(p1) - p2 := vec2_64(p2) - alpha := f64(alpha) + // p0 := vec2_64(p0) + // p1 := vec2_64(p1) + // p2 := vec2_64(p2) + // alpha := f64(alpha) weight_start := (1 - alpha) * (1 - alpha) weight_control := 2.0 * (1 - alpha) * alpha @@ -152,11 +152,11 @@ when ! Use_SIMD_For_Bezier_Ops // ve_fontcache_eval_bezier (cubic) eval_point_on_bezier4 :: #force_inline proc "contextless" ( p0, p1, p2, p3 : Vec2, alpha : f32 ) -> Vec2 { - p0 := vec2_64(p0) - p1 := vec2_64(p1) - p2 := vec2_64(p2) - p3 := vec2_64(p3) - alpha := f64(alpha) + // p0 := vec2_64(p0) + // p1 := vec2_64(p1) + // p2 := vec2_64(p2) + // p3 := vec2_64(p3) + // alpha := f64(alpha) weight_start := (1 - alpha) * (1 - alpha) * (1 - alpha) weight_c_a := 3 * (1 - alpha) * (1 - alpha) * alpha @@ -184,59 +184,6 @@ else return Vec2{ simd.extract(v, 0), simd.extract(v, 1) } } - vec2_add_simd :: #force_inline proc "contextless" (a, b: Vec2) -> Vec2 { - simd_a := vec2_to_simd(a) - simd_b := vec2_to_simd(b) - result := simd.add(simd_a, simd_b) - return simd_to_vec2(result) - } - - vec2_sub_simd :: #force_inline proc "contextless" (a, b: Vec2) -> Vec2 { - simd_a := vec2_to_simd(a) - simd_b := vec2_to_simd(b) - result := simd.sub(simd_a, simd_b) - return simd_to_vec2(result) - } - - vec2_mul_simd :: #force_inline proc "contextless" (a: Vec2, s: f32) -> Vec2 { - simd_a := vec2_to_simd(a) - simd_s := Vec2_SIMD{s, s, s, s} - result := simd.mul(simd_a, simd_s) - return simd_to_vec2(result) - } - - vec2_div_simd :: #force_inline proc "contextless" (a: Vec2, s: f32) -> Vec2 { - simd_a := vec2_to_simd(a) - simd_s := Vec2_SIMD{s, s, s, s} - result := simd.div(simd_a, simd_s) - return simd_to_vec2(result) - } - - vec2_dot_simd :: #force_inline proc "contextless" (a, b: Vec2) -> f32 { - simd_a := vec2_to_simd(a) - simd_b := vec2_to_simd(b) - result := simd.mul(simd_a, simd_b) - return simd.reduce_add_ordered(result) - } - - vec2_length_sqr_simd :: #force_inline proc "contextless" (a: Vec2) -> f32 { - return vec2_dot_simd(a, a) - } - - vec2_length_simd :: #force_inline proc "contextless" (a: Vec2) -> f32 { - return math.sqrt(vec2_length_sqr_simd(a)) - } - - vec2_normalize_simd :: #force_inline proc "contextless" (a: Vec2) -> Vec2 { - len := vec2_length_simd(a) - if len > 0 { - inv_len := 1.0 / len - return vec2_mul_simd(a, inv_len) - } - return a - } - - // SIMD-optimized version of eval_point_on_bezier3 eval_point_on_bezier3 :: #force_inline proc "contextless" (p0, p1, p2: Vec2, alpha: f32) -> Vec2 { simd_p0 := vec2_to_simd(p0) diff --git a/code/font/vefontcache/parser.odin b/code/font/vefontcache/parser.odin index 5d7cb74..1eef70d 100644 --- a/code/font/vefontcache/parser.odin +++ b/code/font/vefontcache/parser.odin @@ -1,4 +1,4 @@ -package VEFontCache +package vefontcache /* Notes: @@ -12,6 +12,7 @@ STB_Truetype has macros for its allocation unfortuantely import "base:runtime" import "core:c" import "core:math" +import "core:slice" import stbtt "vendor:stb/truetype" import freetype "thirdparty:freetype" @@ -52,13 +53,11 @@ ParserGlyphShape :: [dynamic]ParserGlyphVertex ParserContext :: struct { kind : ParserKind, ft_library : freetype.Library, - - // fonts : HMapChained(ParserFontInfo), } -parser_init :: proc( ctx : ^ParserContext ) +parser_init :: proc( ctx : ^ParserContext, kind : ParserKind ) { - switch ctx.kind + switch kind { case .Freetype: result := freetype.init_free_type( & ctx.ft_library ) @@ -68,9 +67,7 @@ parser_init :: proc( ctx : ^ParserContext ) // Do nothing intentional } - // error : AllocatorError - // ctx.fonts, error = make( HMapChained(ParserFontInfo), 256 ) - // assert( error == .None, "VEFontCache.parser_init: Failed to allocate fonts array" ) + ctx.kind = kind } parser_shutdown :: proc( ctx : ^ParserContext ) { @@ -79,13 +76,6 @@ parser_shutdown :: proc( ctx : ^ParserContext ) { parser_load_font :: proc( ctx : ^ParserContext, label : string, data : []byte ) -> (font : ParserFontInfo) { - // key := font_key_from_label(label) - // font = get( ctx.fonts, key ) - // if font != nil do return - - // error : AllocatorError - // font, error = set( ctx.fonts, key, ParserFontInfo {} ) - // assert( error == .None, "VEFontCache.parser_load_font: Failed to set a new parser font info" ) switch ctx.kind { case .Freetype: @@ -99,6 +89,7 @@ parser_load_font :: proc( ctx : ^ParserContext, label : string, data : []byte ) font.label = label font.data = data + font.kind = ctx.kind return } @@ -190,6 +181,10 @@ parser_get_font_vertical_metrics :: #force_inline proc "contextless" ( font : ^P 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 ) @@ -225,137 +220,8 @@ parser_get_glyph_shape :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> 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 { - return - } - - glyph := font.freetype_info.glyph - if glyph.format != .Outline { - return - } - - /* - convert freetype outline to stb_truetype shape - - freetype docs: https://freetype.org/freetype2/docs/glyphs/glyphs-6.html - - stb_truetype shape info: - The shape is a series of contours. Each one starts with - a STBTT_moveto, then consists of a series of mixed - STBTT_lineto and STBTT_curveto segments. A lineto - draws a line from previous endpoint to its x,y; a curveto - draws a quadratic bezier from previous endpoint to - its x,y, using cx,cy as the bezier control point. - */ - { - FT_CURVE_TAG_CONIC :: 0x00 - FT_CURVE_TAG_ON :: 0x01 - FT_CURVE_TAG_CUBIC :: 0x02 - - vertices, error := make( [dynamic]ParserGlyphVertex, 1024 ) - assert( error == .None ) - - // TODO(Ed): This makes freetype second class I guess but VEFontCache doesn't have native support for freetype originally so.... - outline := & glyph.outline - - contours := transmute( [^]u16) outline.contours - points := transmute( [^]freetype.Vector) outline.points - tags := transmute( [^]u8) outline.tags - - // TODO(Ed): Review this, never tested before and its problably bad. - for contour : i32 = 0; contour < i32(outline.n_contours); contour += 1 - { - start := (contour == 0) ? 0 : i32(contours[ contour - 1 ] + 1) - end := i32(contours[ contour ]) - - for index := start; index < i32(outline.n_points); index += 1 - { - point := points[ index ] - tag := tags[ index ] - - if (tag & FT_CURVE_TAG_ON) != 0 - { - if len(vertices) > 0 && !(vertices[len(vertices) - 1].type == .Move ) - { - // Close the previous contour if needed - append(& vertices, ParserGlyphVertex { type = .Line, - x = i16(points[start].x), y = i16(points[start].y), - contour_x0 = i16(0), contour_y0 = i16(0), - contour_x1 = i16(0), contour_y1 = i16(0), - padding = 0, - }) - } - - append(& vertices, ParserGlyphVertex { type = .Move, - x = i16(point.x), y = i16(point.y), - contour_x0 = i16(0), contour_y0 = i16(0), - contour_x1 = i16(0), contour_y1 = i16(0), - padding = 0, - }) - } - else if (tag & FT_CURVE_TAG_CUBIC) != 0 - { - point1 := points[ index + 1 ] - point2 := points[ index + 2 ] - append(& vertices, ParserGlyphVertex { type = .Cubic, - x = i16(point2.x), y = i16(point2.y), - contour_x0 = i16(point.x), contour_y0 = i16(point.y), - contour_x1 = i16(point1.x), contour_y1 = i16(point1.y), - padding = 0, - }) - index += 2 - } - else if (tag & FT_CURVE_TAG_CONIC) != 0 - { - // TODO(Ed): This is using a very dead simple algo to convert the conic to a cubic curve - // not sure if we need something more sophisticaated - point1 := points[ index + 1 ] - - control_conv :: f32(0.5) // Conic to cubic control point distance - to_float := f32(1.0 / 64.0) - - fp := Vec2 { f32(point.x), f32(point.y) } * to_float - fp1 := Vec2 { f32(point1.x), f32(point1.y) } * to_float - - control1 := freetype.Vector { - point.x + freetype.Pos( (fp1.x - fp.x) * control_conv * 64.0 ), - point.y + freetype.Pos( (fp1.y - fp.y) * control_conv * 64.0 ), - } - control2 := freetype.Vector { - point1.x + freetype.Pos( (fp.x - fp1.x) * control_conv * 64.0 ), - point1.y + freetype.Pos( (fp.y - fp1.y) * control_conv * 64.0 ), - } - append(& vertices, ParserGlyphVertex { type = .Cubic, - x = i16(point1.x), y = i16(point1.y), - contour_x0 = i16(control1.x), contour_y0 = i16(control1.y), - contour_x1 = i16(control2.x), contour_y1 = i16(control2.y), - padding = 0, - }) - index += 1 - } - else - { - append(& vertices, ParserGlyphVertex { type = .Line, - x = i16(point.x), y = i16(point.y), - contour_x0 = i16(0), contour_y0 = i16(0), - contour_x1 = i16(0), contour_y1 = i16(0), - padding = 0, - }) - } - } - - // Close contour - append(& vertices, ParserGlyphVertex { type = .Line, - x = i16(points[start].x), y = i16(points[start].y), - contour_x0 = i16(0), contour_y0 = i16(0), - contour_x1 = i16(0), contour_y1 = i16(0), - padding = 0, - }) - } - - shape = vertices - } + // 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 @@ -447,48 +313,3 @@ parser_scale_for_mapping_em_to_pixels :: #force_inline proc "contextless" ( font } return 0 } - -when false { -parser_convert_conic_to_cubic_freetype :: proc( vertices : Array(ParserGlyphVertex), p0, p1, p2 : freetype.Vector, tolerance : f32 ) -{ - scratch : [Kilobyte * 4]u8 - scratch_arena : Arena; arena_init(& scratch_arena, scratch[:]) - - points, error := make( Array(freetype.Vector), 256, allocator = arena_allocator( &scratch_arena) ) - assert(error == .None) - - append( & points, p0) - append( & points, p1) - append( & points, p2) - - to_float : f32 = 1.0 / 64.0 - control_conv :: f32(2.0 / 3.0) // Conic to cubic control point distance - - for ; points.num > 1; { - p0 := points.data[0] - p1 := points.data[1] - p2 := points.data[2] - - fp0 := Vec2{ f32(p0.x), f32(p0.y) } * to_float - fp1 := Vec2{ f32(p1.x), f32(p1.y) } * to_float - fp2 := Vec2{ f32(p2.x), f32(p2.y) } * to_float - - delta_x := fp0.x - 2 * fp1.x + fp2.x; - delta_y := fp0.y - 2 * fp1.y + fp2.y; - distance := math.sqrt(delta_x * delta_x + delta_y * delta_y); - - if distance <= tolerance - { - control1 := { - - } - } - else - { - control2 := { - - } - } - } -} -} diff --git a/code/font/vefontcache/shaped_text.odin b/code/font/vefontcache/shaped_text.odin index d265f76..5e7e9bd 100644 --- a/code/font/vefontcache/shaped_text.odin +++ b/code/font/vefontcache/shaped_text.odin @@ -1,4 +1,4 @@ -package VEFontCache +package vefontcache ShapedText :: struct { glyphs : [dynamic]Glyph, @@ -65,8 +65,6 @@ shape_text_uncached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) - use_full_text_shape := ctx.text_shape_adv - clear( & output.glyphs ) clear( & output.positions ) @@ -76,11 +74,9 @@ shape_text_uncached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, line_gap := f32(line_gap_i32) line_height := (ascent - descent + line_gap) * entry.size_scale - if use_full_text_shape + if ctx.text_shape_adv { - // assert( entry.shaper_info != nil ) shaper_shape_from_text( & ctx.shaper_ctx, & entry.shaper_info, output, text_utf8, ascent_i32, descent_i32, line_gap_i32, entry.size, entry.size_scale ) - // TODO(Ed): Need to be able to provide the text height as well return } else @@ -106,7 +102,7 @@ shape_text_uncached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, max_line_width = max(max_line_width, position.x) position.x = 0.0 position.y -= line_height - position.y = ceil(position.y) + position.y = position.y prev_codepoint = rune(0) continue } @@ -117,19 +113,22 @@ shape_text_uncached :: proc( ctx : ^Context, font : FontID, text_utf8 : string, append( & output.glyphs, parser_find_glyph_index( & entry.parser_info, codepoint )) advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint ) + if ctx.snap_shape_pos do position.x = ceil(position.x) + append( & output.positions, Vec2 { - ctx.snap_shape_pos ? ceil(position.x) : position.x, + position.x, position.y }) - position.x += ctx.snap_shape_pos ? ceil(f32(advance) * entry.size_scale) : f32(advance) * entry.size_scale + position.x += f32(advance) * entry.size_scale + if ctx.snap_shape_pos do position.x = ceil(position.x) prev_codepoint = codepoint } output.end_cursor_pos = position max_line_width = max(max_line_width, position.x) - output.size.x = ceil(max_line_width) + output.size.x = max_line_width output.size.y = f32(line_count) * line_height } } diff --git a/code/font/vefontcache/shaper.odin b/code/font/vefontcache/shaper.odin index 67a3d23..ecc8495 100644 --- a/code/font/vefontcache/shaper.odin +++ b/code/font/vefontcache/shaper.odin @@ -1,4 +1,4 @@ -package VEFontCache +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. */ @@ -13,7 +13,6 @@ ShaperKind :: enum { ShaperContext :: struct { hb_buffer : harfbuzz.Buffer, - // infos : HMapChained(ShaperInfo), } ShaperInfo :: struct { @@ -26,10 +25,6 @@ shaper_init :: proc( ctx : ^ShaperContext ) { ctx.hb_buffer = harfbuzz.buffer_create() assert( ctx.hb_buffer != nil, "VEFontCache.shaper_init: Failed to create harfbuzz buffer") - - // error : AllocatorError - // ctx.infos, error = make( HMapChained(ShaperInfo), 256 ) - // assert( error == .None, "VEFontCache.shaper_init: Failed to create shaper infos map" ) } shaper_shutdown :: proc( ctx : ^ShaperContext ) @@ -37,20 +32,10 @@ shaper_shutdown :: proc( ctx : ^ShaperContext ) if ctx.hb_buffer != nil { harfbuzz.buffer_destroy( ctx.hb_buffer ) } - - // delete(& ctx.infos) } shaper_load_font :: proc( ctx : ^ShaperContext, label : string, data : []byte, user_data : rawptr ) -> (info : ShaperInfo) { - // key := font_key_from_label( label ) - // info = get( ctx.infos, key ) - // if info != nil do return - - // error : AllocatorError - // info, error = set( ctx.infos, key, ShaperInfo {} ) - // assert( error == .None, "VEFontCache.parser_load_font: Failed to set a new shaper info" ) - using info blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil ) face = harfbuzz.face_create( blob, 0 ) @@ -79,10 +64,14 @@ shaper_shape_from_text :: proc( ctx : ^ShaperContext, info : ^ShaperInfo, output descent := f32(descent) line_gap := f32(line_gap) + max_line_width := f32(0) + line_count := 1 + line_height := (ascent - descent + line_gap) * size_scale + 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 ) + position, vertical_position, max_line_width: ^f32, line_count: ^int, + ascent, descent, line_gap, size, size_scale: f32 ) { // Set script and direction. We use the system's default langauge. // script = HB_SCRIPT_LATIN @@ -91,6 +80,7 @@ shaper_shape_from_text :: proc( ctx : ^ShaperContext, info : ^ShaperInfo, output harfbuzz.buffer_set_language( buffer, harfbuzz.language_get_default() ) // Perform the actual shaping of this run using HarfBuzz. + harfbuzz.buffer_set_content_type( buffer, harfbuzz.Buffer_Content_Type.UNICODE ) harfbuzz.shape( font, buffer, nil, 0 ) // Loop over glyphs and append to output buffer. @@ -98,6 +88,8 @@ shaper_shape_from_text :: proc( ctx : ^ShaperContext, info : ^ShaperInfo, output glyph_infos := harfbuzz.buffer_get_glyph_infos( buffer, & glyph_count ) glyph_positions := harfbuzz.buffer_get_glyph_positions( buffer, & glyph_count ) + line_height := (ascent - descent + line_gap) * size_scale + for index : i32; index < i32(glyph_count); index += 1 { hb_glyph := glyph_infos[ index ] @@ -106,9 +98,11 @@ shaper_shape_from_text :: proc( ctx : ^ShaperContext, info : ^ShaperInfo, output if hb_glyph.cluster > 0 { + (max_line_width^) = max( max_line_width^, position^ ) (position^) = 0.0 - (vertical_position^) -= (ascent - descent + line_gap) * size_scale + (vertical_position^) -= line_height (vertical_position^) = cast(f32) i32(vertical_position^ + 0.5) + (line_count^) += 1 continue } if abs( size ) <= Advance_Snap_Smallfont_Size @@ -128,6 +122,7 @@ shaper_shape_from_text :: proc( ctx : ^ShaperContext, info : ^ShaperInfo, output (position^) += f32(hb_gposition.x_advance) * size_scale (vertical_position^) += f32(hb_gposition.y_advance) * size_scale + (max_line_width^) = max(max_line_width^, position^) } output.end_cursor_pos.x = position^ @@ -141,25 +136,31 @@ shaper_shape_from_text :: proc( ctx : ^ShaperContext, info : ^ShaperInfo, output for codepoint, byte_offset in text_utf8 { - script := harfbuzz.unicode_script( hb_ucfunc, cast(harfbuzz.Codepoint) codepoint ) + hb_codepoint := cast(harfbuzz.Codepoint) codepoint + + script := harfbuzz.unicode_script( hb_ucfunc, hb_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 ) + if special_script || script == current_script || byte_offset == 0 { + harfbuzz.buffer_add( ctx.hb_buffer, hb_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 ) + shape_run( ctx.hb_buffer, current_script, info.font, output, & position, & vertical_position, & max_line_width, & line_count, ascent, descent, line_gap, size, size_scale ) + harfbuzz.buffer_add( ctx.hb_buffer, hb_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 ) + shape_run( ctx.hb_buffer, current_script, info.font, output, & position, & vertical_position, & max_line_width, & line_count, ascent, descent, line_gap, size, size_scale ) + + // Set the final size + output.size.x = max_line_width + output.size.y = f32(line_count) * line_height return } diff --git a/code/font/vefontcache/vefontcache.odin b/code/font/vefontcache/vefontcache.odin index b7b9046..45da3cb 100644 --- a/code/font/vefontcache/vefontcache.odin +++ b/code/font/vefontcache/vefontcache.odin @@ -3,7 +3,7 @@ A port of (https://github.com/hypernewbie/VEFontCache) to Odin. See: https://github.com/Ed94/VEFontCache-Odin */ -package VEFontCache +package vefontcache import "base:runtime" @@ -32,7 +32,6 @@ Entry_Default :: Entry { Context :: struct { backing : Allocator, - parser_kind : ParserKind, parser_ctx : ParserContext, shaper_ctx : ShaperContext, @@ -45,6 +44,7 @@ Context :: struct { snap_width : f32, snap_height : f32, + colour : Colour, cursor_pos : Vec2, @@ -67,7 +67,7 @@ Context :: struct { debug_print_verbose : b32, } -#region("lifetime") +//#region("lifetime") InitAtlasRegionParams :: struct { width : u32, @@ -136,6 +136,7 @@ startup :: proc( ctx : ^Context, parser_kind : ParserKind, atlas_params := InitAtlasParams_Default, glyph_draw_params := InitGlyphDrawParams_Default, shape_cache_params := InitShapeCacheParams_Default, + use_advanced_text_shaper : b32 = true, snap_shape_position : b32 = true, default_curve_quality : u32 = 3, entires_reserve : u32 = 512, @@ -150,6 +151,7 @@ startup :: proc( ctx : ^Context, parser_kind : ParserKind, context.allocator = ctx.backing snap_shape_pos = snap_shape_position + text_shape_adv = use_advanced_text_shaper if default_curve_quality == 0 { default_curve_quality = 3 @@ -264,7 +266,7 @@ startup :: proc( ctx : ^Context, parser_kind : ParserKind, assert( error == .None, "VEFontCache.init : Failed to allocate vertices array for clear_draw_list" ) } - parser_init( & parser_ctx ) + parser_init( & parser_ctx, parser_kind ) shaper_init( & shaper_ctx ) } @@ -320,9 +322,38 @@ shutdown :: proc( ctx : ^Context ) unload_font( ctx, entry.id ) } - shaper_shutdown( & shaper_ctx ) + delete( entries ) + delete( temp_path ) + delete( temp_codepoint_seen ) - // TODO(Ed): Finish implementing, there is quite a few resource not released here. + delete( draw_list.vertices ) + delete( draw_list.indices ) + delete( draw_list.calls ) + + 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 { + stroage_entry := & shape_cache.storage[idx] + using stroage_entry + + delete( glyphs ) + delete( positions ) + } + LRU_free( & shape_cache.state ) + + 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 ) + + shaper_shutdown( & shaper_ctx ) + parser_shutdown( & parser_ctx ) } // ve_fontcache_load @@ -352,10 +383,7 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32, used = true parser_info = parser_load_font( & parser_ctx, label, data ) - // assert( parser_info != nil, "VEFontCache.load_font: Failed to load font info from parser" ) - shaper_info = shaper_load_font( & shaper_ctx, label, data, transmute(rawptr) id ) - // assert( shaper_info != nil, "VEFontCache.load_font: Failed to load font from shaper") size = size_px size_scale = size_px < 0.0 ? \ @@ -391,9 +419,9 @@ unload_font :: proc( ctx : ^Context, font : FontID ) shaper_unload_font( & entry.shaper_info ) } -#endregion("lifetime") +//#endregion("lifetime") -#region("drawing") +//#region("drawing") // ve_fontcache_configure_snap configure_snap :: #force_inline proc( ctx : ^Context, snap_width, snap_height : u32 ) { @@ -407,7 +435,7 @@ set_colour :: #force_inline proc "contextless" ( ctx : ^Context, colour : Co draw_text :: proc( ctx : ^Context, font : FontID, text_utf8 : string, position, scale : Vec2 ) -> b32 { - // profile(#procedure) + profile(#procedure) assert( ctx != nil ) assert( font >= 0 && int(font) < len(ctx.entries) ) @@ -469,9 +497,9 @@ flush_draw_list_layer :: proc( ctx : ^Context ) { draw_layer.calls_offset = len(draw_list.calls) } -#endregion("drawing") +//#endregion("drawing") -#region("metrics") +//#region("metrics") measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -> (measured : Vec2) { @@ -498,4 +526,4 @@ get_font_vertical_metrics :: #force_inline proc ( ctx : ^Context, font : FontID return } -#endregion("metrics") +//#endregion("metrics")