Gut raylib usage from the codebase.
Going to either fully commit to sokol or if it fails, rolling the platform layer myself.
This commit is contained in:
@ -1,2 +1,2 @@
|
||||
package sectr
|
||||
|
||||
package sectr
|
353
code/sectr/font/fontstash.odin
Normal file
353
code/sectr/font/fontstash.odin
Normal file
@ -0,0 +1,353 @@
|
||||
/*
|
||||
Yet another port of fontstash.
|
||||
|
||||
I decided t use this instead of the odin port as it already deviated from the original impl.
|
||||
So The code was small enough that I mine as well learn it by porting for my use case.
|
||||
|
||||
Original copyright for fonstash.h:
|
||||
------------------------------------------------------------------------------
|
||||
Copyright (c) 2009-2013 Mikko Mononen memon@inside.org
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
package sectr
|
||||
|
||||
import stbtt "vendor:stb/truetype"
|
||||
|
||||
Range2_i16 :: struct #raw_union {
|
||||
using pts : Vec2_i16,
|
||||
using xy : struct {
|
||||
x0, y0, x1, y1 : i16,
|
||||
}
|
||||
}
|
||||
|
||||
Vec2_i16 :: [2]i16
|
||||
|
||||
FSTASH_Invalid :: -1
|
||||
|
||||
FSTASH_Hash_Lut_Size :: 256
|
||||
FSTASH_Max_Fallbacks :: 20
|
||||
FSTASH_Max_States :: 20
|
||||
FSTASH_Vertex_Count :: 1024
|
||||
FSTASH_Init_Atlas_Nodes :: 256
|
||||
|
||||
FSTASH_FontLuts :: [FSTASH_Hash_Lut_Size]i32
|
||||
FSTASH_FontFallbacks :: [FSTASH_Max_Fallbacks]i32
|
||||
|
||||
FSTASH_HandleErrorProc :: #type proc( uptr : rawptr, error, val : i32 )
|
||||
|
||||
FSTASH_RenderCreateProc :: #type proc( uptr : rawptr, width, height : i32 )
|
||||
FSTASH_RenderResizeProc :: #type proc( uptr : rawptr, width, height : i32 )
|
||||
FSTASH_RenderUpdateProc :: #type proc( uptr : rawptr, rect : ^i32, data : ^u8 )
|
||||
FSTASH_RenderDrawProc :: #type proc( uptr : rawptr, verts : ^f32, tcoords : ^f32, colors : ^i32, num_verts : i32 )
|
||||
FSTASH_RenderDelete :: #type proc( uptr : rawptr )
|
||||
|
||||
FSTASH_AlignFlag :: enum u32 {
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
Top,
|
||||
Middle,
|
||||
Bottom,
|
||||
Baseline,
|
||||
}
|
||||
FSTASH_AlignFlags :: bit_set[ FSTASH_AlignFlag; u32 ]
|
||||
|
||||
// FONSflags
|
||||
FSTASH_QuadLocation :: enum u32 {
|
||||
Top_Left = 1,
|
||||
Bottom_Left = 2,
|
||||
}
|
||||
|
||||
FSTASH_Atlas :: struct {
|
||||
dud : i32,
|
||||
}
|
||||
|
||||
FSTASH_AtlasNode :: struct {
|
||||
x, y, width : i16,
|
||||
}
|
||||
|
||||
FSTASH_ErrorCode :: enum u32 {
|
||||
Atlas_Full,
|
||||
Scratch_Full,
|
||||
States_Overflow,
|
||||
States_Underflow,
|
||||
}
|
||||
|
||||
FSTASH_Quad :: struct {
|
||||
x0, y0, s0, t0 : f32,
|
||||
x1, y1, s1, t1 : f32,
|
||||
}
|
||||
|
||||
FSTASH_Font :: struct {
|
||||
info : stbtt.fontinfo,
|
||||
name : string,
|
||||
data : []byte,
|
||||
free_data : bool,
|
||||
|
||||
ascender : f32,
|
||||
descender : f32,
|
||||
line_height : f32,
|
||||
|
||||
glyphs : Array(FSTASH_Glyph),
|
||||
lut : FSTASH_FontLuts,
|
||||
fallbacks : FSTASH_FontFallbacks,
|
||||
num_fallbacks : i32,
|
||||
}
|
||||
|
||||
FSTASH_Glyph :: struct {
|
||||
codepoint : rune,
|
||||
index, next : i32,
|
||||
size, blur : i16,
|
||||
x_advance : i16,
|
||||
box : Range2_i16,
|
||||
offset : Vec2_i16,
|
||||
}
|
||||
|
||||
FSTASH_Params :: struct {
|
||||
width, height : i32,
|
||||
quad_location : FSTASH_QuadLocation, // (flags)
|
||||
render_create : FSTASH_RenderCreateProc,
|
||||
render_resize : FSTASH_RenderResizeProc,
|
||||
render_update : FSTASH_RenderUpdateProc,
|
||||
render_draw : FSTASH_RenderDrawProc,
|
||||
render_delete : FSTASH_RenderDelete,
|
||||
}
|
||||
|
||||
FSTASH_State :: struct {
|
||||
font : i32,
|
||||
alignment : i32,
|
||||
size : f32,
|
||||
color : [4]u8,
|
||||
blur : f32,
|
||||
spacing : f32,
|
||||
}
|
||||
|
||||
FSTASH_TextIter :: struct {
|
||||
x, y : f32,
|
||||
next_x, next_y : f32,
|
||||
scale, spacing : f32,
|
||||
|
||||
isize, iblur : i16,
|
||||
|
||||
font : ^FSTASH_Font,
|
||||
prev_glyph_id : i32,
|
||||
|
||||
codepoint : rune,
|
||||
utf8_state : rune,
|
||||
|
||||
str : string,
|
||||
next : string,
|
||||
end : string,
|
||||
}
|
||||
|
||||
FSTASH_Context :: struct {
|
||||
params : FSTASH_Params,
|
||||
|
||||
// Atlas
|
||||
atlas : Array(FSTASH_AtlasNode),
|
||||
texture_data : []byte,
|
||||
width, height : i32,
|
||||
// ----
|
||||
|
||||
normalized_size : Vec2,
|
||||
|
||||
verts : [FSTASH_Vertex_Count * 2]f32,
|
||||
tcoords : [FSTASH_Vertex_Count * 2]f32,
|
||||
colors : [FSTASH_Vertex_Count ]f32,
|
||||
|
||||
states : [FSTASH_Max_States]FSTASH_State,
|
||||
num_states : i32,
|
||||
|
||||
handle_error : FSTASH_HandleErrorProc,
|
||||
error_uptr : rawptr,
|
||||
}
|
||||
|
||||
fstash_decode_utf8 :: proc( state : ^rune, codepoint : ^rune, to_decode : byte ) -> bool
|
||||
{
|
||||
UTF8_Accept :: 0
|
||||
UTF8_Reject :: 1
|
||||
|
||||
@static UTF8_Decode_Table := [?]u8 {
|
||||
// The first part of the table maps bytes to character classes that
|
||||
// to reduce the size of the transition table and create bitmasks.
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1F
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3F
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5F
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7F
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9F
|
||||
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // A0..BF
|
||||
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C0..DF
|
||||
10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, // E0..FF
|
||||
|
||||
// The second part is a transition table that maps a combination
|
||||
// of a state of the automaton and a character class to a state.
|
||||
0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
|
||||
12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
|
||||
12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
|
||||
12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
|
||||
12,36,12,12,12,12,12,12,12,12,12,12,
|
||||
}
|
||||
|
||||
to_decode_rune := rune(to_decode)
|
||||
type := UTF8_Decode_Table[to_decode_rune]
|
||||
|
||||
// Update codepoint otherwise initialize it.
|
||||
(codepoint^) = ((state^) != UTF8_Accept) ? \
|
||||
((to_decode_rune & 0x3F) | ((codepoint^) << 6)) \
|
||||
: ((0xFF >> type) & (to_decode_rune))
|
||||
|
||||
(state^) = cast(rune)(UTF8_Decode_Table[256 + (state^) * 16 + rune(type)])
|
||||
return (state^) == UTF8_Accept
|
||||
}
|
||||
|
||||
fstash_atlas_delete :: proc ( ctx : ^FSTASH_Context ) {
|
||||
using ctx
|
||||
array_free( ctx.atlas )
|
||||
}
|
||||
|
||||
fstash_atlas_expand :: proc( ctx : ^FSTASH_Context, width, height : i32 )
|
||||
{
|
||||
if width > ctx.width {
|
||||
fstash_atlas_insert( ctx, ctx.atlas.num, ctx.width, 0, width - ctx.width )
|
||||
}
|
||||
|
||||
ctx.width = width
|
||||
ctx.height = height
|
||||
}
|
||||
|
||||
fstash_atlas_init :: proc( ctx : ^FSTASH_Context, width, height : i32, num_nodes : u32 = FSTASH_Init_Atlas_Nodes )
|
||||
{
|
||||
error : AllocatorError
|
||||
ctx.atlas, error = array_init_reserve( FSTASH_AtlasNode, context.allocator, u64(num_nodes), dbg_name = "font atlas" )
|
||||
ensure(error != AllocatorError.None, "Failed to allocate font atlas")
|
||||
|
||||
ctx.width = width
|
||||
ctx.height = height
|
||||
|
||||
array_append( & ctx.atlas, FSTASH_AtlasNode{ width = i16(width)} )
|
||||
}
|
||||
|
||||
fstash_atlas_insert :: proc( ctx : ^FSTASH_Context, id : u64, x, y, width : i32 ) -> (error : AllocatorError)
|
||||
{
|
||||
error = array_append_at( & ctx.atlas, FSTASH_AtlasNode{ i16(x), i16(y), i16(width) }, id )
|
||||
return
|
||||
}
|
||||
|
||||
fstash_atlas_remove :: proc( ctx : ^FSTASH_Context, id : u64 )
|
||||
{
|
||||
array_remove_at( ctx.atlas, id )
|
||||
}
|
||||
|
||||
fstash_atlas_reset :: proc( ctx : ^FSTASH_Context, width, height : i32 )
|
||||
{
|
||||
ctx.width = width
|
||||
ctx.height = height
|
||||
array_clear( ctx.atlas )
|
||||
|
||||
array_append( & ctx.atlas, FSTASH_AtlasNode{ width = i16(width)} )
|
||||
}
|
||||
|
||||
fstash_atlas_add_skyline_level :: proc (ctx : ^FSTASH_Context, id : u64, x, y, width, height : i32 ) -> (error : AllocatorError)
|
||||
{
|
||||
insert :: fstash_atlas_insert
|
||||
remove :: fstash_atlas_remove
|
||||
|
||||
error = insert( ctx, id, x, y + height, width)
|
||||
if error != AllocatorError.None {
|
||||
ensure( false, "Failed to insert into atlas")
|
||||
return
|
||||
}
|
||||
|
||||
// Delete skyline segments that fall under the shadow of the new segment.
|
||||
for sky_id := id; sky_id < ctx.atlas.num; sky_id += 1
|
||||
{
|
||||
curr := & ctx.atlas.data[sky_id ]
|
||||
next := & ctx.atlas.data[sky_id + 1]
|
||||
if curr.x >= next.x + next.width do break
|
||||
|
||||
shrink := i16(next.x + next.width - curr.x)
|
||||
curr.x += shrink
|
||||
curr.width -= shrink
|
||||
|
||||
if curr.width > 0 do break
|
||||
|
||||
remove(ctx, sky_id)
|
||||
sky_id -= 1
|
||||
}
|
||||
|
||||
|
||||
// Merge same height skyline segments that are next to each other.
|
||||
for sky_id := id; sky_id < ctx.atlas.num - 1;
|
||||
{
|
||||
curr := & ctx.atlas.data[sky_id ]
|
||||
next := & ctx.atlas.data[sky_id + 1]
|
||||
|
||||
if curr.y == next.y {
|
||||
curr.width += next.width
|
||||
remove(ctx, sky_id + 1)
|
||||
}
|
||||
else {
|
||||
sky_id += 1
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
fstash_atlas_rect_fits :: proc( ctx : ^FSTASH_Context, location, width, height : i32 ) -> (max_height : i32)
|
||||
{
|
||||
// Checks if there is enough space at the location of skyline span 'i',
|
||||
// and return the max height of all skyline spans under that at that location,
|
||||
// (think tetris block being dropped at that position). Or -1 if no space found.
|
||||
atlas := array_to_slice(ctx.atlas)
|
||||
node := atlas[location]
|
||||
|
||||
space_left : i32
|
||||
if i32(node.x) + width > ctx.width {
|
||||
max_height = -1
|
||||
return
|
||||
}
|
||||
|
||||
space_left = width;
|
||||
|
||||
y := i32(node.y)
|
||||
location := location
|
||||
for ; space_left > 0;
|
||||
{
|
||||
if u64(location) == ctx.atlas.num {
|
||||
max_height = -1
|
||||
return
|
||||
}
|
||||
|
||||
node := atlas[location]
|
||||
|
||||
y := max(y, i32(node.y))
|
||||
if y + height > ctx.height {
|
||||
max_height = -1
|
||||
return
|
||||
}
|
||||
|
||||
space_left -= i32(node.width)
|
||||
location += 1
|
||||
}
|
||||
max_height = y
|
||||
return
|
||||
}
|
||||
|
||||
fstash_atlas_add_rect :: proc( ctx : ^FSTASH_Context, )
|
||||
{
|
||||
|
||||
}
|
@ -1,13 +1,5 @@
|
||||
package sectr
|
||||
|
||||
import "core:fmt"
|
||||
import "core:math"
|
||||
import "core:mem"
|
||||
import "core:path/filepath"
|
||||
import "core:os"
|
||||
|
||||
import rl "vendor:raylib"
|
||||
|
||||
Font_Largest_Px_Size :: 32
|
||||
|
||||
Font_Size_Interval :: 2
|
||||
@ -35,158 +27,30 @@ FontTag :: struct {
|
||||
point_size : f32
|
||||
}
|
||||
|
||||
FontGlyphsRender :: struct {
|
||||
size : i32,
|
||||
count : i32,
|
||||
padding : i32,
|
||||
texture : rl.Texture2D,
|
||||
recs : [^]rl.Rectangle, // Characters rectangles in texture
|
||||
glyphs : [^]rl.GlyphInfo, // Characters info data
|
||||
}
|
||||
|
||||
FontDef :: struct {
|
||||
path_file : string,
|
||||
|
||||
// TODO(Ed) : you may have to store font data in the future if we render on demand
|
||||
// data : []u8,
|
||||
|
||||
default_size : i32,
|
||||
size_table : [Font_Largest_Px_Size / Font_Size_Interval] FontGlyphsRender,
|
||||
placeholder : int,
|
||||
}
|
||||
|
||||
FontProviderData :: struct {
|
||||
// font_cache : HMapZPL(FontDef),
|
||||
font_cache : HMapChainedPtr(FontDef),
|
||||
}
|
||||
|
||||
font_provider_startup :: proc()
|
||||
{
|
||||
profile(#procedure)
|
||||
state := get_state()
|
||||
font_provider_data := & get_state().font_provider_data; using font_provider_data
|
||||
|
||||
font_cache_alloc_error : AllocatorError
|
||||
font_cache, font_cache_alloc_error = hmap_chained_init(FontDef, hmap_closest_prime(1 * Kilo), persistent_allocator(), dbg_name = "font_cache" )
|
||||
verify( font_cache_alloc_error == AllocatorError.None, "Failed to allocate font_cache" )
|
||||
|
||||
log("font_cache created")
|
||||
log("font_provider initialized")
|
||||
}
|
||||
|
||||
font_provider_shutdown :: proc()
|
||||
{
|
||||
font_provider_data := & get_state().font_provider_data; using font_provider_data
|
||||
|
||||
for & entry in font_cache.lookup
|
||||
{
|
||||
if entry == nil do continue
|
||||
|
||||
def := entry.value
|
||||
for & px_render in def.size_table {
|
||||
using px_render
|
||||
rl.UnloadFontData( glyphs, count )
|
||||
rl.UnloadTexture ( texture )
|
||||
rl.MemFree( recs )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
font_load :: proc( path_file : string,
|
||||
font_load :: proc(ath_file : string,
|
||||
default_size : f32 = Font_Load_Use_Default_Size,
|
||||
desired_id : string = Font_Load_Gen_ID
|
||||
) -> FontID
|
||||
{
|
||||
profile(#procedure)
|
||||
log( str_fmt("Loading font: %v", path_file))
|
||||
font_provider_data := & get_state().font_provider_data; using font_provider_data
|
||||
|
||||
font_data, read_succeded : = os.read_entire_file( path_file )
|
||||
verify( b32(read_succeded), str_fmt("Failed to read font file for: %v", path_file) )
|
||||
font_data_size := cast(i32) len(font_data)
|
||||
|
||||
desired_id := desired_id
|
||||
// Use file name as key
|
||||
if len(desired_id) == 0 {
|
||||
// NOTE(Ed): This should never be used except for laziness so I'll be throwing a warning everytime.
|
||||
log("desired_key not provided, using file name. Give it a proper name!", LogLevel.Warning)
|
||||
// desired_id = cast(FontID) file_name_from_path(path_file)
|
||||
desired_id = file_name_from_path(path_file)
|
||||
}
|
||||
|
||||
default_size := default_size
|
||||
if default_size == Font_Load_Use_Default_Size {
|
||||
default_size = Font_Default_Point_Size
|
||||
}
|
||||
|
||||
key := cast(u64) crc32( transmute([]byte) desired_id )
|
||||
def, set_error := hmap_chained_set(font_cache, key, FontDef{})
|
||||
verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" )
|
||||
|
||||
def.path_file = path_file
|
||||
def.default_size = i32(points_to_pixels(default_size))
|
||||
|
||||
// TODO(Ed): this is slow & eats quite a bit of memory early on. Setup a more on demand load for a specific size.
|
||||
// Also, we need to eventually switch to a SDF shader for rendering
|
||||
|
||||
// Render all sizes at once
|
||||
// Note(Ed) : We only generate textures for even multiples of the font.
|
||||
for font_size : i32 = Font_Size_Interval; font_size <= Font_Largest_Px_Size; font_size += Font_Size_Interval
|
||||
{
|
||||
profile("font size render")
|
||||
id := (font_size / Font_Size_Interval) + (font_size % Font_Size_Interval)
|
||||
|
||||
px_render := & def.size_table[id - 1]
|
||||
using px_render
|
||||
size = font_size
|
||||
count = 95 // This is the default codepoint count from raylib when loading a font.
|
||||
padding = Font_TTF_Default_Chars_Padding
|
||||
glyphs = rl.LoadFontData( raw_data(font_data), font_data_size,
|
||||
fontSize = size,
|
||||
codepoints = nil,
|
||||
codepointCount = count,
|
||||
type = rl.FontType.DEFAULT )
|
||||
verify( glyphs != nil, str_fmt("Failed to load glyphs for font: %v at desired size: %v", desired_id, size ) )
|
||||
|
||||
atlas := rl.GenImageFontAtlas( glyphs, & recs, count, size, padding, i32(Font_Atlas_Packing_Method.Raylib_Basic) )
|
||||
texture = rl.LoadTextureFromImage( atlas )
|
||||
|
||||
// glyphs_slice := slice_ptr( glyphs, count )
|
||||
// for glyph in glyphs_slice {
|
||||
// TODO(Ed) : See if above can properly reference
|
||||
|
||||
// NOTE(raylib): Update glyphs[i].image to use alpha, required to be used on image_draw_text()
|
||||
for glyph_id : i32 = 0; glyph_id < count; glyph_id += 1 {
|
||||
glyph := & glyphs[glyph_id]
|
||||
|
||||
rl.UnloadImage( glyph.image )
|
||||
glyph.image = rl.ImageFromImage( atlas, recs[glyph_id] )
|
||||
}
|
||||
rl.UnloadImage( atlas )
|
||||
}
|
||||
|
||||
return { key, desired_id }
|
||||
}
|
||||
|
||||
Font_Use_Default_Size :: f32(0.0)
|
||||
|
||||
to_rl_Font :: proc( id : FontID, size := Font_Use_Default_Size ) -> rl.Font
|
||||
{
|
||||
font_provider_data := & get_state().font_provider_data; using font_provider_data
|
||||
|
||||
even_size := math.round(size * (1.0/f32(Font_Size_Interval))) * f32(Font_Size_Interval)
|
||||
size := clamp( i32( even_size), 4, Font_Largest_Px_Size )
|
||||
def := hmap_chained_get( font_cache, id.key )
|
||||
size = size if size != i32(Font_Use_Default_Size) else def.default_size
|
||||
|
||||
id := (size / Font_Size_Interval) + (size % Font_Size_Interval)
|
||||
px_render := & def.size_table[ id - 1 ]
|
||||
|
||||
rl_font : rl.Font
|
||||
rl_font.baseSize = px_render.size
|
||||
rl_font.glyphCount = px_render.count
|
||||
rl_font.glyphPadding = px_render.padding
|
||||
rl_font.glyphs = px_render.glyphs
|
||||
rl_font.recs = px_render.recs
|
||||
rl_font.texture = px_render.texture
|
||||
return rl_font
|
||||
return {}
|
||||
}
|
||||
|
0
code/sectr/font/provider_fontstash.odin
Normal file
0
code/sectr/font/provider_fontstash.odin
Normal file
Reference in New Issue
Block a user