2
0
Эх сурвалжийг харах

Merge pull request #56714 from bruvzg/fix_alignment_break_overrun

Rémi Verschelde 3 жил өмнө
parent
commit
a987ebfde2

+ 7 - 0
doc/classes/TextServer.xml

@@ -1051,6 +1051,13 @@
 				Returns composite character's bounds as offsets from the start of the line.
 				Returns composite character's bounds as offsets from the start of the line.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="shaped_text_get_inferred_direction" qualifiers="const">
+			<return type="int" enum="TextServer.Direction" />
+			<argument index="0" name="shaped" type="RID" />
+			<description>
+				Returns direction of the text, inferred by the BiDi algorithm.
+			</description>
+		</method>
 		<method name="shaped_text_get_line_breaks" qualifiers="const">
 		<method name="shaped_text_get_line_breaks" qualifiers="const">
 			<return type="PackedInt32Array" />
 			<return type="PackedInt32Array" />
 			<argument index="0" name="shaped" type="RID" />
 			<argument index="0" name="shaped" type="RID" />

+ 7 - 0
doc/classes/TextServerExtension.xml

@@ -1061,6 +1061,13 @@
 				Returns composite character's bounds as offsets from the start of the line.
 				Returns composite character's bounds as offsets from the start of the line.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="_shaped_text_get_inferred_direction" qualifiers="virtual const">
+			<return type="int" />
+			<argument index="0" name="shaped" type="RID" />
+			<description>
+				Returns direction of the text, inferred by the BiDi algorithm.
+			</description>
+		</method>
 		<method name="_shaped_text_get_line_breaks" qualifiers="virtual const">
 		<method name="_shaped_text_get_line_breaks" qualifiers="virtual const">
 			<return type="PackedInt32Array" />
 			<return type="PackedInt32Array" />
 			<argument index="0" name="shaped" type="RID" />
 			<argument index="0" name="shaped" type="RID" />

+ 182 - 81
modules/text_server_adv/text_server_adv.cpp

@@ -3029,6 +3029,14 @@ TextServer::Direction TextServerAdvanced::shaped_text_get_direction(RID p_shaped
 	return sd->direction;
 	return sd->direction;
 }
 }
 
 
+TextServer::Direction TextServerAdvanced::shaped_text_get_inferred_direction(RID p_shaped) const {
+	const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
+	ERR_FAIL_COND_V(!sd, TextServer::DIRECTION_LTR);
+
+	MutexLock lock(sd->mutex);
+	return sd->para_direction;
+}
+
 void TextServerAdvanced::shaped_text_set_custom_punctuation(RID p_shaped, const String &p_punct) {
 void TextServerAdvanced::shaped_text_set_custom_punctuation(RID p_shaped, const String &p_punct) {
 	_THREAD_SAFE_METHOD_
 	_THREAD_SAFE_METHOD_
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
 	ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
@@ -3582,7 +3590,11 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width,
 	float justification_width;
 	float justification_width;
 	if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) == JUSTIFICATION_CONSTRAIN_ELLIPSIS) {
 	if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) == JUSTIFICATION_CONSTRAIN_ELLIPSIS) {
 		if (sd->overrun_trim_data.trim_pos >= 0) {
 		if (sd->overrun_trim_data.trim_pos >= 0) {
-			start_pos = sd->overrun_trim_data.trim_pos;
+			if (sd->para_direction == DIRECTION_RTL) {
+				start_pos = sd->overrun_trim_data.trim_pos;
+			} else {
+				end_pos = sd->overrun_trim_data.trim_pos;
+			}
 			justification_width = sd->width_trimmed;
 			justification_width = sd->width_trimmed;
 		} else {
 		} else {
 			return sd->width;
 			return sd->width;
@@ -3592,6 +3604,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width,
 	}
 	}
 
 
 	if ((p_jst_flags & JUSTIFICATION_TRIM_EDGE_SPACES) == JUSTIFICATION_TRIM_EDGE_SPACES) {
 	if ((p_jst_flags & JUSTIFICATION_TRIM_EDGE_SPACES) == JUSTIFICATION_TRIM_EDGE_SPACES) {
+		// Trim spaces.
 		while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
 		while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
 			justification_width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat;
 			justification_width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat;
 			sd->glyphs.write[start_pos].advance = 0;
 			sd->glyphs.write[start_pos].advance = 0;
@@ -3602,6 +3615,14 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width,
 			sd->glyphs.write[end_pos].advance = 0;
 			sd->glyphs.write[end_pos].advance = 0;
 			end_pos -= sd->glyphs[end_pos].count;
 			end_pos -= sd->glyphs[end_pos].count;
 		}
 		}
+	} else {
+		// Skip breaks, but do not reset size.
+		while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
+			start_pos += sd->glyphs[start_pos].count;
+		}
+		while ((start_pos < end_pos) && ((sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
+			end_pos -= sd->glyphs[end_pos].count;
+		}
 	}
 	}
 
 
 	int space_count = 0;
 	int space_count = 0;
@@ -3610,7 +3631,10 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width,
 		const Glyph &gl = sd->glyphs[i];
 		const Glyph &gl = sd->glyphs[i];
 		if (gl.count > 0) {
 		if (gl.count > 0) {
 			if ((gl.flags & GRAPHEME_IS_ELONGATION) == GRAPHEME_IS_ELONGATION) {
 			if ((gl.flags & GRAPHEME_IS_ELONGATION) == GRAPHEME_IS_ELONGATION) {
-				elongation_count++;
+				if ((i > 0) && ((sd->glyphs[i - 1].flags & GRAPHEME_IS_ELONGATION) != GRAPHEME_IS_ELONGATION)) {
+					// Expand once per elongation sequence.
+					elongation_count++;
+				}
 			}
 			}
 			if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
 			if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
 				space_count++;
 				space_count++;
@@ -3624,12 +3648,17 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width,
 			Glyph &gl = sd->glyphs.write[i];
 			Glyph &gl = sd->glyphs.write[i];
 			if (gl.count > 0) {
 			if (gl.count > 0) {
 				if (((gl.flags & GRAPHEME_IS_ELONGATION) == GRAPHEME_IS_ELONGATION) && (gl.advance > 0)) {
 				if (((gl.flags & GRAPHEME_IS_ELONGATION) == GRAPHEME_IS_ELONGATION) && (gl.advance > 0)) {
-					int count = delta_width_per_kashida / gl.advance;
-					int prev_count = gl.repeat;
-					if ((gl.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) {
-						gl.repeat = MAX(count, 0);
+					if ((i > 0) && ((sd->glyphs[i - 1].flags & GRAPHEME_IS_ELONGATION) != GRAPHEME_IS_ELONGATION)) {
+						// Expand once per elongation sequence.
+						int count = delta_width_per_kashida / gl.advance;
+						int prev_count = gl.repeat;
+						if ((gl.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) {
+							gl.repeat = MAX(count, 0);
+						} else {
+							gl.repeat = MAX(count + 1, 1);
+						}
+						justification_width += (gl.repeat - prev_count) * gl.advance;
 					}
 					}
-					justification_width += (gl.repeat - prev_count) * gl.advance;
 				}
 				}
 			}
 			}
 		}
 		}
@@ -3671,7 +3700,7 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width,
 		sd->width = justification_width;
 		sd->width = justification_width;
 	}
 	}
 
 
