feat: switch text to UTF-8; add Unicode key events, dead keys & Czech layout; fix numpad navigation; add NumLock support
This commit is contained in:
1 parent
32660ddd1a
commit
2aa6360b3d
101 files changed
+3733
-1639
No files matched your search
@@ -263,7 +263,7 @@ static int wp_char_advance_at(WordProcessorState* wp, int abs_pos, int layout_dp
|
||||
wp_font_at(wp, abs_pos, layout_dpi, &font, &gc);
|
||||
if (!font || !gc) return wp_points_to_pixels(6, layout_dpi);
|
||||
|
||||
CachedGlyph* g = font->get_glyph(gc, (unsigned char)ch);
|
||||
CachedGlyph* g = font->get_glyph(gc, decode_single_byte_codepoint((unsigned char)ch));
|
||||
return g ? g->advance : wp_points_to_pixels(6, layout_dpi);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,17 +14,46 @@ static void wp_set_status(WordProcessorState* wp, const char* msg) {
|
||||
wp->status_msg[sizeof(wp->status_msg) - 1] = '\0';
|
||||
}
|
||||
|
||||
// Documents store text as Windows-1252 (one byte per character, matching the
|
||||
// WinAnsi fonts used for PDF export); the clipboard is UTF-8. Convert both ways.
|
||||
static char* wp_cp1252_to_utf8_alloc(const char* text, int len, int* out_len) {
|
||||
char* out = (char*)montauk::malloc(len * 3 + 1);
|
||||
if (!out) return nullptr;
|
||||
int o = 0;
|
||||
for (int i = 0; i < len; i++)
|
||||
o += montauk::utf8::encode(montauk::utf8::from_cp1252((uint8_t)text[i]), out + o);
|
||||
out[o] = '\0';
|
||||
*out_len = o;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Characters with no Windows-1252 form become '?'.
|
||||
static int wp_utf8_to_cp1252_inplace(char* text, int len) {
|
||||
int o = 0;
|
||||
for (int pos = 0; pos < len;) {
|
||||
int n;
|
||||
uint32_t cp = montauk::utf8::decode(text + pos, len - pos, &n);
|
||||
pos += n;
|
||||
uint8_t b = cp == 0 ? 0 : montauk::utf8::to_cp1252(cp);
|
||||
text[o++] = b ? (char)b : '?';
|
||||
}
|
||||
text[o] = '\0';
|
||||
return o;
|
||||
}
|
||||
|
||||
static bool wp_copy_selection_to_clipboard(WordProcessorState* wp) {
|
||||
int len = 0;
|
||||
char* text = wp_selection_plain_text_alloc(wp, &len);
|
||||
if (text == nullptr || len <= 0) {
|
||||
if (text) montauk::mfree(text);
|
||||
char* raw = wp_selection_plain_text_alloc(wp, &len);
|
||||
if (raw == nullptr || len <= 0) {
|
||||
if (raw) montauk::mfree(raw);
|
||||
wp_set_status(wp, "Nothing selected");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = gui::clipboard_set_text(text, len);
|
||||
montauk::mfree(text);
|
||||
char* text = wp_cp1252_to_utf8_alloc(raw, len, &len);
|
||||
montauk::mfree(raw);
|
||||
bool ok = text && gui::clipboard_set_text(text, len);
|
||||
if (text) montauk::mfree(text);
|
||||
if (!ok) {
|
||||
wp_set_status(wp, "Copy failed");
|
||||
return false;
|
||||
@@ -52,6 +81,7 @@ static bool wp_paste_from_clipboard(WordProcessorState* wp) {
|
||||
wp_set_status(wp, "Clipboard is empty");
|
||||
return false;
|
||||
}
|
||||
len = wp_utf8_to_cp1252_inplace(text, len);
|
||||
|
||||
bool ok = wp_replace_selection_with_text(wp, text, len);
|
||||
montauk::mfree(text);
|
||||
@@ -263,7 +293,7 @@ static int wp_hit_test_text(WordProcessorState* wp, int local_x, int local_y, in
|
||||
for (int ci = 0; ci < to_check; ci++) {
|
||||
char ch = r->text[ro + ci];
|
||||
CachedGlyph* g = (ch >= 32 || ch < 0) && ch != '\n'
|
||||
? font->get_glyph(gc, (unsigned char)ch) : nullptr;
|
||||
? font->get_glyph(gc, decode_single_byte_codepoint((unsigned char)ch)) : nullptr;
|
||||
int char_w = g ? g->advance : 0;
|
||||
|
||||
if (x + char_w / 2 > click_x) return best_abs;
|
||||
@@ -611,32 +641,18 @@ void wp_handle_key(const montauk::abi::KeyEvent& key) {
|
||||
return;
|
||||
}
|
||||
if (key.ascii == '\b' || key.scancode == 0x0E) {
|
||||
if (wp->pathbar_cursor > 0) {
|
||||
for (int i = wp->pathbar_cursor - 1; i < wp->pathbar_len - 1; i++)
|
||||
wp->pathbar_text[i] = wp->pathbar_text[i + 1];
|
||||
wp->pathbar_len--;
|
||||
wp->pathbar_cursor--;
|
||||
wp->pathbar_text[wp->pathbar_len] = '\0';
|
||||
}
|
||||
montauk::utf8::erase_before(wp->pathbar_text, &wp->pathbar_len, &wp->pathbar_cursor);
|
||||
return;
|
||||
}
|
||||
if (key.scancode == 0x4B) {
|
||||
if (wp->pathbar_cursor > 0) wp->pathbar_cursor--;
|
||||
if (montauk::nav_scancode(key) == 0x4B) {
|
||||
wp->pathbar_cursor = montauk::utf8::prev_boundary(wp->pathbar_text, wp->pathbar_cursor);
|
||||
return;
|
||||
}
|
||||
if (key.scancode == 0x4D) {
|
||||
if (wp->pathbar_cursor < wp->pathbar_len) wp->pathbar_cursor++;
|
||||
if (montauk::nav_scancode(key) == 0x4D) {
|
||||
wp->pathbar_cursor = montauk::utf8::next_boundary(wp->pathbar_text, wp->pathbar_len, wp->pathbar_cursor);
|
||||
return;
|
||||
}
|
||||
if ((unsigned char)key.ascii >= 32
|
||||
&& (unsigned char)key.ascii != 127 && wp->pathbar_len < 254) {
|
||||
for (int i = wp->pathbar_len; i > wp->pathbar_cursor; i--)
|
||||
wp->pathbar_text[i] = wp->pathbar_text[i - 1];
|
||||
wp->pathbar_text[wp->pathbar_cursor] = key.ascii;
|
||||
wp->pathbar_cursor++;
|
||||
wp->pathbar_len++;
|
||||
wp->pathbar_text[wp->pathbar_len] = '\0';
|
||||
}
|
||||
montauk::utf8::insert_key(wp->pathbar_text, &wp->pathbar_len, 255, &wp->pathbar_cursor, key);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -736,22 +752,22 @@ void wp_handle_key(const montauk::abi::KeyEvent& key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.ctrl && key.alt && key.scancode == 0x48) {
|
||||
if (key.ctrl && key.alt && montauk::nav_scancode(key) == 0x48) {
|
||||
wp_adjust_paragraph_spacing_before(wp, -WP_SPACE_STEP);
|
||||
wp_history_checkpoint(wp);
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && key.alt && key.scancode == 0x50) {
|
||||
if (key.ctrl && key.alt && montauk::nav_scancode(key) == 0x50) {
|
||||
wp_adjust_paragraph_spacing_before(wp, WP_SPACE_STEP);
|
||||
wp_history_checkpoint(wp);
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && key.shift && key.scancode == 0x48) {
|
||||
if (key.ctrl && key.shift && montauk::nav_scancode(key) == 0x48) {
|
||||
wp_adjust_paragraph_spacing_after(wp, -WP_SPACE_STEP);
|
||||
wp_history_checkpoint(wp);
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && key.shift && key.scancode == 0x50) {
|
||||
if (key.ctrl && key.shift && montauk::nav_scancode(key) == 0x50) {
|
||||
wp_adjust_paragraph_spacing_after(wp, WP_SPACE_STEP);
|
||||
wp_history_checkpoint(wp);
|
||||
return;
|
||||
@@ -783,21 +799,21 @@ void wp_handle_key(const montauk::abi::KeyEvent& key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.scancode == 0x48) {
|
||||
if (montauk::nav_scancode(key) == 0x48) {
|
||||
if (key.shift) wp_start_selection(wp);
|
||||
else wp_clear_selection(wp);
|
||||
wp_cursor_up(wp);
|
||||
if (key.shift) wp_update_selection_to_cursor(wp);
|
||||
return;
|
||||
}
|
||||
if (key.scancode == 0x50) {
|
||||
if (montauk::nav_scancode(key) == 0x50) {
|
||||
if (key.shift) wp_start_selection(wp);
|
||||
else wp_clear_selection(wp);
|
||||
wp_cursor_down(wp);
|
||||
if (key.shift) wp_update_selection_to_cursor(wp);
|
||||
return;
|
||||
}
|
||||
if (key.scancode == 0x4B) {
|
||||
if (montauk::nav_scancode(key) == 0x4B) {
|
||||
if (key.shift) wp_start_selection(wp);
|
||||
else if (wp->has_selection) {
|
||||
int s, e;
|
||||
@@ -810,7 +826,7 @@ void wp_handle_key(const montauk::abi::KeyEvent& key) {
|
||||
if (key.shift) wp_update_selection_to_cursor(wp);
|
||||
return;
|
||||
}
|
||||
if (key.scancode == 0x4D) {
|
||||
if (montauk::nav_scancode(key) == 0x4D) {
|
||||
if (key.shift) wp_start_selection(wp);
|
||||
else if (wp->has_selection) {
|
||||
int s, e;
|
||||
@@ -824,7 +840,7 @@ void wp_handle_key(const montauk::abi::KeyEvent& key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.scancode == 0x47) {
|
||||
if (montauk::nav_scancode(key) == 0x47) {
|
||||
if (key.shift) wp_start_selection(wp);
|
||||
else wp_clear_selection(wp);
|
||||
int abs = wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset);
|
||||
@@ -834,7 +850,7 @@ void wp_handle_key(const montauk::abi::KeyEvent& key) {
|
||||
if (key.shift) wp_update_selection_to_cursor(wp);
|
||||
return;
|
||||
}
|
||||
if (key.scancode == 0x4F) {
|
||||
if (montauk::nav_scancode(key) == 0x4F) {
|
||||
if (key.shift) wp_start_selection(wp);
|
||||
else wp_clear_selection(wp);
|
||||
int abs = wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset);
|
||||
@@ -858,7 +874,7 @@ void wp_handle_key(const montauk::abi::KeyEvent& key) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.scancode == 0x53) {
|
||||
if (montauk::nav_scancode(key) == 0x53) {
|
||||
if (wp->has_selection) wp_delete_selection(wp);
|
||||
else wp_delete_char(wp);
|
||||
wp_history_checkpoint(wp);
|
||||
|
||||
@@ -217,7 +217,7 @@ static void wp_draw_print_list_marker(Canvas& c, WordProcessorState* wp, WrapLin
|
||||
if (!font) font = g_ui_font;
|
||||
int th = text_height(font, font_px);
|
||||
int top_y = py + (wl->height - th) / 2;
|
||||
char bullet[2] = { '\x95', '\0' };
|
||||
const char* bullet = "\u2022";
|
||||
draw_text(c, font, bullet_x, top_y, bullet, colors::BLACK, font_px);
|
||||
return;
|
||||
}
|
||||
@@ -293,7 +293,8 @@ static bool wp_encode_print_page(WordProcessorState* wp,
|
||||
|
||||
if (ch != '\n' && (ch >= 32 || ch < 0)) {
|
||||
int adv = font->draw_char_to_buffer(
|
||||
c.pixels, c.w, c.h, x, baseline, (unsigned char)ch, colors::BLACK, gc);
|
||||
c.pixels, c.w, c.h, x, baseline,
|
||||
decode_single_byte_codepoint((unsigned char)ch), colors::BLACK, gc);
|
||||
x += adv;
|
||||
}
|
||||
char_idx++;
|
||||
|
||||
@@ -111,7 +111,8 @@ static void wp_fit_ui_text(const char* src, char* out, int out_len, int max_w) {
|
||||
if (text_width(g_ui_font, out, fonts::UI_SIZE) <= max_w) return;
|
||||
|
||||
int slen = montauk::slen(src);
|
||||
for (int keep = slen; keep > 1; keep--) {
|
||||
for (int keep = slen; keep > 1;
|
||||
keep = slen - montauk::utf8::next_boundary(src, slen, slen - keep)) {
|
||||
char candidate[256];
|
||||
candidate[0] = '.';
|
||||
candidate[1] = '.';
|
||||
@@ -138,7 +139,8 @@ static void wp_fit_ui_text_end(const char* src, char* out, int out_len, int max_
|
||||
out[out_len - 1] = '\0';
|
||||
if (text_width(g_ui_font, out, fonts::UI_SIZE) <= max_w) return;
|
||||
|
||||
for (int keep = montauk::slen(src); keep > 0; keep--) {
|
||||
for (int keep = montauk::slen(src); keep > 0;
|
||||
keep = montauk::utf8::prev_boundary(src, keep)) {
|
||||
char candidate[256];
|
||||
if (keep > (int)sizeof(candidate) - 3) continue;
|
||||
montauk::memcpy(candidate, src, keep);
|
||||
@@ -225,7 +227,7 @@ static void wp_draw_list_marker(Canvas& c, WordProcessorState* wp, WrapLine* wl,
|
||||
if (!font) font = g_ui_font;
|
||||
int th = text_height(font, bullet_size);
|
||||
int top_y = py + (wl->height - th) / 2;
|
||||
char bullet[2] = { '\x95', '\0' };
|
||||
const char* bullet = "\u2022";
|
||||
draw_text(c, font, bullet_x, top_y, bullet, colors::TEXT_COLOR, bullet_size);
|
||||
return;
|
||||
}
|
||||
@@ -346,7 +348,7 @@ void wp_render() {
|
||||
Color special_bg = wp->special_char_flyout_open ? btn_active : btn_bg;
|
||||
c.fill_rounded_rect(WP_BTN_SECTION_X, 6, 24, 24, 3, special_bg);
|
||||
{
|
||||
char section[2] = { (char)0xA7, '\0' };
|
||||
const char* section = "\u00A7";
|
||||
TrueTypeFont* font = wp_get_font(wp_default_font_id(), 0);
|
||||
draw_text(c, font ? font : g_ui_font, WP_BTN_SECTION_X + 6, 8, section, colors::TEXT_COLOR, fonts::UI_SIZE);
|
||||
}
|
||||
@@ -355,7 +357,7 @@ void wp_render() {
|
||||
|
||||
Color pageview_bg = wp->page_view_mode ? btn_active : btn_bg;
|
||||
c.fill_rounded_rect(WP_BTN_PAGEVIEW_X, 6, 24, 24, 3, pageview_bg);
|
||||
wp_draw_ui_icon_button(c, WP_BTN_PAGEVIEW_X, 6, 24, 24, g_icon_page_view, "\xA7",
|
||||
wp_draw_ui_icon_button(c, WP_BTN_PAGEVIEW_X, 6, 24, 24, g_icon_page_view, "\u00A7",
|
||||
pageview_bg, colors::TEXT_COLOR);
|
||||
|
||||
c.hline(0, WP_TOOLBAR_H - 1, c.w, colors::BORDER);
|
||||
@@ -506,7 +508,7 @@ void wp_render() {
|
||||
|
||||
int char_adv = 0;
|
||||
if (ch != '\n' && (ch >= 32 || ch < 0)) {
|
||||
CachedGlyph* g = font->get_glyph(gc, (unsigned char)ch);
|
||||
CachedGlyph* g = font->get_glyph(gc, decode_single_byte_codepoint((unsigned char)ch));
|
||||
char_adv = g ? g->advance : 8;
|
||||
}
|
||||
|
||||
@@ -558,7 +560,8 @@ void wp_render() {
|
||||
Color text_col = (wp->has_selection && abs_ch >= sel_s && abs_ch < sel_e)
|
||||
? Color::from_rgb(0x10, 0x10, 0x10) : colors::TEXT_COLOR;
|
||||
int adv = font->draw_char_to_buffer(
|
||||
c.pixels, c.w, c.h, x, baseline, (unsigned char)ch, text_col, gc);
|
||||
c.pixels, c.w, c.h, x, baseline,
|
||||
decode_single_byte_codepoint((unsigned char)ch), text_col, gc);
|
||||
x += adv;
|
||||
} else {
|
||||
x += char_adv;
|
||||
@@ -717,7 +720,9 @@ void wp_render() {
|
||||
c.rect(dx, dy, dw, dh, colors::BORDER);
|
||||
for (int i = 0; i < WP_SPECIAL_CHAR_OPTION_COUNT; i++) {
|
||||
int iy = dy + 2 + i * WP_SPECIAL_CHAR_ROW_H;
|
||||
char glyph[2] = { (char)WP_SPECIAL_CHAR_OPTIONS[i].code, '\0' };
|
||||
char glyph[5];
|
||||
glyph[montauk::utf8::encode(montauk::utf8::from_cp1252(
|
||||
WP_SPECIAL_CHAR_OPTIONS[i].code), glyph)] = '\0';
|
||||
c.fill_rect(dx + 2, iy, dw - 4, WP_SPECIAL_CHAR_ROW_H - 2, colors::MENU_BG);
|
||||
draw_text(c, glyph_font ? glyph_font : g_ui_font,
|
||||
dx + 8, iy + (WP_SPECIAL_CHAR_ROW_H - sfh) / 2,
|
||||
|
||||
@@ -193,6 +193,10 @@ struct WPFontTable {
|
||||
bool loaded;
|
||||
};
|
||||
|
||||
// Document text is Windows-1252, one byte per character, unlike the rest of
|
||||
// MontaukOS (UTF-8). It matches the MWP1 file format and the WinAnsi-encoded
|
||||
// fonts of PDF export. Convert at the edges: decode_single_byte_codepoint()
|
||||
// for glyphs, and UTF-8 <-> 1252 for the clipboard (input.cpp).
|
||||
struct StyledRun {
|
||||
char* text;
|
||||
int len;
|
||||
|
||||
Reference in new issue
Block a user