summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/classes/TextLine.xml1
-rw-r--r--doc/classes/TextParagraph.xml1
-rw-r--r--doc/classes/TextServer.xml27
-rw-r--r--doc/classes/TextServerExtension.xml27
-rw-r--r--modules/text_server_adv/text_server_adv.cpp492
-rw-r--r--modules/text_server_adv/text_server_adv.h29
-rw-r--r--modules/text_server_fb/text_server_fb.cpp291
-rw-r--r--modules/text_server_fb/text_server_fb.h33
-rw-r--r--scene/gui/label.cpp40
-rw-r--r--scene/gui/label.h1
-rw-r--r--scene/gui/rich_text_label.cpp77
-rw-r--r--scene/gui/rich_text_label.h2
-rw-r--r--scene/gui/text_edit.cpp73
-rw-r--r--scene/gui/text_edit.h3
-rw-r--r--scene/resources/text_line.cpp6
-rw-r--r--scene/resources/text_line.h2
-rw-r--r--scene/resources/text_paragraph.cpp6
-rw-r--r--scene/resources/text_paragraph.h2
-rw-r--r--servers/text/text_server_extension.cpp34
-rw-r--r--servers/text/text_server_extension.h11
-rw-r--r--servers/text_server.cpp6
-rw-r--r--servers/text_server.h20
22 files changed, 699 insertions, 485 deletions
diff --git a/doc/classes/TextLine.xml b/doc/classes/TextLine.xml
index bf1abf86ae..7bfc7eed7e 100644
--- a/doc/classes/TextLine.xml
+++ b/doc/classes/TextLine.xml
@@ -26,6 +26,7 @@
<argument index="2" name="size" type="int" />
<argument index="3" name="opentype_features" type="Dictionary" default="{}" />
<argument index="4" name="language" type="String" default="&quot;&quot;" />
+ <argument index="5" name="meta" type="Variant" default="null" />
<description>
Adds text span and font to draw it.
</description>
diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml
index 3d3c0dd6ff..acf3dcd43b 100644
--- a/doc/classes/TextParagraph.xml
+++ b/doc/classes/TextParagraph.xml
@@ -26,6 +26,7 @@
<argument index="2" name="size" type="int" />
<argument index="3" name="opentype_features" type="Dictionary" default="{}" />
<argument index="4" name="language" type="String" default="&quot;&quot;" />
+ <argument index="5" name="meta" type="Variant" default="null" />
<description>
Adds text span and font to draw it.
</description>
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index 61024ef0c8..9025e4ff2a 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -896,6 +896,32 @@
[b]Note:[/b] This function is used by during project export, to include TextServer database.
</description>
</method>
+ <method name="shaped_get_span_count" qualifiers="const">
+ <return type="int" />
+ <argument index="0" name="shaped" type="RID" />
+ <description>
+ Returns number of text spans added using [method shaped_text_add_string] or [method shaped_text_add_object].
+ </description>
+ </method>
+ <method name="shaped_get_span_meta" qualifiers="const">
+ <return type="Variant" />
+ <argument index="0" name="shaped" type="RID" />
+ <argument index="1" name="index" type="int" />
+ <description>
+ Returns text span metadata.
+ </description>
+ </method>
+ <method name="shaped_set_span_update_font">
+ <return type="void" />
+ <argument index="0" name="shaped" type="RID" />
+ <argument index="1" name="index" type="int" />
+ <argument index="2" name="fonts" type="Array" />
+ <argument index="3" name="size" type="int" />
+ <argument index="4" name="opentype_features" type="Dictionary" default="{}" />
+ <description>
+ Changes text span font, font size and OpenType features, without changing the text.
+ </description>
+ </method>
<method name="shaped_text_add_object">
<return type="bool" />
<argument index="0" name="shaped" type="RID" />
@@ -915,6 +941,7 @@
<argument index="3" name="size" type="int" />
<argument index="4" name="opentype_features" type="Dictionary" default="{}" />
<argument index="5" name="language" type="String" default="&quot;&quot;" />
+ <argument index="6" name="meta" type="Variant" default="null" />
<description>
Adds text span and font to draw it to the text buffer.
</description>
diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml
index 8b4825026d..5967340f8c 100644
--- a/doc/classes/TextServerExtension.xml
+++ b/doc/classes/TextServerExtension.xml
@@ -904,6 +904,32 @@
[b]Note:[/b] This function is used by during project export, to include TextServer database.
</description>
</method>
+ <method name="_shaped_get_span_count" qualifiers="virtual const">
+ <return type="int" />
+ <argument index="0" name="shaped" type="RID" />
+ <description>
+ Returns number of text spans added using [method _shaped_text_add_string] or [method _shaped_text_add_object].
+ </description>
+ </method>
+ <method name="_shaped_get_span_meta" qualifiers="virtual const">
+ <return type="Variant" />
+ <argument index="0" name="shaped" type="RID" />
+ <argument index="1" name="index" type="int" />
+ <description>
+ Returns text span metadata.
+ </description>
+ </method>
+ <method name="_shaped_set_span_update_font" qualifiers="virtual">
+ <return type="void" />
+ <argument index="0" name="shaped" type="RID" />
+ <argument index="1" name="index" type="int" />
+ <argument index="2" name="fonts" type="Array" />
+ <argument index="3" name="size" type="int" />
+ <argument index="4" name="opentype_features" type="Dictionary" />
+ <description>
+ Changes text span font, font size and OpenType features, without changing the text.
+ </description>
+ </method>
<method name="_shaped_text_add_object" qualifiers="virtual">
<return type="bool" />
<argument index="0" name="shaped" type="RID" />
@@ -923,6 +949,7 @@
<argument index="3" name="size" type="int" />
<argument index="4" name="opentype_features" type="Dictionary" />
<argument index="5" name="language" type="String" />
+ <argument index="6" name="meta" type="Variant" />
<description>
Adds text span and font to draw it to the text buffer.
</description>
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index e0ee809331..e50a5337cb 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -2901,7 +2901,7 @@ void TextServerAdvanced::font_set_global_oversampling(float p_oversampling) {
List<RID> text_bufs;
shaped_owner.get_owned_list(&text_bufs);
for (const RID &E : text_bufs) {
- invalidate(shaped_owner.get_or_null(E));
+ invalidate(shaped_owner.get_or_null(E), false);
}
}
}
@@ -2936,7 +2936,7 @@ int TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int
return limit;
}
-void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped) {
+void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) {
p_shaped->valid = false;
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
@@ -2951,27 +2951,32 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *
p_shaped->glyphs_logical.clear();
p_shaped->overrun_trim_data = TrimData();
p_shaped->utf16 = Char16String();
- if (p_shaped->script_iter != nullptr) {
- memdelete(p_shaped->script_iter);
- p_shaped->script_iter = nullptr;
- }
for (int i = 0; i < p_shaped->bidi_iter.size(); i++) {
ubidi_close(p_shaped->bidi_iter[i]);
}
p_shaped->bidi_iter.clear();
+
+ if (p_text) {
+ if (p_shaped->script_iter != nullptr) {
+ memdelete(p_shaped->script_iter);
+ p_shaped->script_iter = nullptr;
+ }
+ p_shaped->break_ops_valid = false;
+ p_shaped->js_ops_valid = false;
+ }
}
void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) {
ShapedTextDataAdvanced *parent = shaped_owner.get_or_null(p_shaped->parent);
- for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) {
+ for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : parent->objects) {
if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) {
p_shaped->objects[E.key] = E.value;
}
}
- for (int k = 0; k < parent->spans.size(); k++) {
- ShapedTextDataAdvanced::Span span = parent->spans[k];
+ for (int i = 0; i < parent->spans.size(); i++) {
+ ShapedTextDataAdvanced::Span span = parent->spans[i];
if (span.start >= p_shaped->end || span.end <= p_shaped->start) {
continue;
}
@@ -3004,7 +3009,7 @@ void TextServerAdvanced::shaped_text_clear(RID p_shaped) {
sd->spans.clear();
sd->objects.clear();
sd->bidi_override.clear();
- invalidate(sd);
+ invalidate(sd, true);
}
void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Direction p_direction) {
@@ -3017,7 +3022,7 @@ void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Dir
full_copy(sd);
}
sd->direction = p_direction;
- invalidate(sd);
+ invalidate(sd, false);
}
}
@@ -3047,7 +3052,7 @@ void TextServerAdvanced::shaped_text_set_custom_punctuation(RID p_shaped, const
full_copy(sd);
}
sd->custom_punct = p_punct;
- invalidate(sd);
+ invalidate(sd, false);
}
}
@@ -3070,7 +3075,7 @@ void TextServerAdvanced::shaped_text_set_bidi_override(RID p_shaped, const Array
for (int i = 0; i < p_override.size(); i++) {
sd->bidi_override.push_back(p_override[i]);
}
- invalidate(sd);
+ invalidate(sd, false);
}
void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) {
@@ -3083,7 +3088,7 @@ void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::O
full_copy(sd);
}
sd->orientation = p_orientation;
- invalidate(sd);
+ invalidate(sd, false);
}
}
@@ -3095,7 +3100,7 @@ void TextServerAdvanced::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e
ERR_FAIL_COND(sd->parent != RID());
if (sd->preserve_invalid != p_enabled) {
sd->preserve_invalid = p_enabled;
- invalidate(sd);
+ invalidate(sd, false);
}
}
@@ -3117,7 +3122,7 @@ void TextServerAdvanced::shaped_text_set_preserve_control(RID p_shaped, bool p_e
full_copy(sd);
}
sd->preserve_control = p_enabled;
- invalidate(sd);
+ invalidate(sd, false);
}
}
@@ -3137,7 +3142,41 @@ TextServer::Orientation TextServerAdvanced::shaped_text_get_orientation(RID p_sh
return sd->orientation;
}
-bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
+int TextServerAdvanced::shaped_get_span_count(RID p_shaped) const {
+ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V(!sd, 0);
+ return sd->spans.size();
+}
+
+Variant TextServerAdvanced::shaped_get_span_meta(RID p_shaped, int p_index) const {
+ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V(!sd, Variant());
+ ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
+ return sd->spans[p_index].meta;
+}
+
+void TextServerAdvanced::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) {
+ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND(!sd);
+ ERR_FAIL_INDEX(p_index, sd->spans.size());
+
+ ShapedTextDataAdvanced::Span &span = sd->spans.write[p_index];
+ bool changed = (span.font_size != p_size) || (span.features != p_opentype_features) || (p_fonts.size() != span.fonts.size());
+ if (!changed) {
+ for (int i = 0; i < p_fonts.size(); i++) {
+ changed = changed || (span.fonts[i] != p_fonts[i]);
+ }
+ }
+ if (changed) {
+ span.fonts = p_fonts;
+ span.font_size = p_size;
+ span.features = p_opentype_features;
+
+ invalidate(sd, false);
+ }
+}
+
+bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
ERR_FAIL_COND_V(p_size <= 0, false);
@@ -3162,11 +3201,12 @@ bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_te
span.font_size = p_size;
span.language = p_language;
span.features = p_opentype_features;
+ span.meta = p_meta;
sd->spans.push_back(span);
sd->text += p_text;
sd->end += p_text.length();
- invalidate(sd);
+ invalidate(sd, true);
return true;
}
@@ -3196,13 +3236,13 @@ bool TextServerAdvanced::shaped_text_add_object(RID p_shaped, Variant p_key, con
sd->text += String::chr(0xfffc).repeat(p_length);
sd->end += p_length;
sd->objects[p_key] = obj;
- invalidate(sd);
+ invalidate(sd, true);
return true;
}
bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@@ -3222,7 +3262,7 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key,
Glyph gl = sd->glyphs[i];
Variant key;
if (gl.count == 1) {
- for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
+ for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) {
if (E.value.pos == gl.start) {
key = E.key;
break;
@@ -3262,75 +3302,78 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key,
sd->width += gl.advance * gl.repeat;
}
}
+ _realign(sd);
+ }
+ return true;
+}
- // Align embedded objects to baseline.
- float full_ascent = sd->ascent;
- float full_descent = sd->descent;
- for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
- if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) {
- if (sd->orientation == ORIENTATION_HORIZONTAL) {
- switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
- case INLINE_ALIGNMENT_TO_TOP: {
- E.value.rect.position.y = -sd->ascent;
- } break;
- case INLINE_ALIGNMENT_TO_CENTER: {
- E.value.rect.position.y = (-sd->ascent + sd->descent) / 2;
- } break;
- case INLINE_ALIGNMENT_TO_BASELINE: {
- E.value.rect.position.y = 0;
- } break;
- case INLINE_ALIGNMENT_TO_BOTTOM: {
- E.value.rect.position.y = sd->descent;
- } break;
- }
- switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
- case INLINE_ALIGNMENT_BOTTOM_TO: {
- E.value.rect.position.y -= E.value.rect.size.y;
- } break;
- case INLINE_ALIGNMENT_CENTER_TO: {
- E.value.rect.position.y -= E.value.rect.size.y / 2;
- } break;
- case INLINE_ALIGNMENT_TOP_TO: {
- // NOP
- } break;
- }
- full_ascent = MAX(full_ascent, -E.value.rect.position.y);
- full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
- } else {
- switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
- case INLINE_ALIGNMENT_TO_TOP: {
- E.value.rect.position.x = -sd->ascent;
- } break;
- case INLINE_ALIGNMENT_TO_CENTER: {
- E.value.rect.position.x = (-sd->ascent + sd->descent) / 2;
- } break;
- case INLINE_ALIGNMENT_TO_BASELINE: {
- E.value.rect.position.x = 0;
- } break;
- case INLINE_ALIGNMENT_TO_BOTTOM: {
- E.value.rect.position.x = sd->descent;
- } break;
- }
- switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
- case INLINE_ALIGNMENT_BOTTOM_TO: {
- E.value.rect.position.x -= E.value.rect.size.x;
- } break;
- case INLINE_ALIGNMENT_CENTER_TO: {
- E.value.rect.position.x -= E.value.rect.size.x / 2;
- } break;
- case INLINE_ALIGNMENT_TOP_TO: {
- // NOP
- } break;
- }
- full_ascent = MAX(full_ascent, -E.value.rect.position.x);
- full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
+void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const {
+ // Align embedded objects to baseline.
+ float full_ascent = p_sd->ascent;
+ float full_descent = p_sd->descent;
+ for (KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) {
+ if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) {
+ if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
+ switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
+ case INLINE_ALIGNMENT_TO_TOP: {
+ E.value.rect.position.y = -p_sd->ascent;
+ } break;
+ case INLINE_ALIGNMENT_TO_CENTER: {
+ E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2;
+ } break;
+ case INLINE_ALIGNMENT_TO_BASELINE: {
+ E.value.rect.position.y = 0;
+ } break;
+ case INLINE_ALIGNMENT_TO_BOTTOM: {
+ E.value.rect.position.y = p_sd->descent;
+ } break;
+ }
+ switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
+ case INLINE_ALIGNMENT_BOTTOM_TO: {
+ E.value.rect.position.y -= E.value.rect.size.y;
+ } break;
+ case INLINE_ALIGNMENT_CENTER_TO: {
+ E.value.rect.position.y -= E.value.rect.size.y / 2;
+ } break;
+ case INLINE_ALIGNMENT_TOP_TO: {
+ // NOP
+ } break;
+ }
+ full_ascent = MAX(full_ascent, -E.value.rect.position.y);
+ full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
+ } else {
+ switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
+ case INLINE_ALIGNMENT_TO_TOP: {
+ E.value.rect.position.x = -p_sd->ascent;
+ } break;
+ case INLINE_ALIGNMENT_TO_CENTER: {
+ E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2;
+ } break;
+ case INLINE_ALIGNMENT_TO_BASELINE: {
+ E.value.rect.position.x = 0;
+ } break;
+ case INLINE_ALIGNMENT_TO_BOTTOM: {
+ E.value.rect.position.x = p_sd->descent;
+ } break;
+ }
+ switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
+ case INLINE_ALIGNMENT_BOTTOM_TO: {
+ E.value.rect.position.x -= E.value.rect.size.x;
+ } break;
+ case INLINE_ALIGNMENT_CENTER_TO: {
+ E.value.rect.position.x -= E.value.rect.size.x / 2;
+ } break;
+ case INLINE_ALIGNMENT_TOP_TO: {
+ // NOP
+ } break;
}
+ full_ascent = MAX(full_ascent, -E.value.rect.position.x);
+ full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
}
}
- sd->ascent = full_ascent;
- sd->descent = full_descent;
}
- return true;
+ p_sd->ascent = full_ascent;
+ p_sd->descent = full_descent;
}
RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_length) const {
@@ -3423,7 +3466,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
Variant key;
bool find_embedded = false;
if (gl.count == 1) {
- for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_sd->objects) {
+ for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) {
if (E.value.pos == gl.start) {
find_embedded = true;
key = E.key;
@@ -3466,72 +3509,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
}
}
- // Align embedded objects to baseline.
- float full_ascent = p_new_sd->ascent;
- float full_descent = p_new_sd->descent;
- for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_new_sd->objects) {
- if ((E.value.pos >= p_new_sd->start) && (E.value.pos < p_new_sd->end)) {
- if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
- switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
- case INLINE_ALIGNMENT_TO_TOP: {
- E.value.rect.position.y = -p_new_sd->ascent;
- } break;
- case INLINE_ALIGNMENT_TO_CENTER: {
- E.value.rect.position.y = (-p_new_sd->ascent + p_new_sd->descent) / 2;
- } break;
- case INLINE_ALIGNMENT_TO_BASELINE: {
- E.value.rect.position.y = 0;
- } break;
- case INLINE_ALIGNMENT_TO_BOTTOM: {
- E.value.rect.position.y = p_new_sd->descent;
- } break;
- }
- switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
- case INLINE_ALIGNMENT_BOTTOM_TO: {
- E.value.rect.position.y -= E.value.rect.size.y;
- } break;
- case INLINE_ALIGNMENT_CENTER_TO: {
- E.value.rect.position.y -= E.value.rect.size.y / 2;
- } break;
- case INLINE_ALIGNMENT_TOP_TO: {
- // NOP
- } break;
- }
- full_ascent = MAX(full_ascent, -E.value.rect.position.y);
- full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
- } else {
- switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
- case INLINE_ALIGNMENT_TO_TOP: {
- E.value.rect.position.x = -p_new_sd->ascent;
- } break;
- case INLINE_ALIGNMENT_TO_CENTER: {
- E.value.rect.position.x = (-p_new_sd->ascent + p_new_sd->descent) / 2;
- } break;
- case INLINE_ALIGNMENT_TO_BASELINE: {
- E.value.rect.position.x = 0;
- } break;
- case INLINE_ALIGNMENT_TO_BOTTOM: {
- E.value.rect.position.x = p_new_sd->descent;
- } break;
- }
- switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
- case INLINE_ALIGNMENT_BOTTOM_TO: {
- E.value.rect.position.x -= E.value.rect.size.x;
- } break;
- case INLINE_ALIGNMENT_CENTER_TO: {
- E.value.rect.position.x -= E.value.rect.size.x / 2;
- } break;
- case INLINE_ALIGNMENT_TOP_TO: {
- // NOP
- } break;
- }
- full_ascent = MAX(full_ascent, -E.value.rect.position.x);
- full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
- }
- }
- }
- p_new_sd->ascent = full_ascent;
- p_new_sd->descent = full_descent;
+ _realign(p_new_sd);
}
p_new_sd->valid = true;
@@ -3968,40 +3946,43 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
const UChar *data = sd->utf16.ptr();
- HashMap<int, bool> breaks;
- UErrorCode err = U_ZERO_ERROR;
- int i = 0;
- while (i < sd->spans.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) {
- i++;
- }
- int r_end = sd->spans[i].end;
- UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err);
- if (U_FAILURE(err)) {
- // No data loaded - use fallback.
- for (int j = r_start; j < r_end; j++) {
- char32_t c = sd->text[j - sd->start];
- if (is_whitespace(c)) {
- breaks[j + 1] = false;
- }
- if (is_linebreak(c)) {
- breaks[j + 1] = true;
- }
+ if (!sd->break_ops_valid) {
+ sd->breaks.clear();
+ UErrorCode err = U_ZERO_ERROR;
+ int i = 0;
+ while (i < sd->spans.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) {
+ i++;
}
- } else {
- while (ubrk_next(bi) != UBRK_DONE) {
- int pos = _convert_pos(sd, ubrk_current(bi)) + r_start;
- if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) {
- breaks[pos] = true;
- } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) {
- breaks[pos] = false;
+ int r_end = sd->spans[i].end;
+ UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err);
+ if (U_FAILURE(err)) {
+ // No data loaded - use fallback.
+ for (int j = r_start; j < r_end; j++) {
+ char32_t c = sd->text[j - sd->start];
+ if (is_whitespace(c)) {
+ sd->breaks[j + 1] = false;
+ }
+ if (is_linebreak(c)) {
+ sd->breaks[j + 1] = true;
+ }
+ }
+ } else {
+ while (ubrk_next(bi) != UBRK_DONE) {
+ int pos = _convert_pos(sd, ubrk_current(bi)) + r_start;
+ if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) {
+ sd->breaks[pos] = true;
+ } else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) {
+ sd->breaks[pos] = false;
+ }
}
}
+ ubrk_close(bi);
+ i++;
}
- ubrk_close(bi);
- i++;
+ sd->break_ops_valid = true;
}
sd->sort_valid = false;
@@ -4013,7 +3994,7 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
int c_punct_size = sd->custom_punct.length();
const char32_t *c_punct = sd->custom_punct.ptr();
- for (i = 0; i < sd_size; i++) {
+ for (int i = 0; i < sd_size; i++) {
if (sd_glyphs[i].count > 0) {
char32_t c = ch[sd_glyphs[i].start - sd->start];
if (c == 0xfffc) {
@@ -4040,8 +4021,8 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
if (is_underscore(c)) {
sd_glyphs[i].flags |= GRAPHEME_IS_UNDERSCORE;
}
- if (breaks.has(sd_glyphs[i].end)) {
- if (breaks[sd_glyphs[i].end] && (is_linebreak(c))) {
+ if (sd->breaks.has(sd_glyphs[i].end)) {
+ if (sd->breaks[sd_glyphs[i].end] && (is_linebreak(c))) {
sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD;
} else if (is_whitespace(c)) {
sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT;
@@ -4186,41 +4167,45 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) {
const UChar *data = sd->utf16.ptr();
int32_t data_size = sd->utf16.length();
- Map<int, bool> jstops;
+ if (!sd->js_ops_valid) {
+ sd->jstops.clear();
- // Use ICU word iterator and custom kashida detection.
- UErrorCode err = U_ZERO_ERROR;
- UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err);
- if (U_FAILURE(err)) {
- // No data - use fallback.
- int limit = 0;
- for (int i = 0; i < sd->text.length(); i++) {
- if (is_whitespace(data[i])) {
- int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start;
- if (ks != -1) {
- jstops[ks] = true;
- }
- limit = i + 1;
+ // Use ICU word iterator and custom kashida detection.
+ UErrorCode err = U_ZERO_ERROR;
+ UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err);
+ if (U_FAILURE(err)) {
+ // No data - use fallback.
+ int limit = 0;
+ for (int i = 0; i < sd->text.length(); i++) {
+ if (is_whitespace(data[i])) {
+ int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start;
+ if (ks != -1) {
+ sd->jstops[ks] = true;
+ }
+ limit = i + 1;
+ }
}
- }
- int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start;
- if (ks != -1) {
- jstops[ks] = true;
- }
- } else {
- int limit = 0;
- while (ubrk_next(bi) != UBRK_DONE) {
- if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) {
- int i = _convert_pos(sd, ubrk_current(bi));
- jstops[i + sd->start] = false;
- int ks = _generate_kashida_justification_opportunies(sd->text, limit, i);
- if (ks != -1) {
- jstops[ks + sd->start] = true;
- }
- limit = i;
+ int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start;
+ if (ks != -1) {
+ sd->jstops[ks] = true;
}
+ } else {
+ int limit = 0;
+ while (ubrk_next(bi) != UBRK_DONE) {
+ if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) {
+ int i = _convert_pos(sd, ubrk_current(bi));
+ sd->jstops[i + sd->start] = false;
+ int ks = _generate_kashida_justification_opportunies(sd->text, limit, i);
+ if (ks != -1) {
+ sd->jstops[ks + sd->start] = true;
+ }
+ limit = i;
+ }
+ }
+ ubrk_close(bi);
}
- ubrk_close(bi);
+
+ sd->js_ops_valid = true;
}
sd->sort_valid = false;
@@ -4228,18 +4213,18 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) {
Glyph *sd_glyphs = sd->glyphs.ptrw();
int sd_size = sd->glyphs.size();
- if (jstops.size() > 0) {
+ if (sd->jstops.size() > 0) {
for (int i = 0; i < sd_size; i++) {
if (sd_glyphs[i].count > 0) {
char32_t c = sd->text[sd_glyphs[i].start - sd->start];
if (c == 0x0640) {
sd_glyphs[i].flags |= GRAPHEME_IS_ELONGATION;
}
- if (jstops.has(sd_glyphs[i].start)) {
+ if (sd->jstops.has(sd_glyphs[i].start)) {
if (c == 0xfffc) {
continue;
}
- if (jstops[sd_glyphs[i].start]) {
+ if (sd->jstops[sd_glyphs[i].start]) {
if (c != 0x0640) {
if (sd_glyphs[i].font_rid != RID()) {
Glyph gl = _shape_single_glyph(sd, 0x0640, HB_SCRIPT_ARABIC, HB_DIRECTION_RTL, sd->glyphs[i].font_rid, sd->glyphs[i].font_size);
@@ -4574,7 +4559,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) {
return true;
}
- invalidate(sd);
+ invalidate(sd, false);
if (sd->parent != RID()) {
shaped_text_shape(sd->parent);
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
@@ -4733,70 +4718,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) {
}
}
- // Align embedded objects to baseline.
- float full_ascent = sd->ascent;
- float full_descent = sd->descent;
- for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
- if (sd->orientation == ORIENTATION_HORIZONTAL) {
- switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
- case INLINE_ALIGNMENT_TO_TOP: {
- E.value.rect.position.y = -sd->ascent;
- } break;
- case INLINE_ALIGNMENT_TO_CENTER: {
- E.value.rect.position.y = (-sd->ascent + sd->descent) / 2;
- } break;
- case INLINE_ALIGNMENT_TO_BASELINE: {
- E.value.rect.position.y = 0;
- } break;
- case INLINE_ALIGNMENT_TO_BOTTOM: {
- E.value.rect.position.y = sd->descent;
- } break;
- }
- switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
- case INLINE_ALIGNMENT_BOTTOM_TO: {
- E.value.rect.position.y -= E.value.rect.size.y;
- } break;
- case INLINE_ALIGNMENT_CENTER_TO: {
- E.value.rect.position.y -= E.value.rect.size.y / 2;
- } break;
- case INLINE_ALIGNMENT_TOP_TO: {
- // NOP
- } break;
- }
- full_ascent = MAX(full_ascent, -E.value.rect.position.y);
- full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
- } else {
- switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
- case INLINE_ALIGNMENT_TO_TOP: {
- E.value.rect.position.x = -sd->ascent;
- } break;
- case INLINE_ALIGNMENT_TO_CENTER: {
- E.value.rect.position.x = (-sd->ascent + sd->descent) / 2;
- } break;
- case INLINE_ALIGNMENT_TO_BASELINE: {
- E.value.rect.position.x = 0;
- } break;
- case INLINE_ALIGNMENT_TO_BOTTOM: {
- E.value.rect.position.x = sd->descent;
- } break;
- }
- switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
- case INLINE_ALIGNMENT_BOTTOM_TO: {
- E.value.rect.position.x -= E.value.rect.size.x;
- } break;
- case INLINE_ALIGNMENT_CENTER_TO: {
- E.value.rect.position.x -= E.value.rect.size.x / 2;
- } break;
- case INLINE_ALIGNMENT_TOP_TO: {
- // NOP
- } break;
- }
- full_ascent = MAX(full_ascent, -E.value.rect.position.x);
- full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
- }
- }
- sd->ascent = full_ascent;
- sd->descent = full_descent;
+ _realign(sd);
sd->valid = true;
return sd->valid;
}
@@ -4863,7 +4785,7 @@ Array TextServerAdvanced::shaped_text_get_objects(RID p_shaped) const {
ERR_FAIL_COND_V(!sd, ret);
MutexLock lock(sd->mutex);
- for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
+ for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) {
ret.push_back(E.key);
}
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index f2ae24cae6..145d740b68 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -242,6 +242,21 @@ class TextServerAdvanced : public TextServer {
// Shaped text cache data.
struct ShapedTextDataAdvanced : public ShapedTextData {
+ struct Span {
+ int start = -1;
+ int end = -1;
+
+ Vector<RID> fonts;
+ int font_size = 0;
+
+ Variant embedded_key;
+
+ String language;
+ Dictionary features;
+ Variant meta;
+ };
+ Vector<Span> spans;
+
/* Intermediate data */
Char16String utf16;
Vector<UBiDi *> bidi_iter;
@@ -249,6 +264,11 @@ class TextServerAdvanced : public TextServer {
ScriptIterator *script_iter = nullptr;
hb_buffer_t *hb_buffer = nullptr;
+ HashMap<int, bool> jstops;
+ HashMap<int, bool> breaks;
+ bool break_ops_valid = false;
+ bool js_ops_valid = false;
+
~ShapedTextDataAdvanced() {
for (int i = 0; i < bidi_iter.size(); i++) {
ubidi_close(bidi_iter[i]);
@@ -268,6 +288,7 @@ class TextServerAdvanced : public TextServer {
mutable RID_PtrOwner<FontDataAdvanced> font_owner;
mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner;
+ void _realign(ShapedTextDataAdvanced *p_sd) const;
int _convert_pos(const ShapedTextDataAdvanced *p_sd, int p_pos) const;
int _convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int p_pos) const;
bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int p_start, int p_length) const;
@@ -302,7 +323,7 @@ protected:
static void _bind_methods(){};
void full_copy(ShapedTextDataAdvanced *p_shaped);
- void invalidate(ShapedTextDataAdvanced *p_shaped);
+ void invalidate(ShapedTextDataAdvanced *p_shaped, bool p_text = false);
public:
virtual bool has_feature(Feature p_feature) const override;
@@ -482,10 +503,14 @@ public:
virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override;
virtual bool shaped_text_get_preserve_control(RID p_shaped) const override;
- virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override;
+ virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override;
virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override;
virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override;
+ virtual int shaped_get_span_count(RID p_shaped) const override;
+ virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override;
+ virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override;
+
virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override;
virtual RID shaped_text_get_parent(RID p_shaped) const override;
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 4a0415fbf9..dd520a2e40 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -91,7 +91,7 @@ void TextServerFallback::free(RID p_rid) {
font_owner.free(p_rid);
memdelete(fd);
} else if (shaped_owner.owns(p_rid)) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_rid);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid);
shaped_owner.free(p_rid);
memdelete(sd);
}
@@ -2061,7 +2061,7 @@ void TextServerFallback::font_set_global_oversampling(float p_oversampling) {
/* Shaped text buffer interface */
/*************************************************************************/
-void TextServerFallback::invalidate(ShapedTextData *p_shaped) {
+void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) {
p_shaped->valid = false;
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
@@ -2075,17 +2075,17 @@ void TextServerFallback::invalidate(ShapedTextData *p_shaped) {
p_shaped->glyphs_logical.clear();
}
-void TextServerFallback::full_copy(ShapedTextData *p_shaped) {
- ShapedTextData *parent = shaped_owner.get_or_null(p_shaped->parent);
+void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) {
+ ShapedTextDataFallback *parent = shaped_owner.get_or_null(p_shaped->parent);
- for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) {
+ for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : parent->objects) {
if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) {
p_shaped->objects[E.key] = E.value;
}
}
for (int k = 0; k < parent->spans.size(); k++) {
- ShapedTextData::Span span = parent->spans[k];
+ ShapedTextDataFallback::Span span = parent->spans[k];
if (span.start >= p_shaped->end || span.end <= p_shaped->start) {
continue;
}
@@ -2099,7 +2099,7 @@ void TextServerFallback::full_copy(ShapedTextData *p_shaped) {
RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) {
_THREAD_SAFE_METHOD_
- ShapedTextData *sd = memnew(ShapedTextData);
+ ShapedTextDataFallback *sd = memnew(ShapedTextDataFallback);
sd->direction = p_direction;
sd->orientation = p_orientation;
@@ -2107,7 +2107,7 @@ RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, Te
}
void TextServerFallback::shaped_text_clear(RID p_shaped) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
@@ -2136,7 +2136,7 @@ TextServer::Direction TextServerFallback::shaped_text_get_inferred_direction(RID
void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String &p_punct) {
_THREAD_SAFE_METHOD_
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
if (sd->custom_punct != p_punct) {
@@ -2150,13 +2150,13 @@ void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const
String TextServerFallback::shaped_text_get_custom_punctuation(RID p_shaped) const {
_THREAD_SAFE_METHOD_
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, String());
return sd->custom_punct;
}
void TextServerFallback::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
@@ -2174,7 +2174,7 @@ void TextServerFallback::shaped_text_set_bidi_override(RID p_shaped, const Array
}
TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL);
MutexLock lock(sd->mutex);
@@ -2182,7 +2182,7 @@ TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_sh
}
void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
MutexLock lock(sd->mutex);
ERR_FAIL_COND(!sd);
@@ -2196,7 +2196,7 @@ void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e
}
bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@@ -2204,7 +2204,7 @@ bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const {
}
void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
@@ -2218,15 +2218,52 @@ void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_e
}
bool TextServerFallback::shaped_text_get_preserve_control(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
return sd->preserve_control;
}
-bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+int TextServerFallback::shaped_get_span_count(RID p_shaped) const {
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V(!sd, 0);
+ return sd->spans.size();
+}
+
+Variant TextServerFallback::shaped_get_span_meta(RID p_shaped, int p_index) const {
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V(!sd, Variant());
+ ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
+ return sd->spans[p_index].meta;
+}
+
+void TextServerFallback::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) {
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND(!sd);
+ ERR_FAIL_INDEX(p_index, sd->spans.size());
+
+ ShapedTextDataFallback::Span &span = sd->spans.write[p_index];
+ span.fonts.clear();
+ // Pre-sort fonts, push fonts with the language support first.
+ Vector<RID> fonts_no_match;
+ int font_count = p_fonts.size();
+ for (int i = 0; i < font_count; i++) {
+ if (font_is_language_supported(p_fonts[i], span.language)) {
+ span.fonts.push_back(p_fonts[i]);
+ } else {
+ fonts_no_match.push_back(p_fonts[i]);
+ }
+ }
+ span.fonts.append_array(fonts_no_match);
+ span.font_size = p_size;
+ span.features = p_opentype_features;
+
+ sd->valid = false;
+}
+
+bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@@ -2244,7 +2281,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te
full_copy(sd);
}
- ShapedTextData::Span span;
+ ShapedTextDataFallback::Span span;
span.start = sd->text.length();
span.end = span.start + p_text.length();
@@ -2263,6 +2300,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te
ERR_FAIL_COND_V(span.fonts.is_empty(), false);
span.font_size = p_size;
span.language = p_language;
+ span.meta = p_meta;
sd->spans.push_back(span);
sd->text += p_text;
@@ -2273,7 +2311,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te
}
bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align, int p_length) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@@ -2284,12 +2322,12 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con
full_copy(sd);
}
- ShapedTextData::Span span;
+ ShapedTextDataFallback::Span span;
span.start = sd->start + sd->text.length();
span.end = span.start + p_length;
span.embedded_key = p_key;
- ShapedTextData::EmbeddedObject obj;
+ ShapedTextDataFallback::EmbeddedObject obj;
obj.inline_align = p_inline_align;
obj.rect.size = p_size;
obj.pos = span.start;
@@ -2304,7 +2342,7 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con
}
bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@@ -2324,7 +2362,7 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key,
Glyph gl = sd->glyphs[i];
Variant key;
if (gl.count == 1) {
- for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
+ for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
if (E.value.pos == gl.start) {
key = E.key;
break;
@@ -2364,79 +2402,82 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key,
sd->width += gl.advance * gl.repeat;
}
}
+ _realign(sd);
+ }
+ return true;
+}
- // Align embedded objects to baseline.
- float full_ascent = sd->ascent;
- float full_descent = sd->descent;
- for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
- if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) {
- if (sd->orientation == ORIENTATION_HORIZONTAL) {
- switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
- case INLINE_ALIGNMENT_TO_TOP: {
- E.value.rect.position.y = -sd->ascent;
- } break;
- case INLINE_ALIGNMENT_TO_CENTER: {
- E.value.rect.position.y = (-sd->ascent + sd->descent) / 2;
- } break;
- case INLINE_ALIGNMENT_TO_BASELINE: {
- E.value.rect.position.y = 0;
- } break;
- case INLINE_ALIGNMENT_TO_BOTTOM: {
- E.value.rect.position.y = sd->descent;
- } break;
- }
- switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
- case INLINE_ALIGNMENT_BOTTOM_TO: {
- E.value.rect.position.y -= E.value.rect.size.y;
- } break;
- case INLINE_ALIGNMENT_CENTER_TO: {
- E.value.rect.position.y -= E.value.rect.size.y / 2;
- } break;
- case INLINE_ALIGNMENT_TOP_TO: {
- // NOP
- } break;
- }
- full_ascent = MAX(full_ascent, -E.value.rect.position.y);
- full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
- } else {
- switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
- case INLINE_ALIGNMENT_TO_TOP: {
- E.value.rect.position.x = -sd->ascent;
- } break;
- case INLINE_ALIGNMENT_TO_CENTER: {
- E.value.rect.position.x = (-sd->ascent + sd->descent) / 2;
- } break;
- case INLINE_ALIGNMENT_TO_BASELINE: {
- E.value.rect.position.x = 0;
- } break;
- case INLINE_ALIGNMENT_TO_BOTTOM: {
- E.value.rect.position.x = sd->descent;
- } break;
- }
- switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
- case INLINE_ALIGNMENT_BOTTOM_TO: {
- E.value.rect.position.x -= E.value.rect.size.x;
- } break;
- case INLINE_ALIGNMENT_CENTER_TO: {
- E.value.rect.position.x -= E.value.rect.size.x / 2;
- } break;
- case INLINE_ALIGNMENT_TOP_TO: {
- // NOP
- } break;
- }
- full_ascent = MAX(full_ascent, -E.value.rect.position.x);
- full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
+void TextServerFallback::_realign(ShapedTextDataFallback *p_sd) const {
+ // Align embedded objects to baseline.
+ float full_ascent = p_sd->ascent;
+ float full_descent = p_sd->descent;
+ for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : p_sd->objects) {
+ if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) {
+ if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
+ switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
+ case INLINE_ALIGNMENT_TO_TOP: {
+ E.value.rect.position.y = -p_sd->ascent;
+ } break;
+ case INLINE_ALIGNMENT_TO_CENTER: {
+ E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2;
+ } break;
+ case INLINE_ALIGNMENT_TO_BASELINE: {
+ E.value.rect.position.y = 0;
+ } break;
+ case INLINE_ALIGNMENT_TO_BOTTOM: {
+ E.value.rect.position.y = p_sd->descent;
+ } break;
+ }
+ switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
+ case INLINE_ALIGNMENT_BOTTOM_TO: {
+ E.value.rect.position.y -= E.value.rect.size.y;
+ } break;
+ case INLINE_ALIGNMENT_CENTER_TO: {
+ E.value.rect.position.y -= E.value.rect.size.y / 2;
+ } break;
+ case INLINE_ALIGNMENT_TOP_TO: {
+ // NOP
+ } break;
}
+ full_ascent = MAX(full_ascent, -E.value.rect.position.y);
+ full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
+ } else {
+ switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
+ case INLINE_ALIGNMENT_TO_TOP: {
+ E.value.rect.position.x = -p_sd->ascent;
+ } break;
+ case INLINE_ALIGNMENT_TO_CENTER: {
+ E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2;
+ } break;
+ case INLINE_ALIGNMENT_TO_BASELINE: {
+ E.value.rect.position.x = 0;
+ } break;
+ case INLINE_ALIGNMENT_TO_BOTTOM: {
+ E.value.rect.position.x = p_sd->descent;
+ } break;
+ }
+ switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
+ case INLINE_ALIGNMENT_BOTTOM_TO: {
+ E.value.rect.position.x -= E.value.rect.size.x;
+ } break;
+ case INLINE_ALIGNMENT_CENTER_TO: {
+ E.value.rect.position.x -= E.value.rect.size.x / 2;
+ } break;
+ case INLINE_ALIGNMENT_TOP_TO: {
+ // NOP
+ } break;
+ }
+ full_ascent = MAX(full_ascent, -E.value.rect.position.x);
+ full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
}
}
- sd->ascent = full_ascent;
- sd->descent = full_descent;
}
- return true;
+ p_sd->ascent = full_ascent;
+ p_sd->descent = full_descent;
}
RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_length) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, RID());
MutexLock lock(sd->mutex);
@@ -2450,7 +2491,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng
ERR_FAIL_COND_V(sd->start > p_start || sd->end < p_start, RID());
ERR_FAIL_COND_V(sd->end < p_start + p_length, RID());
- ShapedTextData *new_sd = memnew(ShapedTextData);
+ ShapedTextDataFallback *new_sd = memnew(ShapedTextDataFallback);
new_sd->parent = p_shaped;
new_sd->start = p_start;
new_sd->end = p_start + p_length;
@@ -2476,7 +2517,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng
Variant key;
bool find_embedded = false;
if (gl.count == 1) {
- for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
+ for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
if (E.value.pos == gl.start) {
find_embedded = true;
key = E.key;
@@ -2520,7 +2561,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng
// Align embedded objects to baseline.
float full_ascent = new_sd->ascent;
float full_descent = new_sd->descent;
- for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : new_sd->objects) {
+ for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : new_sd->objects) {
if ((E.value.pos >= new_sd->start) && (E.value.pos < new_sd->end)) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
@@ -2590,7 +2631,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng
}
RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, RID());
MutexLock lock(sd->mutex);
@@ -2598,7 +2639,7 @@ RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const {
}
float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, uint16_t /*JustificationFlag*/ p_jst_flags) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@@ -2707,7 +2748,7 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width,
}
float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@@ -2763,7 +2804,7 @@ float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat3
}
bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@@ -2819,7 +2860,7 @@ bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) {
}
bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@@ -2835,7 +2876,7 @@ bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) {
}
void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint16_t p_trim_flags) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped_line);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line);
ERR_FAIL_COND_MSG(!sd, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
@@ -2863,9 +2904,9 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
return;
}
- Vector<ShapedTextData::Span> &spans = sd->spans;
+ Vector<ShapedTextDataFallback::Span> &spans = sd->spans;
if (sd->parent != RID()) {
- ShapedTextData *parent_sd = shaped_owner.get_or_null(sd->parent);
+ ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent);
ERR_FAIL_COND(!parent_sd->valid);
spans = parent_sd->spans;
}
@@ -2987,39 +3028,39 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
}
int TextServerFallback::shaped_text_get_trim_pos(RID p_shaped) const {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid.");
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.trim_pos;
}
int TextServerFallback::shaped_text_get_ellipsis_pos(RID p_shaped) const {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid.");
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_pos;
}
const Glyph *TextServerFallback::shaped_text_get_ellipsis_glyphs(RID p_shaped) const {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextData invalid.");
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.ptr();
}
int TextServerFallback::shaped_text_get_ellipsis_glyph_count(RID p_shaped) const {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
- ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextData invalid.");
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+ ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.size();
}
bool TextServerFallback::shaped_text_shape(RID p_shaped) {
- ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@@ -3046,7 +3087,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
// "Shape" string.
for (int i = 0; i < sd->spans.size(); i++) {
- const ShapedTextData::Span &span = sd->spans[i];
+ const ShapedTextDataFallback::Span &span = sd->spans[i];
if (span.embedded_key != Variant()) {
// Embedded object.
if (sd->orientation == ORIENTATION_HORIZONTAL) {
@@ -3146,7 +3187,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
// Align embedded objects to baseline.
float full_ascent = sd->ascent;
float full_descent = sd->descent;
- for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
+ for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
@@ -3212,7 +3253,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
}
bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@@ -3220,7 +3261,7 @@ bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const {
}
const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, nullptr);
MutexLock lock(sd->mutex);
@@ -3231,7 +3272,7 @@ const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const {
}
int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0);
MutexLock lock(sd->mutex);
@@ -3242,7 +3283,7 @@ int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const {
}
const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, nullptr);
MutexLock lock(sd->mutex);
@@ -3254,7 +3295,7 @@ const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) {
}
Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Vector2i());
MutexLock lock(sd->mutex);
@@ -3263,11 +3304,11 @@ Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const {
Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const {
Array ret;
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, ret);
MutexLock lock(sd->mutex);
- for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
+ for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
ret.push_back(E.key);
}
@@ -3275,7 +3316,7 @@ Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const {
}
Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_key) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Rect2());
MutexLock lock(sd->mutex);
@@ -3287,7 +3328,7 @@ Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_ke
}
Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Size2());
MutexLock lock(sd->mutex);
@@ -3302,7 +3343,7 @@ Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const {
}
float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@@ -3313,7 +3354,7 @@ float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const {
}
float TextServerFallback::shaped_text_get_descent(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@@ -3324,7 +3365,7 @@ float TextServerFallback::shaped_text_get_descent(RID p_shaped) const {
}
float TextServerFallback::shaped_text_get_width(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@@ -3335,7 +3376,7 @@ float TextServerFallback::shaped_text_get_width(RID p_shaped) const {
}
float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@@ -3347,7 +3388,7 @@ float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const
}
float TextServerFallback::shaped_text_get_underline_thickness(RID p_shaped) const {
- const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
+ const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index b356b90d2c..be944cde58 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -203,17 +203,38 @@ class TextServerFallback : public TextServer {
}
}
+ // Shaped text cache data.
+
+ struct ShapedTextDataFallback : public ShapedTextData {
+ struct Span {
+ int start = -1;
+ int end = -1;
+
+ Vector<RID> fonts;
+ int font_size = 0;
+
+ Variant embedded_key;
+
+ String language;
+ Dictionary features;
+ Variant meta;
+ };
+ Vector<Span> spans;
+ };
+
// Common data.
float oversampling = 1.f;
mutable RID_PtrOwner<FontDataFallback> font_owner;
- mutable RID_PtrOwner<ShapedTextData> shaped_owner;
+ mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner;
+
+ void _realign(ShapedTextDataFallback *p_sd) const;
protected:
static void _bind_methods(){};
- void full_copy(ShapedTextData *p_shaped);
- void invalidate(ShapedTextData *p_shaped);
+ void full_copy(ShapedTextDataFallback *p_shaped);
+ void invalidate(ShapedTextDataFallback *p_shaped);
public:
virtual bool has_feature(Feature p_feature) const override;
@@ -391,10 +412,14 @@ public:
virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override;
virtual bool shaped_text_get_preserve_control(RID p_shaped) const override;
- virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override;
+ virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override;
virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override;
virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override;
+ virtual int shaped_get_span_count(RID p_shaped) const override;
+ virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override;
+ virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override;
+
virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override;
virtual RID shaped_text_get_parent(RID p_shaped) const override;
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index fab420d593..852aaaab24 100644
--- a/scene/gui/label.cpp
+++ b/scene/gui/label.cpp
@@ -82,9 +82,11 @@ void Label::_shape() {
Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label"));
int width = (get_size().width - style->get_minimum_size().width);
- if (dirty) {
+ if (dirty || font_dirty) {
String lang = (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale();
- TS->shaped_text_clear(text_rid);
+ if (dirty) {
+ TS->shaped_text_clear(text_rid);
+ }
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
TS->shaped_text_set_direction(text_rid, is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
} else {
@@ -97,9 +99,17 @@ void Label::_shape() {
if (visible_chars >= 0 && visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) {
text = text.substr(0, visible_chars);
}
- TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang);
+ if (dirty) {
+ TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang);
+ } else {
+ int spans = TS->shaped_get_span_count(text_rid);
+ for (int i = 0; i < spans; i++) {
+ TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, opentype_features);
+ }
+ }
TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text));
dirty = false;
+ font_dirty = false;
lines_dirty = true;
}
@@ -276,7 +286,7 @@ void Label::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true);
}
- if (dirty || lines_dirty) {
+ if (dirty || font_dirty || lines_dirty) {
_shape();
}
@@ -521,7 +531,7 @@ void Label::_notification(int p_what) {
}
if (p_what == NOTIFICATION_THEME_CHANGED) {
- dirty = true;
+ font_dirty = true;
update();
}
if (p_what == NOTIFICATION_RESIZED) {
@@ -531,7 +541,7 @@ void Label::_notification(int p_what) {
Size2 Label::get_minimum_size() const {
// don't want to mutable everything
- if (dirty || lines_dirty) {
+ if (dirty || font_dirty || lines_dirty) {
const_cast<Label *>(this)->_shape();
}
@@ -555,7 +565,7 @@ int Label::get_line_count() const {
if (!is_inside_tree()) {
return 1;
}
- if (dirty || lines_dirty) {
+ if (dirty || font_dirty || lines_dirty) {
const_cast<Label *>(this)->_shape();
}
@@ -630,7 +640,7 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
- dirty = true;
+ font_dirty = true;
update();
}
}
@@ -638,7 +648,7 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) {
void Label::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
- dirty = true;
+ font_dirty = true;
update();
}
}
@@ -649,7 +659,7 @@ Control::StructuredTextParser Label::get_structured_text_bidi_override() const {
void Label::set_structured_text_bidi_override_options(Array p_args) {
st_args = p_args;
- dirty = true;
+ font_dirty = true;
update();
}
@@ -663,7 +673,7 @@ Control::TextDirection Label::get_text_direction() const {
void Label::clear_opentype_features() {
opentype_features.clear();
- dirty = true;
+ font_dirty = true;
update();
}
@@ -671,7 +681,7 @@ void Label::set_opentype_feature(const String &p_name, int p_value) {
int32_t tag = TS->name_to_tag(p_name);
if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) {
opentype_features[tag] = p_value;
- dirty = true;
+ font_dirty = true;
update();
}
}
@@ -798,7 +808,7 @@ int Label::get_max_lines_visible() const {
}
int Label::get_total_character_count() const {
- if (dirty || lines_dirty) {
+ if (dirty || font_dirty || lines_dirty) {
const_cast<Label *>(this)->_shape();
}
@@ -814,13 +824,13 @@ bool Label::_set(const StringName &p_name, const Variant &p_value) {
if (value == -1) {
if (opentype_features.has(tag)) {
opentype_features.erase(tag);
- dirty = true;
+ font_dirty = true;
update();
}
} else {
if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) {
opentype_features[tag] = value;
- dirty = true;
+ font_dirty = true;
update();
}
}
diff --git a/scene/gui/label.h b/scene/gui/label.h
index 354e9c664d..0b931b3084 100644
--- a/scene/gui/label.h
+++ b/scene/gui/label.h
@@ -73,6 +73,7 @@ private:
bool lines_dirty = true;
bool dirty = true;
+ bool font_dirty = true;
RID text_rid;
Vector<RID> lines_rid;
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 151ae2f092..8f4c063c58 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -205,6 +205,49 @@ String RichTextLabel::_letters(int p_num, bool p_capitalize) const {
return s;
}
+void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
+ ERR_FAIL_COND(p_frame == nullptr);
+ ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
+
+ Line &l = p_frame->lines.write[p_line];
+
+ RID t = l.text_buf->get_rid();
+ int spans = TS->shaped_get_span_count(t);
+ for (int i = 0; i < spans; i++) {
+ ItemText *it = (ItemText *)(uint64_t)TS->shaped_get_span_meta(t, i);
+ if (it) {
+ Ref<Font> font = _find_font(it);
+ if (font.is_null()) {
+ font = p_base_font;
+ }
+ int font_size = _find_font_size(it);
+ if (font_size == -1) {
+ font_size = p_base_font_size;
+ }
+ Dictionary font_ftr = _find_font_features(it);
+ TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font_ftr);
+ }
+ }
+
+ Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
+ for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
+ switch (it->type) {
+ case ITEM_TABLE: {
+ ItemTable *table = static_cast<ItemTable *>(it);
+ for (Item *E : table->subitems) {
+ ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
+ ItemFrame *frame = static_cast<ItemFrame *>(E);
+ for (int i = 0; i < frame->lines.size(); i++) {
+ _update_line_font(frame, i, p_base_font, p_base_font_size);
+ }
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+}
+
void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width) {
ERR_FAIL_COND(p_frame == nullptr);
ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
@@ -449,7 +492,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
remaining_characters -= tx.length();
- l.text_buf->add_string(tx, font, font_size, font_ftr, lang);
+ l.text_buf->add_string(tx, font, font_size, font_ftr, lang, (uint64_t)it);
text += tx;
l.char_count += tx.length();
} break;
@@ -1448,7 +1491,10 @@ void RichTextLabel::_notification(int p_what) {
update();
} break;
- case NOTIFICATION_THEME_CHANGED:
+ case NOTIFICATION_THEME_CHANGED: {
+ main->first_invalid_font_line = 0; //invalidate ALL
+ update();
+ } break;
case NOTIFICATION_ENTER_TREE: {
if (!text.is_empty()) {
set_text(text);
@@ -1545,6 +1591,10 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const
return get_default_cursor_shape(); //invalid
}
+ if (main->first_invalid_font_line < main->lines.size()) {
+ return get_default_cursor_shape(); //invalid
+ }
+
if (main->first_resized_line < main->lines.size()) {
return get_default_cursor_shape(); //invalid
}
@@ -1569,6 +1619,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
if (main->first_invalid_line < main->lines.size()) {
return;
}
+ if (main->first_invalid_font_line < main->lines.size()) {
+ return;
+ }
if (main->first_resized_line < main->lines.size()) {
return;
}
@@ -1732,6 +1785,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
if (main->first_invalid_line < main->lines.size()) {
return;
}
+ if (main->first_invalid_font_line < main->lines.size()) {
+ return;
+ }
if (main->first_resized_line < main->lines.size()) {
return;
}
@@ -2184,6 +2240,18 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) {
void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
if (p_frame->first_invalid_line == p_frame->lines.size()) {
+ Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
+ int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
+
+ // Update fonts.
+ if (p_frame->first_invalid_font_line != p_frame->lines.size()) {
+ for (int i = p_frame->first_invalid_font_line; i < p_frame->lines.size(); i++) {
+ _update_line_font(p_frame, i, base_font, base_font_size);
+ }
+ p_frame->first_resized_line = p_frame->first_invalid_font_line;
+ p_frame->first_invalid_font_line = p_frame->lines.size();
+ }
+
if (p_frame->first_resized_line == p_frame->lines.size()) {
return;
}
@@ -2191,9 +2259,6 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
// Resize lines without reshaping.
Rect2 text_rect = _get_text_rect();
- Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
- int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
-
for (int i = p_frame->first_resized_line; i < p_frame->lines.size(); i++) {
_resize_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w);
}
@@ -2237,6 +2302,7 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
p_frame->first_invalid_line = p_frame->lines.size();
p_frame->first_resized_line = p_frame->lines.size();
+ p_frame->first_invalid_font_line = p_frame->lines.size();
updating_scroll = true;
vscroll->set_max(total_height);
@@ -4494,6 +4560,7 @@ RichTextLabel::RichTextLabel() {
main->lines.write[0].from = main;
main->first_invalid_line = 0;
main->first_resized_line = 0;
+ main->first_invalid_font_line = 0;
current_frame = main;
vscroll = memnew(VScrollBar);
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 70467e7e7c..2b24da4c37 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -129,6 +129,7 @@ private:
Vector<Line> lines;
int first_invalid_line = 0;
+ int first_invalid_font_line = 0;
int first_resized_line = 0;
ItemFrame *parent_frame = nullptr;
@@ -414,6 +415,7 @@ private:
void _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset);
void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width);
+ void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr);
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index e060d3b901..0ee4a6af4e 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -187,29 +187,44 @@ void TextEdit::Text::_calculate_max_line_width() {
max_width = width;
}
-void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Array &p_bidi_override) {
+void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) {
ERR_FAIL_INDEX(p_line, text.size());
if (font.is_null() || font_size <= 0) {
return; // Not in tree?
}
- text.write[p_line].data_buf->clear();
+ if (p_text_changed) {
+ text.write[p_line].data_buf->clear();
+ }
+
text.write[p_line].data_buf->set_width(width);
text.write[p_line].data_buf->set_direction((TextServer::Direction)direction);
text.write[p_line].data_buf->set_preserve_control(draw_control_chars);
if (p_ime_text.length() > 0) {
- text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language);
+ if (p_text_changed) {
+ text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language);
+ }
if (!p_bidi_override.is_empty()) {
TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override);
}
} else {
- text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language);
+ if (p_text_changed) {
+ text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language);
+ }
if (!text[p_line].bidi_override.is_empty()) {
TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override);
}
}
+ if (!p_text_changed) {
+ RID r = text.write[p_line].data_buf->get_rid();
+ int spans = TS->shaped_get_span_count(r);
+ for (int i = 0; i < spans; i++) {
+ TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, opentype_features);
+ }
+ }
+
// Apply tab align.
if (tab_size > 0) {
Vector<float> tabs;
@@ -266,6 +281,24 @@ void TextEdit::Text::invalidate_all_lines() {
}
}
+void TextEdit::Text::invalidate_font() {
+ if (!is_dirty) {
+ return;
+ }
+
+ max_width = -1;
+ line_height = -1;
+
+ if (!font.is_null() && font_size > 0) {
+ font_height = font->get_height(font_size);
+ }
+
+ for (int i = 0; i < text.size(); i++) {
+ invalidate_cache(i, -1, false);
+ }
+ is_dirty = false;
+}
+
void TextEdit::Text::invalidate_all() {
if (!is_dirty) {
return;
@@ -279,7 +312,7 @@ void TextEdit::Text::invalidate_all() {
}
for (int i = 0; i < text.size(); i++) {
- invalidate_cache(i);
+ invalidate_cache(i, -1, true);
}
is_dirty = false;
}
@@ -294,7 +327,7 @@ void TextEdit::Text::clear() {
line.gutters.resize(gutter_count);
line.data = "";
text.insert(0, line);
- invalidate_cache(0);
+ invalidate_cache(0, -1, true);
}
int TextEdit::Text::get_max_width() const {
@@ -306,7 +339,7 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o
text.write[p_line].data = p_text;
text.write[p_line].bidi_override = p_bidi_override;
- invalidate_cache(p_line);
+ invalidate_cache(p_line, -1, true);
}
void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) {
@@ -331,7 +364,7 @@ void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector
line.data = p_text[i];
line.bidi_override = p_bidi_override[i];
text.write[p_at + i] = line;
- invalidate_cache(p_at + i);
+ invalidate_cache(p_at + i, -1, true);
}
}
@@ -1446,9 +1479,11 @@ void TextEdit::_notification(int p_what) {
DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
}
- ime_text = "";
- ime_selection = Point2();
- text.invalidate_cache(caret.line, caret.column, ime_text);
+ if (!ime_text.is_empty()) {
+ ime_text = "";
+ ime_selection = Point2();
+ text.invalidate_cache(caret.line, caret.column, true, ime_text);
+ }
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
@@ -1470,7 +1505,7 @@ void TextEdit::_notification(int p_what) {
t = ime_text;
}
- text.invalidate_cache(caret.line, caret.column, t, structured_text_parser(st_parser, st_args, t));
+ text.invalidate_cache(caret.line, caret.column, true, t, structured_text_parser(st_parser, st_args, t));
update();
}
} break;
@@ -2538,7 +2573,7 @@ void TextEdit::_update_caches() {
text.set_draw_control_chars(draw_control_chars);
text.set_font(font);
text.set_font_size(font_size);
- text.invalidate_all();
+ text.invalidate_font();
_update_placeholder();
/* Syntax highlighting. */
@@ -2718,7 +2753,7 @@ void TextEdit::set_text_direction(Control::TextDirection p_text_direction) {
dir = (TextServer::Direction)text_direction;
}
text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
- text.invalidate_all();
+ text.invalidate_font();
_update_placeholder();
if (menu_dir) {
@@ -2740,7 +2775,7 @@ void TextEdit::set_opentype_feature(const String &p_name, int p_value) {
if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) {
opentype_features[tag] = p_value;
text.set_font_features(opentype_features);
- text.invalidate_all();
+ text.invalidate_font();
_update_placeholder();
update();
}
@@ -2757,7 +2792,7 @@ int TextEdit::get_opentype_feature(const String &p_name) const {
void TextEdit::clear_opentype_features() {
opentype_features.clear();
text.set_font_features(opentype_features);
- text.invalidate_all();
+ text.invalidate_font();
_update_placeholder();
update();
}
@@ -4852,7 +4887,7 @@ void TextEdit::set_draw_control_chars(bool p_enabled) {
menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars);
}
text.set_draw_control_chars(draw_control_chars);
- text.invalidate_all();
+ text.invalidate_font();
_update_placeholder();
update();
}
@@ -5319,7 +5354,7 @@ bool TextEdit::_set(const StringName &p_name, const Variant &p_value) {
if (opentype_features.has(tag)) {
opentype_features.erase(tag);
text.set_font_features(opentype_features);
- text.invalidate_all();
+ text.invalidate_font();
_update_placeholder();
update();
}
@@ -5327,7 +5362,7 @@ bool TextEdit::_set(const StringName &p_name, const Variant &p_value) {
if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) {
opentype_features[tag] = value;
text.set_font_features(opentype_features);
- text.invalidate_all();
+ text.invalidate_font();
_update_placeholder();
update();
}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 079890249e..83a63ae40a 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -210,7 +210,8 @@ private:
int size() const { return text.size(); }
void clear();
- void invalidate_cache(int p_line, int p_column = -1, const String &p_ime_text = String(), const Array &p_bidi_override = Array());
+ void invalidate_cache(int p_line, int p_column = -1, bool p_text_changed = false, const String &p_ime_text = String(), const Array &p_bidi_override = Array());
+ void invalidate_font();
void invalidate_all();
void invalidate_all_lines();
diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp
index f3752053c0..c3b5bd3564 100644
--- a/scene/resources/text_line.cpp
+++ b/scene/resources/text_line.cpp
@@ -55,7 +55,7 @@ void TextLine::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bidi_override", "override"), &TextLine::set_bidi_override);
- ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language"), &TextLine::add_string, DEFVAL(Dictionary()), DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language", "meta"), &TextLine::add_string, DEFVAL(Dictionary()), DEFVAL(""), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextLine::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1));
ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextLine::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER));
@@ -200,9 +200,9 @@ void TextLine::set_bidi_override(const Array &p_override) {
dirty = true;
}
-bool TextLine::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
+bool TextLine::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ERR_FAIL_COND_V(p_fonts.is_null(), false);
- bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language);
+ bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language, p_meta);
spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP);
spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM);
dirty = true;
diff --git a/scene/resources/text_line.h b/scene/resources/text_line.h
index e68049ee45..c5762db0f2 100644
--- a/scene/resources/text_line.h
+++ b/scene/resources/text_line.h
@@ -86,7 +86,7 @@ public:
void set_preserve_control(bool p_enabled);
bool get_preserve_control() const;
- bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "");
+ bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant());
bool add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1);
bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER);
diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp
index c3bdef7b01..4d75874199 100644
--- a/scene/resources/text_paragraph.cpp
+++ b/scene/resources/text_paragraph.cpp
@@ -63,7 +63,7 @@ void TextParagraph::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_dropcap", "text", "fonts", "size", "dropcap_margins", "opentype_features", "language"), &TextParagraph::set_dropcap, DEFVAL(Rect2()), DEFVAL(Dictionary()), DEFVAL(""));
ClassDB::bind_method(D_METHOD("clear_dropcap"), &TextParagraph::clear_dropcap);
- ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language"), &TextParagraph::add_string, DEFVAL(Dictionary()), DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language", "meta"), &TextParagraph::add_string, DEFVAL(Dictionary()), DEFVAL(""), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextParagraph::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1));
ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextParagraph::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER));
@@ -344,9 +344,9 @@ void TextParagraph::clear_dropcap() {
lines_dirty = true;
}
-bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
+bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ERR_FAIL_COND_V(p_fonts.is_null(), false);
- bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language);
+ bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language, p_meta);
spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP);
spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM);
lines_dirty = true;
diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h
index 773cc1a858..8a8a53943b 100644
--- a/scene/resources/text_paragraph.h
+++ b/scene/resources/text_paragraph.h
@@ -102,7 +102,7 @@ public:
bool set_dropcap(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "");
void clear_dropcap();
- bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "");
+ bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant());
bool add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1);
bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER);
diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp
index d7e7960496..db1f589334 100644
--- a/servers/text/text_server_extension.cpp
+++ b/servers/text/text_server_extension.cpp
@@ -210,10 +210,14 @@ void TextServerExtension::_bind_methods() {
GDVIRTUAL_BIND(_shaped_text_set_preserve_control, "shaped", "enabled");
GDVIRTUAL_BIND(_shaped_text_get_preserve_control, "shaped");
- GDVIRTUAL_BIND(_shaped_text_add_string, "shaped", "text", "fonts", "size", "opentype_features", "language");
+ GDVIRTUAL_BIND(_shaped_text_add_string, "shaped", "text", "fonts", "size", "opentype_features", "language", "meta");
GDVIRTUAL_BIND(_shaped_text_add_object, "shaped", "key", "size", "inline_align", "length");
GDVIRTUAL_BIND(_shaped_text_resize_object, "shaped", "key", "size", "inline_align");
+ GDVIRTUAL_BIND(_shaped_get_span_count, "shaped");
+ GDVIRTUAL_BIND(_shaped_get_span_meta, "shaped", "index");
+ GDVIRTUAL_BIND(_shaped_set_span_update_font, "shaped", "index", "fonts", "size", "opentype_features");
+
GDVIRTUAL_BIND(_shaped_text_substr, "shaped", "start", "length");
GDVIRTUAL_BIND(_shaped_text_get_parent, "shaped");
@@ -1018,13 +1022,13 @@ bool TextServerExtension::shaped_text_get_preserve_control(RID p_shaped) const {
return false;
}
-bool TextServerExtension::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
+bool TextServerExtension::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
bool ret;
Array fonts;
for (int i = 0; i < p_fonts.size(); i++) {
fonts.push_back(p_fonts[i]);
}
- if (GDVIRTUAL_CALL(_shaped_text_add_string, p_shaped, p_text, fonts, p_size, p_opentype_features, p_language, ret)) {
+ if (GDVIRTUAL_CALL(_shaped_text_add_string, p_shaped, p_text, fonts, p_size, p_opentype_features, p_language, p_meta, ret)) {
return ret;
}
return false;
@@ -1046,6 +1050,30 @@ bool TextServerExtension::shaped_text_resize_object(RID p_shaped, Variant p_key,
return false;
}
+int TextServerExtension::shaped_get_span_count(RID p_shaped) const {
+ int ret;
+ if (GDVIRTUAL_CALL(_shaped_get_span_count, p_shaped, ret)) {
+ return ret;
+ }
+ return 0;
+}
+
+Variant TextServerExtension::shaped_get_span_meta(RID p_shaped, int p_index) const {
+ Variant ret;
+ if (GDVIRTUAL_CALL(_shaped_get_span_meta, p_shaped, p_index, ret)) {
+ return ret;
+ }
+ return false;
+}
+
+void TextServerExtension::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) {
+ Array fonts;
+ for (int i = 0; i < p_fonts.size(); i++) {
+ fonts.push_back(p_fonts[i]);
+ }
+ GDVIRTUAL_CALL(_shaped_set_span_update_font, p_shaped, p_index, fonts, p_size, p_opentype_features);
+}
+
RID TextServerExtension::shaped_text_substr(RID p_shaped, int p_start, int p_length) const {
RID ret;
if (GDVIRTUAL_CALL(_shaped_text_substr, p_shaped, p_start, p_length, ret)) {
diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h
index 6e203f22ee..df1f1d7a70 100644
--- a/servers/text/text_server_extension.h
+++ b/servers/text/text_server_extension.h
@@ -343,13 +343,20 @@ public:
GDVIRTUAL2(_shaped_text_set_preserve_control, RID, bool);
GDVIRTUAL1RC(bool, _shaped_text_get_preserve_control, RID);
- virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override;
+ virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override;
virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override;
virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override;
- GDVIRTUAL6R(bool, _shaped_text_add_string, RID, const String &, const Array &, int, const Dictionary &, const String &);
+ GDVIRTUAL7R(bool, _shaped_text_add_string, RID, const String &, const Array &, int, const Dictionary &, const String &, const Variant &);
GDVIRTUAL5R(bool, _shaped_text_add_object, RID, Variant, const Size2 &, InlineAlignment, int);
GDVIRTUAL4R(bool, _shaped_text_resize_object, RID, Variant, const Size2 &, InlineAlignment);
+ virtual int shaped_get_span_count(RID p_shaped) const override;
+ virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override;
+ virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override;
+ GDVIRTUAL1RC(int, _shaped_get_span_count, RID);
+ GDVIRTUAL2RC(Variant, _shaped_get_span_meta, RID, int);
+ GDVIRTUAL5(_shaped_set_span_update_font, RID, int, const Array &, int, const Dictionary &);
+
virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override;
virtual RID shaped_text_get_parent(RID p_shaped) const override;
GDVIRTUAL3RC(RID, _shaped_text_substr, RID, int, int);
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index 17840584ee..b7cd39b9b2 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -363,10 +363,14 @@ void TextServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("shaped_text_set_preserve_control", "shaped", "enabled"), &TextServer::shaped_text_set_preserve_control);
ClassDB::bind_method(D_METHOD("shaped_text_get_preserve_control", "shaped"), &TextServer::shaped_text_get_preserve_control);
- ClassDB::bind_method(D_METHOD("shaped_text_add_string", "shaped", "text", "fonts", "size", "opentype_features", "language"), &TextServer::shaped_text_add_string, DEFVAL(Dictionary()), DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("shaped_text_add_string", "shaped", "text", "fonts", "size", "opentype_features", "language", "meta"), &TextServer::shaped_text_add_string, DEFVAL(Dictionary()), DEFVAL(""), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("shaped_text_add_object", "shaped", "key", "size", "inline_align", "length"), &TextServer::shaped_text_add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1));
ClassDB::bind_method(D_METHOD("shaped_text_resize_object", "shaped", "key", "size", "inline_align"), &TextServer::shaped_text_resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER));
+ 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_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);
ClassDB::bind_method(D_METHOD("shaped_text_get_parent", "shaped"), &TextServer::shaped_text_get_parent);
ClassDB::bind_method(D_METHOD("shaped_text_fit_to_width", "shaped", "width", "jst_flags"), &TextServer::shaped_text_fit_to_width, DEFVAL(JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA));
diff --git a/servers/text_server.h b/servers/text_server.h
index 599cc3afd0..6724c02caf 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -155,20 +155,6 @@ protected:
TextServer::Direction direction = DIRECTION_LTR; // Desired text direction.
TextServer::Orientation orientation = ORIENTATION_HORIZONTAL;
- struct Span {
- int start = -1;
- int end = -1;
-
- Vector<RID> fonts;
- int font_size = 0;
-
- Variant embedded_key;
-
- String language;
- Dictionary features;
- };
- Vector<Span> spans;
-
struct EmbeddedObject {
int pos = 0;
InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
@@ -387,10 +373,14 @@ public:
virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) = 0;
virtual bool shaped_text_get_preserve_control(RID p_shaped) const = 0;
- virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") = 0;
+ virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) = 0;
virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) = 0;
virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) = 0;
+ virtual int shaped_get_span_count(RID p_shaped) const = 0;
+ virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const = 0;
+ virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) = 0;
+
virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const = 0; // Copy shaped substring (e.g. line break) without reshaping, but correctly reordered, preservers range.
virtual RID shaped_text_get_parent(RID p_shaped) const = 0;