diff --git a/src/core.c b/src/core.c index 1991bfb..7c62be9 100644 --- a/src/core.c +++ b/src/core.c @@ -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 diff --git a/src/draw.c b/src/draw.c index de74cb5..171776a 100644 --- a/src/draw.c +++ b/src/draw.c @@ -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, diff --git a/src/input.c b/src/input.c index 818af60..6edebc8 100644 --- a/src/input.c +++ b/src/input.c @@ -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, @@ -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; } diff --git a/src/internal.h b/src/internal.h index fa2a901..6e67bb6 100644 --- a/src/internal.h +++ b/src/internal.h @@ -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, @@ -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;