2023-09-26 19:40:16 +02:00
|
|
|
#include "text_draw.h"
|
|
|
|
|
#include "../lib/color.h"
|
|
|
|
|
#include "../lib/math.h"
|
|
|
|
|
#include "../lib/geometry.h"
|
|
|
|
|
#include "../lib/text.h"
|
|
|
|
|
#include "../lib/ds.h"
|
|
|
|
|
#include "../platform.h"
|
|
|
|
|
#include "stb_truetype.h"
|
|
|
|
|
#include "../debug/logger.h"
|
|
|
|
|
#include "../assets.h"
|
|
|
|
|
#include "../enginestate.h"
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct gui_glyph_info
|
|
|
|
|
{
|
|
|
|
|
utf8_codepoint codepoint;
|
|
|
|
|
Rect box; // .position = offset from position for alignment, .size = size of the bitmap to draw
|
|
|
|
|
v2u position; // Position of the top left border in the texture
|
|
|
|
|
s32 advance;
|
|
|
|
|
u32 next; // Anything >= container_capacity is to be considered NULL
|
|
|
|
|
u32 previous;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Glyph caching:
|
|
|
|
|
* We store a small number of sizes and a fairly large number of characters for each size.
|
|
|
|
|
* Each slot will have the same width and height, so that we can easily replace it without
|
|
|
|
|
* re-packing everything. This will also make the code simpler.
|
|
|
|
|
* We use 0xFFFFFFFF as a placeholder for empty slots.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
struct gui_glyph_codepoint_map
|
|
|
|
|
{
|
|
|
|
|
utf8_codepoint codepoint;
|
|
|
|
|
u32 index;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct gui_glyph_texture
|
|
|
|
|
{
|
|
|
|
|
f32 font_size;
|
|
|
|
|
|
|
|
|
|
struct gui_glyph_codepoint_map *sorted_indices;
|
|
|
|
|
gui_glyph_info *info;
|
|
|
|
|
u32 oldest;
|
|
|
|
|
u32 newest;
|
|
|
|
|
u32 capacity;
|
|
|
|
|
|
|
|
|
|
v2s glyph_max_size;
|
|
|
|
|
|
|
|
|
|
r_texture *texture;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct gui_glyph_cache
|
|
|
|
|
{
|
|
|
|
|
gui_glyph_texture *glyphs;
|
|
|
|
|
u32 capacity;
|
|
|
|
|
u32 oldest;
|
|
|
|
|
|
|
|
|
|
u32 max_glyphs_per_texture;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void gui_glyph_cache_init();
|
|
|
|
|
static void gui_glyph_cache_deinit();
|
|
|
|
|
static gui_glyph_texture gui_glyph_texture_create(f32 font_size, u32 capacity);
|
|
|
|
|
static void gui_glyph_texture_destroy(gui_glyph_texture *glyphs);
|
|
|
|
|
static gui_glyph_texture *gui_glyph_cache_texture_for_codepoints(f32 font_size, const utf8_codepoint *codepoints, u64 count);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Globals
|
|
|
|
|
static u8 *Font_File;
|
|
|
|
|
static stbtt_fontinfo Font_Info;
|
|
|
|
|
static gui_glyph_cache Glyph_Cache;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool gui_text_draw_init()
|
|
|
|
|
{
|
|
|
|
|
// @Feature: user specified font
|
|
|
|
|
// @Cleanup: one-line file read
|
|
|
|
|
p_file f;
|
|
|
|
|
p_file_init(&f, "assets/fonts/DejaVuSerif-Bold.ttf");
|
|
|
|
|
Buffer buf;
|
|
|
|
|
buf.size = p_file_size(&f);
|
|
|
|
|
buf.data = (u8*)p_alloc(buf.size + 1);
|
|
|
|
|
buf.data[buf.size] = '\0';
|
|
|
|
|
p_file_read(&f, &buf, buf.size);
|
|
|
|
|
p_file_deinit(&f);
|
|
|
|
|
Font_File = buf.data;
|
|
|
|
|
|
|
|
|
|
if(!stbtt_InitFont(&Font_Info, Font_File, 0))
|
|
|
|
|
{
|
|
|
|
|
LOG(LOG_ERROR, "Cannot load font.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gui_glyph_cache_init();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui_text_draw_deinit()
|
|
|
|
|
{
|
|
|
|
|
gui_glyph_cache_deinit();
|
|
|
|
|
p_free(Font_File);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
v2 gui_text_draw_size(const char *text, f32 font_size, v2 *cursor_position)
|
|
|
|
|
{
|
|
|
|
|
// UTF8 conversion
|
|
|
|
|
u64 text_length = utf8_codepoint_count(text);
|
|
|
|
|
utf8_codepoint *codepoints = (utf8_codepoint*) p_alloc(text_length * sizeof(utf8_codepoint));
|
|
|
|
|
{
|
|
|
|
|
u64 bytes_read = 0;
|
|
|
|
|
text_length = utf8_from_string(text, &bytes_read, codepoints, text_length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute size
|
|
|
|
|
v2 result = gui_utf8_text_draw_size(codepoints, text_length, font_size, cursor_position);
|
|
|
|
|
|
|
|
|
|
p_free(codepoints);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui_text_draw(Rect r, const char *text, f32 font_size, v4 color)
|
|
|
|
|
{
|
|
|
|
|
// UTF8 conversion
|
|
|
|
|
u64 text_length = utf8_codepoint_count(text);
|
|
|
|
|
utf8_codepoint *codepoints = (utf8_codepoint*) p_alloc(text_length * sizeof(utf8_codepoint));
|
|
|
|
|
{
|
|
|
|
|
u64 bytes_read = 0;
|
|
|
|
|
text_length = utf8_from_string(text, &bytes_read, codepoints, text_length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Draw
|
|
|
|
|
gui_utf8_text_draw(r, codepoints, text_length, font_size, color);
|
|
|
|
|
|
|
|
|
|
p_free(codepoints);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
v2 gui_utf8_text_draw_size(const utf8_codepoint *text, u64 length, f32 font_size, v2 *cursor_position)
|
|
|
|
|
{
|
|
|
|
|
f32 font_scale;
|
|
|
|
|
s32 font_ascent;
|
|
|
|
|
s32 font_descent;
|
|
|
|
|
s32 font_line_gap;
|
|
|
|
|
s32 font_baseline;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
|
|
|
|
|
stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
|
|
|
|
|
font_baseline = font_ascent;
|
|
|
|
|
|
|
|
|
|
font_ascent *= font_scale;
|
|
|
|
|
font_descent *= font_scale;
|
|
|
|
|
font_line_gap *= font_scale;
|
|
|
|
|
font_baseline *= font_scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute size
|
|
|
|
|
v2 size = {0, (f32)(font_ascent - font_descent)};
|
|
|
|
|
{
|
|
|
|
|
v2 cursor = {0, (f32)(font_ascent - font_descent)};
|
|
|
|
|
for(u64 i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
s32 advance, lsb;
|
|
|
|
|
stbtt_GetCodepointHMetrics(&Font_Info, text[i], &advance, &lsb);
|
|
|
|
|
|
|
|
|
|
// Special characters
|
|
|
|
|
if(text[i] == 10) // '\n'
|
|
|
|
|
{
|
|
|
|
|
advance = 0;
|
|
|
|
|
cursor.x = 0;
|
|
|
|
|
cursor.y += font_ascent - font_descent + font_line_gap;
|
|
|
|
|
size.y = cursor.y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normal characters
|
|
|
|
|
cursor.x += floor(advance * font_scale); // Remember to consider kerning
|
|
|
|
|
size.x = maximum(size.x, cursor.x);
|
|
|
|
|
}
|
|
|
|
|
if(cursor_position)
|
|
|
|
|
*cursor_position = cursor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void gui_utf8_text_draw(Rect r, const utf8_codepoint *text, u64 length, f32 font_size, v4 color)
|
|
|
|
|
{
|
|
|
|
|
f32 font_scale;
|
|
|
|
|
s32 font_ascent;
|
|
|
|
|
s32 font_descent;
|
|
|
|
|
s32 font_line_gap;
|
|
|
|
|
s32 font_baseline;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
|
|
|
|
|
stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
|
|
|
|
|
font_baseline = font_ascent;
|
|
|
|
|
|
|
|
|
|
font_ascent *= font_scale;
|
|
|
|
|
font_descent *= font_scale;
|
|
|
|
|
font_line_gap *= font_scale;
|
|
|
|
|
font_baseline *= font_scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute glyphs
|
|
|
|
|
gui_glyph_texture *glyphs = gui_glyph_cache_texture_for_codepoints(font_size, text, length);
|
|
|
|
|
|
|
|
|
|
// Map text to quads
|
|
|
|
|
v2 *vertices;
|
|
|
|
|
v2 *uvs;
|
|
|
|
|
u64 draw_count = 0;
|
|
|
|
|
{
|
|
|
|
|
vertices = (v2*) p_alloc(6 * length * sizeof(v2)); // 2 triangles, 3 vertices each = 6 vertices
|
|
|
|
|
uvs = (v2*) p_alloc(6 * length * sizeof(v2));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
v2 position = r.position;
|
|
|
|
|
position.y += font_baseline;
|
|
|
|
|
for(u64 i = 0; i < length; i++)
|
|
|
|
|
{
|
|
|
|
|
gui_glyph_codepoint_map *found = (gui_glyph_codepoint_map*) bsearch(&text[i], glyphs->sorted_indices, glyphs->capacity, sizeof(gui_glyph_codepoint_map), u32_cmp);
|
|
|
|
|
if(found == NULL)
|
|
|
|
|
{
|
|
|
|
|
LOG(LOG_ERROR, "Cannot find codepoint 0x%X in glyph list.", text[i]);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u64 glyph_i = glyphs->sorted_indices[found - glyphs->sorted_indices].index;
|
|
|
|
|
gui_glyph_info *info = &glyphs->info[glyph_i];
|
|
|
|
|
|
|
|
|
|
// Special characters
|
|
|
|
|
if(text[i] == 10) // '\n'
|
|
|
|
|
{
|
|
|
|
|
position.x = r.position.x;
|
|
|
|
|
position.y += font_ascent - font_descent + font_line_gap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normal characters
|
|
|
|
|
// Map character to its vertices
|
|
|
|
|
{
|
|
|
|
|
Rect r;
|
|
|
|
|
Rect r_uv;
|
|
|
|
|
r.position = position + info->box.position;
|
|
|
|
|
r.position.x = floor(r.position.x + 0.5);
|
|
|
|
|
r.position.y = floor(r.position.y + 0.5);
|
|
|
|
|
r.size = info->box.size;
|
|
|
|
|
|
|
|
|
|
r_uv.position = V2(info->position) / V2(glyphs->texture->size);
|
|
|
|
|
r_uv.size = info->box.size / V2(glyphs->texture->size);
|
|
|
|
|
|
|
|
|
|
v2 a = r.position + v2{r.w, 0 };
|
|
|
|
|
v2 b = r.position + v2{0 , 0 };
|
|
|
|
|
v2 c = r.position + v2{0 , r.h};
|
|
|
|
|
v2 d = r.position + v2{r.w, r.h};
|
|
|
|
|
v2 a_uv = r_uv.position + v2{r_uv.w, 0 };
|
|
|
|
|
v2 b_uv = r_uv.position + v2{0 , 0 };
|
|
|
|
|
v2 c_uv = r_uv.position + v2{0 , r_uv.h};
|
|
|
|
|
v2 d_uv = r_uv.position + v2{r_uv.w, r_uv.h};
|
|
|
|
|
|
|
|
|
|
vertices[6*draw_count + 0] = a;
|
|
|
|
|
vertices[6*draw_count + 1] = b;
|
|
|
|
|
vertices[6*draw_count + 2] = c;
|
|
|
|
|
vertices[6*draw_count + 3] = a;
|
|
|
|
|
vertices[6*draw_count + 4] = c;
|
|
|
|
|
vertices[6*draw_count + 5] = d;
|
|
|
|
|
uvs[6*draw_count + 0] = a_uv;
|
|
|
|
|
uvs[6*draw_count + 1] = b_uv;
|
|
|
|
|
uvs[6*draw_count + 2] = c_uv;
|
|
|
|
|
uvs[6*draw_count + 3] = a_uv;
|
|
|
|
|
uvs[6*draw_count + 4] = c_uv;
|
|
|
|
|
uvs[6*draw_count + 5] = d_uv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
position.x += info->advance; // Remember to consider kerning
|
|
|
|
|
draw_count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render quads
|
|
|
|
|
r_2d_immediate_mesh(6*draw_count, vertices, color, uvs, glyphs->texture);
|
|
|
|
|
|
|
|
|
|
p_free(vertices);
|
|
|
|
|
p_free(uvs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void gui_glyph_cache_init()
|
|
|
|
|
{
|
|
|
|
|
Glyph_Cache.capacity = 4;
|
|
|
|
|
Glyph_Cache.oldest = 0;
|
|
|
|
|
Glyph_Cache.glyphs = (gui_glyph_texture*)p_alloc(sizeof(gui_glyph_texture) * Glyph_Cache.capacity);
|
|
|
|
|
memset(Glyph_Cache.glyphs, 0, sizeof(gui_glyph_texture) * Glyph_Cache.capacity);
|
|
|
|
|
Glyph_Cache.max_glyphs_per_texture = 256; // @Correctness: test with small values, to trigger the glyph replacement code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void gui_glyph_cache_deinit()
|
|
|
|
|
{
|
|
|
|
|
for(u32 i = 0; i < Glyph_Cache.capacity; i++)
|
|
|
|
|
gui_glyph_texture_destroy(&Glyph_Cache.glyphs[i]);
|
|
|
|
|
p_free(Glyph_Cache.glyphs);
|
|
|
|
|
Glyph_Cache.glyphs = NULL;
|
|
|
|
|
Glyph_Cache.capacity = 0;
|
|
|
|
|
Glyph_Cache.oldest = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gui_glyph_texture gui_glyph_texture_create(f32 font_size, u32 capacity)
|
|
|
|
|
{
|
|
|
|
|
// Init container for glyphs and info
|
|
|
|
|
gui_glyph_texture glyphs;
|
|
|
|
|
glyphs.font_size = font_size;
|
|
|
|
|
glyphs.sorted_indices = (gui_glyph_codepoint_map*) p_alloc(sizeof(gui_glyph_codepoint_map) * capacity);
|
|
|
|
|
glyphs.info = (gui_glyph_info*) p_alloc(sizeof(gui_glyph_info) * capacity);
|
|
|
|
|
glyphs.oldest = 0;
|
|
|
|
|
glyphs.newest = capacity - 1;
|
|
|
|
|
glyphs.capacity = capacity;
|
|
|
|
|
|
|
|
|
|
// Estimate max glyph size.
|
|
|
|
|
// @Correctness: Text draw will fail if a bigger glyph is used.
|
|
|
|
|
f32 font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
|
|
|
|
|
utf8_codepoint cp[] = {' ', 'M', 'j', '{', '=', 'w', 'W'};
|
|
|
|
|
glyphs.glyph_max_size = v2s{0, 0};
|
|
|
|
|
for(s32 i = 0; i < sizeof(cp)/sizeof(utf8_codepoint); i++)
|
|
|
|
|
{
|
|
|
|
|
v2s top_left, bottom_right;
|
|
|
|
|
stbtt_GetCodepointBitmapBox(&Font_Info, cp[i], font_scale, font_scale, &top_left.x, &top_left.y, &bottom_right.x, &bottom_right.y);
|
|
|
|
|
v2s size = bottom_right - top_left;
|
|
|
|
|
|
|
|
|
|
glyphs.glyph_max_size.x = maximum(glyphs.glyph_max_size.x, size.x);
|
|
|
|
|
glyphs.glyph_max_size.y = maximum(glyphs.glyph_max_size.y, size.y);
|
|
|
|
|
}
|
|
|
|
|
LOG(LOG_DEBUG, "Font size %f not in cache. Slot size (%d %d)", font_size, glyphs.glyph_max_size.x, glyphs.glyph_max_size.y);
|
|
|
|
|
|
|
|
|
|
// Precompile some info data
|
2023-09-30 02:39:12 +02:00
|
|
|
v2u texture_slots;
|
|
|
|
|
texture_slots.x = r_render_state.max_texture_size / glyphs.glyph_max_size.x;
|
|
|
|
|
texture_slots.y = (glyphs.capacity + texture_slots.x) / texture_slots.x;
|
2023-09-26 19:40:16 +02:00
|
|
|
for(u32 i = 0; i < glyphs.capacity; i++)
|
|
|
|
|
{
|
2023-09-30 02:39:12 +02:00
|
|
|
v2u position = {
|
|
|
|
|
i % texture_slots.x * glyphs.glyph_max_size.x,
|
|
|
|
|
i / texture_slots.x * glyphs.glyph_max_size.y
|
|
|
|
|
};
|
2023-09-26 19:40:16 +02:00
|
|
|
glyphs.info[i] = gui_glyph_info{
|
|
|
|
|
.codepoint = 0xFFFFFFFF,
|
|
|
|
|
.box = {0,0,0,0},
|
2023-09-30 02:39:12 +02:00
|
|
|
.position = position,
|
2023-09-26 19:40:16 +02:00
|
|
|
.advance = 0,
|
|
|
|
|
.next = i + 1, // Last .next will be >= capacity (== capacity to be precise), so we will consider it to be NULL
|
|
|
|
|
.previous = ((i == 0) ? glyphs.capacity : (i - 1))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
glyphs.sorted_indices[i] = gui_glyph_codepoint_map{0xFFFFFFFF, i};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize texture
|
2023-09-30 02:39:12 +02:00
|
|
|
v2s texture_size = glyphs.glyph_max_size * V2S(texture_slots);
|
2023-09-26 19:40:16 +02:00
|
|
|
u8 *texture_data = (u8*)p_alloc(sizeof(u8) * texture_size.x * texture_size.y);
|
|
|
|
|
memset(texture_data, 0, sizeof(u8) * texture_size.x * texture_size.y);
|
|
|
|
|
|
|
|
|
|
glyphs.texture = assets_new_textures(&engine.am, 1);
|
|
|
|
|
*glyphs.texture = r_texture_create(texture_data, texture_size, R_TEXTURE_ALPHA | R_TEXTURE_NO_MIPMAP);
|
|
|
|
|
|
|
|
|
|
return glyphs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void gui_glyph_texture_destroy(gui_glyph_texture *glyphs)
|
|
|
|
|
{
|
|
|
|
|
if(glyphs->sorted_indices)
|
|
|
|
|
p_free(glyphs->sorted_indices);
|
|
|
|
|
if(glyphs->info)
|
|
|
|
|
p_free(glyphs->info);
|
|
|
|
|
if(glyphs->texture)
|
|
|
|
|
r_texture_destroy(glyphs->texture);
|
|
|
|
|
glyphs->sorted_indices = NULL;
|
|
|
|
|
glyphs->info = NULL;
|
|
|
|
|
glyphs->texture = NULL;
|
|
|
|
|
|
|
|
|
|
glyphs->font_size = 0;
|
|
|
|
|
glyphs->oldest = 0;
|
|
|
|
|
glyphs->newest = 0;
|
|
|
|
|
glyphs->capacity = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static gui_glyph_texture *gui_glyph_cache_texture_for_codepoints(f32 font_size, const utf8_codepoint *codepoints, u64 count)
|
|
|
|
|
{
|
|
|
|
|
// Approximate font_size. We don't really want to build different bitmaps for size 12.000000 and 12.000001.
|
|
|
|
|
// This will also prevent floating point rounding errors from rebuilding the cache.
|
|
|
|
|
font_size = floor(font_size * 10) / 10;
|
|
|
|
|
|
|
|
|
|
// Find cached texture for this size or build a new one
|
|
|
|
|
gui_glyph_texture *glyphs = NULL;
|
|
|
|
|
for(u32 i = 0; i < Glyph_Cache.capacity; i++)
|
|
|
|
|
{
|
|
|
|
|
//LOG(LOG_DEBUG, "Font size: %f - Cached: %f", font_size, Glyph_Cache.glyphs[i].font_size);
|
|
|
|
|
if(abs(Glyph_Cache.glyphs[i].font_size - font_size) < 0.01)
|
|
|
|
|
{
|
|
|
|
|
glyphs = &Glyph_Cache.glyphs[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if(glyphs == NULL)
|
|
|
|
|
{
|
|
|
|
|
//LOG(LOG_DEBUG, "Size not matched");
|
|
|
|
|
glyphs = &Glyph_Cache.glyphs[Glyph_Cache.oldest];
|
|
|
|
|
Glyph_Cache.oldest = (Glyph_Cache.oldest + 1) % Glyph_Cache.capacity;
|
|
|
|
|
|
|
|
|
|
gui_glyph_texture_destroy(glyphs);
|
|
|
|
|
*glyphs = gui_glyph_texture_create(font_size, Glyph_Cache.max_glyphs_per_texture);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Build list of unique codepoints (so that we don't render the same codepoint twice)
|
|
|
|
|
utf8_codepoint *unique = (utf8_codepoint*) p_alloc(count * sizeof(utf8_codepoint));
|
|
|
|
|
memcpy(unique, codepoints, count * sizeof(utf8_codepoint));
|
|
|
|
|
u64 unique_count = make_unique(unique, count, sizeof(utf8_codepoint), u32_cmp);
|
|
|
|
|
if(unique_count > glyphs->capacity)
|
|
|
|
|
LOG(LOG_ERROR, "Unique codepoints count > cache capacity. Some codepoints will not be rendered.");
|
|
|
|
|
|
|
|
|
|
// Find which codepoints are not already in the cache and need to be rendered
|
|
|
|
|
utf8_codepoint to_render[unique_count];
|
|
|
|
|
u32 to_render_count = 0;
|
|
|
|
|
for(u32 i = 0; i < unique_count; i++)
|
|
|
|
|
{
|
|
|
|
|
gui_glyph_codepoint_map *found = (gui_glyph_codepoint_map*) bsearch(&unique[i], glyphs->sorted_indices, glyphs->capacity, sizeof(gui_glyph_codepoint_map), u32_cmp);
|
|
|
|
|
if(found == NULL)
|
|
|
|
|
{
|
|
|
|
|
// Not found -> add to the list of glyphs to render
|
|
|
|
|
to_render[to_render_count] = unique[i];
|
|
|
|
|
to_render_count++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Found -> mark it as recent, so that is does not get deleted prematurely
|
|
|
|
|
u32 index = glyphs->sorted_indices[found - glyphs->sorted_indices].index;
|
|
|
|
|
if(index == glyphs->newest)
|
|
|
|
|
{
|
|
|
|
|
// Already the newest. Do nothing.
|
|
|
|
|
}
|
|
|
|
|
else if(index == glyphs->oldest)
|
|
|
|
|
{
|
|
|
|
|
// We have no previous to fix, only next
|
|
|
|
|
u32 next = glyphs->info[index].next;
|
|
|
|
|
glyphs->info[next].previous = glyphs->capacity; // Next is the new oldest -> no previous
|
|
|
|
|
glyphs->oldest = next;
|
|
|
|
|
|
|
|
|
|
// Set index as last element
|
|
|
|
|
glyphs->info[index].next = glyphs->capacity;
|
|
|
|
|
glyphs->info[index].previous = glyphs->newest;
|
|
|
|
|
glyphs->info[glyphs->newest].next = index;
|
|
|
|
|
glyphs->newest = index;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// We in between the list. We have both previous and next elements to fix.
|
|
|
|
|
u32 previous = glyphs->info[index].previous;
|
|
|
|
|
u32 next = glyphs->info[index].next;
|
|
|
|
|
glyphs->info[previous].next = next;
|
|
|
|
|
glyphs->info[next].previous = previous;
|
|
|
|
|
|
|
|
|
|
// Set index as last element
|
|
|
|
|
glyphs->info[index].next = glyphs->capacity;
|
|
|
|
|
glyphs->info[index].previous = glyphs->newest;
|
|
|
|
|
glyphs->info[glyphs->newest].next = index;
|
|
|
|
|
glyphs->newest = index;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p_free(unique);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Get info for rendering
|
|
|
|
|
f32 font_scale;
|
|
|
|
|
s32 font_ascent;
|
|
|
|
|
s32 font_descent;
|
|
|
|
|
s32 font_line_gap;
|
|
|
|
|
s32 font_baseline;
|
|
|
|
|
{
|
|
|
|
|
font_scale = stbtt_ScaleForPixelHeight(&Font_Info, font_size);
|
|
|
|
|
stbtt_GetFontVMetrics(&Font_Info, &font_ascent, &font_descent, &font_line_gap);
|
|
|
|
|
font_baseline = font_ascent;
|
|
|
|
|
|
|
|
|
|
font_ascent *= font_scale;
|
|
|
|
|
font_descent *= font_scale;
|
|
|
|
|
font_line_gap *= font_scale;
|
|
|
|
|
font_baseline *= font_scale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render glyph in its appropriate place
|
|
|
|
|
for(u32 i = 0; i < to_render_count; i++)
|
|
|
|
|
{
|
|
|
|
|
u32 index = glyphs->oldest;
|
|
|
|
|
glyphs->oldest = glyphs->info[index].next;
|
|
|
|
|
glyphs->info[glyphs->oldest].previous = glyphs->capacity;
|
|
|
|
|
|
|
|
|
|
glyphs->info[index].next = glyphs->capacity;
|
|
|
|
|
glyphs->info[index].previous = glyphs->newest;
|
|
|
|
|
glyphs->info[index].codepoint = to_render[i];
|
|
|
|
|
|
|
|
|
|
glyphs->info[glyphs->newest].next = index;
|
|
|
|
|
glyphs->newest = index;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Complete gui_glyph_info structure and render
|
|
|
|
|
gui_glyph_info *info = &glyphs->info[index];
|
|
|
|
|
|
|
|
|
|
v2s top_left, bottom_right;
|
|
|
|
|
stbtt_GetCodepointBitmapBox(&Font_Info, info->codepoint, font_scale, font_scale, &top_left.x, &top_left.y, &bottom_right.x, &bottom_right.y);
|
|
|
|
|
s32 advance, lsb;
|
|
|
|
|
stbtt_GetCodepointHMetrics(&Font_Info, info->codepoint, &advance, &lsb);
|
|
|
|
|
|
|
|
|
|
v2s size = bottom_right - top_left;
|
|
|
|
|
|
|
|
|
|
// Special codepoints
|
|
|
|
|
if(info->codepoint == 10) // '\n'
|
|
|
|
|
{
|
|
|
|
|
size = {0, 0};
|
|
|
|
|
advance = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info->box.position = V2(top_left);
|
|
|
|
|
info->box.size = V2(size);
|
|
|
|
|
//info->position = v2u{glyphs->glyph_max_size.x * index, 0}; // Commented because it's already pre-computed.
|
|
|
|
|
info->advance = advance * font_scale;
|
|
|
|
|
|
|
|
|
|
// @Correctness: needs to be premultiplied alpha
|
|
|
|
|
stbtt_MakeCodepointBitmap(&Font_Info, glyphs->texture->data + info->position.x, info->box.size.x, info->box.size.y, glyphs->texture->size.x, font_scale, font_scale, info->codepoint);
|
|
|
|
|
r_texture_update(glyphs->texture, glyphs->texture->data + info->position.x, V2S(info->box.size), V2S(info->position), glyphs->texture->size.x);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build sorted array with indices that point to the element
|
|
|
|
|
u32 nonempty_count = glyphs->capacity;
|
|
|
|
|
for(u32 i = 0; i < glyphs->capacity; i++)
|
|
|
|
|
{
|
|
|
|
|
glyphs->sorted_indices[i] = gui_glyph_codepoint_map{glyphs->info[i].codepoint, i};
|
|
|
|
|
if(glyphs->info[i].codepoint == 0xFFFFFFFF)
|
|
|
|
|
{
|
|
|
|
|
// When the cache is mostly empty, this makes the sorting way faster.
|
|
|
|
|
nonempty_count = i;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
qsort(glyphs->sorted_indices, nonempty_count, sizeof(gui_glyph_codepoint_map), u32_cmp);
|
|
|
|
|
|
|
|
|
|
return glyphs;
|
|
|
|
|
}
|