text wrap

This commit is contained in:
2025-11-04 19:16:36 -06:00
parent 149ee23e45
commit a05dda4914
7 changed files with 257 additions and 80 deletions

View File

@@ -159,23 +159,22 @@ const char *esc_color(const char *c, struct rgba *color, struct rgba defc)
return c;
}
// text is a string, font f, wrap is how long a line is before wrapping. -1to not wrap
HMM_Vec2 measure_text(const char *text, font *f, float letterSpacing, float wrap)
// text is a string, font f, wrap is how long a line is before wrapping. -1 to not wrap
HMM_Vec2 measure_text(const char *text, font *f, float letterSpacing, float wrap, TEXT_BREAK breakAt, TEXT_ALIGN align)
{
int breakAtWord = 0;
int breakAtWord = (breakAt == WORD);
HMM_Vec2 dim = {0};
float maxWidth = 0; // Maximum width of any line
float lineWidth = 0; // Current line width
float maxWidth = 0;
float lineWidth = 0;
float lineHeight = f->ascent - f->descent;
float height = lineHeight; // Total height
const char *wordStart = text; // Start of the current word for word wrapping
float wordWidth = 0; // Width of the current word
float spaceWidth = f->Characters[' '].advance + letterSpacing; // Space character width
float height = lineHeight;
const char *wordStart = text;
float wordWidth = 0;
float spaceWidth = f->Characters[' '].advance + letterSpacing;
for (const char *c = text; *c != '\0'; c++) {
if (*c == '\n') {
// Handle explicit line breaks
maxWidth = fmaxf(maxWidth, lineWidth);
lineWidth = 0;
height += lineHeight + f->linegap;
@@ -186,24 +185,18 @@ HMM_Vec2 measure_text(const char *text, font *f, float letterSpacing, float wrap
float charWidth = f->Characters[(unsigned char)*c].advance + letterSpacing;
// Handle wrapping
if (wrap > 0 && lineWidth + charWidth > wrap) {
if (breakAtWord && *c != ' ') {
// Roll back to the last word if breaking at word boundaries
if (wordWidth > 0) {
lineWidth -= wordWidth + spaceWidth;
c = wordStart - 1; // Reset to start of the word
}
if (breakAtWord && *c != ' ' && wordWidth > 0) {
lineWidth -= wordWidth;
c = wordStart - 1;
}
// Finish the current line and reset
maxWidth = fmaxf(maxWidth, lineWidth);
lineWidth = 0;
height += lineHeight + f->linegap;
wordStart = c + 1; // Start a new word
wordStart = c + 1;
wordWidth = 0;
// Skip to next character if wrapping on letters
if (!breakAtWord) {
lineWidth += charWidth;
continue;
@@ -212,7 +205,6 @@ HMM_Vec2 measure_text(const char *text, font *f, float letterSpacing, float wrap
lineWidth += charWidth;
// Update word width if breaking at word boundaries
if (breakAtWord) {
if (*c == ' ') {
wordWidth = 0;
@@ -223,43 +215,143 @@ HMM_Vec2 measure_text(const char *text, font *f, float letterSpacing, float wrap
}
}
// Finalize dimensions
maxWidth = fmaxf(maxWidth, lineWidth);
dim.x = maxWidth;
dim.y = height;
return dim;
}
/* pos given in screen coordinates */
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, colorf color, float wrap) {
text_vert *buffer = NULL;
HMM_Vec2 cursor = pos;
float lineHeight = f->ascent - f->descent;
float lineWidth = 0;
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, colorf color, float wrap, TEXT_BREAK breakAt, TEXT_ALIGN align) {
text_vert *buffer = NULL;
int breakAtWord = (breakAt == WORD);
float lineHeight = f->ascent - f->descent;
float spaceWidth = f->Characters[' '].advance;
// First pass: calculate line breaks and widths
typedef struct {
const char *start;
const char *end;
float width;
} Line;
Line *lines = NULL;
const char *lineStart = text;
const char *wordStart = text;
float lineWidth = 0;
float wordWidth = 0;
for (const char *c = text; *c != '\0'; c++) {
if (*c == '\n') {
Line line = {lineStart, c, lineWidth};
arrput(lines, line);
lineStart = c + 1;
wordStart = c + 1;
lineWidth = 0;
wordWidth = 0;
continue;
}
for (const char *c = text; *c != 0; c++) {
if (*c == '\n') {
cursor.x = pos.x;
cursor.y -= lineHeight + f->linegap;
lineWidth = 0;
continue;
float charWidth = f->Characters[(unsigned char)*c].advance;
if (wrap > 0 && lineWidth + charWidth > wrap) {
const char *breakPoint = c;
float breakWidth = lineWidth;
if (breakAtWord && *c != ' ' && wordWidth > 0 && wordStart > lineStart) {
breakPoint = wordStart;
breakWidth = lineWidth - wordWidth;
}
Line line = {lineStart, breakPoint, breakWidth};
arrput(lines, line);
lineStart = breakPoint;
if (breakAtWord && *lineStart == ' ') {
lineStart++;
}
c = lineStart - 1;
wordStart = lineStart;
lineWidth = 0;
wordWidth = 0;
continue;
}
lineWidth += charWidth;
if (breakAtWord) {
if (*c == ' ') {
wordWidth = 0;
wordStart = c + 1;
} else {
wordWidth += charWidth;
}
}
}
struct character chara = f->Characters[(unsigned char)*c];
if (wrap > 0 && lineWidth + chara.advance > wrap) {
cursor.x = pos.x;
cursor.y -= lineHeight + f->linegap;
lineWidth = 0;
// Add final line
if (lineStart < text + strlen(text)) {
Line line = {lineStart, text + strlen(text), lineWidth};
arrput(lines, line);
}
if (!isspace(*c))
draw_char_verts(&buffer, chara, cursor, color);
lineWidth += chara.advance;
cursor.x += chara.advance;
}
return buffer;
// Second pass: render with alignment
HMM_Vec2 cursor = pos;
for (int i = 0; i < arrlen(lines); i++) {
Line *line = &lines[i];
float startX = pos.x;
float extraSpace = 0;
int spaceCount = 0;
// Calculate alignment offset
if (wrap > 0) {
switch (align) {
case RIGHT:
startX = pos.x + wrap - line->width;
break;
case CENTER:
startX = pos.x + (wrap - line->width) / 2.0f;
break;
case JUSTIFY:
if (i < arrlen(lines) - 1 && *(line->end) != '\n') {
for (const char *c = line->start; c < line->end; c++) {
if (*c == ' ') spaceCount++;
}
if (spaceCount > 0) {
extraSpace = (wrap - line->width) / spaceCount;
}
}
break;
case LEFT:
default:
break;
}
}
cursor.x = startX;
for (const char *c = line->start; c < line->end; c++) {
struct character chara = f->Characters[(unsigned char)*c];
if (!isspace(*c)) {
draw_char_verts(&buffer, chara, cursor, color);
}
cursor.x += chara.advance;
if (align == JUSTIFY && *c == ' ') {
cursor.x += extraSpace;
}
}
cursor.x = pos.x;
cursor.y -= lineHeight + f->linegap;
}
arrfree(lines);
return buffer;
}

View File

@@ -12,7 +12,12 @@ typedef enum {
RIGHT,
CENTER,
JUSTIFY
} ALIGN;
} TEXT_ALIGN;
typedef enum {
WORD,
CHARACTER
} TEXT_BREAK;
struct text_vert {
HMM_Vec2 pos;
@@ -57,7 +62,7 @@ typedef struct Character glyph;
void font_free(JSRuntime *rt,font *f);
struct sFont *MakeFont(void *data, size_t len, int height);
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, colorf color, float wrap);
HMM_Vec2 measure_text(const char *text, font *f, float letterSpacing, float wrap);
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, colorf color, float wrap, TEXT_BREAK breakAt, TEXT_ALIGN align);
HMM_Vec2 measure_text(const char *text, font *f, float letterSpacing, float wrap, TEXT_BREAK breakAt, TEXT_ALIGN align);
#endif

View File

@@ -757,8 +757,7 @@ JSC_CCALL(input_get_events,
int event_count = 0;
while (SDL_PollEvent(&event)) {
// Process event with ImGui first
// gui_input(&event);
gui_input(&event);
WotaBuffer wb = event2wota(&event);
JSValue event_obj = wota2value(js, wb.data);

View File

@@ -68,9 +68,15 @@ JSC_CCALL(staef_font_text_size,
const char *str = JS_ToCString(js, argv[0]);
float letterSpacing = argc > 1 ? js2number(js, argv[1]) : 0;
float wrap = argc > 2 ? js2number(js, argv[2]) : -1;
const char *breakat = argc > 3 ? JS_ToCString(js, argv[3]) : NULL;
int breakAt = (breakat == "character") ? CHARACTER : WORD;
const char *align = argc > 4 ? JS_ToCString(js, argv[4]) : NULL;
int alignAt = (align == "right") ? RIGHT : (align == "center") ? CENTER : (align == "justify") ? JUSTIFY : LEFT;
ret = vec22js(js, measure_text(str, f, letterSpacing, wrap));
ret = vec22js(js, measure_text(str, f, letterSpacing, wrap, breakAt, alignAt));
JS_FreeCString(js, str);
if (breakat) JS_FreeCString(js, breakat);
if (align) JS_FreeCString(js, align);
)
// Generate text buffer (mesh data)
@@ -80,11 +86,17 @@ JSC_CCALL(staef_font_make_text_buffer,
rect rectpos = js2rect(js, argv[1]);
colorf c = js2color(js, argv[2]);
float wrap = argc > 3 ? js2number(js, argv[3]) : -1;
const char *breakat = argc > 4 ? JS_ToCString(js, argv[4]) : NULL;
int breakAt = (breakat == "character") ? CHARACTER : WORD;
const char *align = argc > 5 ? JS_ToCString(js, argv[5]) : NULL;
int alignAt = (align == "right") ? RIGHT : (align == "center") ? CENTER : (align == "justify") ? JUSTIFY : LEFT;
HMM_Vec2 startpos = {.x = rectpos.x, .y = rectpos.y };
text_vert *buffer = renderText(s, startpos, f, c, wrap);
text_vert *buffer = renderText(s, startpos, f, c, wrap, breakAt, alignAt);
ret = quads_to_mesh(js, buffer);
JS_FreeCString(js, s);
if (breakat) JS_FreeCString(js, breakat);
if (align) JS_FreeCString(js, align);
arrfree(buffer);
)
@@ -93,8 +105,8 @@ JSC_GETSET(font, linegap, number)
// Font methods
static const JSCFunctionListEntry js_font_funcs[] = {
MIST_FUNC_DEF(staef_font, text_size, 3),
MIST_FUNC_DEF(staef_font, make_text_buffer, 4),
MIST_FUNC_DEF(staef_font, text_size, 4),
MIST_FUNC_DEF(staef_font, make_text_buffer, 6),
CGETSET_ADD(font, linegap),
};