[Text Overrun] Add option to set custom ellipsis character, add support for system font fallback.

This commit is contained in:
bruvzg 2023-10-01 13:39:13 +03:00
parent d76c1d0e51
commit 56579f397d
No known key found for this signature in database
GPG Key ID: 7960FCF39844EC38
20 changed files with 599 additions and 337 deletions

View File

@ -45,6 +45,9 @@
<member name="clip_text" type="bool" setter="set_clip_text" getter="is_clipping_text" default="false"> <member name="clip_text" type="bool" setter="set_clip_text" getter="is_clipping_text" default="false">
If [code]true[/code], the Label only shows the text that fits inside its bounding rectangle and will clip text horizontally. If [code]true[/code], the Label only shows the text that fits inside its bounding rectangle and will clip text horizontally.
</member> </member>
<member name="ellipsis_char" type="String" setter="set_ellipsis_char" getter="get_ellipsis_char" default="&quot;…&quot;">
Ellipsis character used for text clipping.
</member>
<member name="horizontal_alignment" type="int" setter="set_horizontal_alignment" getter="get_horizontal_alignment" enum="HorizontalAlignment" default="0"> <member name="horizontal_alignment" type="int" setter="set_horizontal_alignment" getter="get_horizontal_alignment" enum="HorizontalAlignment" default="0">
Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants.
</member> </member>

View File

@ -151,6 +151,9 @@
<member name="direction" type="int" setter="set_direction" getter="get_direction" enum="TextServer.Direction" default="0"> <member name="direction" type="int" setter="set_direction" getter="get_direction" enum="TextServer.Direction" default="0">
Text writing direction. Text writing direction.
</member> </member>
<member name="ellipsis_char" type="String" setter="set_ellipsis_char" getter="get_ellipsis_char" default="&quot;…&quot;">
Ellipsis character used for text clipping.
</member>
<member name="flags" type="int" setter="set_flags" getter="get_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="3"> <member name="flags" type="int" setter="set_flags" getter="get_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="3">
Line alignment rules. For more info see [TextServer]. Line alignment rules. For more info see [TextServer].
</member> </member>

View File

@ -274,6 +274,9 @@
<member name="direction" type="int" setter="set_direction" getter="get_direction" enum="TextServer.Direction" default="0"> <member name="direction" type="int" setter="set_direction" getter="get_direction" enum="TextServer.Direction" default="0">
Text writing direction. Text writing direction.
</member> </member>
<member name="ellipsis_char" type="String" setter="set_ellipsis_char" getter="get_ellipsis_char" default="&quot;…&quot;">
Ellipsis character used for text clipping.
</member>
<member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163"> <member name="justification_flags" type="int" setter="set_justification_flags" getter="get_justification_flags" enum="TextServer.JustificationFlag" is_bitfield="true" default="163">
Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. Line fill alignment rules. For more info see [enum TextServer.JustificationFlag].
</member> </member>

View File

@ -1243,6 +1243,13 @@
Returns array of the composite character boundaries. Returns array of the composite character boundaries.
</description> </description>
</method> </method>
<method name="shaped_text_get_custom_ellipsis" qualifiers="const">
<return type="int" />
<param index="0" name="shaped" type="RID" />
<description>
Returns ellipsis character used for text clipping.
</description>
</method>
<method name="shaped_text_get_custom_punctuation" qualifiers="const"> <method name="shaped_text_get_custom_punctuation" qualifiers="const">
<return type="String" /> <return type="String" />
<param index="0" name="shaped" type="RID" /> <param index="0" name="shaped" type="RID" />
@ -1547,6 +1554,14 @@
Override ranges should cover full source text without overlaps. BiDi algorithm will be used on each range separately. Override ranges should cover full source text without overlaps. BiDi algorithm will be used on each range separately.
</description> </description>
</method> </method>
<method name="shaped_text_set_custom_ellipsis">
<return type="void" />
<param index="0" name="shaped" type="RID" />
<param index="1" name="char" type="int" />
<description>
Sets ellipsis character used for text clipping.
</description>
</method>
<method name="shaped_text_set_custom_punctuation"> <method name="shaped_text_set_custom_punctuation">
<return type="void" /> <return type="void" />
<param index="0" name="shaped" type="RID" /> <param index="0" name="shaped" type="RID" />

View File

@ -1073,6 +1073,12 @@
<description> <description>
</description> </description>
</method> </method>
<method name="_shaped_text_get_custom_ellipsis" qualifiers="virtual const">
<return type="int" />
<param index="0" name="shaped" type="RID" />
<description>
</description>
</method>
<method name="_shaped_text_get_custom_punctuation" qualifiers="virtual const"> <method name="_shaped_text_get_custom_punctuation" qualifiers="virtual const">
<return type="String" /> <return type="String" />
<param index="0" name="shaped" type="RID" /> <param index="0" name="shaped" type="RID" />
@ -1329,6 +1335,13 @@
<description> <description>
</description> </description>
</method> </method>
<method name="_shaped_text_set_custom_ellipsis" qualifiers="virtual">
<return type="void" />
<param index="0" name="shaped" type="RID" />
<param index="1" name="char" type="int" />
<description>
</description>
</method>
<method name="_shaped_text_set_custom_punctuation" qualifiers="virtual"> <method name="_shaped_text_set_custom_punctuation" qualifiers="virtual">
<return type="void" /> <return type="void" />
<param index="0" name="shaped" type="RID" /> <param index="0" name="shaped" type="RID" />

View File

