attempting to fix text wrapping ...
This commit is contained in:
@@ -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})
|
||||
|
||||
@@ -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,
|
||||
|
||||
345
source/font.c
345
source/font.c
@@ -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;
|
||||
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
|
||||
|
||||
const char *wordStart = text;
|
||||
float wordWidth = 0;
|
||||
float spaceWidth = f->Characters[' '].advance + letterSpacing;
|
||||
const char *word_start = text;
|
||||
float word_width = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
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 lineHeight = f->ascent - f->descent;
|
||||
float spaceWidth = f->Characters[' '].advance;
|
||||
typedef struct { const char *start, *end; float width; } line_t;
|
||||
line_t *lines = NULL;
|
||||
|
||||
// First pass: calculate line breaks and widths
|
||||
typedef struct {
|
||||
const char *start;
|
||||
const char *end;
|
||||
float width;
|
||||
} Line;
|
||||
const char *line_start = text, *word_start = text;
|
||||
float line_width = 0, word_width = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
line_width += char_w;
|
||||
|
||||
for (int i = 0; i < arrlen(lines); i++) {
|
||||
Line *line = &lines[i];
|
||||
float startX = pos.x;
|
||||
float extraSpace = 0;
|
||||
int spaceCount = 0;
|
||||
if (break_at_word) {
|
||||
if (*p == ' ') {
|
||||
word_width = 0;
|
||||
word_start = p + 1;
|
||||
} else {
|
||||
word_width += char_w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
if (line_start < text + strlen(text)) {
|
||||
line_t L = { line_start, text + strlen(text), line_width };
|
||||
arrput(lines, L);
|
||||
}
|
||||
|
||||
cursor.x = startX;
|
||||
printf("renderText: Number of lines: %d\n", arrlen(lines));
|
||||
|
||||
for (const char *c = line->start; c < line->end; c++) {
|
||||
struct character chara = f->Characters[(unsigned char)*c];
|
||||
HMM_Vec2 cursor = pos;
|
||||
|
||||
if (!isspace(*c)) {
|
||||
draw_char_verts(&buffer, chara, cursor, color);
|
||||
}
|
||||
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;
|
||||
|
||||
cursor.x += chara.advance;
|
||||
|
||||
if (align == JUSTIFY && *c == ' ') {
|
||||
cursor.x += extraSpace;
|
||||
}
|
||||
}
|
||||
|
||||
cursor.x = pos.x;
|
||||
cursor.y -= lineHeight + f->linegap;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
arrfree(lines);
|
||||
return buffer;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user