[TextServer] Fix some line breaking edge cases.

This commit is contained in:
Pāvels Nadtočajevs 2024-11-21 10:57:07 +02:00
parent 0c45ace151
commit 0708048530
3 changed files with 159 additions and 25 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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]);
}