Browse Source

Merge pull request #55636 from bruvzg/vis_char_modes

Add different "visible characters" behavior modes.
Rémi Verschelde 3 years ago
parent
commit
1906b59675

+ 20 - 0
doc/classes/Label.xml

@@ -82,6 +82,7 @@
 		<member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" overrides="Control" enum="Control.MouseFilter" default="2" />
 		<member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" overrides="Control" enum="Control.MouseFilter" default="2" />
 		<member name="percent_visible" type="float" setter="set_percent_visible" getter="get_percent_visible" default="1.0">
 		<member name="percent_visible" type="float" setter="set_percent_visible" getter="get_percent_visible" default="1.0">
 			Limits the amount of visible characters. If you set [code]percent_visible[/code] to 0.5, only up to half of the text's characters will display on screen. Useful to animate the text in a dialog box.
 			Limits the amount of visible characters. If you set [code]percent_visible[/code] to 0.5, only up to half of the text's characters will display on screen. Useful to animate the text in a dialog box.
+			[b]Note:[/b] Setting this property updates [member visible_characters] based on current [method get_total_character_count].
 		</member>
 		</member>
 		<member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" default="4" />
 		<member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" default="4" />
 		<member name="structured_text_bidi_override" type="int" setter="set_structured_text_bidi_override" getter="get_structured_text_bidi_override" enum="Control.StructuredTextParser" default="0">
 		<member name="structured_text_bidi_override" type="int" setter="set_structured_text_bidi_override" getter="get_structured_text_bidi_override" enum="Control.StructuredTextParser" default="0">
@@ -107,6 +108,10 @@
 		</member>
 		</member>
 		<member name="visible_characters" type="int" setter="set_visible_characters" getter="get_visible_characters" default="-1">
 		<member name="visible_characters" type="int" setter="set_visible_characters" getter="get_visible_characters" default="-1">
 			Restricts the number of characters to display. Set to -1 to disable.
 			Restricts the number of characters to display. Set to -1 to disable.
+			[b]Note:[/b] Setting this property updates [member percent_visible] based on current [method get_total_character_count].
+		</member>
+		<member name="visible_characters_behavior" type="int" setter="set_visible_characters_behavior" getter="get_visible_characters_behavior" enum="Label.VisibleCharactersBehavior" default="0">
+			Sets the clipping behavior when [member visible_characters] or [member percent_visible] is set. See [enum VisibleCharactersBehavior] for more info.
 		</member>
 		</member>
 	</members>
 	</members>
 	<constants>
 	<constants>
@@ -137,6 +142,21 @@
 		<constant name="OVERRUN_TRIM_WORD_ELLIPSIS" value="4" enum="OverrunBehavior">
 		<constant name="OVERRUN_TRIM_WORD_ELLIPSIS" value="4" enum="OverrunBehavior">
 			Trims the text per word and adds an ellipsis to indicate that parts are hidden.
 			Trims the text per word and adds an ellipsis to indicate that parts are hidden.
 		</constant>
 		</constant>
+		<constant name="VC_CHARS_BEFORE_SHAPING" value="0" enum="VisibleCharactersBehavior">
+			Trims text before the shaping. e.g, increasing [member visible_characters] value is visually identical to typing the text.
+		</constant>
+		<constant name="VC_CHARS_AFTER_SHAPING" value="1" enum="VisibleCharactersBehavior">
+			Displays glyphs that are mapped to the first [member visible_characters] characters from the beginning of the text.
+		</constant>
+		<constant name="VC_GLYPHS_AUTO" value="2" enum="VisibleCharactersBehavior">
+			Displays [member percent_visible] glyphs, starting from the left or from the right, depending on [member Control.layout_direction] value.
+		</constant>
+		<constant name="VC_GLYPHS_LTR" value="3" enum="VisibleCharactersBehavior">
+			Displays [member percent_visible] glyphs, starting from the left.
+		</constant>
+		<constant name="VC_GLYPHS_RTL" value="4" enum="VisibleCharactersBehavior">
+			Displays [member percent_visible] glyphs, starting from the right.
+		</constant>
 	</constants>
 	</constants>
 	<theme_items>
 	<theme_items>
 		<theme_item name="font_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
 		<theme_item name="font_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">

+ 18 - 0
doc/classes/RichTextLabel.xml

