From cc1db569e1aef8d9fb4893bc75f664868b60acac Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Thu, 26 Sep 2024 09:37:47 +0300 Subject: [PATCH] [TextServer] Improve embedded objects handling performance. --- doc/classes/TextServer.xml | 8 ++ doc/classes/TextServerExtension.xml | 9 ++ editor/gui/editor_spin_slider.cpp | 2 +- modules/text_server_adv/text_server_adv.cpp | 136 ++++++++++++++------ modules/text_server_adv/text_server_adv.h | 3 + modules/text_server_fb/text_server_fb.cpp | 126 ++++++++++++------ modules/text_server_fb/text_server_fb.h | 3 + scene/3d/label_3d.cpp | 2 +- scene/gui/label.cpp | 2 +- scene/gui/rich_text_label.cpp | 22 +++- servers/text/text_server_dummy.h | 1 + servers/text/text_server_extension.cpp | 9 +- servers/text/text_server_extension.h | 2 + servers/text_server.cpp | 1 + servers/text_server.h | 5 +- 15 files changed, 242 insertions(+), 89 deletions(-) diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index 7fb6ec5b16c..bc7dc8ab93f 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -1207,6 +1207,14 @@ Returns number of text spans added using [method shaped_text_add_string] or [method shaped_text_add_object]. + + + + + + Returns text embedded object key. + + diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml index 1df451705c3..37f4984ad36 100644 --- a/doc/classes/TextServerExtension.xml +++ b/doc/classes/TextServerExtension.xml @@ -1314,6 +1314,15 @@ Returns number of text spans added using [method _shaped_text_add_string] or [method _shaped_text_add_object]. + + + + + + [b]Required.[/b] + Returns text embedded object key. + + diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index bb9afc23222..35ae1de5c89 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -366,7 +366,7 @@ void EditorSpinSlider::_draw_spin_slider() { } if (glyphs[i].font_rid != RID()) { TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); - } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color); } } diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 00f303d80ff..2eed5bdec1b 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -4218,15 +4218,14 @@ void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) { } } - for (int i = 0; i < parent->spans.size(); i++) { + for (int i = p_shaped->first_span; i <= p_shaped->last_span; i++) { ShapedTextDataAdvanced::Span span = parent->spans[i]; - if (span.start >= p_shaped->end || span.end <= p_shaped->start) { - continue; - } span.start = MAX(p_shaped->start, span.start); span.end = MIN(p_shaped->end, span.end); p_shaped->spans.push_back(span); } + p_shaped->first_span = 0; + p_shaped->last_span = 0; p_shaped->parent = RID(); } @@ -4252,6 +4251,8 @@ void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) { sd->end = 0; sd->text = String(); sd->spans.clear(); + sd->first_span = 0; + sd->last_span = 0; sd->objects.clear(); sd->bidi_override.clear(); invalidate(sd, true); @@ -4436,19 +4437,48 @@ TextServer::Orientation TextServerAdvanced::_shaped_text_get_orientation(const R int64_t TextServerAdvanced::_shaped_get_span_count(const RID &p_shaped) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL_V(sd, 0); - return sd->spans.size(); + + if (sd->parent != RID()) { + return sd->last_span - sd->first_span + 1; + } else { + return sd->spans.size(); + } } Variant TextServerAdvanced::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL_V(sd, Variant()); - ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); - return sd->spans[p_index].meta; + if (sd->parent != RID()) { + ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); + ERR_FAIL_COND_V(!parent_sd->valid.is_set(), Variant()); + ERR_FAIL_INDEX_V(p_index + sd->first_span, parent_sd->spans.size(), Variant()); + return parent_sd->spans[p_index + sd->first_span].meta; + } else { + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].meta; + } +} + +Variant TextServerAdvanced::_shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const { + ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_NULL_V(sd, Variant()); + if (sd->parent != RID()) { + ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); + ERR_FAIL_COND_V(!parent_sd->valid.is_set(), Variant()); + ERR_FAIL_INDEX_V(p_index + sd->first_span, parent_sd->spans.size(), Variant()); + return parent_sd->spans[p_index + sd->first_span].embedded_key; + } else { + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].embedded_key; + } } void TextServerAdvanced::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL(sd); + if (sd->parent != RID()) { + full_copy(sd); + } ERR_FAIL_INDEX(p_index, sd->spans.size()); ShapedTextDataAdvanced::Span &span = sd->spans.ptrw()[p_index]; @@ -4542,18 +4572,22 @@ bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const V sd->width = 0; sd->upos = 0; sd->uthk = 0; + + Vector &spans = sd->spans; + if (sd->parent != RID()) { + ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); + ERR_FAIL_COND_V(!parent_sd->valid.is_set(), false); + spans = parent_sd->spans; + } + int sd_size = sd->glyphs.size(); + int span_size = spans.size(); for (int i = 0; i < sd_size; i++) { Glyph gl = sd->glyphs[i]; Variant key; - if (gl.count == 1) { - for (const KeyValue &E : sd->objects) { - if (E.value.start == gl.start) { - key = E.key; - break; - } - } + if ((gl.flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && gl.span_index + sd->first_span >= 0 && gl.span_index + sd->first_span < span_size) { + key = spans[gl.span_index + sd->first_span].embedded_key; } if (key != Variant()) { if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -4724,6 +4758,20 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S p_new_sd->utf16 = p_new_sd->text.utf16(); p_new_sd->script_iter = memnew(ScriptIterator(p_new_sd->text, 0, p_new_sd->text.length())); + int span_size = p_sd->spans.size(); + + p_new_sd->first_span = 0; + p_new_sd->last_span = span_size - 1; + for (int i = 0; i < span_size; i++) { + const ShapedTextDataAdvanced::Span &span = p_sd->spans[i]; + if (span.end <= p_start) { + p_new_sd->first_span = i + 1; + } else if (span.start >= p_start + p_length) { + p_new_sd->last_span = i - 1; + break; + } + } + int sd_size = p_sd->glyphs.size(); const Glyph *sd_glyphs = p_sd->glyphs.ptr(); for (int ov = 0; ov < p_sd->bidi_override.size(); ov++) { @@ -4802,31 +4850,27 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S if ((sd_glyphs[j].start >= bidi_run_start) && (sd_glyphs[j].end <= bidi_run_end)) { // Copy glyphs. Glyph gl = sd_glyphs[j]; + if (gl.span_index >= 0) { + gl.span_index -= p_new_sd->first_span; + } if (gl.end == p_start + p_length && ((gl.flags & GRAPHEME_IS_SOFT_HYPHEN) == GRAPHEME_IS_SOFT_HYPHEN)) { uint32_t index = font_get_glyph_index(gl.font_rid, gl.font_size, 0x00ad, 0); float w = font_get_glyph_advance(gl.font_rid, gl.font_size, index)[(p_new_sd->orientation == ORIENTATION_HORIZONTAL) ? 0 : 1]; gl.index = index; gl.advance = w; } - Variant key; - bool find_embedded = false; - if (gl.count == 1) { - for (const KeyValue &E : p_sd->objects) { - if (E.value.start == gl.start) { - find_embedded = true; - key = E.key; - p_new_sd->objects[key] = E.value; - break; + if ((gl.flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && gl.span_index >= 0 && gl.span_index < span_size) { + Variant key = p_sd->spans[gl.span_index].embedded_key; + if (key != Variant()) { + ShapedTextDataAdvanced::EmbeddedObject obj = p_sd->objects[key]; + if (p_new_sd->orientation == ORIENTATION_HORIZONTAL) { + obj.rect.position.x = p_new_sd->width; + p_new_sd->width += obj.rect.size.x; + } else { + obj.rect.position.y = p_new_sd->width; + p_new_sd->width += obj.rect.size.y; } - } - } - if (find_embedded) { - if (p_new_sd->orientation == ORIENTATION_HORIZONTAL) { - p_new_sd->objects[key].rect.position.x = p_new_sd->width; - p_new_sd->width += p_new_sd->objects[key].rect.size.x; - } else { - p_new_sd->objects[key].rect.position.y = p_new_sd->width; - p_new_sd->width += p_new_sd->objects[key].rect.size.y; + p_new_sd->objects[key] = obj; } } else { if (gl.font_rid.is_valid()) { @@ -5284,7 +5328,8 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ spans = parent_sd->spans; } - if (spans.size() == 0) { + int span_size = spans.size(); + if (span_size == 0) { return; } @@ -5296,7 +5341,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; if (add_ellipsis || enforce_ellipsis) { if (!_font_has_char(dot_gl_font_rid, sd->el_char)) { - const Array &fonts = spans[spans.size() - 1].fonts; + const Array &fonts = spans[span_size - 1].fonts; for (int i = 0; i < fonts.size(); i++) { if (_font_has_char(fonts[i], sd->el_char)) { dot_gl_font_rid = fonts[i]; @@ -5306,7 +5351,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ } if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { const char32_t u32str[] = { sd->el_char, 0 }; - RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str); + RID rid = _find_sys_font_for_text(fonts[0], String(), spans[span_size - 1].language, u32str); if (rid.is_valid()) { dot_gl_font_rid = rid; found_el_char = true; @@ -5319,7 +5364,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ bool found_dot_char = false; dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; if (!_font_has_char(dot_gl_font_rid, '.')) { - const Array &fonts = spans[spans.size() - 1].fonts; + const Array &fonts = spans[span_size - 1].fonts; for (int i = 0; i < fonts.size(); i++) { if (_font_has_char(fonts[i], '.')) { dot_gl_font_rid = fonts[i]; @@ -5328,7 +5373,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ } } if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { - RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, "."); + RID rid = _find_sys_font_for_text(fonts[0], String(), spans[span_size - 1].language, "."); if (rid.is_valid()) { dot_gl_font_rid = rid; } @@ -5338,7 +5383,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ } RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; if (!_font_has_char(whitespace_gl_font_rid, ' ')) { - const Array &fonts = spans[spans.size() - 1].fonts; + const Array &fonts = spans[span_size - 1].fonts; for (int i = 0; i < fonts.size(); i++) { if (_font_has_char(fonts[i], ' ')) { whitespace_gl_font_rid = fonts[i]; @@ -5497,7 +5542,8 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const { spans = parent_sd->spans; } - while (i < spans.size()) { + int span_size = spans.size(); + while (i < span_size) { if (spans[i].start > p_sd->end) { break; } @@ -5508,7 +5554,7 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const { 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) { + while (i + 1 < span_size && language == spans[i + 1].language) { i++; } int r_end = MIN(spans[i].end - p_sd->start, p_sd->text.length()); @@ -5570,10 +5616,11 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) { sd->break_inserts = 0; UErrorCode err = U_ZERO_ERROR; int i = 0; - while (i < sd->spans.size()) { + int span_size = sd->spans.size(); + while (i < span_size) { String language = sd->spans[i].language; int r_start = sd->spans[i].start; - while (i + 1 < sd->spans.size() && language == sd->spans[i + 1].language) { + while (i + 1 < span_size && language == sd->spans[i + 1].language) { i++; } int r_end = sd->spans[i].end; @@ -5697,6 +5744,7 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) { } } Glyph gl; + gl.span_index = sd_glyphs[i].span_index; gl.start = sd_glyphs[i].start; gl.end = sd_glyphs[i].end; gl.count = 1; @@ -5945,6 +5993,7 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap } // Inject virtual space for alignment. Glyph gl; + gl.span_index = sd_glyphs[i].span_index; gl.start = sd_glyphs[i].start; gl.end = sd_glyphs[i].end; gl.count = 1; @@ -6091,6 +6140,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star for (int i = fb_from; i != fb_to; i += fb_delta) { if (p_sd->preserve_invalid || (p_sd->preserve_control && is_control(p_sd->text[i]))) { Glyph gl; + gl.span_index = p_span; gl.start = i + p_sd->start; gl.end = i + 1 + p_sd->start; gl.count = 1; @@ -6247,6 +6297,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star Glyph &gl = w[i]; gl = Glyph(); + gl.span_index = p_span; gl.start = glyph_info[i].cluster; gl.end = end; gl.count = 0; @@ -6556,6 +6607,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { gl.start = span.start; gl.end = span.end; gl.count = 1; + gl.span_index = k; gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT; if (sd->orientation == ORIENTATION_HORIZONTAL) { gl.advance = sd->objects[span.embedded_key].rect.size.x; diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 86fbfb70b06..ab37abc6f7e 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -489,6 +489,8 @@ class TextServerAdvanced : public TextServerExtension { Variant meta; }; Vector spans; + int first_span = 0; // First span in the parent ShapedTextData. + int last_span = 0; struct EmbeddedObject { int start = -1; @@ -957,6 +959,7 @@ public: MODBIND1RC(int64_t, shaped_get_span_count, const RID &); MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t); + MODBIND2RC(Variant, shaped_get_span_embedded_object, const RID &, int64_t); MODBIND5(shaped_set_span_update_font, const RID &, int64_t, const TypedArray &, int64_t, const Dictionary &); MODBIND3RC(RID, shaped_text_substr, const RID &, int64_t, int64_t); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index f3bfce60e88..f367567feed 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -3087,15 +3087,14 @@ void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) { } } - for (int k = 0; k < parent->spans.size(); k++) { - ShapedTextDataFallback::Span span = parent->spans[k]; - if (span.start >= p_shaped->end || span.end <= p_shaped->start) { - continue; - } + for (int i = p_shaped->first_span; i <= p_shaped->last_span; i++) { + ShapedTextDataFallback::Span span = parent->spans[i]; span.start = MAX(p_shaped->start, span.start); span.end = MIN(p_shaped->end, span.end); p_shaped->spans.push_back(span); } + p_shaped->first_span = 0; + p_shaped->last_span = 0; p_shaped->parent = RID(); } @@ -3121,6 +3120,8 @@ void TextServerFallback::_shaped_text_clear(const RID &p_shaped) { sd->end = 0; sd->text = String(); sd->spans.clear(); + sd->first_span = 0; + sd->last_span = 0; sd->objects.clear(); invalidate(sd); } @@ -3273,19 +3274,48 @@ int64_t TextServerFallback::_shaped_text_get_spacing(const RID &p_shaped, Spacin int64_t TextServerFallback::_shaped_get_span_count(const RID &p_shaped) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL_V(sd, 0); - return sd->spans.size(); + + if (sd->parent != RID()) { + return sd->last_span - sd->first_span + 1; + } else { + return sd->spans.size(); + } } Variant TextServerFallback::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL_V(sd, Variant()); - ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); - return sd->spans[p_index].meta; + if (sd->parent != RID()) { + ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent); + ERR_FAIL_COND_V(!parent_sd->valid.is_set(), Variant()); + ERR_FAIL_INDEX_V(p_index + sd->first_span, parent_sd->spans.size(), Variant()); + return parent_sd->spans[p_index + sd->first_span].meta; + } else { + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].meta; + } +} + +Variant TextServerFallback::_shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const { + ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); + ERR_FAIL_NULL_V(sd, Variant()); + if (sd->parent != RID()) { + ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent); + ERR_FAIL_COND_V(!parent_sd->valid.is_set(), Variant()); + ERR_FAIL_INDEX_V(p_index + sd->first_span, parent_sd->spans.size(), Variant()); + return parent_sd->spans[p_index + sd->first_span].embedded_key; + } else { + ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant()); + return sd->spans[p_index].embedded_key; + } } void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL(sd); + if (sd->parent != RID()) { + full_copy(sd); + } ERR_FAIL_INDEX(p_index, sd->spans.size()); ShapedTextDataFallback::Span &span = sd->spans.ptrw()[p_index]; @@ -3407,18 +3437,22 @@ bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const V sd->width = 0; sd->upos = 0; sd->uthk = 0; + + Vector &spans = sd->spans; + if (sd->parent != RID()) { + ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent); + ERR_FAIL_COND_V(!parent_sd->valid.is_set(), false); + spans = parent_sd->spans; + } + int sd_size = sd->glyphs.size(); + int span_size = spans.size(); for (int i = 0; i < sd_size; i++) { Glyph gl = sd->glyphs[i]; Variant key; - if (gl.count == 1) { - for (const KeyValue &E : sd->objects) { - if (E.value.start == gl.start) { - key = E.key; - break; - } - } + if ((gl.flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && gl.span_index + sd->first_span >= 0 && gl.span_index + sd->first_span < span_size) { + key = spans[gl.span_index + sd->first_span].embedded_key; } if (key != Variant()) { if (sd->orientation == ORIENTATION_HORIZONTAL) { @@ -3570,35 +3604,46 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start if (p_length > 0) { new_sd->text = sd->text.substr(p_start - sd->start, p_length); + + int span_size = sd->spans.size(); + + new_sd->first_span = 0; + new_sd->last_span = span_size - 1; + for (int i = 0; i < span_size; i++) { + const ShapedTextDataFallback::Span &span = sd->spans[i]; + if (span.end <= p_start) { + new_sd->first_span = i + 1; + } else if (span.start >= p_start + p_length) { + new_sd->last_span = i - 1; + break; + } + } + int sd_size = sd->glyphs.size(); const Glyph *sd_glyphs = sd->glyphs.ptr(); for (int i = 0; i < sd_size; i++) { if ((sd_glyphs[i].start >= new_sd->start) && (sd_glyphs[i].end <= new_sd->end)) { Glyph gl = sd_glyphs[i]; + if (gl.span_index >= 0) { + gl.span_index -= new_sd->first_span; + } if (gl.end == p_start + p_length && ((gl.flags & GRAPHEME_IS_SOFT_HYPHEN) == GRAPHEME_IS_SOFT_HYPHEN)) { gl.index = 0x00ad; gl.advance = font_get_glyph_advance(gl.font_rid, gl.font_size, 0x00ad).x; } - Variant key; - bool find_embedded = false; - if (gl.count == 1) { - for (const KeyValue &E : sd->objects) { - if (E.value.start == gl.start) { - find_embedded = true; - key = E.key; - new_sd->objects[key] = E.value; - break; + if ((gl.flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && gl.span_index >= 0 && gl.span_index < span_size) { + Variant key = sd->spans[gl.span_index].embedded_key; + if (key != Variant()) { + ShapedTextDataFallback::EmbeddedObject obj = sd->objects[key]; + if (new_sd->orientation == ORIENTATION_HORIZONTAL) { + obj.rect.position.x = new_sd->width; + new_sd->width += obj.rect.size.x; + } else { + obj.rect.position.y = new_sd->width; + new_sd->width += obj.rect.size.y; } - } - } - if (find_embedded) { - if (new_sd->orientation == ORIENTATION_HORIZONTAL) { - new_sd->objects[key].rect.position.x = new_sd->width; - new_sd->width += new_sd->objects[key].rect.size.x; - } else { - new_sd->objects[key].rect.position.y = new_sd->width; - new_sd->width += new_sd->objects[key].rect.size.y; + new_sd->objects[key] = obj; } } else { if (gl.font_rid.is_valid()) { @@ -4088,7 +4133,8 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ spans = parent_sd->spans; } - if (spans.size() == 0) { + int span_size = spans.size(); + if (span_size == 0) { return; } @@ -4100,7 +4146,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; if (add_ellipsis || enforce_ellipsis) { if (!_font_has_char(dot_gl_font_rid, sd->el_char)) { - const Array &fonts = spans[spans.size() - 1].fonts; + const Array &fonts = spans[span_size - 1].fonts; for (int i = 0; i < fonts.size(); i++) { if (_font_has_char(fonts[i], sd->el_char)) { dot_gl_font_rid = fonts[i]; @@ -4110,7 +4156,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ } if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { const char32_t u32str[] = { sd->el_char, 0 }; - RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str); + RID rid = _find_sys_font_for_text(fonts[0], String(), spans[span_size - 1].language, u32str); if (rid.is_valid()) { dot_gl_font_rid = rid; found_el_char = true; @@ -4123,7 +4169,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ bool found_dot_char = false; dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; if (!_font_has_char(dot_gl_font_rid, '.')) { - const Array &fonts = spans[spans.size() - 1].fonts; + const Array &fonts = spans[span_size - 1].fonts; for (int i = 0; i < fonts.size(); i++) { if (_font_has_char(fonts[i], '.')) { dot_gl_font_rid = fonts[i]; @@ -4132,7 +4178,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ } } if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) { - RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, "."); + RID rid = _find_sys_font_for_text(fonts[0], String(), spans[span_size - 1].language, "."); if (rid.is_valid()) { dot_gl_font_rid = rid; } @@ -4142,7 +4188,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ } RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; if (!_font_has_char(whitespace_gl_font_rid, ' ')) { - const Array &fonts = spans[spans.size() - 1].fonts; + const Array &fonts = spans[span_size - 1].fonts; for (int i = 0; i < fonts.size(); i++) { if (_font_has_char(fonts[i], ' ')) { whitespace_gl_font_rid = fonts[i]; @@ -4314,6 +4360,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { sd->width += sd->objects[span.embedded_key].rect.size.y; } Glyph gl; + gl.span_index = i; gl.start = span.start; gl.end = span.end; gl.count = 1; @@ -4330,6 +4377,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { RID prev_font; for (int j = span.start; j < span.end; j++) { Glyph gl; + gl.span_index = i; gl.start = j; gl.end = j + 1; gl.count = 1; diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index f784a559288..b66f7189bee 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -427,6 +427,8 @@ class TextServerFallback : public TextServerExtension { Variant meta; }; Vector spans; + int first_span = 0; // First span in the parent ShapedTextData. + int last_span = 0; struct EmbeddedObject { int start = -1; @@ -817,6 +819,7 @@ public: MODBIND1RC(int64_t, shaped_get_span_count, const RID &); MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t); + MODBIND2RC(Variant, shaped_get_span_embedded_object, const RID &, int64_t); MODBIND5(shaped_set_span_update_font, const RID &, int64_t, const TypedArray &, int64_t, const Dictionary &); MODBIND3RC(RID, shaped_text_substr, const RID &, int64_t, int64_t); diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp index 01f15137bec..6cc9f4ee497 100644 --- a/scene/3d/label_3d.cpp +++ b/scene/3d/label_3d.cpp @@ -344,7 +344,7 @@ void Label3D::_generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset, gl_uv = TS->font_get_glyph_uv_rect(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index); texs = TS->font_get_glyph_texture_size(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index); } - } else { + } else if (((p_glyph.flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((p_glyph.flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { gl_sz = TS->get_hex_code_box_size(p_glyph.font_size, p_glyph.index) * pixel_size; gl_of = Vector2(0, -gl_sz.y); } diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index b5acbcc6f22..8d8f32cc1de 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -372,7 +372,7 @@ void Label::_update_visible() const { inline void draw_glyph(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Vector2 &p_ofs) { if (p_gl.font_rid != RID()) { TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); - } else { + } else if (((p_gl.flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((p_gl.flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); } } diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index ad850b574c3..66e6b624ae1 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -550,7 +550,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref if (font_size_it && font_size_it->font_size > 0) { font_size = font_size_it->font_size; } - l.text_buf->add_string(String::chr(0x200B), font, font_size); + l.text_buf->add_string(String::chr(0x200B), font, font_size, String(), it->rid); txt += "\n"; l.char_count++; remaining_characters--; @@ -792,7 +792,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o MutexLock lock(l.text_buf->get_mutex()); Item *it_from = l.from; - Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr; if (it_from == nullptr) { return 0; @@ -1032,9 +1031,26 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o float box_start = 0.0; Color last_color = Color(0, 0, 0, 0); + Item *it = it_from; + int span = -1; for (int i = 0; i < gl_size; i++) { bool selected = selection.active && (sel_start != -1) && (glyphs[i].start >= sel_start) && (glyphs[i].end <= sel_end); - Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start); + if (glyphs[i].span_index != span) { + span = glyphs[i].span_index; + if (span >= 0) { + if ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) == TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) { + Item *new_it = items.get_or_null(TS->shaped_get_span_embedded_object(rid, span)); + if (new_it) { + it = new_it; + } + } else { + Item *new_it = items.get_or_null(TS->shaped_get_span_meta(rid, span)); + if (new_it) { + it = new_it; + } + } + } + } Color font_color = (step == DRAW_STEP_SHADOW || step == DRAW_STEP_OUTLINE || step == DRAW_STEP_TEXT) ? _find_color(it, p_base_color) : Color(); int outline_size = (step == DRAW_STEP_OUTLINE) ? _find_outline_size(it, p_outline_size) : 0; diff --git a/servers/text/text_server_dummy.h b/servers/text/text_server_dummy.h index 10fe3b45951..c7dba0f5cf7 100644 --- a/servers/text/text_server_dummy.h +++ b/servers/text/text_server_dummy.h @@ -99,6 +99,7 @@ public: virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) override { return false; } virtual int64_t shaped_get_span_count(const RID &p_shaped) const override { return 0; } virtual Variant shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const override { return Variant(); } + virtual Variant shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const override { return Variant(); } virtual void shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) override {} virtual RID shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const override { return RID(); } virtual RID shaped_text_get_parent(const RID &p_shaped) const override { return RID(); } diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp index 40d13d24cf3..b26dd4ea6a5 100644 --- a/servers/text/text_server_extension.cpp +++ b/servers/text/text_server_extension.cpp @@ -268,6 +268,7 @@ void TextServerExtension::_bind_methods() { GDVIRTUAL_BIND(_shaped_get_span_count, "shaped"); GDVIRTUAL_BIND(_shaped_get_span_meta, "shaped", "index"); + GDVIRTUAL_BIND(_shaped_get_span_embedded_object, "shaped", "index"); GDVIRTUAL_BIND(_shaped_set_span_update_font, "shaped", "index", "fonts", "size", "opentype_features"); GDVIRTUAL_BIND(_shaped_text_substr, "shaped", "start", "length"); @@ -1187,11 +1188,17 @@ int64_t TextServerExtension::shaped_get_span_count(const RID &p_shaped) const { } Variant TextServerExtension::shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const { - Variant ret = false; + Variant ret; GDVIRTUAL_CALL(_shaped_get_span_meta, p_shaped, p_index, ret); return ret; } +Variant TextServerExtension::shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const { + Variant ret; + GDVIRTUAL_CALL(_shaped_get_span_embedded_object, p_shaped, p_index, ret); + return ret; +} + void TextServerExtension::shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) { GDVIRTUAL_CALL(_shaped_set_span_update_font, p_shaped, p_index, p_fonts, p_size, p_opentype_features); } diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h index ea0487832cb..39e61d8aca8 100644 --- a/servers/text/text_server_extension.h +++ b/servers/text/text_server_extension.h @@ -444,9 +444,11 @@ public: virtual int64_t shaped_get_span_count(const RID &p_shaped) const override; virtual Variant shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const override; + virtual Variant shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const override; virtual void shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary()) override; GDVIRTUAL1RC_REQUIRED(int64_t, _shaped_get_span_count, RID); GDVIRTUAL2RC_REQUIRED(Variant, _shaped_get_span_meta, RID, int64_t); + GDVIRTUAL2RC_REQUIRED(Variant, _shaped_get_span_embedded_object, RID, int64_t); GDVIRTUAL5_REQUIRED(_shaped_set_span_update_font, RID, int64_t, const TypedArray &, int64_t, const Dictionary &); virtual RID shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const override; diff --git a/servers/text_server.cpp b/servers/text_server.cpp index ab69ef71b49..83a53bfc7c1 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -424,6 +424,7 @@ void TextServer::_bind_methods() { ClassDB::bind_method(D_METHOD("shaped_get_span_count", "shaped"), &TextServer::shaped_get_span_count); ClassDB::bind_method(D_METHOD("shaped_get_span_meta", "shaped", "index"), &TextServer::shaped_get_span_meta); + ClassDB::bind_method(D_METHOD("shaped_get_span_embedded_object", "shaped", "index"), &TextServer::shaped_get_span_embedded_object); ClassDB::bind_method(D_METHOD("shaped_set_span_update_font", "shaped", "index", "fonts", "size", "opentype_features"), &TextServer::shaped_set_span_update_font, DEFVAL(Dictionary())); ClassDB::bind_method(D_METHOD("shaped_text_substr", "shaped", "start", "length"), &TextServer::shaped_text_substr); diff --git a/servers/text_server.h b/servers/text_server.h index aeeb004dcf7..5e6cbceaee4 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -468,6 +468,7 @@ public: virtual int64_t shaped_get_span_count(const RID &p_shaped) const = 0; virtual Variant shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const = 0; + virtual Variant shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const = 0; virtual void shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary()) = 0; virtual RID shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const = 0; // Copy shaped substring (e.g. line break) without reshaping, but correctly reordered, preservers range. @@ -587,9 +588,11 @@ struct Glyph { float advance = 0.f; // Advance to the next glyph along baseline(x for horizontal layout, y for vertical). RID font_rid; // Font resource. - int font_size = 0; // Font size; + int font_size = 0; // Font size. int32_t index = 0; // Glyph index (font specific) or UTF-32 codepoint (for the invalid glyphs). + int span_index = -1; + bool operator==(const Glyph &p_a) const; bool operator!=(const Glyph &p_a) const;