@ -4048,6 +4048,20 @@ String TextServerAdvanced::_shaped_text_get_custom_punctuation(const RID &p_shap
return sd->custom_punct; return sd->custom_punct;
} }
void TextServerAdvanced::_shaped_text_set_custom_ellipsis(const RID &p_shaped, int64_t p_char) {
_THREAD_SAFE_METHOD_
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL(sd);
sd->el_char = p_char;
}
int64_t TextServerAdvanced::_shaped_text_get_custom_ellipsis(const RID &p_shaped) const {
_THREAD_SAFE_METHOD_
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, 0);
return sd->el_char;
}
void TextServerAdvanced::_shaped_text_set_bidi_override(const RID &p_shaped, const Array &p_override) { void TextServerAdvanced::_shaped_text_set_bidi_override(const RID &p_shaped, const Array &p_override) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL(sd); ERR_FAIL_NULL(sd);
@ -4800,6 +4814,166 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac
return 0.0; return 0.0;
} }
RID TextServerAdvanced::_find_sys_font_for_text(const RID &p_fdef, const String &p_script_code, const String &p_language, const String &p_text) {
RID f;
// Try system fallback.
String font_name = _font_get_name(p_fdef);
BitField<FontStyle> font_style = _font_get_style(p_fdef);
int font_weight = _font_get_weight(p_fdef);
int font_stretch = _font_get_stretch(p_fdef);
Dictionary dvar = _font_get_variation_coordinates(p_fdef);
static int64_t wgth_tag = _name_to_tag("weight");
static int64_t wdth_tag = _name_to_tag("width");
static int64_t ital_tag = _name_to_tag("italic");
if (dvar.has(wgth_tag)) {
font_weight = dvar[wgth_tag].operator int();
}
if (dvar.has(wdth_tag)) {
font_stretch = dvar[wdth_tag].operator int();
}
if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) {
font_style.set_flag(TextServer::FONT_ITALIC);
}
String locale = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language;
PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, p_text, locale, p_script_code, font_weight, font_stretch, font_style & TextServer::FONT_ITALIC);
#ifdef GDEXTENSION
for (int fb = 0; fb < fallback_font_name.size(); fb++) {
const String &E = fallback_font_name[fb];
#else
for (const String &E : fallback_font_name) {
#endif
SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, p_fdef, this);
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) {
const SystemFontCacheRec &F = sysf_cache.var[face_idx];
if (unlikely(!_font_has_char(F.rid, p_text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(F.rid);
int weight = _font_get_weight(F.rid);
int stretch = _font_get_stretch(F.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match != -1) {
f = sysf_cache.var[best_match].rid;
}
}
if (!f.is_valid()) {
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
if (sysf_cache.max_var == sysf_cache.var.size()) {
// All subfonts already tested, skip.
continue;
}
}
if (!system_font_data.has(E)) {
system_font_data[E] = FileAccess::get_file_as_bytes(E);
}
const PackedByteArray &font_data = system_font_data[E];
SystemFontCacheRec sysf;
sysf.rid = _create_font();
_font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
Dictionary var = dvar;
// Select matching style from collection.
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) {
_font_set_face_index(sysf.rid, face_idx);
if (unlikely(!_font_has_char(sysf.rid, p_text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(sysf.rid);
int weight = _font_get_weight(sysf.rid);
int stretch = _font_get_stretch(sysf.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match == -1) {
_free_rid(sysf.rid);
continue;
} else {
_font_set_face_index(sysf.rid, best_match);
}
sysf.index = best_match;
// If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
if (best_score != 70) {
Dictionary ftr = _font_supported_variation_list(sysf.rid);
if (ftr.has(wdth_tag)) {
var[wdth_tag] = font_stretch;
_font_set_stretch(sysf.rid, font_stretch);
}
if (ftr.has(wgth_tag)) {
var[wgth_tag] = font_weight;
_font_set_weight(sysf.rid, font_weight);
}
if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) {
var[ital_tag] = 1;
_font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC);
}
}
_font_set_antialiasing(sysf.rid, key.antialiasing);
_font_set_generate_mipmaps(sysf.rid, key.mipmaps);
_font_set_multichannel_signed_distance_field(sysf.rid, key.msdf);
_font_set_msdf_pixel_range(sysf.rid, key.msdf_range);
_font_set_msdf_size(sysf.rid, key.msdf_source_size);
_font_set_fixed_size(sysf.rid, key.fixed_size);
_font_set_force_autohinter(sysf.rid, key.force_autohinter);
_font_set_hinting(sysf.rid, key.hinting);
_font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning);
_font_set_variation_coordinates(sysf.rid, var);
_font_set_oversampling(sysf.rid, key.oversampling);
_font_set_embolden(sysf.rid, key.embolden);
_font_set_transform(sysf.rid, key.transform);
_font_set_spacing(sysf.rid, SPACING_TOP, key.extra_spacing[SPACING_TOP]);
_font_set_spacing(sysf.rid, SPACING_BOTTOM, key.extra_spacing[SPACING_BOTTOM]);
_font_set_spacing(sysf.rid, SPACING_SPACE, key.extra_spacing[SPACING_SPACE]);
_font_set_spacing(sysf.rid, SPACING_GLYPH, key.extra_spacing[SPACING_GLYPH]);
if (system_fonts.has(key)) {
system_fonts[key].var.push_back(sysf);
} else {
SystemFontCache &sysf_cache = system_fonts[key];
sysf_cache.max_var = _font_get_face_count(sysf.rid);
sysf_cache.var.push_back(sysf);
}
f = sysf.rid;
}
break;
}
return f;
}
void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) { void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped_line); ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped_line);
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid."); ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid.");
@ -4842,20 +5016,52 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
int sd_size = sd->glyphs.size(); int sd_size = sd->glyphs.size();
int last_gl_font_size = sd_glyphs[sd_size - 1].font_size; int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
bool found_el_char = false;
// Find usable fonts, if fonts from the last glyph do not have required chars. // 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; RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
if (!_font_has_char(dot_gl_font_rid, '.')) { if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
const Array &fonts = spans[spans.size() - 1].fonts; const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) { for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], '.')) { if (_font_has_char(fonts[i], sd->el_char)) {
dot_gl_font_rid = fonts[i]; dot_gl_font_rid = fonts[i];
found_el_char = true;
break; break;
} }
} }
if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
const char32_t u32str[] = { sd->el_char, 0 };
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
if (rid.is_valid()) {
dot_gl_font_rid = rid;
found_el_char = true;
}
}
} else {
found_el_char = true;
}
if (!found_el_char) {
bool found_dot_char = false;
dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
if (!_font_has_char(dot_gl_font_rid, '.')) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], '.')) {
dot_gl_font_rid = fonts[i];
found_dot_char = true;
break;
}
}
if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
if (rid.is_valid()) {
dot_gl_font_rid = rid;
}
}
}
} }
RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
if (!_font_has_char(whitespace_gl_font_rid, '.')) { if (!_font_has_char(whitespace_gl_font_rid, ' ')) {
const Array &fonts = spans[spans.size() - 1].fonts; const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) { for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], ' ')) { if (_font_has_char(fonts[i], ' ')) {
@ -4865,14 +5071,14 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
} }
} }
int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, '.', 0) : -10; int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
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(); 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, ' ', 0) : -10; int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1;
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(); 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; int ellipsis_width = 0;
if (add_ellipsis && whitespace_gl_font_rid.is_valid()) { if (add_ellipsis && whitespace_gl_font_rid.is_valid()) {
ellipsis_width = 3 * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(dot_gl_font_rid, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0); ellipsis_width = (found_el_char ? 1 : 3) * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(dot_gl_font_rid, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
} }
int ell_min_characters = 6; int ell_min_characters = 6;
@ -4951,7 +5157,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if (dot_gl_idx != 0) { if (dot_gl_idx != 0) {
Glyph gl; Glyph gl;
gl.count = 1; gl.count = 1;
gl.repeat = 3; gl.repeat = (found_el_char ? 1 : 3);
gl.advance = dot_adv.x; gl.advance = dot_adv.x;
gl.index = dot_gl_idx; gl.index = dot_gl_idx;
gl.font_rid = dot_gl_font_rid; gl.font_rid = dot_gl_font_rid;
@ -5580,166 +5786,12 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
break; break;
} }
} }
String text = p_sd->text.substr(p_start, next - p_start);
String font_name = _font_get_name(fdef);
BitField<FontStyle> font_style = _font_get_style(fdef);
int font_weight = _font_get_weight(fdef);
int font_stretch = _font_get_stretch(fdef);
Dictionary dvar = _font_get_variation_coordinates(fdef);
static int64_t wgth_tag = _name_to_tag("weight");
static int64_t wdth_tag = _name_to_tag("width");
static int64_t ital_tag = _name_to_tag("italic");
if (dvar.has(wgth_tag)) {
font_weight = dvar[wgth_tag].operator int();
}
if (dvar.has(wdth_tag)) {
font_stretch = dvar[wdth_tag].operator int();
}
if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) {
font_style.set_flag(TextServer::FONT_ITALIC);
}
char scr_buffer[5] = { 0, 0, 0, 0, 0 }; char scr_buffer[5] = { 0, 0, 0, 0, 0 };
hb_tag_to_string(hb_script_to_iso15924_tag(p_script), scr_buffer); hb_tag_to_string(hb_script_to_iso15924_tag(p_script), scr_buffer);
String script_code = String(scr_buffer); String script_code = String(scr_buffer);
String locale = (p_sd->spans[p_span].language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_sd->spans[p_span].language;
PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, text, locale, script_code, font_weight, font_stretch, font_style & TextServer::FONT_ITALIC); String text = p_sd->text.substr(p_start, next - p_start);
#ifdef GDEXTENSION f = _find_sys_font_for_text(fdef, script_code, p_sd->spans[p_span].language, text);
for (int fb = 0; fb < fallback_font_name.size(); fb++) {
const String &E = fallback_font_name[fb];
#else
for (const String &E : fallback_font_name) {
#endif
SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, fdef, this);
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) {
const SystemFontCacheRec &F = sysf_cache.var[face_idx];
if (unlikely(!_font_has_char(F.rid, text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(F.rid);
int weight = _font_get_weight(F.rid);
int stretch = _font_get_stretch(F.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match != -1) {
f = sysf_cache.var[best_match].rid;
}
}
if (!f.is_valid()) {
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
if (sysf_cache.max_var == sysf_cache.var.size()) {
// All subfonts already tested, skip.
continue;
}
}
if (!system_font_data.has(E)) {
system_font_data[E] = FileAccess::get_file_as_bytes(E);
}
const PackedByteArray &font_data = system_font_data[E];
SystemFontCacheRec sysf;
sysf.rid = _create_font();
_font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
Dictionary var = dvar;
// Select matching style from collection.
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) {
_font_set_face_index(sysf.rid, face_idx);
if (unlikely(!_font_has_char(sysf.rid, text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(sysf.rid);
int weight = _font_get_weight(sysf.rid);
int stretch = _font_get_stretch(sysf.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match == -1) {
_free_rid(sysf.rid);
continue;
} else {
_font_set_face_index(sysf.rid, best_match);
}
sysf.index = best_match;
// If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
if (best_score != 70) {
Dictionary ftr = _font_supported_variation_list(sysf.rid);
if (ftr.has(wdth_tag)) {
var[wdth_tag] = font_stretch;
_font_set_stretch(sysf.rid, font_stretch);
}
if (ftr.has(wgth_tag)) {
var[wgth_tag] = font_weight;
_font_set_weight(sysf.rid, font_weight);
}
if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) {
var[ital_tag] = 1;
_font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC);
}
}
_font_set_antialiasing(sysf.rid, key.antialiasing);
_font_set_generate_mipmaps(sysf.rid, key.mipmaps);
_font_set_multichannel_signed_distance_field(sysf.rid, key.msdf);
_font_set_msdf_pixel_range(sysf.rid, key.msdf_range);
_font_set_msdf_size(sysf.rid, key.msdf_source_size);
_font_set_fixed_size(sysf.rid, key.fixed_size);
_font_set_force_autohinter(sysf.rid, key.force_autohinter);
_font_set_hinting(sysf.rid, key.hinting);
_font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning);
_font_set_variation_coordinates(sysf.rid, var);
_font_set_oversampling(sysf.rid, key.oversampling);
_font_set_embolden(sysf.rid, key.embolden);
_font_set_transform(sysf.rid, key.transform);
_font_set_spacing(sysf.rid, SPACING_TOP, key.extra_spacing[SPACING_TOP]);
_font_set_spacing(sysf.rid, SPACING_BOTTOM, key.extra_spacing[SPACING_BOTTOM]);
_font_set_spacing(sysf.rid, SPACING_SPACE, key.extra_spacing[SPACING_SPACE]);
_font_set_spacing(sysf.rid, SPACING_GLYPH, key.extra_spacing[SPACING_GLYPH]);
if (system_fonts.has(key)) {
system_fonts[key].var.push_back(sysf);
} else {
SystemFontCache &sysf_cache = system_fonts[key];
sysf_cache.max_var = _font_get_face_count(sysf.rid);
sysf_cache.var.push_back(sysf);
}
f = sysf.rid;
}
break;
}
} }
} }

