attempting to fix text wrapping ...

This commit is contained in:
2025-11-06 14:47:36 -06:00
parent 6961e19114
commit c02b42f710
5 changed files with 183 additions and 206 deletions

View File

@@ -248,7 +248,10 @@ clay.text = function text(str, ...configs)
var config = rectify_configs(configs);
config.size ??= [0,0];
config.font = graphics.get_font(config.font)
var tsize = config.font.text_size(str, 0, config.size.x, config.text_break, config.text_align);
config.text = str
var tsize = config.font.text_size(str, 0, config.size[0], config.text_break, config.text_align);
log.console(json.encode(config))
log.console(json.encode(tsize))
tsize.x = Math.ceil(tsize.x)
tsize.y = Math.ceil(tsize.y)
config.size = config.size.map((x,i) => Math.max(x, tsize[i]));
@@ -278,18 +281,6 @@ clay.button = function button(str, action, config = {})
config.action = action;
}
clay.textbox = function(str, on_change, ...configs) {
var config = rectify_configs(configs)
config.on_change = on_change
config.text = str
config.font = graphics.get_font(config.font)
var tsize = config.font.text_size(str, 0, 0, config.text_break, config.text_align)
config.size ??= [0,0]
config.size = [Math.ceil(tsize.x), Math.ceil(tsize.y)]
config.size = [Math.max(config.size[0], config.size[0]), Math.max(config.size[1], config.size[1])]
add_item(config)
}
var point = use('point')
clay.draw_commands = function draw_commands(tree_root, pos = {x:0,y:0})

View File

@@ -723,6 +723,7 @@ cmd_fns.draw_text = function(cmd)
cmd.pos.width ??= size[0]
cmd.pos.height ??= size[1]
log.console(json.encode(cmd))
var mesh = font.make_text_buffer(
cmd.text,
cmd.pos,

View File

@@ -18,6 +18,7 @@ struct sFont *use_font;
void font_free(JSRuntime *rt, font *f)
{
if (f->surface) SDL_DestroySurface(f->surface);
free(f);
}
@@ -50,10 +51,11 @@ struct sFont *MakeFont(void *ttf_buffer, size_t len, int height) {
int ascent, descent, linegap;
stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &linegap);
float emscale = stbtt_ScaleForMappingEmToPixels(&fontinfo, height);
newfont->ascent = ascent*emscale;
newfont->descent = descent*emscale;
newfont->linegap = linegap*emscale;
float s = stbtt_ScaleForPixelHeight(&fontinfo, height);
newfont->ascent = ascent * s;
newfont->descent = descent * s; /* descent is negative */
newfont->linegap = linegap * s;
newfont->line_height = (newfont->ascent - newfont->descent) + newfont->linegap;
newfont->surface = SDL_CreateSurface(packsize,packsize, SDL_PIXELFORMAT_RGBA32);
if (!newfont->surface) printf("SDL ERROR: %s\n", SDL_GetError());
for (int i = 0; i < packsize; i++)
@@ -160,204 +162,187 @@ const char *esc_color(const char *c, struct rgba *color, struct rgba defc)
}
// 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)
HMM_Vec2 measure_text(const char *text, font *f, float letter_spacing, float wrap, TEXT_BREAK break_at, TEXT_ALIGN align)
{
int breakAtWord = (breakAt == WORD);
HMM_Vec2 dim = {0};
float maxWidth = 0;
float lineWidth = 0;
float lineHeight = f->ascent - f->descent;
float height = lineHeight;
const char *wordStart = text;
float wordWidth = 0;
float spaceWidth = f->Characters[' '].advance + letterSpacing;
int break_at_word = (break_at == WORD);
float lh = f->line_height;
float height = lh;
float max_width = 0, line_width = 0;
int line_count = 1; // Start with 1 line
for (const char *c = text; *c != '\0'; ) {
if (*c == '\n') {
maxWidth = fmaxf(maxWidth, lineWidth);
lineWidth = 0;
height += lineHeight + f->linegap;
wordStart = c + 1;
wordWidth = 0;
c++;
continue;
}
const char *word_start = text;
float word_width = 0;
float charWidth = f->Characters[(unsigned char)*c].advance + letterSpacing;
if (wrap > 0 && lineWidth + charWidth > wrap) {
if (breakAtWord && *c != ' ' && wordWidth > 0 && wordWidth <= wrap) {
// Backtrack to word start only if the word would fit on a new line
lineWidth -= wordWidth;
c = wordStart;
} else {
// Character-based wrapping or word too long to fit
maxWidth = fmaxf(maxWidth, lineWidth);
lineWidth = 0;
height += lineHeight + f->linegap;
wordStart = c;
wordWidth = 0;
if (!breakAtWord || *c == ' ') {
c++;
continue;
}
// For word-based wrapping with long words, don't advance c here - let it be processed in this line
}
} else {
c++;
}
lineWidth += charWidth;
if (breakAtWord) {
if (*c == ' ') {
wordWidth = 0;
wordStart = c + 1;
} else {
wordWidth += charWidth;
}
}
for (const char *p = text; *p; ) {
if (*p == '\n') {
if (line_width > max_width) max_width = line_width;
line_width = 0;
height += lh;
line_count++;
word_start = p + 1;
word_width = 0;
p++;
continue;
}
maxWidth = fmaxf(maxWidth, lineWidth);
dim.x = maxWidth;
dim.y = height;
unsigned char ch = (unsigned char)*p;
float adv = f->Characters[ch].advance;
/* add letter spacing unless this will be end-of-line char; unknown yet, so include it here;
wrap logic below uses same width */
float char_w = adv + letter_spacing;
return dim;
if (wrap > 0 && line_width + char_w > wrap) {
if (break_at_word && *p != ' ' && word_width > 0 && word_width <= wrap) {
line_width -= word_width;
p = word_start;
} else {
if (line_width > max_width) max_width = line_width;
line_width = 0;
height += lh;
line_count++;
word_start = p;
word_width = 0;
/* if not word-breaking or current is space, consume it here */
if (!break_at_word || *p == ' ') { p++; }
continue;
}
} else {
p++;
}
line_width += char_w;
if (break_at_word) {
if (*p == ' ') {
word_width = 0;
word_start = p + 1;
} else {
word_width += char_w;
}
}
}
if (line_width > max_width) max_width = line_width;
printf("measure_text: Number of lines: %d\n", line_count);
HMM_Vec2 dim = { max_width, 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_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;
}
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, colorf color,
float wrap, TEXT_BREAK break_at, TEXT_ALIGN align,
float letter_spacing)
{
text_vert *buffer = NULL;
int break_at_word = (break_at == WORD);
float lh = f->line_height;
float charWidth = f->Characters[(unsigned char)*c].advance;
typedef struct { const char *start, *end; float width; } line_t;
line_t *lines = NULL;
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;
}
const char *line_start = text, *word_start = text;
float line_width = 0, word_width = 0;
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;
}
}
for (const char *p = text; *p; p++) {
if (*p == '\n') {
line_t L = { line_start, p, line_width };
arrput(lines, L);
line_start = p + 1;
word_start = p + 1;
line_width = 0;
word_width = 0;
continue;
}
// Add final line
if (lineStart < text + strlen(text)) {
Line line = {lineStart, text + strlen(text), lineWidth};
arrput(lines, line);
unsigned char ch = (unsigned char)*p;
float adv = f->Characters[ch].advance;
float char_w = adv + letter_spacing;
if (wrap > 0 && line_width + char_w > wrap) {
const char *break_pt = p;
float break_w = line_width;
if (break_at_word && *p != ' ' && word_width > 0 && word_start > line_start) {
break_pt = word_start;
break_w = line_width - word_width;
}
line_t L = { line_start, break_pt, break_w };
arrput(lines, L);
line_start = break_pt;
if (break_at_word && *line_start == ' ') line_start++;
p = line_start - 1;
word_start = line_start;
line_width = 0;
word_width = 0;
continue;
}
// 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;
line_width += char_w;
if (break_at_word) {
if (*p == ' ') {
word_width = 0;
word_start = p + 1;
} else {
word_width += char_w;
}
}
arrfree(lines);
return buffer;
}
if (line_start < text + strlen(text)) {
line_t L = { line_start, text + strlen(text), line_width };
arrput(lines, L);
}
printf("renderText: Number of lines: %d\n", arrlen(lines));
HMM_Vec2 cursor = pos;
for (int i = 0; i < arrlen(lines); i++) {
line_t *L = &lines[i];
float start_x = pos.x;
float extra_space = 0;
int space_count = 0;
if (wrap > 0) {
switch (align) {
case RIGHT: start_x = pos.x + wrap - L->width; break;
case CENTER: start_x = pos.x + (wrap - L->width) * 0.5f; break;
case JUSTIFY:
if (i < arrlen(lines) - 1 && *(L->end) != '\n') {
for (const char *p = L->start; p < L->end; p++) if (*p == ' ') space_count++;
if (space_count > 0) extra_space = (wrap - L->width) / space_count;
}
break;
case LEFT: default: break;
}
}
cursor.x = start_x;
const char *p = L->start;
while (p < L->end) {
unsigned char ch = (unsigned char)*p;
struct character g = f->Characters[ch];
if (!isspace(*p)) draw_char_verts(&buffer, g, cursor, color);
cursor.x += g.advance;
/* add letter spacing unless this is last char of the line */
if (p + 1 < L->end) cursor.x += letter_spacing;
if (align == JUSTIFY && *p == ' ') cursor.x += extra_space;
p++;
}
cursor.x = pos.x;
cursor.y -= lh;
}
arrfree(lines);
return buffer;
}