@@ -429,6 +429,9 @@
 			The restricted number of characters to display in the label. If [code]-1[/code], all characters will be displayed.
 			The restricted number of characters to display in the label. If [code]-1[/code], all characters will be displayed.
 			[b]Note:[/b] Setting this property updates [member percent_visible] based on current [method get_total_character_count].
 			[b]Note:[/b] Setting this property updates [member percent_visible] based on current [method get_total_character_count].
 		</member>
 		</member>
+		<member name="visible_characters_behavior" type="int" setter="set_visible_characters_behavior" getter="get_visible_characters_behavior" enum="RichTextLabel.VisibleCharactersBehavior" default="0">
+			Sets the clipping behavior when [member visible_characters] or [member percent_visible] is set. See [enum VisibleCharactersBehavior] for more info.
+		</member>
 	</members>
 	</members>
 	<signals>
 	<signals>
 		<signal name="meta_clicked">
 		<signal name="meta_clicked">
@@ -515,6 +518,21 @@
 		</constant>
 		</constant>
 		<constant name="ITEM_CUSTOMFX" value="25" enum="ItemType">
 		<constant name="ITEM_CUSTOMFX" value="25" enum="ItemType">
 		</constant>
 		</constant>
+		<constant name="VC_CHARS_BEFORE_SHAPING" value="0" enum="VisibleCharactersBehavior">
+			Trims text before the shaping. e.g, increasing [member visible_characters] value is visually identical to typing the text.
+		</constant>
+		<constant name="VC_CHARS_AFTER_SHAPING" value="1" enum="VisibleCharactersBehavior">
+			Displays glyphs that are mapped to the first [member visible_characters] characters from the beginning of the text.
+		</constant>
+		<constant name="VC_GLYPHS_AUTO" value="2" enum="VisibleCharactersBehavior">
+			Displays [member percent_visible] glyphs, starting from the left or from the right, depending on [member Control.layout_direction] value.
+		</constant>
+		<constant name="VC_GLYPHS_LTR" value="3" enum="VisibleCharactersBehavior">
+			Displays [member percent_visible] glyphs, starting from the left.
+		</constant>
+		<constant name="VC_GLYPHS_RTL" value="4" enum="VisibleCharactersBehavior">
+			Displays [member percent_visible] glyphs, starting from the right.
+		</constant>
 	</constants>
 	</constants>
 	<theme_items>
 	<theme_items>
 		<theme_item name="default_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
 		<theme_item name="default_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">

+ 66 - 9
scene/gui/label.cpp

@@ -93,7 +93,7 @@ void Label::_shape() {
 		int font_size = get_theme_font_size(SNAME("font_size"));
 		int font_size = get_theme_font_size(SNAME("font_size"));
 		ERR_FAIL_COND(font.is_null());
 		ERR_FAIL_COND(font.is_null());
 		String text = (uppercase) ? xl_text.to_upper() : xl_text;
 		String text = (uppercase) ? xl_text.to_upper() : xl_text;
-		if (visible_chars >= 0) {
+		if (visible_chars >= 0 && visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) {
 			text = text.substr(0, visible_chars);
 			text = text.substr(0, visible_chars);
 		}
 		}
 		TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
 		TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
@@ -316,12 +316,19 @@ void Label::_notification(int p_what) {
 		}
 		}
 
 
 		int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped);
 		int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped);
+		bool trim_chars = (visible_chars >= 0) && (visible_chars_behavior == VC_CHARS_AFTER_SHAPING);
+		bool trim_glyphs_ltr = (visible_chars >= 0) && ((visible_chars_behavior == VC_GLYPHS_LTR) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && !rtl_layout));
+		bool trim_glyphs_rtl = (visible_chars >= 0) && ((visible_chars_behavior == VC_GLYPHS_RTL) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && rtl_layout));
 
 
 		// Get real total height.
 		// Get real total height.
+		int total_glyphs = 0;
 		total_h = 0;
 		total_h = 0;
 		for (int64_t i = lines_skipped; i < last_line; i++) {
 		for (int64_t i = lines_skipped; i < last_line; i++) {
 			total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing;
 			total_h += TS->shaped_text_get_size(lines_rid[i]).y + font->get_spacing(TextServer::SPACING_TOP) + font->get_spacing(TextServer::SPACING_BOTTOM) + line_spacing;
+			total_glyphs += TS->shaped_text_get_glyph_count(lines_rid[i]) + TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]);
 		}
 		}
