Browse Source

[RTL] Use separate paragraph copy for the partially visible paragraphs.

Pāvels Nadtočajevs 3 months ago
parent
commit
0d19e18b00

+ 13 - 0
doc/classes/TextLine.xml

@@ -58,6 +58,12 @@
 				Draw text into a canvas item at a given position, with [param color]. [param pos] specifies the top left corner of the bounding box. If [param oversampling] is greater than zero, it is used as font oversampling factor, otherwise viewport oversampling settings are used.
 				Draw text into a canvas item at a given position, with [param color]. [param pos] specifies the top left corner of the bounding box. If [param oversampling] is greater than zero, it is used as font oversampling factor, otherwise viewport oversampling settings are used.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="duplicate" qualifiers="const">
+			<return type="TextLine" />
+			<description>
+				Duplicates this [TextLine].
+			</description>
+		</method>
 		<method name="get_inferred_direction" qualifiers="const">
 		<method name="get_inferred_direction" qualifiers="const">
 			<return type="int" enum="TextServer.Direction" />
 			<return type="int" enum="TextServer.Direction" />
 			<description>
 			<description>
@@ -119,6 +125,13 @@
 				Returns size of the bounding box of the text.
 				Returns size of the bounding box of the text.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="has_object" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="key" type="Variant" />
+			<description>
+				Returns [code]true[/code] if an object with [param key] is embedded in this line.
+			</description>
+		</method>
 		<method name="hit_test" qualifiers="const">
 		<method name="hit_test" qualifiers="const">
 			<return type="int" />
 			<return type="int" />
 			<param index="0" name="coords" type="float" />
 			<param index="0" name="coords" type="float" />

+ 13 - 0
doc/classes/TextParagraph.xml

@@ -110,6 +110,12 @@
 				Draw outlines of all lines of the text and drop cap into a canvas item at a given position, with [param color]. [param pos] specifies the top left corner of the bounding box. If [param oversampling] is greater than zero, it is used as font oversampling factor, otherwise viewport oversampling settings are used.
 				Draw outlines of all lines of the text and drop cap into a canvas item at a given position, with [param color]. [param pos] specifies the top left corner of the bounding box. If [param oversampling] is greater than zero, it is used as font oversampling factor, otherwise viewport oversampling settings are used.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="duplicate" qualifiers="const">
+			<return type="TextParagraph" />
+			<description>
+				Duplicates this [TextParagraph].
+			</description>
+		</method>
 		<method name="get_dropcap_lines" qualifiers="const">
 		<method name="get_dropcap_lines" qualifiers="const">
 			<return type="int" />
 			<return type="int" />
 			<description>
 			<description>
@@ -235,6 +241,13 @@
 				Returns the size of the bounding box of the paragraph.
 				Returns the size of the bounding box of the paragraph.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="has_object" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="key" type="Variant" />
+			<description>
+				Returns [code]true[/code] if an object with [param key] is embedded in this shaped text buffer.
+			</description>
+		</method>
 		<method name="hit_test" qualifiers="const">
 		<method name="hit_test" qualifiers="const">
 			<return type="int" />
 			<return type="int" />
 			<param index="0" name="coords" type="Vector2" />
 			<param index="0" name="coords" type="Vector2" />

+ 15 - 0
doc/classes/TextServer.xml

@@ -1418,6 +1418,13 @@
 				[param clip_l] and [param clip_r] are offsets relative to [param pos], going to the right in horizontal layout and downward in vertical layout. If [param clip_l] is not negative, glyphs starting before the offset are clipped. If [param clip_r] is not negative, glyphs ending after the offset are clipped.
 				[param clip_l] and [param clip_r] are offsets relative to [param pos], going to the right in horizontal layout and downward in vertical layout. If [param clip_l] is not negative, glyphs starting before the offset are clipped. If [param clip_r] is not negative, glyphs ending after the offset are clipped.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="shaped_text_duplicate">
+			<return type="RID" />
+			<param index="0" name="rid" type="RID" />
+			<description>
+				Duplicates shaped text buffer.
+			</description>
+		</method>
 		<method name="shaped_text_fit_to_width">
 		<method name="shaped_text_fit_to_width">
 			<return type="float" />
 			<return type="float" />
 			<param index="0" name="shaped" type="RID" />
 			<param index="0" name="shaped" type="RID" />
@@ -1687,6 +1694,14 @@
 				Breaks text into words and returns array of character ranges. Use [param grapheme_flags] to set what characters are used for breaking.
 				Breaks text into words and returns array of character ranges. Use [param grapheme_flags] to set what characters are used for breaking.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="shaped_text_has_object" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="key" type="Variant" />
+			<description>
+				Returns [code]true[/code] if an object with [param key] is embedded in this shaped text buffer.
+			</description>
+		</method>
 		<method name="shaped_text_has_visible_chars" qualifiers="const">
 		<method name="shaped_text_has_visible_chars" qualifiers="const">
 			<return type="bool" />
 			<return type="bool" />
 			<param index="0" name="shaped" type="RID" />
 			<param index="0" name="shaped" type="RID" />

+ 15 - 0
doc/classes/TextServerExtension.xml

@@ -1388,6 +1388,13 @@
 				Draw the outline of the shaped text into a canvas item at a given position, with [param color]. [param pos] specifies the leftmost point of the baseline (for horizontal layout) or topmost point of the baseline (for vertical layout). If [param oversampling] is greater than zero, it is used as font oversampling factor, otherwise viewport oversampling settings are used.
 				Draw the outline of the shaped text into a canvas item at a given position, with [param color]. [param pos] specifies the leftmost point of the baseline (for horizontal layout) or topmost point of the baseline (for vertical layout). If [param oversampling] is greater than zero, it is used as font oversampling factor, otherwise viewport oversampling settings are used.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="_shaped_text_duplicate" qualifiers="virtual required">
+			<return type="RID" />
+			<param index="0" name="shaped" type="RID" />
+			<description>
+				Duplicates shaped text buffer.
+			</description>
+		</method>
 		<method name="_shaped_text_fit_to_width" qualifiers="virtual">
 		<method name="_shaped_text_fit_to_width" qualifiers="virtual">
 			<return type="float" />
 			<return type="float" />
 			<param index="0" name="shaped" type="RID" />
 			<param index="0" name="shaped" type="RID" />
@@ -1655,6 +1662,14 @@
 				Breaks text into words and returns array of character ranges. Use [param grapheme_flags] to set what characters are used for breaking.
 				Breaks text into words and returns array of character ranges. Use [param grapheme_flags] to set what characters are used for breaking.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="_shaped_text_has_object" qualifiers="virtual required const">
+			<return type="bool" />
+			<param index="0" name="shaped" type="RID" />
+			<param index="1" name="key" type="Variant" />
+			<description>
+				Returns [code]true[/code] if an object with [param key] is embedded in this shaped text buffer.
+			</description>
+		</method>
 		<method name="_shaped_text_hit_test_grapheme" qualifiers="virtual const">
 		<method name="_shaped_text_hit_test_grapheme" qualifiers="virtual const">
 			<return type="int" />
 			<return type="int" />
 			<param index="0" name="shaped" type="RID" />
 			<param index="0" name="shaped" type="RID" />

+ 46 - 1
modules/text_server_adv/text_server_adv.cpp

@@ -4446,7 +4446,7 @@ void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) {
 		}
 		}
 	}
 	}
 
 
