diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index a15db9528f1..e83c062e8a5 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -34,13 +34,13 @@ #include "core/string/print_string.h" #include "core/string/translation.h" -void Label::set_autowrap(bool p_autowrap) { - if (autowrap == p_autowrap) { - return; - } +#include "servers/text_server.h" - autowrap = p_autowrap; - word_cache_dirty = true; +void Label::set_autowrap(bool p_autowrap) { + if (autowrap != p_autowrap) { + autowrap = p_autowrap; + lines_dirty = true; + } update(); if (clip) { @@ -54,7 +54,8 @@ bool Label::has_autowrap() const { void Label::set_uppercase(bool p_uppercase) { uppercase = p_uppercase; - word_cache_dirty = true; + dirty = true; + update(); } @@ -62,8 +63,95 @@ bool Label::is_uppercase() const { return uppercase; } -int Label::get_line_height() const { - return get_theme_font("font")->get_height(); +int Label::get_line_height(int p_line) const { + if (p_line >= 0 && p_line < lines_rid.size()) { + return TS->shaped_text_get_size(lines_rid[p_line]).y; + } else if (lines_rid.size() > 0) { + int h = 0; + for (int i = 0; i < lines_rid.size(); i++) { + h = MAX(h, TS->shaped_text_get_size(lines_rid[i]).y); + } + return h; + } else { + return get_theme_font("font")->get_height(get_theme_font_size("font_size")); + } +} + +void Label::_shape() { + Ref style = get_theme_stylebox("normal", "Label"); + int width = (get_size().width - style->get_minimum_size().width); + + if (dirty) { + TS->shaped_text_clear(text_rid); + if (text_direction == Control::TEXT_DIRECTION_INHERITED) { + TS->shaped_text_set_direction(text_rid, is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); + } else { + TS->shaped_text_set_direction(text_rid, (TextServer::Direction)text_direction); + } + TS->shaped_text_add_string(text_rid, (uppercase) ? xl_text.to_upper() : xl_text, get_theme_font("font")->get_rids(), get_theme_font_size("font_size"), opentype_features, (language != "") ? language : TranslationServer::get_singleton()->get_tool_locale()); + TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, xl_text)); + dirty = false; + lines_dirty = true; + } + if (lines_dirty) { + for (int i = 0; i < lines_rid.size(); i++) { + TS->free(lines_rid[i]); + } + lines_rid.clear(); + + Vector lines = TS->shaped_text_get_line_breaks(text_rid, width, 0, (autowrap) ? (TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND) : TextServer::BREAK_MANDATORY); + for (int i = 0; i < lines.size(); i++) { + RID line = TS->shaped_text_substr(text_rid, lines[i].x, lines[i].y - lines[i].x); + lines_rid.push_back(line); + } + } + + if (xl_text.length() == 0) { + minsize = Size2(1, get_line_height()); + return; + } + if (!autowrap) { + minsize.width = 0.0f; + for (int i = 0; i < lines_rid.size(); i++) { + if (minsize.width < TS->shaped_text_get_size(lines_rid[i]).x) { + minsize.width = TS->shaped_text_get_size(lines_rid[i]).x; + } + } + } + + if (lines_dirty) { // Fill after min_size calculation. + if (align == ALIGN_FILL) { + for (int i = 0; i < lines_rid.size(); i++) { + TS->shaped_text_fit_to_width(lines_rid.write[i], width); + } + } + lines_dirty = false; + } + + _update_visible(); + + if (!autowrap || !clip) { + minimum_size_changed(); + } +} + +void Label::_update_visible() { + int line_spacing = get_theme_constant("line_spacing", "Label"); + Ref style = get_theme_stylebox("normal", "Label"); + int lines_visible = lines_rid.size(); + + if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { + lines_visible = max_lines_visible; + } + + minsize.height = 0; + int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped); + for (int64_t i = lines_skipped; i < last_line; i++) { + minsize.height += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; + if (minsize.height > (get_size().height - style->get_minimum_size().height + line_spacing)) { + break; + } + } } void Label::_notification(int p_what) { @@ -73,8 +161,8 @@ void Label::_notification(int p_what) { return; //nothing new } xl_text = new_text; + dirty = true; - regenerate_word_cache(); update(); } @@ -83,8 +171,8 @@ void Label::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true); } - if (word_cache_dirty) { - regenerate_word_cache(); + if (dirty || lines_dirty) { + _shape(); } RID ci = get_canvas_item(); @@ -93,54 +181,61 @@ void Label::_notification(int p_what) { Size2 size = get_size(); Ref style = get_theme_stylebox("normal"); Ref font = get_theme_font("font"); - int font_size = get_theme_font_size("font_size"); Color font_color = get_theme_color("font_color"); Color font_color_shadow = get_theme_color("font_color_shadow"); - bool use_outline = get_theme_constant("shadow_as_outline"); Point2 shadow_ofs(get_theme_constant("shadow_offset_x"), get_theme_constant("shadow_offset_y")); int line_spacing = get_theme_constant("line_spacing"); - //Color font_outline_modulate = get_theme_color("font_outline_modulate"); + Color font_outline_modulate = get_theme_color("font_outline_modulate"); + int outline_size = get_theme_constant("outline_size"); + int shadow_outline_size = get_theme_constant("shadow_outline_size"); + bool rtl = is_layout_rtl(); style->draw(ci, Rect2(Point2(0, 0), get_size())); - //RenderingServer::get_singleton()->canvas_item_set_distance_field_mode(get_canvas_item(), font.is_valid() && font->is_distance_field_hint()); + float total_h = 0; + int lines_visible = 0; - int font_h = font->get_height() + line_spacing; - - int lines_visible = (size.y + line_spacing) / font_h; - - real_t space_w = font->get_char_size(' ').width; - int chars_total = 0; - - int vbegin = 0, vsep = 0; - - if (lines_visible > line_count) { - lines_visible = line_count; + // Get number of lines to fit to the height. + for (int64_t i = lines_skipped; i < lines_rid.size(); i++) { + total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; + if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) { + break; + } + lines_visible++; } if (max_lines_visible >= 0 && lines_visible > max_lines_visible) { lines_visible = max_lines_visible; } + int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped); + + // Get real total height. + total_h = 0; + for (int64_t i = lines_skipped; i < last_line; i++) { + total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing; + } + + int vbegin = 0, vsep = 0; if (lines_visible > 0) { switch (valign) { case VALIGN_TOP: { //nothing } break; case VALIGN_CENTER: { - vbegin = (size.y - (lines_visible * font_h - line_spacing)) / 2; + vbegin = (size.y - (total_h - line_spacing)) / 2; vsep = 0; } break; case VALIGN_BOTTOM: { - vbegin = size.y - (lines_visible * font_h - line_spacing); + vbegin = size.y - (total_h - line_spacing); vsep = 0; } break; case VALIGN_FILL: { vbegin = 0; if (lines_visible > 1) { - vsep = (size.y - (lines_visible * font_h - line_spacing)) / (lines_visible - 1); + vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1); } else { vsep = 0; } @@ -149,139 +244,109 @@ void Label::_notification(int p_what) { } } - WordCache *wc = word_cache; - if (!wc) { - return; + int visible_glyphs = -1; + int glyhps_drawn = 0; + if (percent_visible < 1) { + int total_glyphs = 0; + for (int i = lines_skipped; i < last_line; i++) { + const Vector glyphs = TS->shaped_text_get_glyphs(lines_rid[i]); + for (int j = 0; j < glyphs.size(); j++) { + if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + total_glyphs++; + } + } + } + visible_glyphs = total_glyphs * percent_visible; } - int line = 0; - int line_to = lines_skipped + (lines_visible > 0 ? lines_visible : 1); - //FontDrawer drawer(font, font_outline_modulate); - while (wc) { - /* handle lines not meant to be drawn quickly */ - if (line >= line_to) { - break; - } - if (line < lines_skipped) { - while (wc && wc->char_pos >= 0) { - wc = wc->next; - } - if (wc) { - wc = wc->next; - } - line++; - continue; - } - - /* handle lines normally */ - - if (wc->char_pos < 0) { - //empty line - wc = wc->next; - line++; - continue; - } - - WordCache *from = wc; - WordCache *to = wc; - - int taken = 0; - int spaces = 0; - while (to && to->char_pos >= 0) { - taken += to->pixel_width; - if (to->space_count) { - spaces += to->space_count; - } - to = to->next; - } - - bool can_fill = to && to->char_pos == WordCache::CHAR_WRAPLINE; - - float x_ofs = 0; - + Vector2 ofs; + ofs.y = style->get_offset().y + vbegin; + for (int i = lines_skipped; i < last_line; i++) { + ofs.x = 0; + ofs.y += TS->shaped_text_get_ascent(lines_rid[i]); switch (align) { case ALIGN_FILL: case ALIGN_LEFT: { - x_ofs = style->get_offset().x; + if (rtl) { + ofs.x = int(size.width - style->get_margin(MARGIN_RIGHT) - TS->shaped_text_get_size(lines_rid[i]).x); + } else { + ofs.x = style->get_offset().x; + } } break; case ALIGN_CENTER: { - x_ofs = int(size.width - (taken + spaces * space_w)) / 2; + ofs.x = int(size.width - TS->shaped_text_get_size(lines_rid[i]).x) / 2; } break; case ALIGN_RIGHT: { - x_ofs = int(size.width - style->get_margin(MARGIN_RIGHT) - (taken + spaces * space_w)); + if (rtl) { + ofs.x = style->get_offset().x; + } else { + ofs.x = int(size.width - style->get_margin(MARGIN_RIGHT) - TS->shaped_text_get_size(lines_rid[i]).x); + } } break; } - float y_ofs = style->get_offset().y; - y_ofs += (line - lines_skipped) * font_h + font->get_ascent(); - y_ofs += vbegin + line * vsep; + const Vector glyphs = TS->shaped_text_get_glyphs(lines_rid[i]); - while (from != to) { - // draw a word - int pos = from->char_pos; - if (from->char_pos < 0) { - ERR_PRINT("BUG"); - return; - } - if (from->space_count) { - /* spacing */ - x_ofs += space_w * from->space_count; - if (can_fill && align == ALIGN_FILL && spaces) { - x_ofs += int((size.width - (taken + space_w * spaces)) / spaces); + float x = ofs.x; + int outlines_drawn = glyhps_drawn; + for (int j = 0; j < glyphs.size(); j++) { + for (int k = 0; k < glyphs[j].repeat; k++) { + if (glyphs[j].font_rid != RID()) { + if (font_color_shadow.a > 0) { + TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + shadow_ofs, glyphs[j].index, font_color_shadow); + if (shadow_outline_size > 0) { + //draw shadow + TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, shadow_outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + Vector2(-shadow_ofs.x, shadow_ofs.y), glyphs[j].index, font_color_shadow); + TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, shadow_outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + Vector2(shadow_ofs.x, -shadow_ofs.y), glyphs[j].index, font_color_shadow); + TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, shadow_outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off) + Vector2(-shadow_ofs.x, -shadow_ofs.y), glyphs[j].index, font_color_shadow); + } + } + if (font_outline_modulate.a != 0.0 && outline_size > 0) { + TS->font_draw_glyph_outline(glyphs[j].font_rid, ci, glyphs[j].font_size, outline_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off), glyphs[j].index, font_outline_modulate); + } } + ofs.x += glyphs[j].advance; } - - if (font_color_shadow.a > 0) { - int chars_total_shadow = chars_total; //save chars drawn - float x_ofs_shadow = x_ofs; - for (int i = 0; i < from->word_len; i++) { - if (visible_chars < 0 || chars_total_shadow < visible_chars) { - char32_t c = xl_text[i + pos]; - char32_t n = xl_text[i + pos + 1]; - if (uppercase) { - c = String::char_uppercase(c); - n = String::char_uppercase(n); - } - - //TODO replace with TS - float move = font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + shadow_ofs, c, n, font_size, font_color_shadow); - if (use_outline) { - font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, shadow_ofs.y), c, n, font_size, font_color_shadow); - font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(shadow_ofs.x, -shadow_ofs.y), c, n, font_size, font_color_shadow); - font->draw_char(ci, Point2(x_ofs_shadow, y_ofs) + Vector2(-shadow_ofs.x, -shadow_ofs.y), c, n, font_size, font_color_shadow); - } - x_ofs_shadow += move; - chars_total_shadow++; + if (visible_glyphs != -1) { + if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + outlines_drawn++; + if (outlines_drawn >= visible_glyphs) { + break; } } } - for (int i = 0; i < from->word_len; i++) { - if (visible_chars < 0 || chars_total < visible_chars) { - char32_t c = xl_text[i + pos]; - char32_t n = xl_text[i + pos + 1]; - if (uppercase) { - c = String::char_uppercase(c); - n = String::char_uppercase(n); - } + } + ofs.x = x; - x_ofs += font->draw_char(ci, Point2(x_ofs, y_ofs), c, n, font_size, font_color); - chars_total++; + for (int j = 0; j < glyphs.size(); j++) { + for (int k = 0; k < glyphs[j].repeat; k++) { + if (glyphs[j].font_rid != RID()) { + TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off), glyphs[j].index, font_color); + } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + TS->draw_hex_code_box(ci, glyphs[j].font_size, ofs + Vector2(glyphs[j].x_off, glyphs[j].y_off), glyphs[j].index, font_color); + } + ofs.x += glyphs[j].advance; + } + if (visible_glyphs != -1) { + if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + glyhps_drawn++; + if (glyhps_drawn >= visible_glyphs) { + return; + } } } - from = from->next; } - wc = to ? to->next : nullptr; - line++; + ofs.y += TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing; } } if (p_what == NOTIFICATION_THEME_CHANGED) { - word_cache_dirty = true; + dirty = true; update(); } if (p_what == NOTIFICATION_RESIZED) { - word_cache_dirty = true; + lines_dirty = true; } } @@ -289,8 +354,8 @@ Size2 Label::get_minimum_size() const { Size2 min_style = get_theme_stylebox("normal")->get_minimum_size(); // don't want to mutable everything - if (word_cache_dirty) { - const_cast