-	return sd->width;
+	return justification_width;
 }
 }
 
 
 float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) {
 float TextServerAdvanced::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) {
@@ -3759,23 +3788,56 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
 		return;
 		return;
 	}
 	}
 
 
+	Vector<ShapedTextDataAdvanced::Span> &spans = sd->spans;
+	if (sd->parent != RID()) {
+		ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_COND(!parent_sd->valid);
+		spans = parent_sd->spans;
+	}
+
+	if (spans.size() == 0) {
+		return;
+	}
+
 	int sd_size = sd->glyphs.size();
 	int sd_size = sd->glyphs.size();
-	RID last_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
 	int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
 	int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
-	int32_t dot_gl_idx = font_get_glyph_index(last_gl_font_rid, last_gl_font_size, '.');
-	Vector2 dot_adv = font_get_glyph_advance(last_gl_font_rid, last_gl_font_size, dot_gl_idx);
-	int32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, last_gl_font_size, ' ');
-	Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, last_gl_font_size, whitespace_gl_idx);
+
+	// Find usable fonts, if fonts from the last glyph do not have required chars.
+	RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+	if (!font_has_char(dot_gl_font_rid, '.')) {
+		const Vector<RID> &fonts = spans[spans.size() - 1].fonts;
+		for (const RID &font : fonts) {
+			if (font_has_char(font, '.')) {
+				dot_gl_font_rid = font;
+				break;
+			}
+		}
+	}
+	RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+	if (!font_has_char(whitespace_gl_font_rid, '.')) {
+		const Vector<RID> &fonts = spans[spans.size() - 1].fonts;
+		for (const RID &font : fonts) {
+			if (font_has_char(font, ' ')) {
+				whitespace_gl_font_rid = font;
+				break;
+			}
+		}
+	}
+
+	int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, '.') : -10;
+	Vector2 dot_adv = dot_gl_font_rid.is_valid() ? font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
+	int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ') : -10;
+	Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2();
 
 
 	int ellipsis_width = 0;
 	int ellipsis_width = 0;
-	if (add_ellipsis) {
-		ellipsis_width = 3 * dot_adv.x + font_get_spacing(last_gl_font_rid, last_gl_font_size, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
+	if (add_ellipsis && whitespace_gl_font_rid.is_valid()) {
+		ellipsis_width = 3 * dot_adv.x + font_get_spacing(whitespace_gl_font_rid, last_gl_font_size, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
 	}
 	}
 
 
 	int ell_min_characters = 6;
 	int ell_min_characters = 6;
 	float width = sd->width;
 	float width = sd->width;
 
 
-	bool is_rtl = sd->direction == DIRECTION_RTL || (sd->direction == DIRECTION_AUTO && sd->para_direction == DIRECTION_RTL);
+	bool is_rtl = sd->para_direction == DIRECTION_RTL;
 
 
 	int trim_pos = (is_rtl) ? sd_size : 0;
 	int trim_pos = (is_rtl) ? sd_size : 0;
 	int ellipsis_pos = (enforce_ellipsis) ? 0 : -1;
 	int ellipsis_pos = (enforce_ellipsis) ? 0 : -1;
@@ -3833,23 +3895,25 @@ void TextServerAdvanced::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
 				gl.count = 1;
 				gl.count = 1;
 				gl.advance = whitespace_adv.x;
 				gl.advance = whitespace_adv.x;
 				gl.index = whitespace_gl_idx;
 				gl.index = whitespace_gl_idx;
-				gl.font_rid = last_gl_font_rid;
+				gl.font_rid = whitespace_gl_font_rid;
 				gl.font_size = last_gl_font_size;
 				gl.font_size = last_gl_font_size;
 				gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0);
 				gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0);
 
 
 				sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
 				sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
 			}
 			}
 			// Add ellipsis dots.
 			// Add ellipsis dots.
-			Glyph gl;
-			gl.count = 1;
-			gl.repeat = 3;
-			gl.advance = dot_adv.x;
-			gl.index = dot_gl_idx;
-			gl.font_rid = last_gl_font_rid;
-			gl.font_size = last_gl_font_size;
-			gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0);
-
-			sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
+			if (dot_gl_idx != 0) {
+				Glyph gl;
+				gl.count = 1;
+				gl.repeat = 3;
+				gl.advance = dot_adv.x;
+				gl.index = dot_gl_idx;
+				gl.font_rid = dot_gl_font_rid;
+				gl.font_size = last_gl_font_size;
+				gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL | (is_rtl ? GRAPHEME_IS_RTL : 0);
+
+				sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
+			}
 		}
 		}
 
 
 		sd->text_trimmed = true;
 		sd->text_trimmed = true;
@@ -3920,21 +3984,19 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
 			for (int j = r_start; j < r_end; j++) {
 			for (int j = r_start; j < r_end; j++) {
 				char32_t c = sd->text[j - sd->start];
 				char32_t c = sd->text[j - sd->start];
 				if (is_whitespace(c)) {
 				if (is_whitespace(c)) {
-					breaks[j] = false;
+					breaks[j + 1] = false;
 				}
 				}
 				if (is_linebreak(c)) {
 				if (is_linebreak(c)) {
-					breaks[j] = true;
+					breaks[j + 1] = true;
 				}
 				}
 			}
 			}
 		} else {
 		} else {
 			while (ubrk_next(bi) != UBRK_DONE) {
 			while (ubrk_next(bi) != UBRK_DONE) {
-				int pos = _convert_pos(sd, ubrk_current(bi)) + r_start - 1;
-				if (pos != r_end) {
-					if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) {
-						breaks[pos] = true;
-					} else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) {
-						breaks[pos] = false;
-					}
+				int pos = _convert_pos(sd, ubrk_current(bi)) + r_start;
+				if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) {
+					breaks[pos] = true;
+				} else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) {
+					breaks[pos] = false;
 				}
 				}
 			}
 			}
 		}
 		}
@@ -3978,34 +4040,46 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
 			if (is_underscore(c)) {
 			if (is_underscore(c)) {
 				sd_glyphs[i].flags |= GRAPHEME_IS_UNDERSCORE;
 				sd_glyphs[i].flags |= GRAPHEME_IS_UNDERSCORE;
 			}
 			}
