diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 9bc75d34d10..94f310ebfa6 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -196,7 +196,7 @@ - + @@ -215,7 +215,7 @@ - + @@ -311,7 +311,7 @@ - + @@ -348,7 +348,7 @@ - + diff --git a/doc/classes/Font.xml b/doc/classes/Font.xml index 548376f262b..03cd7f835a4 100644 --- a/doc/classes/Font.xml +++ b/doc/classes/Font.xml @@ -45,7 +45,7 @@ - + @@ -65,7 +65,7 @@ - + @@ -82,7 +82,7 @@ - + @@ -100,7 +100,7 @@ - + @@ -201,7 +201,7 @@ - + @@ -234,7 +234,7 @@ - + diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index b002d456f16..6a26da9f4f0 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -49,6 +49,9 @@ Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. + + Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. + A [LabelSettings] resource that can be shared between multiple [Label] nodes. Takes priority over theme properties. diff --git a/doc/classes/Label3D.xml b/doc/classes/Label3D.xml index c605f8bec1f..7499074dc9a 100644 --- a/doc/classes/Label3D.xml +++ b/doc/classes/Label3D.xml @@ -71,6 +71,9 @@ Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. + + Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. + Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index a93fdfd2965..c44f54749b6 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -409,6 +409,7 @@ + Adds a [code][p][/code] tag to the tag stack. diff --git a/doc/classes/TextMesh.xml b/doc/classes/TextMesh.xml index 48b2a9c943c..9258dca6573 100644 --- a/doc/classes/TextMesh.xml +++ b/doc/classes/TextMesh.xml @@ -29,6 +29,9 @@ Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. + + Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. + Language code used for text shaping algorithms, if left empty current locale is used instead. diff --git a/doc/classes/TextParagraph.xml b/doc/classes/TextParagraph.xml index caba9d0a461..a6eabd415c1 100644 --- a/doc/classes/TextParagraph.xml +++ b/doc/classes/TextParagraph.xml @@ -274,8 +274,8 @@ Text writing direction. - - Line alignment rules. For more info see [TextServer]. + + Line fill alignment rules. For more info see [enum TextServer.JustificationFlag]. Limits the lines of text shown. diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index 1763df938a5..207349a76aa 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -1161,7 +1161,7 @@ - + Adjusts text width to fit to specified width, returns new text width. @@ -1395,6 +1395,13 @@ Breaks text into words and returns array of character ranges. Use [param grapheme_flags] to set what characters are used for breaking (see [enum GraphemeFlag]). + + + + + Returns [code]true[/code], if text buffer contents any visible characters. + + @@ -1671,6 +1678,15 @@ Apply justification to the trimmed line with ellipsis. + + Do not apply justification to the last line of the paragraph. + + + Do not apply justification to the last line of the paragraph with visible characters (takes precedence over [constant JUSTIFICATION_SKIP_LAST_LINE]). + + + Always apply justification to the paragraphs with a single line ([constant JUSTIFICATION_SKIP_LAST_LINE] and [constant JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS] are ignored). + Autowrap is disabled. diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml index a3891442b74..0f04c3a10df 100644 --- a/doc/classes/TextServerExtension.xml +++ b/doc/classes/TextServerExtension.xml @@ -1002,7 +1002,7 @@ - + diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp index 5fc36abb76f..198fb70ad75 100644 --- a/scene/3d/label_3d.cpp +++ b/scene/3d/label_3d.cpp @@ -88,6 +88,9 @@ void Label3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Label3D::set_autowrap_mode); ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &Label3D::get_autowrap_mode); + ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &Label3D::set_justification_flags); + ClassDB::bind_method(D_METHOD("get_justification_flags"), &Label3D::get_justification_flags); + ClassDB::bind_method(D_METHOD("set_width", "width"), &Label3D::set_width); ClassDB::bind_method(D_METHOD("get_width"), &Label3D::get_width); @@ -157,6 +160,7 @@ void Label3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "line_spacing", PROPERTY_HINT_NONE, "suffix:px"), "set_line_spacing", "get_line_spacing"); ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_NONE, "suffix:px"), "set_width", "get_width"); ADD_GROUP("BiDi", ""); @@ -533,8 +537,24 @@ void Label3D::_shape() { } if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { - for (int i = 0; i < lines_rid.size() - 1; i++) { - TS->shaped_text_fit_to_width(lines_rid[i], (width > 0) ? width : max_line_w, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA); + int jst_to_line = lines_rid.size(); + if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { + jst_to_line = lines_rid.size(); + } else { + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { + jst_to_line = lines_rid.size() - 1; + } + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { + for (int i = lines_rid.size() - 1; i >= 0; i--) { + if (TS->shaped_text_has_visible_chars(lines_rid[i])) { + jst_to_line = i; + break; + } + } + } + } + for (int i = 0; i < jst_to_line; i++) { + TS->shaped_text_fit_to_width(lines_rid[i], (width > 0) ? width : max_line_w, jst_flags); } } dirty_lines = false; @@ -871,6 +891,18 @@ TextServer::AutowrapMode Label3D::get_autowrap_mode() const { return autowrap_mode; } +void Label3D::set_justification_flags(BitField p_flags) { + if (jst_flags != p_flags) { + jst_flags = p_flags; + dirty_lines = true; + _queue_update(); + } +} + +BitField Label3D::get_justification_flags() const { + return jst_flags; +} + void Label3D::set_width(float p_width) { if (width != p_width) { width = p_width; diff --git a/scene/3d/label_3d.h b/scene/3d/label_3d.h index 912f4853548..332eaac18dd 100644 --- a/scene/3d/label_3d.h +++ b/scene/3d/label_3d.h @@ -112,6 +112,7 @@ private: bool uppercase = false; 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; float width = 500.0; int font_size = 32; @@ -215,6 +216,9 @@ public: void set_autowrap_mode(TextServer::AutowrapMode p_mode); TextServer::AutowrapMode get_autowrap_mode() const; + void set_justification_flags(BitField p_flags); + BitField get_justification_flags() const; + void set_width(float p_width); float get_width() const; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 59f75118946..a9f704e904d 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -55,6 +55,20 @@ TextServer::AutowrapMode Label::get_autowrap_mode() const { return autowrap_mode; } +void Label::set_justification_flags(BitField p_flags) { + if (jst_flags == p_flags) { + return; + } + + jst_flags = p_flags; + lines_dirty = true; + queue_redraw(); +} + +BitField Label::get_justification_flags() const { + return jst_flags; +} + void Label::set_uppercase(bool p_uppercase) { if (uppercase == p_uppercase) { return; @@ -198,11 +212,27 @@ void Label::_shape() { overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS); } if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { + int jst_to_line = visible_lines; + if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { + jst_to_line = lines_rid.size(); + } else { + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { + jst_to_line = visible_lines - 1; + } + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { + for (int i = visible_lines - 1; i >= 0; i--) { + if (TS->shaped_text_has_visible_chars(lines_rid[i])) { + jst_to_line = i; + break; + } + } + } + } for (int i = 0; i < lines_rid.size(); i++) { - if (i < visible_lines - 1 || lines_rid.size() == 1) { - TS->shaped_text_fit_to_width(lines_rid[i], width); + if (i < jst_to_line) { + TS->shaped_text_fit_to_width(lines_rid[i], width, jst_flags); } else if (i == (visible_lines - 1)) { - TS->shaped_text_overrun_trim_to_width(lines_rid[visible_lines - 1], width, overrun_flags); + TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); } } } else if (lines_hidden) { @@ -210,12 +240,28 @@ void Label::_shape() { } } else { // Autowrap disabled. + int jst_to_line = lines_rid.size(); + if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { + jst_to_line = lines_rid.size(); + } else { + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { + jst_to_line = lines_rid.size() - 1; + } + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { + for (int i = lines_rid.size() - 1; i >= 0; i--) { + if (TS->shaped_text_has_visible_chars(lines_rid[i])) { + jst_to_line = i; + break; + } + } + } + } for (int i = 0; i < lines_rid.size(); i++) { - if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { - TS->shaped_text_fit_to_width(lines_rid[i], width); + if (i < jst_to_line && horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { + TS->shaped_text_fit_to_width(lines_rid[i], width, jst_flags); overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); - TS->shaped_text_fit_to_width(lines_rid[i], width, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); + TS->shaped_text_fit_to_width(lines_rid[i], width, jst_flags | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); } else { TS->shaped_text_overrun_trim_to_width(lines_rid[i], width, overrun_flags); } @@ -936,6 +982,8 @@ void Label::_bind_methods() { ClassDB::bind_method(D_METHOD("get_language"), &Label::get_language); ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Label::set_autowrap_mode); ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &Label::get_autowrap_mode); + ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &Label::set_justification_flags); + ClassDB::bind_method(D_METHOD("get_justification_flags"), &Label::get_justification_flags); ClassDB::bind_method(D_METHOD("set_clip_text", "enable"), &Label::set_clip_text); ClassDB::bind_method(D_METHOD("is_clipping_text"), &Label::is_clipping_text); ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Label::set_text_overrun_behavior); @@ -966,6 +1014,8 @@ void Label::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom,Fill"), "set_vertical_alignment", "get_vertical_alignment"); ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags"); + 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::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); diff --git a/scene/gui/label.h b/scene/gui/label.h index 23505252360..5102750bc36 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -43,6 +43,7 @@ private: String text; String xl_text; 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; TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING; Size2 minsize; @@ -122,6 +123,9 @@ public: void set_autowrap_mode(TextServer::AutowrapMode p_mode); TextServer::AutowrapMode get_autowrap_mode() const; + void set_justification_flags(BitField p_flags); + BitField get_justification_flags() const; + void set_uppercase(bool p_uppercase); bool is_uppercase() const; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 43c68a8ecad..185db1b972c 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -471,7 +471,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref // Clear cache. l.text_buf->clear(); l.text_buf->set_break_flags(autowrap_flags); - l.text_buf->set_justification_flags(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); + l.text_buf->set_justification_flags(_find_jst_flags(l.from)); l.char_offset = *r_char_offset; l.char_count = 0; @@ -2453,6 +2453,21 @@ int RichTextLabel::_find_margin(Item *p_item, const Ref &p_base_font, int return margin; } +BitField RichTextLabel::_find_jst_flags(Item *p_item) { + Item *item = p_item; + + while (item) { + if (item->type == ITEM_PARAGRAPH) { + ItemParagraph *p = static_cast(item); + return p->jst_flags; + } + + item = item->parent; + } + + return default_jst_flags; +} + HorizontalAlignment RichTextLabel::_find_alignment(Item *p_item) { Item *item = p_item; @@ -3297,7 +3312,7 @@ void RichTextLabel::push_strikethrough() { _add_item(item, true); } -void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, TextServer::StructuredTextParser p_st_parser) { +void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction, const String &p_language, TextServer::StructuredTextParser p_st_parser, BitField p_jst_flags) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -3308,6 +3323,7 @@ void RichTextLabel::push_paragraph(HorizontalAlignment p_alignment, Control::Tex item->direction = p_direction; item->language = p_language; item->st_parser = p_st_parser; + item->jst_flags = p_jst_flags; _add_item(item, true, true); } @@ -4079,10 +4095,30 @@ void RichTextLabel::append_text(const String &p_bbcode) { Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; String lang; TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; + BitField jst_flags = default_jst_flags; for (int i = 0; i < subtag.size(); i++) { Vector subtag_a = subtag[i].split("="); if (subtag_a.size() == 2) { - if (subtag_a[0] == "align") { + if (subtag_a[0] == "justification_flags" || subtag_a[0] == "jst") { + Vector subtag_b = subtag_a[1].split(","); + for (const String &E : subtag_b) { + if (E == "kashida" || E == "k") { + jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA); + } else if (E == "word" || E == "w") { + jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND); + } else if (E == "trim" || E == "tr") { + jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); + } else if (E == "after_last_tab" || E == "lt") { + jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB); + } else if (E == "skip_last" || E == "sl") { + jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE); + } else if (E == "skip_last_with_chars" || E == "sv") { + jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS); + } else if (E == "do_not_skip_singe" || E == "ns") { + jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); + } + } + } else if (subtag_a[0] == "align") { if (subtag_a[1] == "l" || subtag_a[1] == "left") { alignment = HORIZONTAL_ALIGNMENT_LEFT; } else if (subtag_a[1] == "c" || subtag_a[1] == "center") { @@ -4121,7 +4157,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } } - push_paragraph(alignment, dir, lang, st_parser_type); + push_paragraph(alignment, dir, lang, st_parser_type, jst_flags); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag == "url") { @@ -5370,7 +5406,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("push_color", "color"), &RichTextLabel::push_color); ClassDB::bind_method(D_METHOD("push_outline_size", "outline_size"), &RichTextLabel::push_outline_size); ClassDB::bind_method(D_METHOD("push_outline_color", "color"), &RichTextLabel::push_outline_color); - ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(TextServer::STRUCTURED_TEXT_DEFAULT)); + ClassDB::bind_method(D_METHOD("push_paragraph", "alignment", "base_direction", "language", "st_parser", "justification_flags"), &RichTextLabel::push_paragraph, DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(""), DEFVAL(TextServer::STRUCTURED_TEXT_DEFAULT), DEFVAL(TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)); ClassDB::bind_method(D_METHOD("push_indent", "level"), &RichTextLabel::push_indent); ClassDB::bind_method(D_METHOD("push_list", "level", "type", "capitalize", "bullet"), &RichTextLabel::push_list, DEFVAL(String::utf8("•"))); ClassDB::bind_method(D_METHOD("push_meta", "data"), &RichTextLabel::push_meta); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 567528652c1..3e3413d47ab 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -240,6 +240,7 @@ private: String language; Control::TextDirection direction = Control::TEXT_DIRECTION_AUTO; TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; + BitField jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; ItemParagraph() { type = ITEM_PARAGRAPH; } }; @@ -405,6 +406,7 @@ private: bool use_selected_font_color = false; HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT; + BitField default_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; ItemMeta *meta_hovering = nullptr; Variant current_meta; @@ -492,6 +494,7 @@ private: int _find_list(Item *p_item, Vector &r_index, Vector &r_list); int _find_margin(Item *p_item, const Ref &p_base_font, int p_base_font_size); HorizontalAlignment _find_alignment(Item *p_item); + BitField _find_jst_flags(Item *p_item); TextServer::Direction _find_direction(Item *p_item); TextServer::StructuredTextParser _find_stt(Item *p_item); String _find_language(Item *p_item); @@ -593,7 +596,7 @@ public: void push_outline_color(const Color &p_color); void push_underline(); void push_strikethrough(); - void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", TextServer::StructuredTextParser p_st_parser = TextServer::STRUCTURED_TEXT_DEFAULT); + void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", TextServer::StructuredTextParser p_st_parser = TextServer::STRUCTURED_TEXT_DEFAULT, BitField p_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); void push_indent(int p_level); void push_list(int p_level, ListType p_list, bool p_capitalize, const String &p_bullet = String::utf8("•")); void push_meta(const Variant &p_meta); diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index cb115a6d941..afadd96d9bc 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -1031,10 +1031,10 @@ void CanvasItem::_bind_methods() { ClassDB::bind_method(D_METHOD("draw_primitive", "points", "colors", "uvs", "texture"), &CanvasItem::draw_primitive, DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("draw_polygon", "points", "colors", "uvs", "texture"), &CanvasItem::draw_polygon, DEFVAL(PackedVector2Array()), DEFVAL(Ref())); ClassDB::bind_method(D_METHOD("draw_colored_polygon", "points", "color", "uvs", "texture"), &CanvasItem::draw_colored_polygon, DEFVAL(PackedVector2Array()), DEFVAL(Ref())); - ClassDB::bind_method(D_METHOD("draw_string", "font", "pos", "text", "alignment", "width", "font_size", "modulate", "jst_flags", "direction", "orientation"), &CanvasItem::draw_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); - ClassDB::bind_method(D_METHOD("draw_multiline_string", "font", "pos", "text", "alignment", "width", "font_size", "max_lines", "modulate", "brk_flags", "jst_flags", "direction", "orientation"), &CanvasItem::draw_multiline_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); - ClassDB::bind_method(D_METHOD("draw_string_outline", "font", "pos", "text", "alignment", "width", "font_size", "size", "modulate", "jst_flags", "direction", "orientation"), &CanvasItem::draw_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); - ClassDB::bind_method(D_METHOD("draw_multiline_string_outline", "font", "pos", "text", "alignment", "width", "font_size", "max_lines", "size", "modulate", "brk_flags", "jst_flags", "direction", "orientation"), &CanvasItem::draw_multiline_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_string", "font", "pos", "text", "alignment", "width", "font_size", "modulate", "justification_flags", "direction", "orientation"), &CanvasItem::draw_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_multiline_string", "font", "pos", "text", "alignment", "width", "font_size", "max_lines", "modulate", "brk_flags", "justification_flags", "direction", "orientation"), &CanvasItem::draw_multiline_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_string_outline", "font", "pos", "text", "alignment", "width", "font_size", "size", "modulate", "justification_flags", "direction", "orientation"), &CanvasItem::draw_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_multiline_string_outline", "font", "pos", "text", "alignment", "width", "font_size", "max_lines", "size", "modulate", "brk_flags", "justification_flags", "direction", "orientation"), &CanvasItem::draw_multiline_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); ClassDB::bind_method(D_METHOD("draw_char", "font", "pos", "char", "font_size", "modulate"), &CanvasItem::draw_char, DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0))); ClassDB::bind_method(D_METHOD("draw_char_outline", "font", "pos", "char", "font_size", "size", "modulate"), &CanvasItem::draw_char_outline, DEFVAL(Font::DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0))); ClassDB::bind_method(D_METHOD("draw_mesh", "mesh", "texture", "transform", "modulate"), &CanvasItem::draw_mesh, DEFVAL(Transform2D()), DEFVAL(Color(1, 1, 1, 1))); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index c4b5834ad52..a9d8fc38a4e 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -72,14 +72,14 @@ void Font::_bind_methods() { // Drawing string. ClassDB::bind_method(D_METHOD("set_cache_capacity", "single_line", "multi_line"), &Font::set_cache_capacity); - ClassDB::bind_method(D_METHOD("get_string_size", "text", "alignment", "width", "font_size", "jst_flags", "direction", "orientation"), &Font::get_string_size, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); - ClassDB::bind_method(D_METHOD("get_multiline_string_size", "text", "alignment", "width", "font_size", "max_lines", "brk_flags", "jst_flags", "direction", "orientation"), &Font::get_multiline_string_size, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("get_string_size", "text", "alignment", "width", "font_size", "justification_flags", "direction", "orientation"), &Font::get_string_size, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("get_multiline_string_size", "text", "alignment", "width", "font_size", "max_lines", "brk_flags", "justification_flags", "direction", "orientation"), &Font::get_multiline_string_size, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); - ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "alignment", "width", "font_size", "modulate", "jst_flags", "direction", "orientation"), &Font::draw_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); - ClassDB::bind_method(D_METHOD("draw_multiline_string", "canvas_item", "pos", "text", "alignment", "width", "font_size", "max_lines", "modulate", "brk_flags", "jst_flags", "direction", "orientation"), &Font::draw_multiline_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_string", "canvas_item", "pos", "text", "alignment", "width", "font_size", "modulate", "justification_flags", "direction", "orientation"), &Font::draw_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_multiline_string", "canvas_item", "pos", "text", "alignment", "width", "font_size", "max_lines", "modulate", "brk_flags", "justification_flags", "direction", "orientation"), &Font::draw_multiline_string, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); - ClassDB::bind_method(D_METHOD("draw_string_outline", "canvas_item", "pos", "text", "alignment", "width", "font_size", "size", "modulate", "jst_flags", "direction", "orientation"), &Font::draw_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); - ClassDB::bind_method(D_METHOD("draw_multiline_string_outline", "canvas_item", "pos", "text", "alignment", "width", "font_size", "max_lines", "size", "modulate", "brk_flags", "jst_flags", "direction", "orientation"), &Font::draw_multiline_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_string_outline", "canvas_item", "pos", "text", "alignment", "width", "font_size", "size", "modulate", "justification_flags", "direction", "orientation"), &Font::draw_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); + ClassDB::bind_method(D_METHOD("draw_multiline_string_outline", "canvas_item", "pos", "text", "alignment", "width", "font_size", "max_lines", "size", "modulate", "brk_flags", "justification_flags", "direction", "orientation"), &Font::draw_multiline_string_outline, DEFVAL(HORIZONTAL_ALIGNMENT_LEFT), DEFVAL(-1), DEFVAL(DEFAULT_FONT_SIZE), DEFVAL(-1), DEFVAL(1), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND), DEFVAL(TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND), DEFVAL(TextServer::DIRECTION_AUTO), DEFVAL(TextServer::ORIENTATION_HORIZONTAL)); // Drawing char. ClassDB::bind_method(D_METHOD("get_char_size", "char", "font_size"), &Font::get_char_size); diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp index 8ed68626a8e..6a4f7d082e8 100644 --- a/scene/resources/primitive_meshes.cpp +++ b/scene/resources/primitive_meshes.cpp @@ -2967,8 +2967,24 @@ void TextMesh::_create_mesh_array(Array &p_arr) const { } if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { - for (int i = 0; i < lines_rid.size() - 1; i++) { - TS->shaped_text_fit_to_width(lines_rid[i], (width > 0) ? width : max_line_w, TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA); + int jst_to_line = lines_rid.size(); + if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { + jst_to_line = lines_rid.size(); + } else { + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { + jst_to_line = lines_rid.size() - 1; + } + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { + for (int i = lines_rid.size() - 1; i >= 0; i--) { + if (TS->shaped_text_has_visible_chars(lines_rid[i])) { + jst_to_line = i; + break; + } + } + } + } + for (int i = 0; i < jst_to_line; i++) { + TS->shaped_text_fit_to_width(lines_rid[i], (width > 0) ? width : max_line_w, jst_flags); } } dirty_lines = false; @@ -3293,6 +3309,9 @@ void TextMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &TextMesh::set_autowrap_mode); ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &TextMesh::get_autowrap_mode); + ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &TextMesh::set_justification_flags); + ClassDB::bind_method(D_METHOD("get_justification_flags"), &TextMesh::get_justification_flags); + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &TextMesh::set_depth); ClassDB::bind_method(D_METHOD("get_depth"), &TextMesh::get_depth); @@ -3335,6 +3354,7 @@ void TextMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "line_spacing", PROPERTY_HINT_NONE, "suffix:px"), "set_line_spacing", "get_line_spacing"); ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags"); ADD_GROUP("Mesh", ""); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pixel_size", PROPERTY_HINT_RANGE, "0.0001,128,0.0001,suffix:m"), "set_pixel_size", "get_pixel_size"); @@ -3512,6 +3532,18 @@ TextServer::AutowrapMode TextMesh::get_autowrap_mode() const { return autowrap_mode; } +void TextMesh::set_justification_flags(BitField p_flags) { + if (jst_flags != p_flags) { + jst_flags = p_flags; + dirty_lines = true; + _request_update(); + } +} + +BitField TextMesh::get_justification_flags() const { + return jst_flags; +} + void TextMesh::set_depth(real_t p_depth) { if (depth != p_depth) { depth = MAX(p_depth, 0.0); diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h index e62f26b17c8..5b788b726e3 100644 --- a/scene/resources/primitive_meshes.h +++ b/scene/resources/primitive_meshes.h @@ -591,6 +591,7 @@ private: Ref font_override; 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; float width = 500.0; float line_spacing = 0.f; Point2 lbl_offset; @@ -649,6 +650,9 @@ public: void set_autowrap_mode(TextServer::AutowrapMode p_mode); TextServer::AutowrapMode get_autowrap_mode() const; + void set_justification_flags(BitField p_flags); + BitField get_justification_flags() const; + void set_text_direction(TextServer::Direction p_text_direction); TextServer::Direction get_text_direction() const; diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 191b71f3011..33f11371fe6 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -82,7 +82,7 @@ void TextParagraph::_bind_methods() { ClassDB::bind_method(D_METHOD("set_justification_flags", "flags"), &TextParagraph::set_justification_flags); ClassDB::bind_method(D_METHOD("get_justification_flags"), &TextParagraph::get_justification_flags); - ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification,Word Justification,Trim Edge Spaces After Justification,Justify Only After Last Tab,Constrain Ellipsis"), "set_justification_flags", "get_justification_flags"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Trim Edge Spaces After Justification:4,Justify Only After Last Tab:8,Constrain Ellipsis:16,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags"); ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &TextParagraph::set_text_overrun_behavior); ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &TextParagraph::get_text_overrun_behavior); @@ -232,9 +232,25 @@ void TextParagraph::_shape_lines() { overrun_flags.set_flag(TextServer::OVERRUN_ENFORCE_ELLIPSIS); } if (alignment == HORIZONTAL_ALIGNMENT_FILL) { + int jst_to_line = visible_lines; + if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { + jst_to_line = lines_rid.size(); + } else { + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { + jst_to_line = visible_lines - 1; + } + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { + for (int i = visible_lines - 1; i >= 0; i--) { + if (TS->shaped_text_has_visible_chars(lines_rid[i])) { + jst_to_line = i; + break; + } + } + } + } for (int i = 0; i < (int)lines_rid.size(); i++) { float line_w = (i <= dropcap_lines) ? (width - h_offset) : width; - if (i < visible_lines - 1 || (int)lines_rid.size() == 1) { + 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_overrun_trim_to_width(lines_rid[visible_lines - 1], line_w, overrun_flags); @@ -245,9 +261,25 @@ void TextParagraph::_shape_lines() { } } else { // Autowrap disabled. + int jst_to_line = lines_rid.size(); + if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { + jst_to_line = lines_rid.size(); + } else { + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { + jst_to_line = lines_rid.size() - 1; + } + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { + for (int i = lines_rid.size() - 1; i >= 0; i--) { + if (TS->shaped_text_has_visible_chars(lines_rid[i])) { + jst_to_line = i; + break; + } + } + } + } for (int i = 0; i < (int)lines_rid.size(); i++) { float line_w = (i <= dropcap_lines) ? (width - h_offset) : width; - if (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); overrun_flags.set_flag(TextServer::OVERRUN_JUSTIFICATION_AWARE); TS->shaped_text_overrun_trim_to_width(lines_rid[i], line_w, overrun_flags); diff --git a/scene/resources/text_paragraph.h b/scene/resources/text_paragraph.h index 5723dabeca6..31fc95ac025 100644 --- a/scene/resources/text_paragraph.h +++ b/scene/resources/text_paragraph.h @@ -55,7 +55,7 @@ private: int max_lines_visible = -1; BitField brk_flags = TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND; - BitField jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA; + BitField jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING; HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp index c1078d94930..511417664f2 100644 --- a/servers/text/text_server_extension.cpp +++ b/servers/text/text_server_extension.cpp @@ -251,7 +251,7 @@ void TextServerExtension::_bind_methods() { GDVIRTUAL_BIND(_shaped_text_substr, "shaped", "start", "length"); GDVIRTUAL_BIND(_shaped_text_get_parent, "shaped"); - GDVIRTUAL_BIND(_shaped_text_fit_to_width, "shaped", "width", "jst_flags"); + GDVIRTUAL_BIND(_shaped_text_fit_to_width, "shaped", "width", "justification_flags"); GDVIRTUAL_BIND(_shaped_text_tab_align, "shaped", "tab_stops"); GDVIRTUAL_BIND(_shaped_text_shape, "shaped"); diff --git a/servers/text_server.cpp b/servers/text_server.cpp index 36c31374722..30c601bc0d1 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -404,11 +404,12 @@ void TextServer::_bind_methods() { ClassDB::bind_method(D_METHOD("shaped_text_substr", "shaped", "start", "length"), &TextServer::shaped_text_substr); ClassDB::bind_method(D_METHOD("shaped_text_get_parent", "shaped"), &TextServer::shaped_text_get_parent); - ClassDB::bind_method(D_METHOD("shaped_text_fit_to_width", "shaped", "width", "jst_flags"), &TextServer::shaped_text_fit_to_width, DEFVAL(JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA)); + ClassDB::bind_method(D_METHOD("shaped_text_fit_to_width", "shaped", "width", "justification_flags"), &TextServer::shaped_text_fit_to_width, DEFVAL(JUSTIFICATION_WORD_BOUND | JUSTIFICATION_KASHIDA)); ClassDB::bind_method(D_METHOD("shaped_text_tab_align", "shaped", "tab_stops"), &TextServer::shaped_text_tab_align); ClassDB::bind_method(D_METHOD("shaped_text_shape", "shaped"), &TextServer::shaped_text_shape); ClassDB::bind_method(D_METHOD("shaped_text_is_ready", "shaped"), &TextServer::shaped_text_is_ready); + ClassDB::bind_method(D_METHOD("shaped_text_has_visible_chars", "shaped"), &TextServer::shaped_text_has_visible_chars); ClassDB::bind_method(D_METHOD("shaped_text_get_glyphs", "shaped"), &TextServer::_shaped_text_get_glyphs_wrapper); ClassDB::bind_method(D_METHOD("shaped_text_sort_logical", "shaped"), &TextServer::_shaped_text_sort_logical_wrapper); @@ -497,6 +498,9 @@ void TextServer::_bind_methods() { BIND_BITFIELD_FLAG(JUSTIFICATION_TRIM_EDGE_SPACES); BIND_BITFIELD_FLAG(JUSTIFICATION_AFTER_LAST_TAB); BIND_BITFIELD_FLAG(JUSTIFICATION_CONSTRAIN_ELLIPSIS); + BIND_BITFIELD_FLAG(JUSTIFICATION_SKIP_LAST_LINE); + BIND_BITFIELD_FLAG(JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS); + BIND_BITFIELD_FLAG(JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); /* AutowrapMode */ BIND_ENUM_CONSTANT(AUTOWRAP_OFF); @@ -684,6 +688,21 @@ void TextServer::draw_hex_code_box(const RID &p_canvas, int64_t p_size, const Ve } } +bool TextServer::shaped_text_has_visible_chars(const RID &p_shaped) const { + int v_size = shaped_text_get_glyph_count(p_shaped); + if (v_size == 0) { + return false; + } + + const Glyph *glyphs = shaped_text_get_glyphs(p_shaped); + for (int i = 0; i < v_size; i++) { + if (glyphs[i].index != 0 && (glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL) { + return true; + } + } + return false; +} + PackedInt32Array TextServer::shaped_text_get_line_breaks_adv(const RID &p_shaped, const PackedFloat32Array &p_width, int64_t p_start, bool p_once, BitField p_break_flags) const { PackedInt32Array lines; diff --git a/servers/text_server.h b/servers/text_server.h index af6efb8c7dd..170249bf77f 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -81,6 +81,9 @@ public: JUSTIFICATION_TRIM_EDGE_SPACES = 1 << 2, JUSTIFICATION_AFTER_LAST_TAB = 1 << 3, JUSTIFICATION_CONSTRAIN_ELLIPSIS = 1 << 4, + JUSTIFICATION_SKIP_LAST_LINE = 1 << 5, + JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS = 1 << 6, + JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE = 1 << 7, }; enum VisibleCharactersBehavior { @@ -441,6 +444,7 @@ public: virtual bool shaped_text_update_justification_ops(const RID &p_shaped) = 0; virtual bool shaped_text_is_ready(const RID &p_shaped) const = 0; + bool shaped_text_has_visible_chars(const RID &p_shaped) const; virtual const Glyph *shaped_text_get_glyphs(const RID &p_shaped) const = 0; TypedArray _shaped_text_get_glyphs_wrapper(const RID &p_shaped) const;