+		int visible_glyphs = total_glyphs * percent_visible;
+		int processed_glyphs = 0;
 		total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
 		total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
 
 
 		int vbegin = 0, vsep = 0;
 		int vbegin = 0, vsep = 0;
@@ -395,14 +402,19 @@ void Label::_notification(int p_what) {
 			int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]);
 			int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]);
 
 
 			// Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps.
 			// Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps.
+			int processed_glyphs_ol = processed_glyphs;
 			if ((outline_size > 0 && font_outline_color.a != 0) || (font_shadow_color.a != 0)) {
 			if ((outline_size > 0 && font_outline_color.a != 0) || (font_shadow_color.a != 0)) {
 				Vector2 offset = ofs;
 				Vector2 offset = ofs;
 				// Draw RTL ellipsis string when necessary.
 				// Draw RTL ellipsis string when necessary.
 				if (rtl && ellipsis_pos >= 0) {
 				if (rtl && ellipsis_pos >= 0) {
 					for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) {
 					for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) {
 						for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
 						for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
+							bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs));
 							//Draw glyph outlines and shadow.
 							//Draw glyph outlines and shadow.
-							draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+							if (!skip) {
+								draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+							}
+							processed_glyphs_ol++;
 							offset.x += ellipsis_glyphs[gl_idx].advance;
 							offset.x += ellipsis_glyphs[gl_idx].advance;
 						}
 						}
 					}
 					}
@@ -423,9 +435,13 @@ void Label::_notification(int p_what) {
 								}
 								}
 							}
 							}
 						}
 						}
+						bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs));
 
 
 						// Draw glyph outlines and shadow.
 						// Draw glyph outlines and shadow.
-						draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+						if (!skip) {
+							draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+						}
+						processed_glyphs_ol++;
 						offset.x += glyphs[j].advance;
 						offset.x += glyphs[j].advance;
 					}
 					}
 				}
 				}
@@ -433,8 +449,12 @@ void Label::_notification(int p_what) {
 				if (!rtl && ellipsis_pos >= 0) {
 				if (!rtl && ellipsis_pos >= 0) {
 					for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) {
 					for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) {
 						for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
 						for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
+							bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs));
 							//Draw glyph outlines and shadow.
 							//Draw glyph outlines and shadow.
-							draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+							if (!skip) {
+								draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs);
+							}
+							processed_glyphs_ol++;
 							offset.x += ellipsis_glyphs[gl_idx].advance;
 							offset.x += ellipsis_glyphs[gl_idx].advance;
 						}
 						}
 					}
 					}
@@ -447,8 +467,12 @@ void Label::_notification(int p_what) {
 			if (rtl && ellipsis_pos >= 0) {
 			if (rtl && ellipsis_pos >= 0) {
 				for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) {
 				for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) {
 					for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
 					for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
+						bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs));
 						//Draw glyph outlines and shadow.
 						//Draw glyph outlines and shadow.
-						draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs);
+						if (!skip) {
+							draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs);
+						}
+						processed_glyphs++;
 						ofs.x += ellipsis_glyphs[gl_idx].advance;
 						ofs.x += ellipsis_glyphs[gl_idx].advance;
 					}
 					}
 				}
 				}
@@ -469,9 +493,13 @@ void Label::_notification(int p_what) {
 							}
 							}
 						}
 						}
 					}
 					}
+					bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs));
 
 
 					// Draw glyph outlines and shadow.
 					// Draw glyph outlines and shadow.
-					draw_glyph(glyphs[j], ci, font_color, ofs);
+					if (!skip) {
+						draw_glyph(glyphs[j], ci, font_color, ofs);
+					}
+					processed_glyphs++;
 					ofs.x += glyphs[j].advance;
 					ofs.x += glyphs[j].advance;
 				}
 				}
 			}
 			}
@@ -479,8 +507,12 @@ void Label::_notification(int p_what) {
 			if (!rtl && ellipsis_pos >= 0) {
 			if (!rtl && ellipsis_pos >= 0) {
 				for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) {
 				for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) {
 					for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
 					for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) {
+						bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs));
 						//Draw glyph outlines and shadow.
 						//Draw glyph outlines and shadow.
-						draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs);
+						if (!skip) {
+							draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs);
+						}
+						processed_glyphs++;
 						ofs.x += ellipsis_glyphs[gl_idx].advance;
 						ofs.x += ellipsis_glyphs[gl_idx].advance;
 					}
 					}
 				}
 				}
