Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,24 @@ float iui_text_width_vec(const char *text, float font_height)
return w;
}

/* Codepoint width measurement for built-in vector font.
* Returns the width of a Unicode codepoint at the given font height.
* Codepoints outside ASCII range (0x20-0x7E) use the fallback box glyph.
*/
float iui_codepoint_width_vec(uint32_t cp, float font_height)
{
if (font_height <= 0.f)
return 0.f;

float scale = font_height / IUI_FONT_UNITS_PER_EM;
const float pen_w = iui_vector_pen_for_height(font_height);
const float side = iui_vector_side_bearing(scale, pen_w);
/* iui_get_glyph handles out-of-range codepoints by returning box glyph */
const signed char *g = iui_get_glyph((unsigned char) (cp > 0x7F ? 0 : cp));
float glyph_w = (float) (IUI_GLYPH_RIGHT(g) - IUI_GLYPH_LEFT(g)) * scale;
return glyph_w + side * 2.f;
}

/* Emit vector commands for drawing a glyph.
* @ctx: Current UI context
* @g: Pointer to glyph data
Expand Down
36 changes: 36 additions & 0 deletions src/draw.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,42 @@ float iui_get_text_width(iui_context *ctx, const char *text)
return width;
}

/* Get width of a Unicode codepoint. Uses renderer callback if available,
* otherwise falls back to built-in vector font.
*
* Note: For custom renderers, this measures single codepoints independently.
* Fonts with kerning may show slight cursor drift vs rendered text. For the
* built-in vector font (no kerning), width accumulation is exact.
*/
float iui_get_codepoint_width(iui_context *ctx, uint32_t cp)
{
/* Clamp invalid codepoints to replacement character */
if (cp > 0x10FFFF)
cp = 0xFFFD;

if (ctx->renderer.text_width) {
/* Encode codepoint to UTF-8 for renderer callback */
char tmp[5] = {0};
if (cp < 0x80) {
tmp[0] = (char) cp;
} else if (cp < 0x800) {
tmp[0] = (char) (0xC0 | (cp >> 6));
tmp[1] = (char) (0x80 | (cp & 0x3F));
} else if (cp < 0x10000) {
tmp[0] = (char) (0xE0 | (cp >> 12));
tmp[1] = (char) (0x80 | ((cp >> 6) & 0x3F));
tmp[2] = (char) (0x80 | (cp & 0x3F));
} else {
tmp[0] = (char) (0xF0 | (cp >> 18));
tmp[1] = (char) (0x80 | ((cp >> 12) & 0x3F));
tmp[2] = (char) (0x80 | ((cp >> 6) & 0x3F));
tmp[3] = (char) (0x80 | (cp & 0x3F));
}
return ctx->renderer.text_width(tmp, ctx->renderer.user);
}
return iui_codepoint_width_vec(cp, ctx->font_height);
}

void iui_internal_draw_text(iui_context *ctx,
float x,
float y,
Expand Down
28 changes: 18 additions & 10 deletions src/input.c
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ iui_textfield_result iui_textfield(iui_context *ctx,
* @text_x_start: Starting x position of text
* @click_x: X coordinate of mouse click
*
* Returns position in buffer closest to click_x.
* Returns byte position in buffer closest to click_x.
* Uses incremental width computation for O(n) complexity.
* Iterates by UTF-8 codepoint to ensure cursor lands on valid boundaries.
*/
static size_t iui_find_cursor_from_x(iui_context *ctx,
const char *buffer,
Expand All @@ -295,20 +297,26 @@ static size_t iui_find_cursor_from_x(iui_context *ctx,
if (len == 0)
return 0;

/* Binary search would be ideal, but linear scan is fine for typical lengths
*/
float best_dist = fabsf(click_x - text_x_start);
float cumulative_x = text_x_start;
size_t pos = 0;

char tmp[IUI_STRING_BUFFER_SIZE];
for (size_t i = 1; i <= len && i < sizeof(tmp); i++) {
memcpy(tmp, buffer, i);
tmp[i] = '\0';
float char_x = text_x_start + iui_get_text_width(ctx, tmp);
float dist = fabsf(click_x - char_x);
while (pos < len) {
/* Decode codepoint at current position */
uint32_t cp = iui_utf8_decode(buffer, pos, len);
size_t next_pos = iui_utf8_next(buffer, pos, len);

/* Accumulate width for this codepoint */
cumulative_x += iui_get_codepoint_width(ctx, cp);

/* Check if this boundary is closer to click position */
float dist = fabsf(click_x - cumulative_x);
if (dist < best_dist) {
best_dist = dist;
best_pos = i;
best_pos = next_pos;
}

pos = next_pos;
}
return best_pos;
}
Expand Down
2 changes: 2 additions & 0 deletions src/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,7 @@ static inline bool iui_process_text_input(iui_context *ctx,

/* Text rendering helpers (implemented in iui_draw.c) */
float iui_get_text_width(iui_context *ctx, const char *text);
float iui_get_codepoint_width(iui_context *ctx, uint32_t cp);
void iui_internal_draw_text(iui_context *ctx,
float x,
float y,
Expand Down Expand Up @@ -1009,6 +1010,7 @@ void iui_compute_vector_metrics(float font_height,
float *out_ascent_px,
float *out_descent_px);
float iui_vector_pen_for_height(float font_height);
float iui_codepoint_width_vec(uint32_t cp, float font_height);

/* Theme globals (defined in iui_core.c) */
extern const iui_theme_t g_theme_light;
Expand Down
Loading