mirror of
https://github.com/fltk/fltk.git
synced 2026-05-28 20:06:18 +08:00
Let Fl_Text_Editor and Fl_Input handle gracefully composed unicode characters.
This commit is contained in:
+4
-1
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Author: Jean-Marc Lienher ( http://oksid.ch )
|
* Author: Jean-Marc Lienher ( http://oksid.ch )
|
||||||
* Copyright 2000-2010 by O'ksi'D.
|
* 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
|
* 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
|
* 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 */
|
/* OD: recursively create a path in the file system */
|
||||||
FL_EXPORT char fl_make_path( const char *path );
|
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);
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
|
|||||||
+13
-5
@@ -1,7 +1,7 @@
|
|||||||
//
|
//
|
||||||
// Input widget for the Fast Light Tool Kit (FLTK).
|
// 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
|
// 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
|
// 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() {
|
int Fl_Input::kf_delete_char_right() {
|
||||||
if (readonly()) { fl_beep(); return 1; }
|
if (readonly()) { fl_beep(); return 1; }
|
||||||
if (mark() != insert_position()) cut();
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Fl_Input::kf_delete_char_left() {
|
int Fl_Input::kf_delete_char_left() {
|
||||||
if (readonly()) { fl_beep(); return 1; }
|
if (readonly()) { fl_beep(); return 1; }
|
||||||
if (mark() != insert_position()) cut();
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +231,8 @@ int Fl_Input::kf_clear_eol() {
|
|||||||
// If OPTION_ARROW_FOCUS is disabled, return 1 to prevent focus navigation.
|
// If OPTION_ARROW_FOCUS is disabled, return 1 to prevent focus navigation.
|
||||||
//
|
//
|
||||||
int Fl_Input::kf_move_char_left() {
|
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;
|
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.
|
// If OPTION_ARROW_FOCUS is disabled, return 1 to prevent focus navigation.
|
||||||
//
|
//
|
||||||
int Fl_Input::kf_move_char_right() {
|
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;
|
return Fl::option(Fl::OPTION_ARROW_FOCUS) ? i : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-4
@@ -1,7 +1,7 @@
|
|||||||
//
|
//
|
||||||
// Common input widget routines for the Fast Light Tool Kit (FLTK).
|
// 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
|
// 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
|
// 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_;
|
const char *l, *r, *t; double f0 = Fl::event_x()-X+xscroll_;
|
||||||
for (l = p, r = e; l<r; ) {
|
for (l = p, r = e; l<r; ) {
|
||||||
double f;
|
double f;
|
||||||
int cw = fl_utf8len((char)l[0]);
|
int cw = fl_utf8_next_composed_char(l, value() + size()) - l;
|
||||||
if (cw < 1) cw = 1;
|
|
||||||
t = l+cw;
|
t = l+cw;
|
||||||
f = X-xscroll_+expandpos(p, t, buf, 0);
|
f = X-xscroll_+expandpos(p, t, buf, 0);
|
||||||
if (f <= Fl::event_x()) {l = t; f0 = Fl::event_x()-f;}
|
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:
|
if (l < e) { // see if closer to character on right:
|
||||||
double f1;
|
double f1;
|
||||||
int cw = fl_utf8len((char)l[0]);
|
int cw = fl_utf8_next_composed_char(l, value() + size()) - l;
|
||||||
if (cw > 0) {
|
if (cw > 0) {
|
||||||
f1 = X-xscroll_+expandpos(p, l + cw, buf, 0) - Fl::event_x();
|
f1 = X-xscroll_+expandpos(p, l + cw, buf, 0) - Fl::event_x();
|
||||||
if (f1 < f0) l = l+cw;
|
if (f1 < f0) l = l+cw;
|
||||||
|
|||||||
+5
-11
@@ -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
|
// Original code Copyright Mark Edel. Permission to distribute under
|
||||||
// the LGPL for the FLTK library granted by Mark Edel.
|
// 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;
|
return 0;
|
||||||
|
|
||||||
IS_UTF8_ALIGNED2(this, (pos))
|
IS_UTF8_ALIGNED2(this, (pos))
|
||||||
|
const char *previous = fl_utf8_previous_composed_char(address(0) + pos, address(0));
|
||||||
char c;
|
pos = previous - address(0);
|
||||||
do {
|
|
||||||
pos--;
|
|
||||||
if (pos==0)
|
|
||||||
return 0;
|
|
||||||
c = byte_at(pos);
|
|
||||||
} while ( (c&0xc0) == 0x80);
|
|
||||||
|
|
||||||
IS_UTF8_ALIGNED2(this, (pos))
|
IS_UTF8_ALIGNED2(this, (pos))
|
||||||
return 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
|
int Fl_Text_Buffer::next_char(int pos) const
|
||||||
{
|
{
|
||||||
IS_UTF8_ALIGNED2(this, (pos))
|
IS_UTF8_ALIGNED2(this, (pos))
|
||||||
int n = fl_utf8len1(byte_at(pos));
|
const char *next = fl_utf8_next_composed_char(address(0) + pos, address(0) + mLength);
|
||||||
pos += n;
|
pos = next - address(0);
|
||||||
if (pos>=mLength)
|
if (pos>=mLength)
|
||||||
return mLength;
|
return mLength;
|
||||||
IS_UTF8_ALIGNED2(this, (pos))
|
IS_UTF8_ALIGNED2(this, (pos))
|
||||||
|
|||||||
@@ -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
|
// Original code Copyright Mark Edel. Permission to distribute under
|
||||||
// the LGPL for the FLTK library granted by Mark Edel.
|
// 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 i = 0;
|
||||||
int last_w = 0; // STR #2788
|
int last_w = 0; // STR #2788
|
||||||
while (i<len) {
|
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) );
|
int w = int( string_width(s, i+cl, style) );
|
||||||
if (w>x) {
|
if (w>x) {
|
||||||
if (cursor_pos && (w-x < x-last_w)) return i+cl; // STR #2788
|
if (cursor_pos && (w-x < x-last_w)) return i+cl; // STR #2788
|
||||||
|
|||||||
@@ -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);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|||||||
Reference in New Issue
Block a user