#include "pke-test-font.hpp" #include "asset-manager.hpp" #include "ecs.hpp" #include "font.hpp" #include "pk.h" #include "pke-test-stubs.h" #include "static-plane.hpp" #include "thread-pool.hpp" #include "window.hpp" static pk_membucket *bkt; struct FontInstanceBufferItem { glm::mat4 pos_scale; glm::vec4 color_foreground; glm::vec4 color_background; glm::vec2 sprite_region_min; glm::vec2 sprite_region_max; glm::vec2 bounding_region_min; glm::vec2 bounding_region_max; float width; float padding[3]; }; float FontType_Inner_LookAheadWordLength(const FontType *const ft, const FontRender *const fr, uint32_t index, float font_glyph_spacing, uint32_t *char_count); float FontType_Inner_LookAheadLineLength(const FontType *const ft, const FontRender *const fr, const uint32_t index, float font_glyph_spacing, uint32_t *char_count); float FontType_Inner_LookAheadLineCount(const FontType *const ft, const FontRender *const fr, const uint32_t index, float font_glyph_spacing); bool FontType_Inner_CalcTransforms(const FontType *ft, FontRender *fr, FontInstanceBufferItem *ptr_dst, uint32_t *count); void pke_test_font_setup() { pke_test_stub_init_vulkan(); Extent.width = 1920; Extent.height = 1080; bkt = pk_mem_bucket_create("pke_test_font", PK_MEM_DEFAULT_BUCKET_SIZE, PK_MEMBUCKET_FLAG_NONE); pk_mem_bucket_set_client_mem_bucket(bkt); pk_ev_init(bkt); PkeThreads_Init(); AM_Init(); ECS_Init(); FontType_Init(); } void pke_test_font_teardown() { FontType_Teardown(); ECS_Teardown(); AM_Teardown(); PkeThreads_Teardown(); pk_ev_teardown(); pk_mem_bucket_destroy(bkt); pk_mem_bucket_set_client_mem_bucket(nullptr); bkt = nullptr; pke_test_stub_teardown_vulkan(); } /* Ensure the font exists */ int pke_test_font_001() { FontType *ft = FontType_Get({0,0}); PK_TEST_ASSERT_NEQ_RET(nullptr, ft); return 0; } /* Ensure we can have more than one FontRender */ int pke_test_font_002() { FontTypeHandle fti{}; FontTypeRender handle_001{}; FontTypeRender handle_002{}; FontRenderSettings frs{}; handle_001 = FontType_AddStringRender(fti, std::move(cstring_to_pk_cstr("string one")), &frs); PK_TEST_ASSERT_EQ_RET(0u, handle_001.font_type_handle.b); PK_TEST_ASSERT_EQ_RET(0u, handle_001.font_type_handle.i); PK_TEST_ASSERT_EQ_RET(0u, handle_001.font_render_handle.b); PK_TEST_ASSERT_EQ_RET(0u, handle_001.font_render_handle.i); handle_002 = FontType_AddStringRender(fti, std::move(cstring_to_pk_cstr("string two")), &frs); PK_TEST_ASSERT_EQ_RET(0u, handle_002.font_type_handle.b); PK_TEST_ASSERT_EQ_RET(0u, handle_002.font_type_handle.i); PK_TEST_ASSERT_EQ_RET(0u, handle_002.font_render_handle.b); PK_TEST_ASSERT_EQ_RET(1u, handle_002.font_render_handle.i); return 0; } /* Ensure we can have a lot of FontRenders */ int pke_test_font_003() { unsigned short u; const unsigned short count = 96; FontTypeHandle fti{0,0}; FontTypeRender handles[count]; FontRenderSettings frs{}; frs.surface_area_size = glm::vec2(300, 300); frs.surface_area_type_flags |= FONT_RENDER_SURFACE_AREA_TYPE_FLAGS_CENTER_BOTH; for (u = 0; u < count; ++u) { handles[u].font_type_handle = {0,0}; handles[u].font_render_handle = {0,0}; } for (u = 0; u < count; ++u) { char *s = (char*)malloc(64); snprintf(s, 1024, "%s - %u", "test", u); handles[u] = FontType_AddStringRender(fti, std::move(cstring_to_pk_cstr(s)), &frs); PK_TEST_ASSERT_EQ_RET(0u, handles[u].font_type_handle.b); PK_TEST_ASSERT_EQ_RET(0u, handles[u].font_type_handle.i); PK_TEST_ASSERT_EQ_RET(floor(u / (float)PK_BKT_ARR_HANDLE_I_MAX), handles[u].font_render_handle.b); PK_TEST_ASSERT_EQ_RET(u % PK_BKT_ARR_HANDLE_I_MAX, handles[u].font_render_handle.i); } return 0; } struct pke_test_font_inner_func_params { uint32_t index; uint32_t char_count; float val; }; int pke_test_font_inner_word_length(const FontType *const ft, const FontRender *const fr, float font_glyph_spacing, pke_test_font_inner_func_params *params, uint32_t n_params) { uint32_t u; for (u = 0; u < n_params; ++u) { params[u].val = FontType_Inner_LookAheadWordLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } return 0; } int pke_test_font_inner_line_length(const FontType *const ft, const FontRender *const fr, float font_glyph_spacing, pke_test_font_inner_func_params *params, uint32_t n_params) { uint32_t u; for (u = 0; u < n_params; ++u) { params[u].val = FontType_Inner_LookAheadLineLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } return 0; } int pke_test_font_inner_line_count(const FontType *const ft, const FontRender *const fr, float font_glyph_spacing, pke_test_font_inner_func_params *params, uint32_t n_params) { uint32_t u; for (u = 0; u < n_params; ++u) { params[u].val = FontType_Inner_LookAheadLineCount(ft, fr, params[u].index, font_glyph_spacing); } return 0; } /* Ensure expected results from LookAhead functions * Simple * */ int pke_test_font_004() { FontTypeHandle fti{}; FontTypeRender handle_001{}; FontType *ft; FontRender *fr; FontRenderSettings frs{}; uint32_t u; float font_glyph_spacing; const char *str = "string one"; frs.surface_area_size = glm::vec2(300, 300); handle_001 = FontType_AddStringRender(fti, std::move(cstring_to_pk_cstr(str)), &frs); FontType_Tick(0.f); ft = FontType_Get(fti); fr = FontType_GetFontRender(handle_001); PK_TEST_ASSERT_EQ_RET(10, fr->n_glyphs); font_glyph_spacing = ft->spacing.em_size * fr->settings.char_scale; pke_test_font_inner_func_params params[5]; params[0].index = 0; params[1].index = 6; params[2].index = 7; params[3].index = 9; params[4].index = 10; // word length { for (u = 0; u < 5; ++u) { params[u].val = FontType_Inner_LookAheadWordLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } PK_TEST_ASSERT_EQ_RET (6, params[0].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[0].val); PK_TEST_ASSERT_EQ_RET (0, params[1].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[1].val); PK_TEST_ASSERT_EQ_RET (3, params[2].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[2].val); PK_TEST_ASSERT_EQ_RET (1, params[3].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[3].val); PK_TEST_ASSERT_EQ_RET (0, params[4].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[4].val); } // line length { for (u = 0; u < 5; ++u) { params[u].val = FontType_Inner_LookAheadLineLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } PK_TEST_ASSERT_EQ_RET (10, params[0].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[0].val); PK_TEST_ASSERT_EQ_RET (4, params[1].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[1].val); PK_TEST_ASSERT_EQ_RET (3, params[2].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[2].val); PK_TEST_ASSERT_EQ_RET (1, params[3].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[3].val); PK_TEST_ASSERT_EQ_RET (0, params[4].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[4].val); } // line count { for (u = 0; u < 5; ++u) { params[u].val = FontType_Inner_LookAheadLineCount(ft, fr, params[u].index, font_glyph_spacing); } PK_TEST_ASSERT_EQ_RET (1.f, params[0].val); PK_TEST_ASSERT_EQ_RET (1.f, params[1].val); PK_TEST_ASSERT_EQ_RET (1.f, params[2].val); PK_TEST_ASSERT_EQ_RET (1.f, params[3].val); PK_TEST_ASSERT_EQ_RET (0.f, params[4].val); } return 0; } /* Ensure expected results from LookAhead functions * multi-line * */ int pke_test_font_005() { FontTypeHandle fti{}; FontTypeRender handle_001{}; FontType *ft; FontRender *fr; FontRenderSettings frs{}; uint32_t u; float font_glyph_spacing; const char *str = "line one\nline_two\r\nline 3"; frs.surface_area_size = glm::vec2(300, 300); handle_001 = FontType_AddStringRender(fti, std::move(cstring_to_pk_cstr(str)), &frs); FontType_Tick(0.f); ft = FontType_Get(fti); fr = FontType_GetFontRender(handle_001); PK_TEST_ASSERT_EQ_RET(25, fr->n_glyphs); font_glyph_spacing = ft->spacing.em_size * fr->settings.char_scale; pke_test_font_inner_func_params params[10]; params[0].index = 0; params[1].index = 4; params[2].index = 5; params[3].index = 8; params[4].index = 9; params[5].index = 17; params[6].index = 18; params[7].index = 19; params[8].index = 23; params[9].index = 24; // word length { for (u = 0; u < 10; ++u) { params[u].val = FontType_Inner_LookAheadWordLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } PK_TEST_ASSERT_EQ_RET (4, params[0].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[0].val); PK_TEST_ASSERT_EQ_RET (0, params[1].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[1].val); PK_TEST_ASSERT_EQ_RET (3, params[2].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[2].val); PK_TEST_ASSERT_EQ_RET (0, params[3].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[3].val); PK_TEST_ASSERT_EQ_RET (8, params[4].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[4].val); PK_TEST_ASSERT_EQ_RET (0, params[5].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[5].val); PK_TEST_ASSERT_EQ_RET (0, params[6].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[6].val); PK_TEST_ASSERT_EQ_RET (4, params[7].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[7].val); PK_TEST_ASSERT_EQ_RET (0, params[8].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[8].val); PK_TEST_ASSERT_EQ_RET (1, params[9].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[9].val); } // line length { for (u = 0; u < 10; ++u) { params[u].val = FontType_Inner_LookAheadLineLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } PK_TEST_ASSERT_EQ_RET (8, params[0].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[0].val); PK_TEST_ASSERT_EQ_RET (4, params[1].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[1].val); PK_TEST_ASSERT_EQ_RET (3, params[2].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[2].val); PK_TEST_ASSERT_EQ_RET (0, params[3].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[3].val); PK_TEST_ASSERT_EQ_RET (8, params[4].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[4].val); PK_TEST_ASSERT_EQ_RET (0, params[5].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[5].val); PK_TEST_ASSERT_EQ_RET (0, params[6].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[6].val); PK_TEST_ASSERT_EQ_RET (6, params[7].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[7].val); PK_TEST_ASSERT_EQ_RET (2, params[8].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[8].val); PK_TEST_ASSERT_EQ_RET (1, params[9].char_count); PK_TEST_ASSERT_NEQ_RET(0.f, params[9].val); } // line count { for (u = 0; u < 10; ++u) { params[u].val = FontType_Inner_LookAheadLineCount(ft, fr, params[u].index, font_glyph_spacing); } PK_TEST_ASSERT_EQ_RET (3.f, params[0].val); PK_TEST_ASSERT_EQ_RET (3.f, params[1].val); PK_TEST_ASSERT_EQ_RET (3.f, params[2].val); PK_TEST_ASSERT_EQ_RET (3.f, params[3].val); PK_TEST_ASSERT_EQ_RET (2.f, params[4].val); PK_TEST_ASSERT_EQ_RET (2.f, params[5].val); PK_TEST_ASSERT_EQ_RET (2.f, params[6].val); PK_TEST_ASSERT_EQ_RET (1.f, params[7].val); PK_TEST_ASSERT_EQ_RET (1.f, params[8].val); PK_TEST_ASSERT_EQ_RET (1.f, params[9].val); } return 0; } int pke_test_font_006() { FontTypeHandle fti{}; FontTypeRender handle_001{}; FontType *ft; FontRender *fr; FontRenderSettings frs{}; uint32_t u; float font_glyph_spacing; const char *str = "cowabungalong\ttest"; frs.char_scale = 10; frs.surface_area_size = glm::vec2(300, 300); handle_001 = FontType_AddStringRender(fti, std::move(cstring_to_pk_cstr(str)), &frs); FontType_Tick(0.f); ft = FontType_Get(fti); fr = FontType_GetFontRender(handle_001); PK_TEST_ASSERT_EQ_RET(18, fr->n_glyphs); font_glyph_spacing = ft->spacing.em_size * fr->settings.char_scale; pke_test_font_inner_func_params params[3]; params[0].index = 0; params[1].index = 13; params[2].index = 14; // word length { for (u = 0; u < 3; ++u) { params[u].val = FontType_Inner_LookAheadWordLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } PK_TEST_ASSERT_EQ_RET (13, params[0].char_count); PK_TEST_ASSERT_EQ_RET (75.f, params[0].val); PK_TEST_ASSERT_EQ_RET (0, params[1].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[1].val); PK_TEST_ASSERT_EQ_RET (4, params[2].char_count); PK_TEST_ASSERT_EQ_RET (21.25f, params[2].val); } // line length { for (u = 0; u < 3; ++u) { params[u].val = FontType_Inner_LookAheadLineLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } PK_TEST_ASSERT_EQ_RET (18, params[0].char_count); PK_TEST_ASSERT_EQ_RET (11527.f, floor((double)params[0].val * 100)); PK_TEST_ASSERT_EQ_RET (5, params[1].char_count); PK_TEST_ASSERT_EQ_RET (402777.f, floor((double)params[1].val * 10000)); PK_TEST_ASSERT_EQ_RET (4, params[2].char_count); PK_TEST_ASSERT_EQ_RET (202777.f, floor((double)params[2].val * 10000)); } // line count { for (u = 0; u < 3; ++u) { params[u].val = FontType_Inner_LookAheadLineCount(ft, fr, params[u].index, font_glyph_spacing); } PK_TEST_ASSERT_EQ_RET (1.f, params[0].val); PK_TEST_ASSERT_EQ_RET (1.f, params[1].val); PK_TEST_ASSERT_EQ_RET (1.f, params[2].val); } return 0; } int pke_test_font_007() { FontTypeHandle fti{}; FontTypeRender handle_001{}; FontType *ft; FontRender *fr; FontRenderSettings frs{}; uint32_t u = 0; float font_glyph_spacing; glm::vec4 coord; pke_test_font_inner_func_params params[3]; pk_arr_t fibis{}; params[0].index = 0; params[1].index = 13; params[2].index = 14; const char *str = "cowabungalong\ttest"; pk_arr_resize(&fibis, strlen(str) + 2); frs.surface_area_type_flags |= FONT_RENDER_SURFACE_AREA_TYPE_FLAGS_CENTER_BOTH; frs.char_scale = 10; frs.surface_area_size = glm::vec2(80, 300); // 1 = 0; // TODO // there's something wrong with wrapping. // The issue is that the first line doesn't re-center when the line wraps, // but only for like one pixel. // It's possible the "inner" funcs are correct and the error is happening in // the logic that writes the fibis. // The fact it's a 1-pixel error makes me think it's a `<` vs `<=` thing, but // it isn't jumping out at me. - Perhaps a different "off-by-one" scenario. // I should also consider refactoring the "inner" funcs to just write the // fibis as we go (sans centering). I would need to track: // - index of where new lines start // - line length of each line for centering (do not write to fibi) // - would need to loop a second time to add centering padding // I should wait on this until I do the first refactor, so I can test for // accuracy as I make the changes. handle_001 = FontType_AddStringRender(fti, std::move(cstring_to_pk_cstr(str)), &frs); ft = FontType_Get(fti); fr = FontType_GetFontRender(handle_001); // FontType_Tick(0.f); FontType_Inner_CalcTransforms(ft, fr, &fibis[0], &u); PK_TEST_ASSERT_EQ_RET(18, fr->n_glyphs); PK_TEST_ASSERT_EQ_RET(17, u); font_glyph_spacing = ft->spacing.em_size * fr->settings.char_scale; // word length { for (u = 0; u < 3; ++u) { params[u].val = FontType_Inner_LookAheadWordLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } PK_TEST_ASSERT_EQ_RET (13, params[0].char_count); PK_TEST_ASSERT_EQ_RET (75.f, params[0].val); PK_TEST_ASSERT_EQ_RET (0, params[1].char_count); PK_TEST_ASSERT_EQ_RET (0.f, params[1].val); PK_TEST_ASSERT_EQ_RET (4, params[2].char_count); PK_TEST_ASSERT_EQ_RET (21.25f, params[2].val); } // line length { for (u = 0; u < 3; ++u) { params[u].val = FontType_Inner_LookAheadLineLength(ft, fr, params[u].index, font_glyph_spacing, ¶ms[u].char_count); } PK_TEST_ASSERT_EQ_RET (13, params[0].char_count); // centered text ignores character padding at front + back PK_TEST_ASSERT_EQ_RET (7402.f, floor((double)params[0].val * 100)); PK_TEST_ASSERT_EQ_RET (5, params[1].char_count); // centered text removes white-space at beginning+end of lines PK_TEST_ASSERT_EQ_RET (202777.f, floor((double)params[1].val * 10000)); PK_TEST_ASSERT_EQ_RET (4, params[2].char_count); PK_TEST_ASSERT_EQ_RET (202777.f, floor((double)params[2].val * 10000)); } // line count { for (u = 0; u < 3; ++u) { params[u].val = FontType_Inner_LookAheadLineCount(ft, fr, params[u].index, font_glyph_spacing); } PK_TEST_ASSERT_EQ_RET (2.f, params[0].val); PK_TEST_ASSERT_EQ_RET (1.f, params[1].val); PK_TEST_ASSERT_EQ_RET (1.f, params[2].val); } // render & fibi stuff { /* {-1, -1}, { 1, -1}, { 1, 1}, {-1, 1}, */ // top left // ~6 (total padding) == (80 (box size) - 74.02) // ~3 padding on each side when centered coord = fibis[0].pos_scale * glm::vec4(pkeIntrinsicsPlane.vert[0], 0, 1); coord.x += 1; coord.y += 1; coord.x *= 1920.f/2.f; coord.y *= 1080.f/2.f; PK_TEST_ASSERT_GTE_RET(2.98, coord.x); PK_TEST_ASSERT_LTE_RET(2.99, coord.x); PK_TEST_ASSERT_GTE_RET(139.86f, coord.y); PK_TEST_ASSERT_LTE_RET(139.87f, coord.y); // bottom right coord = fibis[12].pos_scale * glm::vec4(pkeIntrinsicsPlane.vert[2], 0, 1); coord.x += 1; coord.y += 1; coord.x *= 1920.f/2.f; coord.y *= 1080.f/2.f; PK_TEST_ASSERT_GTE_RET(2.98, frs.surface_area_size.x-coord.x); PK_TEST_ASSERT_LTE_RET(2.99, frs.surface_area_size.x-coord.x); // I'm not 100% sure why these differ from above. // However, a manual test reveals that this value is correct. // using the test project, center some text and change its container's // size; the text will remain in the same position (center box too). PK_TEST_ASSERT_GTE_RET(151.11f, frs.surface_area_size.y-coord.y); PK_TEST_ASSERT_LTE_RET(151.12f, frs.surface_area_size.y-coord.y); } return 0; } struct pk_test_group *pke_test_font_get_group() { static const uint64_t test_count = 7; static struct pk_test tests[test_count] = { { .title = "test 001", .func = pke_test_font_001, .expected_result = 0, }, { .title = "test 002", .func = pke_test_font_002, .expected_result = 0, }, { .title = "test 003", .func = pke_test_font_003, .expected_result = 0, }, { .title = "test 004", .func = pke_test_font_004, .expected_result = 0, }, { .title = "test 005", .func = pke_test_font_005, .expected_result = 0, }, { .title = "test 006", .func = pke_test_font_006, .expected_result = 0, }, { .title = "test 007", .func = pke_test_font_007, .expected_result = 0, }, }; static struct pk_test_group group = {}; group.title = "font test"; group.test_setup = pke_test_font_setup; group.test_teardown = pke_test_font_teardown; group.n_tests = test_count; group.tests = &tests[0]; return &group; }