-	for (int i = p_shaped->first_span; i <= p_shaped->last_span; i++) {
+	for (int i = MAX(0, p_shaped->first_span); i <= MIN(p_shaped->last_span, parent->spans.size() - 1); i++) {
 		ShapedTextDataAdvanced::Span span = parent->spans[i];
 		ShapedTextDataAdvanced::Span span = parent->spans[i];
 		span.start = MAX(p_shaped->start, span.start);
 		span.start = MAX(p_shaped->start, span.start);
 		span.end = MIN(p_shaped->end, span.end);
 		span.end = MIN(p_shaped->end, span.end);
@@ -4486,6 +4486,43 @@ void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) {
 	invalidate(sd, true);
 	invalidate(sd, true);
 }
 }
 
 
+RID TextServerAdvanced::_shaped_text_duplicate(const RID &p_shaped) {
+	_THREAD_SAFE_METHOD_
+
+	const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, RID());
+
+	MutexLock lock(sd->mutex);
+
+	ShapedTextDataAdvanced *new_sd = memnew(ShapedTextDataAdvanced);
+	new_sd->parent = p_shaped;
+	new_sd->start = sd->start;
+	new_sd->end = sd->end;
+	new_sd->text = sd->text;
+	new_sd->hb_buffer = hb_buffer_create();
+	new_sd->utf16 = new_sd->text.utf16();
+	new_sd->script_iter = memnew(ScriptIterator(new_sd->text, 0, new_sd->text.length()));
+	new_sd->orientation = sd->orientation;
+	new_sd->direction = sd->direction;
+	new_sd->custom_punct = sd->custom_punct;
+	new_sd->para_direction = sd->para_direction;
+	new_sd->base_para_direction = sd->base_para_direction;
+	new_sd->line_breaks_valid = sd->line_breaks_valid;
+	new_sd->justification_ops_valid = sd->justification_ops_valid;
+	new_sd->sort_valid = false;
+	new_sd->upos = sd->upos;
+	new_sd->uthk = sd->uthk;
+	new_sd->runs.clear();
+	new_sd->runs_dirty = true;
+	for (int i = 0; i < TextServer::SPACING_MAX; i++) {
+		new_sd->extra_spacing[i] = sd->extra_spacing[i];
+	}
+	full_copy(new_sd);
+	new_sd->valid.clear();
+
+	return shaped_owner.make_rid(new_sd);
+}
+
 void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
 void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction.");
 	ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction.");
@@ -4998,6 +5035,14 @@ String TextServerAdvanced::_shaped_get_text(const RID &p_shaped) const {
 	return sd->text;
 	return sd->text;
 }
 }
 
 
+bool TextServerAdvanced::_shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const {
+	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, false);
+
+	MutexLock lock(sd->mutex);
+	return sd->objects.has(p_key);
+}
+
 bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
 bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL_V(sd, false);
 	ERR_FAIL_NULL_V(sd, false);

+ 2 - 0
modules/text_server_adv/text_server_adv.h

@@ -1032,6 +1032,7 @@ public:
 	MODBIND2R(RID, create_shaped_text, Direction, Orientation);
 	MODBIND2R(RID, create_shaped_text, Direction, Orientation);
 
 
 	MODBIND1(shaped_text_clear, const RID &);
 	MODBIND1(shaped_text_clear, const RID &);
+	MODBIND1R(RID, shaped_text_duplicate, const RID &);
 
 
 	MODBIND2(shaped_text_set_direction, const RID &, Direction);
 	MODBIND2(shaped_text_set_direction, const RID &, Direction);
 	MODBIND1RC(Direction, shaped_text_get_direction, const RID &);
 	MODBIND1RC(Direction, shaped_text_get_direction, const RID &);
@@ -1060,6 +1061,7 @@ public:
 	MODBIND7R(bool, shaped_text_add_string, const RID &, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
 	MODBIND7R(bool, shaped_text_add_string, const RID &, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
 	MODBIND6R(bool, shaped_text_add_object, const RID &, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
 	MODBIND6R(bool, shaped_text_add_object, const RID &, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
 	MODBIND5R(bool, shaped_text_resize_object, const RID &, const Variant &, const Size2 &, InlineAlignment, double);
 	MODBIND5R(bool, shaped_text_resize_object, const RID &, const Variant &, const Size2 &, InlineAlignment, double);
+	MODBIND2RC(bool, shaped_text_has_object, const RID &, const Variant &);
 	MODBIND1RC(String, shaped_get_text, const RID &);
 	MODBIND1RC(String, shaped_get_text, const RID &);
 
 
 	MODBIND1RC(int64_t, shaped_get_span_count, const RID &);
 	MODBIND1RC(int64_t, shaped_get_span_count, const RID &);

+ 42 - 1
modules/text_server_fb/text_server_fb.cpp

@@ -3285,7 +3285,7 @@ void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) {
 		}
 		}
 	}
 	}
 
 