View File

@ -502,6 +502,7 @@ class TextServerAdvanced : public TextServerExtension {
double upos = 0.0; double upos = 0.0;
double uthk = 0.0; double uthk = 0.0;
char32_t el_char = 0x2026;
TrimData overrun_trim_data; TrimData overrun_trim_data;
bool fit_width_minimum_reached = false; bool fit_width_minimum_reached = false;
@ -647,6 +648,7 @@ class TextServerAdvanced : public TextServerExtension {
bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const; bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const;
void _shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index, int64_t p_prev_start, int64_t p_prev_end); void _shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_end, hb_script_t p_script, hb_direction_t p_direction, TypedArray<RID> p_fonts, int64_t p_span, int64_t p_fb_index, int64_t p_prev_start, int64_t p_prev_end);
Glyph _shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, const RID &p_font, int64_t p_font_size); Glyph _shape_single_glyph(ShapedTextDataAdvanced *p_sd, char32_t p_char, hb_script_t p_script, hb_direction_t p_direction, const RID &p_font, int64_t p_font_size);
_FORCE_INLINE_ RID _find_sys_font_for_text(const RID &p_fdef, const String &p_script_code, const String &p_language, const String &p_text);
_FORCE_INLINE_ void _add_featuers(const Dictionary &p_source, Vector<hb_feature_t> &r_ftrs); _FORCE_INLINE_ void _add_featuers(const Dictionary &p_source, Vector<hb_feature_t> &r_ftrs);
@ -899,6 +901,9 @@ public:
MODBIND2(shaped_text_set_custom_punctuation, const RID &, const String &); MODBIND2(shaped_text_set_custom_punctuation, const RID &, const String &);
MODBIND1RC(String, shaped_text_get_custom_punctuation, const RID &); MODBIND1RC(String, shaped_text_get_custom_punctuation, const RID &);
MODBIND2(shaped_text_set_custom_ellipsis, const RID &, int64_t);
MODBIND1RC(int64_t, shaped_text_get_custom_ellipsis, const RID &);
MODBIND2(shaped_text_set_orientation, const RID &, Orientation); MODBIND2(shaped_text_set_orientation, const RID &, Orientation);
MODBIND1RC(Orientation, shaped_text_get_orientation, const RID &); MODBIND1RC(Orientation, shaped_text_get_orientation, const RID &);