View File

@@ -52,6 +52,7 @@ struct sFont {
float ascent; // pixels
float descent; // pixels
float linegap; //pixels
float line_height; // pixels
struct character Characters[256];
SDL_Surface *surface;
};
@@ -62,7 +63,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, TEXT_BREAK breakAt, TEXT_ALIGN align);
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, colorf color, float wrap, TEXT_BREAK breakAt, TEXT_ALIGN align, float letter_spacing);
HMM_Vec2 measure_text(const char *text, font *f, float letterSpacing, float wrap, TEXT_BREAK breakAt, TEXT_ALIGN align);
#endif

View File

@@ -72,7 +72,6 @@ JSC_CCALL(staef_font_text_size,
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, breakAt, alignAt));
JS_FreeCString(js, str);
if (breakat) JS_FreeCString(js, breakat);
@@ -92,7 +91,7 @@ JSC_CCALL(staef_font_make_text_buffer,
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, breakAt, alignAt);
text_vert *buffer = renderText(s, startpos, f, c, wrap, breakAt, alignAt, 0);
ret = quads_to_mesh(js, buffer);
JS_FreeCString(js, s);
if (breakat) JS_FreeCString(js, breakat);
@@ -105,7 +104,7 @@ JSC_GETSET(font, linegap, number)
// Font methods
static const JSCFunctionListEntry js_font_funcs[] = {
MIST_FUNC_DEF(staef_font, text_size, 4),
MIST_FUNC_DEF(staef_font, text_size, 5),
MIST_FUNC_DEF(staef_font, make_text_buffer, 6),
CGETSET_ADD(font, linegap),
};