-			if (breaks.has(sd->glyphs[i].start)) {
-				if (breaks[sd->glyphs[i].start]) {
+			if (breaks.has(sd_glyphs[i].end)) {
+				if (breaks[sd_glyphs[i].end] && (is_linebreak(c))) {
 					sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD;
 					sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD;
+				} else if (is_whitespace(c)) {
+					sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT;
 				} else {
 				} else {
-					if (is_whitespace(c)) {
-						sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT;
+					int count = sd_glyphs[i].count;
+					// Do not add extra space at the end of the line.
+					if (sd_glyphs[i].end == sd->end) {
+						continue;
+					}
+					// Do not add extra space after existing space.
+					if (sd_glyphs[i].flags & GRAPHEME_IS_RTL) {
+						if ((i + count < sd_size - 1) && ((sd_glyphs[i + count].flags & (GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT)) == (GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT))) {
+							continue;
+						}
 					} else {
 					} else {
-						Glyph gl;
-						gl.start = sd_glyphs[i].start;
-						gl.end = sd_glyphs[i].end;
-						gl.count = 1;
-						gl.font_rid = sd_glyphs[i].font_rid;
-						gl.font_size = sd_glyphs[i].font_size;
-						gl.flags = GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL;
-						if (sd->glyphs[i].flags & GRAPHEME_IS_RTL) {
-							gl.flags |= GRAPHEME_IS_RTL;
-							sd->glyphs.insert(i, gl); // Insert before.
-						} else {
-							sd->glyphs.insert(i + sd_glyphs[i].count, gl); // Insert after.
+						if ((i > 0) && ((sd_glyphs[i - 1].flags & (GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT)) == (GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT))) {
+							continue;
 						}
 						}
-
-						// Update write pointer and size.
-						sd_size = sd->glyphs.size();
-						sd_glyphs = sd->glyphs.ptrw();
-
-						i += sd_glyphs[i].count;
-						continue;
 					}
 					}
+					Glyph gl;
+					gl.start = sd_glyphs[i].start;
+					gl.end = sd_glyphs[i].end;
+					gl.count = 1;
+					gl.font_rid = sd_glyphs[i].font_rid;
+					gl.font_size = sd_glyphs[i].font_size;
+					gl.flags = GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL | GRAPHEME_IS_SPACE;
+					if (sd_glyphs[i].flags & GRAPHEME_IS_RTL) {
+						gl.flags |= GRAPHEME_IS_RTL;
+						sd->glyphs.insert(i, gl); // Insert before.
+					} else {
+						sd->glyphs.insert(i + count, gl); // Insert after.
+					}
+					i += count;
+
+					// Update write pointer and size.
+					sd_size = sd->glyphs.size();
+					sd_glyphs = sd->glyphs.ptrw();
+					continue;
 				}
 				}
 			}
 			}
 
 
@@ -4151,53 +4225,80 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) {
 
 
 	sd->sort_valid = false;
 	sd->sort_valid = false;
 	sd->glyphs_logical.clear();
 	sd->glyphs_logical.clear();
-	int sd_size = sd->glyphs.size();
 
 
+	Glyph *sd_glyphs = sd->glyphs.ptrw();
+	int sd_size = sd->glyphs.size();
 	if (jstops.size() > 0) {
 	if (jstops.size() > 0) {
 		for (int i = 0; i < sd_size; i++) {
 		for (int i = 0; i < sd_size; i++) {
-			if (sd->glyphs[i].count > 0) {
-				if (jstops.has(sd->glyphs[i].start)) {
-					char32_t c = sd->text[sd->glyphs[i].start - sd->start];
+			if (sd_glyphs[i].count > 0) {
+				char32_t c = sd->text[sd_glyphs[i].start - sd->start];
+				if (c == 0x0640) {
+					sd_glyphs[i].flags |= GRAPHEME_IS_ELONGATION;
+				}
+				if (jstops.has(sd_glyphs[i].start)) {
 					if (c == 0xfffc) {
 					if (c == 0xfffc) {
 						continue;
 						continue;
 					}
 					}
-					if (jstops[sd->glyphs[i].start]) {
-						if (c == 0x0640) {
-							sd->glyphs.write[i].flags |= GRAPHEME_IS_ELONGATION;
-						} else {
-							if (sd->glyphs[i].font_rid != RID()) {
+					if (jstops[sd_glyphs[i].start]) {
+						if (c != 0x0640) {
+							if (sd_glyphs[i].font_rid != RID()) {
 								Glyph gl = _shape_single_glyph(sd, 0x0640, HB_SCRIPT_ARABIC, HB_DIRECTION_RTL, sd->glyphs[i].font_rid, sd->glyphs[i].font_size);
 								Glyph gl = _shape_single_glyph(sd, 0x0640, HB_SCRIPT_ARABIC, HB_DIRECTION_RTL, sd->glyphs[i].font_rid, sd->glyphs[i].font_size);
-								if ((gl.flags & GRAPHEME_IS_VALID) == GRAPHEME_IS_VALID) {
-									gl.start = sd->glyphs[i].start;
-									gl.end = sd->glyphs[i].end;
+								if ((sd_glyphs[i].flags & GRAPHEME_IS_VALID) == GRAPHEME_IS_VALID) {
+									gl.start = sd_glyphs[i].start;
+									gl.end = sd_glyphs[i].end;
 									gl.repeat = 0;
 									gl.repeat = 0;
 									gl.count = 1;
 									gl.count = 1;
 									if (sd->orientation == ORIENTATION_HORIZONTAL) {
 									if (sd->orientation == ORIENTATION_HORIZONTAL) {
-										gl.y_off = sd->glyphs[i].y_off;
+										gl.y_off = sd_glyphs[i].y_off;
 									} else {
 									} else {
-										gl.x_off = sd->glyphs[i].x_off;
+										gl.x_off = sd_glyphs[i].x_off;
 									}
 									}
 									gl.flags |= GRAPHEME_IS_ELONGATION | GRAPHEME_IS_VIRTUAL;
 									gl.flags |= GRAPHEME_IS_ELONGATION | GRAPHEME_IS_VIRTUAL;
 									sd->glyphs.insert(i, gl);
 									sd->glyphs.insert(i, gl);
 									i++;
 									i++;
+
+									// Update write pointer and size.
+									sd_size = sd->glyphs.size();
+									sd_glyphs = sd->glyphs.ptrw();
+									continue;
 								}
 								}
 							}
 							}
 						}
 						}
-					} else if (!is_whitespace(c)) {
+					} else if ((sd_glyphs[i].flags & GRAPHEME_IS_SPACE) != GRAPHEME_IS_SPACE) {
+						int count = sd_glyphs[i].count;
+						// Do not add extra spaces at the end of the line.
+						if (sd_glyphs[i].end == sd->end) {
+							continue;
+						}
+						// Do not add extra space after existing space.
+						if (sd_glyphs[i].flags & GRAPHEME_IS_RTL) {
+							if ((i + count < sd_size - 1) && ((sd_glyphs[i + count].flags & (GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT)) == (GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT))) {
+								continue;
+							}
+						} else {
+							if ((i > 0) && ((sd_glyphs[i - 1].flags & (GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT)) == (GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT))) {
+								continue;
+							}
+						}
+						// Inject virtual space for alignment.
 						Glyph gl;
 						Glyph gl;
-						gl.start = sd->glyphs[i].start;
-						gl.end = sd->glyphs[i].end;
+						gl.start = sd_glyphs[i].start;
+						gl.end = sd_glyphs[i].end;
 						gl.count = 1;
 						gl.count = 1;
-						gl.font_rid = sd->glyphs[i].font_rid;
-						gl.font_size = sd->glyphs[i].font_size;
+						gl.font_rid = sd_glyphs[i].font_rid;
+						gl.font_size = sd_glyphs[i].font_size;
 						gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_VIRTUAL;
 						gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_VIRTUAL;
-						if (sd->glyphs[i].flags & GRAPHEME_IS_RTL) {
+						if (sd_glyphs[i].flags & GRAPHEME_IS_RTL) {
 							gl.flags |= GRAPHEME_IS_RTL;
 							gl.flags |= GRAPHEME_IS_RTL;
 							sd->glyphs.insert(i, gl); // Insert before.
 							sd->glyphs.insert(i, gl); // Insert before.
 						} else {
 						} else {
-							sd->glyphs.insert(i + sd->glyphs[i].count, gl); // Insert after.
+							sd->glyphs.insert(i + count, gl); // Insert after.
 						}
 						}
-						i += sd->glyphs[i].count;
+						i += count;
+
+						// Update write pointer and size.
+						sd_size = sd->glyphs.size();
+						sd_glyphs = sd->glyphs.ptrw();
 						continue;
 						continue;
 					}
 					}
 				}
 				}

+ 1 - 0
modules/text_server_adv/text_server_adv.h

@@ -466,6 +466,7 @@ public:
 
 
 	virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) override;
 	virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) override;
 	virtual Direction shaped_text_get_direction(RID p_shaped) const override;
 	virtual Direction shaped_text_get_direction(RID p_shaped) const override;
+	virtual Direction shaped_text_get_inferred_direction(RID p_shaped) const override;
 
 
 	virtual void shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) override;
 	virtual void shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) override;
 
 

+ 93 - 24
modules/text_server_fb/text_server_fb.cpp

@@ -2128,6 +2128,10 @@ TextServer::Direction TextServerFallback::shaped_text_get_direction(RID p_shaped
 	return TextServer::DIRECTION_LTR;
 	return TextServer::DIRECTION_LTR;
 }
 }
 
 
+TextServer::Direction TextServerFallback::shaped_text_get_inferred_direction(RID p_shaped) const {
+	return TextServer::DIRECTION_LTR;
+}
+
 void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String &p_punct) {
 void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String &p_punct) {
 	_THREAD_SAFE_METHOD_
 	_THREAD_SAFE_METHOD_
 	ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
 	ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
@@ -2631,17 +2635,38 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width,
 		}
 		}
 	}
 	}
 
 