-	for (int i = p_shaped->first_span; i <= p_shaped->last_span; i++) {
+	for (int i = MAX(0, p_shaped->first_span); i <= MIN(p_shaped->last_span, parent->spans.size() - 1); i++) {
 		ShapedTextDataFallback::Span span = parent->spans[i];
 		ShapedTextDataFallback::Span span = parent->spans[i];
 		span.start = MAX(p_shaped->start, span.start);
 		span.start = MAX(p_shaped->start, span.start);
 		span.end = MIN(p_shaped->end, span.end);
 		span.end = MIN(p_shaped->end, span.end);
@@ -3324,6 +3324,39 @@ void TextServerFallback::_shaped_text_clear(const RID &p_shaped) {
 	invalidate(sd);
 	invalidate(sd);
 }
 }
 
 
+RID TextServerFallback::_shaped_text_duplicate(const RID &p_shaped) {
+	_THREAD_SAFE_METHOD_
+
+	const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, RID());
+
+	MutexLock lock(sd->mutex);
+
+	ShapedTextDataFallback *new_sd = memnew(ShapedTextDataFallback);
+	new_sd->parent = p_shaped;
+	new_sd->start = sd->start;
+	new_sd->end = sd->end;
+	new_sd->text = sd->text;
+	new_sd->orientation = sd->orientation;
+	new_sd->direction = sd->direction;
+	new_sd->custom_punct = sd->custom_punct;
+	new_sd->para_direction = sd->para_direction;
+	new_sd->line_breaks_valid = sd->line_breaks_valid;
+	new_sd->justification_ops_valid = sd->justification_ops_valid;
+	new_sd->sort_valid = false;
+	new_sd->upos = sd->upos;
+	new_sd->uthk = sd->uthk;
+	new_sd->runs.clear();
+	new_sd->runs_dirty = true;
+	for (int i = 0; i < TextServer::SPACING_MAX; i++) {
+		new_sd->extra_spacing[i] = sd->extra_spacing[i];
+	}
+	full_copy(new_sd);
+	new_sd->valid.clear();
+
+	return shaped_owner.make_rid(new_sd);
+}
+
 void TextServerFallback::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
 void TextServerFallback::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
 	ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction.");
 	ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction.");
 	if (p_direction == DIRECTION_RTL) {
 	if (p_direction == DIRECTION_RTL) {
@@ -3832,6 +3865,14 @@ String TextServerFallback::_shaped_get_text(const RID &p_shaped) const {
 	return sd->text;
 	return sd->text;
 }
 }
 
 
+bool TextServerFallback::_shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const {
+	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_NULL_V(sd, false);
+
+	MutexLock lock(sd->mutex);
+	return sd->objects.has(p_key);
+}
+
 bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
 bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
 	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
 	ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
 	ERR_FAIL_NULL_V(sd, false);
 	ERR_FAIL_NULL_V(sd, false);

+ 2 - 0
modules/text_server_fb/text_server_fb.h

@@ -811,6 +811,7 @@ public:
 	MODBIND2R(RID, create_shaped_text, Direction, Orientation);
 	MODBIND2R(RID, create_shaped_text, Direction, Orientation);
 
 
 	MODBIND1(shaped_text_clear, const RID &);
 	MODBIND1(shaped_text_clear, const RID &);
+	MODBIND1R(RID, shaped_text_duplicate, const RID &);
 
 
 	MODBIND2(shaped_text_set_direction, const RID &, Direction);
 	MODBIND2(shaped_text_set_direction, const RID &, Direction);
 	MODBIND1RC(Direction, shaped_text_get_direction, const RID &);
 	MODBIND1RC(Direction, shaped_text_get_direction, const RID &);
@@ -839,6 +840,7 @@ public:
 	MODBIND7R(bool, shaped_text_add_string, const RID &, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
 	MODBIND7R(bool, shaped_text_add_string, const RID &, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
 	MODBIND6R(bool, shaped_text_add_object, const RID &, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
 	MODBIND6R(bool, shaped_text_add_object, const RID &, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
 	MODBIND5R(bool, shaped_text_resize_object, const RID &, const Variant &, const Size2 &, InlineAlignment, double);
 	MODBIND5R(bool, shaped_text_resize_object, const RID &, const Variant &, const Size2 &, InlineAlignment, double);
+	MODBIND2RC(bool, shaped_text_has_object, const RID &, const Variant &);
 	MODBIND1RC(String, shaped_get_text, const RID &);
 	MODBIND1RC(String, shaped_get_text, const RID &);
 
 
 	MODBIND1RC(int64_t, shaped_get_span_count, const RID &);
 	MODBIND1RC(int64_t, shaped_get_span_count, const RID &);

+ 110 - 49
scene/gui/rich_text_label.cpp

@@ -381,31 +381,62 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<
 	// List.
 	// List.
 	_add_list_prefixes(p_frame, p_line, l);
 	_add_list_prefixes(p_frame, p_line, l);
 
 
-	RID t = l.text_buf->get_rid();
-	int spans = TS->shaped_get_span_count(t);
-	for (int i = 0; i < spans; i++) {
-		Item *it_span = items.get_or_null(TS->shaped_get_span_meta(t, i));
-		ItemText *it = reinterpret_cast<ItemText *>(it_span);
-		if (it) {
-			Ref<Font> font = p_base_font;
-			int font_size = p_base_font_size;
+	{
+		RID t = l.text_buf->get_rid();
+		int spans = TS->shaped_get_span_count(t);
+		for (int i = 0; i < spans; i++) {
+			Item *it_span = items.get_or_null(TS->shaped_get_span_meta(t, i));
+			ItemText *it = reinterpret_cast<ItemText *>(it_span);
+			if (it) {
+				Ref<Font> font = p_base_font;
+				int font_size = p_base_font_size;
 
 
-			ItemFont *font_it = _find_font(it);
-			if (font_it) {
-				if (font_it->font.is_valid()) {
-					font = font_it->font;
+				ItemFont *font_it = _find_font(it);
+				if (font_it) {
+					if (font_it->font.is_valid()) {
+						font = font_it->font;
+					}
+					if (font_it->font_size > 0) {
+						font_size = font_it->font_size;
+					}
 				}
 				}
-				if (font_it->font_size > 0) {
-					font_size = font_it->font_size;
+				ItemFontSize *font_size_it = _find_font_size(it);
+				if (font_size_it && font_size_it->font_size > 0) {
+					font_size = font_size_it->font_size;
 				}
 				}
+				TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features());
+			} else {
+				TS->shaped_set_span_update_font(t, i, p_base_font->get_rids(), p_base_font_size, p_base_font->get_opentype_features());
 			}
 			}
-			ItemFontSize *font_size_it = _find_font_size(it);
-			if (font_size_it && font_size_it->font_size > 0) {
-				font_size = font_size_it->font_size;
+		}
+	}
+	if (l.text_buf_disp.is_valid()) {
+		RID t = l.text_buf_disp->get_rid();
+		int spans = TS->shaped_get_span_count(t);
+		for (int i = 0; i < spans; i++) {
+			Item *it_span = items.get_or_null(TS->shaped_get_span_meta(t, i));
+			ItemText *it = reinterpret_cast<ItemText *>(it_span);
+			if (it) {
+				Ref<Font> font = p_base_font;
+				int font_size = p_base_font_size;
+
+				ItemFont *font_it = _find_font(it);
+				if (font_it) {
+					if (font_it->font.is_valid()) {
+						font = font_it->font;
+					}
+					if (font_it->font_size > 0) {
+						font_size = font_it->font_size;
+					}
+				}
+				ItemFontSize *font_size_it = _find_font_size(it);
+				if (font_size_it && font_size_it->font_size > 0) {
+					font_size = font_size_it->font_size;
+				}
+				TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features());
+			} else {
+				TS->shaped_set_span_update_font(t, i, p_base_font->get_rids(), p_base_font_size, p_base_font->get_opentype_features());
 			}
 			}
-			TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features());
-		} else {
-			TS->shaped_set_span_update_font(t, i, p_base_font->get_rids(), p_base_font_size, p_base_font->get_opentype_features());
 		}
 		}
 	}
 	}
 
 
@@ -449,6 +480,17 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
 		l.text_buf->tab_align(tabs);
 		l.text_buf->tab_align(tabs);
 	}
 	}
 
 
+	if (l.text_buf_disp.is_valid()) {
+		l.text_buf_disp->set_width(p_width - l.offset.x);
+		if (!tab_stops.is_empty()) {
+			l.text_buf_disp->tab_align(tab_stops);
+		} else if (tab_size > 0) { // Align inline tabs.
+			Vector<float> tabs;
+			tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width);
+			l.text_buf_disp->tab_align(tabs);
+		}
+	}
+
 	Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
 	Item *it_to = (p_line + 1 < (int)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)) {
 	for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
 		switch (it->type) {
 		switch (it->type) {
@@ -458,6 +500,9 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
 				if (img->width_in_percent || img->height_in_percent) {
 				if (img->width_in_percent || img->height_in_percent) {
 					img_size = _get_image_size(img->image, img->width_in_percent ? (p_width * img->rq_size.width / 100.f) : img->rq_size.width, img->height_in_percent ? (p_width * img->rq_size.height / 100.f) : img->rq_size.height, img->region);
 					img_size = _get_image_size(img->image, img->width_in_percent ? (p_width * img->rq_size.width / 100.f) : img->rq_size.width, img->height_in_percent ? (p_width * img->rq_size.height / 100.f) : img->rq_size.height, img->region);
 					l.text_buf->resize_object(it->rid, img_size, img->inline_align);
 					l.text_buf->resize_object(it->rid, img_size, img->inline_align);
+					if (l.text_buf_disp.is_valid() && l.text_buf_disp->has_object(it->rid)) {
+						l.text_buf_disp->resize_object(it->rid, img_size, img->inline_align);
+					}
 				}
 				}
 			} break;
 			} break;
 			case ITEM_TABLE: {
 			case ITEM_TABLE: {
@@ -489,8 +534,14 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
 				int row_idx = (table->align_to_row < 0) ? table->rows_baseline.size() - 1 : table->align_to_row;
 				int row_idx = (table->align_to_row < 0) ? table->rows_baseline.size() - 1 : table->align_to_row;
 				if (table->rows_baseline.size() != 0 && row_idx < (int)table->rows_baseline.size()) {
 				if (table->rows_baseline.size() != 0 && row_idx < (int)table->rows_baseline.size()) {
 					l.text_buf->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, Math::round(table->rows_baseline[row_idx]));
 					l.text_buf->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, Math::round(table->rows_baseline[row_idx]));
+					if (l.text_buf_disp.is_valid() && l.text_buf_disp->has_object(it->rid)) {
+						l.text_buf_disp->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, Math::round(table->rows_baseline[row_idx]));
+					}
 				} else {
 				} else {
 					l.text_buf->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align);
 					l.text_buf->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align);