@@ -702,7 +734,9 @@ void Label::set_visible_characters(int p_amount) {
 		} else {
 		} else {
 			percent_visible = 1.0;
 			percent_visible = 1.0;
 		}
 		}
-		dirty = true;
+		if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) {
+			dirty = true;
+		}
 		update();
 		update();
 	}
 	}
 }
 }
@@ -720,7 +754,9 @@ void Label::set_percent_visible(float p_percent) {
 			visible_chars = get_total_character_count() * p_percent;
 			visible_chars = get_total_character_count() * p_percent;
 			percent_visible = p_percent;
 			percent_visible = p_percent;
 		}
 		}
-		dirty = true;
+		if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) {
+			dirty = true;
+		}
 		update();
 		update();
 	}
 	}
 }
 }
@@ -729,6 +765,18 @@ float Label::get_percent_visible() const {
 	return percent_visible;
 	return percent_visible;
 }
 }
 
 
+Label::VisibleCharactersBehavior Label::get_visible_characters_behavior() const {
+	return visible_chars_behavior;
+}
+
+void Label::set_visible_characters_behavior(Label::VisibleCharactersBehavior p_behavior) {
+	if (visible_chars_behavior != p_behavior) {
+		visible_chars_behavior = p_behavior;
+		dirty = true;
+		update();
+	}
+}
+
 void Label::set_lines_skipped(int p_lines) {
 void Label::set_lines_skipped(int p_lines) {
 	ERR_FAIL_COND(p_lines < 0);
 	ERR_FAIL_COND(p_lines < 0);
 	lines_skipped = p_lines;
 	lines_skipped = p_lines;
@@ -836,6 +884,8 @@ void Label::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_total_character_count"), &Label::get_total_character_count);
 	ClassDB::bind_method(D_METHOD("get_total_character_count"), &Label::get_total_character_count);
 	ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &Label::set_visible_characters);
 	ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &Label::set_visible_characters);
 	ClassDB::bind_method(D_METHOD("get_visible_characters"), &Label::get_visible_characters);
 	ClassDB::bind_method(D_METHOD("get_visible_characters"), &Label::get_visible_characters);
+	ClassDB::bind_method(D_METHOD("get_visible_characters_behavior"), &Label::get_visible_characters_behavior);
+	ClassDB::bind_method(D_METHOD("set_visible_characters_behavior", "behavior"), &Label::set_visible_characters_behavior);
 	ClassDB::bind_method(D_METHOD("set_percent_visible", "percent_visible"), &Label::set_percent_visible);
 	ClassDB::bind_method(D_METHOD("set_percent_visible", "percent_visible"), &Label::set_percent_visible);
 	ClassDB::bind_method(D_METHOD("get_percent_visible"), &Label::get_percent_visible);
 	ClassDB::bind_method(D_METHOD("get_percent_visible"), &Label::get_percent_visible);
 	ClassDB::bind_method(D_METHOD("set_lines_skipped", "lines_skipped"), &Label::set_lines_skipped);
 	ClassDB::bind_method(D_METHOD("set_lines_skipped", "lines_skipped"), &Label::set_lines_skipped);
@@ -858,6 +908,12 @@ void Label::_bind_methods() {
 	BIND_ENUM_CONSTANT(OVERRUN_TRIM_ELLIPSIS);
 	BIND_ENUM_CONSTANT(OVERRUN_TRIM_ELLIPSIS);
 	BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD_ELLIPSIS);
 	BIND_ENUM_CONSTANT(OVERRUN_TRIM_WORD_ELLIPSIS);
 
 
+	BIND_ENUM_CONSTANT(VC_CHARS_BEFORE_SHAPING);
+	BIND_ENUM_CONSTANT(VC_CHARS_AFTER_SHAPING);
+	BIND_ENUM_CONSTANT(VC_GLYPHS_AUTO);
+	BIND_ENUM_CONSTANT(VC_GLYPHS_LTR);
+	BIND_ENUM_CONSTANT(VC_GLYPHS_RTL);
+
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, "", PROPERTY_USAGE_DEFAULT_INTL), "set_text", "get_text");
 	ADD_GROUP("Locale", "");
 	ADD_GROUP("Locale", "");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
