1
0
mirror of https://github.com/godotengine/godot.git synced 2025-12-02 16:48:55 +00:00

[TextServer] Track emoji subruns separately from parentheses stack.

This commit is contained in:
Pāvels Nadtočajevs
2025-11-19 08:34:30 +02:00
parent b15a13eed3
commit 2479e1737a
2 changed files with 67 additions and 18 deletions

View File

@@ -56,6 +56,11 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
UScriptCode script_code; UScriptCode script_code;
}; };
struct EmojiSubrunEntry {
int start;
int end;
};
if (p_start >= p_length) { if (p_start >= p_length) {
p_start = p_length - 1; p_start = p_length - 1;
} }
@@ -65,8 +70,12 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
} }
int paren_size = PAREN_STACK_DEPTH; int paren_size = PAREN_STACK_DEPTH;
ParenStackEntry starter_stack[PAREN_STACK_DEPTH]; ParenStackEntry starter_paren_stack[PAREN_STACK_DEPTH];
ParenStackEntry *paren_stack = starter_stack; ParenStackEntry *paren_stack = starter_paren_stack;
int emoji_size = EMOJI_STACK_DEPTH;
EmojiSubrunEntry starter_emoji_stack[EMOJI_STACK_DEPTH];
EmojiSubrunEntry *emoji_stack = starter_emoji_stack;
int script_start; int script_start;
int script_end = p_start; int script_end = p_start;
@@ -78,19 +87,38 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
do { do {
script_code = USCRIPT_COMMON; script_code = USCRIPT_COMMON;
int emoji_sp = -1;
bool emoji_run = false;
for (script_start = script_end; script_end < p_length; script_end++) { for (script_start = script_end; script_end < p_length; script_end++) {
UChar32 ch = str[script_end]; UChar32 ch = str[script_end];
UChar32 n = (script_end + 1 < p_length) ? str[script_end + 1] : 0; UChar32 n = (script_end + 1 < p_length) ? str[script_end + 1] : 0;
if (is_emoji(ch, n)) {
if (!emoji_run) {
emoji_run = true;
emoji_sp++;
if (unlikely(emoji_sp >= emoji_size)) {
emoji_size += EMOJI_STACK_DEPTH;
if (emoji_stack == starter_emoji_stack) {
emoji_stack = static_cast<EmojiSubrunEntry *>(memalloc(emoji_size * sizeof(EmojiSubrunEntry)));
} else {
emoji_stack = static_cast<EmojiSubrunEntry *>(memrealloc(emoji_stack, emoji_size * sizeof(EmojiSubrunEntry)));
}
}
emoji_stack[emoji_sp].start = script_end;
emoji_stack[emoji_sp].end = script_end;
}
} else if (emoji_run && ch != ZERO_WIDTH_JOINER && ch != VARIATION_SELECTOR_16 && !(u_hasBinaryProperty(ch, UCHAR_EXTENDED_PICTOGRAPHIC) && n != VARIATION_SELECTOR_15)) {
emoji_run = false;
emoji_stack[emoji_sp].end = script_end;
}
UScriptCode sc = uscript_getScript(ch, &err); UScriptCode sc = uscript_getScript(ch, &err);
if (U_FAILURE(err)) { if (U_FAILURE(err)) {
if (paren_stack != starter_stack) { if (paren_stack != starter_paren_stack) {
memfree(paren_stack); memfree(paren_stack);
} }
ERR_FAIL_MSG(u_errorName(err)); ERR_FAIL_MSG(u_errorName(err));
} }
if (is_emoji(ch, n)) {
sc = USCRIPT_SYMBOLS_EMOJI;
}
if (u_getIntPropertyValue(ch, UCHAR_BIDI_PAIRED_BRACKET_TYPE) != U_BPT_NONE) { if (u_getIntPropertyValue(ch, UCHAR_BIDI_PAIRED_BRACKET_TYPE) != U_BPT_NONE) {
if (u_getIntPropertyValue(ch, UCHAR_BIDI_PAIRED_BRACKET_TYPE) == U_BPT_OPEN) { if (u_getIntPropertyValue(ch, UCHAR_BIDI_PAIRED_BRACKET_TYPE) == U_BPT_OPEN) {
@@ -99,7 +127,7 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
if (unlikely(paren_sp >= paren_size)) { if (unlikely(paren_sp >= paren_size)) {
// If the stack is full, allocate more space to handle deeply nested parentheses. This is unlikely to happen with any real text. // If the stack is full, allocate more space to handle deeply nested parentheses. This is unlikely to happen with any real text.
paren_size += PAREN_STACK_DEPTH; paren_size += PAREN_STACK_DEPTH;
if (paren_stack == starter_stack) { if (paren_stack == starter_paren_stack) {
paren_stack = static_cast<ParenStackEntry *>(memalloc(paren_size * sizeof(ParenStackEntry))); paren_stack = static_cast<ParenStackEntry *>(memalloc(paren_size * sizeof(ParenStackEntry)));
} else { } else {
paren_stack = static_cast<ParenStackEntry *>(memrealloc(paren_stack, paren_size * sizeof(ParenStackEntry))); paren_stack = static_cast<ParenStackEntry *>(memrealloc(paren_stack, paren_size * sizeof(ParenStackEntry)));
@@ -122,11 +150,7 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
} }
} }
if (script_code == USCRIPT_SYMBOLS_EMOJI && script_code != sc) { if (same_script(script_code, sc)) {
if (ch == VARIATION_SELECTOR_15 || n == VARIATION_SELECTOR_15 || !(is_emoji(ch, n) || ch == ZERO_WIDTH_JOINER || ch == VARIATION_SELECTOR_16 || u_hasBinaryProperty(ch, UCHAR_EXTENDED_PICTOGRAPHIC))) {
break;
}
} else if (same_script(script_code, sc)) {
if (script_code <= USCRIPT_INHERITED && sc > USCRIPT_INHERITED) { if (script_code <= USCRIPT_INHERITED && sc > USCRIPT_INHERITED) {
script_code = sc; script_code = sc;
// Now that we have a final script code, fix any open characters we pushed before we knew the script code. // Now that we have a final script code, fix any open characters we pushed before we knew the script code.
@@ -145,16 +169,40 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
break; break;
} }
} }
if (emoji_run) {
emoji_stack[emoji_sp].end = script_end;
}
ScriptRange rng; for (int sub = 0; sub <= emoji_sp; sub++) {
rng.script = hb_icu_script_to_script(script_code); if (emoji_stack[sub].start > script_start) {
rng.start = script_start; ScriptRange rng;
rng.end = script_end; rng.script = hb_icu_script_to_script(script_code);
rng.start = script_start;
rng.end = emoji_stack[sub].start;
script_ranges.push_back(rng);
}
ScriptRange rng;
rng.script = (hb_script_t)HB_TAG('Z', 's', 'y', 'e');
rng.start = emoji_stack[sub].start;
rng.end = emoji_stack[sub].end;
script_ranges.push_back(rng);
script_ranges.push_back(rng); script_start = emoji_stack[sub].end;
}
if (script_start != script_end) {
ScriptRange rng;
rng.script = hb_icu_script_to_script(script_code);
rng.start = script_start;
rng.end = script_end;
script_ranges.push_back(rng);
}
if (emoji_stack != starter_emoji_stack) {
memfree(emoji_stack);
}
} while (script_end < p_length); } while (script_end < p_length);
if (paren_stack != starter_stack) { if (paren_stack != starter_paren_stack) {
memfree(paren_stack); memfree(paren_stack);
} }
} }

View File

@@ -58,6 +58,7 @@ using namespace godot;
class ScriptIterator { class ScriptIterator {
static const int PAREN_STACK_DEPTH = 128; static const int PAREN_STACK_DEPTH = 128;
static const int EMOJI_STACK_DEPTH = 32;
public: public:
struct ScriptRange { struct ScriptRange {