diff options
| -rw-r--r-- | FL/fl_utf8.h | 5 | ||||
| -rw-r--r-- | src/Fl_Input.cxx | 18 | ||||
| -rw-r--r-- | src/Fl_Input_.cxx | 7 | ||||
| -rw-r--r-- | src/Fl_Text_Buffer.cxx | 16 | ||||
| -rw-r--r-- | src/Fl_Text_Display.cxx | 5 | ||||
| -rw-r--r-- | src/fl_utf8.cxx | 60 |
6 files changed, 88 insertions, 23 deletions
diff --git a/FL/fl_utf8.h b/FL/fl_utf8.h index 9bf562f3f..c55db12e9 100644 --- a/FL/fl_utf8.h +++ b/FL/fl_utf8.h @@ -1,7 +1,7 @@ /* * Author: Jean-Marc Lienher ( http://oksid.ch ) * Copyright 2000-2010 by O'ksi'D. - * Copyright 2016-2021 by Bill Spitzak and others. + * Copyright 2016-2026 by Bill Spitzak and others. * * This library is free software. Distribution and use rights are outlined in * the file "COPYING" which should have been included with this file. If this @@ -205,6 +205,9 @@ FL_EXPORT void fl_make_path_for_file( const char *path ); /* OD: recursively create a path in the file system */ FL_EXPORT char fl_make_path( const char *path ); +FL_EXPORT const char *fl_utf8_next_composed_char(const char *from, const char *end); + +FL_EXPORT const char *fl_utf8_previous_composed_char(const char *from, const char *begin); /** @} */ diff --git a/src/Fl_Input.cxx b/src/Fl_Input.cxx index fd66d8f24..bd317ad09 100644 --- a/src/Fl_Input.cxx +++ b/src/Fl_Input.cxx @@ -1,7 +1,7 @@ // // Input widget for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2024 by Bill Spitzak and others. +// Copyright 1998-2026 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -190,14 +190,20 @@ int Fl_Input::kf_delete_eol() { int Fl_Input::kf_delete_char_right() { if (readonly()) { fl_beep(); return 1; } if (mark() != insert_position()) cut(); - else cut(1); + else { + const char *next = fl_utf8_next_composed_char(value() + insert_position(), value() + size()); + replace(insert_position(), next - value(), 0); + } return 1; } int Fl_Input::kf_delete_char_left() { if (readonly()) { fl_beep(); return 1; } if (mark() != insert_position()) cut(); - else cut(-1); + else { + const char *before = fl_utf8_previous_composed_char(value() + insert_position(), value()); + replace(insert_position(), before - value(), 0); + } return 1; } @@ -225,7 +231,8 @@ int Fl_Input::kf_clear_eol() { // If OPTION_ARROW_FOCUS is disabled, return 1 to prevent focus navigation. // int Fl_Input::kf_move_char_left() { - int i = shift_position(insert_position()-1) + NORMAL_INPUT_MOVE; + const char *before = fl_utf8_previous_composed_char(value() + insert_position(), value()); + int i = shift_position(before - value()) + NORMAL_INPUT_MOVE; return Fl::option(Fl::OPTION_ARROW_FOCUS) ? i : 1; } @@ -233,7 +240,8 @@ int Fl_Input::kf_move_char_left() { // If OPTION_ARROW_FOCUS is disabled, return 1 to prevent focus navigation. // int Fl_Input::kf_move_char_right() { - int i = shift_position(insert_position()+1) + NORMAL_INPUT_MOVE; + const char *next = fl_utf8_next_composed_char(value() + insert_position(), value() + size()); + int i = shift_position(next - value()) + NORMAL_INPUT_MOVE; return Fl::option(Fl::OPTION_ARROW_FOCUS) ? i : 1; } diff --git a/src/Fl_Input_.cxx b/src/Fl_Input_.cxx index 18945c242..c052de10d 100644 --- a/src/Fl_Input_.cxx +++ b/src/Fl_Input_.cxx @@ -1,7 +1,7 @@ // // Common input widget routines for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2011 by Bill Spitzak and others. +// Copyright 1998-2026 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -658,8 +658,7 @@ void Fl_Input_::handle_mouse(int X, int Y, int /*W*/, int /*H*/, int drag) { const char *l, *r, *t; double f0 = Fl::event_x()-X+xscroll_; for (l = p, r = e; l<r; ) { double f; - int cw = fl_utf8len((char)l[0]); - if (cw < 1) cw = 1; + int cw = fl_utf8_next_composed_char(l, value() + size()) - l; t = l+cw; f = X-xscroll_+expandpos(p, t, buf, 0); if (f <= Fl::event_x()) {l = t; f0 = Fl::event_x()-f;} @@ -667,7 +666,7 @@ void Fl_Input_::handle_mouse(int X, int Y, int /*W*/, int /*H*/, int drag) { } if (l < e) { // see if closer to character on right: double f1; - int cw = fl_utf8len((char)l[0]); + int cw = fl_utf8_next_composed_char(l, value() + size()) - l; if (cw > 0) { f1 = X-xscroll_+expandpos(p, l + cw, buf, 0) - Fl::event_x(); if (f1 < f0) l = l+cw; diff --git a/src/Fl_Text_Buffer.cxx b/src/Fl_Text_Buffer.cxx index c33e36d3a..907a5cce6 100644 --- a/src/Fl_Text_Buffer.cxx +++ b/src/Fl_Text_Buffer.cxx @@ -1,5 +1,5 @@ // -// Copyright 2001-2023 by Bill Spitzak and others. +// Copyright 2001-2026 by Bill Spitzak and others. // Original code Copyright Mark Edel. Permission to distribute under // the LGPL for the FLTK library granted by Mark Edel. // @@ -2090,14 +2090,8 @@ int Fl_Text_Buffer::prev_char_clipped(int pos) const return 0; IS_UTF8_ALIGNED2(this, (pos)) - - char c; - do { - pos--; - if (pos==0) - return 0; - c = byte_at(pos); - } while ( (c&0xc0) == 0x80); + const char *previous = fl_utf8_previous_composed_char(address(0) + pos, address(0)); + pos = previous - address(0); IS_UTF8_ALIGNED2(this, (pos)) return pos; @@ -2122,8 +2116,8 @@ int Fl_Text_Buffer::prev_char(int pos) const int Fl_Text_Buffer::next_char(int pos) const { IS_UTF8_ALIGNED2(this, (pos)) - int n = fl_utf8len1(byte_at(pos)); - pos += n; + const char *next = fl_utf8_next_composed_char(address(0) + pos, address(0) + mLength); + pos = next - address(0); if (pos>=mLength) return mLength; IS_UTF8_ALIGNED2(this, (pos)) diff --git a/src/Fl_Text_Display.cxx b/src/Fl_Text_Display.cxx index 7f14623d6..9e0aacc2f 100644 --- a/src/Fl_Text_Display.cxx +++ b/src/Fl_Text_Display.cxx @@ -1,5 +1,5 @@ // -// Copyright 2001-2022 by Bill Spitzak and others. +// Copyright 2001-2026 by Bill Spitzak and others. // Original code Copyright Mark Edel. Permission to distribute under // the LGPL for the FLTK library granted by Mark Edel. // @@ -2263,7 +2263,8 @@ int Fl_Text_Display::find_x(const char *s, int len, int style, int x) const { int i = 0; int last_w = 0; // STR #2788 while (i<len) { - int cl = fl_utf8len1(s[i]); + const char *next = fl_utf8_next_composed_char(s + i, s + len); + int cl = next - (s+i); int w = int( string_width(s, i+cl, style) ); if (w>x) { if (cursor_pos && (w-x < x-last_w)) return i+cl; // STR #2788 diff --git a/src/fl_utf8.cxx b/src/fl_utf8.cxx index 6a69e0780..df4c6d423 100644 --- a/src/fl_utf8.cxx +++ b/src/fl_utf8.cxx @@ -1629,4 +1629,64 @@ unsigned fl_utf8from_mb(char* dst, unsigned dstlen, const char* src, unsigned sr return Fl::system_driver()->utf8from_mb(dst, dstlen, src, srclen); } + +/** + Returns pointer to beginning of next unicode character after potentially composed character. + Some unicode characters (example: 👩✈️ "woman pilot") are composed of several unicode points. They may pair two successive + codepoints with U+200D (zero-width joiner) and may qualify any component with variation selectors or Fitzpatrick emoji modifiers. + \param from points to a location within a UTF8 string. If this location is inside the UTF8 + encoding of a codepoint or is an invalid byte, this function returns \p from + 1. + \param end points past last codepoint of the string. + \return pointer to start of first codepoint after potentially composed character beginning at \p from. + */ +const char *fl_utf8_next_composed_char(const char *from, const char *end) { + int skip = fl_utf8len(*from); + if (skip == -1) return from + 1; + from += skip; // skip 1st codepoint + while (from < end) { + unsigned u = fl_utf8decode(from, end, NULL); + if (u == 0x200D) { // zero-width joiner + from += fl_utf8len(*from); // skip joiner + from += fl_utf8len(*from); // skip joined codepoint + } else if (u >= 0xFE00 && u <= 0xFE0F) { // a variation selector + from += fl_utf8len(*from); // skip variation selector + } else if (u >= 0x1F3FB && u <= 0x1F3FF) { // EMOJI MODIFIER FITZPATRICK + from += fl_utf8len(*from); // skip modifier + } else break; + } + return from; +} + + +/** + Returns pointer to beginning of previous potentially composed character before given unicode character. + See fl_utf8_next_composed_char() for a hint about what is a composed unicode character. + \param from points to a location within a UTF8 string. If this location is inside the UTF8 + encoding of a codepoint or is an invalid byte, this function returns \p from - 1. + \param begin points to start of first codepoint of the string. + \return pointer to start of first potentially composed character before the codepoint beginning at \p from. + */ +const char *fl_utf8_previous_composed_char(const char *from, const char *begin) { + if (from <= begin || fl_utf8len(*from) == -1) return from - 1; + const char *keep = from; + from = fl_utf8back(from - 1, begin, NULL); + while (from >= begin) { + unsigned u = fl_utf8decode(from, keep, NULL); + if (u >= 0xFE00 && u <= 0xFE0F) { // a variation selector + from = fl_utf8back(from - 1, begin, NULL); + } else if (u >= 0x1F3FB && u <= 0x1F3FF) { // EMOJI MODIFIER FITZPATRICK + from = fl_utf8back(from - 1, begin, NULL); + } else if (from > begin) { + keep = fl_utf8back(from - 1, begin, NULL); + u = fl_utf8decode(keep, from, NULL); + if (u == 0x200D) { // zero-width joiner + from = fl_utf8back(keep - 1, begin, NULL); + continue; + } + return from; + } else break; + } + return from; +} + /** @} */ |
