From 77012ae90daeed58feab79a58c08c66b41ebc767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Sat, 10 Feb 2024 21:28:48 +0100 Subject: [PATCH] font: font_wrap support --- bind/v4k.lua | 1 + demos/01-font.c | 23 ++- engine/joint/v4k.h | 388 ++++++++++++++++++++++++---------------- engine/split/v4k_font.c | 386 +++++++++++++++++++++++---------------- engine/split/v4k_font.h | 2 +- engine/v4k.c | 386 +++++++++++++++++++++++---------------- engine/v4k.h | 2 +- 7 files changed, 709 insertions(+), 479 deletions(-) diff --git a/bind/v4k.lua b/bind/v4k.lua index 80ff0f5..2801f8f 100644 --- a/bind/v4k.lua +++ b/bind/v4k.lua @@ -681,6 +681,7 @@ typedef struct font_metrics_t { void font_goto(float x, float y); vec2 font_print(const char *text); vec2 font_clip(const char *text, vec4 rect); + const char* font_wrap(const char *text, float max_width); vec2 font_rect(const char *text); font_metrics_t font_metrics(const char *text); void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords); diff --git a/demos/01-font.c b/demos/01-font.c index 745f6ef..e93e312 100644 --- a/demos/01-font.c +++ b/demos/01-font.c @@ -132,8 +132,22 @@ int main() { font_goto(pos.x, pos.y); vec2 size=font_rect(txt); font_metrics_t m=font_metrics(txt); - ddraw_aabb(vec3(pos.x,pos.y,0), vec3(pos.x+size.x,pos.y+size.y-m.descent+m.linegap+100,0)); - font_clip(txt, vec4(pos.x, pos.y, size.x, size.y+100)); + ddraw_aabb(vec3(pos.x,pos.y,0), vec3(pos.x+size.x,pos.y+size.y-m.descent+m.linegap,0)); + font_clip(txt, vec4(pos.x, pos.y, size.x, size.y)); + ddraw_pop_2d(); + } + + static float max_width = 300.0f; + { + vec2 pos = vec2(2000,240); + ddraw_push_2d(); + char *txt = "This is the first line.\n \nAnd now the second line.\n \nYou can do a third great line, too!\n \nNow this is a very long line aaaaaaaaaa!\n"; + font_goto(pos.x, pos.y); + const char *wrapped_text = font_wrap(txt, max_width); + vec2 size=font_rect(wrapped_text); + font_metrics_t m=font_metrics(wrapped_text); + ddraw_aabb(vec3(pos.x,pos.y,0), vec3(pos.x+size.x,pos.y+size.y-m.descent+m.linegap,0)); + font_clip(wrapped_text, vec4(pos.x, pos.y, size.x, size.y)); ddraw_pop_2d(); } @@ -207,5 +221,10 @@ int main() { ui_font(); ui_panel_end(); } + + if (ui_panel("Textbox", 0)) { + ui_float("Max width", &max_width); + ui_panel_end(); + } } } diff --git a/engine/joint/v4k.h b/engine/joint/v4k.h index 7e32988..324b8ed 100644 --- a/engine/joint/v4k.h +++ b/engine/joint/v4k.h @@ -16147,9 +16147,9 @@ API vec2 font_xy(); API void font_goto(float x, float y); API vec2 font_print(const char *text); API vec2 font_clip(const char *text, vec4 rect); +API const char* font_wrap(const char *text, float max_width); API vec2 font_rect(const char *text); API font_metrics_t font_metrics(const char *text); -// void font_wrap(vec2 topleft, vec2 bottomright); // syntax highlighting API void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords); // comma separated tokens. expensive, please cache result. @@ -363702,6 +363702,234 @@ vec2 font_draw_ex(const char *text, vec2 offset, vec4 rect, const char *col, voi return abs2(vec2(W*W > X*X ? W : X, Y*Y > LL*LL ? Y : LL)); } +static vec2 gotoxy = {0}; + +// Return cursor +vec2 font_xy() { + return gotoxy; +} + +// Relocate cursor +void font_goto(float x, float y) { + gotoxy = vec2(x, y); +} + +// Print and linefeed. Text may include markup code +vec2 font_clip(const char *text, vec4 rect) { + int l=0,c=0,r=0,j=0,t=0,b=0,m=0,B=0; + + while ( text[0] == FONT_LEFT[0] ) { + int has_set=0; + if (text[1] == FONT_LEFT[1]) l = 1, has_set=1; + if (text[1] == FONT_CENTER[1]) c = 1, has_set=1; + if (text[1] == FONT_RIGHT[1]) r = 1, has_set=1; + if (text[1] == FONT_JUSTIFY[1]) j = 1, has_set=1; + if (text[1] == FONT_TOP[1]) t = 1, has_set=1; + if (text[1] == FONT_BOTTOM[1]) b = 1, has_set=1; + if (text[1] == FONT_MIDDLE[1]) m = 1, has_set=1; + if (text[1] == FONT_BASELINE[1]) B = 1, has_set=1; + if (!has_set) break; + else text += 2; + } + + int num_newlines = 0; + for (int i = 0, end = strlen(text); i < end; ++i) { + if (text[i] == '\n') ++num_newlines; + } + + if (num_newlines > 1) { + vec2 text_dims = font_rect(text); + char tags[4] = {0}; + int t=0; + while (*text) { + char ch = *text; + + if( (ch >= 1 && ch <= 6) || + (ch >= 0x1a && ch <= 0x1f) || + (ch >= 0x10 && ch <= 0x19)) { + if (t < sizeof(tags)) tags[t++] = ch; + } + else break; + ++text; + } + array(char *) lines = strsplit(text, "\n"); + if (b) { + gotoxy.y += (rect.w - text_dims.y); + } + if (m) { + gotoxy.y += (rect.w/2. - text_dims.y/2.); + } + if (B) { + gotoxy.y += (rect.w/2. - text_dims.y/1.); + } + for (int i = 0; i < array_count(lines); i++) { + char *line = va("%s%s\n", tags, lines[i]); + vec2 text_rect = font_rect(line); + if( l || c || r ) { + gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; + } else if (j) { + float words_space = 0.0f; + array(char *) words = strsplit(lines[i], " "); + for (int k = 0; k < array_count(words); ++k) { + words_space += font_rect(words[k]).x; + } + if (array_count(words) == 0) { + gotoxy.y += text_rect.y; + continue; + } + float extra_space = rect.z - words_space; + int gaps = array_count(words) - 1; + float space_offset = gaps > 0 ? extra_space / (float)gaps : 0; + for (int k = 0; k < array_count(words); ++k) { + vec2 dims = font_draw_ex(va("%s%s", tags, words[k]), gotoxy, rect, NULL, font_draw_cmd); + gotoxy.x += dims.x + space_offset; + } + gotoxy.x = rect.x; + } + + if (!j) { + font_draw_ex(line, gotoxy, rect, NULL, font_draw_cmd); + } + + gotoxy.y += text_rect.y; + } + return text_dims; + } else { + if( l || c || r ) { + vec2 text_rect = font_rect(text); + gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; + } + if( t || b || m || B ) { + vec2 text_rect = font_rect(text); + gotoxy.y = t ? rect.y : b ? ((rect.y+rect.w) - text_rect.y) : m ? rect.y+rect.w/2.-text_rect.y/2. : rect.y+rect.w/2.-text_rect.y/1; + } + vec2 dims = font_draw_ex(text, gotoxy, rect, NULL, font_draw_cmd); + gotoxy.y += strchr(text, '\n') ? dims.y : 0; + gotoxy.x = strchr(text, '\n') ? rect.x : gotoxy.x + dims.x; + return dims; + } +} + +vec2 font_print(const char *text) { + vec4 dims = {0, 0, window_width(), window_height()}; + return font_clip(text, dims); +} + +const char *font_wrap(const char *text, float max_width) { + // return early if the text fits the max_width already + if (font_rect(text).x <= max_width) { + return text; + } + + // skip alignment flags and collect tags + while ( text[0] == FONT_LEFT[0] ) { + int has_set=0; + if (text[1] == FONT_LEFT[1]) has_set=1; + if (text[1] == FONT_CENTER[1]) has_set=1; + if (text[1] == FONT_RIGHT[1]) has_set=1; + if (text[1] == FONT_JUSTIFY[1]) has_set=1; + if (text[1] == FONT_TOP[1]) has_set=1; + if (text[1] == FONT_BOTTOM[1]) has_set=1; + if (text[1] == FONT_MIDDLE[1]) has_set=1; + if (text[1] == FONT_BASELINE[1]) has_set=1; + if (!has_set) break; + else text += 2; + } + + char tags[4] = {0}; + int t=0; + while (*text) { + char ch = *text; + + if( (ch >= 1 && ch <= 6) || + (ch >= 0x1a && ch <= 0x1f) || + (ch >= 0x10 && ch <= 0x19)) { + if (t < sizeof(tags)) tags[t++] = ch; + } + else break; + ++text; + } + + array(char*) words = strsplit(text, " "); + static __thread int slot = 0; + static __thread char *buf[16] = {0}; + + int len = strlen(text) + array_count(words); + slot = (slot+1) % 16; + buf[slot] = REALLOC(buf[slot], len+1); + memset(buf[slot], 0, len+1); + + char *out = buf[slot]; + + float width = 0.0f; + for (int i = 0; i < array_count(words); ++i) { + char *word = words[i]; + float word_width = font_rect(va("%s%s ", tags, word)).x; + if (strstr(word, "\n")) { + width = word_width; + strcat(out, va("%s ", word)); + } else { + if (width+word_width > max_width) { + width = 0.0f; + strcat(out, "\n"); + } + width += word_width; + strcat(out, va("%s ", word)); + } + } + + // get rid of the space added at the end + out[strlen(out)] = 0; + + return out; +} + +// Print a code snippet with syntax highlighting +vec2 font_highlight(const char *text, const void *colors) { + vec4 screen_dim = {0, 0, window_width(), window_height()}; + vec2 dims = font_draw_ex(text, gotoxy, screen_dim, (const char *)colors, font_draw_cmd); + gotoxy.y += strchr(text, '\n') ? dims.y : 0; + gotoxy.x = strchr(text, '\n') ? 0 : gotoxy.x + dims.x; + return dims; +} + +// Calculate the size of a string, in the pixel size specified. Count stray newlines too. +vec2 font_rect(const char *str) { + vec4 dims = {0, 0, window_width(), window_height()}; + return font_draw_ex(str, gotoxy, dims, NULL, NULL); +} + +font_metrics_t font_metrics(const char *text) { + font_metrics_t m={0}; + int S = 3; + font_t *f = &fonts[0]; + + // utf8 to utf32 + array(uint32_t) unicode = string32(text); + + // parse string + for( int i = 0, end = array_count(unicode); i < end; ++i ) { + uint32_t ch = unicode[i]; + if( ch >= 1 && ch <= 6 ) { + S = ch; + continue; + } + if( ch >= 0x1a && ch <= 0x1f ) { + if( fonts[ ch - 0x1a ].initialized) { + // change face + f = &fonts[ ch - 0x1a ]; + } + continue; + } + } + + m.ascent = f->ascent*f->factor*f->scale[S]; + m.descent = f->descent*f->factor*f->scale[S]; + m.linegap = f->linegap*f->factor*f->scale[S]; + m.linedist = f->linedist*f->factor*f->scale[S]; + return m; +} + void *font_colorize(const char *text, const char *comma_types, const char *comma_keywords) { // reallocate memory static __thread int slot = 0; @@ -363924,164 +364152,6 @@ void *font_colorize(const char *text, const char *comma_types, const char *comma return col; } -static vec2 gotoxy = {0}; - -// Return cursor -vec2 font_xy() { - return gotoxy; -} - -// Relocate cursor -void font_goto(float x, float y) { - gotoxy = vec2(x, y); -} - -// Print and linefeed. Text may include markup code -vec2 font_clip(const char *text, vec4 rect) { - int l=0,c=0,r=0,j=0,t=0,b=0,m=0,B=0; - - while ( text[0] == FONT_LEFT[0] ) { - int has_set=0; - if (text[1] == FONT_LEFT[1]) l = 1, has_set=1; - if (text[1] == FONT_CENTER[1]) c = 1, has_set=1; - if (text[1] == FONT_RIGHT[1]) r = 1, has_set=1; - if (text[1] == FONT_JUSTIFY[1]) j = 1, has_set=1; - if (text[1] == FONT_TOP[1]) t = 1, has_set=1; - if (text[1] == FONT_BOTTOM[1]) b = 1, has_set=1; - if (text[1] == FONT_MIDDLE[1]) m = 1, has_set=1; - if (text[1] == FONT_BASELINE[1]) B = 1, has_set=1; - if (!has_set) break; - else text += 2; - } - - int num_newlines = 0; - for (int i = 0, end = strlen(text); i < end; ++i) { - if (text[i] == '\n') ++num_newlines; - } - - if (num_newlines > 1) { - vec2 text_dims = font_rect(text); - char tags[4] = {0}; - int t=0; - while (*text) { - char ch = *text; - - if( (ch >= 1 && ch <= 6) || - (ch >= 0x1a && ch <= 0x1f) || - (ch >= 0x10 && ch <= 0x19)) { - if (t < sizeof(tags)) tags[t++] = ch; - } - else break; - ++text; - } - array(char *) lines = strsplit(text, "\n"); - if (b) { - gotoxy.y += (rect.w - text_dims.y); - } - if (m) { - gotoxy.y += (rect.w/2. - text_dims.y/2.); - } - if (B) { - gotoxy.y += (rect.w/2. - text_dims.y/1.); - } - for (int i = 0; i < array_count(lines); i++) { - char *line = va("%s%s\n", tags, lines[i]); - vec2 text_rect = font_rect(line); - if( l || c || r ) { - gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; - } else if (j) { - float words_space = 0.0f; - array(char *) words = strsplit(lines[i], " "); - for (int k = 0; k < array_count(words); ++k) { - words_space += font_rect(words[k]).x; - } - if (array_count(words) == 0) { - gotoxy.y += text_rect.y; - continue; - } - float extra_space = rect.z - words_space; - int gaps = array_count(words) - 1; - float space_offset = gaps > 0 ? extra_space / (float)gaps : 0; - for (int k = 0; k < array_count(words); ++k) { - vec2 dims = font_draw_ex(va("%s%s", tags, words[k]), gotoxy, rect, NULL, font_draw_cmd); - gotoxy.x += dims.x + space_offset; - } - gotoxy.x = rect.x; - } - - if (!j) { - font_draw_ex(line, gotoxy, rect, NULL, font_draw_cmd); - } - - gotoxy.y += text_rect.y; - } - return text_dims; - } else { - if( l || c || r ) { - vec2 text_rect = font_rect(text); - gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; - } - if( t || b || m || B ) { - vec2 text_rect = font_rect(text); - gotoxy.y = t ? rect.y : b ? ((rect.y+rect.w) - text_rect.y) : m ? rect.y+rect.w/2.-text_rect.y/2. : rect.y+rect.w/2.-text_rect.y/1; - } - vec2 dims = font_draw_ex(text, gotoxy, rect, NULL, font_draw_cmd); - gotoxy.y += strchr(text, '\n') ? dims.y : 0; - gotoxy.x = strchr(text, '\n') ? rect.x : gotoxy.x + dims.x; - return dims; - } -} - -vec2 font_print(const char *text) { - vec4 dims = {0, 0, window_width(), window_height()}; - return font_clip(text, dims); -} - -// Print a code snippet with syntax highlighting -vec2 font_highlight(const char *text, const void *colors) { - vec4 screen_dim = {0, 0, window_width(), window_height()}; - vec2 dims = font_draw_ex(text, gotoxy, screen_dim, (const char *)colors, font_draw_cmd); - gotoxy.y += strchr(text, '\n') ? dims.y : 0; - gotoxy.x = strchr(text, '\n') ? 0 : gotoxy.x + dims.x; - return dims; -} - -// Calculate the size of a string, in the pixel size specified. Count stray newlines too. -vec2 font_rect(const char *str) { - vec4 dims = {0, 0, window_width(), window_height()}; - return font_draw_ex(str, gotoxy, dims, NULL, NULL); -} - -font_metrics_t font_metrics(const char *text) { - font_metrics_t m={0}; - int S = 3; - font_t *f = &fonts[0]; - - // utf8 to utf32 - array(uint32_t) unicode = string32(text); - - // parse string - for( int i = 0, end = array_count(unicode); i < end; ++i ) { - uint32_t ch = unicode[i]; - if( ch >= 1 && ch <= 6 ) { - S = ch; - continue; - } - if( ch >= 0x1a && ch <= 0x1f ) { - if( fonts[ ch - 0x1a ].initialized) { - // change face - f = &fonts[ ch - 0x1a ]; - } - continue; - } - } - - m.ascent = f->ascent*f->factor*f->scale[S]; - m.descent = f->descent*f->factor*f->scale[S]; - m.linegap = f->linegap*f->factor*f->scale[S]; - m.linedist = f->linedist*f->factor*f->scale[S]; - return m; -} #line 0 #line 1 "v4k_gui.c" diff --git a/engine/split/v4k_font.c b/engine/split/v4k_font.c index 6f85f8e..6b28324 100644 --- a/engine/split/v4k_font.c +++ b/engine/split/v4k_font.c @@ -2145,6 +2145,234 @@ vec2 font_draw_ex(const char *text, vec2 offset, vec4 rect, const char *col, voi return abs2(vec2(W*W > X*X ? W : X, Y*Y > LL*LL ? Y : LL)); } +static vec2 gotoxy = {0}; + +// Return cursor +vec2 font_xy() { + return gotoxy; +} + +// Relocate cursor +void font_goto(float x, float y) { + gotoxy = vec2(x, y); +} + +// Print and linefeed. Text may include markup code +vec2 font_clip(const char *text, vec4 rect) { + int l=0,c=0,r=0,j=0,t=0,b=0,m=0,B=0; + + while ( text[0] == FONT_LEFT[0] ) { + int has_set=0; + if (text[1] == FONT_LEFT[1]) l = 1, has_set=1; + if (text[1] == FONT_CENTER[1]) c = 1, has_set=1; + if (text[1] == FONT_RIGHT[1]) r = 1, has_set=1; + if (text[1] == FONT_JUSTIFY[1]) j = 1, has_set=1; + if (text[1] == FONT_TOP[1]) t = 1, has_set=1; + if (text[1] == FONT_BOTTOM[1]) b = 1, has_set=1; + if (text[1] == FONT_MIDDLE[1]) m = 1, has_set=1; + if (text[1] == FONT_BASELINE[1]) B = 1, has_set=1; + if (!has_set) break; + else text += 2; + } + + int num_newlines = 0; + for (int i = 0, end = strlen(text); i < end; ++i) { + if (text[i] == '\n') ++num_newlines; + } + + if (num_newlines > 1) { + vec2 text_dims = font_rect(text); + char tags[4] = {0}; + int t=0; + while (*text) { + char ch = *text; + + if( (ch >= 1 && ch <= 6) || + (ch >= 0x1a && ch <= 0x1f) || + (ch >= 0x10 && ch <= 0x19)) { + if (t < sizeof(tags)) tags[t++] = ch; + } + else break; + ++text; + } + array(char *) lines = strsplit(text, "\n"); + if (b) { + gotoxy.y += (rect.w - text_dims.y); + } + if (m) { + gotoxy.y += (rect.w/2. - text_dims.y/2.); + } + if (B) { + gotoxy.y += (rect.w/2. - text_dims.y/1.); + } + for (int i = 0; i < array_count(lines); i++) { + char *line = va("%s%s\n", tags, lines[i]); + vec2 text_rect = font_rect(line); + if( l || c || r ) { + gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; + } else if (j) { + float words_space = 0.0f; + array(char *) words = strsplit(lines[i], " "); + for (int k = 0; k < array_count(words); ++k) { + words_space += font_rect(words[k]).x; + } + if (array_count(words) == 0) { + gotoxy.y += text_rect.y; + continue; + } + float extra_space = rect.z - words_space; + int gaps = array_count(words) - 1; + float space_offset = gaps > 0 ? extra_space / (float)gaps : 0; + for (int k = 0; k < array_count(words); ++k) { + vec2 dims = font_draw_ex(va("%s%s", tags, words[k]), gotoxy, rect, NULL, font_draw_cmd); + gotoxy.x += dims.x + space_offset; + } + gotoxy.x = rect.x; + } + + if (!j) { + font_draw_ex(line, gotoxy, rect, NULL, font_draw_cmd); + } + + gotoxy.y += text_rect.y; + } + return text_dims; + } else { + if( l || c || r ) { + vec2 text_rect = font_rect(text); + gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; + } + if( t || b || m || B ) { + vec2 text_rect = font_rect(text); + gotoxy.y = t ? rect.y : b ? ((rect.y+rect.w) - text_rect.y) : m ? rect.y+rect.w/2.-text_rect.y/2. : rect.y+rect.w/2.-text_rect.y/1; + } + vec2 dims = font_draw_ex(text, gotoxy, rect, NULL, font_draw_cmd); + gotoxy.y += strchr(text, '\n') ? dims.y : 0; + gotoxy.x = strchr(text, '\n') ? rect.x : gotoxy.x + dims.x; + return dims; + } +} + +vec2 font_print(const char *text) { + vec4 dims = {0, 0, window_width(), window_height()}; + return font_clip(text, dims); +} + +const char *font_wrap(const char *text, float max_width) { + // return early if the text fits the max_width already + if (font_rect(text).x <= max_width) { + return text; + } + + // skip alignment flags and collect tags + while ( text[0] == FONT_LEFT[0] ) { + int has_set=0; + if (text[1] == FONT_LEFT[1]) has_set=1; + if (text[1] == FONT_CENTER[1]) has_set=1; + if (text[1] == FONT_RIGHT[1]) has_set=1; + if (text[1] == FONT_JUSTIFY[1]) has_set=1; + if (text[1] == FONT_TOP[1]) has_set=1; + if (text[1] == FONT_BOTTOM[1]) has_set=1; + if (text[1] == FONT_MIDDLE[1]) has_set=1; + if (text[1] == FONT_BASELINE[1]) has_set=1; + if (!has_set) break; + else text += 2; + } + + char tags[4] = {0}; + int t=0; + while (*text) { + char ch = *text; + + if( (ch >= 1 && ch <= 6) || + (ch >= 0x1a && ch <= 0x1f) || + (ch >= 0x10 && ch <= 0x19)) { + if (t < sizeof(tags)) tags[t++] = ch; + } + else break; + ++text; + } + + array(char*) words = strsplit(text, " "); + static __thread int slot = 0; + static __thread char *buf[16] = {0}; + + int len = strlen(text) + array_count(words); + slot = (slot+1) % 16; + buf[slot] = REALLOC(buf[slot], len+1); + memset(buf[slot], 0, len+1); + + char *out = buf[slot]; + + float width = 0.0f; + for (int i = 0; i < array_count(words); ++i) { + char *word = words[i]; + float word_width = font_rect(va("%s%s ", tags, word)).x; + if (strstr(word, "\n")) { + width = word_width; + strcat(out, va("%s ", word)); + } else { + if (width+word_width > max_width) { + width = 0.0f; + strcat(out, "\n"); + } + width += word_width; + strcat(out, va("%s ", word)); + } + } + + // get rid of the space added at the end + out[strlen(out)] = 0; + + return out; +} + +// Print a code snippet with syntax highlighting +vec2 font_highlight(const char *text, const void *colors) { + vec4 screen_dim = {0, 0, window_width(), window_height()}; + vec2 dims = font_draw_ex(text, gotoxy, screen_dim, (const char *)colors, font_draw_cmd); + gotoxy.y += strchr(text, '\n') ? dims.y : 0; + gotoxy.x = strchr(text, '\n') ? 0 : gotoxy.x + dims.x; + return dims; +} + +// Calculate the size of a string, in the pixel size specified. Count stray newlines too. +vec2 font_rect(const char *str) { + vec4 dims = {0, 0, window_width(), window_height()}; + return font_draw_ex(str, gotoxy, dims, NULL, NULL); +} + +font_metrics_t font_metrics(const char *text) { + font_metrics_t m={0}; + int S = 3; + font_t *f = &fonts[0]; + + // utf8 to utf32 + array(uint32_t) unicode = string32(text); + + // parse string + for( int i = 0, end = array_count(unicode); i < end; ++i ) { + uint32_t ch = unicode[i]; + if( ch >= 1 && ch <= 6 ) { + S = ch; + continue; + } + if( ch >= 0x1a && ch <= 0x1f ) { + if( fonts[ ch - 0x1a ].initialized) { + // change face + f = &fonts[ ch - 0x1a ]; + } + continue; + } + } + + m.ascent = f->ascent*f->factor*f->scale[S]; + m.descent = f->descent*f->factor*f->scale[S]; + m.linegap = f->linegap*f->factor*f->scale[S]; + m.linedist = f->linedist*f->factor*f->scale[S]; + return m; +} + void *font_colorize(const char *text, const char *comma_types, const char *comma_keywords) { // reallocate memory static __thread int slot = 0; @@ -2367,161 +2595,3 @@ void *font_colorize(const char *text, const char *comma_types, const char *comma return col; } -static vec2 gotoxy = {0}; - -// Return cursor -vec2 font_xy() { - return gotoxy; -} - -// Relocate cursor -void font_goto(float x, float y) { - gotoxy = vec2(x, y); -} - -// Print and linefeed. Text may include markup code -vec2 font_clip(const char *text, vec4 rect) { - int l=0,c=0,r=0,j=0,t=0,b=0,m=0,B=0; - - while ( text[0] == FONT_LEFT[0] ) { - int has_set=0; - if (text[1] == FONT_LEFT[1]) l = 1, has_set=1; - if (text[1] == FONT_CENTER[1]) c = 1, has_set=1; - if (text[1] == FONT_RIGHT[1]) r = 1, has_set=1; - if (text[1] == FONT_JUSTIFY[1]) j = 1, has_set=1; - if (text[1] == FONT_TOP[1]) t = 1, has_set=1; - if (text[1] == FONT_BOTTOM[1]) b = 1, has_set=1; - if (text[1] == FONT_MIDDLE[1]) m = 1, has_set=1; - if (text[1] == FONT_BASELINE[1]) B = 1, has_set=1; - if (!has_set) break; - else text += 2; - } - - int num_newlines = 0; - for (int i = 0, end = strlen(text); i < end; ++i) { - if (text[i] == '\n') ++num_newlines; - } - - if (num_newlines > 1) { - vec2 text_dims = font_rect(text); - char tags[4] = {0}; - int t=0; - while (*text) { - char ch = *text; - - if( (ch >= 1 && ch <= 6) || - (ch >= 0x1a && ch <= 0x1f) || - (ch >= 0x10 && ch <= 0x19)) { - if (t < sizeof(tags)) tags[t++] = ch; - } - else break; - ++text; - } - array(char *) lines = strsplit(text, "\n"); - if (b) { - gotoxy.y += (rect.w - text_dims.y); - } - if (m) { - gotoxy.y += (rect.w/2. - text_dims.y/2.); - } - if (B) { - gotoxy.y += (rect.w/2. - text_dims.y/1.); - } - for (int i = 0; i < array_count(lines); i++) { - char *line = va("%s%s\n", tags, lines[i]); - vec2 text_rect = font_rect(line); - if( l || c || r ) { - gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; - } else if (j) { - float words_space = 0.0f; - array(char *) words = strsplit(lines[i], " "); - for (int k = 0; k < array_count(words); ++k) { - words_space += font_rect(words[k]).x; - } - if (array_count(words) == 0) { - gotoxy.y += text_rect.y; - continue; - } - float extra_space = rect.z - words_space; - int gaps = array_count(words) - 1; - float space_offset = gaps > 0 ? extra_space / (float)gaps : 0; - for (int k = 0; k < array_count(words); ++k) { - vec2 dims = font_draw_ex(va("%s%s", tags, words[k]), gotoxy, rect, NULL, font_draw_cmd); - gotoxy.x += dims.x + space_offset; - } - gotoxy.x = rect.x; - } - - if (!j) { - font_draw_ex(line, gotoxy, rect, NULL, font_draw_cmd); - } - - gotoxy.y += text_rect.y; - } - return text_dims; - } else { - if( l || c || r ) { - vec2 text_rect = font_rect(text); - gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; - } - if( t || b || m || B ) { - vec2 text_rect = font_rect(text); - gotoxy.y = t ? rect.y : b ? ((rect.y+rect.w) - text_rect.y) : m ? rect.y+rect.w/2.-text_rect.y/2. : rect.y+rect.w/2.-text_rect.y/1; - } - vec2 dims = font_draw_ex(text, gotoxy, rect, NULL, font_draw_cmd); - gotoxy.y += strchr(text, '\n') ? dims.y : 0; - gotoxy.x = strchr(text, '\n') ? rect.x : gotoxy.x + dims.x; - return dims; - } -} - -vec2 font_print(const char *text) { - vec4 dims = {0, 0, window_width(), window_height()}; - return font_clip(text, dims); -} - -// Print a code snippet with syntax highlighting -vec2 font_highlight(const char *text, const void *colors) { - vec4 screen_dim = {0, 0, window_width(), window_height()}; - vec2 dims = font_draw_ex(text, gotoxy, screen_dim, (const char *)colors, font_draw_cmd); - gotoxy.y += strchr(text, '\n') ? dims.y : 0; - gotoxy.x = strchr(text, '\n') ? 0 : gotoxy.x + dims.x; - return dims; -} - -// Calculate the size of a string, in the pixel size specified. Count stray newlines too. -vec2 font_rect(const char *str) { - vec4 dims = {0, 0, window_width(), window_height()}; - return font_draw_ex(str, gotoxy, dims, NULL, NULL); -} - -font_metrics_t font_metrics(const char *text) { - font_metrics_t m={0}; - int S = 3; - font_t *f = &fonts[0]; - - // utf8 to utf32 - array(uint32_t) unicode = string32(text); - - // parse string - for( int i = 0, end = array_count(unicode); i < end; ++i ) { - uint32_t ch = unicode[i]; - if( ch >= 1 && ch <= 6 ) { - S = ch; - continue; - } - if( ch >= 0x1a && ch <= 0x1f ) { - if( fonts[ ch - 0x1a ].initialized) { - // change face - f = &fonts[ ch - 0x1a ]; - } - continue; - } - } - - m.ascent = f->ascent*f->factor*f->scale[S]; - m.descent = f->descent*f->factor*f->scale[S]; - m.linegap = f->linegap*f->factor*f->scale[S]; - m.linedist = f->linedist*f->factor*f->scale[S]; - return m; -} diff --git a/engine/split/v4k_font.h b/engine/split/v4k_font.h index f360cbd..30a0c3c 100644 --- a/engine/split/v4k_font.h +++ b/engine/split/v4k_font.h @@ -90,9 +90,9 @@ API vec2 font_xy(); API void font_goto(float x, float y); API vec2 font_print(const char *text); API vec2 font_clip(const char *text, vec4 rect); +API const char* font_wrap(const char *text, float max_width); API vec2 font_rect(const char *text); API font_metrics_t font_metrics(const char *text); -// void font_wrap(vec2 topleft, vec2 bottomright); // syntax highlighting API void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords); // comma separated tokens. expensive, please cache result. diff --git a/engine/v4k.c b/engine/v4k.c index 53f5524..44d5129 100644 --- a/engine/v4k.c +++ b/engine/v4k.c @@ -10843,6 +10843,234 @@ vec2 font_draw_ex(const char *text, vec2 offset, vec4 rect, const char *col, voi return abs2(vec2(W*W > X*X ? W : X, Y*Y > LL*LL ? Y : LL)); } +static vec2 gotoxy = {0}; + +// Return cursor +vec2 font_xy() { + return gotoxy; +} + +// Relocate cursor +void font_goto(float x, float y) { + gotoxy = vec2(x, y); +} + +// Print and linefeed. Text may include markup code +vec2 font_clip(const char *text, vec4 rect) { + int l=0,c=0,r=0,j=0,t=0,b=0,m=0,B=0; + + while ( text[0] == FONT_LEFT[0] ) { + int has_set=0; + if (text[1] == FONT_LEFT[1]) l = 1, has_set=1; + if (text[1] == FONT_CENTER[1]) c = 1, has_set=1; + if (text[1] == FONT_RIGHT[1]) r = 1, has_set=1; + if (text[1] == FONT_JUSTIFY[1]) j = 1, has_set=1; + if (text[1] == FONT_TOP[1]) t = 1, has_set=1; + if (text[1] == FONT_BOTTOM[1]) b = 1, has_set=1; + if (text[1] == FONT_MIDDLE[1]) m = 1, has_set=1; + if (text[1] == FONT_BASELINE[1]) B = 1, has_set=1; + if (!has_set) break; + else text += 2; + } + + int num_newlines = 0; + for (int i = 0, end = strlen(text); i < end; ++i) { + if (text[i] == '\n') ++num_newlines; + } + + if (num_newlines > 1) { + vec2 text_dims = font_rect(text); + char tags[4] = {0}; + int t=0; + while (*text) { + char ch = *text; + + if( (ch >= 1 && ch <= 6) || + (ch >= 0x1a && ch <= 0x1f) || + (ch >= 0x10 && ch <= 0x19)) { + if (t < sizeof(tags)) tags[t++] = ch; + } + else break; + ++text; + } + array(char *) lines = strsplit(text, "\n"); + if (b) { + gotoxy.y += (rect.w - text_dims.y); + } + if (m) { + gotoxy.y += (rect.w/2. - text_dims.y/2.); + } + if (B) { + gotoxy.y += (rect.w/2. - text_dims.y/1.); + } + for (int i = 0; i < array_count(lines); i++) { + char *line = va("%s%s\n", tags, lines[i]); + vec2 text_rect = font_rect(line); + if( l || c || r ) { + gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; + } else if (j) { + float words_space = 0.0f; + array(char *) words = strsplit(lines[i], " "); + for (int k = 0; k < array_count(words); ++k) { + words_space += font_rect(words[k]).x; + } + if (array_count(words) == 0) { + gotoxy.y += text_rect.y; + continue; + } + float extra_space = rect.z - words_space; + int gaps = array_count(words) - 1; + float space_offset = gaps > 0 ? extra_space / (float)gaps : 0; + for (int k = 0; k < array_count(words); ++k) { + vec2 dims = font_draw_ex(va("%s%s", tags, words[k]), gotoxy, rect, NULL, font_draw_cmd); + gotoxy.x += dims.x + space_offset; + } + gotoxy.x = rect.x; + } + + if (!j) { + font_draw_ex(line, gotoxy, rect, NULL, font_draw_cmd); + } + + gotoxy.y += text_rect.y; + } + return text_dims; + } else { + if( l || c || r ) { + vec2 text_rect = font_rect(text); + gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; + } + if( t || b || m || B ) { + vec2 text_rect = font_rect(text); + gotoxy.y = t ? rect.y : b ? ((rect.y+rect.w) - text_rect.y) : m ? rect.y+rect.w/2.-text_rect.y/2. : rect.y+rect.w/2.-text_rect.y/1; + } + vec2 dims = font_draw_ex(text, gotoxy, rect, NULL, font_draw_cmd); + gotoxy.y += strchr(text, '\n') ? dims.y : 0; + gotoxy.x = strchr(text, '\n') ? rect.x : gotoxy.x + dims.x; + return dims; + } +} + +vec2 font_print(const char *text) { + vec4 dims = {0, 0, window_width(), window_height()}; + return font_clip(text, dims); +} + +const char *font_wrap(const char *text, float max_width) { + // return early if the text fits the max_width already + if (font_rect(text).x <= max_width) { + return text; + } + + // skip alignment flags and collect tags + while ( text[0] == FONT_LEFT[0] ) { + int has_set=0; + if (text[1] == FONT_LEFT[1]) has_set=1; + if (text[1] == FONT_CENTER[1]) has_set=1; + if (text[1] == FONT_RIGHT[1]) has_set=1; + if (text[1] == FONT_JUSTIFY[1]) has_set=1; + if (text[1] == FONT_TOP[1]) has_set=1; + if (text[1] == FONT_BOTTOM[1]) has_set=1; + if (text[1] == FONT_MIDDLE[1]) has_set=1; + if (text[1] == FONT_BASELINE[1]) has_set=1; + if (!has_set) break; + else text += 2; + } + + char tags[4] = {0}; + int t=0; + while (*text) { + char ch = *text; + + if( (ch >= 1 && ch <= 6) || + (ch >= 0x1a && ch <= 0x1f) || + (ch >= 0x10 && ch <= 0x19)) { + if (t < sizeof(tags)) tags[t++] = ch; + } + else break; + ++text; + } + + array(char*) words = strsplit(text, " "); + static __thread int slot = 0; + static __thread char *buf[16] = {0}; + + int len = strlen(text) + array_count(words); + slot = (slot+1) % 16; + buf[slot] = REALLOC(buf[slot], len+1); + memset(buf[slot], 0, len+1); + + char *out = buf[slot]; + + float width = 0.0f; + for (int i = 0; i < array_count(words); ++i) { + char *word = words[i]; + float word_width = font_rect(va("%s%s ", tags, word)).x; + if (strstr(word, "\n")) { + width = word_width; + strcat(out, va("%s ", word)); + } else { + if (width+word_width > max_width) { + width = 0.0f; + strcat(out, "\n"); + } + width += word_width; + strcat(out, va("%s ", word)); + } + } + + // get rid of the space added at the end + out[strlen(out)] = 0; + + return out; +} + +// Print a code snippet with syntax highlighting +vec2 font_highlight(const char *text, const void *colors) { + vec4 screen_dim = {0, 0, window_width(), window_height()}; + vec2 dims = font_draw_ex(text, gotoxy, screen_dim, (const char *)colors, font_draw_cmd); + gotoxy.y += strchr(text, '\n') ? dims.y : 0; + gotoxy.x = strchr(text, '\n') ? 0 : gotoxy.x + dims.x; + return dims; +} + +// Calculate the size of a string, in the pixel size specified. Count stray newlines too. +vec2 font_rect(const char *str) { + vec4 dims = {0, 0, window_width(), window_height()}; + return font_draw_ex(str, gotoxy, dims, NULL, NULL); +} + +font_metrics_t font_metrics(const char *text) { + font_metrics_t m={0}; + int S = 3; + font_t *f = &fonts[0]; + + // utf8 to utf32 + array(uint32_t) unicode = string32(text); + + // parse string + for( int i = 0, end = array_count(unicode); i < end; ++i ) { + uint32_t ch = unicode[i]; + if( ch >= 1 && ch <= 6 ) { + S = ch; + continue; + } + if( ch >= 0x1a && ch <= 0x1f ) { + if( fonts[ ch - 0x1a ].initialized) { + // change face + f = &fonts[ ch - 0x1a ]; + } + continue; + } + } + + m.ascent = f->ascent*f->factor*f->scale[S]; + m.descent = f->descent*f->factor*f->scale[S]; + m.linegap = f->linegap*f->factor*f->scale[S]; + m.linedist = f->linedist*f->factor*f->scale[S]; + return m; +} + void *font_colorize(const char *text, const char *comma_types, const char *comma_keywords) { // reallocate memory static __thread int slot = 0; @@ -11065,164 +11293,6 @@ void *font_colorize(const char *text, const char *comma_types, const char *comma return col; } -static vec2 gotoxy = {0}; - -// Return cursor -vec2 font_xy() { - return gotoxy; -} - -// Relocate cursor -void font_goto(float x, float y) { - gotoxy = vec2(x, y); -} - -// Print and linefeed. Text may include markup code -vec2 font_clip(const char *text, vec4 rect) { - int l=0,c=0,r=0,j=0,t=0,b=0,m=0,B=0; - - while ( text[0] == FONT_LEFT[0] ) { - int has_set=0; - if (text[1] == FONT_LEFT[1]) l = 1, has_set=1; - if (text[1] == FONT_CENTER[1]) c = 1, has_set=1; - if (text[1] == FONT_RIGHT[1]) r = 1, has_set=1; - if (text[1] == FONT_JUSTIFY[1]) j = 1, has_set=1; - if (text[1] == FONT_TOP[1]) t = 1, has_set=1; - if (text[1] == FONT_BOTTOM[1]) b = 1, has_set=1; - if (text[1] == FONT_MIDDLE[1]) m = 1, has_set=1; - if (text[1] == FONT_BASELINE[1]) B = 1, has_set=1; - if (!has_set) break; - else text += 2; - } - - int num_newlines = 0; - for (int i = 0, end = strlen(text); i < end; ++i) { - if (text[i] == '\n') ++num_newlines; - } - - if (num_newlines > 1) { - vec2 text_dims = font_rect(text); - char tags[4] = {0}; - int t=0; - while (*text) { - char ch = *text; - - if( (ch >= 1 && ch <= 6) || - (ch >= 0x1a && ch <= 0x1f) || - (ch >= 0x10 && ch <= 0x19)) { - if (t < sizeof(tags)) tags[t++] = ch; - } - else break; - ++text; - } - array(char *) lines = strsplit(text, "\n"); - if (b) { - gotoxy.y += (rect.w - text_dims.y); - } - if (m) { - gotoxy.y += (rect.w/2. - text_dims.y/2.); - } - if (B) { - gotoxy.y += (rect.w/2. - text_dims.y/1.); - } - for (int i = 0; i < array_count(lines); i++) { - char *line = va("%s%s\n", tags, lines[i]); - vec2 text_rect = font_rect(line); - if( l || c || r ) { - gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; - } else if (j) { - float words_space = 0.0f; - array(char *) words = strsplit(lines[i], " "); - for (int k = 0; k < array_count(words); ++k) { - words_space += font_rect(words[k]).x; - } - if (array_count(words) == 0) { - gotoxy.y += text_rect.y; - continue; - } - float extra_space = rect.z - words_space; - int gaps = array_count(words) - 1; - float space_offset = gaps > 0 ? extra_space / (float)gaps : 0; - for (int k = 0; k < array_count(words); ++k) { - vec2 dims = font_draw_ex(va("%s%s", tags, words[k]), gotoxy, rect, NULL, font_draw_cmd); - gotoxy.x += dims.x + space_offset; - } - gotoxy.x = rect.x; - } - - if (!j) { - font_draw_ex(line, gotoxy, rect, NULL, font_draw_cmd); - } - - gotoxy.y += text_rect.y; - } - return text_dims; - } else { - if( l || c || r ) { - vec2 text_rect = font_rect(text); - gotoxy.x = l ? rect.x : r ? ((rect.x+rect.z) - text_rect.x) : rect.x+rect.z/2. - text_rect.x/2.; - } - if( t || b || m || B ) { - vec2 text_rect = font_rect(text); - gotoxy.y = t ? rect.y : b ? ((rect.y+rect.w) - text_rect.y) : m ? rect.y+rect.w/2.-text_rect.y/2. : rect.y+rect.w/2.-text_rect.y/1; - } - vec2 dims = font_draw_ex(text, gotoxy, rect, NULL, font_draw_cmd); - gotoxy.y += strchr(text, '\n') ? dims.y : 0; - gotoxy.x = strchr(text, '\n') ? rect.x : gotoxy.x + dims.x; - return dims; - } -} - -vec2 font_print(const char *text) { - vec4 dims = {0, 0, window_width(), window_height()}; - return font_clip(text, dims); -} - -// Print a code snippet with syntax highlighting -vec2 font_highlight(const char *text, const void *colors) { - vec4 screen_dim = {0, 0, window_width(), window_height()}; - vec2 dims = font_draw_ex(text, gotoxy, screen_dim, (const char *)colors, font_draw_cmd); - gotoxy.y += strchr(text, '\n') ? dims.y : 0; - gotoxy.x = strchr(text, '\n') ? 0 : gotoxy.x + dims.x; - return dims; -} - -// Calculate the size of a string, in the pixel size specified. Count stray newlines too. -vec2 font_rect(const char *str) { - vec4 dims = {0, 0, window_width(), window_height()}; - return font_draw_ex(str, gotoxy, dims, NULL, NULL); -} - -font_metrics_t font_metrics(const char *text) { - font_metrics_t m={0}; - int S = 3; - font_t *f = &fonts[0]; - - // utf8 to utf32 - array(uint32_t) unicode = string32(text); - - // parse string - for( int i = 0, end = array_count(unicode); i < end; ++i ) { - uint32_t ch = unicode[i]; - if( ch >= 1 && ch <= 6 ) { - S = ch; - continue; - } - if( ch >= 0x1a && ch <= 0x1f ) { - if( fonts[ ch - 0x1a ].initialized) { - // change face - f = &fonts[ ch - 0x1a ]; - } - continue; - } - } - - m.ascent = f->ascent*f->factor*f->scale[S]; - m.descent = f->descent*f->factor*f->scale[S]; - m.linegap = f->linegap*f->factor*f->scale[S]; - m.linedist = f->linedist*f->factor*f->scale[S]; - return m; -} #line 0 #line 1 "v4k_gui.c" diff --git a/engine/v4k.h b/engine/v4k.h index b87155a..8996206 100644 --- a/engine/v4k.h +++ b/engine/v4k.h @@ -2214,9 +2214,9 @@ API vec2 font_xy(); API void font_goto(float x, float y); API vec2 font_print(const char *text); API vec2 font_clip(const char *text, vec4 rect); +API const char* font_wrap(const char *text, float max_width); API vec2 font_rect(const char *text); API font_metrics_t font_metrics(const char *text); -// void font_wrap(vec2 topleft, vec2 bottomright); // syntax highlighting API void* font_colorize(const char *text, const char *comma_types, const char *comma_keywords); // comma separated tokens. expensive, please cache result.