diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index f13f1bdcf40..fda45a42c8d 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -45,6 +45,9 @@ If [code]true[/code], the Label only shows the text that fits inside its bounding rectangle and will clip text horizontally. + + Ellipsis character used for text clipping. + Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. diff --git a/doc/classes/TextLine.xml b/doc/classes/TextLine.xml index e65006716d7..2e4a3f7e4c8 100644 --- a/doc/classes/TextLine.xml +++ b/doc/classes/TextLine.xml @@ -151,6 +151,9 @@ Text writing direction. + + Ellipsis character used for text clipping. + Line alignment rules. For more info see [TextServer]. diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml index a8a4c3a7641..541078ed223 100644 --- a/doc/classes/TextParagraph.xml +++ b/doc/classes/TextParagraph.xml @@ -274,6 +274,9 @@ Text writing direction. + + Ellipsis character used for text clipping. + Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index f4246a8c504..09f484f5230 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -1243,6 +1243,13 @@ Returns array of the composite character boundaries. + + + + + Returns ellipsis character used for text clipping. + + @@ -1547,6 +1554,14 @@ Override ranges should cover full source text without overlaps. BiDi algorithm will be used on each range separately. + + + + + + Sets ellipsis character used for text clipping. + + diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml index 267e0b65cb0..9555718b262 100644 --- a/doc/classes/TextServerExtension.xml +++ b/doc/classes/TextServerExtension.xml @@ -1073,6 +1073,12 @@ + + + + + + @@ -1329,6 +1335,13 @@ + + + + + + + diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 6d0a398218f..d93aa1fcd8a 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -4048,6 +4048,20 @@ String TextServerAdvanced::_shaped_text_get_custom_punctuation(const RID &p_shap 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) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL(sd); @@ -4800,6 +4814,166 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac 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 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 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 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 p_trim_flags) { ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped_line); 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 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. 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; 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]; + found_el_char = true; 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; - if (!_font_has_char(whitespace_gl_font_rid, '.')) { + if (!_font_has_char(whitespace_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], ' ')) { @@ -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(); - 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(); int ellipsis_width = 0; 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; @@ -4951,7 +5157,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ if (dot_gl_idx != 0) { Glyph gl; gl.count = 1; - gl.repeat = 3; + gl.repeat = (found_el_char ? 1 : 3); gl.advance = dot_adv.x; gl.index = dot_gl_idx; gl.font_rid = dot_gl_font_rid; @@ -5580,166 +5786,12 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star break; } } - String text = p_sd->text.substr(p_start, next - p_start); - - String font_name = _font_get_name(fdef); - BitField 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 }; hb_tag_to_string(hb_script_to_iso15924_tag(p_script), 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); -#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 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 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; - } + String text = p_sd->text.substr(p_start, next - p_start); + f = _find_sys_font_for_text(fdef, script_code, p_sd->spans[p_span].language, text); } } diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index cbd2911aaf5..e5d0ab3105d 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -502,6 +502,7 @@ class TextServerAdvanced : public TextServerExtension { double upos = 0.0; double uthk = 0.0; + char32_t el_char = 0x2026; TrimData overrun_trim_data; 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; 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 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); + _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 &r_ftrs); @@ -899,6 +901,9 @@ public: MODBIND2(shaped_text_set_custom_punctuation, const RID &, const String &); 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); MODBIND1RC(Orientation, shaped_text_get_orientation, const RID &); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index f12275d10c7..9f21642a176 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -2905,6 +2905,20 @@ String TextServerFallback::_shaped_text_get_custom_punctuation(const RID &p_shap 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) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL(sd); @@ -3601,6 +3615,168 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap 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 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 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 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 p_trim_flags) { ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line); 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 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. 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; 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]; + found_el_char = true; 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; - if (!_font_has_char(whitespace_gl_font_rid, '.')) { + if (!_font_has_char(whitespace_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], ' ')) { @@ -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(); - 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(); int ellipsis_width = 0; 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; @@ -3742,7 +3950,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ if (dot_gl_idx != 0) { Glyph gl; gl.count = 1; - gl.repeat = 3; + gl.repeat = (found_el_char ? 1 : 3); gl.advance = dot_adv.x; gl.index = dot_gl_idx; 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]; if (_font_is_allow_system_fallback(fdef)) { String text = sd->text.substr(j, 1); - String font_name = _font_get_name(fdef); - BitField 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 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 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; - } + gl.font_rid = _find_sys_font_for_text(fdef, String(), span.language, text); } } prev_font = gl.font_rid; diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 5c30ea0c05e..68e0cbff6cd 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -447,6 +447,7 @@ class TextServerFallback : public TextServerExtension { double upos = 0.0; double uthk = 0.0; + char32_t el_char = 0x2026; TrimData overrun_trim_data; bool fit_width_minimum_reached = false; @@ -555,6 +556,7 @@ class TextServerFallback : public TextServerExtension { mutable HashMap system_font_data; 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; @@ -766,6 +768,9 @@ public: MODBIND2(shaped_text_set_custom_punctuation, const RID &, const String &); 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); MODBIND1RC(Orientation, shaped_text_get_orientation, const RID &); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 2fbd29b0480..3df0d971602 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -240,10 +240,12 @@ void Label::_shape() { if (i < jst_to_line) { TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags); } 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); } } } 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); } } else { @@ -268,9 +270,11 @@ void Label::_shape() { if (i < jst_to_line && horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { TS->shaped_text_fit_to_width(lines_rid[i], width, line_jst_flags); 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_fit_to_width(lines_rid[i], width, line_jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); } 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); } } @@ -887,6 +891,27 @@ TextServer::OverrunBehavior Label::get_text_overrun_behavior() const { 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 { 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("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("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("is_uppercase"), &Label::is_uppercase); 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::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::PACKED_FLOAT32_ARRAY, "tab_stops"), "set_tab_stops", "get_tab_stops"); diff --git a/scene/gui/label.h b/scene/gui/label.h index a5126c6b91c..44443e3eb4f 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -45,6 +45,7 @@ private: TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF; BitField jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; bool clip = false; + String el_char = U"…"; TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING; Size2 minsize; bool uppercase = false; @@ -147,6 +148,9 @@ public: void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); 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); int get_lines_skipped() const; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 5ed1a9d5e3a..5cca09bcf68 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -1914,12 +1914,15 @@ bool LineEdit::is_secret() const { } 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; } - - secret_character = p_string; - update_configuration_warnings(); + secret_character = c; _shape(); queue_redraw(); } @@ -2285,14 +2288,8 @@ void LineEdit::_shape() { if (text.length() == 0 && ime_text.length() == 0) { t = placeholder_translated; } else if (pass) { - // TODO: Integrate with text server to add support for non-latin scripts. - // Allow secret_character as empty strings, act like if a space was used as a secret character. - 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()); + String s = (secret_character.length() > 0) ? secret_character.left(1) : U"•"; + t = s.repeat(text.length() + ime_text.length()); } else { if (ime_text.length() > 0) { t = text.substr(0, caret_column) + ime_text + text.substr(caret_column, text.length()); diff --git a/scene/resources/text_line.cpp b/scene/resources/text_line.cpp index 98527da96bb..76c0a28ea43 100644 --- a/scene/resources/text_line.cpp +++ b/scene/resources/text_line.cpp @@ -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"); + 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_object_rect", "key"), &TextLine::get_object_rect); @@ -137,8 +141,10 @@ void TextLine::_shape() { if (alignment == HORIZONTAL_ALIGNMENT_FILL) { TS->shaped_text_fit_to_width(rid, width, flags); 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); } 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); } } else if (alignment == HORIZONTAL_ALIGNMENT_FILL) { @@ -306,6 +312,23 @@ TextServer::OverrunBehavior TextLine::get_text_overrun_behavior() const { 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) { width = p_width; if (alignment == HORIZONTAL_ALIGNMENT_FILL || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { diff --git a/scene/resources/text_line.h b/scene/resources/text_line.h index cc52d2e6a79..795bdeb15c5 100644 --- a/scene/resources/text_line.h +++ b/scene/resources/text_line.h @@ -47,6 +47,7 @@ private: float width = -1.0; BitField flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; + String el_char = U"…"; TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_TRIM_ELLIPSIS; Vector tab_stops; @@ -90,6 +91,9 @@ public: void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); 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); float get_width() const; diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 147c9044b80..5da47966ddd 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -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"); + 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("get_width"), &TextParagraph::get_width); @@ -252,10 +256,12 @@ void TextParagraph::_shape_lines() { if (i < jst_to_line) { TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags); } 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); } } } 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); } } else { @@ -281,9 +287,11 @@ void TextParagraph::_shape_lines() { if (i < jst_to_line && alignment == HORIZONTAL_ALIGNMENT_FILL) { TS->shaped_text_fit_to_width(lines_rid[i], line_w, jst_flags); 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_fit_to_width(lines_rid[i], line_w, jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); } 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); } } @@ -501,6 +509,23 @@ TextServer::OverrunBehavior TextParagraph::get_text_overrun_behavior() const { 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) { _THREAD_SAFE_METHOD_ diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h index 31fc95ac025..28c69967ac2 100644 --- a/scene/resources/text_paragraph.h +++ b/scene/resources/text_paragraph.h @@ -56,6 +56,7 @@ private: BitField brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND; BitField 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; HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; @@ -112,6 +113,9 @@ public: void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior); 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); float get_width() const; diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp index d6844bba6a0..ec622cdb1c7 100644 --- a/servers/text/text_server_extension.cpp +++ b/servers/text/text_server_extension.cpp @@ -236,6 +236,9 @@ void TextServerExtension::_bind_methods() { GDVIRTUAL_BIND(_shaped_text_set_custom_punctuation, "shaped", "punct"); 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_get_orientation, "shaped"); @@ -1058,6 +1061,16 @@ String TextServerExtension::shaped_text_get_custom_punctuation(const RID &p_shap 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) { GDVIRTUAL_CALL(_shaped_text_set_preserve_invalid, p_shaped, p_enabled); } diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h index 7605ed480dd..d0d7ee31ada 100644 --- a/servers/text/text_server_extension.h +++ b/servers/text/text_server_extension.h @@ -391,6 +391,11 @@ public: GDVIRTUAL2(_shaped_text_set_custom_punctuation, RID, String); 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 Orientation shaped_text_get_orientation(const RID &p_shaped) const override; GDVIRTUAL2(_shaped_text_set_orientation, RID, Orientation); diff --git a/servers/text_server.cpp b/servers/text_server.cpp index 302d14bd8d9..b282c517d35 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -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_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_get_orientation", "shaped"), &TextServer::shaped_text_get_orientation); diff --git a/servers/text_server.h b/servers/text_server.h index fdc0e9fc4d2..70d2129b51a 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -427,6 +427,9 @@ public: 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 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 Orientation shaped_text_get_orientation(const RID &p_shaped) const = 0;