+	float justification_width;
+	if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) == JUSTIFICATION_CONSTRAIN_ELLIPSIS) {
+		if (sd->overrun_trim_data.trim_pos >= 0) {
+			end_pos = sd->overrun_trim_data.trim_pos;
+			justification_width = sd->width_trimmed;
+		} else {
+			return sd->width;
+		}
+	} else {
+		justification_width = sd->width;
+	}
+
 	if ((p_jst_flags & JUSTIFICATION_TRIM_EDGE_SPACES) == JUSTIFICATION_TRIM_EDGE_SPACES) {
 	if ((p_jst_flags & JUSTIFICATION_TRIM_EDGE_SPACES) == JUSTIFICATION_TRIM_EDGE_SPACES) {
+		// Trim spaces.
 		while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
 		while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
-			sd->width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat;
+			justification_width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat;
 			sd->glyphs.write[start_pos].advance = 0;
 			sd->glyphs.write[start_pos].advance = 0;
 			start_pos += sd->glyphs[start_pos].count;
 			start_pos += sd->glyphs[start_pos].count;
 		}
 		}
 		while ((start_pos < end_pos) && ((sd->glyphs[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
 		while ((start_pos < end_pos) && ((sd->glyphs[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
-			sd->width -= sd->glyphs[end_pos].advance * sd->glyphs[end_pos].repeat;
+			justification_width -= sd->glyphs[end_pos].advance * sd->glyphs[end_pos].repeat;
 			sd->glyphs.write[end_pos].advance = 0;
 			sd->glyphs.write[end_pos].advance = 0;
 			end_pos -= sd->glyphs[end_pos].count;
 			end_pos -= sd->glyphs[end_pos].count;
 		}
 		}
+	} else {
+		// Skip breaks, but do not reset size.
+		while ((start_pos < end_pos) && ((sd->glyphs[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD)) {
+			start_pos += sd->glyphs[start_pos].count;
+		}
+		while ((start_pos < end_pos) && ((sd->glyphs[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD)) {
+			end_pos -= sd->glyphs[end_pos].count;
+		}
 	}
 	}
 
 
 	int space_count = 0;
 	int space_count = 0;
@@ -2655,20 +2680,28 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width,
 	}
 	}
 
 
 	if ((space_count > 0) && ((p_jst_flags & JUSTIFICATION_WORD_BOUND) == JUSTIFICATION_WORD_BOUND)) {
 	if ((space_count > 0) && ((p_jst_flags & JUSTIFICATION_WORD_BOUND) == JUSTIFICATION_WORD_BOUND)) {
-		float delta_width_per_space = (p_width - sd->width) / space_count;
+		float delta_width_per_space = (p_width - justification_width) / space_count;
 		for (int i = start_pos; i <= end_pos; i++) {
 		for (int i = start_pos; i <= end_pos; i++) {
 			Glyph &gl = sd->glyphs.write[i];
 			Glyph &gl = sd->glyphs.write[i];
 			if (gl.count > 0) {
 			if (gl.count > 0) {
 				if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
 				if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
 					float old_adv = gl.advance;
 					float old_adv = gl.advance;
 					gl.advance = MAX(gl.advance + delta_width_per_space, Math::round(0.1 * gl.font_size));
 					gl.advance = MAX(gl.advance + delta_width_per_space, Math::round(0.1 * gl.font_size));
-					sd->width += (gl.advance - old_adv);
+					justification_width += (gl.advance - old_adv);
 				}
 				}
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	return sd->width;
+	if (Math::floor(p_width) < Math::floor(justification_width)) {
+		sd->fit_width_minimum_reached = true;
+	}
+
+	if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) != JUSTIFICATION_CONSTRAIN_ELLIPSIS) {
+		sd->width = justification_width;
+	}
+
+	return justification_width;
 }
 }
 
 
 float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) {
 float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) {
@@ -2769,6 +2802,7 @@ bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) {
 				sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT;
 				sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT;
 			}
 			}
 			if (is_linebreak(c)) {
 			if (is_linebreak(c)) {
+				sd_glyphs[i].flags |= GRAPHEME_IS_SPACE;
 				sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD;
 				sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD;
 			}
 			}
 			if (c == 0x0009 || c == 0x000b) {
 			if (c == 0x0009 || c == 0x000b) {
@@ -2827,17 +2861,50 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
 		return;
 		return;
 	}
 	}
 
 
+	Vector<ShapedTextData::Span> &spans = sd->spans;
+	if (sd->parent != RID()) {
+		ShapedTextData *parent_sd = shaped_owner.get_or_null(sd->parent);
+		ERR_FAIL_COND(!parent_sd->valid);
+		spans = parent_sd->spans;
+	}
+
+	if (spans.size() == 0) {
+		return;
+	}
+
 	int sd_size = sd->glyphs.size();
 	int sd_size = sd->glyphs.size();
-	RID last_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
 	int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
 	int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
-	int32_t dot_gl_idx = font_get_glyph_index(last_gl_font_rid, '.', 0);
-	Vector2 dot_adv = font_get_glyph_advance(last_gl_font_rid, last_gl_font_size, dot_gl_idx);
-	int32_t whitespace_gl_idx = font_get_glyph_index(last_gl_font_rid, ' ', 0);
-	Vector2 whitespace_adv = font_get_glyph_advance(last_gl_font_rid, last_gl_font_size, whitespace_gl_idx);
+
+	// Find usable fonts, if fonts from the last glyph do not have required chars.
+	RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+	if (!font_has_char(dot_gl_font_rid, '.')) {
+		const Vector<RID> &fonts = spans[spans.size() - 1].fonts;
+		for (const RID &font : fonts) {
+			if (font_has_char(font, '.')) {
+				dot_gl_font_rid = font;
+				break;
+			}
+		}
+	}
+	RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
+	if (!font_has_char(whitespace_gl_font_rid, '.')) {
+		const Vector<RID> &fonts = spans[spans.size() - 1].fonts;
+		for (const RID &font : fonts) {
+			if (font_has_char(font, ' ')) {
+				whitespace_gl_font_rid = font;
+				break;
+			}
+		}
+	}
+
+	int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, '.') : -10;
+	Vector2 dot_adv = dot_gl_font_rid.is_valid() ? font_get_glyph_advance(dot_gl_font_rid, last_gl_font_size, dot_gl_idx) : Vector2();
+	int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ') : -10;
+	Vector2 whitespace_adv = whitespace_gl_font_rid.is_valid() ? font_get_glyph_advance(whitespace_gl_font_rid, last_gl_font_size, whitespace_gl_idx) : Vector2();
 
 
 	int ellipsis_width = 0;
 	int ellipsis_width = 0;
-	if (add_ellipsis) {
-		ellipsis_width = 3 * dot_adv.x + font_get_spacing(last_gl_font_rid, last_gl_font_size, TextServer::SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
+	if (add_ellipsis && whitespace_gl_font_rid.is_valid()) {
+		ellipsis_width = 3 * dot_adv.x + font_get_spacing(whitespace_gl_font_rid, last_gl_font_size, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
 	}
 	}
 
 
 	int ell_min_characters = 6;
 	int ell_min_characters = 6;
@@ -2891,23 +2958,25 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
 				gl.count = 1;
 				gl.count = 1;
 				gl.advance = whitespace_adv.x;
 				gl.advance = whitespace_adv.x;
 				gl.index = whitespace_gl_idx;
 				gl.index = whitespace_gl_idx;
-				gl.font_rid = last_gl_font_rid;
+				gl.font_rid = whitespace_gl_font_rid;
 				gl.font_size = last_gl_font_size;
 				gl.font_size = last_gl_font_size;
 				gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL;
 				gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL;
 
 
 				sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
 				sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
 			}
 			}
 			// Add ellipsis dots.
 			// Add ellipsis dots.
-			Glyph gl;
-			gl.count = 1;
-			gl.repeat = 3;
-			gl.advance = dot_adv.x;
-			gl.index = dot_gl_idx;
-			gl.font_rid = last_gl_font_rid;
-			gl.font_size = last_gl_font_size;
-			gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL;
+			if (dot_gl_idx != 0) {
+				Glyph gl;
+				gl.count = 1;
+				gl.repeat = 3;
+				gl.advance = dot_adv.x;
+				gl.index = dot_gl_idx;
+				gl.font_rid = dot_gl_font_rid;
+				gl.font_size = last_gl_font_size;
+				gl.flags = GRAPHEME_IS_PUNCTUATION | GRAPHEME_IS_VIRTUAL;
 
 
-			sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
+				sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
+			}
 		}
 		}
 
 
 		sd->text_trimmed = true;
 		sd->text_trimmed = true;
@@ -3023,13 +3092,13 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
 				if (gl.font_rid.is_valid()) {
 				if (gl.font_rid.is_valid()) {
 					if (sd->text[j - sd->start] != 0 && !is_linebreak(sd->text[j - sd->start])) {
 					if (sd->text[j - sd->start] != 0 && !is_linebreak(sd->text[j - sd->start])) {
 						if (sd->orientation == ORIENTATION_HORIZONTAL) {
 						if (sd->orientation == ORIENTATION_HORIZONTAL) {
-							gl.advance = font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x;
+							gl.advance = Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x);
 							gl.x_off = 0;
 							gl.x_off = 0;
 							gl.y_off = 0;
 							gl.y_off = 0;
 							sd->ascent = MAX(sd->ascent, font_get_ascent(gl.font_rid, gl.font_size));
 							sd->ascent = MAX(sd->ascent, font_get_ascent(gl.font_rid, gl.font_size));
 							sd->descent = MAX(sd->descent, font_get_descent(gl.font_rid, gl.font_size));
 							sd->descent = MAX(sd->descent, font_get_descent(gl.font_rid, gl.font_size));
 						} else {
 						} else {
-							gl.advance = font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).y;
+							gl.advance = Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).y);
 							gl.x_off = -Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5);
 							gl.x_off = -Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5);
 							gl.y_off = font_get_ascent(gl.font_rid, gl.font_size);
 							gl.y_off = font_get_ascent(gl.font_rid, gl.font_size);
 							sd->ascent = MAX(sd->ascent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));
 							sd->ascent = MAX(sd->ascent, Math::round(font_get_glyph_advance(gl.font_rid, gl.font_size, gl.index).x * 0.5));

