text wrap
This commit is contained in:
194
source/font.c
194
source/font.c
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user