Prechádzať zdrojové kódy

Merge pull request #99488 from bruvzg/te_brk_cases

[TextServer] Fix some line breaking edge cases.
Rémi Verschelde 8 mesiacov pred
rodič
commit
5836a24a1c

+ 126 - 25
servers/text_server.cpp

@@ -827,7 +827,12 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 			continue;
 		}
 		if (l_gl[i].count > 0) {
-			if ((l_width > 0) && (width + l_gl[i].advance > l_width) && (last_safe_break >= 0)) {
+			float adv = 0.0;
+			for (int j = i; j < l_size && l_gl[i].end == l_gl[j].end && l_gl[i].start == l_gl[j].start; j++) {
+				adv += l_gl[j].advance * l_gl[j].repeat;
+			}
+			if ((l_width > 0) && (width + adv > l_width) && (last_safe_break >= 0)) {
+				int cur_safe_brk = last_safe_break;
 				if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) {
 					int start_pos = prev_safe_break;
 					int end_pos = last_safe_break;
@@ -840,6 +845,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 					if (last_end <= l_gl[start_pos].start) {
 						lines.push_back(l_gl[start_pos].start);
 						lines.push_back(l_gl[end_pos].end);
+						cur_safe_brk = end_pos;
 						last_end = l_gl[end_pos].end;
 					}
 					trim_next = true;
@@ -850,9 +856,12 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 						last_end = l_gl[last_safe_break].end;
 					}
 				}
-				line_start = l_gl[last_safe_break].end;
-				prev_safe_break = last_safe_break + 1;
-				i = last_safe_break;
+				line_start = l_gl[cur_safe_brk].end;
+				prev_safe_break = cur_safe_brk + 1;
+				while (prev_safe_break < l_size && l_gl[prev_safe_break].end == line_start) {
+					prev_safe_break++;
+				}
+				i = cur_safe_brk;
 				last_safe_break = -1;
 				width = 0;
 				word_count = 0;
@@ -867,6 +876,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 			}
 			if (p_break_flags.has_flag(BREAK_MANDATORY)) {
 				if ((l_gl[i].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD) {
+					int cur_safe_brk = i;
 					if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) {
 						int start_pos = prev_safe_break;
 						int end_pos = i;
@@ -880,6 +890,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 							lines.push_back(l_gl[start_pos].start);
 							lines.push_back(l_gl[end_pos].end);
 							last_end = l_gl[end_pos].end;
+							cur_safe_brk = end_pos;
 						}
 						trim_next = false;
 					} else {
@@ -889,8 +900,11 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 							last_end = l_gl[i].end;
 						}
 					}
-					line_start = l_gl[i].end;
-					prev_safe_break = i + 1;
+					line_start = l_gl[cur_safe_brk].end;
+					prev_safe_break = cur_safe_brk + 1;
+					while (prev_safe_break < l_size && l_gl[prev_safe_break].end == line_start) {
+						prev_safe_break++;
+					}
 					last_safe_break = -1;
 					width = 0;
 					chunk = 0;
@@ -905,7 +919,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 					if ((l_gl[i].flags & GRAPHEME_IS_SOFT_HYPHEN) == GRAPHEME_IS_SOFT_HYPHEN) {
 						uint32_t gl = font_get_glyph_index(l_gl[i].font_rid, l_gl[i].font_size, 0x00ad, 0);
 						float w = font_get_glyph_advance(l_gl[i].font_rid, l_gl[i].font_size, gl)[(orientation == ORIENTATION_HORIZONTAL) ? 0 : 1];
-						if (width + l_gl[i].advance + w <= p_width[chunk]) {
+						if (width + adv + w <= p_width[chunk]) {
 							last_safe_break = i;
 							word_count++;
 						}
@@ -919,20 +933,24 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped
 				last_safe_break = i;
 			}
 		}
