Przeglądaj źródła

[TextServer] Add function to change font, font size, and OpenType features without invalidating line break points, justification points, or recreating shaped text buffer.

bruvzg 3 lat temu
rodzic
commit
215bede6ff

+ 1 - 0
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>

+ 1 - 0
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>

+ 27 - 0
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>

+ 27 - 0
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>

+ 207 - 285
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);
 	}
 

+ 27 - 2
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;
 

+ 166 - 125
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);

+ 29 - 4
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;
 

+ 25 - 15
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();
 			}
 		}

+ 1 - 0
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;
 

+ 72 - 5
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);

+ 2 - 0
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);
 

+ 54 - 19
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();
 			}

+ 2 - 1
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();
 

+ 3 - 3
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;

+ 1 - 1
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);
 

+ 3 - 3
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;

+ 1 - 1
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);
 

+ 31 - 3
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)) {

+ 9 - 2
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);

+ 5 - 1
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));

+ 5 - 15
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;