fix(input): update boredword, browser, grapher and libwidget to support UTF8

This commit is contained in:
boreddevnl 2026-04-23 21:59:42 +02:00
parent 8006a83449
commit 81ea21e746
9 changed files with 477 additions and 202 deletions

View file

@ -7,6 +7,7 @@
#include "libc/libui.h"
#include "libc/stdlib.h"
#include "../../wm/libwidget.h"
#include "utf-8.h"
#include <stddef.h>
#define COLOR_DARK_PANEL 0xFF202020
@ -243,23 +244,24 @@ static void update_formatting_state(void) {
}
}
static void handle_arrows(ui_window_t win, char c) {
if (c == 17) {
static void handle_arrows(ui_window_t win, uint32_t c) {
if (c == 17) { // UP
if (cursor_para > 0) {
cursor_para--;
cursor_run = paragraphs[cursor_para].run_count - 1;
if (cursor_run < 0) cursor_run = 0;
cursor_pos = paragraphs[cursor_para].runs[cursor_run].len;
}
} else if (c == 18) {
} else if (c == 18) { // DOWN
if (cursor_para < para_count - 1) {
cursor_para++;
cursor_run = 0;
cursor_pos = 0;
}
} else if (c == 19) {
} else if (c == 19) { // LEFT
if (cursor_pos > 0) {
cursor_pos--;
const char *prev = text_prev_utf8(paragraphs[cursor_para].runs[cursor_run].text, paragraphs[cursor_para].runs[cursor_run].text + cursor_pos);
cursor_pos = (int)(prev - paragraphs[cursor_para].runs[cursor_run].text);
} else if (cursor_run > 0) {
cursor_run--;
cursor_pos = paragraphs[cursor_para].runs[cursor_run].len;
@ -269,9 +271,10 @@ static void handle_arrows(ui_window_t win, char c) {
if (cursor_run < 0) cursor_run = 0;
cursor_pos = paragraphs[cursor_para].runs[cursor_run].len;
}
} else if (c == 20) {
} else if (c == 20) { // RIGHT
if (cursor_pos < paragraphs[cursor_para].runs[cursor_run].len) {
cursor_pos++;
const char *next = text_next_utf8(paragraphs[cursor_para].runs[cursor_run].text + cursor_pos);
cursor_pos = (int)(next - paragraphs[cursor_para].runs[cursor_run].text);
} else if (cursor_run < paragraphs[cursor_para].run_count - 1) {
cursor_run++;
cursor_pos = 0;
@ -357,7 +360,7 @@ static void delete_selection(void) {
file_modified = 1;
}
static void insert_char(ui_window_t win, char c) {
static void insert_char(ui_window_t win, uint32_t c, int legacy) {
_Bool has_selection = 0;
if (sel_start_para != -1 && sel_end_para != -1) {
if (!(sel_start_para == sel_end_para && sel_start_run == sel_end_run && sel_start_pos == sel_end_pos)) {
@ -367,7 +370,7 @@ static void insert_char(ui_window_t win, char c) {
if (has_selection) {
delete_selection();
if (c == '\b') return;
if (legacy == '\b' || legacy == 127) return;
} else {
if (sel_start_para != -1) {
sel_start_para = -1;
@ -375,20 +378,23 @@ static void insert_char(ui_window_t win, char c) {
}
}
if (c < 32 && c != '\n' && c != '\b') {
if (c >= 17 && c <= 20) {
handle_arrows(win, c);
}
// Handle special legacy keys
if (legacy >= 17 && legacy <= 20) {
handle_arrows(win, legacy);
return;
}
if (c == '\b') {
if (legacy == '\b') {
save_undo_state();
if (cursor_pos > 0) {
TextRun *r = &paragraphs[cursor_para].runs[cursor_run];
for(int i=cursor_pos-1; i<r->len; i++) r->text[i] = r->text[i+1];
r->len--;
cursor_pos--;
const char *prev = text_prev_utf8(r->text, r->text + cursor_pos);
int char_len = (int)((r->text + cursor_pos) - prev);
for(int i=cursor_pos-char_len; i<r->len-char_len; i++) r->text[i] = r->text[i+char_len];
r->len -= char_len;
cursor_pos -= char_len;
r->text[r->len] = 0;
if (r->len == 0 && paragraphs[cursor_para].run_count > 1) {
for(int i = cursor_run; i < paragraphs[cursor_para].run_count - 1; i++) {
@ -411,7 +417,10 @@ static void insert_char(ui_window_t win, char c) {
if (cursor_run < 0) cursor_run = 0;
TextRun *r = &paragraphs[cursor_para].runs[cursor_run];
if (r->len > 0) {
r->len--;
const char *prev = text_prev_utf8(r->text, r->text + r->len);
int char_len = (int)((r->text + r->len) - prev);
r->len -= char_len;
r->text[r->len] = 0;
cursor_pos = r->len;
if (r->len == 0 && paragraphs[cursor_para].run_count > 1) {
@ -453,7 +462,7 @@ static void insert_char(ui_window_t win, char c) {
return;
}
if (c == '\n') {
if (legacy == '\n') {
save_undo_state();
if (para_count >= MAX_PARAGRAPHS) return;
for(int i=para_count; i>cursor_para+1; i--) paragraphs[i] = paragraphs[i-1];
@ -519,14 +528,18 @@ static void insert_char(ui_window_t win, char c) {
r->color = current_text_color;
}
if (c == ' ') save_undo_state();
if (legacy == ' ') save_undo_state();
if (r->len < MAX_RUN_TEXT - 1) {
for(int i=r->len; i>cursor_pos; i--) r->text[i] = r->text[i-1];
r->text[cursor_pos] = c;
r->len++;
cursor_pos++;
file_modified = 1;
char utf8[4];
if (c >= 32 && c != 127) {
int clen = text_encode_utf8(c, utf8);
if (clen > 0 && r->len + clen < MAX_RUN_TEXT) {
for(int i=r->len; i>cursor_pos; i--) r->text[i + clen - 1] = r->text[i-1];
for(int i=0; i<clen; i++) r->text[cursor_pos + i] = utf8[i];
r->len += clen;
cursor_pos += clen;
file_modified = 1;
}
}
ensure_cursor_visible(win);
}
@ -945,12 +958,14 @@ static void load_file(ui_window_t win, const char* path) {
i++;
while(i < size && buf[i] != ')') {
if (buf[i] == '\\' && i+1 < size) i++;
insert_char(win, buf[i]);
i++;
int adv;
uint32_t cp = text_decode_utf8(buf + i, &adv);
insert_char(win, cp, (int)buf[i]);
i += adv;
}
}
else if ((buf[i] == 'T' && buf[i+1] == 'd') || (buf[i] == 'T' && buf[i+1] == 'm')) {
insert_char(win, '\n');
insert_char(win, '\n', '\n');
i += 2;
}
else {
@ -958,8 +973,15 @@ static void load_file(ui_window_t win, const char* path) {
}
}
} else {
for(int i=0; i<size; i++) {
if (buf[i] != '\r') insert_char(win, buf[i]);
for(int i=0; i<size; ) {
if (buf[i] != '\r') {
int adv;
uint32_t cp = text_decode_utf8(buf + i, &adv);
insert_char(win, cp, (int)buf[i]);
i += adv;
} else {
i++;
}
}
}
file_modified = 0;
@ -1171,7 +1193,12 @@ static void draw_document(ui_window_t win) {
if (fh > max_h) max_h = fh;
while(c_idx < run->len) {
char buf[2] = {run->text[c_idx], 0};
int adv;
text_decode_utf8(run->text + c_idx, &adv);
char buf[5];
for (int k = 0; k < adv; k++) buf[k] = run->text[c_idx + k];
buf[adv] = 0;
int cw = ui_get_string_width_scaled(buf, run->font_size);
if (run->text[c_idx] == ' ') {
@ -1184,7 +1211,7 @@ static void draw_document(ui_window_t win) {
break;
}
line_w += cw;
c_idx++;
c_idx += adv;
}
if (c_idx < run->len) break;
@ -1242,9 +1269,11 @@ static void draw_document(ui_window_t win) {
else chars_to_draw = run->len - d_char;
if (p == cursor_para && d_run == cursor_run && cursor_pos >= d_char && cursor_pos <= d_char + chars_to_draw) {
char sub[128];
for(int i=0; i<cursor_pos - d_char; i++) sub[i] = run->text[d_char + i];
sub[cursor_pos - d_char] = 0;
char sub[512];
int len_before = cursor_pos - d_char;
if (len_before > 511) len_before = 511;
for(int i=0; i<len_before; i++) sub[i] = run->text[d_char + i];
sub[len_before] = 0;
line_cursor_x = cur_x + ui_get_string_width_scaled(sub, run->font_size);
}
@ -1257,9 +1286,15 @@ static void draw_document(ui_window_t win) {
int text_y = cur_y + y_offset;
if (text_y + run_h > 40 && text_y < win_h) {
for(int i=0; i<chars_to_draw; i++) {
char buf[2] = {run->text[d_char + i], 0};
_Bool in_sel = is_in_selection(p, d_run, d_char + i);
int c_offset_local = 0;
while (c_offset_local < chars_to_draw) {
int adv;
text_decode_utf8(run->text + d_char + c_offset_local, &adv);
char buf[5];
for (int k = 0; k < adv; k++) buf[k] = run->text[d_char + c_offset_local + k];
buf[adv] = 0;
_Bool in_sel = is_in_selection(p, d_run, d_char + c_offset_local);
uint32_t text_col = in_sel ? COLOR_WHITE : run->color;
int cw = ui_get_string_width_scaled(buf, run->font_size);
@ -1281,11 +1316,14 @@ static void draw_document(ui_window_t win) {
}
}
cur_x += cw;
c_offset_local += adv;
}
} else {
char buf[128];
for(int i=0; i<chars_to_draw; i++) buf[i] = run->text[d_char + i];
buf[chars_to_draw] = 0;
char buf[512];
int len_to_copy = chars_to_draw;
if (len_to_copy > 511) len_to_copy = 511;
for(int i=0; i<len_to_copy; i++) buf[i] = run->text[d_char + i];
buf[len_to_copy] = 0;
cur_x += ui_get_string_width_scaled(buf, run->font_size);
}
}
@ -1367,12 +1405,17 @@ static void ensure_cursor_visible(ui_window_t win) {
if (fh > max_h) max_h = fh;
while(c_idx < run->len) {
char buf[2] = {run->text[c_idx], 0};
int adv;
text_decode_utf8(run->text + c_idx, &adv);
char buf[5];
for (int k = 0; k < adv; k++) buf[k] = run->text[c_idx + k];
buf[adv] = 0;
int cw = ui_get_string_width_scaled(buf, run->font_size);
if (run->text[c_idx] == ' ') { last_space_run = r_idx; last_space_char = c_idx; last_space_w = line_w + cw; }
if (line_w + cw > page_w - 20) break;
line_w += cw;
c_idx++;
c_idx += adv;
}
if (c_idx < run->len) break;
r_idx++; c_idx = 0;
@ -1692,12 +1735,17 @@ static void handle_click(ui_window_t win, int x, int y) {
int fh = ui_get_font_height_scaled(run->font_size);
if (fh > max_h) max_h = fh;
while(c_idx < run->len) {
char buf[2] = {run->text[c_idx], 0};
int adv;
text_decode_utf8(run->text + c_idx, &adv);
char buf[5];
for (int k = 0; k < adv; k++) buf[k] = run->text[c_idx + k];
buf[adv] = 0;
int cw = ui_get_string_width_scaled(buf, run->font_size);
if (run->text[c_idx] == ' ') { last_space_run = r_idx; last_space_char = c_idx; last_space_w = line_w + cw; }
if (line_w + cw > page_w - 20) break;
line_w += cw;
c_idx++;
c_idx += adv;
}
if (c_idx < run->len) break;
r_idx++; c_idx = 0;
@ -1731,31 +1779,38 @@ static void handle_click(ui_window_t win, int x, int y) {
TextRun *run = &para->runs[d_run];
set_active_font(win, run->font_idx);
int chars_to_draw = (d_run == end_run) ? (end_char - d_char) : (run->len - d_char);
for(int i=0; i<chars_to_draw; i++) {
char buf[2] = {run->text[d_char + i], 0};
int cur_c_offset = 0;
while(cur_c_offset < chars_to_draw) {
int adv;
text_decode_utf8(run->text + d_char + cur_c_offset, &adv);
char buf[5];
for (int k = 0; k < adv; k++) buf[k] = run->text[d_char + cur_c_offset + k];
buf[adv] = 0;
int cw = ui_get_string_width_scaled(buf, run->font_size);
if (target_x >= cur_x && target_x < cur_x + cw/2) {
cursor_para = p; cursor_run = d_run; cursor_pos = d_char + i;
cursor_para = p; cursor_run = d_run; cursor_pos = d_char + cur_c_offset;
if (selection_started) {
sel_start_para = p; sel_start_run = d_run; sel_start_pos = d_char + i;
sel_start_para = p; sel_start_run = d_run; sel_start_pos = d_char + cur_c_offset;
sel_end_para = -1;
selection_started = 0;
} else if (is_dragging) {
update_selection(p, d_run, d_char + i);
update_selection(p, d_run, d_char + cur_c_offset);
}
return;
} else if (target_x >= cur_x + cw/2 && target_x < cur_x + cw) {
cursor_para = p; cursor_run = d_run; cursor_pos = d_char + i + 1;
cursor_para = p; cursor_run = d_run; cursor_pos = d_char + cur_c_offset + adv;
if (selection_started) {
sel_start_para = p; sel_start_run = d_run; sel_start_pos = d_char + i + 1;
sel_start_para = p; sel_start_run = d_run; sel_start_pos = d_char + cur_c_offset + adv;
sel_end_para = -1;
selection_started = 0;
} else if (is_dragging) {
update_selection(p, d_run, d_char + i + 1);
update_selection(p, d_run, d_char + cur_c_offset + adv);
}
return;
}
cur_x += cw;
cur_c_offset += adv;
}
d_char = 0; d_run++;
}
@ -1902,16 +1957,22 @@ int main(int argc, char **argv) {
} else if (ev.type == GUI_EVENT_CLICK) {
needs_repaint = 1;
} else if (ev.type == GUI_EVENT_KEY) {
uint32_t cp = (uint32_t)ev.arg4;
if (active_dialog == 1) {
char c = (char)ev.arg1;
if (c == '\b' && dialog_input_len > 0) {
dialog_input[--dialog_input_len] = 0;
} else if (c >= 32 && c < 127 && dialog_input_len < 127) {
dialog_input[dialog_input_len++] = c;
if (cp == '\b' && dialog_input_len > 0) {
const char *prev = text_prev_utf8(dialog_input, dialog_input + dialog_input_len);
dialog_input_len = (int)(prev - dialog_input);
dialog_input[dialog_input_len] = 0;
} else if (cp >= 32 && cp != 127) {
char utf8[4];
int clen = text_encode_utf8(cp, utf8);
if (clen > 0 && dialog_input_len + clen < 255) {
for(int i=0; i<clen; i++) dialog_input[dialog_input_len++] = utf8[i];
dialog_input[dialog_input_len] = 0;
}
}
} else {
insert_char(win, (char)ev.arg1);
insert_char(win, cp, (int)ev.arg1);
}
needs_repaint = 1;
} else if (ev.type == GUI_EVENT_CLOSE) {

View file

@ -9,6 +9,7 @@
#include <stddef.h>
#include <stdint.h>
#include "libc/stdlib.h"
#include "utf-8.h"
static int win_w = 1280;
static int win_h = 960;
@ -782,84 +783,73 @@ static void decode_html_entities(char *str) {
if (str_istarts_with(src, "&gt;")) { *dst++ = '>'; src += 4; continue; }
if (str_istarts_with(src, "&apos;")) { *dst++ = '\''; src += 6; continue; }
if (str_istarts_with(src, "&nbsp;")) { *dst++ = ' '; src += 6; continue; }
if (str_istarts_with(src, "&mdash;")) { *dst++ = (char)128; src += 7; continue; }
if (str_istarts_with(src, "&mdash")) { *dst++ = (char)128; src += 6; continue; }
if (str_istarts_with(src, "&ndash;")) { *dst++ = (char)129; src += 7; continue; }
if (str_istarts_with(src, "&ndash")) { *dst++ = (char)129; src += 6; continue; }
if (str_istarts_with(src, "&bull;")) { *dst++ = (char)130; src += 6; continue; }
if (str_istarts_with(src, "&bull")) { *dst++ = (char)130; src += 5; continue; }
if (str_istarts_with(src, "&hellip;")){ *dst++ = (char)131; src += 8; continue; }
if (str_istarts_with(src, "&hellip")){ *dst++ = (char)131; src += 7; continue; }
if (str_istarts_with(src, "&trade;")) { *dst++ = (char)132; src += 7; continue; }
if (str_istarts_with(src, "&euro;")) { *dst++ = (char)133; src += 6; continue; }
if (str_istarts_with(src, "&middot;")){ *dst++ = (char)134; src += 8; continue; }
if (str_istarts_with(src, "&mdash;")) { dst += text_encode_utf8(8212, dst); src += 7; continue; }
if (str_istarts_with(src, "&mdash")) { dst += text_encode_utf8(8212, dst); src += 6; continue; }
if (str_istarts_with(src, "&ndash;")) { dst += text_encode_utf8(8211, dst); src += 7; continue; }
if (str_istarts_with(src, "&ndash")) { dst += text_encode_utf8(8211, dst); src += 6; continue; }
if (str_istarts_with(src, "&bull;")) { dst += text_encode_utf8(8226, dst); src += 6; continue; }
if (str_istarts_with(src, "&bull")) { dst += text_encode_utf8(8226, dst); src += 5; continue; }
if (str_istarts_with(src, "&hellip;")){ dst += text_encode_utf8(8230, dst); src += 8; continue; }
if (str_istarts_with(src, "&hellip")){ dst += text_encode_utf8(8230, dst); src += 7; continue; }
if (str_istarts_with(src, "&trade;")) { dst += text_encode_utf8(8482, dst); src += 7; continue; }
if (str_istarts_with(src, "&euro;")) { dst += text_encode_utf8(8364, dst); src += 6; continue; }
if (str_istarts_with(src, "&middot;")){ dst += text_encode_utf8(183, dst); src += 8; continue; }
if (str_istarts_with(src, "&lsquo;")) { *dst++ = '\''; src += 7; continue; }
if (str_istarts_with(src, "&rsquo;")) { *dst++ = '\''; src += 7; continue; }
if (str_istarts_with(src, "&ldquo;")) { *dst++ = '\"'; src += 7; continue; }
if (str_istarts_with(src, "&rdquo;")) { *dst++ = '\"'; src += 7; continue; }
if (str_istarts_with(src, "&iexcl;")) { *dst++ = (char)161; src += 7; continue; }
if (str_istarts_with(src, "&cent;")) { *dst++ = (char)162; src += 6; continue; }
if (str_istarts_with(src, "&pound;")) { *dst++ = (char)163; src += 7; continue; }
if (str_istarts_with(src, "&yen;")) { *dst++ = (char)165; src += 5; continue; }
if (str_istarts_with(src, "&copy;")) { *dst++ = (char)169; src += 6; continue; }
if (str_istarts_with(src, "&reg;")) { *dst++ = (char)174; src += 5; continue; }
if (str_istarts_with(src, "&deg;")) { *dst++ = (char)176; src += 5; continue; }
if (str_istarts_with(src, "&aacute;")) { *dst++ = (char)225; src += 8; continue; }
if (str_istarts_with(src, "&eacute;")) { *dst++ = (char)233; src += 8; continue; }
if (str_istarts_with(src, "&iacute;")) { *dst++ = (char)237; src += 8; continue; }
if (str_istarts_with(src, "&oacute;")) { *dst++ = (char)243; src += 8; continue; }
if (str_istarts_with(src, "&uacute;")) { *dst++ = (char)250; src += 8; continue; }
if (str_istarts_with(src, "&ntilde;")) { *dst++ = (char)241; src += 8; continue; }
if (str_istarts_with(src, "&uuml;")) { *dst++ = (char)252; src += 6; continue; }
if (str_istarts_with(src, "&iquest;")) { *dst++ = (char)191; src += 8; continue; }
if (str_istarts_with(src, "&Agrave;")) { *dst++ = (char)192; src += 8; continue; }
if (str_istarts_with(src, "&Aacute;")) { *dst++ = (char)193; src += 8; continue; }
if (str_istarts_with(src, "&times;")) { *dst++ = (char)215; src += 7; continue; }
if (str_istarts_with(src, "&divide;")) { *dst++ = (char)247; src += 8; continue; }
if (str_istarts_with(src, "&plusmn;")) { *dst++ = (char)177; src += 8; continue; }
if (str_istarts_with(src, "&micro;")) { *dst++ = (char)181; src += 7; continue; }
if (str_istarts_with(src, "&para;")) { *dst++ = (char)182; src += 6; continue; }
if (str_istarts_with(src, "&brvbar;")) { *dst++ = (char)166; src += 8; continue; }
if (str_istarts_with(src, "&sect;")) { *dst++ = (char)167; src += 6; continue; }
if (str_istarts_with(src, "&uml;")) { *dst++ = (char)168; src += 5; continue; }
if (str_istarts_with(src, "&ordf;")) { *dst++ = (char)170; src += 6; continue; }
if (str_istarts_with(src, "&laquo;")) { *dst++ = (char)171; src += 7; continue; }
if (str_istarts_with(src, "&not;")) { *dst++ = (char)172; src += 5; continue; }
if (str_istarts_with(src, "&iexcl;")) { dst += text_encode_utf8(161, dst); src += 7; continue; }
if (str_istarts_with(src, "&cent;")) { dst += text_encode_utf8(162, dst); src += 6; continue; }
if (str_istarts_with(src, "&pound;")) { dst += text_encode_utf8(163, dst); src += 7; continue; }
if (str_istarts_with(src, "&yen;")) { dst += text_encode_utf8(165, dst); src += 5; continue; }
if (str_istarts_with(src, "&copy;")) { dst += text_encode_utf8(169, dst); src += 6; continue; }
if (str_istarts_with(src, "&reg;")) { dst += text_encode_utf8(174, dst); src += 5; continue; }
if (str_istarts_with(src, "&deg;")) { dst += text_encode_utf8(176, dst); src += 5; continue; }
if (str_istarts_with(src, "&aacute;")) { dst += text_encode_utf8(225, dst); src += 8; continue; }
if (str_istarts_with(src, "&eacute;")) { dst += text_encode_utf8(233, dst); src += 8; continue; }
if (str_istarts_with(src, "&iacute;")) { dst += text_encode_utf8(237, dst); src += 8; continue; }
if (str_istarts_with(src, "&oacute;")) { dst += text_encode_utf8(243, dst); src += 8; continue; }
if (str_istarts_with(src, "&uacute;")) { dst += text_encode_utf8(250, dst); src += 8; continue; }
if (str_istarts_with(src, "&ntilde;")) { dst += text_encode_utf8(241, dst); src += 8; continue; }
if (str_istarts_with(src, "&uuml;")) { dst += text_encode_utf8(252, dst); src += 6; continue; }
if (str_istarts_with(src, "&iquest;")) { dst += text_encode_utf8(191, dst); src += 8; continue; }
if (str_istarts_with(src, "&Agrave;")) { dst += text_encode_utf8(192, dst); src += 8; continue; }
if (str_istarts_with(src, "&Aacute;")) { dst += text_encode_utf8(193, dst); src += 8; continue; }
if (str_istarts_with(src, "&times;")) { dst += text_encode_utf8(215, dst); src += 7; continue; }
if (str_istarts_with(src, "&divide;")) { dst += text_encode_utf8(247, dst); src += 8; continue; }
if (str_istarts_with(src, "&plusmn;")) { dst += text_encode_utf8(177, dst); src += 8; continue; }
if (str_istarts_with(src, "&micro;")) { dst += text_encode_utf8(181, dst); src += 7; continue; }
if (str_istarts_with(src, "&para;")) { dst += text_encode_utf8(182, dst); src += 6; continue; }
if (str_istarts_with(src, "&brvbar;")) { dst += text_encode_utf8(166, dst); src += 8; continue; }
if (str_istarts_with(src, "&sect;")) { dst += text_encode_utf8(167, dst); src += 6; continue; }
if (str_istarts_with(src, "&uml;")) { dst += text_encode_utf8(168, dst); src += 5; continue; }
if (str_istarts_with(src, "&ordf;")) { dst += text_encode_utf8(170, dst); src += 6; continue; }
if (str_istarts_with(src, "&laquo;")) { dst += text_encode_utf8(171, dst); src += 7; continue; }
if (str_istarts_with(src, "&not;")) { dst += text_encode_utf8(172, dst); src += 5; continue; }
if (str_istarts_with(src, "&shy;")) { src += 5; continue; } // Soft hyphen, ignore
if (str_istarts_with(src, "&macr;")) { *dst++ = (char)175; src += 6; continue; }
if (str_istarts_with(src, "&sup2;")) { *dst++ = (char)178; src += 6; continue; }
if (str_istarts_with(src, "&sup3;")) { *dst++ = (char)179; src += 6; continue; }
if (str_istarts_with(src, "&acute;")) { *dst++ = (char)180; src += 7; continue; }
if (str_istarts_with(src, "&cedil;")) { *dst++ = (char)184; src += 7; continue; }
if (str_istarts_with(src, "&sup1;")) { *dst++ = (char)185; src += 6; continue; }
if (str_istarts_with(src, "&ordm;")) { *dst++ = (char)186; src += 6; continue; }
if (str_istarts_with(src, "&raquo;")) { *dst++ = (char)187; src += 7; continue; }
if (str_istarts_with(src, "&frac14;")) { *dst++ = (char)188; src += 8; continue; }
if (str_istarts_with(src, "&frac12;")) { *dst++ = (char)189; src += 8; continue; }
if (str_istarts_with(src, "&frac34;")) { *dst++ = (char)190; src += 8; continue; }
if (str_istarts_with(src, "&macr;")) { dst += text_encode_utf8(175, dst); src += 6; continue; }
if (str_istarts_with(src, "&sup2;")) { dst += text_encode_utf8(178, dst); src += 6; continue; }
if (str_istarts_with(src, "&sup3;")) { dst += text_encode_utf8(179, dst); src += 6; continue; }
if (str_istarts_with(src, "&acute;")) { dst += text_encode_utf8(180, dst); src += 7; continue; }
if (str_istarts_with(src, "&cedil;")) { dst += text_encode_utf8(184, dst); src += 7; continue; }
if (str_istarts_with(src, "&sup1;")) { dst += text_encode_utf8(185, dst); src += 6; continue; }
if (str_istarts_with(src, "&ordm;")) { dst += text_encode_utf8(186, dst); src += 6; continue; }
if (str_istarts_with(src, "&raquo;")) { dst += text_encode_utf8(187, dst); src += 7; continue; }
if (str_istarts_with(src, "&frac14;")) { dst += text_encode_utf8(188, dst); src += 8; continue; }
if (str_istarts_with(src, "&frac12;")) { dst += text_encode_utf8(189, dst); src += 8; continue; }
if (str_istarts_with(src, "&frac34;")) { dst += text_encode_utf8(190, dst); src += 8; continue; }
if (src[1] == '#') {
int val = 0;
char *end = NULL;
if (src[2] == 'x' || src[2] == 'X') {
val = strtol(src + 3, &end, 16);
val = (int)strtol(src + 3, &end, 16);
} else {
val = strtol(src + 2, &end, 10);
val = (int)strtol(src + 2, &end, 10);
}
if (end && *end == ';' && end > src + 2) {
if (val == 8211) val = 129; // &ndash;
else if (val == 8212) val = 128; // &mdash;
else if (val == 8226) val = 130; // &bull;
else if (val == 8230) val = 131; // &hellip;
else if (val == 8482) val = 132; // &trade;
else if (val == 8364) val = 133; // &euro;
else if (val == 183) val = 134; // &middot;
else if (val == 8216 || val == 8217) val = '\'';
else if (val == 8220 || val == 8221) val = '\"';
else if (val == 160) val = ' ';
if (val > 0 && val < 256) {
*dst++ = (char)val;
if (val > 0) {
dst += text_encode_utf8((uint32_t)val, dst);
src = end + 1;
continue;
}
@ -1810,7 +1800,7 @@ int main(int argc, char **argv) {
if (ev.type != GUI_EVENT_CLICK && ev.type != GUI_EVENT_MOUSE_DOWN) continue;
if (my < URL_BAR_H) {
if (widget_textbox_handle_mouse(&url_tb, mx, my, is_click, NULL)) {
if (widget_textbox_handle_mouse(&browser_ctx, &url_tb, mx, my, is_click, NULL)) {
focused_element = -1;
needs_repaint = true;
continue;
@ -1994,26 +1984,46 @@ int main(int argc, char **argv) {
if (!found) { focused_element = -1; needs_repaint = true; }
} else if (ev.type == GUI_EVENT_KEY || ev.type == GUI_EVENT_KEYUP) {
if (ev.type == GUI_EVENT_KEYUP) continue;
uint32_t cp = (uint32_t)ev.arg4;
char c = (char)ev.arg1;
if (focused_element == -1) {
if (c == 13 || c == 10) {
if (cp == 13 || cp == 10) {
if (history_count < HISTORY_MAX) { int j=0; while(url_input_buffer[j]) { history_stack[history_count][j] = url_input_buffer[j]; j++; } history_stack[history_count][j] = 0; history_count++; }
navigate(url_input_buffer); scroll_y = 0;
needs_repaint = true;
}
else if (c == 19) { if (url_cursor > 0) url_cursor--; }
else if (c == 20) { int len = 0; while(url_input_buffer[len]) len++; if (url_cursor < len) url_cursor++; }
else if (c == 127 || c == 8) {
else if (c == 19) { // LEFT
if (url_cursor > 0) {
int len = 0; while(url_input_buffer[len]) len++;
for (int k=url_cursor-1; k<len; k++) url_input_buffer[k] = url_input_buffer[k+1];
url_cursor--;
const char *prev = text_prev_utf8(url_input_buffer, url_input_buffer + url_cursor);
url_cursor = (int)(prev - url_input_buffer);
}
}
else if (c >= 32 && c <= 126 && url_cursor < 511) {
int len = 0; while(url_input_buffer[len]) len++;
for (int k=len; k>=url_cursor; k--) url_input_buffer[k+1] = url_input_buffer[k];
url_input_buffer[url_cursor++] = c;
else if (c == 20) { // RIGHT
int len = (int)strlen(url_input_buffer);
if (url_cursor < len) {
const char *next = text_next_utf8(url_input_buffer + url_cursor);
url_cursor = (int)(next - url_input_buffer);
}
}
else if (c == 127 || c == 8) {
if (url_cursor > 0) {
int len = (int)strlen(url_input_buffer);
const char *prev = text_prev_utf8(url_input_buffer, url_input_buffer + url_cursor);
int char_len = (int)((url_input_buffer + url_cursor) - prev);
for (int k=url_cursor-char_len; k<len-char_len; k++) url_input_buffer[k] = url_input_buffer[k+char_len];
url_cursor -= char_len;
url_input_buffer[len - char_len] = 0;
}
}
else if (cp >= 32 && cp != 127 && url_cursor < 511) {
char utf8[4];
int clen = text_encode_utf8(cp, utf8);
int len = (int)strlen(url_input_buffer);
if (clen > 0 && len + clen < 511) {
for (int k=len; k>=url_cursor; k--) url_input_buffer[k+clen] = url_input_buffer[k];
for (int k=0; k<clen; k++) url_input_buffer[url_cursor + k] = utf8[k];
url_cursor += clen;
}
}
} else {
RenderElement *el = &elements[focused_element];
@ -2060,17 +2070,35 @@ int main(int argc, char **argv) {
navigate(url_input_buffer); scroll_y = 0; focused_element = -1;
needs_repaint = true;
}
else if (c == 19) { if (el->input_cursor > 0) el->input_cursor--; }
else if (c == 20) { if (el->input_cursor < len) el->input_cursor++; }
else if (c == 127 || c == 8) {
else if (c == 19) { // LEFT
if (el->input_cursor > 0) {
for (int k=el->input_cursor-1; k<len; k++) el->attr_value[k] = el->attr_value[k+1];
el->input_cursor--;
const char *prev = text_prev_utf8(el->attr_value, el->attr_value + el->input_cursor);
el->input_cursor = (int)(prev - el->attr_value);
}
}
else if (c >= 32 && c <= 126 && len < 255) {
for (int k=len; k>=el->input_cursor; k--) el->attr_value[k+1] = el->attr_value[k];
el->attr_value[el->input_cursor++] = c;
else if (c == 20) { // RIGHT
if (el->input_cursor < len) {
const char *next = text_next_utf8(el->attr_value + el->input_cursor);
el->input_cursor = (int)(next - el->attr_value);
}
}
else if (c == 127 || c == 8) {
if (el->input_cursor > 0) {
const char *prev = text_prev_utf8(el->attr_value, el->attr_value + el->input_cursor);
int char_len = (int)((el->attr_value + el->input_cursor) - prev);
for (int k=el->input_cursor-char_len; k<len-char_len; k++) el->attr_value[k] = el->attr_value[k+char_len];
el->input_cursor -= char_len;
el->attr_value[len - char_len] = 0;
}
}
else if (cp >= 32 && cp != 127 && len < 255) {
char utf8[4];
int clen = text_encode_utf8(cp, utf8);
if (clen > 0 && len + clen < 255) {
for (int k=len; k>=el->input_cursor; k--) el->attr_value[k+clen] = el->attr_value[k];
for (int k=0; k<clen; k++) el->attr_value[el->input_cursor + k] = utf8[k];
el->input_cursor += clen;
}
}
int max_v = (el->w - 10) / 8;

View file

@ -6,6 +6,7 @@
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/se.sjoerd.Graphs.png;/Library/images/icons/colloid/app-icon-preview.png
#include "syscall.h"
#include "libui.h"
#include "utf-8.h"
#include "../../wm/libwidget.h"
#include <stdbool.h>
#include "stdlib.h"
@ -1598,16 +1599,17 @@ int main(void) {
} else if (ev.type == GUI_EVENT_PAINT) {
needs_repaint = true;
} else if (ev.type == GUI_EVENT_KEY) {
uint32_t cp = (uint32_t)ev.arg4;
char c = (char)ev.arg1;
if (tb_equation.focused) {
if (c == '\n') {
eq_len = strlen(eq_buffer);
if (cp == '\n') {
eq_len = (int)strlen(eq_buffer);
parse_equation();
if (graph_mode == MODE_2D) autofit_2d_view();
needs_repaint = true;
} else {
widget_textbox_handle_key(&tb_equation, c, NULL);
eq_len = strlen(eq_buffer);
widget_textbox_handle_key(&tb_equation, cp, (int)ev.arg1, NULL);
eq_len = (int)strlen(eq_buffer);
needs_repaint = true;
}
} else if ((c == 'r' || c == 'R') && ev.arg3 == 1) {
@ -1699,7 +1701,7 @@ int main(void) {
}
}
widget_textbox_handle_mouse(&tb_equation, mx, my, true, NULL);
widget_textbox_handle_mouse(&wctx, &tb_equation, mx, my, true, NULL);
needs_repaint = true;
} else if (ev.type == GUI_EVENT_MOUSE_DOWN) {

View file

@ -10,6 +10,7 @@
#include <stddef.h>
#include <stdint.h>
#include "../../wm/libwidget.h"
#include "utf-8.h"
#define COLOR_COFFEE 0xFF6B4423
#define COLOR_TEAL 0xFF008080
@ -1069,13 +1070,13 @@ static void control_panel_handle_mouse(int x, int y, bool is_down, bool is_click
}
if (current_view == VIEW_WALLPAPER) {
if (widget_textbox_handle_mouse(&tb_r, x, y, is_click, NULL)) {
if (widget_textbox_handle_mouse(&settings_ctx, &tb_r, x, y, is_click, NULL)) {
focused_field = 0; input_cursor = tb_r.cursor_pos; return;
}
if (widget_textbox_handle_mouse(&tb_g, x, y, is_click, NULL)) {
if (widget_textbox_handle_mouse(&settings_ctx, &tb_g, x, y, is_click, NULL)) {
focused_field = 1; input_cursor = tb_g.cursor_pos; return;
}
if (widget_textbox_handle_mouse(&tb_b, x, y, is_click, NULL)) {
if (widget_textbox_handle_mouse(&settings_ctx, &tb_b, x, y, is_click, NULL)) {
focused_field = 2; input_cursor = tb_b.cursor_pos; return;
}
@ -1112,10 +1113,10 @@ static void control_panel_handle_mouse(int x, int y, bool is_down, bool is_click
}
if (current_view == VIEW_NETWORK) {
if (widget_textbox_handle_mouse(&tb_ip, x, y, is_click, NULL)) {
if (widget_textbox_handle_mouse(&settings_ctx, &tb_ip, x, y, is_click, NULL)) {
focused_field = 5; input_cursor = tb_ip.cursor_pos; return;
}
if (widget_textbox_handle_mouse(&tb_dns, x, y, is_click, NULL)) {
if (widget_textbox_handle_mouse(&settings_ctx, &tb_dns, x, y, is_click, NULL)) {
focused_field = 6; input_cursor = tb_dns.cursor_pos; return;
}
@ -1239,10 +1240,10 @@ static void control_panel_handle_mouse(int x, int y, bool is_down, bool is_click
}
if (current_view == VIEW_DISPLAY) {
if (widget_textbox_handle_mouse(&tb_custom_w, x, y, is_click, NULL)) {
if (widget_textbox_handle_mouse(&settings_ctx, &tb_custom_w, x, y, is_click, NULL)) {
focused_field = 3; disp_sel_res = 5; input_cursor = tb_custom_w.cursor_pos; return;
}
if (widget_textbox_handle_mouse(&tb_custom_h, x, y, is_click, NULL)) {
if (widget_textbox_handle_mouse(&settings_ctx, &tb_custom_h, x, y, is_click, NULL)) {
focused_field = 4; disp_sel_res = 5; input_cursor = tb_custom_h.cursor_pos; return;
}
}
@ -1264,33 +1265,34 @@ static void control_panel_handle_mouse(int x, int y, bool is_down, bool is_click
}
}
static void control_panel_handle_key(char c, bool pressed) {
static void control_panel_handle_key(uint32_t codepoint, int legacy, bool pressed) {
if (!pressed) return;
if (focused_field < 0) return;
if (current_view == VIEW_WALLPAPER) {
if (focused_field == 0) widget_textbox_handle_key(&tb_r, c, NULL);
if (focused_field == 1) widget_textbox_handle_key(&tb_g, c, NULL);
if (focused_field == 2) widget_textbox_handle_key(&tb_b, c, NULL);
if (c == '\t') focused_field = (focused_field + 1) % 3;
if (focused_field == 0) widget_textbox_handle_key(&tb_r, codepoint, legacy, NULL);
if (focused_field == 1) widget_textbox_handle_key(&tb_g, codepoint, legacy, NULL);
if (focused_field == 2) widget_textbox_handle_key(&tb_b, codepoint, legacy, NULL);
if (legacy == '\t') focused_field = (focused_field + 1) % 3;
} else if (current_view == VIEW_DISPLAY) {
char *focused_buffer = (focused_field == 3) ? custom_res_w : custom_res_h;
if (c == '\b') {
if (legacy == '\b' || legacy == 127) {
if (input_cursor > 0) {
input_cursor--;
const char *prev = text_prev_utf8(focused_buffer, focused_buffer + input_cursor);
input_cursor = (int)(prev - focused_buffer);
focused_buffer[input_cursor] = '\0';
}
} else if (c >= '0' && c <= '9') {
} else if (codepoint >= '0' && codepoint <= '9') {
if (input_cursor < 5) {
focused_buffer[input_cursor] = c;
focused_buffer[input_cursor] = (char)codepoint;
input_cursor++;
focused_buffer[input_cursor] = '\0';
}
}
} else if (current_view == VIEW_NETWORK) {
if (focused_field == 5) widget_textbox_handle_key(&tb_ip, c, NULL);
if (focused_field == 6) widget_textbox_handle_key(&tb_dns, c, NULL);
if (c == '\t') focused_field = (focused_field == 5) ? 6 : 5;
if (focused_field == 5) widget_textbox_handle_key(&tb_ip, codepoint, legacy, NULL);
if (focused_field == 6) widget_textbox_handle_key(&tb_dns, codepoint, legacy, NULL);
if (legacy == '\t') focused_field = (focused_field == 5) ? 6 : 5;
}
}
@ -1434,11 +1436,11 @@ int main(int argc, char **argv) {
}
} else if (ev.type == GUI_EVENT_KEY) {
control_panel_handle_key((char)ev.arg1, true);
control_panel_handle_key((uint32_t)ev.arg4, (int)ev.arg1, true);
dirty = true;
} else if (ev.type == GUI_EVENT_KEYUP) {
control_panel_handle_key((char)ev.arg1, false);
control_panel_handle_key((uint32_t)ev.arg4, (int)ev.arg1, false);
} else if (ev.type == GUI_EVENT_CLOSE) {
sys_exit(0);

View file

@ -1140,10 +1140,10 @@ static void handle_key(gui_event_t *ev) {
if (!ctrl) {
if (legacy == KEY_BACKSPACE) {
if (s->input_len > 0) {
// Find previous UTF-8 character boundary
const char *prev = text_prev_utf8(s->current_input, s->current_input + s->input_len);
s->input_len = (int)(prev - s->current_input);
s->current_input[s->input_len] = 0;
s->unacknowledged_chars--;
}
} else if (codepoint >= 32 && codepoint != 127) {
char utf8[4];
@ -1161,8 +1161,6 @@ static void handle_key(gui_event_t *ev) {
s->input_len = 0;
s->current_input[0] = 0;
s->unacknowledged_chars = 0;
} else if (legacy == KEY_BACKSPACE) {
s->unacknowledged_chars--;
}
update_input_color(s);

View file

@ -1,4 +1,5 @@
#include "libwidget.h"
#include "utf-8.h"
#include <stddef.h>
#define COLOR_GRAY 0xFFC0C0C0
@ -237,17 +238,14 @@ void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb) {
if (ctx->draw_string && tb->text) {
int max_w = tb->w - 15;
int scroll_x = 0;
int text_w = 0;
if (ctx->measure_string_width) {
text_w = ctx->measure_string_width(ctx->user_data, tb->text);
} else {
text_w = string_len(tb->text) * 8;
text_w = (int)text_strlen_utf8(tb->text) * 8;
}
if (text_w > max_w) scroll_x = text_w - max_w;
// Very basic simple drawing, without proper clipping since context lacks it
ctx->draw_string(ctx->user_data, tb->x + 5, tb->y + (tb->h - 8) / 2, tb->text, text_color);
@ -263,7 +261,13 @@ void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb) {
tmp[k] = 0;
cx = ctx->measure_string_width(ctx->user_data, tmp);
} else {
cx = tb->cursor_pos * 8;
char tmp[256];
int k = 0;
for (k = 0; k < tb->cursor_pos && tb->text[k]; k++) {
tmp[k] = tb->text[k];
}
tmp[k] = 0;
cx = (int)text_strlen_utf8(tmp) * 8;
}
if (cx > max_w) cx = max_w; // clamped to visible end
@ -273,50 +277,90 @@ void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb) {
}
}
bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data) {
bool widget_textbox_handle_mouse(widget_context_t *ctx, widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data) {
(void)user_data;
bool in_bounds = (mx >= tb->x && mx < tb->x + tb->w && my >= tb->y && my < tb->y + tb->h);
if (mouse_clicked) {
tb->focused = in_bounds;
if (in_bounds && tb->text) {
int rel_x = mx - (tb->x + 5);
if (rel_x < 0) rel_x = 0;
// Rough estimation for fixed-width font 8px chars
int new_pos = rel_x / 8;
int len = string_len(tb->text);
if (new_pos > len) new_pos = len;
tb->cursor_pos = new_pos;
int cur_x = 0;
int byte_pos = 0;
while (tb->text[byte_pos]) {
int adv;
text_decode_utf8(tb->text + byte_pos, &adv);
char buf[5];
for (int k = 0; k < adv; k++) buf[k] = tb->text[byte_pos + k];
buf[adv] = 0;
int cw;
if (ctx && ctx->measure_string_width) {
cw = ctx->measure_string_width(ctx->user_data, buf);
} else {
cw = 8;
}
if (rel_x < cur_x + cw / 2) {
tb->cursor_pos = byte_pos;
return true;
}
if (rel_x < cur_x + cw) {
tb->cursor_pos = byte_pos + adv;
return true;
}
cur_x += cw;
byte_pos += adv;
}
tb->cursor_pos = byte_pos;
}
}
return in_bounds;
}
bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data) {
bool widget_textbox_handle_key(widget_textbox_t *tb, uint32_t codepoint, int legacy, void *user_data) {
if (!tb->focused || !tb->text) return false;
int len = string_len(tb->text);
if (c == 19) { // LEFT
if (tb->cursor_pos > 0) tb->cursor_pos--;
int len = (int)string_len(tb->text);
if (legacy == 19) { // LEFT
if (tb->cursor_pos > 0) {
const char *prev = text_prev_utf8(tb->text, tb->text + tb->cursor_pos);
tb->cursor_pos = (int)(prev - tb->text);
}
return true;
} else if (c == 20) { // RIGHT
if (tb->cursor_pos < len) tb->cursor_pos++;
} else if (legacy == 20) { // RIGHT
if (tb->cursor_pos < len) {
const char *next = text_next_utf8(tb->text + tb->cursor_pos);
tb->cursor_pos = (int)(next - tb->text);
}
return true;
}
if (c == '\b') { // backspace
if (legacy == '\b' || legacy == 127) { // backspace
if (tb->cursor_pos > 0) {
for (int i = tb->cursor_pos - 1; i < len; i++) {
tb->text[i] = tb->text[i + 1];
const char *prev = text_prev_utf8(tb->text, tb->text + tb->cursor_pos);
int char_len = (int)((tb->text + tb->cursor_pos) - prev);
for (int i = tb->cursor_pos - char_len; i < len - char_len; i++) {
tb->text[i] = tb->text[i + char_len];
}
tb->cursor_pos--;
tb->cursor_pos -= char_len;
tb->text[len - char_len] = 0;
if (tb->on_change) tb->on_change(user_data);
}
} else if (c >= 32 && c < 127) {
if (len < tb->max_len - 1) {
} else if (codepoint >= 32 && codepoint != 127) {
char utf8[4];
int clen = text_encode_utf8(codepoint, utf8);
if (clen > 0 && len + clen < tb->max_len) {
for (int i = len; i >= tb->cursor_pos; i--) {
tb->text[i + 1] = tb->text[i];
tb->text[i + clen] = tb->text[i];
}
tb->text[tb->cursor_pos] = c;
tb->cursor_pos++;
for (int i = 0; i < clen; i++) {
tb->text[tb->cursor_pos + i] = utf8[i];
}
tb->cursor_pos += clen;
if (tb->on_change) tb->on_change(user_data);
}
}

View file

@ -60,8 +60,8 @@ typedef struct {
void widget_textbox_init(widget_textbox_t *tb, int x, int y, int w, int h, char *buffer, int max_len);
void widget_textbox_draw(widget_context_t *ctx, widget_textbox_t *tb);
bool widget_textbox_handle_mouse(widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data);
bool widget_textbox_handle_key(widget_textbox_t *tb, char c, void *user_data);
bool widget_textbox_handle_mouse(widget_context_t *ctx, widget_textbox_t *tb, int mx, int my, bool mouse_clicked, void *user_data);
bool widget_textbox_handle_key(widget_textbox_t *tb, uint32_t codepoint, int legacy, void *user_data);
// --- Dropdown ---
typedef struct {

115
src/wm/utf-8.c Normal file
View file

@ -0,0 +1,115 @@
#include "utf-8.h"
static int utf8_write_replacement(char *out) {
out[0] = (char)0xEF;
out[1] = (char)0xBF;
out[2] = (char)0xBD;
return 3;
}
uint32_t text_decode_utf8(const char *s, int *advance) {
const unsigned char *u = (const unsigned char *)s;
if (!u || u[0] == 0) {
if (advance) *advance = 0;
return 0;
}
if ((u[0] & 0x80) == 0) {
if (advance) *advance = 1;
return u[0];
}
if ((u[0] & 0xE0) == 0xC0 &&
(u[1] & 0xC0) == 0x80) {
if (advance) *advance = 2;
return ((u[0] & 0x1F) << 6) |
(u[1] & 0x3F);
}
if ((u[0] & 0xF0) == 0xE0 &&
(u[1] & 0xC0) == 0x80 &&
(u[2] & 0xC0) == 0x80) {
if (advance) *advance = 3;
return ((u[0] & 0x0F) << 12) |
((u[1] & 0x3F) << 6) |
(u[2] & 0x3F);
}
if ((u[0] & 0xF8) == 0xF0 &&
(u[1] & 0xC0) == 0x80 &&
(u[2] & 0xC0) == 0x80 &&
(u[3] & 0xC0) == 0x80) {
if (advance) *advance = 4;
return ((u[0] & 0x07) << 18) |
((u[1] & 0x3F) << 12) |
((u[2] & 0x3F) << 6) |
(u[3] & 0x3F);
}
if (advance) *advance = 1;
return 0xFFFD;
}
int text_encode_utf8(uint32_t cp, char *out) {
if (cp <= 0x7F) {
out[0] = (char)cp;
return 1;
}
if (cp <= 0x7FF) {
out[0] = 0xC0 | (cp >> 6);
out[1] = 0x80 | (cp & 0x3F);
return 2;
}
if (cp <= 0xFFFF) {
out[0] = 0xE0 | (cp >> 12);
out[1] = 0x80 | ((cp >> 6) & 0x3F);
out[2] = 0x80 | (cp & 0x3F);
return 3;
}
if (cp <= 0x10FFFF) {
out[0] = 0xF0 | (cp >> 18);
out[1] = 0x80 | ((cp >> 12) & 0x3F);
out[2] = 0x80 | ((cp >> 6) & 0x3F);
out[3] = 0x80 | (cp & 0x3F);
return 4;
}
return utf8_write_replacement(out);
}
const char* text_next_utf8(const char *s) {
if (!s || *s == 0) return s;
int adv;
text_decode_utf8(s, &adv);
return s + adv;
}
const char* text_prev_utf8(const char *start, const char *s) {
if (!s || s <= start) return start;
s--;
while (s > start && ((*s & 0xC0) == 0x80)) {
s--;
}
return s;
}
int text_strlen_utf8(const char *s) {
if (!s) return 0;
int count = 0;
int adv;
while (*s) {
text_decode_utf8(s, &adv);
s += adv;
count++;
}
return count;
}

25
src/wm/utf-8.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef UTF_8_H
#define UTF_8_H
#include <stdint.h>
// Decode one UTF-8 codepoint
// s: input string
// advance: number of bytes consumed
uint32_t text_decode_utf8(const char *s, int *advance);
// Encode one codepoint into UTF-8
// out must be at least 4 bytes
// return: number of bytes written
int text_encode_utf8(uint32_t cp, char *out);
// Move to next UTF-8 character
const char* text_next_utf8(const char *s);
// Move to previous UTF-8 character
const char* text_prev_utf8(const char *start, const char *s);
// Count characters (not bytes)
int text_strlen_utf8(const char *s);
#endif