View File

@ -2905,6 +2905,20 @@ String TextServerFallback::_shaped_text_get_custom_punctuation(const RID &p_shap
return sd->custom_punct; return sd->custom_punct;
} }
void TextServerFallback::_shaped_text_set_custom_ellipsis(const RID &p_shaped, int64_t p_char) {
_THREAD_SAFE_METHOD_
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL(sd);
sd->el_char = p_char;
}
int64_t TextServerFallback::_shaped_text_get_custom_ellipsis(const RID &p_shaped) const {
_THREAD_SAFE_METHOD_
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, 0);
return sd->el_char;
}
void TextServerFallback::_shaped_text_set_orientation(const RID &p_shaped, TextServer::Orientation p_orientation) { void TextServerFallback::_shaped_text_set_orientation(const RID &p_shaped, TextServer::Orientation p_orientation) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL(sd); ERR_FAIL_NULL(sd);
@ -3601,6 +3615,168 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap
return true; return true;
} }
RID TextServerFallback::_find_sys_font_for_text(const RID &p_fdef, const String &p_script_code, const String &p_language, const String &p_text) {
RID f;
// Try system fallback.
if (_font_is_allow_system_fallback(p_fdef)) {
String font_name = _font_get_name(p_fdef);
BitField<FontStyle> font_style = _font_get_style(p_fdef);
int font_weight = _font_get_weight(p_fdef);
int font_stretch = _font_get_stretch(p_fdef);
Dictionary dvar = _font_get_variation_coordinates(p_fdef);
static int64_t wgth_tag = _name_to_tag("weight");
static int64_t wdth_tag = _name_to_tag("width");
static int64_t ital_tag = _name_to_tag("italic");
if (dvar.has(wgth_tag)) {
font_weight = dvar[wgth_tag].operator int();
}
if (dvar.has(wdth_tag)) {
font_stretch = dvar[wdth_tag].operator int();
}
if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) {
font_style.set_flag(TextServer::FONT_ITALIC);
}
String locale = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language;
PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, p_text, locale, p_script_code, font_weight, font_stretch, font_style & TextServer::FONT_ITALIC);
#ifdef GDEXTENSION
for (int fb = 0; fb < fallback_font_name.size(); fb++) {
const String &E = fallback_font_name[fb];
#else
for (const String &E : fallback_font_name) {
#endif
SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, p_fdef, this);
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) {
const SystemFontCacheRec &F = sysf_cache.var[face_idx];
if (unlikely(!_font_has_char(F.rid, p_text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(F.rid);
int weight = _font_get_weight(F.rid);
int stretch = _font_get_stretch(F.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match != -1) {
f = sysf_cache.var[best_match].rid;
}
}
if (!f.is_valid()) {
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
if (sysf_cache.max_var == sysf_cache.var.size()) {
// All subfonts already tested, skip.
continue;
}
}
if (!system_font_data.has(E)) {
system_font_data[E] = FileAccess::get_file_as_bytes(E);
}
const PackedByteArray &font_data = system_font_data[E];
SystemFontCacheRec sysf;
sysf.rid = _create_font();
_font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
Dictionary var = dvar;
// Select matching style from collection.
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) {
_font_set_face_index(sysf.rid, face_idx);
if (unlikely(!_font_has_char(sysf.rid, p_text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(sysf.rid);
int weight = _font_get_weight(sysf.rid);
int stretch = _font_get_stretch(sysf.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match == -1) {
_free_rid(sysf.rid);
continue;
} else {
_font_set_face_index(sysf.rid, best_match);
}
sysf.index = best_match;
// If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
if (best_score != 70) {
Dictionary ftr = _font_supported_variation_list(sysf.rid);
if (ftr.has(wdth_tag)) {
var[wdth_tag] = font_stretch;
_font_set_stretch(sysf.rid, font_stretch);
}
if (ftr.has(wgth_tag)) {
var[wgth_tag] = font_weight;
_font_set_weight(sysf.rid, font_weight);
}
if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) {
var[ital_tag] = 1;
_font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC);
}
}
_font_set_antialiasing(sysf.rid, key.antialiasing);
_font_set_generate_mipmaps(sysf.rid, key.mipmaps);
_font_set_multichannel_signed_distance_field(sysf.rid, key.msdf);
_font_set_msdf_pixel_range(sysf.rid, key.msdf_range);
_font_set_msdf_size(sysf.rid, key.msdf_source_size);
_font_set_fixed_size(sysf.rid, key.fixed_size);
_font_set_force_autohinter(sysf.rid, key.force_autohinter);
_font_set_hinting(sysf.rid, key.hinting);
_font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning);
_font_set_variation_coordinates(sysf.rid, var);
_font_set_oversampling(sysf.rid, key.oversampling);
_font_set_embolden(sysf.rid, key.embolden);
_font_set_transform(sysf.rid, key.transform);
_font_set_spacing(sysf.rid, SPACING_TOP, key.extra_spacing[SPACING_TOP]);
_font_set_spacing(sysf.rid, SPACING_BOTTOM, key.extra_spacing[SPACING_BOTTOM]);
_font_set_spacing(sysf.rid, SPACING_SPACE, key.extra_spacing[SPACING_SPACE]);
_font_set_spacing(sysf.rid, SPACING_GLYPH, key.extra_spacing[SPACING_GLYPH]);
if (system_fonts.has(key)) {
system_fonts[key].var.push_back(sysf);
} else {
SystemFontCache &sysf_cache = system_fonts[key];
sysf_cache.max_var = _font_get_face_count(sysf.rid);
sysf_cache.var.push_back(sysf);
}
f = sysf.rid;
}
break;
}
}
return f;
}
void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) { void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_line, double p_width, BitField<TextServer::TextOverrunFlag> p_trim_flags) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line); ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line);
ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid."); ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid.");
@ -3643,20 +3819,52 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
int sd_size = sd->glyphs.size(); int sd_size = sd->glyphs.size();
int last_gl_font_size = sd_glyphs[sd_size - 1].font_size; int last_gl_font_size = sd_glyphs[sd_size - 1].font_size;
bool found_el_char = false;
// Find usable fonts, if fonts from the last glyph do not have required chars. // 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; RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
if (!_font_has_char(dot_gl_font_rid, '.')) { if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
const Array &fonts = spans[spans.size() - 1].fonts; const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) { for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], '.')) { if (_font_has_char(fonts[i], sd->el_char)) {
dot_gl_font_rid = fonts[i]; dot_gl_font_rid = fonts[i];
found_el_char = true;
break; break;
} }
} }
if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
const char32_t u32str[] = { sd->el_char, 0 };
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
if (rid.is_valid()) {
dot_gl_font_rid = rid;
found_el_char = true;
}
}
} else {
found_el_char = true;
}
if (!found_el_char) {
bool found_dot_char = false;
dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
if (!_font_has_char(dot_gl_font_rid, '.')) {
const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], '.')) {
dot_gl_font_rid = fonts[i];
found_dot_char = true;
break;
}
}
if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
if (rid.is_valid()) {
dot_gl_font_rid = rid;
}
}
}
} }
RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid; RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
if (!_font_has_char(whitespace_gl_font_rid, '.')) { if (!_font_has_char(whitespace_gl_font_rid, ' ')) {
const Array &fonts = spans[spans.size() - 1].fonts; const Array &fonts = spans[spans.size() - 1].fonts;
for (int i = 0; i < fonts.size(); i++) { for (int i = 0; i < fonts.size(); i++) {
if (_font_has_char(fonts[i], ' ')) { if (_font_has_char(fonts[i], ' ')) {
@ -3666,14 +3874,14 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
} }
} }
int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, '.', 0) : -10; int32_t dot_gl_idx = dot_gl_font_rid.is_valid() ? _font_get_glyph_index(dot_gl_font_rid, last_gl_font_size, (found_el_char ? sd->el_char : '.'), 0) : -1;
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(); 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, ' ', 0) : -10; int32_t whitespace_gl_idx = whitespace_gl_font_rid.is_valid() ? _font_get_glyph_index(whitespace_gl_font_rid, last_gl_font_size, ' ', 0) : -1;
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(); 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; int ellipsis_width = 0;
if (add_ellipsis && whitespace_gl_font_rid.is_valid()) { if (add_ellipsis && whitespace_gl_font_rid.is_valid()) {
ellipsis_width = 3 * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(dot_gl_font_rid, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0); ellipsis_width = (found_el_char ? 1 : 3) * dot_adv.x + sd->extra_spacing[SPACING_GLYPH] + _font_get_spacing(dot_gl_font_rid, SPACING_GLYPH) + (cut_per_word ? whitespace_adv.x : 0);
} }
int ell_min_characters = 6; int ell_min_characters = 6;
@ -3742,7 +3950,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
if (dot_gl_idx != 0) { if (dot_gl_idx != 0) {
Glyph gl; Glyph gl;
gl.count = 1; gl.count = 1;
gl.repeat = 3; gl.repeat = (found_el_char ? 1 : 3);
gl.advance = dot_adv.x; gl.advance = dot_adv.x;
gl.index = dot_gl_idx; gl.index = dot_gl_idx;
gl.font_rid = dot_gl_font_rid; gl.font_rid = dot_gl_font_rid;
@ -3873,161 +4081,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
RID fdef = span.fonts[0]; RID fdef = span.fonts[0];
if (_font_is_allow_system_fallback(fdef)) { if (_font_is_allow_system_fallback(fdef)) {
String text = sd->text.substr(j, 1); String text = sd->text.substr(j, 1);
String font_name = _font_get_name(fdef); gl.font_rid = _find_sys_font_for_text(fdef, String(), span.language, text);
BitField<FontStyle> font_style = _font_get_style(fdef);
int font_weight = _font_get_weight(fdef);
int font_stretch = _font_get_stretch(fdef);
Dictionary dvar = _font_get_variation_coordinates(fdef);
static int64_t wgth_tag = _name_to_tag("weight");
static int64_t wdth_tag = _name_to_tag("width");
static int64_t ital_tag = _name_to_tag("italic");
if (dvar.has(wgth_tag)) {
font_weight = dvar[wgth_tag].operator int();
}
if (dvar.has(wdth_tag)) {
font_stretch = dvar[wdth_tag].operator int();
}
if (dvar.has(ital_tag) && dvar[ital_tag].operator int() == 1) {
font_style.set_flag(TextServer::FONT_ITALIC);
}
String locale = (span.language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : span.language;
PackedStringArray fallback_font_name = OS::get_singleton()->get_system_font_path_for_text(font_name, text, locale, String(), font_weight, font_stretch, font_style & TextServer::FONT_ITALIC);
#ifdef GDEXTENSION
for (int fb = 0; fb < fallback_font_name.size(); fb++) {
const String &E = fallback_font_name[fb];
#else
for (const String &E : fallback_font_name) {
#endif
SystemFontKey key = SystemFontKey(E, font_style & TextServer::FONT_ITALIC, font_weight, font_stretch, fdef, this);
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < sysf_cache.var.size(); face_idx++) {
const SystemFontCacheRec &F = sysf_cache.var[face_idx];
if (unlikely(!_font_has_char(F.rid, text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(F.rid);
int weight = _font_get_weight(F.rid);
int stretch = _font_get_stretch(F.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match != -1) {
gl.font_rid = sysf_cache.var[best_match].rid;
}
}
if (!gl.font_rid.is_valid()) {
if (system_fonts.has(key)) {
const SystemFontCache &sysf_cache = system_fonts[key];
if (sysf_cache.max_var == sysf_cache.var.size()) {
// All subfonts already tested, skip.
continue;
}
}
if (!system_font_data.has(E)) {
system_font_data[E] = FileAccess::get_file_as_bytes(E);
}
const PackedByteArray &font_data = system_font_data[E];
SystemFontCacheRec sysf;
sysf.rid = _create_font();
_font_set_data_ptr(sysf.rid, font_data.ptr(), font_data.size());
Dictionary var = dvar;
// Select matching style from collection.
int best_score = 0;
int best_match = -1;
for (int face_idx = 0; face_idx < _font_get_face_count(sysf.rid); face_idx++) {
_font_set_face_index(sysf.rid, face_idx);
if (unlikely(!_font_has_char(sysf.rid, text[0]))) {
continue;
}
BitField<FontStyle> style = _font_get_style(sysf.rid);
int weight = _font_get_weight(sysf.rid);
int stretch = _font_get_stretch(sysf.rid);
int score = (20 - Math::abs(weight - font_weight) / 50);
score += (20 - Math::abs(stretch - font_stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == bool(font_style & TextServer::FONT_ITALIC)) {
score += 30;
}
if (score >= best_score) {
best_score = score;
best_match = face_idx;
}
if (best_score == 70) {
break;
}
}
if (best_match == -1) {
_free_rid(sysf.rid);
continue;
} else {
_font_set_face_index(sysf.rid, best_match);
}
sysf.index = best_match;
// If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
if (best_score != 70) {
Dictionary ftr = _font_supported_variation_list(sysf.rid);
if (ftr.has(wdth_tag)) {
var[wdth_tag] = font_stretch;
_font_set_stretch(sysf.rid, font_stretch);
}
if (ftr.has(wgth_tag)) {
var[wgth_tag] = font_weight;
_font_set_weight(sysf.rid, font_weight);
}
if ((font_style & TextServer::FONT_ITALIC) && ftr.has(ital_tag)) {
var[ital_tag] = 1;
_font_set_style(sysf.rid, _font_get_style(sysf.rid) | TextServer::FONT_ITALIC);
}
}
_font_set_antialiasing(sysf.rid, key.antialiasing);
_font_set_generate_mipmaps(sysf.rid, key.mipmaps);
_font_set_multichannel_signed_distance_field(sysf.rid, key.msdf);
_font_set_msdf_pixel_range(sysf.rid, key.msdf_range);
_font_set_msdf_size(sysf.rid, key.msdf_source_size);
_font_set_fixed_size(sysf.rid, key.fixed_size);
_font_set_force_autohinter(sysf.rid, key.force_autohinter);
_font_set_hinting(sysf.rid, key.hinting);
_font_set_subpixel_positioning(sysf.rid, key.subpixel_positioning);
_font_set_variation_coordinates(sysf.rid, var);
_font_set_oversampling(sysf.rid, key.oversampling);
_font_set_embolden(sysf.rid, key.embolden);
_font_set_transform(sysf.rid, key.transform);
_font_set_spacing(sysf.rid, SPACING_TOP, key.extra_spacing[SPACING_TOP]);
_font_set_spacing(sysf.rid, SPACING_BOTTOM, key.extra_spacing[SPACING_BOTTOM]);
_font_set_spacing(sysf.rid, SPACING_SPACE, key.extra_spacing[SPACING_SPACE]);
_font_set_spacing(sysf.rid, SPACING_GLYPH, key.extra_spacing[SPACING_GLYPH]);
if (system_fonts.has(key)) {
system_fonts[key].var.push_back(sysf);
} else {
SystemFontCache &sysf_cache = system_fonts[key];
sysf_cache.max_var = _font_get_face_count(sysf.rid);
sysf_cache.var.push_back(sysf);
}
gl.font_rid = sysf.rid;
}
break;
}
} }
} }
prev_font = gl.font_rid; prev_font = gl.font_rid;

View File

@ -447,6 +447,7 @@ class TextServerFallback : public TextServerExtension {
double upos = 0.0; double upos = 0.0;
double uthk = 0.0; double uthk = 0.0;
char32_t el_char = 0x2026;
TrimData overrun_trim_data; TrimData overrun_trim_data;
bool fit_width_minimum_reached = false; bool fit_width_minimum_reached = false;
@ -555,6 +556,7 @@ class TextServerFallback : public TextServerExtension {
mutable HashMap<String, PackedByteArray> system_font_data; mutable HashMap<String, PackedByteArray> system_font_data;
void _realign(ShapedTextDataFallback *p_sd) const; void _realign(ShapedTextDataFallback *p_sd) const;
_FORCE_INLINE_ RID _find_sys_font_for_text(const RID &p_fdef, const String &p_script_code, const String &p_language, const String &p_text);
Mutex ft_mutex; Mutex ft_mutex;
@ -766,6 +768,9 @@ public:
MODBIND2(shaped_text_set_custom_punctuation, const RID &, const String &); MODBIND2(shaped_text_set_custom_punctuation, const RID &, const String &);
MODBIND1RC(String, shaped_text_get_custom_punctuation, const RID &); MODBIND1RC(String, shaped_text_get_custom_punctuation, const RID &);
MODBIND2(shaped_text_set_custom_ellipsis, const RID &, int64_t);
MODBIND1RC(int64_t, shaped_text_get_custom_ellipsis, const RID &);
MODBIND2(shaped_text_set_orientation, const RID &, Orientation); MODBIND2(shaped_text_set_orientation, const RID &, Orientation);
MODBIND1RC(Orientation, shaped_text_get_orientation, const RID &); MODBIND1RC(Orientation, shaped_text_get_orientation, const RID &);

View File

@ -240,10 +240,12 @@ void Label::_shape() {
if (i < jst_to_line) { if (i < jst_to_line) {
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags); TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags);
} else if (i == (visible_lines - 1)) { } else if (i == (visible_lines - 1)) {
TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
} }
} }
} else if (lines_hidden) { } else if (lines_hidden) {
TS->shaped_text_set_custom_ellipsis(lines_rid[visible_lines - 1], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags);
} }
} else { } else {
@ -268,9 +270,11 @@ void Label::_shape() {
if (i < jst_to_line && horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { if (i < jst_to_line && horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) {
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags); TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags);
overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE);
TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
} else { } else {
TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags);
} }
} }
@ -887,6 +891,27 @@ TextServer::OverrunBehavior Label::get_text_overrun_behavior() const {
return overrun_behavior; return overrun_behavior;
} }
void Label::set_ellipsis_char(const String &p_char) {
String c = p_char;
if (c.length() > 1) {
WARN_PRINT("Ellipsis must be exactly one character long (" + itos(c.length()) + " characters given).");
c = c.left(1);
}
if (el_char == c) {
return;
}
el_char = c;
lines_dirty = true;
queue_redraw();
if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {
update_minimum_size();
}
}
String Label::get_ellipsis_char() const {
return el_char;
}
String Label::get_text() const { String Label::get_text() const {
return text; return text;
} }
@ -1007,6 +1032,8 @@ void Label::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_tab_stops"), &Label::get_tab_stops); ClassDB::bind_method(D_METHOD("get_tab_stops"), &Label::get_tab_stops);
ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Label::set_text_overrun_behavior); ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Label::set_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Label::get_text_overrun_behavior); ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Label::get_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("set_ellipsis_char", "char"), &Label::set_ellipsis_char);
ClassDB::bind_method(D_METHOD("get_ellipsis_char"), &Label::get_ellipsis_char);
ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &Label::set_uppercase); ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &Label::set_uppercase);
ClassDB::bind_method(D_METHOD("is_uppercase"), &Label::is_uppercase); ClassDB::bind_method(D_METHOD("is_uppercase"), &Label::is_uppercase);
ClassDB::bind_method(D_METHOD("get_line_height", "line"), &Label::get_line_height, DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_line_height", "line"), &Label::get_line_height, DEFVAL(-1));
@ -1037,6 +1064,7 @@ void Label::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "is_clipping_text"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "is_clipping_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "ellipsis_char"), "set_ellipsis_char", "get_ellipsis_char");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase");
ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops");