-		width += l_gl[i].advance;
+		width += l_gl[i].advance * l_gl[i].repeat;
 	}
 
 	if (l_size > 0) {
 		if (lines.size() == 0 || (lines[lines.size() - 1] < range.y && prev_safe_break < l_size)) {
 			if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) {
 				int start_pos = (prev_safe_break < l_size) ? prev_safe_break : l_size - 1;
-				int end_pos = l_size - 1;
-				while (trim_next && (start_pos < end_pos) && ((l_gl[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
-					start_pos += l_gl[start_pos].count;
+				if (last_end <= l_gl[start_pos].start) {
+					int end_pos = l_size - 1;
+					while (trim_next && (start_pos < end_pos) && ((l_gl[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
+						start_pos += l_gl[start_pos].count;
+					}
+					lines.push_back(l_gl[start_pos].start);
+				} else {
+					lines.push_back(last_end);
 				}
-				lines.push_back(l_gl[start_pos].start);
 			} else {
-				lines.push_back(line_start);
+				lines.push_back(MAX(last_end, line_start));
 			}
 			lines.push_back(range.y);
 		}
@@ -980,7 +998,12 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 			continue;
 		}
 		if (l_gl[i].count > 0) {
-			if ((l_width > 0) && (width + l_gl[i].advance * l_gl[i].repeat > l_width) && (last_safe_break >= 0)) {
+			float adv = 0.0;
+			for (int j = i; j < l_size && l_gl[i].end == l_gl[j].end && l_gl[i].start == l_gl[j].start; j++) {
+				adv += l_gl[j].advance * l_gl[j].repeat;
+			}
+			if ((l_width > 0) && (width + adv > l_width) && (last_safe_break >= 0)) {
+				int cur_safe_brk = last_safe_break;
 				if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) {
 					int start_pos = prev_safe_break;
 					int end_pos = last_safe_break;
@@ -996,6 +1019,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 						if (p_width > indent) {
 							l_width = p_width - indent;
 						}
+						cur_safe_brk = end_pos;
 						last_end = l_gl[end_pos].end;
 					}
 					trim_next = true;
@@ -1009,9 +1033,12 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 						last_end = l_gl[last_safe_break].end;
 					}
 				}
-				line_start = l_gl[last_safe_break].end;
-				prev_safe_break = last_safe_break + 1;
-				i = last_safe_break;
+				line_start = l_gl[cur_safe_brk].end;
+				prev_safe_break = cur_safe_brk + 1;
+				while (prev_safe_break < l_size && l_gl[prev_safe_break].end == line_start) {
+					prev_safe_break++;
+				}
+				i = cur_safe_brk;
 				last_safe_break = -1;
 				width = 0;
 				word_count = 0;
@@ -1019,6 +1046,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 			}
 			if (p_break_flags.has_flag(BREAK_MANDATORY)) {
 				if ((l_gl[i].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD) {
+					int cur_safe_brk = i;
 					if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) {
 						int start_pos = prev_safe_break;
 						int end_pos = i;
@@ -1036,6 +1064,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 								l_width = p_width - indent;
 							}
 							last_end = l_gl[end_pos].end;
+							cur_safe_brk = end_pos;
 						}
 					} else {
 						if (last_end <= line_start) {
@@ -1047,8 +1076,11 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 							last_end = l_gl[i].end;
 						}
 					}
-					line_start = l_gl[i].end;
-					prev_safe_break = i + 1;
+					line_start = l_gl[cur_safe_brk].end;
+					prev_safe_break = cur_safe_brk + 1;
+					while (prev_safe_break < l_size && l_gl[prev_safe_break].end == line_start) {
+						prev_safe_break++;
+					}
 					last_safe_break = -1;
 					width = 0;
 					continue;
@@ -1059,7 +1091,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 					if ((l_gl[i].flags & GRAPHEME_IS_SOFT_HYPHEN) == GRAPHEME_IS_SOFT_HYPHEN) {
 						uint32_t gl = font_get_glyph_index(l_gl[i].font_rid, l_gl[i].font_size, 0x00AD, 0);
 						float w = font_get_glyph_advance(l_gl[i].font_rid, l_gl[i].font_size, gl)[(orientation == ORIENTATION_HORIZONTAL) ? 0 : 1];
-						if (width + l_gl[i].advance + w <= p_width) {
+						if (width + adv + w <= p_width) {
 							last_safe_break = i;
 							word_count++;
 						}
@@ -1083,13 +1115,17 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do
 		if (lines.size() == 0 || (lines[lines.size() - 1] < range.y && prev_safe_break < l_size)) {
 			if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) {
 				int start_pos = (prev_safe_break < l_size) ? prev_safe_break : l_size - 1;
-				int end_pos = l_size - 1;
-				while (trim_next && (start_pos < end_pos) && ((l_gl[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
-					start_pos += l_gl[start_pos].count;
+				if (last_end <= l_gl[start_pos].start) {
+					int end_pos = l_size - 1;
+					while (trim_next && (start_pos < end_pos) && ((l_gl[start_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[start_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[start_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) {
+						start_pos += l_gl[start_pos].count;
+					}
+					lines.push_back(l_gl[start_pos].start);
+				} else {
+					lines.push_back(last_end);
 				}
-				lines.push_back(l_gl[start_pos].start);
 			} else {
-				lines.push_back(line_start);
+				lines.push_back(MAX(last_end, line_start));
 			}
 			lines.push_back(range.y);
 		}
@@ -1808,6 +1844,71 @@ void TextServer::shaped_text_draw_outline(const RID &p_shaped, const RID &p_canv
 	}
 }
 
+#ifdef DEBUG_ENABLED
+
+void TextServer::debug_print_glyph(int p_idx, const Glyph &p_glyph) const {
+	String flags;
+	if (p_glyph.flags & GRAPHEME_IS_VALID) {
+		flags += "v";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_RTL) {
+		flags += "R";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_VIRTUAL) {
+		flags += "V";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_SPACE) {
+		flags += "w";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_BREAK_HARD) {
+		flags += "h";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_BREAK_SOFT) {
+		flags += "s";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_TAB) {
+		flags += "t";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_ELONGATION) {
+		flags += "e";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_PUNCTUATION) {
+		flags += "p";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_UNDERSCORE) {
+		flags += "u";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_CONNECTED) {
+		flags += "C";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL) {
+		flags += "S";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_EMBEDDED_OBJECT) {
+		flags += "E";
+	}
+	if (p_glyph.flags & GRAPHEME_IS_SOFT_HYPHEN) {
+		flags += "h";
+	}
+	print_line(vformat("   %d => range: %d-%d cnt:%d index:%x font:%x(%d) offset:%fx%f adv:%f rep:%d flags:%s", p_idx, p_glyph.start, p_glyph.end, p_glyph.count, p_glyph.index, p_glyph.font_rid.get_id(), p_glyph.font_size, p_glyph.x_off, p_glyph.y_off, p_glyph.advance, p_glyph.repeat, flags));
+}
+
+void TextServer::shaped_text_debug_print(const RID &p_shaped) const {
+	int ellipsis_pos = shaped_text_get_ellipsis_pos(p_shaped);
+	int trim_pos = shaped_text_get_trim_pos(p_shaped);
+	const Vector2i &range = shaped_text_get_range(p_shaped);
+	int v_size = shaped_text_get_glyph_count(p_shaped);
+	const Glyph *glyphs = shaped_text_get_glyphs(p_shaped);
+
+	print_line(vformat("%x: range: %d-%d glyps: %d trim: %d ellipsis: %d", p_shaped.get_id(), range.x, range.y, v_size, trim_pos, ellipsis_pos));
+
+	for (int i = 0; i < v_size; i++) {
+		debug_print_glyph(i, glyphs[i]);
+	}
+}
+
+#endif // DEBUG_ENABLED
+
 void TextServer::_diacritics_map_add(const String &p_from, char32_t p_to) {
 	for (int i = 0; i < p_from.size(); i++) {
 		diacritics_map[p_from[i]] = p_to;

+ 5 - 0
servers/text_server.h

@@ -538,6 +538,11 @@ public:
 	virtual void shaped_text_draw(const RID &p_shaped, const RID &p_canvas, const Vector2 &p_pos, double p_clip_l = -1.0, double p_clip_r = -1.0, const Color &p_color = Color(1, 1, 1)) const;
 	virtual void shaped_text_draw_outline(const RID &p_shaped, const RID &p_canvas, const Vector2 &p_pos, double p_clip_l = -1.0, double p_clip_r = -1.0, int64_t p_outline_size = 1, const Color &p_color = Color(1, 1, 1)) const;
 
+#ifdef DEBUG_ENABLED
+	void debug_print_glyph(int p_idx, const Glyph &p_glyph) const;
+	void shaped_text_debug_print(const RID &p_shaped) const;
+#endif
+
 	// Number conversion.
 	virtual String format_number(const String &p_string, const String &p_language = "") const = 0;
 	virtual String parse_number(const String &p_string, const String &p_language = "") const = 0;

+ 28 - 0
tests/servers/test_text_server.h

@@ -489,6 +489,34 @@ TEST_SUITE("[TextServer]") {
 					ts->free_rid(ctx);
 				}
 
+				if (ts->has_feature(TextServer::FEATURE_BREAK_ITERATORS)) {
+					struct TestCase {
+						String text;
+						PackedInt32Array breaks;
+					};
+					TestCase cases[] = {
+						{ U"            เมาส์ตัวนี้", { 0, 17, 17, 23 } },
+						{ U"              กู้ไฟล์", { 0, 17, 17, 21 } },
+						{ U"             ไม่มีคำ", { 0, 18, 18, 20 } },
+						{ U"             ไม่มีคำพูด", { 0, 18, 18, 23 } },
+						{ U"            ไม่มีคำ", { 0, 17, 17, 19 } },
+						{ U"         มีอุปกรณ์\nนี้", { 0, 11, 11, 19, 19, 22 } },
+						{ U"الحمدا لحمدا لحمـــد", { 0, 13, 13, 20 } },
+						{ U"         الحمد test", { 0, 15, 15, 19 } },
+						{ U"الحمـد الرياضي العربي", { 0, 7, 7, 21 } },
+					};
+					for (size_t j = 0; j < sizeof(cases) / sizeof(TestCase); j++) {
+						RID ctx = ts->create_shaped_text();
+						CHECK_FALSE_MESSAGE(ctx == RID(), "Creating text buffer failed.");
+						bool ok = ts->shaped_text_add_string(ctx, cases[j].text, font, 16);
+						CHECK_FALSE_MESSAGE(!ok, "Adding text to the buffer failed.");
+						PackedInt32Array breaks = ts->shaped_text_get_line_breaks(ctx, 90.0);
+
+						CHECK_FALSE_MESSAGE(breaks != cases[j].breaks, "Invalid break points.");
+						ts->free_rid(ctx);
+					}
+				}
+
 				for (int j = 0; j < font.size(); j++) {
 					ts->free_rid(font[j]);
 				}