Merge pull request #110194 from bruvzg/emoji_run

[TextServer] Shape emojis as separate runs.
This commit is contained in:
Thaddeus Crews 2025-09-22 21:00:58 -05:00
commit 6dddb6e6d6
No known key found for this signature in database
GPG key ID: 8C6E5FEB5FC03CCC
4 changed files with 44 additions and 9 deletions

View file

@ -32,10 +32,20 @@
// This implementation is derived from ICU: icu4c/source/extra/scrptrun/scrptrun.cpp // This implementation is derived from ICU: icu4c/source/extra/scrptrun/scrptrun.cpp
bool ScriptIterator::same_script(int32_t p_script_one, int32_t p_script_two) { inline bool ScriptIterator::same_script(int32_t p_script_one, int32_t p_script_two) {
return p_script_one <= USCRIPT_INHERITED || p_script_two <= USCRIPT_INHERITED || p_script_one == p_script_two; return p_script_one <= USCRIPT_INHERITED || p_script_two <= USCRIPT_INHERITED || p_script_one == p_script_two;
} }
inline bool ScriptIterator::is_emoji(UChar32 p_c, UChar32 p_next) {
if (p_next == 0xFE0E) { // Variation Selector-15
return false;
} else if (p_next == 0xFE0F) { // Variation Selector-16
return true;
} else {
return u_hasBinaryProperty(p_c, UCHAR_EMOJI) || u_hasBinaryProperty(p_c, UCHAR_EMOJI_PRESENTATION) || u_hasBinaryProperty(p_c, UCHAR_EMOJI_MODIFIER) || u_hasBinaryProperty(p_c, UCHAR_REGIONAL_INDICATOR) || u_hasBinaryProperty(p_c, UCHAR_EXTENDED_PICTOGRAPHIC);
}
}
ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length) { ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length) {
struct ParenStackEntry { struct ParenStackEntry {
int pair_index; int pair_index;
@ -65,11 +75,16 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
script_code = USCRIPT_COMMON; script_code = USCRIPT_COMMON;
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;
UScriptCode sc = uscript_getScript(ch, &err); UScriptCode sc = uscript_getScript(ch, &err);
if (U_FAILURE(err)) { if (U_FAILURE(err)) {
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) {
// If it's an open character, push it onto the stack. // If it's an open character, push it onto the stack.
@ -96,7 +111,12 @@ ScriptIterator::ScriptIterator(const String &p_string, int p_start, int p_length
} }
} }
if (same_script(script_code, sc)) { if (script_code == USCRIPT_SYMBOLS_EMOJI && script_code != sc) {
UCharCategory cat = (UCharCategory)u_charType(ch);
if ((cat >= U_SPACE_SEPARATOR && cat <= U_CONTROL_CHAR) || (cat >= U_DASH_PUNCTUATION && cat <= U_OTHER_PUNCTUATION) || (cat >= U_INITIAL_PUNCTUATION && cat <= U_FINAL_PUNCTUATION)) {
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.

View file

@ -68,7 +68,8 @@ public:
Vector<ScriptRange> script_ranges; Vector<ScriptRange> script_ranges;
private: private:
static bool same_script(int32_t p_script_one, int32_t p_script_two); inline static bool same_script(int32_t p_script_one, int32_t p_script_two);
inline static bool is_emoji(UChar32 p_c, UChar32 p_next);
public: public:
ScriptIterator(const String &p_string, int p_start, int p_length); ScriptIterator(const String &p_string, int p_start, int p_length);

View file

@ -2006,7 +2006,7 @@ _FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_d
p_font_data->supported_scripts.clear(); p_font_data->supported_scripts.clear();
} }
hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_t p_size) const { hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_t p_size, bool &r_is_color) const {
FontAdvanced *fd = _get_font_data(p_font_rid); FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, nullptr); ERR_FAIL_NULL_V(fd, nullptr);
@ -2015,6 +2015,11 @@ hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_
FontForSizeAdvanced *ffsd = nullptr; FontForSizeAdvanced *ffsd = nullptr;
ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), nullptr); ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), nullptr);
#ifdef MODULE_FREETYPE_ENABLED
r_is_color = FT_HAS_COLOR(ffsd->face);
#else
r_is_color = false;
#endif
return ffsd->hb_handle; return ffsd->hb_handle;
} }
@ -6527,7 +6532,8 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap
} }
Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, const RID &p_font, int64_t p_font_size) { Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, const RID &p_font, int64_t p_font_size) {
hb_font_t *hb_font = _font_get_hb_handle(p_font, p_font_size); bool color = false;
hb_font_t *hb_font = _font_get_hb_handle(p_font, p_font_size, color);
double scale = _font_get_scale(p_font, p_font_size); double scale = _font_get_scale(p_font, p_font_size);
bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_AUTO && p_font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE); bool subpos = (scale != 1.0) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_HALF) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_ONE_QUARTER) || (_font_get_subpixel_positioning(p_font) == SUBPIXEL_POSITIONING_AUTO && p_font_size <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE);
ERR_FAIL_NULL_V(hb_font, Glyph()); ERR_FAIL_NULL_V(hb_font, Glyph());
@ -6535,7 +6541,7 @@ Glyph TextServerAdvanced::_shape_single_glyph(ShapedTextDataAdvanced *p_sd, char
hb_buffer_clear_contents(p_sd->hb_buffer); hb_buffer_clear_contents(p_sd->hb_buffer);
hb_buffer_set_direction(p_sd->hb_buffer, p_direction); hb_buffer_set_direction(p_sd->hb_buffer, p_direction);
hb_buffer_set_flags(p_sd->hb_buffer, (hb_buffer_flags_t)(HB_BUFFER_FLAG_DEFAULT)); hb_buffer_set_flags(p_sd->hb_buffer, (hb_buffer_flags_t)(HB_BUFFER_FLAG_DEFAULT));
hb_buffer_set_script(p_sd->hb_buffer, p_script); hb_buffer_set_script(p_sd->hb_buffer, (p_script == HB_TAG('Z', 's', 'y', 'e')) ? HB_SCRIPT_COMMON : p_script);
hb_buffer_add_utf32(p_sd->hb_buffer, (const uint32_t *)&p_char, 1, 0, 1); hb_buffer_add_utf32(p_sd->hb_buffer, (const uint32_t *)&p_char, 1, 0, 1);
hb_shape(hb_font, p_sd->hb_buffer, nullptr, 0); hb_shape(hb_font, p_sd->hb_buffer, nullptr, 0);
@ -6740,9 +6746,17 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
FontAdvanced *fd = _get_font_data(f); FontAdvanced *fd = _get_font_data(f);
ERR_FAIL_NULL(fd); ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex); MutexLock lock(fd->mutex);
bool color = false;
Vector2i fss = _get_size(fd, fs); Vector2i fss = _get_size(fd, fs);
hb_font_t *hb_font = _font_get_hb_handle(f, fs); hb_font_t *hb_font = _font_get_hb_handle(f, fs, color);
if (p_script == HB_TAG('Z', 's', 'y', 'e') && !color) {
// Color emoji is requested, skip non-color font.
_shape_run(p_sd, p_start, p_end, p_script, p_direction, p_fonts, p_span, p_fb_index + 1, p_start, p_end, f);
return;
}
double scale = _font_get_scale(f, fs); double scale = _font_get_scale(f, fs);
double sp_sp = p_sd->extra_spacing[SPACING_SPACE] + _font_get_spacing(f, SPACING_SPACE); double sp_sp = p_sd->extra_spacing[SPACING_SPACE] + _font_get_spacing(f, SPACING_SPACE);
double sp_gl = p_sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(f, SPACING_GLYPH); double sp_gl = p_sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(f, SPACING_GLYPH);
@ -6763,7 +6777,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
flags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL; flags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
#endif #endif
hb_buffer_set_flags(p_sd->hb_buffer, (hb_buffer_flags_t)flags); hb_buffer_set_flags(p_sd->hb_buffer, (hb_buffer_flags_t)flags);
hb_buffer_set_script(p_sd->hb_buffer, p_script); hb_buffer_set_script(p_sd->hb_buffer, (p_script == HB_TAG('Z', 's', 'y', 'e')) ? HB_SCRIPT_COMMON : p_script);
if (p_sd->spans[p_span].language.is_empty()) { if (p_sd->spans[p_span].language.is_empty()) {
hb_language_t lang = hb_language_from_string(TranslationServer::get_singleton()->get_tool_locale().ascii().get_data(), -1); hb_language_t lang = hb_language_from_string(TranslationServer::get_singleton()->get_tool_locale().ascii().get_data(), -1);

View file

@ -726,7 +726,7 @@ class TextServerAdvanced : public TextServerExtension {
static void _bmp_font_set_funcs(hb_font_t *p_font, TextServerAdvanced::FontForSizeAdvanced *p_face, bool p_unref); static void _bmp_font_set_funcs(hb_font_t *p_font, TextServerAdvanced::FontForSizeAdvanced *p_face, bool p_unref);
static hb_font_t *_bmp_font_create(TextServerAdvanced::FontForSizeAdvanced *p_face, hb_destroy_func_t p_destroy); static hb_font_t *_bmp_font_create(TextServerAdvanced::FontForSizeAdvanced *p_face, hb_destroy_func_t p_destroy);
hb_font_t *_font_get_hb_handle(const RID &p_font, int64_t p_font_size) const; hb_font_t *_font_get_hb_handle(const RID &p_font, int64_t p_font_size, bool &r_is_color) const;
struct GlyphCompare { // For line breaking reordering. struct GlyphCompare { // For line breaking reordering.
_FORCE_INLINE_ bool operator()(const Glyph &l, const Glyph &r) const { _FORCE_INLINE_ bool operator()(const Glyph &l, const Glyph &r) const {