+ 1 - 0
modules/text_server_fb/text_server_fb.h

@@ -375,6 +375,7 @@ public:
 
 
 	virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) override;
 	virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) override;
 	virtual Direction shaped_text_get_direction(RID p_shaped) const override;
 	virtual Direction shaped_text_get_direction(RID p_shaped) const override;
+	virtual Direction shaped_text_get_inferred_direction(RID p_shaped) const override;
 
 
 	virtual void shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) override;
 	virtual void shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) override;
 
 

+ 23 - 25
scene/gui/label.cpp

@@ -183,11 +183,9 @@ void Label::_shape() {
 						TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
 						TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
 					}
 					}
 				}
 				}
-
 			} else if (lines_hidden) {
 			} else if (lines_hidden) {
 				TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
 				TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
 			}
 			}
-
 		} else {
 		} else {
 			// Autowrap disabled.
 			// Autowrap disabled.
 			for (int i = 0; i < lines_rid.size(); i++) {
 			for (int i = 0; i < lines_rid.size(); i++) {
@@ -294,7 +292,7 @@ void Label::_notification(int p_what) {
 		Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
 		Color font_outline_color = get_theme_color(SNAME("font_outline_color"));
 		int outline_size = get_theme_constant(SNAME("outline_size"));
 		int outline_size = get_theme_constant(SNAME("outline_size"));
 		int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size"));
 		int shadow_outline_size = get_theme_constant(SNAME("shadow_outline_size"));
-		bool rtl = TS->shaped_text_get_direction(text_rid);
+		bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL);
 		bool rtl_layout = is_layout_rtl();
 		bool rtl_layout = is_layout_rtl();
 
 
 		style->draw(ci, Rect2(Point2(0, 0), get_size()));
 		style->draw(ci, Rect2(Point2(0, 0), get_size()));
@@ -422,19 +420,19 @@ void Label::_notification(int p_what) {
 
 
 				// Draw main text.
 				// Draw main text.
 				for (int j = 0; j < gl_size; j++) {
 				for (int j = 0; j < gl_size; j++) {
-					for (int k = 0; k < glyphs[j].repeat; k++) {
-						// Trim when necessary.
-						if (trim_pos >= 0) {
-							if (rtl) {
-								if (j < trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
-									continue;
-								}
-							} else {
-								if (j >= trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
-									break;
-								}
+					// Trim when necessary.
+					if (trim_pos >= 0) {
+						if (rtl) {
+							if (j < trim_pos) {
+								continue;
+							}
+						} else {
+							if (j >= trim_pos) {
+								break;
 							}
 							}
 						}
 						}
+					}
+					for (int k = 0; k < glyphs[j].repeat; k++) {
 						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));
 						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.
@@ -480,19 +478,19 @@ void Label::_notification(int p_what) {
 
 
 			// Draw main text.
 			// Draw main text.
 			for (int j = 0; j < gl_size; j++) {
 			for (int j = 0; j < gl_size; j++) {
-				for (int k = 0; k < glyphs[j].repeat; k++) {
-					// Trim when necessary.
-					if (trim_pos >= 0) {
-						if (rtl) {
-							if (j < trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
-								continue;
-							}
-						} else {
-							if (j >= trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
-								break;
-							}
+				// Trim when necessary.
+				if (trim_pos >= 0) {
+					if (rtl) {
+						if (j < trim_pos) {
+							continue;
+						}
+					} else {
+						if (j >= trim_pos) {
+							break;
 						}
 						}
 					}
 					}
+				}
+				for (int k = 0; k < glyphs[j].repeat; k++) {
 					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));
 					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.

+ 10 - 10
scene/resources/text_paragraph.cpp

@@ -561,7 +561,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo
 	if (h_offset > 0) {
 	if (h_offset > 0) {
 		// Draw dropcap.
 		// Draw dropcap.
 		Vector2 dc_off = ofs;
 		Vector2 dc_off = ofs;
-		if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
+		if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
 			if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
 			if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
 				dc_off.x += width - h_offset;
 				dc_off.x += width - h_offset;
 			} else {
 			} else {
@@ -579,7 +579,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo
 			ofs.x = p_pos.x;
 			ofs.x = p_pos.x;
 			ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
 			ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
 			if (i <= dropcap_lines) {
 			if (i <= dropcap_lines) {
-				if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
+				if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
 					ofs.x -= h_offset;
 					ofs.x -= h_offset;
 				}
 				}
 				l_width -= h_offset;
 				l_width -= h_offset;
@@ -588,7 +588,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo
 			ofs.y = p_pos.y;
 			ofs.y = p_pos.y;
 			ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
 			ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
 			if (i <= dropcap_lines) {
 			if (i <= dropcap_lines) {
-				if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
+				if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
 					ofs.x -= h_offset;
 					ofs.x -= h_offset;
 				}
 				}
 				l_width -= h_offset;
 				l_width -= h_offset;
@@ -598,7 +598,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo
 		if (width > 0) {
 		if (width > 0) {
 			switch (alignment) {
 			switch (alignment) {
 				case HORIZONTAL_ALIGNMENT_FILL:
 				case HORIZONTAL_ALIGNMENT_FILL:
-					if (TS->shaped_text_get_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
+					if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
 						if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
 						if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
 							ofs.x += l_width - line_width;
 							ofs.x += l_width - line_width;
 						} else {
 						} else {
@@ -655,7 +655,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli
 	if (h_offset > 0) {
 	if (h_offset > 0) {
 		// Draw dropcap.
 		// Draw dropcap.
 		Vector2 dc_off = ofs;
 		Vector2 dc_off = ofs;
-		if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
+		if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
 			if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
 			if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
 				dc_off.x += width - h_offset;
 				dc_off.x += width - h_offset;
 			} else {
 			} else {
@@ -671,7 +671,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli
 			ofs.x = p_pos.x;
 			ofs.x = p_pos.x;
 			ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
 			ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
 			if (i <= dropcap_lines) {
 			if (i <= dropcap_lines) {
-				if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
+				if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
 					ofs.x -= h_offset;
 					ofs.x -= h_offset;
 				}
 				}
 				l_width -= h_offset;
 				l_width -= h_offset;
@@ -680,7 +680,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli
 			ofs.y = p_pos.y;
 			ofs.y = p_pos.y;
 			ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
 			ofs.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
 			if (i <= dropcap_lines) {
 			if (i <= dropcap_lines) {
-				if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
+				if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) {
 					ofs.x -= h_offset;
 					ofs.x -= h_offset;
 				}
 				}
 				l_width -= h_offset;
 				l_width -= h_offset;
@@ -690,7 +690,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli
 		if (width > 0) {
 		if (width > 0) {
 			switch (alignment) {
 			switch (alignment) {
 				case HORIZONTAL_ALIGNMENT_FILL:
 				case HORIZONTAL_ALIGNMENT_FILL:
-					if (TS->shaped_text_get_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
+					if (TS->shaped_text_get_inferred_direction(lines_rid[i]) == TextServer::DIRECTION_RTL) {
 						if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
 						if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) {
 							ofs.x += l_width - length;
 							ofs.x += l_width - length;
 						} else {
 						} else {
@@ -772,7 +772,7 @@ void TextParagraph::draw_dropcap(RID p_canvas, const Vector2 &p_pos, const Color
 
 
 	if (h_offset > 0) {
 	if (h_offset > 0) {
 		// Draw dropcap.
 		// Draw dropcap.
-		if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
+		if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
 			if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
 			if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
 				ofs.x += width - h_offset;
 				ofs.x += width - h_offset;
 			} else {
 			} else {
@@ -794,7 +794,7 @@ void TextParagraph::draw_dropcap_outline(RID p_canvas, const Vector2 &p_pos, int
 
 
 	if (h_offset > 0) {
 	if (h_offset > 0) {
 		// Draw dropcap.
 		// Draw dropcap.
-		if (TS->shaped_text_get_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
+		if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_RTL) {
 			if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
 			if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) {
 				ofs.x += width - h_offset;
 				ofs.x += width - h_offset;
 			} else {
 			} else {

+ 9 - 0
servers/text/text_server_extension.cpp

@@ -194,6 +194,7 @@ void TextServerExtension::_bind_methods() {
 
 
 	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");
+	GDVIRTUAL_BIND(_shaped_text_get_inferred_direction, "shaped");
 
 
 	GDVIRTUAL_BIND(_shaped_text_set_bidi_override, "shaped", "override");
 	GDVIRTUAL_BIND(_shaped_text_set_bidi_override, "shaped", "override");
 
 
@@ -954,6 +955,14 @@ TextServer::Direction TextServerExtension::shaped_text_get_direction(RID p_shape
 	return TextServer::Direction::DIRECTION_AUTO;
 	return TextServer::Direction::DIRECTION_AUTO;
 }
 }
 
 
+TextServer::Direction TextServerExtension::shaped_text_get_inferred_direction(RID p_shaped) const {
+	int ret;
+	if (GDVIRTUAL_CALL(_shaped_text_get_inferred_direction, p_shaped, ret)) {
+		return (TextServer::Direction)ret;
+	}
+	return TextServer::Direction::DIRECTION_LTR;
+}
+
 void TextServerExtension::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) {
 void TextServerExtension::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) {
 	GDVIRTUAL_CALL(_shaped_text_set_orientation, p_shaped, p_orientation);
 	GDVIRTUAL_CALL(_shaped_text_set_orientation, p_shaped, p_orientation);
 }
 }

+ 2 - 0
servers/text/text_server_extension.h

@@ -315,8 +315,10 @@ public:
 
 
 	virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) override;
 	virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) override;
 	virtual Direction shaped_text_get_direction(RID p_shaped) const override;
 	virtual Direction shaped_text_get_direction(RID p_shaped) const override;
+	virtual Direction shaped_text_get_inferred_direction(RID p_shaped) const override;
 	GDVIRTUAL2(_shaped_text_set_direction, RID, Direction);
 	GDVIRTUAL2(_shaped_text_set_direction, RID, Direction);
 	GDVIRTUAL1RC(/*Direction*/ int, _shaped_text_get_direction, RID);
 	GDVIRTUAL1RC(/*Direction*/ int, _shaped_text_get_direction, RID);
+	GDVIRTUAL1RC(/*Direction*/ int, _shaped_text_get_inferred_direction, RID);
 
 
 	virtual void shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) override;
 	virtual void shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) override;
 	GDVIRTUAL2(_shaped_text_set_bidi_override, RID, const Array &);
 	GDVIRTUAL2(_shaped_text_set_bidi_override, RID, const Array &);

+ 24 - 23
servers/text_server.cpp

@@ -347,6 +347,7 @@ void TextServer::_bind_methods() {
 
 
 	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);
+	ClassDB::bind_method(D_METHOD("shaped_text_get_inferred_direction", "shaped"), &TextServer::shaped_text_get_inferred_direction);
 
 
 	ClassDB::bind_method(D_METHOD("shaped_text_set_bidi_override", "shaped", "override"), &TextServer::shaped_text_set_bidi_override);
 	ClassDB::bind_method(D_METHOD("shaped_text_set_bidi_override", "shaped", "override"), &TextServer::shaped_text_set_bidi_override);
 
 
@@ -1224,6 +1225,17 @@ void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_p
 	}
 	}
 	// Draw at the baseline.
 	// Draw at the baseline.
 	for (int i = 0; i < v_size; i++) {
 	for (int i = 0; i < v_size; i++) {
+		if (trim_pos >= 0) {
+			if (rtl) {
+				if (i < trim_pos) {
+					continue;
+				}
+			} else {
+				if (i >= trim_pos) {
+					break;
+				}
+			}
+		}
 		for (int j = 0; j < glyphs[i].repeat; j++) {
 		for (int j = 0; j < glyphs[i].repeat; j++) {
 			if (p_clip_r > 0) {
 			if (p_clip_r > 0) {
 				// Clip right / bottom.
 				// Clip right / bottom.
@@ -1251,17 +1263,6 @@ void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_p
 					}
 					}
 				}
 				}
 			}
 			}
-			if (trim_pos >= 0) {
-				if (rtl) {
-					if (i < trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
-						continue;
-					}
-				} else {
-					if (i >= trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
-						break;
-					}
-				}
-			}
 
 
 			if (glyphs[i].font_rid != RID()) {
 			if (glyphs[i].font_rid != RID()) {
 				font_draw_glyph(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
 				font_draw_glyph(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
@@ -1293,7 +1294,7 @@ void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_p
 void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l, float p_clip_r, int p_outline_size, const Color &p_color) const {
 void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vector2 &p_pos, float p_clip_l, float p_clip_r, int p_outline_size, const Color &p_color) const {
 	TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped);
 	TextServer::Orientation orientation = shaped_text_get_orientation(p_shaped);
 
 
-	bool rtl = (shaped_text_get_direction(p_shaped) == DIRECTION_RTL);
+	bool rtl = (shaped_text_get_inferred_direction(p_shaped) == DIRECTION_RTL);
 
 
 	int ellipsis_pos = shaped_text_get_ellipsis_pos(p_shaped);
 	int ellipsis_pos = shaped_text_get_ellipsis_pos(p_shaped);
 	int trim_pos = shaped_text_get_trim_pos(p_shaped);
 	int trim_pos = shaped_text_get_trim_pos(p_shaped);
@@ -1319,6 +1320,17 @@ void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vect
 	}
 	}
 	// Draw at the baseline.
 	// Draw at the baseline.
 	for (int i = 0; i < v_size; i++) {
 	for (int i = 0; i < v_size; i++) {
+		if (trim_pos >= 0) {
+			if (rtl) {
+				if (i < trim_pos) {
+					continue;
+				}
+			} else {
+				if (i >= trim_pos) {
+					break;
+				}
+			}
+		}
 		for (int j = 0; j < glyphs[i].repeat; j++) {
 		for (int j = 0; j < glyphs[i].repeat; j++) {
 			if (p_clip_r > 0) {
 			if (p_clip_r > 0) {
 				// Clip right / bottom.
 				// Clip right / bottom.
@@ -1346,17 +1358,6 @@ void TextServer::shaped_text_draw_outline(RID p_shaped, RID p_canvas, const Vect
 					}
 					}
 				}
 				}
 			}
 			}
-			if (trim_pos >= 0) {
-				if (rtl) {
-					if (i < trim_pos) {
-						continue;
-					}
-				} else {
-					if (i >= trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
-						break;
-					}
-				}
-			}
 			if (glyphs[i].font_rid != RID()) {
 			if (glyphs[i].font_rid != RID()) {
 				font_draw_glyph_outline(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, p_outline_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
 				font_draw_glyph_outline(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, p_outline_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color);
 			}
 			}

+ 1 - 0
servers/text_server.h

@@ -370,6 +370,7 @@ public:
 
 
 	virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) = 0;
 	virtual void shaped_text_set_direction(RID p_shaped, Direction p_direction = DIRECTION_AUTO) = 0;
 	virtual Direction shaped_text_get_direction(RID p_shaped) const = 0;
 	virtual Direction shaped_text_get_direction(RID p_shaped) const = 0;
+	virtual Direction shaped_text_get_inferred_direction(RID p_shaped) const = 0;
 
 
 	virtual void shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) = 0;
 	virtual void shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) = 0;
 
 

+ 206 - 0
tests/servers/test_text_server.h

@@ -154,6 +154,212 @@ TEST_SUITE("[[TextServer]") {
 			}
 			}
 		}
 		}
 
 
+		SUBCASE("[TextServer] Text layout: Line break and align points") {
+			for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
+				Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
+				TEST_FAIL_COND(ts.is_null(), "Invalid TS interface.");
+
+				RID font1 = ts->create_font();
+				ts->font_set_data_ptr(font1, _font_NotoSans_Regular, _font_NotoSans_Regular_size);
+				RID font2 = ts->create_font();
+				ts->font_set_data_ptr(font2, _font_NotoSansThaiUI_Regular, _font_NotoSansThaiUI_Regular_size);
+				RID font3 = ts->create_font();
+				ts->font_set_data_ptr(font3, _font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size);
+
+				Vector<RID> font;
+				font.push_back(font1);
+				font.push_back(font2);
+				font.push_back(font3);
+
+				{
+					String test = U"Test test long text long text\n";
+					RID ctx = ts->create_shaped_text();
+					TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+					bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
+					TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+					ts->shaped_text_update_breaks(ctx);
+					ts->shaped_text_update_justification_ops(ctx);
+
+					const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
+					int gl_size = ts->shaped_text_get_glyph_count(ctx);
+
+					TEST_FAIL_COND(gl_size != 30, "Invalid glyph count.");
+					for (int j = 0; j < gl_size; j++) {
+						bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
+						bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
+						bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
+						bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
+						bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
+						if (j == 4 || j == 9 || j == 14 || j == 19 || j == 24) {
+							TEST_FAIL_COND((!soft || !space || hard || virt || elo), "Invalid glyph flags.");
+						} else if (j == 29) {
+							TEST_FAIL_COND((soft || !space || !hard || virt || elo), "Invalid glyph flags.");
+						} else {
+							TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags.");
+						}
+					}
+					ts->free(ctx);
+				}
+
+				{
+					String test = U"الحمـد";
+					RID ctx = ts->create_shaped_text();
+					TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+					bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
+					TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+					ts->shaped_text_update_breaks(ctx);
+
+					const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
+					int gl_size = ts->shaped_text_get_glyph_count(ctx);
+					TEST_FAIL_COND(gl_size != 6, "Invalid glyph count.");
+					for (int j = 0; j < gl_size; j++) {
+						bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
+						bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
+						bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
+						bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
+						bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
+						TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags.");
+					}
+
+					if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {
+						ts->shaped_text_update_justification_ops(ctx);
+
+						glyphs = ts->shaped_text_get_glyphs(ctx);
+						gl_size = ts->shaped_text_get_glyph_count(ctx);
+
+						TEST_FAIL_COND(gl_size != 6, "Invalid glyph count.");
+						for (int j = 0; j < gl_size; j++) {
+							bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
+							bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
+							bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
+							bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
+							bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
+							if (j == 1) {
+								TEST_FAIL_COND((soft || space || hard || virt || !elo), "Invalid glyph flags.");
+							} else {
+								TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags.");
+							}
+						}
+					}
+					ts->free(ctx);
+				}
+
+				{
+					String test = U"الحمـد الرياضي العربي";
+					RID ctx = ts->create_shaped_text();
+					TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+					bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
+					TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+					ts->shaped_text_update_breaks(ctx);
+
+					const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
+					int gl_size = ts->shaped_text_get_glyph_count(ctx);
+
+					TEST_FAIL_COND(gl_size != 21, "Invalid glyph count.");
+					for (int j = 0; j < gl_size; j++) {
+						bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
+						bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
+						bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
+						bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
+						bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
+						if (j == 6 || j == 14) {
+							TEST_FAIL_COND((!soft || !space || hard || virt || elo), "Invalid glyph flags.");
+						} else {
+							TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags.");
+						}
+					}
+
+					if (ts->has_feature(TextServer::FEATURE_KASHIDA_JUSTIFICATION)) {
+						ts->shaped_text_update_justification_ops(ctx);
+
+						glyphs = ts->shaped_text_get_glyphs(ctx);
+						gl_size = ts->shaped_text_get_glyph_count(ctx);
+
+						TEST_FAIL_COND(gl_size != 23, "Invalid glyph count.");
+						for (int j = 0; j < gl_size; j++) {
+							bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
+							bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
+							bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
+							bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
+							bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
+							if (j == 7 || j == 16) {
+								TEST_FAIL_COND((!soft || !space || hard || virt || elo), "Invalid glyph flags.");
+							} else if (j == 3 || j == 9) {
+								TEST_FAIL_COND((soft || space || hard || !virt || !elo), "Invalid glyph flags.");
+							} else if (j == 18) {
+								TEST_FAIL_COND((soft || space || hard || virt || !elo), "Invalid glyph flags.");
+							} else {
+								TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags.");
+							}
+						}
+					}
+
+					ts->free(ctx);
+				}
+
+				{
+					String test = U"เป็น ภาษา ราชการ และ ภาษา";
+					RID ctx = ts->create_shaped_text();
+					TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+					bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
+					TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+					ts->shaped_text_update_breaks(ctx);
+					ts->shaped_text_update_justification_ops(ctx);
+
+					const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
+					int gl_size = ts->shaped_text_get_glyph_count(ctx);
+
+					TEST_FAIL_COND(gl_size != 25, "Invalid glyph count.");
+					for (int j = 0; j < gl_size; j++) {
+						bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
+						bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
+						bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
+						bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
+						bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
+						if (j == 4 || j == 9 || j == 16 || j == 20) {
+							TEST_FAIL_COND((!soft || !space || hard || virt || elo), "Invalid glyph flags.");
+						} else {
+							TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags.");
+						}
+					}
+					ts->free(ctx);
+				}
+
+				if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) {
+					String test = U"เป็นภาษาราชการและภาษา";
+					RID ctx = ts->create_shaped_text();
+					TEST_FAIL_COND(ctx == RID(), "Creating text buffer failed.");
+					bool ok = ts->shaped_text_add_string(ctx, test, font, 16);
+					TEST_FAIL_COND(!ok, "Adding text to the buffer failed.");
+					ts->shaped_text_update_breaks(ctx);
+					ts->shaped_text_update_justification_ops(ctx);
+
+					const Glyph *glyphs = ts->shaped_text_get_glyphs(ctx);
+					int gl_size = ts->shaped_text_get_glyph_count(ctx);
+
+					TEST_FAIL_COND(gl_size != 25, "Invalid glyph count.");
+					for (int j = 0; j < gl_size; j++) {
+						bool hard = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_HARD) == TextServer::GRAPHEME_IS_BREAK_HARD;
+						bool soft = (glyphs[j].flags & TextServer::GRAPHEME_IS_BREAK_SOFT) == TextServer::GRAPHEME_IS_BREAK_SOFT;
+						bool space = (glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE;
+						bool virt = (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) == TextServer::GRAPHEME_IS_VIRTUAL;
+						bool elo = (glyphs[j].flags & TextServer::GRAPHEME_IS_ELONGATION) == TextServer::GRAPHEME_IS_ELONGATION;
+						if (j == 4 || j == 9 || j == 16 || j == 20) {
+							TEST_FAIL_COND((!soft || !space || hard || !virt || elo), "Invalid glyph flags.");
+						} else {
+							TEST_FAIL_COND((soft || space || hard || virt || elo), "Invalid glyph flags.");
+						}
+					}
+					ts->free(ctx);
+				}
+
+				for (int j = 0; j < font.size(); j++) {
+					ts->free(font[j]);
+				}
+				font.clear();
+			}
+		}
+
 		SUBCASE("[TextServer] Text layout: Line breaking") {
 		SUBCASE("[TextServer] Text layout: Line breaking") {
 			for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
 			for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
 				Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);
 				Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);