@@ -869,6 +925,7 @@ void Label::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters_behavior", PROPERTY_HINT_ENUM, "Characters Before Shaping,Characters After Shaping,Glyphs (Layout Direction),Glyphs (Left-to-Right),Glyphs (Right-to-Left)"), "set_visible_characters_behavior", "get_visible_characters_behavior");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "lines_skipped", PROPERTY_HINT_RANGE, "0,999,1"), "set_lines_skipped", "get_lines_skipped");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "lines_skipped", PROPERTY_HINT_RANGE, "0,999,1"), "set_lines_skipped", "get_lines_skipped");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lines_visible", PROPERTY_HINT_RANGE, "-1,999,1"), "set_max_lines_visible", "get_max_lines_visible");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "max_lines_visible", PROPERTY_HINT_RANGE, "-1,999,1"), "set_max_lines_visible", "get_max_lines_visible");

+ 13 - 0
scene/gui/label.h

@@ -52,6 +52,14 @@ public:
 		OVERRUN_TRIM_WORD_ELLIPSIS,
 		OVERRUN_TRIM_WORD_ELLIPSIS,
 	};
 	};
 
 
+	enum VisibleCharactersBehavior {
+		VC_CHARS_BEFORE_SHAPING,
+		VC_CHARS_AFTER_SHAPING,
+		VC_GLYPHS_AUTO,
+		VC_GLYPHS_LTR,
+		VC_GLYPHS_RTL,
+	};
+
 private:
 private:
 	HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT;
 	HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT;
 	VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP;
 	VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP;
@@ -76,6 +84,7 @@ private:
 
 
 	float percent_visible = 1.0;
 	float percent_visible = 1.0;
 
 
+	VisibleCharactersBehavior visible_chars_behavior = VC_CHARS_BEFORE_SHAPING;
 	int visible_chars = -1;
 	int visible_chars = -1;
 	int lines_skipped = 0;
 	int lines_skipped = 0;
 	int max_lines_visible = -1;
 	int max_lines_visible = -1;
@@ -126,6 +135,9 @@ public:
 	void set_uppercase(bool p_uppercase);
 	void set_uppercase(bool p_uppercase);
 	bool is_uppercase() const;
 	bool is_uppercase() const;
 
 
+	VisibleCharactersBehavior get_visible_characters_behavior() const;
+	void set_visible_characters_behavior(VisibleCharactersBehavior p_behavior);
+
 	void set_visible_characters(int p_amount);
 	void set_visible_characters(int p_amount);
 	int get_visible_characters() const;
 	int get_visible_characters() const;
 	int get_total_character_count() const;
 	int get_total_character_count() const;
@@ -155,5 +167,6 @@ public:
 
 
 VARIANT_ENUM_CAST(Label::AutowrapMode);
 VARIANT_ENUM_CAST(Label::AutowrapMode);
 VARIANT_ENUM_CAST(Label::OverrunBehavior);
 VARIANT_ENUM_CAST(Label::OverrunBehavior);
+VARIANT_ENUM_CAST(Label::VisibleCharactersBehavior);
 
 
 #endif
 #endif

+ 72 - 14
scene/gui/rich_text_label.cpp

@@ -397,7 +397,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
 	Item *it_to = (p_line + 1 < p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
 	Item *it_to = (p_line + 1 < 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)) {
-		if (visible_characters >= 0 && remaining_characters <= 0) {
+		if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters <= 0) {
 			break;
 			break;
 		}
 		}
 		switch (it->type) {
 		switch (it->type) {
@@ -440,7 +440,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
 				Dictionary font_ftr = _find_font_features(it);
 				Dictionary font_ftr = _find_font_features(it);
 				String lang = _find_language(it);
 				String lang = _find_language(it);
 				String tx = t->text;
 				String tx = t->text;
-				if (visible_characters >= 0 && remaining_characters >= 0) {
+				if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0) {
 					tx = tx.substr(0, remaining_characters);
 					tx = tx.substr(0, remaining_characters);
 				}
 				}
 				remaining_characters -= tx.length();
 				remaining_characters -= tx.length();
@@ -621,7 +621,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
 	}
 	}
 }
 }
 
 
-int RichTextLabel::_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 RichTextLabel::_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) {
 	Vector2 off;
 	Vector2 off;
 
 
 	ERR_FAIL_COND_V(p_frame == nullptr, 0);
 	ERR_FAIL_COND_V(p_frame == nullptr, 0);
@@ -641,6 +641,12 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 	bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL);
 	bool rtl = (l.text_buf->get_direction() == TextServer::DIRECTION_RTL);
 	bool lrtl = is_layout_rtl();
 	bool lrtl = is_layout_rtl();
 
 