View File

@ -45,6 +45,7 @@ private:
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF; TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
bool clip = false; bool clip = false;
String el_char = U"";
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING; TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
Size2 minsize; Size2 minsize;
bool uppercase = false; bool uppercase = false;
@ -147,6 +148,9 @@ public:
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const; TextServer::OverrunBehavior get_text_overrun_behavior() const;
void set_ellipsis_char(const String &p_char);
String get_ellipsis_char() const;
void set_lines_skipped(int p_lines); void set_lines_skipped(int p_lines);
int get_lines_skipped() const; int get_lines_skipped() const;

View File

@ -1914,12 +1914,15 @@ bool LineEdit::is_secret() const {
} }
void LineEdit::set_secret_character(const String &p_string) { void LineEdit::set_secret_character(const String &p_string) {
if (secret_character == p_string) { String c = p_string;
if (c.length() > 1) {
WARN_PRINT("Secret character must be exactly one character long (" + itos(c.length()) + " characters given).");
c = c.left(1);
}
if (secret_character == c) {
return; return;
} }
secret_character = c;
secret_character = p_string;
update_configuration_warnings();
_shape(); _shape();
queue_redraw(); queue_redraw();
} }
@ -2285,14 +2288,8 @@ void LineEdit::_shape() {
if (text.length() == 0 && ime_text.length() == 0) { if (text.length() == 0 && ime_text.length() == 0) {
t = placeholder_translated; t = placeholder_translated;
} else if (pass) { } else if (pass) {
// TODO: Integrate with text server to add support for non-latin scripts. String s = (secret_character.length() > 0) ? secret_character.left(1) : U"";
// Allow secret_character as empty strings, act like if a space was used as a secret character. t = s.repeat(text.length() + ime_text.length());
String secret = " ";
// Allow values longer than 1 character in the property, but trim characters after the first one.
if (!secret_character.is_empty()) {
secret = secret_character.left(1);
}
t = secret.repeat(text.length() + ime_text.length());
} else { } else {
if (ime_text.length() > 0) { if (ime_text.length() > 0) {
t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length()); t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length());

View File

@ -81,6 +81,10 @@ void TextLine::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ClassDB::bind_method(D_METHOD("set_ellipsis_char", "char"), &TextLine::set_ellipsis_char);
ClassDB::bind_method(D_METHOD("get_ellipsis_char"), &TextLine::get_ellipsis_char);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "ellipsis_char"), "set_ellipsis_char", "get_ellipsis_char");
ClassDB::bind_method(D_METHOD("get_objects"), &TextLine::get_objects); ClassDB::bind_method(D_METHOD("get_objects"), &TextLine::get_objects);
ClassDB::bind_method(D_METHOD("get_object_rect", "key"), &TextLine::get_object_rect); ClassDB::bind_method(D_METHOD("get_object_rect", "key"), &TextLine::get_object_rect);
@ -137,8 +141,10 @@ void TextLine::_shape() {
if (alignment == HORIZONTAL_ALIGNMENT_FILL) { if (alignment == HORIZONTAL_ALIGNMENT_FILL) {
TS->shaped_text_fit_to_width(rid, width, flags); TS->shaped_text_fit_to_width(rid, width, flags);
overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE);
TS->shaped_text_set_custom_ellipsis(rid, (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags); TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags);
} else { } else {
TS->shaped_text_set_custom_ellipsis(rid, (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags); TS->shaped_text_overrun_trim_to_width(rid, width, overrun_flags);
} }
} else if (alignment == HORIZONTAL_ALIGNMENT_FILL) { } else if (alignment == HORIZONTAL_ALIGNMENT_FILL) {
@ -306,6 +312,23 @@ TextServer::OverrunBehavior TextLine::get_text_overrun_behavior() const {
return overrun_behavior; return overrun_behavior;
} }
void TextLine::set_ellipsis_char(const String &p_char) {
String c = p_char;
if (c.length() > 1) {
WARN_PRINT("Ellipsis must be exactly one character long (" + itos(c.length()) + " characters given).");
c = c.left(1);
}
if (el_char == c) {
return;
}
el_char = c;
dirty = true;
}
String TextLine::get_ellipsis_char() const {
return el_char;
}
void TextLine::set_width(float p_width) { void TextLine::set_width(float p_width) {
width = p_width; width = p_width;
if (alignment == HORIZONTAL_ALIGNMENT_FILL || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { if (alignment == HORIZONTAL_ALIGNMENT_FILL || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) {

View File

@ -47,6 +47,7 @@ private:
float width = -1.0; float width = -1.0;
BitField<TextServer::JustificationFlag> flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; BitField<TextServer::JustificationFlag> flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA;
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
String el_char = U"";
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_TRIM_ELLIPSIS; TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_TRIM_ELLIPSIS;
Vector<float> tab_stops; Vector<float> tab_stops;
@ -90,6 +91,9 @@ public:
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const; TextServer::OverrunBehavior get_text_overrun_behavior() const;
void set_ellipsis_char(const String &p_char);
String get_ellipsis_char() const;
void set_width(float p_width); void set_width(float p_width);
float get_width() const; float get_width() const;

View File

@ -89,6 +89,10 @@ void TextParagraph::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior"); ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ClassDB::bind_method(D_METHOD("set_ellipsis_char", "char"), &TextParagraph::set_ellipsis_char);
ClassDB::bind_method(D_METHOD("get_ellipsis_char"), &TextParagraph::get_ellipsis_char);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "ellipsis_char"), "set_ellipsis_char", "get_ellipsis_char");
ClassDB::bind_method(D_METHOD("set_width", "width"), &TextParagraph::set_width); ClassDB::bind_method(D_METHOD("set_width", "width"), &TextParagraph::set_width);
ClassDB::bind_method(D_METHOD("get_width"), &TextParagraph::get_width); ClassDB::bind_method(D_METHOD("get_width"), &TextParagraph::get_width);
@ -252,10 +256,12 @@ void TextParagraph::_shape_lines() {
if (i < jst_to_line) { if (i < jst_to_line) {
TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags); TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags);
} else if (i == (visible_lines - 1)) { } else if (i == (visible_lines - 1)) {
TS->shaped_text_set_custom_ellipsis(lines_rid[visible_lines - 1], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], line_w, overrun_flags); TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], line_w, overrun_flags);
} }
} }
} else if (lines_hidden) { } else if (lines_hidden) {
TS->shaped_text_set_custom_ellipsis(lines_rid[visible_lines - 1], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], (visible_lines - 1 <= dropcap_lines) ? (width - h_offset) : width, overrun_flags); TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], (visible_lines - 1 <= dropcap_lines) ? (width - h_offset) : width, overrun_flags);
} }
} else { } else {
@ -281,9 +287,11 @@ void TextParagraph::_shape_lines() {
if (i < jst_to_line && alignment == HORIZONTAL_ALIGNMENT_FILL) { if (i < jst_to_line && alignment == HORIZONTAL_ALIGNMENT_FILL) {
TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags); TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags);
overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE);
TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], line_w, overrun_flags); TS->shaped_text_overrun_trim_to_width(lines_rid[i], line_w, overrun_flags);
TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS);
} else { } else {
TS->shaped_text_set_custom_ellipsis(lines_rid[i], (el_char.length() > 0) ? el_char[0] : 0x2026);
TS->shaped_text_overrun_trim_to_width(lines_rid[i], line_w, overrun_flags); TS->shaped_text_overrun_trim_to_width(lines_rid[i], line_w, overrun_flags);
} }
} }
@ -501,6 +509,23 @@ TextServer::OverrunBehavior TextParagraph::get_text_overrun_behavior() const {
return overrun_behavior; return overrun_behavior;
} }
void TextParagraph::set_ellipsis_char(const String &p_char) {
String c = p_char;
if (c.length() > 1) {
WARN_PRINT("Ellipsis must be exactly one character long (" + itos(c.length()) + " characters given).");
c = c.left(1);
}
if (el_char == c) {
return;
}
el_char = c;
lines_dirty = true;
}
String TextParagraph::get_ellipsis_char() const {
return el_char;
}
void TextParagraph::set_width(float p_width) { void TextParagraph::set_width(float p_width) {
_THREAD_SAFE_METHOD_ _THREAD_SAFE_METHOD_

View File

@ -56,6 +56,7 @@ private:
BitField<TextServer::LineBreakFlag> brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND; BitField<TextServer::LineBreakFlag> brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
String el_char = U"";
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING; TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
@ -112,6 +113,9 @@ public:
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const; TextServer::OverrunBehavior get_text_overrun_behavior() const;
void set_ellipsis_char(const String &p_char);
String get_ellipsis_char() const;
void set_width(float p_width); void set_width(float p_width);
float get_width() const; float get_width() const;

View File

@ -236,6 +236,9 @@ void TextServerExtension::_bind_methods() {
GDVIRTUAL_BIND(_shaped_text_set_custom_punctuation, "shaped", "punct"); GDVIRTUAL_BIND(_shaped_text_set_custom_punctuation, "shaped", "punct");
GDVIRTUAL_BIND(_shaped_text_get_custom_punctuation, "shaped"); GDVIRTUAL_BIND(_shaped_text_get_custom_punctuation, "shaped");
GDVIRTUAL_BIND(_shaped_text_set_custom_ellipsis, "shaped", "char");
GDVIRTUAL_BIND(_shaped_text_get_custom_ellipsis, "shaped");
GDVIRTUAL_BIND(_shaped_text_set_orientation, "shaped", "orientation"); GDVIRTUAL_BIND(_shaped_text_set_orientation, "shaped", "orientation");
GDVIRTUAL_BIND(_shaped_text_get_orientation, "shaped"); GDVIRTUAL_BIND(_shaped_text_get_orientation, "shaped");
@ -1058,6 +1061,16 @@ String TextServerExtension::shaped_text_get_custom_punctuation(const RID &p_shap
return ret; return ret;
} }
void TextServerExtension::shaped_text_set_custom_ellipsis(const RID &p_shaped, int64_t p_char) {
GDVIRTUAL_CALL(_shaped_text_set_custom_ellipsis, p_shaped, p_char);
}
int64_t TextServerExtension::shaped_text_get_custom_ellipsis(const RID &p_shaped) const {
int64_t ret = 0;
GDVIRTUAL_CALL(_shaped_text_get_custom_ellipsis, p_shaped, ret);
return ret;
}
void TextServerExtension::shaped_text_set_preserve_invalid(const RID &p_shaped, bool p_enabled) { void TextServerExtension::shaped_text_set_preserve_invalid(const RID &p_shaped, bool p_enabled) {
GDVIRTUAL_CALL(_shaped_text_set_preserve_invalid, p_shaped, p_enabled); GDVIRTUAL_CALL(_shaped_text_set_preserve_invalid, p_shaped, p_enabled);
} }

View File

@ -391,6 +391,11 @@ public:
GDVIRTUAL2(_shaped_text_set_custom_punctuation, RID, String); GDVIRTUAL2(_shaped_text_set_custom_punctuation, RID, String);
GDVIRTUAL1RC(String, _shaped_text_get_custom_punctuation, RID); GDVIRTUAL1RC(String, _shaped_text_get_custom_punctuation, RID);
virtual void shaped_text_set_custom_ellipsis(const RID &p_shaped, int64_t p_char) override;
virtual int64_t shaped_text_get_custom_ellipsis(const RID &p_shaped) const override;
GDVIRTUAL2(_shaped_text_set_custom_ellipsis, RID, int64_t);
GDVIRTUAL1RC(int64_t, _shaped_text_get_custom_ellipsis, RID);
virtual void shaped_text_set_orientation(const RID &p_shaped, Orientation p_orientation = ORIENTATION_HORIZONTAL) override; virtual void shaped_text_set_orientation(const RID &p_shaped, Orientation p_orientation = ORIENTATION_HORIZONTAL) override;
virtual Orientation shaped_text_get_orientation(const RID &p_shaped) const override; virtual Orientation shaped_text_get_orientation(const RID &p_shaped) const override;
GDVIRTUAL2(_shaped_text_set_orientation, RID, Orientation); GDVIRTUAL2(_shaped_text_set_orientation, RID, Orientation);

View File

@ -390,6 +390,9 @@ void TextServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("shaped_text_set_custom_punctuation", "shaped", "punct"), &TextServer::shaped_text_set_custom_punctuation); ClassDB::bind_method(D_METHOD("shaped_text_set_custom_punctuation", "shaped", "punct"), &TextServer::shaped_text_set_custom_punctuation);
ClassDB::bind_method(D_METHOD("shaped_text_get_custom_punctuation", "shaped"), &TextServer::shaped_text_get_custom_punctuation); ClassDB::bind_method(D_METHOD("shaped_text_get_custom_punctuation", "shaped"), &TextServer::shaped_text_get_custom_punctuation);
ClassDB::bind_method(D_METHOD("shaped_text_set_custom_ellipsis", "shaped", "char"), &TextServer::shaped_text_set_custom_ellipsis);
ClassDB::bind_method(D_METHOD("shaped_text_get_custom_ellipsis", "shaped"), &TextServer::shaped_text_get_custom_ellipsis);
ClassDB::bind_method(D_METHOD("shaped_text_set_orientation", "shaped", "orientation"), &TextServer::shaped_text_set_orientation, DEFVAL(ORIENTATION_HORIZONTAL)); ClassDB::bind_method(D_METHOD("shaped_text_set_orientation", "shaped", "orientation"), &TextServer::shaped_text_set_orientation, DEFVAL(ORIENTATION_HORIZONTAL));
ClassDB::bind_method(D_METHOD("shaped_text_get_orientation", "shaped"), &TextServer::shaped_text_get_orientation); ClassDB::bind_method(D_METHOD("shaped_text_get_orientation", "shaped"), &TextServer::shaped_text_get_orientation);

View File

@ -427,6 +427,9 @@ public:
virtual void shaped_text_set_custom_punctuation(const RID &p_shaped, const String &p_punct) = 0; virtual void shaped_text_set_custom_punctuation(const RID &p_shaped, const String &p_punct) = 0;
virtual String shaped_text_get_custom_punctuation(const RID &p_shaped) const = 0; virtual String shaped_text_get_custom_punctuation(const RID &p_shaped) const = 0;
virtual void shaped_text_set_custom_ellipsis(const RID &p_shaped, int64_t p_char) = 0;
virtual int64_t shaped_text_get_custom_ellipsis(const RID &p_shaped) const = 0;
virtual void shaped_text_set_orientation(const RID &p_shaped, Orientation p_orientation = ORIENTATION_HORIZONTAL) = 0; virtual void shaped_text_set_orientation(const RID &p_shaped, Orientation p_orientation = ORIENTATION_HORIZONTAL) = 0;
virtual Orientation shaped_text_get_orientation(const RID &p_shaped) const = 0; virtual Orientation shaped_text_get_orientation(const RID &p_shaped) const = 0;