summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/classes/LineEdit.xml5
-rw-r--r--doc/classes/ProjectSettings.xml5
-rw-r--r--doc/classes/TextEdit.xml5
-rw-r--r--doc/classes/TextServer.xml7
-rw-r--r--doc/classes/TextServerExtension.xml7
-rw-r--r--main/main.cpp9
-rw-r--r--misc/dist/shell/_godot.zsh-completion1
-rw-r--r--misc/dist/shell/godot.bash-completion1
-rw-r--r--misc/dist/shell/godot.fish1
-rw-r--r--modules/text_server_adv/text_server_adv.cpp263
-rw-r--r--modules/text_server_adv/text_server_adv.h1
-rw-r--r--modules/text_server_fb/text_server_fb.cpp117
-rw-r--r--modules/text_server_fb/text_server_fb.h1
-rw-r--r--scene/gui/label.cpp48
-rw-r--r--scene/gui/line_edit.cpp4
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/resources/default_theme/default_theme.cpp2
-rw-r--r--scene/resources/text_paragraph.cpp20
-rw-r--r--servers/text/text_server_extension.cpp9
-rw-r--r--servers/text/text_server_extension.h2
-rw-r--r--servers/text_server.cpp47
-rw-r--r--servers/text_server.h1
-rw-r--r--tests/servers/test_text_server.h206
23 files changed, 583 insertions, 181 deletions
diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml
index b376ab5cb0..ff01ad72fd 100644
--- a/doc/classes/LineEdit.xml
+++ b/doc/classes/LineEdit.xml
@@ -383,7 +383,7 @@
</constants>
<theme_items>
<theme_item name="caret_color" data_type="color" type="Color" default="Color(0.94, 0.94, 0.94, 1)">
- Color of the [LineEdit]'s caret (text cursor).
+ Color of the [LineEdit]'s caret (text cursor). This can be set to a fully transparent color to hide the caret entirely.
</theme_item>
<theme_item name="clear_button_color" data_type="color" type="Color" default="Color(0.88, 0.88, 0.88, 1)">
Color used as default tint for the clear button.
@@ -406,6 +406,9 @@
<theme_item name="selection_color" data_type="color" type="Color" default="Color(0.49, 0.49, 0.49, 1)">
Color of the selection rectangle.
</theme_item>
+ <theme_item name="caret_width" data_type="constant" type="int" default="1">
+ The caret's width in pixels. Greater values can be used to improve accessibility by ensuring the caret is easily visible, or to ensure consistency with a large font size.
+ </theme_item>
<theme_item name="minimum_character_width" data_type="constant" type="int" default="4">
Minimum horizontal space for the text (not counting the clear button and content margins). This value is measured in count of 'M' characters (i.e. this amount of 'M' characters can be displayed without scrolling).
</theme_item>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index a249786388..69f49c7276 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -481,8 +481,9 @@
<member name="display/mouse_cursor/tooltip_position_offset" type="Vector2" setter="" getter="" default="Vector2(10, 10)">
Position offset for tooltips, relative to the mouse cursor's hotspot.
</member>
- <member name="display/window/dpi/allow_hidpi" type="bool" setter="" getter="" default="false">
- If [code]true[/code], allows HiDPI display on Windows, macOS, and the HTML5 platform. This setting has no effect on desktop Linux, as DPI-awareness fallbacks are not supported there.
+ <member name="display/window/dpi/allow_hidpi" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], allows HiDPI display on Windows, macOS, Android, iOS and HTML5. If [code]false[/code], the platform's low-DPI fallback will be used on HiDPI displays, which causes the window to be displayed in a blurry or pixelated manner (and can cause various window management bugs). Therefore, it is recommended to make your project scale to [url=$DOCS_URL/tutorials/viewports/multiple_resolutions.html]multiple resolutions[/url] instead of disabling this setting.
+ [b]Note:[/b] This setting has no effect on Linux as DPI-awareness fallbacks are not supported there.
</member>
<member name="display/window/energy_saving/keep_screen_on" type="bool" setter="" getter="" default="true">
If [code]true[/code], keeps the screen on (even in case of inactivity), so the screensaver does not take over. Works on desktop and mobile platforms.
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index e120f79236..3197e59248 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -1223,7 +1223,7 @@
[Color] of the text behind the caret when using a block caret.
</theme_item>
<theme_item name="caret_color" data_type="color" type="Color" default="Color(0.88, 0.88, 0.88, 1)">
- [Color] of the caret.
+ [Color] of the caret. This can be set to a fully transparent color to hide the caret entirely.
</theme_item>
<theme_item name="current_line_color" data_type="color" type="Color" default="Color(0.25, 0.25, 0.26, 0.8)">
Background [Color] of the line containing the caret.
@@ -1252,6 +1252,9 @@
<theme_item name="word_highlighted_color" data_type="color" type="Color" default="Color(0.8, 0.9, 0.9, 0.15)">
Sets the highlight [Color] of multiple occurrences. [member highlight_all_occurrences] has to be enabled.
</theme_item>
+ <theme_item name="caret_width" data_type="constant" type="int" default="1">
+ The caret's width in pixels. Greater values can be used to improve accessibility by ensuring the caret is easily visible, or to ensure consistency with a large font size. If set to [code]0[/code] or lower, the caret width is automatically set to 1 pixel and multiplied by the display scaling factor.
+ </theme_item>
<theme_item name="line_spacing" data_type="constant" type="int" default="4">
Sets the spacing between the lines.
</theme_item>
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index 64989eb98b..2117451281 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -1051,6 +1051,13 @@
Returns composite character's bounds as offsets from the start of the line.
</description>
</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">
<return type="PackedInt32Array" />
<argument index="0" name="shaped" type="RID" />
diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml
index aa6c93456b..40a1f89395 100644
--- a/doc/classes/TextServerExtension.xml
+++ b/doc/classes/TextServerExtension.xml
@@ -1061,6 +1061,13 @@
Returns composite character's bounds as offsets from the start of the line.
</description>
</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">
<return type="PackedInt32Array" />
<argument index="0" name="shaped" type="RID" />
diff --git a/main/main.cpp b/main/main.cpp
index 9767d4f5fb..9393e8d99d 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -164,7 +164,6 @@ static bool init_windowed = false;
static bool init_always_on_top = false;
static bool init_use_custom_pos = false;
static Vector2 init_custom_pos;
-static bool force_lowdpi = false;
// Debug
@@ -340,7 +339,6 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" -t, --always-on-top Request an always-on-top window.\n");
OS::get_singleton()->print(" --resolution <W>x<H> Request window resolution.\n");
OS::get_singleton()->print(" --position <X>,<Y> Request window position.\n");
- OS::get_singleton()->print(" --low-dpi Force low-DPI mode (macOS and Windows only).\n");
OS::get_singleton()->print(" --single-window Use a single window (no separate subwindows).\n");
OS::get_singleton()->print(" --tablet-driver Pen tablet input driver.\n");
OS::get_singleton()->print("\n");
@@ -870,9 +868,6 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
goto error;
}
- } else if (I->get() == "--low-dpi") { // force low DPI (macOS only)
-
- force_lowdpi = true;
} else if (I->get() == "--headless") { // enable headless mode (no audio, no rendering).
audio_driver = "Dummy";
@@ -1365,9 +1360,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF("internationalization/rendering/force_right_to_left_layout_direction", false);
GLOBAL_DEF("internationalization/locale/include_text_server_data", false);
- if (!force_lowdpi) {
- OS::get_singleton()->_allow_hidpi = GLOBAL_DEF("display/window/dpi/allow_hidpi", false);
- }
+ OS::get_singleton()->_allow_hidpi = GLOBAL_DEF("display/window/dpi/allow_hidpi", true);
// FIXME: Restore support.
#if 0
diff --git a/misc/dist/shell/_godot.zsh-completion b/misc/dist/shell/_godot.zsh-completion
index ba386b55cc..9d42f4fe05 100644
--- a/misc/dist/shell/_godot.zsh-completion
+++ b/misc/dist/shell/_godot.zsh-completion
@@ -49,7 +49,6 @@ _arguments \
'(-t --always-on-top)'{-t,--always-on-top}'[request an always-on-top window]' \
'--resolution[request window resolution]:resolution in WxH format' \
'--position[request window position]:position in X,Y format' \
- '--low-dpi[force low-DPI mode (macOS and Windows only)]' \
'--headless[enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script]' \
'(-d --debug)'{-d,--debug}'[debug (local stdout debugger)]' \
'(-b --breakpoints)'{-b,--breakpoints}'[specify the breakpoint list as source::line comma-separated pairs, no spaces (use %20 instead)]:breakpoint list' \
diff --git a/misc/dist/shell/godot.bash-completion b/misc/dist/shell/godot.bash-completion
index 218fbd81b1..eeee538aba 100644
--- a/misc/dist/shell/godot.bash-completion
+++ b/misc/dist/shell/godot.bash-completion
@@ -52,7 +52,6 @@ _complete_godot_options() {
--always-on-top
--resolution
--position
---low-dpi
--headless
--debug
--breakpoints
diff --git a/misc/dist/shell/godot.fish b/misc/dist/shell/godot.fish
index 2a6b18ed8f..79df2de7f0 100644
--- a/misc/dist/shell/godot.fish
+++ b/misc/dist/shell/godot.fish
@@ -59,7 +59,6 @@ complete -c godot -s w -l windowed -d "Request windowed mode"
complete -c godot -s t -l always-on-top -d "Request an always-on-top window"
complete -c godot -l resolution -d "Request window resolution" -x
complete -c godot -l position -d "Request window position" -x
-complete -c godot -l low-dpi -d "Force low-DPI mode (macOS and Windows only)"
complete -c godot -l headless -d "Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script"
# Debug options:
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index f9997a437f..6002dc80da 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/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;
}
+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) {
_THREAD_SAFE_METHOD_
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;
if ((p_jst_flags & JUSTIFICATION_CONSTRAIN_ELLIPSIS) == JUSTIFICATION_CONSTRAIN_ELLIPSIS) {
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;
} else {
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) {
+ // 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)) {
justification_width -= sd->glyphs[start_pos].advance * sd->glyphs[start_pos].repeat;
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;
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;
@@ -3610,7 +3631,10 @@ float TextServerAdvanced::shaped_text_fit_to_width(RID p_shaped, float p_width,
const Glyph &gl = sd->glyphs[i];
if (gl.count > 0) {
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) {
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];
if (gl.count > 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;
}
- return sd->width;
+ return justification_width;
}
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;
}
+ 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();
- RID last_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
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;
- 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;
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 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.advance = whitespace_adv.x;
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.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);
}
// 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;
@@ -3920,21 +3984,19 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
for (int j = r_start; j < r_end; j++) {
char32_t c = sd->text[j - sd->start];
if (is_whitespace(c)) {
- breaks[j] = false;
+ breaks[j + 1] = false;
}
if (is_linebreak(c)) {
- breaks[j] = true;
+ breaks[j + 1] = true;
}
}
} else {
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)) {
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;
+ } else if (is_whitespace(c)) {
+ sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT;
} 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 {
- 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->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) {
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) {
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);
- 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.count = 1;
if (sd->orientation == ORIENTATION_HORIZONTAL) {
- gl.y_off = sd->glyphs[i].y_off;
+ gl.y_off = sd_glyphs[i].y_off;
} 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;
sd->glyphs.insert(i, gl);
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;
- 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.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;
- if (sd->glyphs[i].flags & GRAPHEME_IS_RTL) {
+ 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.
+ 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;
}
}
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 6ff9817dcf..d088219d91 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/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 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;
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index f28d174c5c..c7a7c4aa70 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/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;
}
+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) {
_THREAD_SAFE_METHOD_
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) {
+ // 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)) {
- 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;
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)) {
- 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;
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;
@@ -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)) {
- 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++) {
Glyph &gl = sd->glyphs.write[i];
if (gl.count > 0) {
if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
float old_adv = gl.advance;
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) {
@@ -2769,6 +2802,7 @@ bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) {
sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT;
}
if (is_linebreak(c)) {
+ sd_glyphs[i].flags |= GRAPHEME_IS_SPACE;
sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD;
}
if (c == 0x0009 || c == 0x000b) {
@@ -2827,17 +2861,50 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
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();
- RID last_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
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;
- 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;
@@ -2891,23 +2958,25 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
gl.count = 1;
gl.advance = whitespace_adv.x;
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.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL;
sd->overrun_trim_data.ellipsis_glyph_buf.append(gl);
}
// 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;
@@ -3023,13 +3092,13 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
if (gl.font_rid.is_valid()) {
if (sd->text[j - sd->start] != 0 && !is_linebreak(sd->text[j - sd->start])) {
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.y_off = 0;
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));
} 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.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));
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 9d1be50b04..e8619e0825 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/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 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;
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
index edd206a1ca..861b70f17e 100644
--- a/scene/gui/label.cpp
+++ b/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);
}
}
-
} else if (lines_hidden) {
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
}
-
} else {
// Autowrap disabled.
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"));
int outline_size = get_theme_constant(SNAME("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();
style->draw(ci, Rect2(Point2(0, 0), get_size()));
@@ -422,19 +420,19 @@ void Label::_notification(int p_what) {
// Draw main text.
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));
// Draw glyph outlines and shadow.
@@ -480,19 +478,19 @@ void Label::_notification(int p_what) {
// Draw main text.
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));
// Draw glyph outlines and shadow.
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index ec18101d2f..f7d6850a88 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -780,8 +780,6 @@ void LineEdit::_notification(int p_what) {
ofs_max -= r_icon->get_width();
}
- int caret_width = Math::round(1 * get_theme_default_base_scale());
-
// Draw selections rects.
Vector2 ofs = Point2(x_ofs + scroll_offset, y_ofs);
if (selection.enabled) {
@@ -843,6 +841,8 @@ void LineEdit::_notification(int p_what) {
// Draw carets.
ofs.x = x_ofs + scroll_offset;
if (draw_caret || drag_caret_force_displayed) {
+ const int caret_width = get_theme_constant(SNAME("caret_width")) * get_theme_default_base_scale();
+
if (ime_text.length() == 0) {
// Normal caret.
CaretInfo caret = TS->shaped_text_get_carets(text_rid, caret_column);
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 8df77daafb..17f2ed1db5 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1244,7 +1244,7 @@ void TextEdit::_notification(int p_what) {
}
// Carets.
- int caret_width = Math::round(1 * get_theme_default_base_scale());
+ const int caret_width = get_theme_constant(SNAME("caret_width")) * get_theme_default_base_scale();
if (!clipped && caret.line == line && line_wrap_index == caret_wrap_index) {
caret.draw_pos.y = ofs_y + ldata->get_line_descent(line_wrap_index);
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index 93c7eb43a1..9c76d74fbd 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -407,6 +407,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("minimum_character_width", "LineEdit", 4);
theme->set_constant("outline_size", "LineEdit", 0);
+ theme->set_constant("caret_width", "LineEdit", 1);
theme->set_icon("clear", "LineEdit", make_icon(line_edit_clear_png));
@@ -451,6 +452,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("line_spacing", "TextEdit", 4 * scale);
theme->set_constant("outline_size", "TextEdit", 0);
+ theme->set_constant("caret_width", "TextEdit", 1);
// CodeEdit
diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp
index 18e46e5476..c3bdef7b01 100644
--- a/scene/resources/text_paragraph.cpp
+++ b/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) {
// Draw dropcap.
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) {
dc_off.x += width - h_offset;
} 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.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
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;
}
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.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
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;
}
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) {
switch (alignment) {
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) {
ofs.x += l_width - line_width;
} else {
@@ -655,7 +655,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli
if (h_offset > 0) {
// Draw dropcap.
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) {
dc_off.x += width - h_offset;
} 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.y += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
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;
}
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.x += TS->shaped_text_get_ascent(lines_rid[i]) + spacing_top;
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;
}
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) {
switch (alignment) {
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) {
ofs.x += l_width - length;
} else {
@@ -772,7 +772,7 @@ void TextParagraph::draw_dropcap(RID p_canvas, const Vector2 &p_pos, const Color
if (h_offset > 0) {
// 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) {
ofs.x += width - h_offset;
} else {
@@ -794,7 +794,7 @@ void TextParagraph::draw_dropcap_outline(RID p_canvas, const Vector2 &p_pos, int
if (h_offset > 0) {
// 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) {
ofs.x += width - h_offset;
} else {
diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp
index a51b62e730..a2195d1ddf 100644
--- a/servers/text/text_server_extension.cpp
+++ b/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_get_direction, "shaped");
+ GDVIRTUAL_BIND(_shaped_text_get_inferred_direction, "shaped");
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;
}
+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) {
GDVIRTUAL_CALL(_shaped_text_set_orientation, p_shaped, p_orientation);
}
diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h
index 9b456c2dd7..77f2ced951 100644
--- a/servers/text/text_server_extension.h
+++ b/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 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);
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;
GDVIRTUAL2(_shaped_text_set_bidi_override, RID, const Array &);
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index 143cda985d..0cc4358785 100644
--- a/servers/text_server.cpp
+++ b/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_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);
@@ -1224,6 +1225,17 @@ void TextServer::shaped_text_draw(RID p_shaped, RID p_canvas, const Vector2 &p_p
}
// Draw at the baseline.
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++) {
if (p_clip_r > 0) {
// 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()) {
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 {
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 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.
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++) {
if (p_clip_r > 0) {
// 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()) {
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);
}
diff --git a/servers/text_server.h b/servers/text_server.h
index d5dccc0edb..07f0ae5045 100644
--- a/servers/text_server.h
+++ b/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 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;
diff --git a/tests/servers/test_text_server.h b/tests/servers/test_text_server.h
index b06f315bc8..0a64237285 100644
--- a/tests/servers/test_text_server.h
+++ b/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") {
for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) {
Ref<TextServer> ts = TextServerManager::get_singleton()->get_interface(i);