+	bool trim_chars = (visible_characters >= 0) && (visible_chars_behavior == VC_CHARS_AFTER_SHAPING);
+	bool trim_glyphs_ltr = (visible_characters >= 0) && ((visible_chars_behavior == VC_GLYPHS_LTR) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && !lrtl));
+	bool trim_glyphs_rtl = (visible_characters >= 0) && ((visible_chars_behavior == VC_GLYPHS_RTL) || ((visible_chars_behavior == VC_GLYPHS_AUTO) && lrtl));
+	int total_glyphs = (trim_glyphs_ltr || trim_glyphs_rtl) ? get_total_glyph_count() : 0;
+	int visible_glyphs = total_glyphs * percent_visible;
+
 	Vector<int> list_index;
 	Vector<int> list_index;
 	Vector<ItemList *> list_items;
 	Vector<ItemList *> list_items;
 	_find_list(l.from, list_index, list_items);
 	_find_list(l.from, list_index, list_items);
@@ -804,7 +810,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 							}
 							}
 
 
 							for (int j = 0; j < frame->lines.size(); j++) {
 							for (int j = 0; j < frame->lines.size(); j++) {
-								_draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs);
+								_draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs);
 							}
 							}
 							idx++;
 							idx++;
 						}
 						}
@@ -820,6 +826,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 
 
 		Vector2 gloff = off;
 		Vector2 gloff = off;
 		// Draw oulines and shadow.
 		// Draw oulines and shadow.
+		int processed_glyphs_ol = r_processed_glyphs;
 		for (int i = 0; i < gl_size; i++) {
 		for (int i = 0; i < gl_size; i++) {
 			Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
 			Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
 			int size = _find_outline_size(it, p_outline_size);
 			int size = _find_outline_size(it, p_outline_size);
@@ -947,7 +954,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 			// Draw glyph outlines.
 			// Draw glyph outlines.
 			for (int j = 0; j < glyphs[i].repeat; j++) {
 			for (int j = 0; j < glyphs[i].repeat; j++) {
 				if (visible) {
 				if (visible) {
-					if (frid != RID()) {
+					bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs));
+					if (!skip && frid != RID()) {
 						if (font_shadow_color.a > 0) {
 						if (font_shadow_color.a > 0) {
 							TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color);
 							TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + gloff + p_shadow_ofs, gl, font_shadow_color);
 						}
 						}
@@ -958,6 +966,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 							TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color);
 							TS->font_draw_glyph_outline(frid, ci, glyphs[i].font_size, size, p_ofs + fx_offset + gloff, gl, font_color);
 						}
 						}
 					}
 					}
+					processed_glyphs_ol++;
 				}
 				}
 				gloff.x += glyphs[i].advance;
 				gloff.x += glyphs[i].advance;
 			}
 			}
@@ -1124,11 +1133,15 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 			// Draw glyphs.
 			// Draw glyphs.
 			for (int j = 0; j < glyphs[i].repeat; j++) {
 			for (int j = 0; j < glyphs[i].repeat; j++) {
 				if (visible) {
 				if (visible) {
-					if (frid != RID()) {
-						TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color);
-					} else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
-						TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color);
+					bool skip = (trim_chars && l.char_offset + glyphs[i].end > visible_characters) || (trim_glyphs_ltr && (r_processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (r_processed_glyphs < total_glyphs - visible_glyphs));
+					if (!skip) {
+						if (frid != RID()) {
+							TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color);
+						} else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
+							TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color);
+						}
 					}
 					}
+					r_processed_glyphs++;
 				}
 				}
 				off.x += glyphs[i].advance;
 				off.x += glyphs[i].advance;
 			}
 			}
@@ -1481,9 +1494,10 @@ void RichTextLabel::_notification(int p_what) {
 
 
 			// New cache draw.
 			// New cache draw.
 			Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
 			Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs);
