From 0708048530a5e558f6180753f1356b04e266d5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:57:07 +0200 Subject: [PATCH] [TextServer] Fix some line breaking edge cases. --- servers/text_server.cpp | 151 ++++++++++++++++++++++++++----- servers/text_server.h | 5 + tests/servers/test_text_server.h | 28 ++++++ 3 files changed, 159 insertions(+), 25 deletions(-) diff --git a/servers/text_server.cpp b/servers/text_server.cpp index d2cf4674ae0..3de93408e47 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -824,7 +824,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; @@ -837,6 +842,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; @@ -847,9 +853,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; @@ -864,6 +873,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; @@ -877,6 +887,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 { @@ -886,8 +897,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; @@ -902,7 +916,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++; } @@ -916,20 +930,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); } @@ -977,7 +995,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; @@ -993,6 +1016,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; @@ -1006,9 +1030,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; @@ -1016,6 +1043,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; @@ -1033,6 +1061,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) { @@ -1044,8 +1073,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; @@ -1056,7 +1088,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++; } @@ -1080,13 +1112,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); } @@ -1805,6 +1841,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; diff --git a/servers/text_server.h b/servers/text_server.h index f448d62cb22..34081e9a4de 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -535,6 +535,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; diff --git a/tests/servers/test_text_server.h b/tests/servers/test_text_server.h index d982102a038..4190d3c23a4 100644 --- a/tests/servers/test_text_server.h +++ b/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]); }