mirror of
https://github.com/Ed94/VEFontCache-Odin.git
synced 2025-08-06 06:52:44 -07:00
WIP: Updating public repo with latest version
This commit is contained in:
52
Readme.md
52
Readme.md
@@ -1,28 +1,52 @@
|
||||
# VE Font Cache : Odin Port
|
||||
# VE Font Cache
|
||||
|
||||
https://github.com/user-attachments/assets/b74f1ec1-f980-45df-b604-d6b7d87d40ff
|
||||
|
||||
This is a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library.
|
||||
|
||||
This started off as a port of the [VEFontCache](https://github.com/hypernewbie/VEFontCache) library to the Odin programming language.
|
||||
Its original purpose was for use in game engines, however its rendeirng quality and performance is more than adequate for many other applications.
|
||||
|
||||
Since then the library has been overhauled to offer higher performance, improved visual fidelity, additional features, and quality of life improvements.
|
||||
|
||||
Features:
|
||||
|
||||
* Simple and well documented.
|
||||
* Load and unload fonts at anytime
|
||||
* Almost entirely configurabe and tunable at runtime!
|
||||
* Full support for hot-reload
|
||||
* Clear the caches at any-time!
|
||||
* 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 (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.
|
||||
* Can hand thousands of draw text calls with very large or small shapes.
|
||||
* 4-Level Regioned Texture Atlas for caching rendered glyphs
|
||||
* Text shape caching
|
||||
* Glyph texture buffer for rendering the text with super-sampling to downsample to the atlas or direct to target screen.
|
||||
* Super-sample by a font size scalar for sharper glyphs
|
||||
* All caching backed by an optimized 32-bit LRU indexing cache
|
||||
* Provides a draw list that is backend agnostic (see [backend](./backend) for usage example).
|
||||
|
||||
Upcoming:
|
||||
|
||||
* Support for ear-clipping triangulation
|
||||
* Support for which triangulation method used on a by font basis?
|
||||
* Multi-threading supported job queue.
|
||||
* Lift heavy-lifting portion of the library's context into a thread context.
|
||||
* Synchronize threads by merging their generated layered drawlist into a file draw-list for processing on the user's render thread.
|
||||
* User defines how context's are distributed for drawing (a basic quandrant basic selector procedure will be provided.)
|
||||
|
||||
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 were developed & tested on Windows. There are bash scripts for building on linux & mac.
|
||||
Currently the scripts provided & the library itself were developed & tested on Windows. There are bash scripts for building on linux (they build on WSL but need additional testing).
|
||||
|
||||
The library depends on freetype, harfbuzz, & stb_truetype 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 warpper interface
|
||||
* 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 interpolation for glyph rendering can be set on a per font basis.
|
||||
The library depends on harfbuzz, & stb_truetype to build.
|
||||
Note: harfbuzz could technically be gutted if the user removes their definitions, however they have not been made into a conditional compilation option (yet).
|
||||
|
||||

|
||||
|
@@ -1,5 +1,7 @@
|
||||
# Interface
|
||||
|
||||
TODO: OUTDATED
|
||||
|
||||
Notes
|
||||
---
|
||||
|
||||
|
@@ -94,11 +94,11 @@ function build-SokolBackendDemo
|
||||
# $build_args += $flag_micro_architecture_native
|
||||
$build_args += $flag_use_separate_modules
|
||||
$build_args += $flag_thread_count + $CoreCount_Physical
|
||||
# $build_args += $flag_optimize_none
|
||||
$build_args += $flag_optimize_none
|
||||
# $build_args += $flag_optimize_minimal
|
||||
# $build_args += $flag_optimize_speed
|
||||
$build_args += $falg_optimize_aggressive
|
||||
# $build_args += $flag_debug
|
||||
# $build_args += $falg_optimize_aggressive
|
||||
$build_args += $flag_debug
|
||||
$build_args += $flag_pdb_name + $pdb
|
||||
$build_args += $flag_subsystem + 'windows'
|
||||
# $build_args += ($flag_extra_linker_flags + $linker_args )
|
||||
@@ -111,6 +111,8 @@ function build-SokolBackendDemo
|
||||
# $build_args += $flag_sanitize_address
|
||||
# $build_args += $flag_sanitize_memory
|
||||
|
||||
Write-Host $build_args
|
||||
|
||||
Invoke-WithColorCodedOutput { & $odin_compiler $build_args }
|
||||
}
|
||||
build-SokolBackendDemo
|
||||
|
@@ -1,22 +1,36 @@
|
||||
package vefontcache
|
||||
|
||||
/*
|
||||
The choice was made to keep the LRU cache implementation as close to the original as possible.
|
||||
/* Note(Ed):
|
||||
Original implementation has been changed moderately.
|
||||
Notably the LRU is now type generic for its key value.
|
||||
This was done to profile between using u64, u32, and u16.
|
||||
|
||||
What ended up happening was using u32 for both the atlas and the shape cache
|
||||
yielded a several ms save for processing thousands of draw text calls.
|
||||
|
||||
There was an attempt at an optimization pass but the directives done here (other than force_inline)
|
||||
are marginal changes at best.
|
||||
*/
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
Pool_ListIter :: i32
|
||||
Pool_ListValue :: u64
|
||||
// 16-bit hashing was attempted, however it seems to get collisions with djb8_hash_16
|
||||
|
||||
Pool_List_Item :: struct {
|
||||
LRU_Fail_Mask_16 :: 0xFFFF
|
||||
LRU_Fail_Mask_32 :: 0xFFFFFFFF
|
||||
LRU_Fail_Mask_64 :: 0xFFFFFFFFFFFFFFFF
|
||||
|
||||
Pool_ListIter :: i32
|
||||
|
||||
Pool_List_Item :: struct( $V_Type : typeid ) #packed {
|
||||
// Pool_List_Item :: struct( $V_Type : typeid ) {
|
||||
prev : Pool_ListIter,
|
||||
next : Pool_ListIter,
|
||||
value : Pool_ListValue,
|
||||
value : V_Type,
|
||||
}
|
||||
|
||||
Pool_List :: struct {
|
||||
items : [dynamic]Pool_List_Item,
|
||||
Pool_List :: struct( $V_Type : typeid) {
|
||||
items : [dynamic]Pool_List_Item(V_Type),
|
||||
free_list : [dynamic]Pool_ListIter,
|
||||
front : Pool_ListIter,
|
||||
back : Pool_ListIter,
|
||||
@@ -25,10 +39,10 @@ Pool_List :: struct {
|
||||
dbg_name : string,
|
||||
}
|
||||
|
||||
pool_list_init :: proc( pool : ^Pool_List, capacity : i32, dbg_name : string = "" )
|
||||
pool_list_init :: proc( pool : ^Pool_List($V_Type), capacity : i32, dbg_name : string = "" )
|
||||
{
|
||||
error : Allocator_Error
|
||||
pool.items, error = make( [dynamic]Pool_List_Item, int(capacity) )
|
||||
pool.items, error = make( [dynamic]Pool_List_Item(V_Type), int(capacity) )
|
||||
assert( error == .None, "VEFontCache.pool_list_init : Failed to allocate items array")
|
||||
resize( & pool.items, capacity )
|
||||
|
||||
@@ -39,130 +53,130 @@ pool_list_init :: proc( pool : ^Pool_List, capacity : i32, dbg_name : string = "
|
||||
pool.capacity = capacity
|
||||
|
||||
pool.dbg_name = dbg_name
|
||||
using pool
|
||||
|
||||
for id in 0 ..< capacity {
|
||||
free_list[id] = i32(id)
|
||||
items[id] = {
|
||||
for id in 0 ..< pool.capacity {
|
||||
pool.free_list[id] = Pool_ListIter(id)
|
||||
pool.items[id] = {
|
||||
prev = -1,
|
||||
next = -1,
|
||||
}
|
||||
}
|
||||
|
||||
front = -1
|
||||
back = -1
|
||||
pool.front = -1
|
||||
pool.back = -1
|
||||
}
|
||||
|
||||
pool_list_free :: proc( pool : ^Pool_List ) {
|
||||
pool_list_free :: proc( pool : ^Pool_List($V_Type) ) {
|
||||
delete( pool.items)
|
||||
delete( pool.free_list)
|
||||
}
|
||||
|
||||
pool_list_reload :: proc( pool : ^Pool_List, allocator : Allocator ) {
|
||||
pool_list_reload :: proc( pool : ^Pool_List($V_Type), allocator : Allocator ) {
|
||||
reload_array( & pool.items, allocator )
|
||||
reload_array( & pool.free_list, allocator )
|
||||
}
|
||||
|
||||
pool_list_clear :: proc( pool: ^Pool_List ) {
|
||||
using pool
|
||||
clear(& items)
|
||||
clear(& free_list)
|
||||
pool_list_clear :: proc( pool: ^Pool_List($V_Type) )
|
||||
{
|
||||
clear(& pool.items)
|
||||
clear(& pool.free_list)
|
||||
resize( & pool.items, cap(pool.items) )
|
||||
resize( & pool.free_list, cap(pool.free_list) )
|
||||
|
||||
for id in 0 ..< capacity {
|
||||
free_list[id] = i32(id)
|
||||
items[id] = {
|
||||
for id in 0 ..< pool.capacity {
|
||||
pool.free_list[id] = Pool_ListIter(id)
|
||||
pool.items[id] = {
|
||||
prev = -1,
|
||||
next = -1,
|
||||
}
|
||||
}
|
||||
|
||||
front = -1
|
||||
back = -1
|
||||
size = 0
|
||||
pool.front = -1
|
||||
pool.back = -1
|
||||
pool.size = 0
|
||||
}
|
||||
|
||||
pool_list_push_front :: proc( pool : ^Pool_List, value : Pool_ListValue )
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_push_front :: proc( pool : ^Pool_List($V_Type), value : V_Type ) #no_bounds_check
|
||||
{
|
||||
using pool
|
||||
if size >= capacity do return
|
||||
if pool.size >= pool.capacity do return
|
||||
|
||||
length := len(free_list)
|
||||
length := len(pool.free_list)
|
||||
assert( length > 0 )
|
||||
assert( length == int(capacity - size) )
|
||||
assert( length == int(pool.capacity - pool.size) )
|
||||
|
||||
id := free_list[ len(free_list) - 1 ]
|
||||
id := pool.free_list[ len(pool.free_list) - 1 ]
|
||||
if pool.dbg_name != "" {
|
||||
logf("pool_list: back %v", id)
|
||||
}
|
||||
pop( & free_list )
|
||||
items[ id ].prev = -1
|
||||
items[ id ].next = front
|
||||
items[ id ].value = value
|
||||
pop( & pool.free_list )
|
||||
pool.items[ id ].prev = -1
|
||||
pool.items[ id ].next = pool.front
|
||||
pool.items[ id ].value = value
|
||||
if pool.dbg_name != "" {
|
||||
logf("pool_list: pushed %v into id %v", value, id)
|
||||
}
|
||||
|
||||
if front != -1 do items[ front ].prev = id
|
||||
if back == -1 do back = id
|
||||
front = id
|
||||
size += 1
|
||||
if pool.front != -1 do pool.items[ pool.front ].prev = id
|
||||
if pool.back == -1 do pool.back = id
|
||||
pool.front = id
|
||||
pool.size += 1
|
||||
}
|
||||
|
||||
pool_list_erase :: proc( pool : ^Pool_List, iter : Pool_ListIter )
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_erase :: proc( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
|
||||
{
|
||||
using pool
|
||||
if size <= 0 do return
|
||||
assert( iter >= 0 && iter < i32(capacity) )
|
||||
assert( len(free_list) == int(capacity - size) )
|
||||
if pool.size <= 0 do return
|
||||
assert( iter >= 0 && iter < Pool_ListIter(pool.capacity) )
|
||||
assert( len(pool.free_list) == int(pool.capacity - pool.size) )
|
||||
|
||||
iter_node := & items[ iter ]
|
||||
iter_node := & pool.items[ iter ]
|
||||
prev := iter_node.prev
|
||||
next := iter_node.next
|
||||
|
||||
if iter_node.prev != -1 do items[ prev ].next = iter_node.next
|
||||
if iter_node.next != -1 do items[ next ].prev = iter_node.prev
|
||||
if iter_node.prev != -1 do pool.items[ prev ].next = iter_node.next
|
||||
if iter_node.next != -1 do pool.items[ next ].prev = iter_node.prev
|
||||
|
||||
if front == iter do front = iter_node.next
|
||||
if back == iter do back = iter_node.prev
|
||||
if pool.front == iter do pool.front = iter_node.next
|
||||
if pool.back == iter do pool.back = iter_node.prev
|
||||
|
||||
iter_node.prev = -1
|
||||
iter_node.next = -1
|
||||
iter_node.value = 0
|
||||
append( & free_list, iter )
|
||||
append( & pool.free_list, iter )
|
||||
|
||||
size -= 1
|
||||
if size == 0 {
|
||||
back = -1
|
||||
front = -1
|
||||
pool.size -= 1
|
||||
if pool.size == 0 {
|
||||
pool.back = -1
|
||||
pool.front = -1
|
||||
}
|
||||
}
|
||||
|
||||
pool_list_move_to_front :: #force_inline proc( pool : ^Pool_List, iter : Pool_ListIter )
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_move_to_front :: proc "contextless" ( pool : ^Pool_List($V_Type), iter : Pool_ListIter ) #no_bounds_check
|
||||
{
|
||||
using pool
|
||||
if pool.front == iter do return
|
||||
|
||||
if front == iter do return
|
||||
item := & pool.items[iter]
|
||||
if item.prev != -1 do pool.items[ item.prev ].next = item.next
|
||||
if item.next != -1 do pool.items[ item.next ].prev = item.prev
|
||||
if pool.back == iter do pool.back = item.prev
|
||||
|
||||
item := & items[iter]
|
||||
if item.prev != -1 do items[ item.prev ].next = item.next
|
||||
if item.next != -1 do items[ item.next ].prev = item.prev
|
||||
if back == iter do back = item.prev
|
||||
|
||||
item.prev = -1
|
||||
item.next = front
|
||||
items[ front ].prev = iter
|
||||
front = iter
|
||||
item.prev = -1
|
||||
item.next = pool.front
|
||||
pool.items[ pool.front ].prev = iter
|
||||
pool.front = iter
|
||||
}
|
||||
|
||||
pool_list_peek_back :: #force_inline proc ( pool : ^Pool_List ) -> Pool_ListValue {
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_peek_back :: #force_inline proc ( pool : Pool_List($V_Type) ) -> V_Type #no_bounds_check {
|
||||
assert( pool.back != - 1 )
|
||||
value := pool.items[ pool.back ].value
|
||||
return value
|
||||
}
|
||||
|
||||
pool_list_pop_back :: #force_inline proc( pool : ^Pool_List ) -> Pool_ListValue {
|
||||
@(optimization_mode="favor_size")
|
||||
pool_list_pop_back :: #force_inline proc( pool : ^Pool_List($V_Type) ) -> V_Type #no_bounds_check {
|
||||
if pool.size <= 0 do return 0
|
||||
assert( pool.back != -1 )
|
||||
|
||||
@@ -171,69 +185,69 @@ pool_list_pop_back :: #force_inline proc( pool : ^Pool_List ) -> Pool_ListValue
|
||||
return value
|
||||
}
|
||||
|
||||
LRU_Link :: struct {
|
||||
pad_top : u64,
|
||||
|
||||
LRU_Link :: struct #packed {
|
||||
value : i32,
|
||||
ptr : Pool_ListIter,
|
||||
|
||||
pad_bottom : u64,
|
||||
}
|
||||
|
||||
LRU_Cache :: struct {
|
||||
LRU_Cache :: struct( $Key_Type : typeid ) {
|
||||
capacity : i32,
|
||||
num : i32,
|
||||
table : map[u64]LRU_Link,
|
||||
key_queue : Pool_List,
|
||||
table : map[Key_Type]LRU_Link,
|
||||
key_queue : Pool_List(Key_Type),
|
||||
}
|
||||
|
||||
lru_init :: proc( cache : ^LRU_Cache, capacity : i32, dbg_name : string = "" ) {
|
||||
lru_init :: proc( cache : ^LRU_Cache($Key_Type), capacity : i32, dbg_name : string = "" ) {
|
||||
error : Allocator_Error
|
||||
cache.capacity = capacity
|
||||
cache.table, error = make( map[u64]LRU_Link, uint(capacity) )
|
||||
cache.table, error = make( map[Key_Type]LRU_Link, uint(capacity) )
|
||||
assert( error == .None, "VEFontCache.lru_init : Failed to allocate cache's table")
|
||||
|
||||
pool_list_init( & cache.key_queue, capacity, dbg_name = dbg_name )
|
||||
}
|
||||
}
|
||||
|
||||
lru_free :: proc( cache : ^LRU_Cache ) {
|
||||
lru_free :: proc( cache : ^LRU_Cache($Key_Type) ) {
|
||||
pool_list_free( & cache.key_queue )
|
||||
delete( cache.table )
|
||||
}
|
||||
|
||||
lru_reload :: #force_inline proc( cache : ^LRU_Cache, allocator : Allocator ) {
|
||||
lru_reload :: #force_inline proc( cache : ^LRU_Cache($Key_Type), allocator : Allocator ) {
|
||||
reload_map( & cache.table, allocator )
|
||||
pool_list_reload( & cache.key_queue, allocator )
|
||||
}
|
||||
|
||||
lru_clear :: proc ( cache : ^LRU_Cache ) {
|
||||
lru_clear :: proc ( cache : ^LRU_Cache($Key_Type) ) {
|
||||
pool_list_clear( & cache.key_queue )
|
||||
clear(& cache.table)
|
||||
cache.num = 0
|
||||
}
|
||||
|
||||
lru_find :: #force_inline proc "contextless" ( cache : ^LRU_Cache, key : u64, must_find := false ) -> (LRU_Link, bool) {
|
||||
@(optimization_mode="favor_size")
|
||||
lru_find :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> (LRU_Link, bool) #no_bounds_check {
|
||||
link, success := cache.table[key]
|
||||
return link, success
|
||||
}
|
||||
|
||||
lru_get :: #force_inline proc( cache: ^LRU_Cache, key : u64 ) -> i32 {
|
||||
@(optimization_mode="favor_size")
|
||||
lru_get :: #force_inline proc ( cache: ^LRU_Cache($Key_Type), key : Key_Type ) -> i32 #no_bounds_check {
|
||||
if link, ok := &cache.table[ key ]; ok {
|
||||
pool_list_move_to_front(&cache.key_queue, link.ptr)
|
||||
return link.value
|
||||
pool_list_move_to_front(&cache.key_queue, link.ptr)
|
||||
return link.value
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
lru_get_next_evicted :: #force_inline proc ( cache : ^LRU_Cache ) -> u64 {
|
||||
@(optimization_mode="favor_size")
|
||||
lru_get_next_evicted :: #force_inline proc ( cache : LRU_Cache($Key_Type) ) -> Key_Type #no_bounds_check {
|
||||
if cache.key_queue.size >= cache.capacity {
|
||||
evict := pool_list_peek_back( & cache.key_queue )
|
||||
evict := pool_list_peek_back( cache.key_queue )
|
||||
return evict
|
||||
}
|
||||
return 0xFFFFFFFFFFFFFFFF
|
||||
return ~Key_Type(0)
|
||||
}
|
||||
|
||||
lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := false ) -> i32 {
|
||||
@(optimization_mode="favor_size")
|
||||
lru_peek :: #force_inline proc "contextless" ( cache : LRU_Cache($Key_Type), key : Key_Type, must_find := false ) -> i32 #no_bounds_check {
|
||||
iter, success := lru_find( cache, key, must_find )
|
||||
if success == false {
|
||||
return -1
|
||||
@@ -241,8 +255,10 @@ lru_peek :: #force_inline proc ( cache : ^LRU_Cache, key : u64, must_find := fal
|
||||
return iter.value
|
||||
}
|
||||
|
||||
lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u64
|
||||
@(optimization_mode="favor_size")
|
||||
lru_put :: proc( cache : ^LRU_Cache($Key_Type), key : Key_Type, value : i32 ) -> Key_Type #no_bounds_check
|
||||
{
|
||||
// profile(#procedure)
|
||||
if link, ok := & cache.table[ key ]; ok {
|
||||
pool_list_move_to_front( & cache.key_queue, link.ptr )
|
||||
link.value = value
|
||||
@@ -265,8 +281,8 @@ lru_put :: #force_inline proc( cache : ^LRU_Cache, key : u64, value : i32 ) -> u
|
||||
return evict
|
||||
}
|
||||
|
||||
lru_refresh :: proc( cache : ^LRU_Cache, key : u64 ) {
|
||||
link, success := lru_find( cache, key )
|
||||
lru_refresh :: proc( cache : ^LRU_Cache($Key_Type), key : Key_Type ) {
|
||||
link, success := lru_find( cache ^, key )
|
||||
pool_list_erase( & cache.key_queue, link.ptr )
|
||||
pool_list_push_front( & cache.key_queue, key )
|
||||
link.ptr = cache.key_queue.front
|
||||
|
@@ -1,127 +1,127 @@
|
||||
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.
|
||||
Atlas_Region_Kind :: enum u8 {
|
||||
None = 0x00,
|
||||
A = 0x41,
|
||||
B = 0x42,
|
||||
C = 0x43,
|
||||
D = 0x44,
|
||||
E = 0x45,
|
||||
A = 0x01,
|
||||
B = 0x02,
|
||||
C = 0x03,
|
||||
D = 0x04,
|
||||
E = 0x05,
|
||||
Ignore = 0xFF, // ve_fontcache_cache_glyph_to_atlas uses a -1 value in clear draw call
|
||||
}
|
||||
|
||||
Atlas_Region :: struct {
|
||||
state : LRU_Cache,
|
||||
// Note(Ed): Using 16 bit hash had collision failures and no observable performance improvement (tried several 16-bit hashers)
|
||||
Atlas_Key :: u32
|
||||
|
||||
width : i32,
|
||||
height : i32,
|
||||
// TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Atlas.
|
||||
/* Essentially a sub-atlas of the atlas. There is a state cache per region that tracks the glyph inventory (what slot they occupy).
|
||||
Unlike the shape cache this one's fixed capacity (natrually) and the next avail slot is tracked.
|
||||
*/
|
||||
Atlas_Region :: struct {
|
||||
state : LRU_Cache(Atlas_Key),
|
||||
|
||||
size : Vec2i,
|
||||
capacity : Vec2i,
|
||||
offset : Vec2i,
|
||||
|
||||
slot_size : Vec2i,
|
||||
|
||||
next_idx : i32,
|
||||
}
|
||||
|
||||
/* There are four regions each succeeding region holds larger sized slots.
|
||||
The generator pipeline for draw lists utilizes the regions array for info lookup.
|
||||
|
||||
Note(Ed):
|
||||
Padding can techncially be larger than 1, however recently I haven't had any artififact issues...
|
||||
size_multiplier usage isn't fully resolved. Intent was to further setup over_sampling or just having
|
||||
a more massive cache for content that used more than the usual common glyphs.
|
||||
*/
|
||||
Atlas :: struct {
|
||||
width : i32,
|
||||
height : i32,
|
||||
|
||||
glyph_padding : i32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region.
|
||||
glyph_over_scalar : f32, // Scalar to apply to bounds_<width/height>_scaled for choosing which atlas region.
|
||||
|
||||
region_a : Atlas_Region,
|
||||
region_b : Atlas_Region,
|
||||
region_c : Atlas_Region,
|
||||
region_d : Atlas_Region,
|
||||
|
||||
regions : [5] ^Atlas_Region,
|
||||
|
||||
glyph_padding : f32, // Padding to add to bounds_<width/height>_scaled for choosing which atlas region.
|
||||
size_multiplier : f32, // Grows all text by this multiple.
|
||||
|
||||
size : Vec2i,
|
||||
}
|
||||
|
||||
atlas_bbox :: proc( atlas : ^Atlas, region : Atlas_Region_Kind, local_idx : i32 ) -> (position, size: Vec2)
|
||||
{
|
||||
switch region
|
||||
{
|
||||
case .A:
|
||||
size.x = f32(atlas.region_a.width)
|
||||
size.y = f32(atlas.region_a.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_a.capacity.x ) * atlas.region_a.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_a.capacity.x ) * atlas.region_a.height)
|
||||
|
||||
position.x += f32(atlas.region_a.offset.x)
|
||||
position.y += f32(atlas.region_a.offset.y)
|
||||
|
||||
case .B:
|
||||
size.x = f32(atlas.region_b.width)
|
||||
size.y = f32(atlas.region_b.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_b.capacity.x ) * atlas.region_b.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_b.capacity.x ) * atlas.region_b.height)
|
||||
|
||||
position.x += f32(atlas.region_b.offset.x)
|
||||
position.y += f32(atlas.region_b.offset.y)
|
||||
|
||||
case .C:
|
||||
size.x = f32(atlas.region_c.width)
|
||||
size.y = f32(atlas.region_c.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_c.capacity.x ) * atlas.region_c.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_c.capacity.x ) * atlas.region_c.height)
|
||||
|
||||
position.x += f32(atlas.region_c.offset.x)
|
||||
position.y += f32(atlas.region_c.offset.y)
|
||||
|
||||
case .D:
|
||||
size.x = f32(atlas.region_d.width)
|
||||
size.y = f32(atlas.region_d.height)
|
||||
|
||||
position.x = cast(f32) (( local_idx % atlas.region_d.capacity.x ) * atlas.region_d.width)
|
||||
position.y = cast(f32) (( local_idx / atlas.region_d.capacity.x ) * atlas.region_d.height)
|
||||
|
||||
position.x += f32(atlas.region_d.offset.x)
|
||||
position.y += f32(atlas.region_d.offset.y)
|
||||
|
||||
case .Ignore, .None, .E:
|
||||
}
|
||||
// Hahser for the atlas.
|
||||
@(optimization_mode="favor_size")
|
||||
atlas_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, px_size : f32, glyph_index : Glyph ) -> (lru_code : Atlas_Key) {
|
||||
// lru_code = u32(glyph_index) + ( ( 0x10000 * u32(font) ) & 0xFFFF0000 )
|
||||
font := font
|
||||
glyph_index := glyph_index
|
||||
px_size := px_size
|
||||
djb8_hash( & lru_code, to_bytes( & font) )
|
||||
djb8_hash( & lru_code, to_bytes( & glyph_index ) )
|
||||
djb8_hash( & lru_code, to_bytes( & px_size ) )
|
||||
return
|
||||
}
|
||||
|
||||
decide_codepoint_region :: proc(ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> (region_kind : Atlas_Region_Kind, region : ^Atlas_Region, over_sample : Vec2)
|
||||
@(optimization_mode="favor_size")
|
||||
atlas_region_bbox :: #force_inline proc( region : Atlas_Region, local_idx : i32 ) -> (position, size: Vec2)
|
||||
{
|
||||
if parser_is_glyph_empty(&entry.parser_info, glyph_index) {
|
||||
return .None, nil, {}
|
||||
}
|
||||
size = vec2(region.slot_size)
|
||||
|
||||
bounds_0, bounds_1 := parser_get_glyph_box(&entry.parser_info, glyph_index)
|
||||
bounds_width := f32(bounds_1.x - bounds_0.x)
|
||||
bounds_height := f32(bounds_1.y - bounds_0.y)
|
||||
position.x = cast(f32) (( local_idx % region.capacity.x ) * region.slot_size.x)
|
||||
position.y = cast(f32) (( local_idx / region.capacity.x ) * region.slot_size.y)
|
||||
|
||||
atlas := & ctx.atlas
|
||||
glyph_buffer := & ctx.glyph_buffer
|
||||
glyph_padding := f32( atlas.glyph_padding ) * 2
|
||||
|
||||
bounds_width_scaled := i32(bounds_width * entry.size_scale * atlas.glyph_over_scalar + glyph_padding)
|
||||
bounds_height_scaled := i32(bounds_height * entry.size_scale * atlas.glyph_over_scalar + glyph_padding)
|
||||
|
||||
// Use a lookup table for faster region selection
|
||||
region_lookup := [4]struct { kind: Atlas_Region_Kind, region: ^Atlas_Region } {
|
||||
{ .A, & atlas.region_a },
|
||||
{ .B, & atlas.region_b },
|
||||
{ .C, & atlas.region_c },
|
||||
{ .D, & atlas.region_d },
|
||||
}
|
||||
|
||||
for region in region_lookup do if bounds_width_scaled <= region.region.width && bounds_height_scaled <= region.region.height {
|
||||
return region.kind, region.region, glyph_buffer.over_sample
|
||||
}
|
||||
|
||||
if bounds_width_scaled <= glyph_buffer.width \
|
||||
&& bounds_height_scaled <= glyph_buffer.height {
|
||||
over_sample = \
|
||||
bounds_width_scaled <= glyph_buffer.width / 2 &&
|
||||
bounds_height_scaled <= glyph_buffer.height / 2 ? \
|
||||
{2.0, 2.0} \
|
||||
: {1.0, 1.0}
|
||||
return .E, nil, over_sample
|
||||
}
|
||||
return .None, nil, {}
|
||||
position.x += f32(region.offset.x)
|
||||
position.y += f32(region.offset.y)
|
||||
return
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
atlas_decide_region :: #force_inline proc "contextless" (atlas : Atlas, glyph_buffer_size : Vec2, bounds_size_scaled : Vec2 ) -> (region_kind : Atlas_Region_Kind)
|
||||
{
|
||||
// profile(#procedure)
|
||||
glyph_padding_dbl := atlas.glyph_padding * 2
|
||||
padded_bounds := bounds_size_scaled + glyph_padding_dbl
|
||||
|
||||
for kind in 1 ..= 4 do if
|
||||
padded_bounds.x <= f32(atlas.regions[kind].slot_size.x) &&
|
||||
padded_bounds.y <= f32(atlas.regions[kind].slot_size.y)
|
||||
{
|
||||
return cast(Atlas_Region_Kind) kind
|
||||
}
|
||||
|
||||
if padded_bounds.x <= glyph_buffer_size.x && padded_bounds.y <= glyph_buffer_size.y{
|
||||
return .E
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
// Grab an atlas LRU cache slot.
|
||||
@(optimization_mode="favor_size")
|
||||
atlas_reserve_slot :: #force_inline proc ( region : ^Atlas_Region, lru_code : Atlas_Key ) -> (atlas_index : i32)
|
||||
{
|
||||
if region.next_idx < region.state.capacity
|
||||
{
|
||||
evicted := lru_put( & region.state, lru_code, region.next_idx )
|
||||
atlas_index = 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 != LRU_Fail_Mask_16)
|
||||
|
||||
atlas_index = lru_peek( region.state, next_evict_codepoint, must_find = true )
|
||||
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 )
|
||||
return
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
163
vefontcache/freetype_wip.odin
Normal file
163
vefontcache/freetype_wip.odin
Normal file
@@ -0,0 +1,163 @@
|
||||
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...
|
||||
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,
|
||||
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 := Draw_Call_Default
|
||||
draw.pass = Frame_Buffer_Pass.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)
|
||||
// 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_filled_path(&ctx.draw_list, outside, path[:], scale, translate)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
@@ -1,16 +1,58 @@
|
||||
package vefontcache
|
||||
|
||||
/*
|
||||
Didn't want to splinter this into more files..
|
||||
Just a bunch of utilities.
|
||||
*/
|
||||
|
||||
import "base:runtime"
|
||||
import "core:simd"
|
||||
import "core:math"
|
||||
|
||||
import core_log "core:log"
|
||||
|
||||
Colour :: [4]f32
|
||||
peek_array :: #force_inline proc "contextless" ( self : [dynamic]$Type ) -> Type {
|
||||
return self[ len(self) - 1 ]
|
||||
}
|
||||
|
||||
reload_array :: #force_inline proc( self : ^[dynamic]$Type, allocator : Allocator ) {
|
||||
raw := transmute( ^runtime.Raw_Dynamic_Array) self
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
reload_array_soa :: #force_inline proc( self : ^#soa[dynamic]$Type, allocator : Allocator ) {
|
||||
raw := runtime.raw_soa_footer(self)
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
reload_map :: #force_inline proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) {
|
||||
raw := transmute( ^runtime.Raw_Map) self
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
to_bytes :: #force_inline proc "contextless" ( typed_data : ^$Type ) -> []byte { return slice_ptr( transmute(^byte) typed_data, size_of(Type) ) }
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
djb8_hash :: #force_inline proc "contextless" ( hash : ^$Type, bytes : []byte ) { for value in bytes do (hash^) = (( (hash^) << 8) + (hash^) ) + Type(value) }
|
||||
|
||||
RGBA8 :: [4]u8
|
||||
RGBAN :: [4]f32
|
||||
Vec2 :: [2]f32
|
||||
Vec2i :: [2]i32
|
||||
Vec2_64 :: [2]f64
|
||||
|
||||
Transform :: struct {
|
||||
pos : Vec2,
|
||||
scale : Vec2,
|
||||
}
|
||||
|
||||
Range2 :: struct {
|
||||
p0, p1 : Vec2,
|
||||
}
|
||||
|
||||
mul_range2_vec2 :: #force_inline proc "contextless" ( range : Range2, v : Vec2 ) -> Range2 { return { range.p0 * v, range.p1 * v } }
|
||||
size_range2 :: #force_inline proc "contextless" ( range : Range2 ) -> Vec2 { return range.p1 - range.p0 }
|
||||
|
||||
vec2_from_scalar :: #force_inline proc "contextless" ( scalar : f32 ) -> Vec2 { return { scalar, scalar }}
|
||||
vec2_64_from_vec2 :: #force_inline proc "contextless" ( v2 : Vec2 ) -> Vec2_64 { return { f64(v2.x), f64(v2.y) }}
|
||||
vec2_from_vec2i :: #force_inline proc "contextless" ( v2i : Vec2i ) -> Vec2 { return { f32(v2i.x), f32(v2i.y) }}
|
||||
@@ -39,91 +81,29 @@ logf :: proc( fmt : string, args : ..any, level := core_log.Level.Info, loc :=
|
||||
core_log.logf( level, fmt, ..args, location = loc )
|
||||
}
|
||||
|
||||
reload_array :: proc( self : ^[dynamic]$Type, allocator : Allocator ) {
|
||||
raw := transmute( ^runtime.Raw_Dynamic_Array) self
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
reload_map :: proc( self : ^map [$KeyType] $EntryType, allocator : Allocator ) {
|
||||
raw := transmute( ^runtime.Raw_Map) self
|
||||
raw.allocator = allocator
|
||||
}
|
||||
|
||||
font_glyph_lru_code :: #force_inline proc "contextless" ( font : Font_ID, glyph_index : Glyph ) -> (lru_code : u64) {
|
||||
lru_code = u64(glyph_index) + ( ( 0x100000000 * u64(font) ) & 0xFFFFFFFF00000000 )
|
||||
return
|
||||
}
|
||||
|
||||
is_empty :: #force_inline proc ( ctx : ^Context, entry : ^Entry, glyph_index : Glyph ) -> b32
|
||||
@(optimization_mode="favor_size")
|
||||
to_glyph_buffer_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 )
|
||||
{
|
||||
if glyph_index == 0 do return true
|
||||
if parser_is_glyph_empty( & entry.parser_info, glyph_index ) do return true
|
||||
return false
|
||||
pos := position^
|
||||
scale_32 := scale^
|
||||
|
||||
quotient : Vec2 = 1.0 / size
|
||||
pos = pos * quotient * 2.0 - 1.0
|
||||
scale_32 = scale_32 * quotient * 2.0
|
||||
|
||||
(position^) = pos
|
||||
(scale^) = scale_32
|
||||
}
|
||||
|
||||
mark_batch_codepoint_seen :: #force_inline proc ( ctx : ^Context, lru_code : u64 ) {
|
||||
ctx.temp_codepoint_seen[lru_code] = true
|
||||
ctx.temp_codepoint_seen_num += 1
|
||||
}
|
||||
|
||||
reset_batch_codepoint_state :: #force_inline proc( ctx : ^Context ) {
|
||||
clear_map( & ctx.temp_codepoint_seen )
|
||||
ctx.temp_codepoint_seen_num = 0
|
||||
}
|
||||
|
||||
USE_F64_PRECISION_ON_X_FORM_OPS :: false
|
||||
|
||||
screenspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 )
|
||||
@(optimization_mode="favor_size")
|
||||
to_target_space :: #force_inline proc "contextless" ( #no_alias position, scale : ^Vec2, size : Vec2 )
|
||||
{
|
||||
when USE_F64_PRECISION_ON_X_FORM_OPS
|
||||
{
|
||||
pos_64 := vec2_64_from_vec2(position^)
|
||||
scale_64 := vec2_64_from_vec2(scale^)
|
||||
|
||||
quotient : Vec2_64 = 1.0 / vec2_64(size)
|
||||
pos_64 = pos_64 * quotient * 2.0 - 1.0
|
||||
scale_64 = scale_64 * quotient * 2.0
|
||||
|
||||
(position^) = { f32(pos_64.x), f32(pos_64.y) }
|
||||
(scale^) = { f32(scale_64.x), f32(scale_64.y) }
|
||||
}
|
||||
else
|
||||
{
|
||||
pos := position^
|
||||
scale_32 := scale^
|
||||
|
||||
quotient : Vec2 = 1.0 / size
|
||||
pos = pos * quotient * 2.0 - 1.0
|
||||
scale_32 = scale_32 * quotient * 2.0
|
||||
|
||||
(position^) = pos
|
||||
(scale^) = scale_32
|
||||
}
|
||||
quotient : Vec2 = 1.0 / size
|
||||
(position^) *= quotient
|
||||
(scale^) *= quotient
|
||||
}
|
||||
|
||||
textspace_x_form :: #force_inline proc "contextless" ( position, scale : ^Vec2, size : Vec2 )
|
||||
{
|
||||
when USE_F64_PRECISION_ON_X_FORM_OPS
|
||||
{
|
||||
pos_64 := vec2_64_from_vec2(position^)
|
||||
scale_64 := vec2_64_from_vec2(scale^)
|
||||
|
||||
quotient : Vec2_64 = 1.0 / vec2_64(size)
|
||||
pos_64 *= quotient
|
||||
scale_64 *= quotient
|
||||
|
||||
(position^) = { f32(pos_64.x), f32(pos_64.y) }
|
||||
(scale^) = { f32(scale_64.x), f32(scale_64.y) }
|
||||
}
|
||||
else
|
||||
{
|
||||
quotient : Vec2 = 1.0 / size
|
||||
(position^) *= quotient
|
||||
(scale^) *= quotient
|
||||
}
|
||||
}
|
||||
|
||||
USE_MANUAL_SIMD_FOR_BEZIER_OPS :: false
|
||||
USE_MANUAL_SIMD_FOR_BEZIER_OPS :: true
|
||||
|
||||
when ! USE_MANUAL_SIMD_FOR_BEZIER_OPS
|
||||
{
|
||||
@@ -132,11 +112,6 @@ when ! USE_MANUAL_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)
|
||||
|
||||
weight_start := (1 - alpha) * (1 - alpha)
|
||||
weight_control := 2.0 * (1 - alpha) * alpha
|
||||
weight_end := alpha * alpha
|
||||
@@ -154,12 +129,6 @@ when ! USE_MANUAL_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)
|
||||
|
||||
weight_start := (1 - alpha) * (1 - alpha) * (1 - alpha)
|
||||
weight_c_a := 3 * (1 - alpha) * (1 - alpha) * alpha
|
||||
weight_c_b := 3 * (1 - alpha) * alpha * alpha
|
||||
@@ -178,14 +147,17 @@ else
|
||||
{
|
||||
Vec2_SIMD :: simd.f32x4
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
vec2_to_simd :: #force_inline proc "contextless" (v: Vec2) -> Vec2_SIMD {
|
||||
return Vec2_SIMD{v.x, v.y, 0, 0}
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
simd_to_vec2 :: #force_inline proc "contextless" (v: Vec2_SIMD) -> Vec2 {
|
||||
return Vec2{ simd.extract(v, 0), simd.extract(v, 1) }
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
eval_point_on_bezier3 :: #force_inline proc "contextless" (p0, p1, p2: Vec2, alpha: f32) -> Vec2
|
||||
{
|
||||
simd_p0 := vec2_to_simd(p0)
|
||||
@@ -209,6 +181,7 @@ else
|
||||
return simd_to_vec2(result)
|
||||
}
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
eval_point_on_bezier4 :: #force_inline proc "contextless" (p0, p1, p2, p3: Vec2, alpha: f32) -> Vec2
|
||||
{
|
||||
simd_p0 := vec2_to_simd(p0)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -1,7 +1,10 @@
|
||||
package vefontcache
|
||||
|
||||
import "base:builtin"
|
||||
resize_soa_non_zero :: non_zero_resize_soa
|
||||
import "base:runtime"
|
||||
import "core:hash"
|
||||
fnv64a :: hash.fnv64a
|
||||
ginger16 :: hash.ginger16
|
||||
import "core:math"
|
||||
ceil_f16 :: math.ceil_f16
|
||||
ceil_f16le :: math.ceil_f16le
|
||||
@@ -34,6 +37,7 @@ import "core:mem"
|
||||
arena_allocator :: mem.arena_allocator
|
||||
arena_init :: mem.arena_init
|
||||
import "core:slice"
|
||||
import "core:unicode"
|
||||
|
||||
//#region("Proc overload mappings")
|
||||
|
||||
@@ -43,6 +47,10 @@ append :: proc {
|
||||
append_elem_string,
|
||||
}
|
||||
|
||||
append_soa :: proc {
|
||||
append_soa_elem
|
||||
}
|
||||
|
||||
ceil :: proc {
|
||||
math.ceil_f16,
|
||||
math.ceil_f16le,
|
||||
@@ -58,8 +66,8 @@ ceil :: proc {
|
||||
}
|
||||
|
||||
clear :: proc {
|
||||
clear_dynamic_array,
|
||||
clear_map,
|
||||
builtin.clear_dynamic_array,
|
||||
builtin.clear_map,
|
||||
}
|
||||
|
||||
floor :: proc {
|
||||
@@ -80,16 +88,39 @@ fill :: proc {
|
||||
slice.fill,
|
||||
}
|
||||
|
||||
max :: proc {
|
||||
linalg.max_single,
|
||||
linalg.max_double,
|
||||
}
|
||||
|
||||
make :: proc {
|
||||
make_dynamic_array,
|
||||
make_dynamic_array_len,
|
||||
make_dynamic_array_len_cap,
|
||||
make_map,
|
||||
make_map_cap,
|
||||
builtin.make_dynamic_array,
|
||||
builtin.make_dynamic_array_len,
|
||||
builtin.make_dynamic_array_len_cap,
|
||||
builtin.make_slice,
|
||||
builtin.make_map,
|
||||
builtin.make_map_cap,
|
||||
}
|
||||
|
||||
make_soa :: proc {
|
||||
builtin.make_soa_dynamic_array_len_cap,
|
||||
builtin.make_soa_slice,
|
||||
}
|
||||
|
||||
mul :: proc {
|
||||
mul_range2_vec2,
|
||||
}
|
||||
|
||||
peek :: proc {
|
||||
peek_array,
|
||||
}
|
||||
|
||||
resize :: proc {
|
||||
resize_dynamic_array,
|
||||
builtin.resize_dynamic_array,
|
||||
}
|
||||
|
||||
size :: proc {
|
||||
size_range2,
|
||||
}
|
||||
|
||||
vec2 :: proc {
|
||||
@@ -105,4 +136,22 @@ vec2_64 :: proc {
|
||||
vec2_64_from_vec2,
|
||||
}
|
||||
|
||||
import "../../grime"
|
||||
|
||||
@(deferred_none = profile_end, disabled = DISABLE_PROFILING)
|
||||
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
|
||||
grime.profile_begin(name, loc)
|
||||
}
|
||||
|
||||
@(disabled = DISABLE_PROFILING)
|
||||
profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
|
||||
grime.profile_begin(name, loc)
|
||||
}
|
||||
|
||||
@(disabled = DISABLE_PROFILING)
|
||||
profile_end :: #force_inline proc "contextless" () {
|
||||
grime.profile_end()
|
||||
}
|
||||
|
||||
//#endregion("Proc overload mappings")
|
||||
|
@@ -1,131 +0,0 @@
|
||||
package vefontcache
|
||||
|
||||
Shaped_Text :: struct {
|
||||
glyphs : [dynamic]Glyph,
|
||||
positions : [dynamic]Vec2,
|
||||
end_cursor_pos : Vec2,
|
||||
size : Vec2,
|
||||
}
|
||||
|
||||
Shaped_Text_Cache :: struct {
|
||||
storage : [dynamic]Shaped_Text,
|
||||
state : LRU_Cache,
|
||||
next_cache_id : i32,
|
||||
}
|
||||
|
||||
shape_lru_hash :: #force_inline proc "contextless" ( hash : ^u64, bytes : []byte ) {
|
||||
for value in bytes {
|
||||
(hash^) = (( (hash^) << 8) + (hash^) ) + u64(value)
|
||||
}
|
||||
}
|
||||
|
||||
shape_text_cached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry ) -> ^Shaped_Text
|
||||
{
|
||||
// profile(#procedure)
|
||||
font := font
|
||||
font_bytes := slice_ptr( transmute(^byte) & font, size_of(Font_ID) )
|
||||
text_bytes := transmute( []byte) text_utf8
|
||||
|
||||
lru_code : u64
|
||||
shape_lru_hash( & lru_code, font_bytes )
|
||||
shape_lru_hash( & lru_code, text_bytes )
|
||||
|
||||
shape_cache := & ctx.shape_cache
|
||||
state := & ctx.shape_cache.state
|
||||
|
||||
shape_cache_idx := lru_get( state, lru_code )
|
||||
if shape_cache_idx == -1
|
||||
{
|
||||
if shape_cache.next_cache_id < i32(state.capacity) {
|
||||
shape_cache_idx = shape_cache.next_cache_id
|
||||
shape_cache.next_cache_id += 1
|
||||
evicted := lru_put( state, lru_code, shape_cache_idx )
|
||||
}
|
||||
else
|
||||
{
|
||||
next_evict_idx := lru_get_next_evicted( state )
|
||||
assert( next_evict_idx != 0xFFFFFFFFFFFFFFFF )
|
||||
|
||||
shape_cache_idx = lru_peek( state, next_evict_idx, must_find = true )
|
||||
assert( shape_cache_idx != - 1 )
|
||||
|
||||
lru_put( state, lru_code, shape_cache_idx )
|
||||
}
|
||||
|
||||
shape_entry := & shape_cache.storage[ shape_cache_idx ]
|
||||
shape_text_uncached( ctx, font, text_utf8, entry, shape_entry )
|
||||
}
|
||||
|
||||
return & shape_cache.storage[ shape_cache_idx ]
|
||||
}
|
||||
|
||||
shape_text_uncached :: proc( ctx : ^Context, font : Font_ID, text_utf8 : string, entry : ^Entry, output : ^Shaped_Text )
|
||||
{
|
||||
// profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
assert( font >= 0 && int(font) < len(ctx.entries) )
|
||||
|
||||
clear( & output.glyphs )
|
||||
clear( & output.positions )
|
||||
|
||||
ascent_i32, descent_i32, line_gap_i32 := parser_get_font_vertical_metrics( & entry.parser_info )
|
||||
ascent := f32(ascent_i32)
|
||||
descent := f32(descent_i32)
|
||||
line_gap := f32(line_gap_i32)
|
||||
line_height := (ascent - descent + line_gap) * entry.size_scale
|
||||
|
||||
if ctx.use_advanced_shaper
|
||||
{
|
||||
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 )
|
||||
return
|
||||
}
|
||||
else
|
||||
{
|
||||
// Note(Original Author):
|
||||
// We use our own fallback dumbass text shaping.
|
||||
// WARNING: PLEASE USE HARFBUZZ. GOOD TEXT SHAPING IS IMPORTANT FOR INTERNATIONALISATION.
|
||||
|
||||
line_count : int = 1
|
||||
max_line_width : f32 = 0
|
||||
position : Vec2
|
||||
|
||||
prev_codepoint : rune
|
||||
for codepoint in text_utf8
|
||||
{
|
||||
if prev_codepoint > 0 {
|
||||
kern := parser_get_codepoint_kern_advance( & entry.parser_info, prev_codepoint, codepoint )
|
||||
position.x += f32(kern) * entry.size_scale
|
||||
}
|
||||
if codepoint == '\n'
|
||||
{
|
||||
line_count += 1
|
||||
max_line_width = max(max_line_width, position.x)
|
||||
position.x = 0.0
|
||||
position.y -= line_height
|
||||
position.y = position.y
|
||||
prev_codepoint = rune(0)
|
||||
continue
|
||||
}
|
||||
if abs( entry.size ) <= ctx.shaper_ctx.adv_snap_small_font_threshold {
|
||||
position.x = ceil(position.x)
|
||||
}
|
||||
|
||||
append( & output.glyphs, parser_find_glyph_index( & entry.parser_info, codepoint ))
|
||||
advance, _ := parser_get_codepoint_horizontal_metrics( & entry.parser_info, codepoint )
|
||||
|
||||
append( & output.positions, Vec2 {
|
||||
ceil(position.x),
|
||||
ceil(position.y)
|
||||
})
|
||||
|
||||
position.x += f32(advance) * entry.size_scale
|
||||
prev_codepoint = codepoint
|
||||
}
|
||||
|
||||
output.end_cursor_pos = position
|
||||
max_line_width = max(max_line_width, position.x)
|
||||
|
||||
output.size.x = max_line_width
|
||||
output.size.y = f32(line_count) * line_height
|
||||
}
|
||||
}
|
@@ -6,11 +6,61 @@ Note(Ed): The only reason I didn't directly use harfbuzz is because hamza exists
|
||||
import "core:c"
|
||||
import "thirdparty:harfbuzz"
|
||||
|
||||
Shape_Key :: u32
|
||||
|
||||
/* A text whose codepoints have had their relevant glyphs and
|
||||
associated data resolved for processing in a draw list generation stage.
|
||||
Traditionally a shape only refers to resolving which glyph and
|
||||
its position should be used for rendering.
|
||||
|
||||
For this library's case it also involes keeping any content
|
||||
that does not have to be resolved once again in the later stage of processing:
|
||||
* Resolve atlas lru codes
|
||||
* Resolve glyph bounds and scale
|
||||
* Resolve atlas region the glyph is associated with.
|
||||
|
||||
Ideally the user should resolve this shape once and cache/store it on their side.
|
||||
They have the best ability to avoid costly lookups to streamline
|
||||
a hot path to only focusing on draw list generation that must be computed every frame.
|
||||
*/
|
||||
Shaped_Text :: struct #packed {
|
||||
glyph : [dynamic]Glyph,
|
||||
position : [dynamic]Vec2,
|
||||
atlas_lru_code : [dynamic]Atlas_Key,
|
||||
region_kind : [dynamic]Atlas_Region_Kind,
|
||||
bounds : [dynamic]Range2,
|
||||
end_cursor_pos : Vec2,
|
||||
size : Vec2,
|
||||
font_id : Font_ID,
|
||||
// TODO(Ed): We need to track the font here for usage in user interface when directly drawing the shape.
|
||||
}
|
||||
|
||||
// Ease of use cache, can handle thousands of lookups per frame with ease.
|
||||
// TODO(Ed) It might perform better with a tailored made hashtable implementation for the LRU_Cache or dedicated array struct/procs for the Shaped_Text.
|
||||
Shaped_Text_Cache :: struct {
|
||||
storage : [dynamic]Shaped_Text,
|
||||
state : LRU_Cache(Shape_Key),
|
||||
next_cache_id : i32,
|
||||
}
|
||||
|
||||
// Used by shaper_shape_text_cached, allows user to specify their own proc at compile-time without having to rewrite the caching implementation.
|
||||
Shaper_Shape_Text_Uncached_Proc :: #type proc( ctx : ^Shaper_Context,
|
||||
atlas : Atlas,
|
||||
glyph_buffer_size : Vec2,
|
||||
entry : Entry,
|
||||
font_px_Size : f32,
|
||||
font_scale : f32,
|
||||
text_utf8 : string,
|
||||
output : ^Shaped_Text
|
||||
)
|
||||
|
||||
// Note(Ed): Not used..
|
||||
Shaper_Kind :: enum {
|
||||
Naive = 0,
|
||||
Latin = 0,
|
||||
Harfbuzz = 1,
|
||||
}
|
||||
|
||||
// Not much here other than just keep track of a harfbuzz var and deciding to keep runtime config here used by the shapers.
|
||||
Shaper_Context :: struct {
|
||||
hb_buffer : harfbuzz.Buffer,
|
||||
|
||||
@@ -18,6 +68,7 @@ Shaper_Context :: struct {
|
||||
adv_snap_small_font_threshold : f32,
|
||||
}
|
||||
|
||||
// Only used with harbuzz for now. Resolved during load_font for a font Entry.
|
||||
Shaper_Info :: struct {
|
||||
blob : harfbuzz.Blob,
|
||||
face : harfbuzz.Face,
|
||||
@@ -37,46 +88,59 @@ shaper_shutdown :: proc( ctx : ^Shaper_Context )
|
||||
}
|
||||
}
|
||||
|
||||
shaper_load_font :: proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr ) -> (info : Shaper_Info)
|
||||
shaper_load_font :: #force_inline proc( ctx : ^Shaper_Context, label : string, data : []byte, user_data : rawptr = nil ) -> (info : 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 )
|
||||
font = harfbuzz.font_create( face )
|
||||
info.blob = harfbuzz.blob_create( raw_data(data), cast(c.uint) len(data), harfbuzz.Memory_Mode.READONLY, user_data, nil )
|
||||
info.face = harfbuzz.face_create( info.blob, 0 )
|
||||
info.font = harfbuzz.font_create( info.face )
|
||||
return
|
||||
}
|
||||
|
||||
shaper_unload_font :: proc( ctx : ^Shaper_Info )
|
||||
shaper_unload_font :: #force_inline proc( info : ^Shaper_Info )
|
||||
{
|
||||
using ctx
|
||||
if blob != nil do harfbuzz.font_destroy( font )
|
||||
if face != nil do harfbuzz.face_destroy( face )
|
||||
if blob != nil do harfbuzz.blob_destroy( blob )
|
||||
if info.blob != nil do harfbuzz.font_destroy( info.font )
|
||||
if info.face != nil do harfbuzz.face_destroy( info.face )
|
||||
if info.blob != nil do harfbuzz.blob_destroy( info.blob )
|
||||
}
|
||||
|
||||
shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, output :^Shaped_Text, text_utf8 : string,
|
||||
ascent, descent, line_gap : i32, size, size_scale : f32 )
|
||||
// Recommended shaper. Very performant.
|
||||
// TODO(Ed): Would be nice to properly support vertical shaping, right now its strictly just horizontal...
|
||||
@(optimization_mode="favor_size")
|
||||
shaper_shape_harfbuzz :: proc( ctx : ^Shaper_Context, text_utf8 : string, entry : Entry, font_px_Size, font_scale : f32, output :^Shaped_Text )
|
||||
{
|
||||
// profile(#procedure)
|
||||
profile(#procedure)
|
||||
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)
|
||||
|
||||
ascent := entry.ascent
|
||||
descent := entry.descent
|
||||
line_gap := entry.line_gap
|
||||
|
||||
max_line_width := f32(0)
|
||||
line_count := 1
|
||||
line_height := ((ascent - descent + line_gap) * size_scale)
|
||||
line_height := ((ascent - descent + line_gap) * font_scale)
|
||||
|
||||
position, vertical_position : f32
|
||||
shape_run :: proc( buffer : harfbuzz.Buffer, script : harfbuzz.Script, font : harfbuzz.Font, output : ^Shaped_Text,
|
||||
position, vertical_position, max_line_width: ^f32, line_count: ^int,
|
||||
ascent, descent, line_gap, size, size_scale: f32,
|
||||
snap_shape_pos : b32, adv_snap_small_font_threshold : f32 )
|
||||
position : Vec2
|
||||
|
||||
@(optimization_mode="favor_size")
|
||||
shape_run :: proc( output : ^Shaped_Text,
|
||||
entry : Entry,
|
||||
buffer : harfbuzz.Buffer,
|
||||
script : harfbuzz.Script,
|
||||
|
||||
position : ^Vec2,
|
||||
max_line_width : ^f32,
|
||||
line_count : ^int,
|
||||
|
||||
font_px_size : f32,
|
||||
font_scale : f32,
|
||||
|
||||
snap_shape_pos : b32,
|
||||
adv_snap_small_font_threshold : f32
|
||||
)
|
||||
{
|
||||
profile(#procedure)
|
||||
// Set script and direction. We use the system's default langauge.
|
||||
// script = HB_SCRIPT_LATIN
|
||||
harfbuzz.buffer_set_script( buffer, script )
|
||||
@@ -85,57 +149,58 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp
|
||||
|
||||
// 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 )
|
||||
harfbuzz.shape( entry.shaper_info.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 )
|
||||
|
||||
line_height := (ascent - descent + line_gap) * size_scale
|
||||
line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale
|
||||
|
||||
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
|
||||
glyph := cast(Glyph) hb_glyph.codepoint
|
||||
|
||||
if hb_glyph.cluster > 0
|
||||
{
|
||||
(max_line_width^) = max( max_line_width^, position^ )
|
||||
(position^) = 0.0
|
||||
(vertical_position^) -= line_height
|
||||
(vertical_position^) = floor(vertical_position^ + 0.5)
|
||||
(max_line_width^) = max( max_line_width^, position.x )
|
||||
position.x = 0.0
|
||||
position.y -= line_height
|
||||
position.y = floor(position.y)
|
||||
(line_count^) += 1
|
||||
continue
|
||||
}
|
||||
if abs( size ) <= adv_snap_small_font_threshold
|
||||
if abs( font_px_size ) <= adv_snap_small_font_threshold
|
||||
{
|
||||
(position^) = ceil( position^ )
|
||||
(position^) = 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
|
||||
pos += offset_x
|
||||
v_pos += offset_y
|
||||
glyph_pos := position^
|
||||
offset := Vec2 { f32(hb_gposition.x_offset), f32(hb_gposition.y_offset) } * font_scale
|
||||
glyph_pos += offset
|
||||
|
||||
if snap_shape_pos {
|
||||
pos = ceil(pos)
|
||||
v_pos = ceil(v_pos)
|
||||
glyph_pos = ceil(glyph_pos)
|
||||
}
|
||||
append( & output.positions, Vec2 {pos, v_pos})
|
||||
|
||||
(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^)
|
||||
advance := Vec2 {
|
||||
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)
|
||||
|
||||
is_empty := parser_is_glyph_empty(entry.parser_info, glyph)
|
||||
if ! is_empty {
|
||||
append( & output.glyph, glyph )
|
||||
append( & output.position, glyph_pos)
|
||||
}
|
||||
}
|
||||
|
||||
output.end_cursor_pos.x = position^
|
||||
output.end_cursor_pos.y = vertical_position^
|
||||
output.end_cursor_pos = position^
|
||||
harfbuzz.buffer_clear_contents( buffer )
|
||||
}
|
||||
|
||||
@@ -160,26 +225,232 @@ shaper_shape_from_text :: proc( ctx : ^Shaper_Context, info : ^Shaper_Info, outp
|
||||
}
|
||||
|
||||
// End current run since we've encountered a script change.
|
||||
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,
|
||||
ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold
|
||||
)
|
||||
shape_run( output,
|
||||
entry,
|
||||
ctx.hb_buffer,
|
||||
current_script,
|
||||
& position,
|
||||
& max_line_width,
|
||||
& line_count,
|
||||
font_px_Size,
|
||||
font_scale,
|
||||
ctx.snap_glyph_position,
|
||||
ctx.adv_snap_small_font_threshold
|
||||
)
|
||||
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, & max_line_width, & line_count,
|
||||
ascent, descent, line_gap, size, size_scale,
|
||||
ctx.snap_glyph_position, ctx.adv_snap_small_font_threshold
|
||||
)
|
||||
shape_run( output,
|
||||
entry,
|
||||
ctx.hb_buffer,
|
||||
current_script,
|
||||
& position,
|
||||
& max_line_width,
|
||||
& line_count,
|
||||
font_px_Size,
|
||||
font_scale,
|
||||
ctx.snap_glyph_position,
|
||||
ctx.adv_snap_small_font_threshold
|
||||
)
|
||||
|
||||
// Set the final size
|
||||
output.size.x = max_line_width
|
||||
output.size.y = f32(line_count) * line_height
|
||||
return
|
||||
}
|
||||
|
||||
shaper_shape_text_uncached_advanced :: #force_inline proc( ctx : ^Shaper_Context,
|
||||
atlas : Atlas,
|
||||
glyph_buffer_size : Vec2,
|
||||
entry : Entry,
|
||||
font_px_size : f32,
|
||||
font_scale : f32,
|
||||
text_utf8 : string,
|
||||
output : ^Shaped_Text
|
||||
)
|
||||
{
|
||||
profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
|
||||
clear( & output.glyph )
|
||||
clear( & output.position )
|
||||
|
||||
shaper_shape_harfbuzz( ctx, text_utf8, entry, font_px_size, font_scale, output )
|
||||
|
||||
// Resolve each glyphs: bounds, atlas lru, and the atlas region as we have everything we need now.
|
||||
|
||||
resize( & output.atlas_lru_code, len(output.glyph) )
|
||||
resize( & output.region_kind, len(output.glyph) )
|
||||
resize( & output.bounds, len(output.glyph) )
|
||||
|
||||
profile_begin("atlas_lru_code")
|
||||
for id, index in output.glyph
|
||||
{
|
||||
output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, id)
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("bounds & region")
|
||||
for id, index in output.glyph
|
||||
{
|
||||
bounds := & output.bounds[index]
|
||||
(bounds ^) = parser_get_bounds( entry.parser_info, id )
|
||||
bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale
|
||||
output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled )
|
||||
}
|
||||
profile_end()
|
||||
}
|
||||
|
||||
// Basic western alphabet based shaping. Not that much faster than harfbuzz if at all.
|
||||
shaper_shape_text_latin :: proc( ctx : ^Shaper_Context,
|
||||
atlas : Atlas,
|
||||
glyph_buffer_size : Vec2,
|
||||
entry : Entry,
|
||||
font_px_size : f32,
|
||||
font_scale : f32,
|
||||
text_utf8 : string,
|
||||
output : ^Shaped_Text
|
||||
)
|
||||
{
|
||||
profile(#procedure)
|
||||
assert( ctx != nil )
|
||||
|
||||
clear( & output.glyph )
|
||||
clear( & output.position )
|
||||
|
||||
line_height := (entry.ascent - entry.descent + entry.line_gap) * font_scale
|
||||
|
||||
line_count : int = 1
|
||||
max_line_width : f32 = 0
|
||||
position : Vec2
|
||||
|
||||
prev_codepoint : rune
|
||||
for codepoint, index in text_utf8
|
||||
{
|
||||
if prev_codepoint > 0 {
|
||||
kern := parser_get_codepoint_kern_advance( entry.parser_info, prev_codepoint, codepoint )
|
||||
position.x += f32(kern) * font_scale
|
||||
}
|
||||
if codepoint == '\n'
|
||||
{
|
||||
line_count += 1
|
||||
max_line_width = max(max_line_width, position.x)
|
||||
position.x = 0.0
|
||||
position.y -= line_height
|
||||
position.y = position.y
|
||||
prev_codepoint = rune(0)
|
||||
continue
|
||||
}
|
||||
if abs( font_px_size ) <= ctx.adv_snap_small_font_threshold {
|
||||
position.x = ceil(position.x)
|
||||
}
|
||||
|
||||
glyph_index := parser_find_glyph_index( entry.parser_info, codepoint )
|
||||
is_glyph_empty := parser_is_glyph_empty( entry.parser_info, glyph_index )
|
||||
if ! is_glyph_empty
|
||||
{
|
||||
append( & output.glyph, glyph_index)
|
||||
append( & output.position, Vec2 {
|
||||
ceil(position.x),
|
||||
ceil(position.y)
|
||||
})
|
||||
}
|
||||
|
||||
advance, _ := parser_get_codepoint_horizontal_metrics( entry.parser_info, codepoint )
|
||||
position.x += f32(advance) * font_scale
|
||||
prev_codepoint = codepoint
|
||||
}
|
||||
|
||||
output.end_cursor_pos = position
|
||||
max_line_width = max(max_line_width, position.x)
|
||||
|
||||
output.size.x = max_line_width
|
||||
output.size.y = f32(line_count) * line_height
|
||||
|
||||
// Resolve each glyphs: bounds, atlas lru, and the atlas region as we have everything we need now.
|
||||
|
||||
resize( & output.atlas_lru_code, len(output.glyph) )
|
||||
resize( & output.region_kind, len(output.glyph) )
|
||||
resize( & output.bounds, len(output.glyph) )
|
||||
|
||||
profile_begin("atlas_lru_code")
|
||||
for id, index in output.glyph
|
||||
{
|
||||
output.atlas_lru_code[index] = atlas_glyph_lru_code(entry.id, font_px_size, id)
|
||||
}
|
||||
profile_end()
|
||||
|
||||
profile_begin("bounds & region")
|
||||
for id, index in output.glyph
|
||||
{
|
||||
bounds := & output.bounds[index]
|
||||
(bounds ^) = parser_get_bounds( entry.parser_info, id )
|
||||
bounds_size_scaled := (bounds.p1 - bounds.p0) * font_scale
|
||||
output.region_kind[index] = atlas_decide_region( atlas, glyph_buffer_size, bounds_size_scaled )
|
||||
}
|
||||
profile_end()
|
||||
}
|
||||
|
||||
// Shapes are tracked by the library's context using the shape cache
|
||||
// and the key is resolved using the font, the desired pixel size, and the text bytes to be shaped.
|
||||
// Thus this procedures cost will be proporitonal to how muh text it has to sift through.
|
||||
// djb8_hash is used as its been pretty good for thousands of hashed lines that around 6-120 charactes long
|
||||
// (and its very fast).
|
||||
@(optimization_mode="favor_size")
|
||||
shaper_shape_text_cached :: proc( text_utf8 : string,
|
||||
ctx : ^Shaper_Context,
|
||||
shape_cache : ^Shaped_Text_Cache,
|
||||
atlas : Atlas,
|
||||
glyph_buffer_size : Vec2,
|
||||
font : Font_ID,
|
||||
entry : Entry,
|
||||
font_px_size : f32,
|
||||
font_scale : f32,
|
||||
shape_text_uncached : $Shaper_Shape_Text_Uncached_Proc
|
||||
) -> (shaped_text : Shaped_Text)
|
||||
{
|
||||
profile(#procedure)
|
||||
font := font
|
||||
font_px_size := font_px_size
|
||||
font_bytes := to_bytes( & font )
|
||||
size_bytes := to_bytes( & font_px_size )
|
||||
text_bytes := transmute( []byte) text_utf8
|
||||
|
||||
lru_code : Shape_Key
|
||||
djb8_hash( & lru_code, font_bytes )
|
||||
djb8_hash( & lru_code, size_bytes )
|
||||
djb8_hash( & lru_code, text_bytes )
|
||||
|
||||
state := & shape_cache.state
|
||||
|
||||
shape_cache_idx := lru_get( state, lru_code )
|
||||
if shape_cache_idx == -1
|
||||
{
|
||||
if shape_cache.next_cache_id < i32(state.capacity) {
|
||||
shape_cache_idx = shape_cache.next_cache_id
|
||||
shape_cache.next_cache_id += 1
|
||||
evicted := lru_put( state, lru_code, shape_cache_idx )
|
||||
}
|
||||
else
|
||||
{
|
||||
next_evict_idx := lru_get_next_evicted( state ^ )
|
||||
assert( next_evict_idx != LRU_Fail_Mask_32 )
|
||||
|
||||
shape_cache_idx = lru_peek( state ^, next_evict_idx, must_find = true )
|
||||
assert( shape_cache_idx != - 1 )
|
||||
|
||||
lru_put( state, lru_code, shape_cache_idx )
|
||||
}
|
||||
|
||||
storage_entry := & shape_cache.storage[ shape_cache_idx ]
|
||||
shape_text_uncached( ctx, atlas, glyph_buffer_size, entry, font_px_size, font_scale, text_utf8, storage_entry )
|
||||
|
||||
shaped_text = storage_entry ^
|
||||
return
|
||||
}
|
||||
|
||||
shaped_text = shape_cache.storage[ shape_cache_idx ]
|
||||
return
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user