+			int processed_glyphs = 0;
 			while (ofs.y < size.height && from_line < main->lines.size()) {
 			while (ofs.y < size.height && from_line < main->lines.size()) {
 				visible_paragraph_count++;
 				visible_paragraph_count++;
-				visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, shadow_outline_size, shadow_ofs);
+				visible_line_count += _draw_line(main, from_line, ofs, text_rect.size.x, base_color, outline_size, outline_color, font_shadow_color, shadow_outline_size, shadow_ofs, processed_glyphs);
 				ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
 				ofs.y += main->lines[from_line].text_buf->get_size().y + get_theme_constant(SNAME("line_separation"));
 				from_line++;
 				from_line++;
 			}
 			}
@@ -3993,8 +4007,10 @@ void RichTextLabel::set_percent_visible(float p_percent) {
 			visible_characters = get_total_character_count() * p_percent;
 			visible_characters = get_total_character_count() * p_percent;
 			percent_visible = p_percent;
 			percent_visible = p_percent;
 		}
 		}
-		main->first_invalid_line = 0; //invalidate ALL
-		_validate_line_caches(main);
+		if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) {
+			main->first_invalid_line = 0; //invalidate ALL
+			_validate_line_caches(main);
+		}
 		update();
 		update();
 	}
 	}
 }
 }
@@ -4135,6 +4151,9 @@ void RichTextLabel::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &RichTextLabel::set_visible_characters);
 	ClassDB::bind_method(D_METHOD("set_visible_characters", "amount"), &RichTextLabel::set_visible_characters);
 	ClassDB::bind_method(D_METHOD("get_visible_characters"), &RichTextLabel::get_visible_characters);
 	ClassDB::bind_method(D_METHOD("get_visible_characters"), &RichTextLabel::get_visible_characters);
 
 
+	ClassDB::bind_method(D_METHOD("get_visible_characters_behavior"), &RichTextLabel::get_visible_characters_behavior);
+	ClassDB::bind_method(D_METHOD("set_visible_characters_behavior", "behavior"), &RichTextLabel::set_visible_characters_behavior);
+
 	ClassDB::bind_method(D_METHOD("set_percent_visible", "percent_visible"), &RichTextLabel::set_percent_visible);
 	ClassDB::bind_method(D_METHOD("set_percent_visible", "percent_visible"), &RichTextLabel::set_percent_visible);
 	ClassDB::bind_method(D_METHOD("get_percent_visible"), &RichTextLabel::get_percent_visible);
 	ClassDB::bind_method(D_METHOD("get_percent_visible"), &RichTextLabel::get_percent_visible);
 
 
@@ -4160,6 +4179,8 @@ void RichTextLabel::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters", PROPERTY_HINT_RANGE, "-1,128000,1"), "set_visible_characters", "get_visible_characters");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "percent_visible", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_percent_visible", "get_percent_visible");
 
 
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "visible_characters_behavior", PROPERTY_HINT_ENUM, "Characters Before Shaping,Characters After Shaping,Glyphs (Layout Direction),Glyphs (Left-to-Right),Glyphs (Right-to-Left)"), "set_visible_characters_behavior", "get_visible_characters_behavior");
+
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "meta_underlined"), "set_meta_underline", "is_meta_underlined");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "tab_size", PROPERTY_HINT_RANGE, "0,24,1"), "set_tab_size", "get_tab_size");
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
@@ -4219,6 +4240,25 @@ void RichTextLabel::_bind_methods() {
 	BIND_ENUM_CONSTANT(ITEM_META);
 	BIND_ENUM_CONSTANT(ITEM_META);
 	BIND_ENUM_CONSTANT(ITEM_DROPCAP);
 	BIND_ENUM_CONSTANT(ITEM_DROPCAP);
 	BIND_ENUM_CONSTANT(ITEM_CUSTOMFX);
 	BIND_ENUM_CONSTANT(ITEM_CUSTOMFX);
+
+	BIND_ENUM_CONSTANT(VC_CHARS_BEFORE_SHAPING);
+	BIND_ENUM_CONSTANT(VC_CHARS_AFTER_SHAPING);
+	BIND_ENUM_CONSTANT(VC_GLYPHS_AUTO);
+	BIND_ENUM_CONSTANT(VC_GLYPHS_LTR);
+	BIND_ENUM_CONSTANT(VC_GLYPHS_RTL);
+}
+
+RichTextLabel::VisibleCharactersBehavior RichTextLabel::get_visible_characters_behavior() const {
+	return visible_chars_behavior;
+}
+
+void RichTextLabel::set_visible_characters_behavior(RichTextLabel::VisibleCharactersBehavior p_behavior) {
+	if (visible_chars_behavior != p_behavior) {
+		visible_chars_behavior = p_behavior;
+		main->first_invalid_line = 0; //invalidate ALL
+		_validate_line_caches(main);
+		update();
+	}
 }
 }
 
 
 void RichTextLabel::set_visible_characters(int p_visible) {
 void RichTextLabel::set_visible_characters(int p_visible) {
@@ -4232,8 +4272,10 @@ void RichTextLabel::set_visible_characters(int p_visible) {
 				percent_visible = (float)p_visible / (float)total_char_count;
 				percent_visible = (float)p_visible / (float)total_char_count;
 			}
 			}
 		}
 		}