+					if (l.text_buf_disp.is_valid() && l.text_buf_disp->has_object(it->rid)) {
+						l.text_buf_disp->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align);
+					}
 				}
 				}
 			} break;
 			} break;
 			default:
 			default:
@@ -528,6 +579,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
 
 
 	// Clear cache.
 	// Clear cache.
 	l.dc_item = nullptr;
 	l.dc_item = nullptr;
+	l.text_buf_disp = Ref<TextParagraph>();
 	l.text_buf->clear();
 	l.text_buf->clear();
 	l.text_buf->set_break_flags(autowrap_flags);
 	l.text_buf->set_break_flags(autowrap_flags);
 	l.text_buf->set_justification_flags(_find_jst_flags(l.from));
 	l.text_buf->set_justification_flags(_find_jst_flags(l.from));
@@ -555,6 +607,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
 
 
 	// Shape current paragraph.
 	// Shape current paragraph.
 	String txt;
 	String txt;
+	String txt_sub;
 	Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
 	Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
 	int remaining_characters = visible_characters - l.char_offset;
 	int remaining_characters = visible_characters - l.char_offset;
 	for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
 	for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
@@ -610,14 +663,13 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
 				}
 				}
 				String lang = _find_language(it);
 				String lang = _find_language(it);
 				String tx = t->text;
 				String tx = t->text;
-				if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0 && tx.length() > remaining_characters) {
-					String first = tx.substr(0, remaining_characters);
-					String second = tx.substr(remaining_characters, -1);
-					l.text_buf->add_string(first, font, font_size, lang, it->rid);
-					l.text_buf->add_string(second, font, font_size, lang, it->rid);
-				} else {
-					l.text_buf->add_string(tx, font, font_size, lang, it->rid);
+				if (l.text_buf_disp.is_null() && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0 && tx.length() > remaining_characters) {
+					String sub = tx.substr(0, remaining_characters);
+					l.text_buf_disp = l.text_buf->duplicate();
+					l.text_buf_disp->add_string(sub, font, font_size, lang, it->rid);
+					txt_sub = txt + sub;
 				}
 				}
+				l.text_buf->add_string(tx, font, font_size, lang, it->rid);
 				remaining_characters -= tx.length();
 				remaining_characters -= tx.length();
 
 
 				txt += tx;
 				txt += tx;
@@ -698,7 +750,11 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
 	}
 	}
 
 
 	// Apply BiDi override.
 	// Apply BiDi override.
-	l.text_buf->set_bidi_override(structured_text_parser(_find_stt(l.from), st_args, txt));
+	TextServer::StructuredTextParser stt = _find_stt(l.from);
+	l.text_buf->set_bidi_override(structured_text_parser(stt, st_args, txt));
+	if (l.text_buf_disp.is_valid()) {
+		l.text_buf_disp->set_bidi_override(structured_text_parser(stt, st_args, txt_sub));
+	}
 
 
 	*r_char_offset = l.char_offset + l.char_count;
 	*r_char_offset = l.char_offset + l.char_count;
 
 
@@ -898,13 +954,15 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 		l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color);
 		l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color);
 	}
 	}
 
 
+	const Ref<TextParagraph> &text_buf = l.text_buf_disp.is_valid() ? l.text_buf_disp : l.text_buf;
+
 	int line_count = 0;
 	int line_count = 0;
 	bool has_visible_chars = false;
 	bool has_visible_chars = false;
 	// Bottom margin for text clipping.
 	// Bottom margin for text clipping.
 	float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM);
 	float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM);
 	Size2 ctrl_size = get_size();
 	Size2 ctrl_size = get_size();
 	// Draw text.
 	// Draw text.
-	for (int line = 0; line < l.text_buf->get_line_count(); line++) {
+	for (int line = 0; line < text_buf->get_line_count(); line++) {
 		if (line > 0) {
 		if (line > 0) {
 			off.y += (theme_cache.line_separation + p_vsep);
 			off.y += (theme_cache.line_separation + p_vsep);
 		}
 		}
@@ -913,14 +971,14 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 			break;
 			break;
 		}
 		}
 
 
-		double l_height = l.text_buf->get_line_ascent(line) + l.text_buf->get_line_descent(line);
+		double l_height = text_buf->get_line_ascent(line) + text_buf->get_line_descent(line);
 		if (p_ofs.y + off.y + l_height <= 0) {
 		if (p_ofs.y + off.y + l_height <= 0) {
 			off.y += l_height;
 			off.y += l_height;
 			continue;
 			continue;
 		}
 		}
 
 
-		float width = l.text_buf->get_width();
-		float length = l.text_buf->get_line_size(line).x;
+		float width = text_buf->get_width();
+		float length = text_buf->get_line_size(line).x;
 
 
 		// Draw line.
 		// Draw line.
 		if (rtl) {
 		if (rtl) {
@@ -936,7 +994,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 		}
 		}
 
 
 		// Draw text.
 		// Draw text.
