From 7aad14a4b6d7ef722851a8c4f9fcafa5b48666b3 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Tue, 30 Aug 2022 11:56:17 +0300 Subject: [PATCH] [TextServer] Add support for trimming edge spaces on line break. --- doc/classes/TextServer.xml | 4 + scene/3d/label_3d.cpp | 3 +- scene/gui/button.cpp | 2 +- scene/gui/item_list.cpp | 6 +- scene/gui/label.cpp | 3 +- scene/gui/rich_text_label.cpp | 1 + scene/resources/text_paragraph.cpp | 2 +- servers/text_server.cpp | 123 +++++++++++++++++++++++++---- servers/text_server.h | 1 + 9 files changed, 124 insertions(+), 21 deletions(-) diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index aad83211f54..1aec6d2c0e5 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -1624,6 +1624,10 @@ Break the line between any unconnected graphemes. + Should be used only in conjunction with [constant BREAK_WORD_BOUND], break the line between any unconnected graphemes, if it's impossible to break it between the words. + + + Remove edge spaces from the broken line segments. Trims text before the shaping. e.g, increasing [member Label.visible_characters] or [member RichTextLabel.visible_characters] value is visually identical to typing the text. diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp index e4a7cf6ee53..d9778749118 100644 --- a/scene/3d/label_3d.cpp +++ b/scene/3d/label_3d.cpp @@ -486,8 +486,9 @@ void Label3D::_shape() { case TextServer::AUTOWRAP_OFF: break; } - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); + autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES; + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); float max_line_w = 0.0; for (int i = 0; i < line_breaks.size(); i = i + 2) { RID line = TS->shaped_text_substr(text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 0b7280619e2..c7b64ba6c68 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -544,7 +544,7 @@ void Button::_bind_methods() { Button::Button(const String &p_text) { text_buf.instantiate(); - text_buf->set_break_flags(TextServer::BREAK_MANDATORY); + text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_TRIM_EDGE_SPACES); set_mouse_filter(MOUSE_FILTER_STOP); set_text(p_text); diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index e3a27ba7d51..308dbe33f2d 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -45,7 +45,7 @@ void ItemList::_shape(int p_idx) { } item.text_buf->add_string(item.text, get_theme_font(SNAME("font")), get_theme_font_size(SNAME("font_size")), item.language); if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); } else { item.text_buf->set_break_flags(TextServer::BREAK_NONE); } @@ -532,7 +532,7 @@ void ItemList::set_max_text_lines(int p_lines) { max_text_lines = p_lines; for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); items.write[i].text_buf->set_max_lines_visible(p_lines); } else { items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE); @@ -582,7 +582,7 @@ void ItemList::set_icon_mode(IconMode p_mode) { icon_mode = p_mode; for (int i = 0; i < items.size(); i++) { if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { - items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND); + items.write[i].text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); } else { items.write[i].text_buf->set_break_flags(TextServer::BREAK_NONE); } diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index cd68d1f0010..fd174619d5a 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -143,8 +143,9 @@ void Label::_shape() { case TextServer::AUTOWRAP_OFF: break; } - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); + autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES; + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); for (int i = 0; i < line_breaks.size(); i = i + 2) { RID line = TS->shaped_text_substr(text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); lines_rid.push_back(line); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index c9a903153d6..c5fe218a155 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -453,6 +453,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref case TextServer::AUTOWRAP_OFF: break; } + autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES; // Clear cache. l.text_buf->clear(); diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 43d3f329fae..7e9a2591e49 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -77,7 +77,7 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("set_break_flags", "flags"), &TextParagraph::set_break_flags); ClassDB::bind_method(D_METHOD("get_break_flags"), &TextParagraph::get_break_flags); - ADD_PROPERTY(PropertyInfo(Variant::INT, "break_flags", PROPERTY_HINT_FLAGS, "Mandatory,Word Bound,Grapheme Bound,Adaptive"), "set_break_flags", "get_break_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "break_flags", PROPERTY_HINT_FLAGS, "Mandatory,Word Bound,Grapheme Bound,Adaptive,Trim Spaces"), "set_break_flags", "get_break_flags"); ClassDB::bind_method(D_METHOD("set_justification_flags", "flags"), &TextParagraph::set_justification_flags); ClassDB::bind_method(D_METHOD("get_justification_flags"), &TextParagraph::get_justification_flags); diff --git a/servers/text_server.cpp b/servers/text_server.cpp index 393160fe9e6..9df74b1b20c 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -498,6 +498,7 @@ void TextServer::_bind_methods() { BIND_BITFIELD_FLAG(BREAK_WORD_BOUND); BIND_BITFIELD_FLAG(BREAK_GRAPHEME_BOUND); BIND_BITFIELD_FLAG(BREAK_ADAPTIVE); + BIND_BITFIELD_FLAG(BREAK_TRIM_EDGE_SPACES); /* VisibleCharactersBehavior */ BIND_ENUM_CONSTANT(VC_CHARS_BEFORE_SHAPING); @@ -680,6 +681,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped real_t width = 0.f; int line_start = MAX(p_start, range.x); + int prev_safe_break = 0; int last_safe_break = -1; int chunk = 0; @@ -688,13 +690,29 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped for (int i = 0; i < l_size; i++) { if (l_gl[i].start < p_start) { + prev_safe_break = i + 1; continue; } if (l_gl[i].count > 0) { if ((p_width[chunk] > 0) && (width + l_gl[i].advance > p_width[chunk]) && (last_safe_break >= 0)) { - lines.push_back(line_start); - lines.push_back(l_gl[last_safe_break].end); + if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) { + int start_pos = prev_safe_break; + int end_pos = last_safe_break; + + while ((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; + } + while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { + end_pos -= l_gl[end_pos].count; + } + lines.push_back(l_gl[start_pos].start); + lines.push_back(l_gl[end_pos].end); + } else { + lines.push_back(line_start); + lines.push_back(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; last_safe_break = -1; width = 0; @@ -709,9 +727,24 @@ 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) { - lines.push_back(line_start); - lines.push_back(l_gl[i].end); + if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) { + int start_pos = prev_safe_break; + int end_pos = i; + + while ((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; + } + while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { + end_pos -= l_gl[end_pos].count; + } + lines.push_back(l_gl[start_pos].start); + lines.push_back(l_gl[end_pos].end); + } else { + lines.push_back(line_start); + lines.push_back(l_gl[i].end); + } line_start = l_gl[i].end; + prev_safe_break = i + 1; last_safe_break = -1; width = 0; chunk = 0; @@ -734,9 +767,23 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped } if (l_size > 0) { - if (lines.size() == 0 || lines[lines.size() - 1] < range.y) { - lines.push_back(line_start); - lines.push_back(range.y); + 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 ((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; + } + while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { + end_pos -= l_gl[end_pos].count; + } + lines.push_back(l_gl[start_pos].start); + lines.push_back(l_gl[end_pos].end); + } else { + lines.push_back(line_start); + lines.push_back(range.y); + } } } else { lines.push_back(0); @@ -754,6 +801,7 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do double width = 0.f; int line_start = MAX(p_start, range.x); + int prev_safe_break = 0; int last_safe_break = -1; int word_count = 0; @@ -762,13 +810,30 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do for (int i = 0; i < l_size; i++) { if (l_gl[i].start < p_start) { + prev_safe_break = i + 1; continue; } if (l_gl[i].count > 0) { if ((p_width > 0) && (width + l_gl[i].advance * l_gl[i].repeat > p_width) && (last_safe_break >= 0)) { - lines.push_back(line_start); - lines.push_back(l_gl[last_safe_break].end); + if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) { + int start_pos = prev_safe_break; + int end_pos = last_safe_break; + + while ((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; + } + while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { + end_pos -= l_gl[end_pos].count; + } + //printf("%s", vformat("BRK TRIM(W): %d..%d -> %d..%d\n", line_start, l_gl[last_safe_break].end, l_gl[start_pos].start, l_gl[end_pos].end).utf8().get_data()); + lines.push_back(l_gl[start_pos].start); + lines.push_back(l_gl[end_pos].end); + } else { + lines.push_back(line_start); + lines.push_back(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; last_safe_break = -1; width = 0; @@ -777,9 +842,25 @@ 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) { - lines.push_back(line_start); - lines.push_back(l_gl[i].end); + if (p_break_flags.has_flag(BREAK_TRIM_EDGE_SPACES)) { + int start_pos = prev_safe_break; + int end_pos = i; + + while ((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; + } + while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { + end_pos -= l_gl[end_pos].count; + } + lines.push_back(l_gl[start_pos].start); + lines.push_back(l_gl[end_pos].end); + //printf("%s", vformat("BRK TRIM(M): %d..%d -> %d..%d\n", line_start, l_gl[i].end, l_gl[start_pos].start, l_gl[end_pos].end).utf8().get_data()); + } else { + lines.push_back(line_start); + lines.push_back(l_gl[i].end); + } line_start = l_gl[i].end; + prev_safe_break = i + 1; last_safe_break = -1; width = 0; continue; @@ -802,9 +883,23 @@ PackedInt32Array TextServer::shaped_text_get_line_breaks(const RID &p_shaped, do } if (l_size > 0) { - if (lines.size() == 0 || lines[lines.size() - 1] < range.y) { - lines.push_back(line_start); - lines.push_back(range.y); + 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 ((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; + } + while ((start_pos < end_pos) && ((l_gl[end_pos].flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_HARD) == GRAPHEME_IS_BREAK_HARD || (l_gl[end_pos].flags & GRAPHEME_IS_BREAK_SOFT) == GRAPHEME_IS_BREAK_SOFT)) { + end_pos -= l_gl[end_pos].count; + } + lines.push_back(l_gl[start_pos].start); + lines.push_back(l_gl[end_pos].end); + } else { + lines.push_back(line_start); + lines.push_back(range.y); + } } } else { lines.push_back(0); diff --git a/servers/text_server.h b/servers/text_server.h index 9304771d1b3..e8945dd807f 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -103,6 +103,7 @@ public: BREAK_WORD_BOUND = 1 << 1, BREAK_GRAPHEME_BOUND = 1 << 2, BREAK_ADAPTIVE = 1 << 3, + BREAK_TRIM_EDGE_SPACES = 1 << 4, }; enum OverrunBehavior {