diff options
author | Harley Acheson <harley.acheson@gmail.com> | 2022-09-27 18:39:24 +0300 |
---|---|---|
committer | Harley Acheson <harley.acheson@gmail.com> | 2022-09-27 18:40:36 +0300 |
commit | 12fdf9069abe3cd2250a9efec6e059eb85ec59d8 (patch) | |
tree | a937501d90a2fd99e5ec63d4d99e6337fc4067ea /source/blender | |
parent | 697e86a76199c66267370f0222932b8fcb30dc3d (diff) |
BLF: Editing Text with Combining Characters
Corrections for caret insertion & movement and deletion for text
strings that include non-precomposed diacritical marks (Unicode
combining characters).
See D15659 for more details and examples.
Differential Revision: https://developer.blender.org/D15659
Reviewed by Campbell Barton
Diffstat (limited to 'source/blender')
-rw-r--r-- | source/blender/blenfont/intern/blf_font.c | 8 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/text.c | 26 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_string_cursor_utf8.h | 3 | ||||
-rw-r--r-- | source/blender/blenlib/intern/string_cursor_utf8.c | 59 | ||||
-rw-r--r-- | source/blender/blenlib/tests/BLI_string_utf8_test.cc | 483 | ||||
-rw-r--r-- | source/blender/editors/curve/editfont.c | 13 |
6 files changed, 543 insertions, 49 deletions
diff --git a/source/blender/blenfont/intern/blf_font.c b/source/blender/blenfont/intern/blf_font.c index cbf656289b5..2ee8cf088aa 100644 --- a/source/blender/blenfont/intern/blf_font.c +++ b/source/blender/blenfont/intern/blf_font.c @@ -33,6 +33,7 @@ #include "BLI_path_util.h" #include "BLI_rect.h" #include "BLI_string.h" +#include "BLI_string_cursor_utf8.h" #include "BLI_string_utf8.h" #include "BLI_threads.h" @@ -973,9 +974,16 @@ size_t blf_str_offset_from_cursor_position(struct FontBLF *font, .r_offset = (size_t)-1, }; blf_font_boundbox_foreach_glyph(font, str, str_len, blf_cursor_position_foreach_glyph, &data); + if (data.r_offset == (size_t)-1) { + /* We are to the right of the string, so return position of null terminator. */ data.r_offset = BLI_strnlen(str, str_len); } + else if (BLI_str_utf8_char_width(&str[data.r_offset]) < 1) { + /* This is a combining character (or invalid), so move to previous visible valid char. */ + BLI_str_cursor_step_prev_utf8(str, str_len, (int *)&data.r_offset); + } + return data.r_offset; } diff --git a/source/blender/blenkernel/intern/text.c b/source/blender/blenkernel/intern/text.c index c32ab64c478..1a0c0716fcd 100644 --- a/source/blender/blenkernel/intern/text.c +++ b/source/blender/blenkernel/intern/text.c @@ -896,8 +896,7 @@ void txt_move_left(Text *text, const bool sel) (*charp) -= tabsize; } else { - const char *prev = BLI_str_find_prev_char_utf8((*linep)->line + *charp, (*linep)->line); - *charp = prev - (*linep)->line; + BLI_str_cursor_step_prev_utf8((*linep)->line, (*linep)->len, charp); } } @@ -941,7 +940,7 @@ void txt_move_right(Text *text, const bool sel) (*charp) += tabsize; } else { - (*charp) += BLI_str_utf8_size((*linep)->line + *charp); + BLI_str_cursor_step_next_utf8((*linep)->line, (*linep)->len, charp); } } @@ -1757,8 +1756,6 @@ void txt_duplicate_line(Text *text) void txt_delete_char(Text *text) { - uint c = '\n'; - if (!text->curl) { return; } @@ -1778,10 +1775,9 @@ void txt_delete_char(Text *text) } } else { /* Just deleting a char */ - size_t c_len = text->curc; - c = BLI_str_utf8_as_unicode_step(text->curl->line, text->curl->len, &c_len); - c_len -= text->curc; - UNUSED_VARS(c); + int pos = text->curc; + BLI_str_cursor_step_next_utf8(text->curl->line, text->curl->len, &pos); + size_t c_len = pos - text->curc; memmove(text->curl->line + text->curc, text->curl->line + text->curc + c_len, @@ -1805,8 +1801,6 @@ void txt_delete_word(Text *text) void txt_backspace_char(Text *text) { - uint c = '\n'; - if (!text->curl) { return; } @@ -1828,13 +1822,9 @@ void txt_backspace_char(Text *text) txt_pop_sel(text); } else { /* Just backspacing a char */ - const char *prev = BLI_str_find_prev_char_utf8(text->curl->line + text->curc, - text->curl->line); - size_t c_len = prev - text->curl->line; - c = BLI_str_utf8_as_unicode_step(text->curl->line, text->curl->len, &c_len); - c_len -= prev - text->curl->line; - - UNUSED_VARS(c); + int pos = text->curc; + BLI_str_cursor_step_prev_utf8(text->curl->line, text->curl->len, &pos); + size_t c_len = text->curc - pos; /* source and destination overlap, don't use memcpy() */ memmove(text->curl->line + text->curc - c_len, diff --git a/source/blender/blenlib/BLI_string_cursor_utf8.h b/source/blender/blenlib/BLI_string_cursor_utf8.h index 70ba5da8e5a..9c0589b230a 100644 --- a/source/blender/blenlib/BLI_string_cursor_utf8.h +++ b/source/blender/blenlib/BLI_string_cursor_utf8.h @@ -25,6 +25,9 @@ typedef enum eStrCursorJumpDirection { bool BLI_str_cursor_step_next_utf8(const char *str, size_t maxlen, int *pos); bool BLI_str_cursor_step_prev_utf8(const char *str, size_t maxlen, int *pos); +bool BLI_str_cursor_step_next_utf32(const char32_t *str, size_t maxlen, int *pos); +bool BLI_str_cursor_step_prev_utf32(const char32_t *str, size_t maxlen, int *pos); + void BLI_str_cursor_step_utf8(const char *str, size_t maxlen, int *pos, diff --git a/source/blender/blenlib/intern/string_cursor_utf8.c b/source/blender/blenlib/intern/string_cursor_utf8.c index 6b7151be969..2405b134428 100644 --- a/source/blender/blenlib/intern/string_cursor_utf8.c +++ b/source/blender/blenlib/intern/string_cursor_utf8.c @@ -96,27 +96,35 @@ static eStrCursorDelimType cursor_delim_type_utf8(const char *ch_utf8, return cursor_delim_type_unicode(uch); } +/* Keep in sync with BLI_str_cursor_step_next_utf32. */ bool BLI_str_cursor_step_next_utf8(const char *str, size_t maxlen, int *pos) { + if ((*pos) >= (int)maxlen) { + return false; + } const char *str_end = str + (maxlen + 1); const char *str_pos = str + (*pos); - const char *str_next = BLI_str_find_next_char_utf8(str_pos, str_end); - if (str_next != str_end) { - (*pos) += (str_next - str_pos); - if ((*pos) > (int)maxlen) { - (*pos) = (int)maxlen; - } - return true; + const char *str_next = str_pos; + do { + str_next = BLI_str_find_next_char_utf8(str_next, str_end); + } while (str_next < str_end && str_next[0] != 0 && BLI_str_utf8_char_width(str_next) < 1); + (*pos) += (str_next - str_pos); + if ((*pos) > (int)maxlen) { + (*pos) = (int)maxlen; } - return false; + return true; } -bool BLI_str_cursor_step_prev_utf8(const char *str, size_t UNUSED(maxlen), int *pos) +/* Keep in sync with BLI_str_cursor_step_prev_utf32. */ +bool BLI_str_cursor_step_prev_utf8(const char *str, size_t maxlen, int *pos) { - if ((*pos) > 0) { + if ((*pos) > 0 && (*pos) <= maxlen) { const char *str_pos = str + (*pos); - const char *str_prev = BLI_str_find_prev_char_utf8(str_pos, str); + const char *str_prev = str_pos; + do { + str_prev = BLI_str_find_prev_char_utf8(str_prev, str); + } while (str_prev > str && BLI_str_utf8_char_width(str_prev) == 0); (*pos) -= (str_pos - str_prev); return true; } @@ -202,26 +210,29 @@ void BLI_str_cursor_step_utf8(const char *str, } } -/* UTF32 version of BLI_str_cursor_step_utf8 (keep in sync!) - * less complex since it doesn't need to do multi-byte stepping. - */ - -/* Helper functions so we can match #BLI_str_cursor_step_utf8. */ -static bool cursor_step_next_utf32(const char32_t *UNUSED(str), size_t maxlen, int *pos) +/* Keep in sync with BLI_str_cursor_step_next_utf8. */ +bool BLI_str_cursor_step_next_utf32(const char32_t *str, size_t maxlen, int *pos) { if ((*pos) >= (int)maxlen) { return false; } - (*pos)++; + do { + (*pos)++; + } while (*pos < (int)maxlen && str[*pos] != 0 && BLI_wcwidth(str[*pos]) == 0); + return true; } -static bool cursor_step_prev_utf32(const char32_t *UNUSED(str), size_t UNUSED(maxlen), int *pos) +/* Keep in sync with BLI_str_cursor_step_prev_utf8. */ +bool BLI_str_cursor_step_prev_utf32(const char32_t *str, size_t UNUSED(maxlen), int *pos) { if ((*pos) <= 0) { return false; } - (*pos)--; + do { + (*pos)--; + } while (*pos > 0 && BLI_wcwidth(str[*pos]) == 0); + return true; } @@ -236,7 +247,7 @@ void BLI_str_cursor_step_utf32(const char32_t *str, if (direction == STRCUR_DIR_NEXT) { if (use_init_step) { - cursor_step_next_utf32(str, maxlen, pos); + BLI_str_cursor_step_next_utf32(str, maxlen, pos); } else { BLI_assert(jump == STRCUR_JUMP_DELIM); @@ -250,7 +261,7 @@ void BLI_str_cursor_step_utf32(const char32_t *str, * look at function cursor_delim_type_unicode() for complete * list of special character, ctr -> */ while ((*pos) < maxlen) { - if (cursor_step_next_utf32(str, maxlen, pos)) { + if (BLI_str_cursor_step_next_utf32(str, maxlen, pos)) { if ((jump != STRCUR_JUMP_ALL) && (delim_type != cursor_delim_type_unicode((uint)str[*pos]))) { break; @@ -264,7 +275,7 @@ void BLI_str_cursor_step_utf32(const char32_t *str, } else if (direction == STRCUR_DIR_PREV) { if (use_init_step) { - cursor_step_prev_utf32(str, maxlen, pos); + BLI_str_cursor_step_prev_utf32(str, maxlen, pos); } else { BLI_assert(jump == STRCUR_JUMP_DELIM); @@ -279,7 +290,7 @@ void BLI_str_cursor_step_utf32(const char32_t *str, * list of special character, ctr -> */ while ((*pos) > 0) { const int pos_prev = *pos; - if (cursor_step_prev_utf32(str, maxlen, pos)) { + if (BLI_str_cursor_step_prev_utf32(str, maxlen, pos)) { if ((jump != STRCUR_JUMP_ALL) && (delim_type != cursor_delim_type_unicode((uint)str[*pos]))) { /* left only: compensate for index/change in direction */ diff --git a/source/blender/blenlib/tests/BLI_string_utf8_test.cc b/source/blender/blenlib/tests/BLI_string_utf8_test.cc index 7179eef5adb..f0c34350e1b 100644 --- a/source/blender/blenlib/tests/BLI_string_utf8_test.cc +++ b/source/blender/blenlib/tests/BLI_string_utf8_test.cc @@ -4,6 +4,7 @@ #include "BLI_rand.h" #include "BLI_string.h" +#include "BLI_string_cursor_utf8.h" #include "BLI_string_utf8.h" #include "BLI_utildefines.h" @@ -393,3 +394,485 @@ TEST(string, Utf8AsUnicodeStep) } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf32_empty + * \{ */ + +TEST(string, StrCursorStepNextUtf32Empty) +{ + const char32_t empty[] = U""; + const size_t len = 0; + int pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_next_utf32(empty, len, &pos)); + pos = 1; + EXPECT_FALSE(BLI_str_cursor_step_next_utf32(empty, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf32_single + * \{ */ + +TEST(string, StrCursorStepNextUtf32Single) + +{ + const char32_t single[] = U"0"; + const size_t len = 1; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(single, len, &pos) && pos == 1); + EXPECT_FALSE(BLI_str_cursor_step_next_utf32(single, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf32_simple + * \{ */ + +TEST(string, StrCursorStepNextUtf32Simple) +{ + const char32_t simple[] = U"012"; + const size_t len = 3; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(simple, len, &pos) && pos == 1); + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(simple, len, &pos) && pos == 2); + EXPECT_FALSE(BLI_str_cursor_step_next_utf32(simple, len - 1, &pos)); + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(simple, len, &pos) && pos == 3); + EXPECT_FALSE(BLI_str_cursor_step_next_utf32(simple, len, &pos)); +} + + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf32_allcombining + * \{ */ + +TEST(string, StrCursorStepNextUtf32AllCombining) +{ + const char32_t allcombining[] = U"\u0300\u0300\u0300"; + const size_t len = 3; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(allcombining, len, &pos) && pos == 3); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(allcombining, len, &pos) && pos == 3); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(allcombining, len, &pos) && pos == 3); + pos = 3; + EXPECT_FALSE(BLI_str_cursor_step_next_utf32(allcombining, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf32_complex + * \{ */ + +TEST(string, StrCursorStepNextUtf32Complex) +{ + /* Combining character, "A", two combining characters, "B".*/ + const char32_t complex[] = U"\u0300\u0041\u0300\u0320\u0042"; + const size_t len = 5; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(complex, len, &pos) && pos == 1); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(complex, len, &pos) && pos == 4); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(complex, len, &pos) && pos == 4); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(complex, len, &pos) && pos == 4); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(complex, len, &pos) && pos == 5); + pos = 5; + EXPECT_FALSE(BLI_str_cursor_step_next_utf32(complex, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf32_invalid + * \{ */ + +TEST(string, StrCursorStepNextUtf32Invalid) +{ + /* Latin1 "À", tab, carriage return, linefeed, separated by combining characters.*/ + const char32_t invalid[] = U"\u00C0\u0300\u0009\u0300\u000D\u0300\u000A\u0300"; + const size_t len = 8; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(invalid, len, &pos) && pos == 2); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(invalid, len, &pos) && pos == 2); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(invalid, len, &pos) && pos == 4); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(invalid, len, &pos) && pos == 4); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(invalid, len, &pos) && pos == 6); + pos = 5; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(invalid, len, &pos) && pos == 6); + pos = 6; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(invalid, len, &pos) && pos == 8); + pos = 7; + EXPECT_TRUE(BLI_str_cursor_step_next_utf32(invalid, len, &pos) && pos == 8); + pos = 8; + EXPECT_FALSE(BLI_str_cursor_step_next_utf32(invalid, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf32_empty + * \{ */ + +TEST(string, StrCursorStepPrevUtf32Empty) +{ + const char32_t emtpy[] = U""; + const size_t len = 0; + int pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_prev_utf32(emtpy, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf32_single + * \{ */ + +TEST(string, StrCursorStepPrevUtf32Single) +{ + const char32_t single[] = U"0"; + const size_t len = 1; + int pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(single, len, &pos) && pos == 0); + EXPECT_FALSE(BLI_str_cursor_step_prev_utf32(single, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf32_simple + * \{ */ + +TEST(string, StrCursorStepPrevUtf32Simple) +{ + const char32_t simple[] = U"012"; + const size_t len = 3; + int pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(simple, len, &pos) && pos == 2); + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(simple, len, &pos) && pos == 1); + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(simple, len, &pos) && pos == 0); + EXPECT_FALSE(BLI_str_cursor_step_prev_utf32(simple, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf32_allcombining + * \{ */ + +TEST(string, StrCursorStepPrevUtf32AllCombining) +{ + const char32_t allcombining[] = U"\u0300\u0300\u0300"; + const size_t len = 3; + int pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(allcombining, len, &pos) && pos == 0); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(allcombining, len, &pos) && pos == 0); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(allcombining, len, &pos) && pos == 0); + pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_prev_utf32(allcombining, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf32_complex + * \{ */ + +TEST(string, StrCursorStepPrevUtf32Complex) +{ + /* Combining character, "A", two combining characters, "B".*/ + const char32_t complex[] = U"\u0300\u0041\u0300\u0320\u0042"; + const size_t len = 5; + int pos = 5; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(complex, len, &pos) && pos == 4); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(complex, len, &pos) && pos == 1); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(complex, len, &pos) && pos == 1); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(complex, len, &pos) && pos == 1); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(complex, len, &pos) && pos == 0); + pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_prev_utf32(complex, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf32_invalid + * \{ */ + +TEST(string, StrCursorStepPrevUtf32Invalid) +{ + /* Latin1 "À", tab, carriage return, linefeed, separated by combining characters.*/ + const char32_t invalid[] = U"\u00C0\u0300\u0009\u0300\u000D\u0300\u000A\u0300"; + const size_t len = 8; + int pos = 8; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(invalid, len, &pos) && pos == 6); + pos = 7; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(invalid, len, &pos) && pos == 6); + pos = 6; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(invalid, len, &pos) && pos == 4); + pos = 5; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(invalid, len, &pos) && pos == 4); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(invalid, len, &pos) && pos == 2); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(invalid, len, &pos) && pos == 2); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(invalid, len, &pos) && pos == 0); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf32(invalid, len, &pos) && pos == 0); + pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_prev_utf32(invalid, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf8_empty + * \{ */ +TEST(string, StrCursorStepNextUtf8Empty) +{ + const char empty[] = ""; + const size_t len = 0; + int pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_next_utf8(empty, len, &pos)); + pos = 1; + EXPECT_FALSE(BLI_str_cursor_step_next_utf8(empty, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf8_single + * \{ */ +TEST(string, StrCursorStepNextUtf8Single) +{ + const char single[] = "0"; + const size_t len = 1; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(single, len, &pos) && pos == 1); + EXPECT_FALSE(BLI_str_cursor_step_next_utf8(single, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf8_simple + * \{ */ + +TEST(string, StrCursorStepNextUtf8Simple) +{ + const char simple[] = "012"; + const size_t len = 3; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(simple, len, &pos) && pos == 1); + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(simple, len, &pos) && pos == 2); + EXPECT_FALSE(BLI_str_cursor_step_next_utf8(simple, len - 1, &pos)); + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(simple, len, &pos) && pos == 3); + EXPECT_FALSE(BLI_str_cursor_step_next_utf8(simple, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf8_allcombining + * \{ */ + +TEST(string, StrCursorStepNextUtf8AllCombining) +{ + const char allcombining[] = "\xCC\x80\xCC\x80\xCC\x80"; + const size_t len = 6; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(allcombining, len, &pos) && pos == 6); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(allcombining, len, &pos) && pos == 6); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(allcombining, len, &pos) && pos == 6); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(allcombining, len, &pos) && pos == 6); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(allcombining, len, &pos) && pos == 6); + pos = 5; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(allcombining, len, &pos) && pos == 6); + pos = 6; + EXPECT_FALSE(BLI_str_cursor_step_next_utf8(allcombining, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf8_complex + * \{ */ + +TEST(string, StrCursorStepNextUtf8AllComplex) +{ + /* Combining character, "A", "©", two combining characters, "B".*/ + const char complex[] = "\xCC\x80\x41\xC2\xA9\xCC\x80\xCC\xA0\x42"; + const size_t len = 10; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 2); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 2); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 3); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 9); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 9); + pos = 5; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 9); + pos = 6; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 9); + pos = 7; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 9); + pos = 8; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 9); + pos = 9; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(complex, len, &pos) && pos == 10); + pos = 10; + EXPECT_FALSE(BLI_str_cursor_step_next_utf8(complex, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_next_utf8_invalid + * \{ */ + +TEST(string, StrCursorStepNextUtf8Invalid) +{ + /* Latin1 "À", combining, tab, carriage return, linefeed, combining.*/ + const char invalid[] = "\xC0\xCC\x80\x09\x0D\x0A\xCC\x80"; + const size_t len = 8; + int pos = 0; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(invalid, len, &pos) && pos == 8); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(invalid, len, &pos) && pos == 8); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(invalid, len, &pos) && pos == 8); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(invalid, len, &pos) && pos == 8); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(invalid, len, &pos) && pos == 8); + pos = 5; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(invalid, len, &pos) && pos == 8); + pos = 6; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(invalid, len, &pos) && pos == 8); + pos = 7; + EXPECT_TRUE(BLI_str_cursor_step_next_utf8(invalid, len, &pos) && pos == 8); + pos = 8; + EXPECT_FALSE(BLI_str_cursor_step_next_utf8(invalid, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf8_empty + * \{ */ + +TEST(string, StrCursorStepPrevUtf8Empty) +{ + const char empty[] = ""; + const size_t len = 0; + int pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_prev_utf8(empty, len, &pos)); + pos = 1; + EXPECT_FALSE(BLI_str_cursor_step_prev_utf8(empty, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf8_single + * \{ */ + +TEST(string, StrCursorStepPrevUtf8Single) +{ + const char single[] = "0"; + const size_t len = 1; + int pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(single, len, &pos) && pos == 0); + EXPECT_FALSE(BLI_str_cursor_step_prev_utf8(single, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf8_single + * \{ */ + +TEST(string, StrCursorStepPrevUtf8Simple) +{ + const char simple[] = "012"; + const size_t len = 3; + int pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(simple, len, &pos) && pos == 2); + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(simple, len, &pos) && pos == 1); + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(simple, len, &pos) && pos == 0); + EXPECT_FALSE(BLI_str_cursor_step_prev_utf8(simple, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf8_allcombining + * \{ */ + +TEST(string, StrCursorStepPrevUtf8AllCombining) +{ + const char allcombining[] = "\xCC\x80\xCC\x80\xCC\x80"; + const size_t len = 6; + int pos = 6; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(allcombining, len, &pos) && pos == 0); + pos = 5; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(allcombining, len, &pos) && pos == 0); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(allcombining, len, &pos) && pos == 0); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(allcombining, len, &pos) && pos == 0); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(allcombining, len, &pos) && pos == 0); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(allcombining, len, &pos) && pos == 0); + pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_prev_utf8(allcombining, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf8_complex + * \{ */ + +TEST(string, StrCursorStepPrevUtf8Complex) +{ + /* Combining character, "A", "©", two combining characters, "B".*/ + const char complex[] = "\xCC\x80\x41\xC2\xA9\xCC\x80\xCC\xA0\x42"; + const size_t len = 10; + int pos = 10; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 9); + pos = 9; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 3); + pos = 8; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 3); + pos = 7; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 3); + pos = 6; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 3); + pos = 5; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 3); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 3); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 2); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 0); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(complex, len, &pos) && pos == 0); + pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_prev_utf8(complex, len, &pos)); +} + +/* -------------------------------------------------------------------- */ +/** \name Test #BLI_str_cursor_step_prev_utf8_invalid + * \{ */ + +TEST(string, StrCursorStepPrevUtf8Invalid) +{ + /* Latin1 "À", combining, tab, carriage return, linefeed, combining.*/ + const char invalid[] = "\xC0\xCC\x80\x09\x0D\x0A\xCC\x80"; + const size_t len = 8; + int pos = 8; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(invalid, len, &pos) && pos == 5); + pos= 7; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(invalid, len, &pos) && pos == 5); + pos = 6; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(invalid, len, &pos) && pos == 5); + pos = 5; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(invalid, len, &pos) && pos == 4); + pos = 4; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(invalid, len, &pos) && pos == 3); + pos = 3; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(invalid, len, &pos) && pos == 0); + pos = 2; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(invalid, len, &pos) && pos == 0); + pos = 1; + EXPECT_TRUE(BLI_str_cursor_step_prev_utf8(invalid, len, &pos) && pos == 0); + pos = 0; + EXPECT_FALSE(BLI_str_cursor_step_prev_utf8(invalid, len, &pos)); +} + +/** \} */ diff --git a/source/blender/editors/curve/editfont.c b/source/blender/editors/curve/editfont.c index 03c485a7671..8adc5173e22 100644 --- a/source/blender/editors/curve/editfont.c +++ b/source/blender/editors/curve/editfont.c @@ -1160,14 +1160,13 @@ static int move_cursor(bContext *C, int type, const bool select) } case PREV_CHAR: - ef->pos--; + BLI_str_cursor_step_prev_utf32(ef->textbuf, ef->len, &ef->pos); cursmove = FO_CURS; break; case NEXT_CHAR: - ef->pos++; + BLI_str_cursor_step_next_utf32(ef->textbuf, ef->len, &ef->pos); cursmove = FO_CURS; - break; case PREV_LINE: @@ -1506,10 +1505,9 @@ static int delete_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - range[0] = ef->pos - 1; range[1] = ef->pos; - - ef->pos--; + BLI_str_cursor_step_prev_utf32(ef->textbuf, ef->len, &ef->pos); + range[0] = ef->pos; break; case DEL_NEXT_CHAR: if (ef->pos >= ef->len) { @@ -1517,7 +1515,8 @@ static int delete_exec(bContext *C, wmOperator *op) } range[0] = ef->pos; - range[1] = ef->pos + 1; + range[1] = ef->pos; + BLI_str_cursor_step_next_utf32(ef->textbuf, ef->len, &range[1]); break; case DEL_NEXT_WORD: { int pos = ef->pos; |