-		switch (l.text_buf->get_alignment()) {
+		switch (text_buf->get_alignment()) {
 			case HORIZONTAL_ALIGNMENT_FILL:
 			case HORIZONTAL_ALIGNMENT_FILL:
 			case HORIZONTAL_ALIGNMENT_LEFT: {
 			case HORIZONTAL_ALIGNMENT_LEFT: {
 				if (rtl) {
 				if (rtl) {
@@ -986,7 +1044,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 			}
 			}
 		}
 		}
 
 
-		RID rid = l.text_buf->get_line_rid(line);
+		RID rid = text_buf->get_line_rid(line);
 		double l_ascent = TS->shaped_text_get_ascent(rid);
 		double l_ascent = TS->shaped_text_get_ascent(rid);
 		Size2 l_size = TS->shaped_text_get_size(rid);
 		Size2 l_size = TS->shaped_text_get_size(rid);
 		double upos = TS->shaped_text_get_underline_position(rid);
 		double upos = TS->shaped_text_get_underline_position(rid);
@@ -1627,10 +1685,12 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
 	Item *table_click_item = nullptr;
 	Item *table_click_item = nullptr;
 	int table_click_char = -1;
 	int table_click_char = -1;
 
 
-	for (int line = 0; line < l.text_buf->get_line_count(); line++) {
-		RID rid = l.text_buf->get_line_rid(line);
+	const Ref<TextParagraph> &text_buf = l.text_buf_disp.is_valid() ? l.text_buf_disp : l.text_buf;
+
+	for (int line = 0; line < text_buf->get_line_count(); line++) {
+		RID rid = text_buf->get_line_rid(line);
 
 
-		float width = l.text_buf->get_width();
+		float width = text_buf->get_width();
 		float length = TS->shaped_text_get_width(rid);
 		float length = TS->shaped_text_get_width(rid);
 
 
 		if (rtl) {
 		if (rtl) {
@@ -1645,7 +1705,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
 			}
 			}
 		}
 		}
 
 
-		switch (l.text_buf->get_alignment()) {
+		switch (text_buf->get_alignment()) {
 			case HORIZONTAL_ALIGNMENT_FILL:
 			case HORIZONTAL_ALIGNMENT_FILL:
 			case HORIZONTAL_ALIGNMENT_LEFT: {
 			case HORIZONTAL_ALIGNMENT_LEFT: {
 				if (rtl) {
 				if (rtl) {
@@ -1662,8 +1722,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
 			} break;
 			} break;
 		}
 		}
 		// Adjust for dropcap.
 		// Adjust for dropcap.
-		int dc_lines = l.text_buf->get_dropcap_lines();
-		float h_off = l.text_buf->get_dropcap_size().x;
+		int dc_lines = text_buf->get_dropcap_lines();
+		float h_off = text_buf->get_dropcap_size().x;
 		if (line <= dc_lines) {
 		if (line <= dc_lines) {
 			if (rtl) {
 			if (rtl) {
 				off.x -= h_off;
 				off.x -= h_off;
@@ -1824,7 +1884,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
 			return table_offy;
 			return table_offy;
 		}
 		}
 
 
-		if (line == l.text_buf->get_line_count() - 1) {
+		if (line == text_buf->get_line_count() - 1) {
 			off.y += TS->shaped_text_get_descent(rid) + theme_cache.paragraph_separation + p_vsep;
 			off.y += TS->shaped_text_get_descent(rid) + theme_cache.paragraph_separation + p_vsep;
 		} else {
 		} else {
 			off.y += TS->shaped_text_get_descent(rid) + theme_cache.line_separation + p_vsep;
 			off.y += TS->shaped_text_get_descent(rid) + theme_cache.line_separation + p_vsep;
@@ -2016,7 +2076,8 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
 	float h_off = l.text_buf->get_dropcap_size().x;
 	float h_off = l.text_buf->get_dropcap_size().x;
 
 
 	// Process text.
 	// Process text.
-	const RID &para_rid = l.text_buf->get_rid();
+	const Ref<TextParagraph> &text_buf = l.text_buf_disp.is_valid() ? l.text_buf_disp : l.text_buf;
+	const RID &para_rid = text_buf->get_rid();
 
 
 	String l_text = TS->shaped_get_text(para_rid).remove_char(0xfffc).strip_edges();
 	String l_text = TS->shaped_get_text(para_rid).remove_char(0xfffc).strip_edges();
 	if (l.dc_item) {
 	if (l.dc_item) {
@@ -2026,7 +2087,7 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
 	if (!l_text.is_empty()) {
 	if (!l_text.is_empty()) {
 		Vector2 off;
 		Vector2 off;
 		if (rtl) {
 		if (rtl) {
-			off.x = p_width - l.offset.x - l.text_buf->get_width();
+			off.x = p_width - l.offset.x - text_buf->get_width();
 			if (!lrtl && p_frame == main) { // Skip Scrollbar.
 			if (!lrtl && p_frame == main) { // Skip Scrollbar.
 				off.x -= scroll_w;
 				off.x -= scroll_w;
 			}
 			}
@@ -2039,7 +2100,7 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
 
 
 		l.accessibility_text_element = DisplayServer::get_singleton()->accessibility_create_sub_element(line_ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
 		l.accessibility_text_element = DisplayServer::get_singleton()->accessibility_create_sub_element(line_ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
 		DisplayServer::get_singleton()->accessibility_update_set_value(l.accessibility_text_element, l_text);
 		DisplayServer::get_singleton()->accessibility_update_set_value(l.accessibility_text_element, l_text);
-		ae_rect = Rect2(p_ofs + off, l.text_buf->get_size());
+		ae_rect = Rect2(p_ofs + off, text_buf->get_size());
 		DisplayServer::get_singleton()->accessibility_update_set_bounds(l.accessibility_text_element, ae_rect);
 		DisplayServer::get_singleton()->accessibility_update_set_bounds(l.accessibility_text_element, ae_rect);
 		ac_element_bounds_cache[l.accessibility_text_element] = ae_rect;
 		ac_element_bounds_cache[l.accessibility_text_element] = ae_rect;
 
 
@@ -2049,14 +2110,14 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
 	}
 	}
 
 
 	Vector2 off;
 	Vector2 off;
-	for (int line = 0; line < l.text_buf->get_line_count(); line++) {
+	for (int line = 0; line < text_buf->get_line_count(); line++) {
 		if (line > 0) {
 		if (line > 0) {
 			off.y += (theme_cache.line_separation + p_vsep);
 			off.y += (theme_cache.line_separation + p_vsep);
 		}
 		}
 
 
-		const Size2 line_size = l.text_buf->get_line_size(line);
+		const Size2 line_size = text_buf->get_line_size(line);
 
 
-		float width = l.text_buf->get_width();
+		float width = text_buf->get_width();
 		float length = line_size.x;
 		float length = line_size.x;
 
 
 		// Process line.
 		// Process line.
@@ -2074,7 +2135,7 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
 		}
 		}
 
 
 		// Process text.
 		// Process text.
-		switch (l.text_buf->get_alignment()) {
+		switch (text_buf->get_alignment()) {
 			case HORIZONTAL_ALIGNMENT_FILL:
 			case HORIZONTAL_ALIGNMENT_FILL:
 			case HORIZONTAL_ALIGNMENT_LEFT: {
 			case HORIZONTAL_ALIGNMENT_LEFT: {
 				if (rtl) {
 				if (rtl) {
@@ -2099,7 +2160,7 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
 			}
 			}
 		}
 		}
 
 
-		const RID &rid = l.text_buf->get_line_rid(line);
+		const RID &rid = text_buf->get_line_rid(line);
 
 
 		Array objects = TS->shaped_text_get_objects(rid);
 		Array objects = TS->shaped_text_get_objects(rid);
 		for (int i = 0; i < objects.size(); i++) {
 		for (int i = 0; i < objects.size(); i++) {

+ 1 - 0
scene/gui/rich_text_label.h

@@ -166,6 +166,7 @@ private:
 		Color prefix_outline_color = Color(0, 0, 0, 0);
 		Color prefix_outline_color = Color(0, 0, 0, 0);
 		float prefix_width = 0;
 		float prefix_width = 0;
 		Ref<TextParagraph> text_buf;
 		Ref<TextParagraph> text_buf;
+		Ref<TextParagraph> text_buf_disp;
 
 
 		RID accessibility_line_element;
 		RID accessibility_line_element;
 		RID accessibility_text_element;
 		RID accessibility_text_element;

+ 24 - 0
scene/resources/text_line.cpp

@@ -33,6 +33,7 @@
 
 
 void TextLine::_bind_methods() {
 void TextLine::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("clear"), &TextLine::clear);
 	ClassDB::bind_method(D_METHOD("clear"), &TextLine::clear);
+	ClassDB::bind_method(D_METHOD("duplicate"), &TextLine::duplicate);
 
 
 	ClassDB::bind_method(D_METHOD("set_direction", "direction"), &TextLine::set_direction);
 	ClassDB::bind_method(D_METHOD("set_direction", "direction"), &TextLine::set_direction);
 	ClassDB::bind_method(D_METHOD("get_direction"), &TextLine::get_direction);
 	ClassDB::bind_method(D_METHOD("get_direction"), &TextLine::get_direction);
@@ -64,6 +65,7 @@ void TextLine::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("add_string", "text", "font", "font_size", "language", "meta"), &TextLine::add_string, DEFVAL(""), DEFVAL(Variant()));
 	ClassDB::bind_method(D_METHOD("add_string", "text", "font", "font_size", "language", "meta"), &TextLine::add_string, DEFVAL(""), DEFVAL(Variant()));
 	ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length", "baseline"), &TextLine::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
 	ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length", "baseline"), &TextLine::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
 	ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align", "baseline"), &TextLine::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
 	ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align", "baseline"), &TextLine::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
+	ClassDB::bind_method(D_METHOD("has_object", "key"), &TextLine::has_object);
 
 
 	ClassDB::bind_method(D_METHOD("set_width", "width"), &TextLine::set_width);
 	ClassDB::bind_method(D_METHOD("set_width", "width"), &TextLine::set_width);
 	ClassDB::bind_method(D_METHOD("get_width"), &TextLine::get_width);
 	ClassDB::bind_method(D_METHOD("get_width"), &TextLine::get_width);
@@ -149,6 +151,23 @@ void TextLine::clear() {
 	TS->shaped_text_clear(rid);
 	TS->shaped_text_clear(rid);
 }
 }
 
 
+Ref<TextLine> TextLine::duplicate() const {
+	Ref<TextLine> copy;
+	copy.instantiate();
+	if (rid.is_valid()) {
+		copy->rid = TS->shaped_text_duplicate(rid);
+	}
+	copy->dirty = true;
+	copy->width = width;
+	copy->flags = flags;
+	copy->alignment = alignment;
+	copy->el_char = el_char;
+	copy->overrun_behavior = overrun_behavior;
+	copy->tab_stops = tab_stops;
+
+	return copy;
+}
+
 void TextLine::set_preserve_invalid(bool p_enabled) {
 void TextLine::set_preserve_invalid(bool p_enabled) {
 	TS->shaped_text_set_preserve_invalid(rid, p_enabled);
 	TS->shaped_text_set_preserve_invalid(rid, p_enabled);
 	dirty = true;
 	dirty = true;
@@ -212,6 +231,11 @@ bool TextLine::resize_object(Variant p_key, const Size2 &p_size, InlineAlignment
 	return TS->shaped_text_resize_object(rid, p_key, p_size, p_inline_align, p_baseline);
 	return TS->shaped_text_resize_object(rid, p_key, p_size, p_inline_align, p_baseline);
 }
 }
 
 
+bool TextLine::has_object(Variant p_key) const {
+	_shape();
+	return TS->shaped_text_has_object(rid, p_key);
+}
+
 Array TextLine::get_objects() const {
 Array TextLine::get_objects() const {
 	return TS->shaped_text_get_objects(rid);
 	return TS->shaped_text_get_objects(rid);
 }
 }

+ 2 - 0
scene/resources/text_line.h

@@ -66,6 +66,7 @@ public:
 	RID get_rid() const;
 	RID get_rid() const;
 
 
 	void clear();
 	void clear();
+	Ref<TextLine> duplicate() const;
 
 
 	void set_direction(TextServer::Direction p_direction);
 	void set_direction(TextServer::Direction p_direction);
 	TextServer::Direction get_direction() const;
 	TextServer::Direction get_direction() const;
@@ -85,6 +86,7 @@ public:
 	bool add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language = "", const Variant &p_meta = Variant());
 	bool add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, 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, float p_baseline = 0.0);
 	bool add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1, float p_baseline = 0.0);
 	bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, float p_baseline = 0.0);
 	bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, float p_baseline = 0.0);
+	bool has_object(Variant p_key) const;
 
 
 	void set_horizontal_alignment(HorizontalAlignment p_alignment);
 	void set_horizontal_alignment(HorizontalAlignment p_alignment);
 	HorizontalAlignment get_horizontal_alignment() const;
 	HorizontalAlignment get_horizontal_alignment() const;

+ 33 - 0
scene/resources/text_paragraph.cpp

@@ -33,6 +33,7 @@
 
 
 void TextParagraph::_bind_methods() {
 void TextParagraph::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("clear"), &TextParagraph::clear);
 	ClassDB::bind_method(D_METHOD("clear"), &TextParagraph::clear);
+	ClassDB::bind_method(D_METHOD("duplicate"), &TextParagraph::duplicate);
 
 
 	ClassDB::bind_method(D_METHOD("set_direction", "direction"), &TextParagraph::set_direction);
 	ClassDB::bind_method(D_METHOD("set_direction", "direction"), &TextParagraph::set_direction);
 	ClassDB::bind_method(D_METHOD("get_direction"), &TextParagraph::get_direction);
 	ClassDB::bind_method(D_METHOD("get_direction"), &TextParagraph::get_direction);
@@ -72,6 +73,7 @@ void TextParagraph::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("add_string", "text", "font", "font_size", "language", "meta"), &TextParagraph::add_string, DEFVAL(""), DEFVAL(Variant()));
 	ClassDB::bind_method(D_METHOD("add_string", "text", "font", "font_size", "language", "meta"), &TextParagraph::add_string, DEFVAL(""), DEFVAL(Variant()));
 	ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length", "baseline"), &TextParagraph::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
 	ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length", "baseline"), &TextParagraph::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
 	ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align", "baseline"), &TextParagraph::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
 	ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align", "baseline"), &TextParagraph::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
+	ClassDB::bind_method(D_METHOD("has_object", "key"), &TextParagraph::has_object);
 
 
 	ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &TextParagraph::set_alignment);
 	ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &TextParagraph::set_alignment);
 	ClassDB::bind_method(D_METHOD("get_alignment"), &TextParagraph::get_alignment);
 	ClassDB::bind_method(D_METHOD("get_alignment"), &TextParagraph::get_alignment);
@@ -322,6 +324,31 @@ void TextParagraph::clear() {
 	TS->shaped_text_clear(dropcap_rid);
 	TS->shaped_text_clear(dropcap_rid);
 }
 }
 
 
+Ref<TextParagraph> TextParagraph::duplicate() const {
+	Ref<TextParagraph> copy;
+	copy.instantiate();
+	if (dropcap_rid.is_valid()) {
+		copy->dropcap_rid = TS->shaped_text_duplicate(dropcap_rid);
+	}
+	copy->dropcap_lines = dropcap_lines;
+	copy->dropcap_margins = dropcap_margins;
+	if (rid.is_valid()) {
+		copy->rid = TS->shaped_text_duplicate(rid);
+	}
+	copy->lines_dirty = true;
+	copy->line_spacing = line_spacing;
+	copy->width = width;
+	copy->max_lines_visible = max_lines_visible;
+	copy->brk_flags = brk_flags;
+	copy->jst_flags = jst_flags;
+	copy->el_char = el_char;
+	copy->overrun_behavior = overrun_behavior;
+	copy->alignment = alignment;
+	copy->tab_stops = tab_stops;
+
+	return copy;
+}
+
 void TextParagraph::set_preserve_invalid(bool p_enabled) {
 void TextParagraph::set_preserve_invalid(bool p_enabled) {
 	_THREAD_SAFE_METHOD_
 	_THREAD_SAFE_METHOD_
 
 
@@ -448,6 +475,12 @@ bool TextParagraph::resize_object(Variant p_key, const Size2 &p_size, InlineAlig
 	return res;
 	return res;
 }
 }
 
 
+bool TextParagraph::has_object(Variant p_key) const {
+	_THREAD_SAFE_METHOD_
+
+	return TS->shaped_text_has_object(rid, p_key);
+}
+
 void TextParagraph::set_alignment(HorizontalAlignment p_alignment) {
 void TextParagraph::set_alignment(HorizontalAlignment p_alignment) {
 	_THREAD_SAFE_METHOD_
 	_THREAD_SAFE_METHOD_
 
 

+ 3 - 0
scene/resources/text_paragraph.h

@@ -85,6 +85,8 @@ public:
 
 
 	void clear();
 	void clear();
 
 
+	Ref<TextParagraph> duplicate() const;
+
 	void set_direction(TextServer::Direction p_direction);
 	void set_direction(TextServer::Direction p_direction);
 	TextServer::Direction get_direction() const;
 	TextServer::Direction get_direction() const;
 	TextServer::Direction get_inferred_direction() const;
 	TextServer::Direction get_inferred_direction() const;
@@ -109,6 +111,7 @@ public:
 	bool add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language = "", const Variant &p_meta = Variant());
 	bool add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, 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, float p_baseline = 0.0);
 	bool add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1, float p_baseline = 0.0);
 	bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, float p_baseline = 0.0);
 	bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, float p_baseline = 0.0);
+	bool has_object(Variant p_key) const;
 
 
 	void set_alignment(HorizontalAlignment p_alignment);
 	void set_alignment(HorizontalAlignment p_alignment);
 	HorizontalAlignment get_alignment() const;
 	HorizontalAlignment get_alignment() const;

+ 2 - 0
servers/text/text_server.cpp

@@ -402,6 +402,7 @@ void TextServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("create_shaped_text", "direction", "orientation"), &TextServer::create_shaped_text, DEFVAL(DIRECTION_AUTO), DEFVAL(ORIENTATION_HORIZONTAL));
 	ClassDB::bind_method(D_METHOD("create_shaped_text", "direction", "orientation"), &TextServer::create_shaped_text, DEFVAL(DIRECTION_AUTO), DEFVAL(ORIENTATION_HORIZONTAL));
 
 
 	ClassDB::bind_method(D_METHOD("shaped_text_clear", "rid"), &TextServer::shaped_text_clear);
 	ClassDB::bind_method(D_METHOD("shaped_text_clear", "rid"), &TextServer::shaped_text_clear);
+	ClassDB::bind_method(D_METHOD("shaped_text_duplicate", "rid"), &TextServer::shaped_text_duplicate);
 
 
 	ClassDB::bind_method(D_METHOD("shaped_text_set_direction", "shaped", "direction"), &TextServer::shaped_text_set_direction, DEFVAL(DIRECTION_AUTO));
 	ClassDB::bind_method(D_METHOD("shaped_text_set_direction", "shaped", "direction"), &TextServer::shaped_text_set_direction, DEFVAL(DIRECTION_AUTO));
 	ClassDB::bind_method(D_METHOD("shaped_text_get_direction", "shaped"), &TextServer::shaped_text_get_direction);
 	ClassDB::bind_method(D_METHOD("shaped_text_get_direction", "shaped"), &TextServer::shaped_text_get_direction);
@@ -430,6 +431,7 @@ void TextServer::_bind_methods() {
 	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_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", "baseline"), &TextServer::shaped_text_add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
 	ClassDB::bind_method(D_METHOD("shaped_text_add_object", "shaped", "key", "size", "inline_align", "length", "baseline"), &TextServer::shaped_text_add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
 	ClassDB::bind_method(D_METHOD("shaped_text_resize_object", "shaped", "key", "size", "inline_align", "baseline"), &TextServer::shaped_text_resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
 	ClassDB::bind_method(D_METHOD("shaped_text_resize_object", "shaped", "key", "size", "inline_align", "baseline"), &TextServer::shaped_text_resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
+	ClassDB::bind_method(D_METHOD("shaped_text_has_object", "shaped", "key"), &TextServer::shaped_text_has_object);
 	ClassDB::bind_method(D_METHOD("shaped_get_text", "shaped"), &TextServer::shaped_get_text);
 	ClassDB::bind_method(D_METHOD("shaped_get_text", "shaped"), &TextServer::shaped_get_text);
 
 
 	ClassDB::bind_method(D_METHOD("shaped_get_span_count", "shaped"), &TextServer::shaped_get_span_count);
 	ClassDB::bind_method(D_METHOD("shaped_get_span_count", "shaped"), &TextServer::shaped_get_span_count);

+ 2 - 0
servers/text/text_server.h

@@ -463,6 +463,7 @@ public:
 	virtual RID create_shaped_text(Direction p_direction = DIRECTION_AUTO, Orientation p_orientation = ORIENTATION_HORIZONTAL) = 0;
 	virtual RID create_shaped_text(Direction p_direction = DIRECTION_AUTO, Orientation p_orientation = ORIENTATION_HORIZONTAL) = 0;
 
 
 	virtual void shaped_text_clear(const RID &p_shaped) = 0;
 	virtual void shaped_text_clear(const RID &p_shaped) = 0;
+	virtual RID shaped_text_duplicate(const RID &p_shaped) = 0;
 
 
 	virtual void shaped_text_set_direction(const RID &p_shaped, Direction p_direction = DIRECTION_AUTO) = 0;
 	virtual void shaped_text_set_direction(const RID &p_shaped, Direction p_direction = DIRECTION_AUTO) = 0;
 	virtual Direction shaped_text_get_direction(const RID &p_shaped) const = 0;
 	virtual Direction shaped_text_get_direction(const RID &p_shaped) const = 0;
@@ -491,6 +492,7 @@ public:
 	virtual bool shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) = 0;
 	virtual bool shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) = 0;
 	virtual bool shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int64_t p_length = 1, double p_baseline = 0.0) = 0;
 	virtual bool shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int64_t p_length = 1, double p_baseline = 0.0) = 0;
 	virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, double p_baseline = 0.0) = 0;
 	virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, double p_baseline = 0.0) = 0;
+	virtual bool shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const = 0;
 	virtual String shaped_get_text(const RID &p_shaped) const = 0;
 	virtual String shaped_get_text(const RID &p_shaped) const = 0;
 
 
 	virtual int64_t shaped_get_span_count(const RID &p_shaped) const = 0;
 	virtual int64_t shaped_get_span_count(const RID &p_shaped) const = 0;

+ 2 - 0
servers/text/text_server_dummy.h

@@ -94,9 +94,11 @@ public:
 
 
 	virtual RID create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) override { return RID(); }
 	virtual RID create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) override { return RID(); }
 	virtual void shaped_text_clear(const RID &p_shaped) override {}
 	virtual void shaped_text_clear(const RID &p_shaped) override {}
+	virtual RID shaped_text_duplicate(const RID &p_shaped) override { return RID(); }
 	virtual bool shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) override { return false; }
 	virtual bool shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) override { return false; }
 	virtual bool shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) override { return false; }
 	virtual bool shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) override { return false; }
 	virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) override { return false; }
 	virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) override { return false; }
+	virtual bool shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const override { return false; }
 	virtual int64_t shaped_get_span_count(const RID &p_shaped) const override { return 0; }
 	virtual int64_t shaped_get_span_count(const RID &p_shaped) const override { return 0; }
 	virtual Variant shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const override { return Variant(); }
 	virtual Variant shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const override { return Variant(); }
 	virtual Variant shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const override { return Variant(); }
 	virtual Variant shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const override { return Variant(); }

+ 14 - 0
servers/text/text_server_extension.cpp

@@ -252,6 +252,7 @@ void TextServerExtension::_bind_methods() {
 	GDVIRTUAL_BIND(_create_shaped_text, "direction", "orientation");
 	GDVIRTUAL_BIND(_create_shaped_text, "direction", "orientation");
 
 
 	GDVIRTUAL_BIND(_shaped_text_clear, "shaped");
 	GDVIRTUAL_BIND(_shaped_text_clear, "shaped");
+	GDVIRTUAL_BIND(_shaped_text_duplicate, "shaped");
 
 
 	GDVIRTUAL_BIND(_shaped_text_set_direction, "shaped", "direction");
 	GDVIRTUAL_BIND(_shaped_text_set_direction, "shaped", "direction");
 	GDVIRTUAL_BIND(_shaped_text_get_direction, "shaped");
 	GDVIRTUAL_BIND(_shaped_text_get_direction, "shaped");
@@ -280,6 +281,7 @@ void TextServerExtension::_bind_methods() {
 	GDVIRTUAL_BIND(_shaped_text_add_string, "shaped", "text", "fonts", "size", "opentype_features", "language", "meta");
 	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", "baseline");
 	GDVIRTUAL_BIND(_shaped_text_add_object, "shaped", "key", "size", "inline_align", "length", "baseline");
 	GDVIRTUAL_BIND(_shaped_text_resize_object, "shaped", "key", "size", "inline_align", "baseline");
 	GDVIRTUAL_BIND(_shaped_text_resize_object, "shaped", "key", "size", "inline_align", "baseline");
+	GDVIRTUAL_BIND(_shaped_text_has_object, "shaped", "key");
 	GDVIRTUAL_BIND(_shaped_get_text, "shaped");
 	GDVIRTUAL_BIND(_shaped_get_text, "shaped");
 
 
 	GDVIRTUAL_BIND(_shaped_get_span_count, "shaped");
 	GDVIRTUAL_BIND(_shaped_get_span_count, "shaped");
@@ -1148,6 +1150,12 @@ void TextServerExtension::shaped_text_clear(const RID &p_shaped) {
 	GDVIRTUAL_CALL(_shaped_text_clear, p_shaped);
 	GDVIRTUAL_CALL(_shaped_text_clear, p_shaped);
 }
 }
 
 
+RID TextServerExtension::shaped_text_duplicate(const RID &p_shaped) {
+	RID ret;
+	GDVIRTUAL_CALL(_shaped_text_duplicate, p_shaped, ret);
+	return ret;
+}
+
 void TextServerExtension::shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
 void TextServerExtension::shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
 	GDVIRTUAL_CALL(_shaped_text_set_direction, p_shaped, p_direction);
 	GDVIRTUAL_CALL(_shaped_text_set_direction, p_shaped, p_direction);
 }
 }
