[TextServer] Fix system font fallback and caret/selection behavior for composite characters.
This commit is contained in:
parent
c495eb5102
commit
5d3fcc5766
|
@ -197,7 +197,7 @@
|
|||
<member name="caret_force_displayed" type="bool" setter="set_caret_force_displayed" getter="is_caret_force_displayed" default="false">
|
||||
If [code]true[/code], the [LineEdit] will always show the caret, even if focus is lost.
|
||||
</member>
|
||||
<member name="caret_mid_grapheme" type="bool" setter="set_caret_mid_grapheme_enabled" getter="is_caret_mid_grapheme_enabled" default="true">
|
||||
<member name="caret_mid_grapheme" type="bool" setter="set_caret_mid_grapheme_enabled" getter="is_caret_mid_grapheme_enabled" default="false">
|
||||
Allow moving caret, selecting and removing the individual composite character components.
|
||||
[b]Note:[/b] [kbd]Backspace[/kbd] is always removing individual composite character components.
|
||||
</member>
|
||||
|
|
|
@ -1100,7 +1100,7 @@
|
|||
<member name="caret_draw_when_editable_disabled" type="bool" setter="set_draw_caret_when_editable_disabled" getter="is_drawing_caret_when_editable_disabled" default="false">
|
||||
If [code]true[/code], caret will be visible when [member editable] is disabled.
|
||||
</member>
|
||||
<member name="caret_mid_grapheme" type="bool" setter="set_caret_mid_grapheme_enabled" getter="is_caret_mid_grapheme_enabled" default="true">
|
||||
<member name="caret_mid_grapheme" type="bool" setter="set_caret_mid_grapheme_enabled" getter="is_caret_mid_grapheme_enabled" default="false">
|
||||
Allow moving caret, selecting and removing the individual composite character components.
|
||||
[b]Note:[/b] [kbd]Backspace[/kbd] is always removing individual composite character components.
|
||||
</member>
|
||||
|
|
|
@ -1139,6 +1139,14 @@
|
|||
Clears text buffer (removes text and inline objects).
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_closest_character_pos" qualifiers="const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="pos" type="int" />
|
||||
<description>
|
||||
Returns composite character position closest to the [param pos].
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_draw" qualifiers="const">
|
||||
<return type="void" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
@ -1189,6 +1197,13 @@
|
|||
Returns shapes of the carets corresponding to the character offset [param position] in the text. Returned caret shape is 1 pixel wide rectangle.
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_get_character_breaks" qualifiers="const">
|
||||
<return type="PackedInt32Array" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<description>
|
||||
Returns array of the composite character boundaries.
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_get_custom_punctuation" qualifiers="const">
|
||||
<return type="String" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
@ -1432,7 +1447,7 @@
|
|||
Returns [code]true[/code] if buffer is successfully shaped.
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_next_grapheme_pos" qualifiers="const">
|
||||
<method name="shaped_text_next_character_pos" qualifiers="const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="pos" type="int" />
|
||||
|
@ -1440,6 +1455,14 @@
|
|||
Returns composite character end position closest to the [param pos].
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_next_grapheme_pos" qualifiers="const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="pos" type="int" />
|
||||
<description>
|
||||
Returns grapheme end position closest to the [param pos].
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_overrun_trim_to_width">
|
||||
<return type="void" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
@ -1449,7 +1472,7 @@
|
|||
Trims text if it exceeds the given width.
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_prev_grapheme_pos" qualifiers="const">
|
||||
<method name="shaped_text_prev_character_pos" qualifiers="const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="pos" type="int" />
|
||||
|
@ -1457,6 +1480,14 @@
|
|||
Returns composite character start position closest to the [param pos].
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_prev_grapheme_pos" qualifiers="const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="pos" type="int" />
|
||||
<description>
|
||||
Returns grapheme start position closest to the [param pos].
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_text_resize_object">
|
||||
<return type="bool" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
@ -1568,6 +1599,18 @@
|
|||
[b]Note:[/b] Always returns [code]false[/code] if the server does not support the [constant FEATURE_UNICODE_SECURITY] feature.
|
||||
</description>
|
||||
</method>
|
||||
<method name="string_get_character_breaks" qualifiers="const">
|
||||
<return type="PackedInt32Array" />
|
||||
<param index="0" name="string" type="String" />
|
||||
<param index="1" name="language" type="String" default="""" />
|
||||
<description>
|
||||
Returns array of the composite character boundaries.
|
||||
[codeblock]
|
||||
var ts = TextServerManager.get_primary_interface()
|
||||
print(ts.string_get_word_breaks("Test ❤️🔥 Test")) # Prints [1, 2, 3, 4, 5, 9, 10, 11, 12, 13, 14]
|
||||
[/codeblock]
|
||||
</description>
|
||||
</method>
|
||||
<method name="string_get_word_breaks" qualifiers="const">
|
||||
<return type="PackedInt32Array" />
|
||||
<param index="0" name="string" type="String" />
|
||||
|
|
|
@ -981,6 +981,13 @@
|
|||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_shaped_text_closest_character_pos" qualifiers="virtual const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="pos" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_shaped_text_draw" qualifiers="virtual const">
|
||||
<return type="void" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
@ -1026,6 +1033,12 @@
|
|||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_shaped_text_get_character_breaks" qualifiers="virtual const">
|
||||
<return type="PackedInt32Array" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_shaped_text_get_custom_punctuation" qualifiers="virtual const">
|
||||
<return type="String" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
@ -1229,6 +1242,13 @@
|
|||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_shaped_text_next_character_pos" qualifiers="virtual const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="pos" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_shaped_text_next_grapheme_pos" qualifiers="virtual const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
@ -1244,6 +1264,13 @@
|
|||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_shaped_text_prev_character_pos" qualifiers="virtual const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="pos" type="int" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_shaped_text_prev_grapheme_pos" qualifiers="virtual const">
|
||||
<return type="int" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
@ -1356,6 +1383,13 @@
|
|||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_string_get_character_breaks" qualifiers="virtual const">
|
||||
<return type="PackedInt32Array" />
|
||||
<param index="0" name="string" type="String" />
|
||||
<param index="1" name="language" type="String" />
|
||||
<description>
|
||||
</description>
|
||||
</method>
|
||||
<method name="_string_get_word_breaks" qualifiers="virtual const">
|
||||
<return type="PackedInt32Array" />
|
||||
<param index="0" name="string" type="String" />
|
||||
|
|
|
@ -3782,6 +3782,7 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *
|
|||
p_shaped->script_iter = nullptr;
|
||||
}
|
||||
p_shaped->break_ops_valid = false;
|
||||
p_shaped->chars_valid = false;
|
||||
p_shaped->js_ops_valid = false;
|
||||
}
|
||||
}
|
||||
|
@ -4835,6 +4836,76 @@ int64_t TextServerAdvanced::_shaped_text_get_ellipsis_glyph_count(const RID &p_s
|
|||
return sd->overrun_trim_data.ellipsis_glyph_buf.size();
|
||||
}
|
||||
|
||||
void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const {
|
||||
if (!p_sd->chars_valid) {
|
||||
p_sd->chars.clear();
|
||||
|
||||
const UChar *data = p_sd->utf16.get_data();
|
||||
UErrorCode err = U_ZERO_ERROR;
|
||||
int prev = -1;
|
||||
int i = 0;
|
||||
|
||||
Vector<ShapedTextDataAdvanced::Span> &spans = p_sd->spans;
|
||||
if (p_sd->parent != RID()) {
|
||||
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(p_sd->parent);
|
||||
ERR_FAIL_COND(!parent_sd->valid);
|
||||
spans = parent_sd->spans;
|
||||
}
|
||||
|
||||
while (i < spans.size()) {
|
||||
if (spans[i].start > p_sd->end) {
|
||||
break;
|
||||
}
|
||||
if (spans[i].end < p_sd->start) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int r_start = MAX(0, spans[i].start - p_sd->start);
|
||||
String language = spans[i].language;
|
||||
while (i + 1 < spans.size() && language == spans[i + 1].language) {
|
||||
i++;
|
||||
}
|
||||
int r_end = MIN(spans[i].end - p_sd->start, p_sd->text.size());
|
||||
|
||||
UBreakIterator *bi = ubrk_open(UBRK_CHARACTER, (language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale().ascii().get_data() : language.ascii().get_data(), data + _convert_pos_inv(p_sd, r_start), _convert_pos_inv(p_sd, r_end - r_start), &err);
|
||||
if (U_SUCCESS(err)) {
|
||||
while (ubrk_next(bi) != UBRK_DONE) {
|
||||
int pos = _convert_pos(p_sd, ubrk_current(bi)) + r_start + p_sd->start;
|
||||
if (prev != pos) {
|
||||
p_sd->chars.push_back(pos);
|
||||
}
|
||||
prev = pos;
|
||||
}
|
||||
ubrk_close(bi);
|
||||
} else {
|
||||
for (int j = r_start; j <= r_end; j++) {
|
||||
if (prev != j) {
|
||||
p_sd->chars.push_back(j + p_sd->start);
|
||||
}
|
||||
prev = j;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
p_sd->chars_valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID &p_shaped) const {
|
||||
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_COND_V(!sd, PackedInt32Array());
|
||||
|
||||
MutexLock lock(sd->mutex);
|
||||
if (!sd->valid) {
|
||||
const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped);
|
||||
}
|
||||
|
||||
_update_chars(sd);
|
||||
|
||||
return sd->chars;
|
||||
}
|
||||
|
||||
bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
|
||||
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_COND_V(!sd, false);
|
||||
|
@ -5338,7 +5409,17 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
|
|||
// Try system fallback.
|
||||
RID fdef = p_fonts[0];
|
||||
if (_font_is_allow_system_fallback(fdef)) {
|
||||
String text = p_sd->text.substr(p_start, 1);
|
||||
_update_chars(p_sd);
|
||||
|
||||
int64_t next = p_end;
|
||||
for (const int32_t &E : p_sd->chars) {
|
||||
if (E > p_start) {
|
||||
next = E;
|
||||
break;
|
||||
}
|
||||
}
|
||||
String text = p_sd->text.substr(p_start, next - p_start);
|
||||
|
||||
String font_name = _font_get_name(fdef);
|
||||
BitField<FontStyle> font_style = _font_get_style(fdef);
|
||||
int font_weight = _font_get_weight(fdef);
|
||||
|
@ -6602,6 +6683,30 @@ PackedInt32Array TextServerAdvanced::_string_get_word_breaks(const String &p_str
|
|||
return ret;
|
||||
}
|
||||
|
||||
PackedInt32Array TextServerAdvanced::_string_get_character_breaks(const String &p_string, const String &p_language) const {
|
||||
const String lang = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language;
|
||||
// Convert to UTF-16.
|
||||
Char16String utf16 = p_string.utf16();
|
||||
|
||||
PackedInt32Array ret;
|
||||
|
||||
UErrorCode err = U_ZERO_ERROR;
|
||||
UBreakIterator *bi = ubrk_open(UBRK_CHARACTER, lang.ascii().get_data(), (const UChar *)utf16.get_data(), utf16.length(), &err);
|
||||
if (U_SUCCESS(err)) {
|
||||
while (ubrk_next(bi) != UBRK_DONE) {
|
||||
int pos = _convert_pos(p_string, utf16, ubrk_current(bi));
|
||||
ret.push_back(pos);
|
||||
}
|
||||
ubrk_close(bi);
|
||||
} else {
|
||||
for (int i = 0; i <= p_string.size(); i++) {
|
||||
ret.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TextServerAdvanced::_is_valid_identifier(const String &p_string) const {
|
||||
#ifndef ICU_STATIC_DATA
|
||||
if (!icu_data_loaded) {
|
||||
|
|
|
@ -509,9 +509,11 @@ class TextServerAdvanced : public TextServerExtension {
|
|||
|
||||
HashMap<int, bool> jstops;
|
||||
HashMap<int, bool> breaks;
|
||||
PackedInt32Array chars;
|
||||
int break_inserts = 0;
|
||||
bool break_ops_valid = false;
|
||||
bool js_ops_valid = false;
|
||||
bool chars_valid = false;
|
||||
|
||||
~ShapedTextDataAdvanced() {
|
||||
for (int i = 0; i < bidi_iter.size(); i++) {
|
||||
|
@ -609,6 +611,7 @@ class TextServerAdvanced : public TextServerExtension {
|
|||
mutable HashMap<SystemFontKey, SystemFontCache, SystemFontKeyHasher> system_fonts;
|
||||
mutable HashMap<String, PackedByteArray> system_font_data;
|
||||
|
||||
void _update_chars(ShapedTextDataAdvanced *p_sd) const;
|
||||
void _realign(ShapedTextDataAdvanced *p_sd) const;
|
||||
int64_t _convert_pos(const String &p_utf32, const Char16String &p_utf16, int64_t p_pos) const;
|
||||
int64_t _convert_pos(const ShapedTextDataAdvanced *p_sd, int64_t p_pos) const;
|
||||
|
@ -920,11 +923,14 @@ public:
|
|||
MODBIND1RC(double, shaped_text_get_underline_position, const RID &);
|
||||
MODBIND1RC(double, shaped_text_get_underline_thickness, const RID &);
|
||||
|
||||
MODBIND1RC(PackedInt32Array, shaped_text_get_character_breaks, const RID &);
|
||||
|
||||
MODBIND2RC(String, format_number, const String &, const String &);
|
||||
MODBIND2RC(String, parse_number, const String &, const String &);
|
||||
MODBIND1RC(String, percent_sign, const String &);
|
||||
|
||||
MODBIND3RC(PackedInt32Array, string_get_word_breaks, const String &, const String &, int64_t);
|
||||
MODBIND2RC(PackedInt32Array, string_get_character_breaks, const String &, const String &);
|
||||
|
||||
MODBIND2RC(int64_t, is_confusable, const String &, const PackedStringArray &);
|
||||
MODBIND1RC(bool, spoof_check, const String &);
|
||||
|
|
|
@ -4079,6 +4079,20 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha
|
|||
return sd->uthk;
|
||||
}
|
||||
|
||||
PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID &p_shaped) const {
|
||||
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_COND_V(!sd, PackedInt32Array());
|
||||
|
||||
MutexLock lock(sd->mutex);
|
||||
|
||||
PackedInt32Array ret;
|
||||
ret.resize(sd->end - sd->start);
|
||||
for (int i = sd->start; i < sd->end; i++) {
|
||||
ret.write[i] = i;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
String TextServerFallback::_string_to_upper(const String &p_string, const String &p_language) const {
|
||||
return p_string.to_upper();
|
||||
}
|
||||
|
|
|
@ -788,6 +788,8 @@ public:
|
|||
MODBIND1RC(double, shaped_text_get_underline_position, const RID &);
|
||||
MODBIND1RC(double, shaped_text_get_underline_thickness, const RID &);
|
||||
|
||||
MODBIND1RC(PackedInt32Array, shaped_text_get_character_breaks, const RID &);
|
||||
|
||||
MODBIND3RC(PackedInt32Array, string_get_word_breaks, const String &, const String &, int64_t);
|
||||
|
||||
MODBIND2RC(String, string_to_upper, const String &, const String &);
|
||||
|
|
|
@ -79,7 +79,7 @@ void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
|
|||
if (caret_mid_grapheme_enabled) {
|
||||
set_caret_column(get_caret_column() - 1);
|
||||
} else {
|
||||
set_caret_column(TS->shaped_text_prev_grapheme_pos(text_rid, get_caret_column()));
|
||||
set_caret_column(TS->shaped_text_prev_character_pos(text_rid, get_caret_column()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ void LineEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
|
|||
if (caret_mid_grapheme_enabled) {
|
||||
set_caret_column(get_caret_column() + 1);
|
||||
} else {
|
||||
set_caret_column(TS->shaped_text_next_grapheme_pos(text_rid, get_caret_column()));
|
||||
set_caret_column(TS->shaped_text_next_character_pos(text_rid, get_caret_column()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,7 +211,7 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) {
|
|||
delete_char();
|
||||
} else {
|
||||
int cc = caret_column;
|
||||
set_caret_column(TS->shaped_text_next_grapheme_pos(text_rid, caret_column));
|
||||
set_caret_column(TS->shaped_text_next_character_pos(text_rid, caret_column));
|
||||
delete_text(cc, caret_column);
|
||||
}
|
||||
}
|
||||
|
@ -1326,6 +1326,9 @@ void LineEdit::set_caret_at_pixel_pos(int p_x) {
|
|||
}
|
||||
|
||||
int ofs = ceil(TS->shaped_text_hit_test_position(text_rid, p_x - x_ofs - scroll_offset));
|
||||
if (!caret_mid_grapheme_enabled) {
|
||||
ofs = TS->shaped_text_closest_character_pos(text_rid, ofs);
|
||||
}
|
||||
set_caret_column(ofs);
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ private:
|
|||
PopupMenu *menu_dir = nullptr;
|
||||
PopupMenu *menu_ctl = nullptr;
|
||||
|
||||
bool caret_mid_grapheme_enabled = true;
|
||||
bool caret_mid_grapheme_enabled = false;
|
||||
|
||||
int caret_column = 0;
|
||||
float scroll_offset = 0.0;
|
||||
|
|
|
@ -1585,6 +1585,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
|
|||
}
|
||||
} else {
|
||||
char_pos = TS->shaped_text_hit_test_position(rid, p_click.x - rect.position.x);
|
||||
char_pos = TS->shaped_text_closest_character_pos(rid, char_pos);
|
||||
}
|
||||
}
|
||||
line_clicked = true;
|
||||
|
|
|
@ -2374,7 +2374,7 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) {
|
|||
if (caret_mid_grapheme_enabled) {
|
||||
set_caret_column(get_caret_column(i) - 1, i == 0, i);
|
||||
} else {
|
||||
set_caret_column(TS->shaped_text_prev_grapheme_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i);
|
||||
set_caret_column(TS->shaped_text_prev_character_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2433,7 +2433,7 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) {
|
|||
if (caret_mid_grapheme_enabled) {
|
||||
set_caret_column(get_caret_column(i) + 1, i == 0, i);
|
||||
} else {
|
||||
set_caret_column(TS->shaped_text_next_grapheme_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i);
|
||||
set_caret_column(TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(i))->get_rid(), get_caret_column(i)), i == 0, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2815,7 +2815,7 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) {
|
|||
if (caret_mid_grapheme_enabled) {
|
||||
next_column = get_caret_column(caret_idx) < curline_len ? (get_caret_column(caret_idx) + 1) : 0;
|
||||
} else {
|
||||
next_column = get_caret_column(caret_idx) < curline_len ? TS->shaped_text_next_grapheme_pos(text.get_line_data(get_caret_line(caret_idx))->get_rid(), (get_caret_column(caret_idx))) : 0;
|
||||
next_column = get_caret_column(caret_idx) < curline_len ? TS->shaped_text_next_character_pos(text.get_line_data(get_caret_line(caret_idx))->get_rid(), (get_caret_column(caret_idx))) : 0;
|
||||
}
|
||||
|
||||
// Remove overlapping carets.
|
||||
|
@ -4331,6 +4331,9 @@ Point2i TextEdit::get_line_column_at_pos(const Point2i &p_pos, bool p_allow_out_
|
|||
colx = TS->shaped_text_get_size(text_rid).x - colx;
|
||||
}
|
||||
col = TS->shaped_text_hit_test_position(text_rid, colx);
|
||||
if (!caret_mid_grapheme_enabled) {
|
||||
col = TS->shaped_text_closest_character_pos(text_rid, col);
|
||||
}
|
||||
|
||||
return Point2i(col, row);
|
||||
}
|
||||
|
@ -7023,7 +7026,11 @@ int TextEdit::_get_char_pos_for_line(int p_px, int p_line, int p_wrap_index) con
|
|||
if (is_layout_rtl()) {
|
||||
p_px = TS->shaped_text_get_size(text_rid).x - p_px;
|
||||
}
|
||||
return TS->shaped_text_hit_test_position(text_rid, p_px);
|
||||
int ofs = TS->shaped_text_hit_test_position(text_rid, p_px);
|
||||
if (!caret_mid_grapheme_enabled) {
|
||||
ofs = TS->shaped_text_closest_character_pos(text_rid, ofs);
|
||||
}
|
||||
return ofs;
|
||||
}
|
||||
|
||||
/* Caret */
|
||||
|
|
|
@ -423,7 +423,7 @@ private:
|
|||
|
||||
bool move_caret_on_right_click = true;
|
||||
|
||||
bool caret_mid_grapheme_enabled = true;
|
||||
bool caret_mid_grapheme_enabled = false;
|
||||
|
||||
bool multi_carets_enabled = true;
|
||||
|
||||
|
|
|
@ -303,6 +303,11 @@ void TextServerExtension::_bind_methods() {
|
|||
GDVIRTUAL_BIND(_shaped_text_next_grapheme_pos, "shaped", "pos");
|
||||
GDVIRTUAL_BIND(_shaped_text_prev_grapheme_pos, "shaped", "pos");
|
||||
|
||||
GDVIRTUAL_BIND(_shaped_text_get_character_breaks, "shaped");
|
||||
GDVIRTUAL_BIND(_shaped_text_next_character_pos, "shaped", "pos");
|
||||
GDVIRTUAL_BIND(_shaped_text_prev_character_pos, "shaped", "pos");
|
||||
GDVIRTUAL_BIND(_shaped_text_closest_character_pos, "shaped", "pos");
|
||||
|
||||
GDVIRTUAL_BIND(_format_number, "string", "language");
|
||||
GDVIRTUAL_BIND(_parse_number, "string", "language");
|
||||
GDVIRTUAL_BIND(_percent_sign, "language");
|
||||
|
@ -311,6 +316,7 @@ void TextServerExtension::_bind_methods() {
|
|||
GDVIRTUAL_BIND(_is_valid_identifier, "string");
|
||||
|
||||
GDVIRTUAL_BIND(_string_get_word_breaks, "string", "language", "chars_per_line");
|
||||
GDVIRTUAL_BIND(_string_get_character_breaks, "string", "language");
|
||||
|
||||
GDVIRTUAL_BIND(_is_confusable, "string", "dict");
|
||||
GDVIRTUAL_BIND(_spoof_check, "string");
|
||||
|
@ -1333,6 +1339,38 @@ int64_t TextServerExtension::shaped_text_prev_grapheme_pos(const RID &p_shaped,
|
|||
return TextServer::shaped_text_prev_grapheme_pos(p_shaped, p_pos);
|
||||
}
|
||||
|
||||
PackedInt32Array TextServerExtension::shaped_text_get_character_breaks(const RID &p_shaped) const {
|
||||
PackedInt32Array ret;
|
||||
if (GDVIRTUAL_CALL(_shaped_text_get_character_breaks, p_shaped, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return PackedInt32Array();
|
||||
}
|
||||
|
||||
int64_t TextServerExtension::shaped_text_next_character_pos(const RID &p_shaped, int64_t p_pos) const {
|
||||
int64_t ret;
|
||||
if (GDVIRTUAL_CALL(_shaped_text_next_character_pos, p_shaped, p_pos, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return TextServer::shaped_text_next_character_pos(p_shaped, p_pos);
|
||||
}
|
||||
|
||||
int64_t TextServerExtension::shaped_text_prev_character_pos(const RID &p_shaped, int64_t p_pos) const {
|
||||
int64_t ret;
|
||||
if (GDVIRTUAL_CALL(_shaped_text_prev_character_pos, p_shaped, p_pos, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return TextServer::shaped_text_prev_character_pos(p_shaped, p_pos);
|
||||
}
|
||||
|
||||
int64_t TextServerExtension::shaped_text_closest_character_pos(const RID &p_shaped, int64_t p_pos) const {
|
||||
int64_t ret;
|
||||
if (GDVIRTUAL_CALL(_shaped_text_closest_character_pos, p_shaped, p_pos, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return TextServer::shaped_text_closest_character_pos(p_shaped, p_pos);
|
||||
}
|
||||
|
||||
String TextServerExtension::format_number(const String &p_string, const String &p_language) const {
|
||||
String ret;
|
||||
if (GDVIRTUAL_CALL(_format_number, p_string, p_language, ret)) {
|
||||
|
@ -1399,6 +1437,14 @@ PackedInt32Array TextServerExtension::string_get_word_breaks(const String &p_str
|
|||
return ret;
|
||||
}
|
||||
|
||||
PackedInt32Array TextServerExtension::string_get_character_breaks(const String &p_string, const String &p_language) const {
|
||||
PackedInt32Array ret;
|
||||
if (GDVIRTUAL_CALL(_string_get_character_breaks, p_string, p_language, ret)) {
|
||||
return ret;
|
||||
}
|
||||
return TextServer::string_get_character_breaks(p_string, p_language);
|
||||
}
|
||||
|
||||
int64_t TextServerExtension::is_confusable(const String &p_string, const PackedStringArray &p_dict) const {
|
||||
int64_t ret;
|
||||
if (GDVIRTUAL_CALL(_is_confusable, p_string, p_dict, ret)) {
|
||||
|
|
|
@ -505,6 +505,15 @@ public:
|
|||
GDVIRTUAL2RC(int64_t, _shaped_text_next_grapheme_pos, RID, int64_t);
|
||||
GDVIRTUAL2RC(int64_t, _shaped_text_prev_grapheme_pos, RID, int64_t);
|
||||
|
||||
virtual PackedInt32Array shaped_text_get_character_breaks(const RID &p_shaped) const override;
|
||||
virtual int64_t shaped_text_next_character_pos(const RID &p_shaped, int64_t p_pos) const override;
|
||||
virtual int64_t shaped_text_prev_character_pos(const RID &p_shaped, int64_t p_pos) const override;
|
||||
virtual int64_t shaped_text_closest_character_pos(const RID &p_shaped, int64_t p_pos) const override;
|
||||
GDVIRTUAL1RC(PackedInt32Array, _shaped_text_get_character_breaks, RID);
|
||||
GDVIRTUAL2RC(int64_t, _shaped_text_next_character_pos, RID, int64_t);
|
||||
GDVIRTUAL2RC(int64_t, _shaped_text_prev_character_pos, RID, int64_t);
|
||||
GDVIRTUAL2RC(int64_t, _shaped_text_closest_character_pos, RID, int64_t);
|
||||
|
||||
virtual String format_number(const String &p_string, const String &p_language = "") const override;
|
||||
virtual String parse_number(const String &p_string, const String &p_language = "") const override;
|
||||
virtual String percent_sign(const String &p_language = "") const override;
|
||||
|
@ -518,6 +527,9 @@ public:
|
|||
virtual PackedInt32Array string_get_word_breaks(const String &p_string, const String &p_language = "", int64_t p_chars_per_line = 0) const override;
|
||||
GDVIRTUAL3RC(PackedInt32Array, _string_get_word_breaks, const String &, const String &, int64_t);
|
||||
|
||||
virtual PackedInt32Array string_get_character_breaks(const String &p_string, const String &p_language = "") const override;
|
||||
GDVIRTUAL2RC(PackedInt32Array, _string_get_character_breaks, const String &, const String &);
|
||||
|
||||
virtual bool is_valid_identifier(const String &p_string) const override;
|
||||
GDVIRTUAL1RC(bool, _is_valid_identifier, const String &);
|
||||
|
||||
|
|
|
@ -448,6 +448,11 @@ void TextServer::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("shaped_text_next_grapheme_pos", "shaped", "pos"), &TextServer::shaped_text_next_grapheme_pos);
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_prev_grapheme_pos", "shaped", "pos"), &TextServer::shaped_text_prev_grapheme_pos);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_get_character_breaks", "shaped"), &TextServer::shaped_text_get_character_breaks);
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_next_character_pos", "shaped", "pos"), &TextServer::shaped_text_next_character_pos);
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_prev_character_pos", "shaped", "pos"), &TextServer::shaped_text_prev_character_pos);
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_closest_character_pos", "shaped", "pos"), &TextServer::shaped_text_closest_character_pos);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_draw", "shaped", "canvas", "pos", "clip_l", "clip_r", "color"), &TextServer::shaped_text_draw, DEFVAL(-1), DEFVAL(-1), DEFVAL(Color(1, 1, 1)));
|
||||
ClassDB::bind_method(D_METHOD("shaped_text_draw_outline", "shaped", "canvas", "pos", "clip_l", "clip_r", "outline_size", "color"), &TextServer::shaped_text_draw_outline, DEFVAL(-1), DEFVAL(-1), DEFVAL(1), DEFVAL(Color(1, 1, 1)));
|
||||
|
||||
|
@ -458,6 +463,7 @@ void TextServer::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("percent_sign", "language"), &TextServer::percent_sign, DEFVAL(""));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("string_get_word_breaks", "string", "language", "chars_per_line"), &TextServer::string_get_word_breaks, DEFVAL(""), DEFVAL(0));
|
||||
ClassDB::bind_method(D_METHOD("string_get_character_breaks", "string", "language"), &TextServer::string_get_character_breaks, DEFVAL(""));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_confusable", "string", "dict"), &TextServer::is_confusable);
|
||||
ClassDB::bind_method(D_METHOD("spoof_check", "string"), &TextServer::spoof_check);
|
||||
|
@ -1424,6 +1430,57 @@ int64_t TextServer::shaped_text_prev_grapheme_pos(const RID &p_shaped, int64_t p
|
|||
return p_pos;
|
||||
}
|
||||
|
||||
int64_t TextServer::shaped_text_prev_character_pos(const RID &p_shaped, int64_t p_pos) const {
|
||||
const PackedInt32Array &chars = shaped_text_get_character_breaks(p_shaped);
|
||||
int64_t prev = 0;
|
||||
for (const int32_t &E : chars) {
|
||||
if (E >= p_pos) {
|
||||
return prev;
|
||||
}
|
||||
prev = E;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
int64_t TextServer::shaped_text_next_character_pos(const RID &p_shaped, int64_t p_pos) const {
|
||||
const PackedInt32Array &chars = shaped_text_get_character_breaks(p_shaped);
|
||||
int64_t prev = 0;
|
||||
for (const int32_t &E : chars) {
|
||||
if (E > p_pos) {
|
||||
return E;
|
||||
}
|
||||
prev = E;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
int64_t TextServer::shaped_text_closest_character_pos(const RID &p_shaped, int64_t p_pos) const {
|
||||
const PackedInt32Array &chars = shaped_text_get_character_breaks(p_shaped);
|
||||
int64_t prev = 0;
|
||||
for (const int32_t &E : chars) {
|
||||
if (E == p_pos) {
|
||||
return E;
|
||||
} else if (E > p_pos) {
|
||||
if ((E - p_pos) < (p_pos - prev)) {
|
||||
return E;
|
||||
} else {
|
||||
return prev;
|
||||
}
|
||||
}
|
||||
prev = E;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
PackedInt32Array TextServer::string_get_character_breaks(const String &p_string, const String &p_language) const {
|
||||
PackedInt32Array ret;
|
||||
ret.resize(p_string.size());
|
||||
for (int i = 0; i <= p_string.size(); i++) {
|
||||
ret.write[i] = i;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void TextServer::shaped_text_draw(const RID &p_shaped, const RID &p_canvas, const Vector2 &p_pos, double p_clip_l, double p_clip_r, const Color &p_color) const {
|
||||
TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped);
|
||||
bool hex_codes = shaped_text_get_preserve_control(p_shaped) || shaped_text_get_preserve_invalid(p_shaped);
|
||||
|
|
|
@ -491,6 +491,11 @@ public:
|
|||
virtual int64_t shaped_text_next_grapheme_pos(const RID &p_shaped, int64_t p_pos) const;
|
||||
virtual int64_t shaped_text_prev_grapheme_pos(const RID &p_shaped, int64_t p_pos) const;
|
||||
|
||||
virtual PackedInt32Array shaped_text_get_character_breaks(const RID &p_shaped) const = 0;
|
||||
virtual int64_t shaped_text_next_character_pos(const RID &p_shaped, int64_t p_pos) const;
|
||||
virtual int64_t shaped_text_prev_character_pos(const RID &p_shaped, int64_t p_pos) const;
|
||||
virtual int64_t shaped_text_closest_character_pos(const RID &p_shaped, int64_t p_pos) const;
|
||||
|
||||
// The pen position is always placed on the baseline and moveing left to right.
|
||||
virtual void shaped_text_draw(const RID &p_shaped, const RID &p_canvas, const Vector2 &p_pos, double p_clip_l = -1.0, double p_clip_r = -1.0, const Color &p_color = Color(1, 1, 1)) const;
|
||||
virtual void shaped_text_draw_outline(const RID &p_shaped, const RID &p_canvas, const Vector2 &p_pos, double p_clip_l = -1.0, double p_clip_r = -1.0, int64_t p_outline_size = 1, const Color &p_color = Color(1, 1, 1)) const;
|
||||
|
@ -502,6 +507,7 @@ public:
|
|||
|
||||
// String functions.
|
||||
virtual PackedInt32Array string_get_word_breaks(const String &p_string, const String &p_language = "", int64_t p_chars_per_line = 0) const = 0;
|
||||
virtual PackedInt32Array string_get_character_breaks(const String &p_string, const String &p_language = "") const;
|
||||
|
||||
virtual int64_t is_confusable(const String &p_string, const PackedStringArray &p_dict) const { return -1; };
|
||||
virtual bool spoof_check(const String &p_string) const { return false; };
|
||||
|
|
|
@ -2297,34 +2297,6 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
|
|||
SIGNAL_CHECK("caret_changed", empty_signal_args);
|
||||
SIGNAL_CHECK("text_changed", empty_signal_args);
|
||||
SIGNAL_CHECK("lines_edited_from", lines_edited_args);
|
||||
|
||||
text_edit->set_caret_mid_grapheme_enabled(false);
|
||||
CHECK_FALSE(text_edit->is_caret_mid_grapheme_enabled());
|
||||
|
||||
text_edit->start_action(TextEdit::EditAction::ACTION_NONE);
|
||||
|
||||
text_edit->undo();
|
||||
MessageQueue::get_singleton()->flush();
|
||||
CHECK(text_edit->get_text() == "ffi some test text.ffi some test text.");
|
||||
|
||||
SIGNAL_DISCARD("text_set");
|
||||
SIGNAL_DISCARD("text_changed");
|
||||
SIGNAL_DISCARD("lines_edited_from");
|
||||
SIGNAL_DISCARD("caret_changed");
|
||||
|
||||
SEND_GUI_ACTION("ui_text_delete");
|
||||
CHECK(text_edit->get_viewport()->is_input_handled());
|
||||
CHECK(text_edit->get_text() == " some test text. some test text.");
|
||||
CHECK(text_edit->get_caret_line() == 0);
|
||||
CHECK(text_edit->get_caret_column() == 0);
|
||||
CHECK_FALSE(text_edit->has_selection(0));
|
||||
|
||||
CHECK(text_edit->get_caret_line(1) == 0);
|
||||
CHECK(text_edit->get_caret_column(1) == 16);
|
||||
CHECK_FALSE(text_edit->has_selection(1));
|
||||
SIGNAL_CHECK("caret_changed", empty_signal_args);
|
||||
SIGNAL_CHECK("text_changed", empty_signal_args);
|
||||
SIGNAL_CHECK("lines_edited_from", lines_edited_args);
|
||||
}
|
||||
|
||||
SUBCASE("[TextEdit] ui_text_caret_word_left") {
|
||||
|
@ -3335,18 +3307,6 @@ TEST_CASE("[SceneTree][TextEdit] caret") {
|
|||
SEND_GUI_ACTION("ui_text_caret_left");
|
||||
CHECK(text_edit->get_caret_column() == 2);
|
||||
|
||||
text_edit->set_caret_mid_grapheme_enabled(false);
|
||||
CHECK_FALSE(text_edit->is_caret_mid_grapheme_enabled());
|
||||
|
||||
SEND_GUI_ACTION("ui_text_caret_left");
|
||||
CHECK(text_edit->get_caret_column() == 0);
|
||||
|
||||
SEND_GUI_ACTION("ui_text_caret_right");
|
||||
CHECK(text_edit->get_caret_column() == 3);
|
||||
|
||||
SEND_GUI_ACTION("ui_text_caret_left");
|
||||
CHECK(text_edit->get_caret_column() == 0);
|
||||
|
||||
text_edit->set_line(0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vasius mattis leo, sed porta ex lacinia bibendum. Nunc bibendum pellentesque.");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
text_edit->insert_line_at(0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vasius mattis leo, sed porta ex lacinia bibendum. Nunc bibendum pellentesque.");
|
||||
|
|
|
@ -636,6 +636,54 @@ TEST_SUITE("[TextServer]") {
|
|||
CHECK(breaks[17] == 42);
|
||||
}
|
||||
}
|
||||
|
||||
if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) {
|
||||
String text2 = U"U+2764 U+FE0F U+200D U+1F525 ; 13.1 # ❤️🔥";
|
||||
|
||||
PackedInt32Array breaks = ts->string_get_character_breaks(text2, "en");
|
||||
CHECK(breaks.size() == 39);
|
||||
if (breaks.size() == 39) {
|
||||
CHECK(breaks[0] == 1);
|
||||
CHECK(breaks[1] == 2);
|
||||
CHECK(breaks[2] == 3);
|
||||
CHECK(breaks[3] == 4);
|
||||
CHECK(breaks[4] == 5);
|
||||
CHECK(breaks[5] == 6);
|
||||
CHECK(breaks[6] == 7);
|
||||
CHECK(breaks[7] == 8);
|
||||
CHECK(breaks[8] == 9);
|
||||
CHECK(breaks[9] == 10);
|
||||
CHECK(breaks[10] == 11);
|
||||
CHECK(breaks[11] == 12);
|
||||
CHECK(breaks[12] == 13);
|
||||
CHECK(breaks[13] == 14);
|
||||
CHECK(breaks[14] == 15);
|
||||
CHECK(breaks[15] == 16);
|
||||
CHECK(breaks[16] == 17);
|
||||
CHECK(breaks[17] == 18);
|
||||
CHECK(breaks[18] == 19);
|
||||
CHECK(breaks[19] == 20);
|
||||
CHECK(breaks[20] == 21);
|
||||
CHECK(breaks[21] == 22);
|
||||
CHECK(breaks[22] == 23);
|
||||
CHECK(breaks[23] == 24);
|
||||
CHECK(breaks[24] == 25);
|
||||
CHECK(breaks[25] == 26);
|
||||
CHECK(breaks[26] == 27);
|
||||
CHECK(breaks[27] == 28);
|
||||
CHECK(breaks[28] == 29);
|
||||
CHECK(breaks[29] == 30);
|
||||
CHECK(breaks[30] == 31);
|
||||
CHECK(breaks[31] == 32);
|
||||
CHECK(breaks[32] == 33);
|
||||
CHECK(breaks[33] == 34);
|
||||
CHECK(breaks[34] == 35);
|
||||
CHECK(breaks[35] == 36);
|
||||
CHECK(breaks[36] == 37);
|
||||
CHECK(breaks[37] == 38);
|
||||
CHECK(breaks[38] == 42);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue