Files
raddebugger/src/font_cache/font_cache.c
T
2024-11-08 09:04:16 -08:00

1067 lines
33 KiB
C

// Copyright (c) 2024 Epic Games Tools
// Licensed under the MIT license (https://opensource.org/license/mit/)
////////////////////////////////
//~ rjf: Basic Functions
#if !defined(XXH_IMPLEMENTATION)
# define XXH_IMPLEMENTATION
# define XXH_STATIC_LINKING_ONLY
# include "linker/third_party_ext/xxHash/xxhash.h"
#endif
internal U128
fnt_hash_from_string(String8 string)
{
union
{
XXH128_hash_t xxhash;
U128 u128;
}
hash;
hash.xxhash = XXH3_128bits(string.str, string.size);
return hash.u128;
}
internal U64
fnt_little_hash_from_string(String8 string)
{
U64 result = XXH3_64bits(string.str, string.size);
return result;
}
internal Vec2S32
fnt_vertex_from_corner(Corner corner)
{
Vec2S32 result = {0};
switch(corner)
{
default: break;
case Corner_00:{result = v2s32(0, 0);}break;
case Corner_01:{result = v2s32(0, 1);}break;
case Corner_10:{result = v2s32(1, 0);}break;
case Corner_11:{result = v2s32(1, 1);}break;
}
return result;
}
////////////////////////////////
//~ rjf: Font Tags
internal FNT_Tag
fnt_tag_zero(void)
{
FNT_Tag result = {0};
return result;
}
internal B32
fnt_tag_match(FNT_Tag a, FNT_Tag b)
{
return a.u64[0] == b.u64[0] && a.u64[1] == b.u64[1];
}
internal FP_Handle
fnt_handle_from_tag(FNT_Tag tag)
{
ProfBeginFunction();
U64 slot_idx = tag.u64[1] % f_state->font_hash_table_size;
FNT_FontHashNode *existing_node = 0;
{
for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next)
{
if(MemoryMatchStruct(&tag, &n->tag))
{
existing_node = n;
break;
}
}
}
FP_Handle result = {0};
if(existing_node != 0)
{
result = existing_node->handle;
}
ProfEnd();
return result;
}
internal FP_Metrics
fnt_fp_metrics_from_tag(FNT_Tag tag)
{
ProfBeginFunction();
U64 slot_idx = tag.u64[1] % f_state->font_hash_table_size;
FNT_FontHashNode *existing_node = 0;
{
for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next)
{
if(MemoryMatchStruct(&tag, &n->tag))
{
existing_node = n;
break;
}
}
}
FP_Metrics result = {0};
if(existing_node != 0)
{
result = existing_node->metrics;
}
ProfEnd();
return result;
}
internal FNT_Tag
fnt_tag_from_path(String8 path)
{
ProfBeginFunction();
//- rjf: produce tag from hash of path
FNT_Tag result = {0};
{
U128 hash = fnt_hash_from_string(path);
MemoryCopy(&result, &hash, sizeof(result));
result.u64[1] |= bit64;
}
//- rjf: tag -> slot index
U64 slot_idx = result.u64[1] % f_state->font_hash_table_size;
//- rjf: slot * tag -> existing node
FNT_FontHashNode *existing_node = 0;
{
for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next)
{
if(MemoryMatchStruct(&result, &n->tag))
{
existing_node = n;
break;
}
}
}
//- rjf: allocate & push new node if we don't have an existing one
FNT_FontHashNode *new_node = 0;
if(existing_node == 0)
{
FNT_FontHashSlot *slot = &f_state->font_hash_table[slot_idx];
new_node = push_array(f_state->arena, FNT_FontHashNode, 1);
new_node->tag = result;
new_node->handle = fp_font_open(path);
new_node->metrics = fp_metrics_from_font(new_node->handle);
new_node->path = push_str8_copy(f_state->arena, path);
SLLQueuePush_N(slot->first, slot->last, new_node, hash_next);
}
//- rjf: return
ProfEnd();
return result;
}
internal FNT_Tag
fnt_tag_from_static_data_string(String8 *data_ptr)
{
ProfBeginFunction();
//- rjf: produce tag hash of ptr
FNT_Tag result = {0};
{
U128 hash = fnt_hash_from_string(str8((U8 *)&data_ptr, sizeof(String8 *)));
MemoryCopy(&result, &hash, sizeof(result));
result.u64[1] &= ~bit64;
}
//- rjf: tag -> slot index
U64 slot_idx = result.u64[1] % f_state->font_hash_table_size;
//- rjf: slot * tag -> existing node
FNT_FontHashNode *existing_node = 0;
{
for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next)
{
if(MemoryMatchStruct(&result, &n->tag))
{
existing_node = n;
break;
}
}
}
//- rjf: allocate & push new node if we don't have an existing one
FNT_FontHashNode *new_node = 0;
if(existing_node == 0)
{
FNT_FontHashSlot *slot = &f_state->font_hash_table[slot_idx];
new_node = push_array(f_state->arena, FNT_FontHashNode, 1);
new_node->tag = result;
new_node->handle = fp_font_open_from_static_data_string(data_ptr);
new_node->metrics = fp_metrics_from_font(new_node->handle);
new_node->path = str8_lit("");
SLLQueuePush_N(slot->first, slot->last, new_node, hash_next);
}
//- rjf: return
ProfEnd();
return result;
}
internal String8
fnt_path_from_tag(FNT_Tag tag)
{
//- rjf: tag -> slot index
U64 slot_idx = tag.u64[1] % f_state->font_hash_table_size;
//- rjf: slot * tag -> existing node
FNT_FontHashNode *existing_node = 0;
{
for(FNT_FontHashNode *n = f_state->font_hash_table[slot_idx].first; n != 0 ; n = n->hash_next)
{
if(MemoryMatchStruct(&tag, &n->tag))
{
existing_node = n;
break;
}
}
}
//- rjf: existing node -> path
String8 result = {0};
if(existing_node != 0)
{
result = existing_node->path;
}
return result;
}
////////////////////////////////
//~ rjf: Atlas
internal Rng2S16
fnt_atlas_region_alloc(Arena *arena, FNT_Atlas *atlas, Vec2S16 needed_size)
{
ProfBeginFunction();
//- rjf: find node with best-fit size
Vec2S16 region_p0 = {0};
Vec2S16 region_sz = {0};
Corner node_corner = Corner_Invalid;
FNT_AtlasRegionNode *node = 0;
{
Vec2S16 n_supported_size = atlas->root_dim;
for(FNT_AtlasRegionNode *n = atlas->root, *next = 0; n != 0; n = next, next = 0)
{
// rjf: we've traversed to a taken node.
if(n->flags & FNT_AtlasRegionNodeFlag_Taken)
{
break;
}
// rjf: calculate if this node can be allocated (all children are non-allocated)
B32 n_can_be_allocated = (n->num_allocated_descendants == 0);
// rjf: fill size
if(n_can_be_allocated)
{
region_sz = n_supported_size;
}
// rjf: calculate size of this node's children
Vec2S16 child_size = v2s16(n_supported_size.x/2, n_supported_size.y/2);
// rjf: find best next child
FNT_AtlasRegionNode *best_child = 0;
if(child_size.x >= needed_size.x && child_size.y >= needed_size.y)
{
for(Corner corner = (Corner)0; corner < Corner_COUNT; corner = (Corner)(corner+1))
{
if(n->children[corner] == 0)
{
n->children[corner] = push_array(arena, FNT_AtlasRegionNode, 1);
n->children[corner]->parent = n;
n->children[corner]->max_free_size[Corner_00] =
n->children[corner]->max_free_size[Corner_01] =
n->children[corner]->max_free_size[Corner_10] =
n->children[corner]->max_free_size[Corner_11] = v2s16(child_size.x/2, child_size.y/2);
}
if(n->max_free_size[corner].x >= needed_size.x &&
n->max_free_size[corner].y >= needed_size.y)
{
best_child = n->children[corner];
node_corner = corner;
Vec2S32 side_vertex = fnt_vertex_from_corner(corner);
region_p0.x += side_vertex.x*child_size.x;
region_p0.y += side_vertex.y*child_size.y;
break;
}
}
}
// rjf: resolve node to this node if it can be allocated and children
// don't fit, or keep going to the next best child
if(n_can_be_allocated && best_child == 0)
{
node = n;
}
else
{
next = best_child;
n_supported_size = child_size;
}
}
}
//- rjf: we're taking the subtree rooted by `node`. mark up all parents
if(node != 0 && node_corner != Corner_Invalid)
{
node->flags |= FNT_AtlasRegionNodeFlag_Taken;
if(node->parent != 0)
{
MemoryZeroStruct(&node->parent->max_free_size[node_corner]);
}
for(FNT_AtlasRegionNode *p = node->parent; p != 0; p = p->parent)
{
p->num_allocated_descendants += 1;
FNT_AtlasRegionNode *parent = p->parent;
if(parent != 0)
{
Corner p_corner = (p == parent->children[Corner_00] ? Corner_00 :
p == parent->children[Corner_01] ? Corner_01 :
p == parent->children[Corner_10] ? Corner_10 :
p == parent->children[Corner_11] ? Corner_11 :
Corner_Invalid);
if(p_corner == Corner_Invalid)
{
InvalidPath;
}
parent->max_free_size[p_corner].x = Max(Max(p->max_free_size[Corner_00].x,
p->max_free_size[Corner_01].x),
Max(p->max_free_size[Corner_10].x,
p->max_free_size[Corner_11].x));
parent->max_free_size[p_corner].y = Max(Max(p->max_free_size[Corner_00].y,
p->max_free_size[Corner_01].y),
Max(p->max_free_size[Corner_10].y,
p->max_free_size[Corner_11].y));
}
}
}
//- rjf: fill rectangular region & return
Rng2S16 result = {0};
result.p0 = region_p0;
result.p1 = add_2s16(region_p0, region_sz);
ProfEnd();
return result;
}
internal void
fnt_atlas_region_release(FNT_Atlas *atlas, Rng2S16 region)
{
ProfBeginFunction();
//- rjf: extract region size
Vec2S16 region_size = v2s16(region.x1 - region.x0, region.y1 - region.y0);
//- rjf: map region to associated node
Vec2S16 calc_region_size = {0};
FNT_AtlasRegionNode *node = 0;
Corner node_corner = Corner_Invalid;
{
Vec2S16 n_p0 = v2s16(0, 0);
Vec2S16 n_sz = atlas->root_dim;
for(FNT_AtlasRegionNode *n = atlas->root, *next = 0; n != 0; n = next)
{
// rjf: is the region within this node's boundaries? (either this node, or a descendant)
if(n_p0.x <= region.p0.x && region.p0.x < n_p0.x+n_sz.x &&
n_p0.y <= region.p0.y && region.p0.y < n_p0.y+n_sz.y)
{
// rjf: check the region against this node
if(region.p0.x == n_p0.x && region.p0.y == n_p0.y &&
region_size.x == n_sz.x && region_size.y == n_sz.y)
{
node = n;
calc_region_size = n_sz;
break;
}
// rjf: check the region against children & iterate
else
{
Vec2S16 r_midpoint = v2s16(region.p0.x + region_size.x/2,
region.p0.y + region_size.y/2);
Vec2S16 n_midpoint = v2s16(n_p0.x + n_sz.x/2,
n_p0.y + n_sz.y/2);
Corner next_corner = Corner_Invalid;
if(r_midpoint.x <= n_midpoint.x && r_midpoint.y <= n_midpoint.y)
{
next_corner = Corner_00;
}
else if(r_midpoint.x <= n_midpoint.x && n_midpoint.y <= r_midpoint.y)
{
next_corner = Corner_01;
}
else if(n_midpoint.x <= r_midpoint.x && r_midpoint.y <= n_midpoint.y)
{
next_corner = Corner_10;
}
else if(n_midpoint.x <= r_midpoint.x && n_midpoint.y <= r_midpoint.y)
{
next_corner = Corner_11;
}
next = n->children[next_corner];
node_corner = next_corner;
n_sz.x /= 2;
n_sz.y /= 2;
Vec2S32 side_vertex = fnt_vertex_from_corner(node_corner);
n_p0.x += side_vertex.x*n_sz.x;
n_p0.y += side_vertex.y*n_sz.y;
}
}
else
{
break;
}
}
}
//- rjf: free node
if(node != 0 && node_corner != Corner_Invalid)
{
node->flags &= ~FNT_AtlasRegionNodeFlag_Taken;
if(node->parent != 0)
{
node->parent->max_free_size[node_corner] = calc_region_size;
}
for(FNT_AtlasRegionNode *p = node->parent; p != 0; p = p->parent)
{
p->num_allocated_descendants -= 1;
FNT_AtlasRegionNode *parent = p->parent;
if(parent != 0)
{
Corner p_corner = (p == parent->children[Corner_00] ? Corner_00 :
p == parent->children[Corner_01] ? Corner_01 :
p == parent->children[Corner_10] ? Corner_10 :
p == parent->children[Corner_11] ? Corner_11 :
Corner_Invalid);
if(p_corner == Corner_Invalid)
{
InvalidPath;
}
parent->max_free_size[p_corner].x = Max(Max(p->max_free_size[Corner_00].x,
p->max_free_size[Corner_01].x),
Max(p->max_free_size[Corner_10].x,
p->max_free_size[Corner_11].x));
parent->max_free_size[p_corner].y = Max(Max(p->max_free_size[Corner_00].y,
p->max_free_size[Corner_01].y),
Max(p->max_free_size[Corner_10].y,
p->max_free_size[Corner_11].y));
}
}
}
ProfEnd();
}
////////////////////////////////
//~ rjf: Piece Type Functions
internal FNT_Piece *
fnt_piece_chunk_list_push_new(Arena *arena, FNT_PieceChunkList *list, U64 cap)
{
FNT_PieceChunkNode *node = list->last;
if(node == 0 || node->count >= node->cap)
{
node = push_array(arena, FNT_PieceChunkNode, 1);
node->v = push_array_no_zero(arena, FNT_Piece, cap);
node->cap = cap;
SLLQueuePush(list->first, list->last, node);
list->node_count += 1;
}
FNT_Piece *result = node->v + node->count;
node->count += 1;
list->total_piece_count += 1;
return result;
}
internal void
fnt_piece_chunk_list_push(Arena *arena, FNT_PieceChunkList *list, U64 cap, FNT_Piece *piece)
{
FNT_Piece *new_piece = fnt_piece_chunk_list_push_new(arena, list, cap);
MemoryCopyStruct(new_piece, piece);
}
internal FNT_PieceArray
fnt_piece_array_from_chunk_list(Arena *arena, FNT_PieceChunkList *list)
{
FNT_PieceArray array = {0};
array.count = list->total_piece_count;
array.v = push_array_no_zero(arena, FNT_Piece, array.count);
U64 write_idx = 0;
for(FNT_PieceChunkNode *node = list->first; node != 0; node = node->next)
{
MemoryCopy(array.v + write_idx, node->v, node->count * sizeof(FNT_Piece));
write_idx += node->count;
}
return array;
}
internal FNT_PieceArray
fnt_piece_array_copy(Arena *arena, FNT_PieceArray *src)
{
FNT_PieceArray dst = {0};
dst.count = src->count;
dst.v = push_array_no_zero(arena, FNT_Piece, dst.count);
MemoryCopy(dst.v, src->v, sizeof(FNT_Piece)*dst.count);
return dst;
}
////////////////////////////////
//~ rjf: Rasterization Cache
internal FNT_Hash2StyleRasterCacheNode *
fnt_hash2style_from_tag_size_flags(FNT_Tag tag, F32 size, FNT_RasterFlags flags)
{
//- rjf: tag * size -> style hash
U64 style_hash = {0};
{
F64 size_f64 = size;
U64 buffer[] =
{
tag.u64[0],
tag.u64[1],
*(U64 *)(&size_f64),
(U64)flags,
};
style_hash = fnt_little_hash_from_string(str8((U8 *)buffer, sizeof(buffer)));
}
//- rjf: style hash -> style node
FNT_Hash2StyleRasterCacheNode *hash2style_node = 0;
{
ProfBegin("style hash -> style node");
U64 slot_idx = style_hash%f_state->hash2style_slots_count;
FNT_Hash2StyleRasterCacheSlot *slot = &f_state->hash2style_slots[slot_idx];
for(FNT_Hash2StyleRasterCacheNode *n = slot->first;
n != 0;
n = n->hash_next)
{
if(n->style_hash == style_hash)
{
hash2style_node = n;
break;
}
}
if(Unlikely(hash2style_node == 0))
{
FNT_Metrics metrics = fnt_metrics_from_tag_size(tag, size);
hash2style_node = push_array(f_state->arena, FNT_Hash2StyleRasterCacheNode, 1);
DLLPushBack_NP(slot->first, slot->last, hash2style_node, hash_next, hash_prev);
hash2style_node->style_hash = style_hash;
hash2style_node->ascent = metrics.ascent;
hash2style_node->descent= metrics.descent;
hash2style_node->utf8_class1_direct_map = push_array_no_zero(f_state->arena, F_RasterCacheInfo, 256);
hash2style_node->hash2info_slots_count = 1024;
hash2style_node->hash2info_slots = push_array(f_state->arena, FNT_Hash2InfoRasterCacheSlot, hash2style_node->hash2info_slots_count);
}
ProfEnd();
}
return hash2style_node;
}
internal FNT_Run
fnt_push_run_from_string(Arena *arena, FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, FNT_RasterFlags flags, String8 string)
{
ProfBeginFunction();
//- rjf: map tag/size to style node
FNT_Hash2StyleRasterCacheNode *hash2style_node = fnt_hash2style_from_tag_size_flags(tag, size, flags);
//- rjf: decode string & produce run pieces
FNT_PieceChunkList piece_chunks = {0};
Vec2F32 dim = {0};
B32 font_handle_mapped_on_miss = 0;
FP_Handle font_handle = {0};
U64 piece_substring_start_idx = 0;
U64 piece_substring_end_idx = 0;
for(U64 idx = 0; idx <= string.size;)
{
//- rjf: decode next codepoint & get piece substring, or continuation rule
U8 byte = (idx < string.size ? string.str[idx] : 0);
B32 need_another_codepoint = 0;
if(byte == 0)
{
idx += 1;
}
else switch(utf8_class[byte>>3])
{
case 1:
{
idx += 1;
piece_substring_end_idx += 1;
need_another_codepoint = 0;
}break;
default:
{
UnicodeDecode decode = utf8_decode(string.str+idx, string.size-idx);
idx += decode.inc;
piece_substring_end_idx += decode.inc;
need_another_codepoint = 0;
}break;
}
//- rjf: need another codepoint, or have no substring? -> continue
if(need_another_codepoint || piece_substring_end_idx == piece_substring_start_idx)
{
continue;
}
//- rjf: do not need another codepoint? -> grab substring, bump piece start idx
String8 piece_substring = str8_substr(string, r1u64(piece_substring_start_idx, piece_substring_end_idx));
piece_substring_start_idx = idx;
piece_substring_end_idx = idx;
//- rjf: determine if this piece is a tab - if so, use space info to draw
B32 is_tab = (piece_substring.size == 1 && piece_substring.str[0] == '\t');
if(is_tab)
{
piece_substring = str8_lit(" ");
}
//- rjf: piece substring -> raster cache info
F_RasterCacheInfo *info = 0;
U64 piece_hash = 0;
{
// rjf: fast path for utf8 class 1 -> direct map
if(piece_substring.size == 1 && hash2style_node->utf8_class1_direct_map_mask[piece_substring.str[0]/64] & (1ull<<(piece_substring.str[0]%64)))
{
info = &hash2style_node->utf8_class1_direct_map[piece_substring.str[0]];
}
// rjf: more general, slower path for other glyphs
if(piece_substring.size > 1)
{
piece_hash = fnt_little_hash_from_string(piece_substring);
U64 slot_idx = piece_hash%hash2style_node->hash2info_slots_count;
FNT_Hash2InfoRasterCacheSlot *slot = &hash2style_node->hash2info_slots[slot_idx];
for(F_Hash2InfoRasterCacheNode *node = slot->first; node != 0; node = node->hash_next)
{
if(node->hash == piece_hash)
{
info = &node->info;
break;
}
}
}
}
//- rjf: no info found -> miss... fill this hash in the cache
if(info == 0)
{
ProfBegin("no info found -> miss... fill this hash in the cache");
Temp scratch = scratch_begin(&arena, 1);
// rjf: grab font handle for this tag if we don't have one already
if(font_handle_mapped_on_miss == 0)
{
font_handle_mapped_on_miss = 1;
// rjf: tag -> font slot index
U64 font_slot_idx = tag.u64[1] % f_state->font_hash_table_size;
// rjf: tag * slot -> existing node
FNT_FontHashNode *existing_node = 0;
{
for(FNT_FontHashNode *n = f_state->font_hash_table[font_slot_idx].first; n != 0 ; n = n->hash_next)
{
if(MemoryMatchStruct(&n->tag, &tag))
{
existing_node = n;
break;
}
}
}
// rjf: existing node -> font handle
if(existing_node != 0)
{
font_handle = existing_node->handle;
}
}
// rjf: call into font provider to rasterize this substring
FP_RasterResult raster = {0};
if(size > 0)
{
FP_RasterFlags fp_flags = 0;
if(flags & FNT_RasterFlag_Smooth) { fp_flags |= FP_RasterFlag_Smooth; }
if(flags & FNT_RasterFlag_Hinted) { fp_flags |= FP_RasterFlag_Hinted; }
raster = fp_raster(scratch.arena, font_handle, floor_f32(size), flags, piece_substring);
}
// rjf: allocate portion of an atlas to upload the rasterization
S16 chosen_atlas_num = 0;
FNT_Atlas *chosen_atlas = 0;
Rng2S16 chosen_atlas_region = {0};
if(raster.atlas_dim.x != 0 && raster.atlas_dim.y != 0)
{
U64 num_atlases = 0;
for(FNT_Atlas *atlas = f_state->first_atlas;; atlas = atlas->next, num_atlases += 1)
{
// rjf: create atlas if needed
if(atlas == 0 && num_atlases < 64)
{
atlas = push_array(f_state->arena, FNT_Atlas, 1);
DLLPushBack(f_state->first_atlas, f_state->last_atlas, atlas);
atlas->root_dim = v2s16(1024, 1024);
atlas->root = push_array(f_state->arena, FNT_AtlasRegionNode, 1);
atlas->root->max_free_size[Corner_00] =
atlas->root->max_free_size[Corner_01] =
atlas->root->max_free_size[Corner_10] =
atlas->root->max_free_size[Corner_11] = v2s16(atlas->root_dim.x/2, atlas->root_dim.y/2);
atlas->texture = r_tex2d_alloc(R_ResourceKind_Dynamic, v2s32((S32)atlas->root_dim.x, (S32)atlas->root_dim.y), R_Tex2DFormat_RGBA8, 0);
}
// rjf: allocate from atlas
if(atlas != 0)
{
Vec2S16 needed_dimensions = v2s16(raster.atlas_dim.x + 2, raster.atlas_dim.y + 2);
chosen_atlas_region = fnt_atlas_region_alloc(f_state->arena, atlas, needed_dimensions);
if(chosen_atlas_region.x1 != chosen_atlas_region.x0)
{
chosen_atlas = atlas;
chosen_atlas_num = (S32)num_atlases;
break;
}
}
else
{
break;
}
}
}
// rjf: upload rasterization to allocated region of atlas texture memory
if(chosen_atlas != 0)
{
Rng2S32 subregion =
{
chosen_atlas_region.x0,
chosen_atlas_region.y0,
chosen_atlas_region.x0 + raster.atlas_dim.x,
chosen_atlas_region.y0 + raster.atlas_dim.y
};
r_fill_tex2d_region(chosen_atlas->texture, subregion, raster.atlas);
}
// rjf: allocate & fill & push node
{
if(piece_substring.size == 1)
{
info = &hash2style_node->utf8_class1_direct_map[piece_substring.str[0]];
hash2style_node->utf8_class1_direct_map_mask[piece_substring.str[0]/64] |= (1ull<<(piece_substring.str[0]%64));
}
else
{
U64 slot_idx = piece_hash%hash2style_node->hash2info_slots_count;
FNT_Hash2InfoRasterCacheSlot *slot = &hash2style_node->hash2info_slots[slot_idx];
F_Hash2InfoRasterCacheNode *node = push_array_no_zero(f_state->arena, F_Hash2InfoRasterCacheNode, 1);
DLLPushBack_NP(slot->first, slot->last, node, hash_next, hash_prev);
node->hash = piece_hash;
info = &node->info;
}
if(info != 0)
{
info->subrect = chosen_atlas_region;
info->atlas_num = chosen_atlas_num;
info->raster_dim = raster.atlas_dim;
info->advance = raster.advance;
}
}
scratch_end(scratch);
ProfEnd();
}
//- rjf: push piece for this raster portion
if(info != 0)
{
// rjf: find atlas
FNT_Atlas *atlas = 0;
{
if(info->subrect.x1 != 0 && info->subrect.y1 != 0)
{
S32 num = 0;
for(FNT_Atlas *a = f_state->first_atlas; a != 0; a = a->next, num += 1)
{
if(info->atlas_num == num)
{
atlas = a;
break;
}
}
}
}
// rjf: on tabs -> expand advance
F32 advance = info->advance;
if(is_tab)
{
advance = floor_f32(tab_size_px) - mod_f32(floor_f32(base_align_px), floor_f32(tab_size_px));
}
// rjf: push piece
{
FNT_Piece *piece = fnt_piece_chunk_list_push_new(arena, &piece_chunks, string.size);
{
piece->texture = atlas ? atlas->texture : r_handle_zero();
piece->subrect = r2s16p(info->subrect.x0,
info->subrect.y0,
info->subrect.x0 + info->raster_dim.x,
info->subrect.y0 + info->raster_dim.y);
piece->advance = advance;
piece->decode_size = piece_substring.size;
piece->offset = v2s16(0, -(hash2style_node->ascent + hash2style_node->descent));
}
base_align_px += advance;
dim.x += piece->advance;
dim.y = Max(dim.y, info->raster_dim.y);
}
}
}
//- rjf: tighten & return
FNT_Run run = {0};
{
if(piece_chunks.node_count == 1)
{
run.pieces.v = piece_chunks.first->v;
run.pieces.count = piece_chunks.first->count;
}
else
{
run.pieces = fnt_piece_array_from_chunk_list(arena, &piece_chunks);
}
run.dim = dim;
run.ascent = hash2style_node->ascent;
run.descent = hash2style_node->descent;
}
ProfEnd();
return run;
}
internal String8List
fnt_wrapped_string_lines_from_font_size_string_max(Arena *arena, FNT_Tag font, F32 size, F32 base_align_px, F32 tab_size_px, String8 string, F32 max)
{
String8List list = {0};
{
Temp scratch = scratch_begin(&arena, 1);
FNT_Run run = fnt_push_run_from_string(scratch.arena, font, size, base_align_px, tab_size_px, 0, string);
F32 off_px = 0;
U64 off_bytes = 0;
U64 line_start_off_bytes = 0;
U64 line_end_off_bytes = 0;
B32 seeking_word_end = 0;
F32 word_start_off_px = 0;
FNT_Piece *last_word_start_piece = 0;
U64 last_word_start_off_bytes = 0;
FNT_Piece *pieces_first = run.pieces.v;
FNT_Piece *pieces_opl = run.pieces.v + run.pieces.count;
for(FNT_Piece *piece = pieces_first, *next = 0; piece != 0 && piece <= pieces_opl; piece = next)
{
if(piece != 0) {next = piece+1;}
// rjf: gather info
U8 byte = off_bytes < string.size ? string.str[off_bytes] : 0;
F32 advance = (piece != 0) ? piece->advance : 0;
U64 decode_size = (piece != 0) ? piece->decode_size : 0;
// rjf: find start/end of words
B32 is_first_byte_of_word = 0;
B32 is_first_space_after_word = 0;
if(!seeking_word_end && !char_is_space(byte))
{
seeking_word_end = 1;
is_first_byte_of_word = 1;
last_word_start_off_bytes = off_bytes;
last_word_start_piece = piece;
word_start_off_px = off_px;
}
else if(seeking_word_end && char_is_space(byte))
{
seeking_word_end = 0;
is_first_space_after_word = 1;
}
else if(seeking_word_end && byte == 0)
{
is_first_space_after_word = 1;
}
// rjf: determine properties of this advance
B32 is_illegal = (off_px >= max);
B32 is_next_illegal = (off_px + advance >= max);
B32 is_end = (byte == 0);
// rjf: legal word end -> extend line
if(is_first_space_after_word && !is_illegal)
{
line_end_off_bytes = off_bytes;
}
// rjf: illegal mid-word split -> wrap mid-word
if(is_next_illegal && word_start_off_px == 0)
{
String8 line = str8(string.str + line_start_off_bytes, off_bytes - line_start_off_bytes);
line = str8_skip_chop_whitespace(line);
if(line.size != 0)
{
str8_list_push(arena, &list, line);
}
off_px = advance;
line_start_off_bytes = off_bytes;
line_end_off_bytes = off_bytes;
word_start_off_px = 0;
last_word_start_piece = piece;
last_word_start_off_bytes = off_bytes;
off_bytes += decode_size;
}
// rjf: illegal word end -> wrap line
else if(is_first_space_after_word && (is_illegal || is_end))
{
String8 line = str8(string.str + line_start_off_bytes, line_end_off_bytes - line_start_off_bytes);
line = str8_skip_chop_whitespace(line);
if(line.size != 0)
{
str8_list_push(arena, &list, line);
}
line_start_off_bytes = line_end_off_bytes;
if(is_illegal)
{
off_px = 0;
word_start_off_px = 0;
off_bytes = last_word_start_off_bytes;
next = last_word_start_piece;
}
}
// rjf: advance offsets otherwise
else
{
off_px += advance;
off_bytes += decode_size;
}
// rjf: 0 piece and 0 next -> done
if(piece == 0 && next == 0)
{
break;
}
}
scratch_end(scratch);
}
return list;
}
internal Vec2F32
fnt_dim_from_tag_size_string(FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, String8 string)
{
ProfBeginFunction();
Temp scratch = scratch_begin(0, 0);
Vec2F32 result = {0};
FNT_Run run = fnt_push_run_from_string(scratch.arena, tag, size, base_align_px, tab_size_px, 0, string);
result = run.dim;
scratch_end(scratch);
ProfEnd();
return result;
}
internal Vec2F32
fnt_dim_from_tag_size_string_list(FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, String8List list)
{
ProfBeginFunction();
Vec2F32 sum = {0};
for(String8Node *n = list.first; n != 0; n = n->next)
{
Vec2F32 str_dim = fnt_dim_from_tag_size_string(tag, size, base_align_px, tab_size_px, n->string);
sum.x += str_dim.x;
sum.y = Max(sum.y, str_dim.y);
}
ProfEnd();
return sum;
}
internal F32
fnt_column_size_from_tag_size(FNT_Tag tag, F32 size)
{
F32 result = fnt_dim_from_tag_size_string(tag, size, 0, 0, str8_lit("H")).x;
return result;
}
internal U64
fnt_char_pos_from_tag_size_string_p(FNT_Tag tag, F32 size, F32 base_align_px, F32 tab_size_px, String8 string, F32 p)
{
Temp scratch = scratch_begin(0, 0);
U64 best_offset_bytes = 0;
F32 best_offset_px = inf32();
U64 offset_bytes = 0;
F32 offset_px = 0.f;
FNT_Run run = fnt_push_run_from_string(scratch.arena, tag, size, base_align_px, tab_size_px, 0, string);
for(U64 idx = 0; idx <= run.pieces.count; idx += 1)
{
F32 this_piece_offset_px = abs_f32(offset_px - p);
if(this_piece_offset_px < best_offset_px)
{
best_offset_bytes = offset_bytes;
best_offset_px = this_piece_offset_px;
}
if(idx < run.pieces.count)
{
FNT_Piece *piece = &run.pieces.v[idx];
offset_px += piece->advance;
offset_bytes += piece->decode_size;
}
}
scratch_end(scratch);
return best_offset_bytes;
}
////////////////////////////////
//~ rjf: Metrics
internal FNT_Metrics
fnt_metrics_from_tag_size(FNT_Tag tag, F32 size)
{
ProfBeginFunction();
FP_Metrics metrics = fnt_fp_metrics_from_tag(tag);
FNT_Metrics result = {0};
{
result.ascent = floor_f32(size) * metrics.ascent / metrics.design_units_per_em;
result.descent = floor_f32(size) * metrics.descent / metrics.design_units_per_em;
result.line_gap = floor_f32(size) * metrics.line_gap / metrics.design_units_per_em;
result.capital_height = floor_f32(size) * metrics.capital_height / metrics.design_units_per_em;
}
ProfEnd();
return result;
}
internal F32
fnt_line_height_from_metrics(FNT_Metrics *metrics)
{
return metrics->ascent + metrics->descent + metrics->line_gap;
}
////////////////////////////////
//~ rjf: Main Calls
internal void
fnt_init(void)
{
Arena *arena = arena_alloc();
f_state = push_array(arena, FNT_State, 1);
f_state->arena = arena;
f_state->font_hash_table_size = 64;
f_state->font_hash_table = push_array(arena, FNT_FontHashSlot, f_state->font_hash_table_size);
f_state->hash2style_slots_count = 1024;
f_state->hash2style_slots = push_array(arena, FNT_Hash2StyleRasterCacheSlot, f_state->hash2style_slots_count);
}