@@ -1246,6 +1254,12 @@ bool TextServerExtension::shaped_text_resize_object(const RID &p_shaped, const V
 	return ret;
 	return ret;
 }
 }
 
 
+bool TextServerExtension::shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const {
+	bool ret = false;
+	GDVIRTUAL_CALL(_shaped_text_has_object, p_shaped, p_key, ret);
+	return ret;
+}
+
 String TextServerExtension::shaped_get_text(const RID &p_shaped) const {
 String TextServerExtension::shaped_get_text(const RID &p_shaped) const {
 	String ret;
 	String ret;
 	GDVIRTUAL_CALL(_shaped_get_text, p_shaped, ret);
 	GDVIRTUAL_CALL(_shaped_get_text, p_shaped, ret);

+ 5 - 0
servers/text/text_server_extension.h

@@ -413,6 +413,9 @@ public:
 	virtual void shaped_text_clear(const RID &p_shaped) override;
 	virtual void shaped_text_clear(const RID &p_shaped) override;
 	GDVIRTUAL1_REQUIRED(_shaped_text_clear, RID);
 	GDVIRTUAL1_REQUIRED(_shaped_text_clear, RID);
 
 
+	virtual RID shaped_text_duplicate(const RID &p_shaped) override;
+	GDVIRTUAL1R_REQUIRED(RID, _shaped_text_duplicate, RID);
+
 	virtual void shaped_text_set_direction(const RID &p_shaped, Direction p_direction = DIRECTION_AUTO) override;
 	virtual void shaped_text_set_direction(const RID &p_shaped, Direction p_direction = DIRECTION_AUTO) override;
 	virtual Direction shaped_text_get_direction(const RID &p_shaped) const override;
 	virtual Direction shaped_text_get_direction(const RID &p_shaped) const override;
 	virtual Direction shaped_text_get_inferred_direction(const RID &p_shaped) const override;
 	virtual Direction shaped_text_get_inferred_direction(const RID &p_shaped) const override;
@@ -456,9 +459,11 @@ public:
 	virtual bool shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override;
 	virtual bool shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override;
 	virtual bool shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int64_t p_length = 1, double p_baseline = 0.0) override;
 	virtual bool shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int64_t p_length = 1, double p_baseline = 0.0) override;
 	virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, double p_baseline = 0.0) override;
 	virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, double p_baseline = 0.0) override;
+	virtual bool shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const override;
 	GDVIRTUAL7R_REQUIRED(bool, _shaped_text_add_string, RID, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
 	GDVIRTUAL7R_REQUIRED(bool, _shaped_text_add_string, RID, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
 	GDVIRTUAL6R_REQUIRED(bool, _shaped_text_add_object, RID, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
 	GDVIRTUAL6R_REQUIRED(bool, _shaped_text_add_object, RID, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
 	GDVIRTUAL5R_REQUIRED(bool, _shaped_text_resize_object, RID, const Variant &, const Size2 &, InlineAlignment, double);
 	GDVIRTUAL5R_REQUIRED(bool, _shaped_text_resize_object, RID, const Variant &, const Size2 &, InlineAlignment, double);
+	GDVIRTUAL2RC_REQUIRED(bool, _shaped_text_has_object, RID, const Variant &);
 
 
 	virtual String shaped_get_text(const RID &p_shaped) const override;
 	virtual String shaped_get_text(const RID &p_shaped) const override;
 	GDVIRTUAL1RC_REQUIRED(String, _shaped_get_text, RID);
 	GDVIRTUAL1RC_REQUIRED(String, _shaped_get_text, RID);