summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Bradley <jcb@pikum.xyz>2025-11-18 22:20:01 -0500
committerJonathan Bradley <jcb@pikum.xyz>2025-11-18 22:20:01 -0500
commit3c2260cc462c4638de2a490ceb90b4937c5bb2a7 (patch)
tree0180e14ebf03bfa5936b894c6d2fccf9c9f93d76
parentd154b48f99aa7cf50cb6e50b4a7668b0e8489cb5 (diff)
pke: chkpt font look-ahead refactor + tests
-rw-r--r--src/font.cpp135
-rw-r--r--tests/pke-test-font.cpp286
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";