diff options
| author | Jonathan Bradley <jcb@pikum.xyz> | 2025-11-18 22:20:01 -0500 |
|---|---|---|
| committer | Jonathan Bradley <jcb@pikum.xyz> | 2025-11-18 22:20:01 -0500 |
| commit | 3c2260cc462c4638de2a490ceb90b4937c5bb2a7 (patch) | |
| tree | 0180e14ebf03bfa5936b894c6d2fccf9c9f93d76 | |
| parent | d154b48f99aa7cf50cb6e50b4a7668b0e8489cb5 (diff) | |
pke: chkpt font look-ahead refactor + tests
| -rw-r--r-- | src/font.cpp | 135 | ||||
| -rw-r--r-- | tests/pke-test-font.cpp | 286 |
2 files changed, 378 insertions, 43 deletions
diff --git a/src/font.cpp b/src/font.cpp index 58be327..cce9cc4 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -92,7 +92,7 @@ uint32_t utf8_to_unicode(const char* str, uint32_t &out) { return i; } -float FontType_Inner_LookAheadWordLength(const FontType *const ft, const FontRender *const fr, uint32_t index, float font_glyph_spacing) { +float FontType_Inner_LookAheadWordLength(const FontType *const ft, const FontRender *const fr, uint32_t index, float font_glyph_spacing, uint32_t *char_count = nullptr) { uint32_t i; float ret = 0; FontGlyphChar *fgc; @@ -101,48 +101,93 @@ float FontType_Inner_LookAheadWordLength(const FontType *const ft, const FontRen if (PK_HAS_FLAG(fgc->flags, FONT_GLYPH_CHAR_FLAGS_WHITESPACE) == true) { break; } + if (PK_HAS_FLAG(fgc->flags, FONT_GLYPH_CHAR_FLAGS_NEW_LINE) == true) { + break; + } ret += fgc->advance * font_glyph_spacing; } + if (char_count != nullptr) { + *char_count = i - index; + } return ret; } -float FontType_Inner_LookAheadLineLength(const FontType *const ft, const FontRender *const fr, uint32_t index, float font_glyph_spacing) { - uint32_t i; - float ret = 0; - FontGlyphChar *fgc; +float FontType_Inner_LookAheadLineLength(const FontType *const ft, const FontRender *const fr, const uint32_t index, float font_glyph_spacing, uint32_t *char_count = nullptr) { + uint32_t i, ii; + float sz, ret = 0; + FontGlyphChar *fgc, *fgc2; for (i = index; i < fr->n_glyphs; ++i) { fgc = &ft->glyphs[fr->glyph_indices[i]]; if (PK_HAS_FLAG(fgc->flags, FONT_GLYPH_CHAR_FLAGS_NEW_LINE) == true) { + if (i != index) { + fgc2 = &ft->glyphs[fr->glyph_indices[i-1]]; + if (PK_HAS_FLAG(fgc2->flags, FONT_GLYPH_CHAR_FLAGS_WHITESPACE) == true) { + ret -= fgc2->advance * font_glyph_spacing; + } + } break; } - if (ret + (fgc->advance * font_glyph_spacing) > fr->settings.surface_area_size.x) { + // we want to burn white-space characters at the front and at the back + if (PK_HAS_FLAG(fgc->flags, FONT_GLYPH_CHAR_FLAGS_WHITESPACE) == true) { + ret += fgc->advance * font_glyph_spacing; + // burn the front + if (i == index) { + ret = 0; + } + continue; + } + sz = FontType_Inner_LookAheadWordLength(ft, fr, i, font_glyph_spacing, &ii); + if (ret + sz > fr->settings.surface_area_size.x) { + // burn the back + if (i != index) { + fgc2 = fgc-1; + if (PK_HAS_FLAG(fgc2->flags, FONT_GLYPH_CHAR_FLAGS_WHITESPACE) == true) { + ret -= fgc2->advance * font_glyph_spacing; + } + } break; } - ret += fgc->advance * font_glyph_spacing; + // -1 because the for loop is about to +1. + // On words that are not the last, not subtracting 1 burns the next character (bad) + // On the last word, not subtracting 1 causes the char_count to be off-by-one + i += ii-1; + ret += sz; + } + if (char_count != nullptr) { + *char_count = i - index; } return ret; } -float FontType_Inner_LookAheadLineCount(const FontType *const ft, const FontRender *const fr, const uint32_t index) { - uint32_t i, u; - float ret = 1; - FontGlyphChar *fgc; - for (i = index; i < fr->n_glyphs; ++i) { +float FontType_Inner_LookAheadLineCount(const FontType *const ft, const FontRender *const fr, const uint32_t index, float font_glyph_spacing) { + uint32_t ii = 0, i = index; + FontGlyphChar *fgc, *fgc2; + float ret = 0; + bool burn = false; + while (i < fr->n_glyphs) { fgc = &ft->glyphs[fr->glyph_indices[i]]; - if (PK_HAS_FLAG(fgc->flags, FONT_GLYPH_CHAR_FLAGS_NEW_LINE) == true) { + if (fgc->unicode == 10 || fgc->unicode == 13) { if (i+1 < fr->n_glyphs) { - if (fgc->unicode == 10) { - u = 11; - } else { - u = 10; - } - // handle \r\n - if (ft->glyphs[fr->glyph_indices[i+1]].unicode == u) { - i += 1; // burn + fgc2 = &ft->glyphs[fr->glyph_indices[i+1]]; + if (fgc->unicode != fgc2->unicode && (fgc2->unicode == 10 || fgc2->unicode == 13)) { + ++i; } } - ret += 1; + ++i; + if (burn == false) { + ret += 1; + } + burn = false; + continue; + } + FontType_Inner_LookAheadLineLength(ft, fr, i, font_glyph_spacing, &ii); + if (ii == 0) { + ++i; + continue; } + i += ii; + ret += 1; + burn = true; } return ret; } @@ -151,7 +196,7 @@ bool FontType_Inner_CalcTransforms(const FontType *ft, FontRender *fr, FontInsta assert(ft != nullptr); assert(fr != nullptr); assert(ptr_dst != nullptr); - FontGlyphChar *fgc; + FontGlyphChar *fgc, *fgc2; FontInstanceBufferItem *buf_item; bool new_word = true; uint32_t i, ii; @@ -206,32 +251,39 @@ bool FontType_Inner_CalcTransforms(const FontType *ft, FontRender *fr, FontInsta if (PK_HAS_FLAG(fgc->flags, FONT_GLYPH_CHAR_FLAGS_NEW_LINE) == true) { // 10 line feed '\n' // 13 carriage return '\r' - if ((fgc->unicode == 10 || fgc->unicode == 13) && i+1 < fr->n_glyphs) { - if ((ft->glyphs[fr->glyph_indices[i+1]].unicode == 10 || ft->glyphs[fr->glyph_indices[i+1]].unicode == 13) && ft->glyphs[fr->glyph_indices[i+1]].unicode != fgc->unicode) { + if (i+1 < fr->n_glyphs) { + fgc2 = &ft->glyphs[fr->glyph_indices[i+1]]; + if (fgc->unicode != fgc2->unicode && (fgc->unicode == 10 || fgc->unicode == 13) && (fgc2->unicode == 10 || fgc2->unicode == 13)) { i += 1;// burn /r/n and /n/r } } line_index += 1; + new_word = true; + cursor_head = 0; line_length = FontType_Inner_LookAheadLineLength(ft, fr, i+1, font_glyph_spacing); if (PK_HAS_FLAG(fr->settings.surface_area_type_flags, FONT_RENDER_SURFACE_AREA_TYPE_FLAGS_CENTER_HORIZONTAL)) { cursor_head = (fr->settings.surface_area_size.x - line_length) / 2.0; - } else { - cursor_head = 0; } continue; } if (PK_HAS_FLAG(fgc->flags, FONT_GLYPH_CHAR_FLAGS_WHITESPACE) == true) { - // x = 2 - // a = 4 - // 6 = 2 + 4 - cursor_head += fgc->advance * font_glyph_spacing; + // 2025-11-18 JCB TODO + // This is tricky. + // if we wrapped, we want to swallow any remaining spaces... Maybe? + // - YES a space between words + // - NO a tab? if (PK_HAS_FLAG(fgc->flags, FONT_GLYPH_CHAR_FLAGS_ALIGN_ADVANCE) == true) { // x2 = x - (a - (x % a)) // x2 = 6 - (4 - (6 & 4)) // x2 = 6 - (4 - 2) // x2 = 6 - (2) // x2 = 4 - cursor_head -= (ft->spacing.em_size * font_glyph_spacing) - fmod(cursor_head, (ft->spacing.em_size * font_glyph_spacing)); + cursor_head += (ft->spacing.em_size * font_glyph_spacing) - fmod(cursor_head, (ft->spacing.em_size * font_glyph_spacing)); + } else { + // x = 2 + // a = 4 + // 6 = 2 + 4 + cursor_head += fgc->advance * font_glyph_spacing; } new_word = true; continue; @@ -240,12 +292,11 @@ bool FontType_Inner_CalcTransforms(const FontType *ft, FontRender *fr, FontInsta float word_width = FontType_Inner_LookAheadWordLength(ft, fr, i, font_glyph_spacing); if (cursor_head + word_width > fr->settings.surface_area_size.x) { line_index += 1; - line_length = FontType_Inner_LookAheadLineLength(ft, fr, i+1, font_glyph_spacing); + cursor_head = 0; + line_length = FontType_Inner_LookAheadLineLength(ft, fr, i, font_glyph_spacing); if (PK_HAS_FLAG(fr->settings.surface_area_type_flags, FONT_RENDER_SURFACE_AREA_TYPE_FLAGS_CENTER_HORIZONTAL)) { cursor_head = (fr->settings.surface_area_size.x - line_length) / 2.0; - } else { - cursor_head = 0; } } new_word = false; @@ -421,6 +472,12 @@ void FontType_Tick(double delta) { pk_bkt_arr_iterate(&ft->renders, iter_fn.invoke, &iter_fn); ft->bindings.instance_counter = index; + /* early-out because vulkan is not initialized */ + if (window == NULL) { + b = pk_bkt_arr_iter_increment(&ftd.fonts, &ft); + continue; + } + // check recreate buffer // check against index because it's the real count // fibis.next includes whitespace & unhandled characters @@ -953,14 +1010,8 @@ FontTypeRender FontType_AddStringRender(FontTypeHandle font_type_handle, const p fr->settings = *settings; fr->text = str; - if (window == NULL) { - return ret; - } - // insert new characters into tmp buffer - pk_arr_t<uint32_t> glyph_indices{}; - glyph_indices.bkt = pkeSettings.mem_bkt.game_transient; - glyph_indices.next = 0; // hide garbage val warning + pk_arr_t<uint32_t> glyph_indices{pkeSettings.mem_bkt.game_transient}; pk_arr_reserve(&glyph_indices, str.length); FontType_cstr_to_unicode(*ft, glyph_indices, str); diff --git a/tests/pke-test-font.cpp b/tests/pke-test-font.cpp index 3f299e4..d475fdd 100644 --- a/tests/pke-test-font.cpp +++ b/tests/pke-test-font.cpp @@ -10,6 +10,10 @@ static pk_membucket *bkt; +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); + void pke_test_font_setup() { pke_test_stub_init_vulkan(); bkt = pk_mem_bucket_create("pke_test_font", PK_MEM_DEFAULT_BUCKET_SIZE, PK_MEMBUCKET_FLAG_NONE); @@ -93,8 +97,278 @@ int pke_test_font_003() { return 0; } +struct pke_test_font_inner_func_params { + uint32_t index; + uint32_t char_count; + float length; +}; + +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) { + float len; + uint32_t u, ii; + for (u = 0; u < n_params; ++u) { + len = FontType_Inner_LookAheadWordLength(ft, fr, params[u].index, font_glyph_spacing, &ii); + PK_TEST_ASSERT_EQ_RET(params[u].char_count, ii); + PK_TEST_ASSERT_NEQ_RET(params[u].length, len); + } + 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) { + float len; + uint32_t u, ii; + for (u = 0; u < n_params; ++u) { + len = FontType_Inner_LookAheadLineLength(ft, fr, params[u].index, font_glyph_spacing, &ii); + PK_TEST_ASSERT_EQ_RET(params[u].char_count, ii); + PK_TEST_ASSERT_NEQ_RET(params[u].length, len); + } + 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) { + float len; + uint32_t u; + for (u = 0; u < n_params; ++u) { + len = FontType_Inner_LookAheadLineCount(ft, fr, params[u].index, font_glyph_spacing); + PK_TEST_ASSERT_EQ_RET(params[u].length, len); + } + 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; + + // word length + { + pke_test_font_inner_func_params params_word_len[5]; + params_word_len[0].index = 0; + params_word_len[0].char_count = 6; + params_word_len[0].length = 0.f; + params_word_len[1].index = 6; + params_word_len[1].char_count = 0; + params_word_len[1].length = 1.f; + params_word_len[2].index = 7; + params_word_len[2].char_count = 3; + params_word_len[2].length = 0.f; + params_word_len[3].index = 9; + params_word_len[3].char_count = 1; + params_word_len[3].length = 0.f; + params_word_len[4].index = 10; + params_word_len[4].char_count = 0; + params_word_len[4].length = 1.f; + + if ((u = pke_test_font_inner_word_length(ft, fr, font_glyph_spacing, params_word_len, 5)), u != 0) { + return u; + } + } + + // line length + { + pke_test_font_inner_func_params params_line_len[5]; + params_line_len[0].index = 0; + params_line_len[0].char_count = 10; + params_line_len[0].length = 0.f; + params_line_len[1].index = 6; + params_line_len[1].char_count = 4; + params_line_len[1].length = 0.f; + params_line_len[2].index = 7; + params_line_len[2].char_count = 3; + params_line_len[2].length = 0.f; + params_line_len[3].index = 9; + params_line_len[3].char_count = 1; + params_line_len[3].length = 0.f; + params_line_len[4].index = 10; + params_line_len[4].char_count = 0; + params_line_len[4].length = 1.f; + + if ((u = pke_test_font_inner_line_length(ft, fr, font_glyph_spacing, params_line_len, 5)), u != 0) { + return u; + } + } + + // line count + { + pke_test_font_inner_func_params params_line_count[5]; + params_line_count[0].index = 0; + params_line_count[0].length = 1.f; + params_line_count[1].index = 6; + params_line_count[1].length = 1.f; + params_line_count[2].index = 7; + params_line_count[2].length = 1.f; + params_line_count[3].index = 9; + params_line_count[3].length = 1.f; + params_line_count[4].index = 10; + params_line_count[4].length = 0.f; + + if ((u = pke_test_font_inner_line_count(ft, fr, font_glyph_spacing, params_line_count, 5)), u != 0) { + return u; + } + } + + 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; + + // word length + { + pke_test_font_inner_func_params params_word_len[10]; + params_word_len[0].index = 0; + params_word_len[0].char_count = 4; + params_word_len[0].length = 0.f; + params_word_len[1].index = 4; + params_word_len[1].char_count = 0; + params_word_len[1].length = 1.f; + params_word_len[2].index = 5; + params_word_len[2].char_count = 3; + params_word_len[2].length = 0.f; + params_word_len[3].index = 8; + params_word_len[3].char_count = 0; + params_word_len[3].length = 1.f; + params_word_len[4].index = 9; + params_word_len[4].char_count = 8; + params_word_len[4].length = 0.f; + params_word_len[5].index = 17; + params_word_len[5].char_count = 0; + params_word_len[5].length = 1.f; + params_word_len[6].index = 18; + params_word_len[6].char_count = 0; + params_word_len[6].length = 1.f; + params_word_len[7].index = 19; + params_word_len[7].char_count = 4; + params_word_len[7].length = 0.f; + params_word_len[8].index = 23; + params_word_len[8].char_count = 0; + params_word_len[8].length = 1.f; + params_word_len[9].index = 24; + params_word_len[9].char_count = 1; + params_word_len[9].length = 0.f; + + if ((u = pke_test_font_inner_word_length(ft, fr, font_glyph_spacing, params_word_len, 10)), u != 0) { + return u; + } + } + + // line length + { + pke_test_font_inner_func_params params_line_len[10]; + params_line_len[0].index = 0; + params_line_len[0].char_count = 8; + params_line_len[0].length = 0.f; + params_line_len[1].index = 4; + params_line_len[1].char_count = 4; + params_line_len[1].length = 0.f; + params_line_len[2].index = 5; + params_line_len[2].char_count = 3; + params_line_len[2].length = 0.f; + params_line_len[3].index = 8; + params_line_len[3].char_count = 0; + params_line_len[3].length = 1.f; + params_line_len[4].index = 9; + params_line_len[4].char_count = 8; + params_line_len[4].length = 0.f; + params_line_len[5].index = 17; + params_line_len[5].char_count = 0; + params_line_len[5].length = 1.f; + params_line_len[6].index = 18; + params_line_len[6].char_count = 0; + params_line_len[6].length = 1.f; + params_line_len[7].index = 19; + params_line_len[7].char_count = 6; + params_line_len[7].length = 0.f; + params_line_len[8].index = 23; + params_line_len[8].char_count = 2; + params_line_len[8].length = 0.f; + params_line_len[9].index = 24; + params_line_len[9].char_count = 1; + params_line_len[9].length = 0.f; + + if ((u = pke_test_font_inner_line_length(ft, fr, font_glyph_spacing, params_line_len, 10)), u != 0) { + return u; + } + } + + // line count + { + pke_test_font_inner_func_params params_line_count[10]; + params_line_count[0].index = 0; + params_line_count[0].length = 3.f; + params_line_count[1].index = 4; + params_line_count[1].length = 3.f; + params_line_count[2].index = 5; + params_line_count[2].length = 3.f; + params_line_count[3].index = 8; + params_line_count[3].length = 3.f; + params_line_count[4].index = 9; + params_line_count[4].length = 2.f; + params_line_count[5].index = 17; + params_line_count[5].length = 2.f; + params_line_count[6].index = 18; + params_line_count[6].length = 2.f; + params_line_count[7].index = 19; + params_line_count[7].length = 1.f; + params_line_count[8].index = 23; + params_line_count[8].length = 1.f; + params_line_count[9].index = 24; + params_line_count[9].length = 1.f; + + if ((u = pke_test_font_inner_line_count(ft, fr, font_glyph_spacing, params_line_count, 10)), u != 0) { + return u; + } + } + + return 0; +} + struct pk_test_group *pke_test_font_get_group() { - static const uint64_t test_count = 3; + static const uint64_t test_count = 5; static struct pk_test tests[test_count] = { { .title = "test 001", @@ -111,6 +385,16 @@ struct pk_test_group *pke_test_font_get_group() { .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, + }, }; static struct pk_test_group group = {}; group.title = "font test"; |