-		main->first_invalid_line = 0; //invalidate ALL
-		_validate_line_caches(main);
+		if (visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) {
+			main->first_invalid_line = 0; //invalidate ALL
+			_validate_line_caches(main);
+		}
 		update();
 		update();
 	}
 	}
 }
 }
@@ -4261,6 +4303,22 @@ int RichTextLabel::get_total_character_count() const {
 	return tc;
 	return tc;
 }
 }
 
 
+int RichTextLabel::get_total_glyph_count() const {
+	int tg = 0;
+	Item *it = main;
+	while (it) {
+		if (it->type == ITEM_FRAME) {
+			ItemFrame *f = static_cast<ItemFrame *>(it);
+			for (int i = 0; i < f->lines.size(); i++) {
+				tg += TS->shaped_text_get_glyph_count(f->lines[i].text_buf->get_rid());
+			}
+		}
+		it = _get_next_item(it, true);
+	}
+
+	return tg;
+}
+
 void RichTextLabel::set_fixed_size_to_width(int p_width) {
 void RichTextLabel::set_fixed_size_to_width(int p_width) {
 	fixed_width = p_width;
 	fixed_width = p_width;
 	update_minimum_size();
 	update_minimum_size();

+ 15 - 1
scene/gui/rich_text_label.h

@@ -75,6 +75,14 @@ public:
 		ITEM_CUSTOMFX
 		ITEM_CUSTOMFX
 	};
 	};
 
 
+	enum VisibleCharactersBehavior {
+		VC_CHARS_BEFORE_SHAPING,
+		VC_CHARS_AFTER_SHAPING,
+		VC_GLYPHS_AUTO,
+		VC_GLYPHS_LTR,
+		VC_GLYPHS_RTL,
+	};
+
 protected:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
 	static void _bind_methods();
 	static void _bind_methods();
@@ -396,6 +404,7 @@ private:
 
 
 	int visible_characters = -1;
 	int visible_characters = -1;
 	float percent_visible = 1.0;
 	float percent_visible = 1.0;
+	VisibleCharactersBehavior visible_chars_behavior = VC_CHARS_BEFORE_SHAPING;
 
 
 	void _find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr);
 	void _find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr);
 
 
@@ -405,7 +414,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 _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 _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width);
-	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 _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);
 	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);
 
 
 	String _roman(int p_num, bool p_capitalize) const;
 	String _roman(int p_num, bool p_capitalize) const;
@@ -572,10 +581,14 @@ public:
 	void set_visible_characters(int p_visible);
 	void set_visible_characters(int p_visible);
 	int get_visible_characters() const;
 	int get_visible_characters() const;
 	int get_total_character_count() const;
 	int get_total_character_count() const;
+	int get_total_glyph_count() const;
 
 
 	void set_percent_visible(float p_percent);
 	void set_percent_visible(float p_percent);
 	float get_percent_visible() const;
 	float get_percent_visible() const;
 
 
+	VisibleCharactersBehavior get_visible_characters_behavior() const;
+	void set_visible_characters_behavior(VisibleCharactersBehavior p_behavior);
+
 	void set_effects(Array p_effects);
 	void set_effects(Array p_effects);
 	Array get_effects();
 	Array get_effects();
 
 
@@ -590,5 +603,6 @@ public:
 
 
 VARIANT_ENUM_CAST(RichTextLabel::ListType);
 VARIANT_ENUM_CAST(RichTextLabel::ListType);
 VARIANT_ENUM_CAST(RichTextLabel::ItemType);
 VARIANT_ENUM_CAST(RichTextLabel::ItemType);
+VARIANT_ENUM_CAST(RichTextLabel::VisibleCharactersBehavior);
 
 
 #endif // RICH_TEXT_LABEL_H
 #endif // RICH_TEXT_LABEL_H