[RTL] Use separate paragraph copy for the partially visible paragraphs.

This commit is contained in:
Pāvels Nadtočajevs 2025-09-09 10:35:16 +03:00
parent 019889d1da
commit 0d19e18b00
No known key found for this signature in database
GPG key ID: 8413210218EF35D2
19 changed files with 346 additions and 51 deletions

View file

@ -58,6 +58,12 @@
Draw text into a canvas item at a given position, with [param color]. [param pos] specifies the top left corner of the bounding box. If [param oversampling] is greater than zero, it is used as font oversampling factor, otherwise viewport oversampling settings are used.
</description>
</method>
<method name="duplicate" qualifiers="const">
<return type="TextLine" />
<description>
Duplicates this [TextLine].
</description>
</method>
<method name="get_inferred_direction" qualifiers="const">
<return type="int" enum="TextServer.Direction" />
<description>
@ -119,6 +125,13 @@
Returns size of the bounding box of the text.
</description>
</method>
<method name="has_object" qualifiers="const">
<return type="bool" />
<param index="0" name="key" type="Variant" />
<description>
Returns [code]true[/code] if an object with [param key] is embedded in this line.
</description>
</method>
<method name="hit_test" qualifiers="const">
<return type="int" />
<param index="0" name="coords" type="float" />

View file

@ -110,6 +110,12 @@
Draw outlines of all lines of the text and drop cap into a canvas item at a given position, with [param color]. [param pos] specifies the top left corner of the bounding box. If [param oversampling] is greater than zero, it is used as font oversampling factor, otherwise viewport oversampling settings are used.
</description>
</method>
<method name="duplicate" qualifiers="const">
<return type="TextParagraph" />
<description>
Duplicates this [TextParagraph].
</description>
</method>
<method name="get_dropcap_lines" qualifiers="const">
<return type="int" />
<description>
@ -235,6 +241,13 @@
Returns the size of the bounding box of the paragraph.
</description>
</method>
<method name="has_object" qualifiers="const">
<return type="bool" />
<param index="0" name="key" type="Variant" />
<description>
Returns [code]true[/code] if an object with [param key] is embedded in this shaped text buffer.
</description>
</method>
<method name="hit_test" qualifiers="const">
<return type="int" />
<param index="0" name="coords" type="Vector2" />

View file

@ -1418,6 +1418,13 @@
[param clip_l] and [param clip_r] are offsets relative to [param pos], going to the right in horizontal layout and downward in vertical layout. If [param clip_l] is not negative, glyphs starting before the offset are clipped. If [param clip_r] is not negative, glyphs ending after the offset are clipped.
</description>
</method>
<method name="shaped_text_duplicate">
<return type="RID" />
<param index="0" name="rid" type="RID" />
<description>
Duplicates shaped text buffer.
</description>
</method>
<method name="shaped_text_fit_to_width">
<return type="float" />
<param index="0" name="shaped" type="RID" />
@ -1687,6 +1694,14 @@
Breaks text into words and returns array of character ranges. Use [param grapheme_flags] to set what characters are used for breaking.
</description>
</method>
<method name="shaped_text_has_object" qualifiers="const">
<return type="bool" />
<param index="0" name="shaped" type="RID" />
<param index="1" name="key" type="Variant" />
<description>
Returns [code]true[/code] if an object with [param key] is embedded in this shaped text buffer.
</description>
</method>
<method name="shaped_text_has_visible_chars" qualifiers="const">
<return type="bool" />
<param index="0" name="shaped" type="RID" />

View file

@ -1388,6 +1388,13 @@
Draw the outline of the shaped text into a canvas item at a given position, with [param color]. [param pos] specifies the leftmost point of the baseline (for horizontal layout) or topmost point of the baseline (for vertical layout). If [param oversampling] is greater than zero, it is used as font oversampling factor, otherwise viewport oversampling settings are used.
</description>
</method>
<method name="_shaped_text_duplicate" qualifiers="virtual required">
<return type="RID" />
<param index="0" name="shaped" type="RID" />
<description>
Duplicates shaped text buffer.
</description>
</method>
<method name="_shaped_text_fit_to_width" qualifiers="virtual">
<return type="float" />
<param index="0" name="shaped" type="RID" />
@ -1655,6 +1662,14 @@
Breaks text into words and returns array of character ranges. Use [param grapheme_flags] to set what characters are used for breaking.
</description>
</method>
<method name="_shaped_text_has_object" qualifiers="virtual required const">
<return type="bool" />
<param index="0" name="shaped" type="RID" />
<param index="1" name="key" type="Variant" />
<description>
Returns [code]true[/code] if an object with [param key] is embedded in this shaped text buffer.
</description>
</method>
<method name="_shaped_text_hit_test_grapheme" qualifiers="virtual const">
<return type="int" />
<param index="0" name="shaped" type="RID" />

View file

@ -4446,7 +4446,7 @@ void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) {
}
}
for (int i = p_shaped->first_span; i <= p_shaped->last_span; i++) {
for (int i = MAX(0, p_shaped->first_span); i <= MIN(p_shaped->last_span, parent->spans.size() - 1); i++) {
ShapedTextDataAdvanced::Span span = parent->spans[i];
span.start = MAX(p_shaped->start, span.start);
span.end = MIN(p_shaped->end, span.end);
@ -4486,6 +4486,43 @@ void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) {
invalidate(sd, true);
}
RID TextServerAdvanced::_shaped_text_duplicate(const RID &p_shaped) {
_THREAD_SAFE_METHOD_
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, RID());
MutexLock lock(sd->mutex);
ShapedTextDataAdvanced *new_sd = memnew(ShapedTextDataAdvanced);
new_sd->parent = p_shaped;
new_sd->start = sd->start;
new_sd->end = sd->end;
new_sd->text = sd->text;
new_sd->hb_buffer = hb_buffer_create();
new_sd->utf16 = new_sd->text.utf16();
new_sd->script_iter = memnew(ScriptIterator(new_sd->text, 0, new_sd->text.length()));
new_sd->orientation = sd->orientation;
new_sd->direction = sd->direction;
new_sd->custom_punct = sd->custom_punct;
new_sd->para_direction = sd->para_direction;
new_sd->base_para_direction = sd->base_para_direction;
new_sd->line_breaks_valid = sd->line_breaks_valid;
new_sd->justification_ops_valid = sd->justification_ops_valid;
new_sd->sort_valid = false;
new_sd->upos = sd->upos;
new_sd->uthk = sd->uthk;
new_sd->runs.clear();
new_sd->runs_dirty = true;
for (int i = 0; i < TextServer::SPACING_MAX; i++) {
new_sd->extra_spacing[i] = sd->extra_spacing[i];
}
full_copy(new_sd);
new_sd->valid.clear();
return shaped_owner.make_rid(new_sd);
}
void TextServerAdvanced::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction.");
@ -4998,6 +5035,14 @@ String TextServerAdvanced::_shaped_get_text(const RID &p_shaped) const {
return sd->text;
}
bool TextServerAdvanced::_shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
return sd->objects.has(p_key);
}
bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);

View file

@ -1032,6 +1032,7 @@ public:
MODBIND2R(RID, create_shaped_text, Direction, Orientation);
MODBIND1(shaped_text_clear, const RID &);
MODBIND1R(RID, shaped_text_duplicate, const RID &);
MODBIND2(shaped_text_set_direction, const RID &, Direction);
MODBIND1RC(Direction, shaped_text_get_direction, const RID &);
@ -1060,6 +1061,7 @@ public:
MODBIND7R(bool, shaped_text_add_string, const RID &, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
MODBIND6R(bool, shaped_text_add_object, const RID &, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
MODBIND5R(bool, shaped_text_resize_object, const RID &, const Variant &, const Size2 &, InlineAlignment, double);
MODBIND2RC(bool, shaped_text_has_object, const RID &, const Variant &);
MODBIND1RC(String, shaped_get_text, const RID &);
MODBIND1RC(int64_t, shaped_get_span_count, const RID &);

View file

@ -3285,7 +3285,7 @@ void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) {
}
}
for (int i = p_shaped->first_span; i <= p_shaped->last_span; i++) {
for (int i = MAX(0, p_shaped->first_span); i <= MIN(p_shaped->last_span, parent->spans.size() - 1); i++) {
ShapedTextDataFallback::Span span = parent->spans[i];
span.start = MAX(p_shaped->start, span.start);
span.end = MIN(p_shaped->end, span.end);
@ -3324,6 +3324,39 @@ void TextServerFallback::_shaped_text_clear(const RID &p_shaped) {
invalidate(sd);
}
RID TextServerFallback::_shaped_text_duplicate(const RID &p_shaped) {
_THREAD_SAFE_METHOD_
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, RID());
MutexLock lock(sd->mutex);
ShapedTextDataFallback *new_sd = memnew(ShapedTextDataFallback);
new_sd->parent = p_shaped;
new_sd->start = sd->start;
new_sd->end = sd->end;
new_sd->text = sd->text;
new_sd->orientation = sd->orientation;
new_sd->direction = sd->direction;
new_sd->custom_punct = sd->custom_punct;
new_sd->para_direction = sd->para_direction;
new_sd->line_breaks_valid = sd->line_breaks_valid;
new_sd->justification_ops_valid = sd->justification_ops_valid;
new_sd->sort_valid = false;
new_sd->upos = sd->upos;
new_sd->uthk = sd->uthk;
new_sd->runs.clear();
new_sd->runs_dirty = true;
for (int i = 0; i < TextServer::SPACING_MAX; i++) {
new_sd->extra_spacing[i] = sd->extra_spacing[i];
}
full_copy(new_sd);
new_sd->valid.clear();
return shaped_owner.make_rid(new_sd);
}
void TextServerFallback::_shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
ERR_FAIL_COND_MSG(p_direction == DIRECTION_INHERITED, "Invalid text direction.");
if (p_direction == DIRECTION_RTL) {
@ -3832,6 +3865,14 @@ String TextServerFallback::_shaped_get_text(const RID &p_shaped) const {
return sd->text;
}
bool TextServerFallback::_shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);
MutexLock lock(sd->mutex);
return sd->objects.has(p_key);
}
bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_NULL_V(sd, false);

View file

@ -811,6 +811,7 @@ public:
MODBIND2R(RID, create_shaped_text, Direction, Orientation);
MODBIND1(shaped_text_clear, const RID &);
MODBIND1R(RID, shaped_text_duplicate, const RID &);
MODBIND2(shaped_text_set_direction, const RID &, Direction);
MODBIND1RC(Direction, shaped_text_get_direction, const RID &);
@ -839,6 +840,7 @@ public:
MODBIND7R(bool, shaped_text_add_string, const RID &, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
MODBIND6R(bool, shaped_text_add_object, const RID &, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
MODBIND5R(bool, shaped_text_resize_object, const RID &, const Variant &, const Size2 &, InlineAlignment, double);
MODBIND2RC(bool, shaped_text_has_object, const RID &, const Variant &);
MODBIND1RC(String, shaped_get_text, const RID &);
MODBIND1RC(int64_t, shaped_get_span_count, const RID &);

View file

@ -381,6 +381,7 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<
// List.
_add_list_prefixes(p_frame, p_line, l);
{
RID t = l.text_buf->get_rid();
int spans = TS->shaped_get_span_count(t);
for (int i = 0; i < spans; i++) {
@ -408,6 +409,36 @@ void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<
TS->shaped_set_span_update_font(t, i, p_base_font->get_rids(), p_base_font_size, p_base_font->get_opentype_features());
}
}
}
if (l.text_buf_disp.is_valid()) {
RID t = l.text_buf_disp->get_rid();
int spans = TS->shaped_get_span_count(t);
for (int i = 0; i < spans; i++) {
Item *it_span = items.get_or_null(TS->shaped_get_span_meta(t, i));
ItemText *it = reinterpret_cast<ItemText *>(it_span);
if (it) {
Ref<Font> font = p_base_font;
int font_size = p_base_font_size;
ItemFont *font_it = _find_font(it);
if (font_it) {
if (font_it->font.is_valid()) {
font = font_it->font;
}
if (font_it->font_size > 0) {
font_size = font_it->font_size;
}
}
ItemFontSize *font_size_it = _find_font_size(it);
if (font_size_it && font_size_it->font_size > 0) {
font_size = font_size_it->font_size;
}
TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font->get_opentype_features());
} else {
TS->shaped_set_span_update_font(t, i, p_base_font->get_rids(), p_base_font_size, p_base_font->get_opentype_features());
}
}
}
Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
@ -449,6 +480,17 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
l.text_buf->tab_align(tabs);
}
if (l.text_buf_disp.is_valid()) {
l.text_buf_disp->set_width(p_width - l.offset.x);
if (!tab_stops.is_empty()) {
l.text_buf_disp->tab_align(tab_stops);
} else if (tab_size > 0) { // Align inline tabs.
Vector<float> tabs;
tabs.push_back(tab_size * p_base_font->get_char_size(' ', p_base_font_size).width);
l.text_buf_disp->tab_align(tabs);
}
}
Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
switch (it->type) {
@ -458,6 +500,9 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
if (img->width_in_percent || img->height_in_percent) {
img_size = _get_image_size(img->image, img->width_in_percent ? (p_width * img->rq_size.width / 100.f) : img->rq_size.width, img->height_in_percent ? (p_width * img->rq_size.height / 100.f) : img->rq_size.height, img->region);
l.text_buf->resize_object(it->rid, img_size, img->inline_align);
if (l.text_buf_disp.is_valid() && l.text_buf_disp->has_object(it->rid)) {
l.text_buf_disp->resize_object(it->rid, img_size, img->inline_align);
}
}
} break;
case ITEM_TABLE: {
@ -489,8 +534,14 @@ float RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font
int row_idx = (table->align_to_row < 0) ? table->rows_baseline.size() - 1 : table->align_to_row;
if (table->rows_baseline.size() != 0 && row_idx < (int)table->rows_baseline.size()) {
l.text_buf->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, Math::round(table->rows_baseline[row_idx]));
if (l.text_buf_disp.is_valid() && l.text_buf_disp->has_object(it->rid)) {
l.text_buf_disp->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align, Math::round(table->rows_baseline[row_idx]));
}
} else {
l.text_buf->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align);
if (l.text_buf_disp.is_valid() && l.text_buf_disp->has_object(it->rid)) {
l.text_buf_disp->resize_object(it->rid, Size2(table->total_width, table->total_height), table->inline_align);
}
}
} break;
default:
@ -528,6 +579,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
// Clear cache.
l.dc_item = nullptr;
l.text_buf_disp = Ref<TextParagraph>();
l.text_buf->clear();
l.text_buf->set_break_flags(autowrap_flags);
l.text_buf->set_justification_flags(_find_jst_flags(l.from));
@ -555,6 +607,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
// Shape current paragraph.
String txt;
String txt_sub;
Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
int remaining_characters = visible_characters - l.char_offset;
for (Item *it = l.from; it && it != it_to; it = _get_next_item(it)) {
@ -610,14 +663,13 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
String lang = _find_language(it);
String tx = t->text;
if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0 && tx.length() > remaining_characters) {
String first = tx.substr(0, remaining_characters);
String second = tx.substr(remaining_characters, -1);
l.text_buf->add_string(first, font, font_size, lang, it->rid);
l.text_buf->add_string(second, font, font_size, lang, it->rid);
} else {
l.text_buf->add_string(tx, font, font_size, lang, it->rid);
if (l.text_buf_disp.is_null() && visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING && visible_characters >= 0 && remaining_characters >= 0 && tx.length() > remaining_characters) {
String sub = tx.substr(0, remaining_characters);
l.text_buf_disp = l.text_buf->duplicate();
l.text_buf_disp->add_string(sub, font, font_size, lang, it->rid);
txt_sub = txt + sub;
}
l.text_buf->add_string(tx, font, font_size, lang, it->rid);
remaining_characters -= tx.length();
txt += tx;
@ -698,7 +750,11 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
// Apply BiDi override.
l.text_buf->set_bidi_override(structured_text_parser(_find_stt(l.from), st_args, txt));
TextServer::StructuredTextParser stt = _find_stt(l.from);
l.text_buf->set_bidi_override(structured_text_parser(stt, st_args, txt));
if (l.text_buf_disp.is_valid()) {
l.text_buf_disp->set_bidi_override(structured_text_parser(stt, st_args, txt_sub));
}
*r_char_offset = l.char_offset + l.char_count;
@ -898,13 +954,15 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
l.text_buf->draw_dropcap(ci, p_ofs + ((rtl) ? Vector2() : Vector2(l.offset.x, 0)), l.dc_color);
}
const Ref<TextParagraph> &text_buf = l.text_buf_disp.is_valid() ? l.text_buf_disp : l.text_buf;
int line_count = 0;
bool has_visible_chars = false;
// Bottom margin for text clipping.
float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM);
Size2 ctrl_size = get_size();
// Draw text.
for (int line = 0; line < l.text_buf->get_line_count(); line++) {
for (int line = 0; line < text_buf->get_line_count(); line++) {
if (line > 0) {
off.y += (theme_cache.line_separation + p_vsep);
}
@ -913,14 +971,14 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
break;
}
double l_height = l.text_buf->get_line_ascent(line) + l.text_buf->get_line_descent(line);
double l_height = text_buf->get_line_ascent(line) + text_buf->get_line_descent(line);
if (p_ofs.y + off.y + l_height <= 0) {
off.y += l_height;
continue;
}
float width = l.text_buf->get_width();
float length = l.text_buf->get_line_size(line).x;
float width = text_buf->get_width();
float length = text_buf->get_line_size(line).x;
// Draw line.
if (rtl) {
@ -936,7 +994,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
// Draw text.
switch (l.text_buf->get_alignment()) {
switch (text_buf->get_alignment()) {
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT: {
if (rtl) {
@ -986,7 +1044,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
}
}
RID rid = l.text_buf->get_line_rid(line);
RID rid = text_buf->get_line_rid(line);
double l_ascent = TS->shaped_text_get_ascent(rid);
Size2 l_size = TS->shaped_text_get_size(rid);
double upos = TS->shaped_text_get_underline_position(rid);
@ -1627,10 +1685,12 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
Item *table_click_item = nullptr;
int table_click_char = -1;
for (int line = 0; line < l.text_buf->get_line_count(); line++) {
RID rid = l.text_buf->get_line_rid(line);
const Ref<TextParagraph> &text_buf = l.text_buf_disp.is_valid() ? l.text_buf_disp : l.text_buf;
float width = l.text_buf->get_width();
for (int line = 0; line < text_buf->get_line_count(); line++) {
RID rid = text_buf->get_line_rid(line);
float width = text_buf->get_width();
float length = TS->shaped_text_get_width(rid);
if (rtl) {
@ -1645,7 +1705,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
}
}
switch (l.text_buf->get_alignment()) {
switch (text_buf->get_alignment()) {
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT: {
if (rtl) {
@ -1662,8 +1722,8 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
} break;
}
// Adjust for dropcap.
int dc_lines = l.text_buf->get_dropcap_lines();
float h_off = l.text_buf->get_dropcap_size().x;
int dc_lines = text_buf->get_dropcap_lines();
float h_off = text_buf->get_dropcap_size().x;
if (line <= dc_lines) {
if (rtl) {
off.x -= h_off;
@ -1824,7 +1884,7 @@ float RichTextLabel::_find_click_in_line(ItemFrame *p_frame, int p_line, const V
return table_offy;
}
if (line == l.text_buf->get_line_count() - 1) {
if (line == text_buf->get_line_count() - 1) {
off.y += TS->shaped_text_get_descent(rid) + theme_cache.paragraph_separation + p_vsep;
} else {
off.y += TS->shaped_text_get_descent(rid) + theme_cache.line_separation + p_vsep;
@ -2016,7 +2076,8 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
float h_off = l.text_buf->get_dropcap_size().x;
// Process text.
const RID &para_rid = l.text_buf->get_rid();
const Ref<TextParagraph> &text_buf = l.text_buf_disp.is_valid() ? l.text_buf_disp : l.text_buf;
const RID &para_rid = text_buf->get_rid();
String l_text = TS->shaped_get_text(para_rid).remove_char(0xfffc).strip_edges();
if (l.dc_item) {
@ -2026,7 +2087,7 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
if (!l_text.is_empty()) {
Vector2 off;
if (rtl) {
off.x = p_width - l.offset.x - l.text_buf->get_width();
off.x = p_width - l.offset.x - text_buf->get_width();
if (!lrtl && p_frame == main) { // Skip Scrollbar.
off.x -= scroll_w;
}
@ -2039,7 +2100,7 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
l.accessibility_text_element = DisplayServer::get_singleton()->accessibility_create_sub_element(line_ae, DisplayServer::AccessibilityRole::ROLE_STATIC_TEXT);
DisplayServer::get_singleton()->accessibility_update_set_value(l.accessibility_text_element, l_text);
ae_rect = Rect2(p_ofs + off, l.text_buf->get_size());
ae_rect = Rect2(p_ofs + off, text_buf->get_size());
DisplayServer::get_singleton()->accessibility_update_set_bounds(l.accessibility_text_element, ae_rect);
ac_element_bounds_cache[l.accessibility_text_element] = ae_rect;
@ -2049,14 +2110,14 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
}
Vector2 off;
for (int line = 0; line < l.text_buf->get_line_count(); line++) {
for (int line = 0; line < text_buf->get_line_count(); line++) {
if (line > 0) {
off.y += (theme_cache.line_separation + p_vsep);
}
const Size2 line_size = l.text_buf->get_line_size(line);
const Size2 line_size = text_buf->get_line_size(line);
float width = l.text_buf->get_width();
float width = text_buf->get_width();
float length = line_size.x;
// Process line.
@ -2074,7 +2135,7 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
}
// Process text.
switch (l.text_buf->get_alignment()) {
switch (text_buf->get_alignment()) {
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT: {
if (rtl) {
@ -2099,7 +2160,7 @@ void RichTextLabel::_accessibility_update_line(RID p_id, ItemFrame *p_frame, int
}
}
const RID &rid = l.text_buf->get_line_rid(line);
const RID &rid = text_buf->get_line_rid(line);
Array objects = TS->shaped_text_get_objects(rid);
for (int i = 0; i < objects.size(); i++) {

View file

@ -166,6 +166,7 @@ private:
Color prefix_outline_color = Color(0, 0, 0, 0);
float prefix_width = 0;
Ref<TextParagraph> text_buf;
Ref<TextParagraph> text_buf_disp;
RID accessibility_line_element;
RID accessibility_text_element;

View file

@ -33,6 +33,7 @@
void TextLine::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &TextLine::clear);
ClassDB::bind_method(D_METHOD("duplicate"), &TextLine::duplicate);
ClassDB::bind_method(D_METHOD("set_direction", "direction"), &TextLine::set_direction);
ClassDB::bind_method(D_METHOD("get_direction"), &TextLine::get_direction);
@ -64,6 +65,7 @@ void TextLine::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_string", "text", "font", "font_size", "language", "meta"), &TextLine::add_string, DEFVAL(""), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length", "baseline"), &TextLine::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align", "baseline"), &TextLine::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("has_object", "key"), &TextLine::has_object);
ClassDB::bind_method(D_METHOD("set_width", "width"), &TextLine::set_width);
ClassDB::bind_method(D_METHOD("get_width"), &TextLine::get_width);
@ -149,6 +151,23 @@ void TextLine::clear() {
TS->shaped_text_clear(rid);
}
Ref<TextLine> TextLine::duplicate() const {
Ref<TextLine> copy;
copy.instantiate();
if (rid.is_valid()) {
copy->rid = TS->shaped_text_duplicate(rid);
}
copy->dirty = true;
copy->width = width;
copy->flags = flags;
copy->alignment = alignment;
copy->el_char = el_char;
copy->overrun_behavior = overrun_behavior;
copy->tab_stops = tab_stops;
return copy;
}
void TextLine::set_preserve_invalid(bool p_enabled) {
TS->shaped_text_set_preserve_invalid(rid, p_enabled);
dirty = true;
@ -212,6 +231,11 @@ bool TextLine::resize_object(Variant p_key, const Size2 &p_size, InlineAlignment
return TS->shaped_text_resize_object(rid, p_key, p_size, p_inline_align, p_baseline);
}
bool TextLine::has_object(Variant p_key) const {
_shape();
return TS->shaped_text_has_object(rid, p_key);
}
Array TextLine::get_objects() const {
return TS->shaped_text_get_objects(rid);
}

View file

@ -66,6 +66,7 @@ public:
RID get_rid() const;
void clear();
Ref<TextLine> duplicate() const;
void set_direction(TextServer::Direction p_direction);
TextServer::Direction get_direction() const;
@ -85,6 +86,7 @@ public:
bool add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language = "", const Variant &p_meta = Variant());
bool add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1, float p_baseline = 0.0);
bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, float p_baseline = 0.0);
bool has_object(Variant p_key) const;
void set_horizontal_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_horizontal_alignment() const;

View file

@ -33,6 +33,7 @@
void TextParagraph::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &TextParagraph::clear);
ClassDB::bind_method(D_METHOD("duplicate"), &TextParagraph::duplicate);
ClassDB::bind_method(D_METHOD("set_direction", "direction"), &TextParagraph::set_direction);
ClassDB::bind_method(D_METHOD("get_direction"), &TextParagraph::get_direction);
@ -72,6 +73,7 @@ void TextParagraph::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_string", "text", "font", "font_size", "language", "meta"), &TextParagraph::add_string, DEFVAL(""), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length", "baseline"), &TextParagraph::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align", "baseline"), &TextParagraph::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("has_object", "key"), &TextParagraph::has_object);
ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &TextParagraph::set_alignment);
ClassDB::bind_method(D_METHOD("get_alignment"), &TextParagraph::get_alignment);
@ -322,6 +324,31 @@ void TextParagraph::clear() {
TS->shaped_text_clear(dropcap_rid);
}
Ref<TextParagraph> TextParagraph::duplicate() const {
Ref<TextParagraph> copy;
copy.instantiate();
if (dropcap_rid.is_valid()) {
copy->dropcap_rid = TS->shaped_text_duplicate(dropcap_rid);
}
copy->dropcap_lines = dropcap_lines;
copy->dropcap_margins = dropcap_margins;
if (rid.is_valid()) {
copy->rid = TS->shaped_text_duplicate(rid);
}
copy->lines_dirty = true;
copy->line_spacing = line_spacing;
copy->width = width;
copy->max_lines_visible = max_lines_visible;
copy->brk_flags = brk_flags;
copy->jst_flags = jst_flags;
copy->el_char = el_char;
copy->overrun_behavior = overrun_behavior;
copy->alignment = alignment;
copy->tab_stops = tab_stops;
return copy;
}
void TextParagraph::set_preserve_invalid(bool p_enabled) {
_THREAD_SAFE_METHOD_
@ -448,6 +475,12 @@ bool TextParagraph::resize_object(Variant p_key, const Size2 &p_size, InlineAlig
return res;
}
bool TextParagraph::has_object(Variant p_key) const {
_THREAD_SAFE_METHOD_
return TS->shaped_text_has_object(rid, p_key);
}
void TextParagraph::set_alignment(HorizontalAlignment p_alignment) {
_THREAD_SAFE_METHOD_

View file

@ -85,6 +85,8 @@ public:
void clear();
Ref<TextParagraph> duplicate() const;
void set_direction(TextServer::Direction p_direction);
TextServer::Direction get_direction() const;
TextServer::Direction get_inferred_direction() const;
@ -109,6 +111,7 @@ public:
bool add_string(const String &p_text, const Ref<Font> &p_font, int p_font_size, const String &p_language = "", const Variant &p_meta = Variant());
bool add_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1, float p_baseline = 0.0);
bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, float p_baseline = 0.0);
bool has_object(Variant p_key) const;
void set_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_alignment() const;

View file

@ -402,6 +402,7 @@ void TextServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_shaped_text", "direction", "orientation"), &TextServer::create_shaped_text, DEFVAL(DIRECTION_AUTO), DEFVAL(ORIENTATION_HORIZONTAL));
ClassDB::bind_method(D_METHOD("shaped_text_clear", "rid"), &TextServer::shaped_text_clear);
ClassDB::bind_method(D_METHOD("shaped_text_duplicate", "rid"), &TextServer::shaped_text_duplicate);
ClassDB::bind_method(D_METHOD("shaped_text_set_direction", "shaped", "direction"), &TextServer::shaped_text_set_direction, DEFVAL(DIRECTION_AUTO));
ClassDB::bind_method(D_METHOD("shaped_text_get_direction", "shaped"), &TextServer::shaped_text_get_direction);
@ -430,6 +431,7 @@ void TextServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("shaped_text_add_string", "shaped", "text", "fonts", "size", "opentype_features", "language", "meta"), &TextServer::shaped_text_add_string, DEFVAL(Dictionary()), DEFVAL(""), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("shaped_text_add_object", "shaped", "key", "size", "inline_align", "length", "baseline"), &TextServer::shaped_text_add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1), DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("shaped_text_resize_object", "shaped", "key", "size", "inline_align", "baseline"), &TextServer::shaped_text_resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(0.0));
ClassDB::bind_method(D_METHOD("shaped_text_has_object", "shaped", "key"), &TextServer::shaped_text_has_object);
ClassDB::bind_method(D_METHOD("shaped_get_text", "shaped"), &TextServer::shaped_get_text);
ClassDB::bind_method(D_METHOD("shaped_get_span_count", "shaped"), &TextServer::shaped_get_span_count);

View file

@ -463,6 +463,7 @@ public:
virtual RID create_shaped_text(Direction p_direction = DIRECTION_AUTO, Orientation p_orientation = ORIENTATION_HORIZONTAL) = 0;
virtual void shaped_text_clear(const RID &p_shaped) = 0;
virtual RID shaped_text_duplicate(const RID &p_shaped) = 0;
virtual void shaped_text_set_direction(const RID &p_shaped, Direction p_direction = DIRECTION_AUTO) = 0;
virtual Direction shaped_text_get_direction(const RID &p_shaped) const = 0;
@ -491,6 +492,7 @@ public:
virtual bool shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) = 0;
virtual bool shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int64_t p_length = 1, double p_baseline = 0.0) = 0;
virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, double p_baseline = 0.0) = 0;
virtual bool shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const = 0;
virtual String shaped_get_text(const RID &p_shaped) const = 0;
virtual int64_t shaped_get_span_count(const RID &p_shaped) const = 0;

View file

@ -94,9 +94,11 @@ public:
virtual RID create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) override { return RID(); }
virtual void shaped_text_clear(const RID &p_shaped) override {}
virtual RID shaped_text_duplicate(const RID &p_shaped) override { return RID(); }
virtual bool shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) override { return false; }
virtual bool shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, int64_t p_length, double p_baseline) override { return false; }
virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align, double p_baseline) override { return false; }
virtual bool shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const override { return false; }
virtual int64_t shaped_get_span_count(const RID &p_shaped) const override { return 0; }
virtual Variant shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const override { return Variant(); }
virtual Variant shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const override { return Variant(); }

View file

@ -252,6 +252,7 @@ void TextServerExtension::_bind_methods() {
GDVIRTUAL_BIND(_create_shaped_text, "direction", "orientation");
GDVIRTUAL_BIND(_shaped_text_clear, "shaped");
GDVIRTUAL_BIND(_shaped_text_duplicate, "shaped");
GDVIRTUAL_BIND(_shaped_text_set_direction, "shaped", "direction");
GDVIRTUAL_BIND(_shaped_text_get_direction, "shaped");
@ -280,6 +281,7 @@ void TextServerExtension::_bind_methods() {
GDVIRTUAL_BIND(_shaped_text_add_string, "shaped", "text", "fonts", "size", "opentype_features", "language", "meta");
GDVIRTUAL_BIND(_shaped_text_add_object, "shaped", "key", "size", "inline_align", "length", "baseline");
GDVIRTUAL_BIND(_shaped_text_resize_object, "shaped", "key", "size", "inline_align", "baseline");
GDVIRTUAL_BIND(_shaped_text_has_object, "shaped", "key");
GDVIRTUAL_BIND(_shaped_get_text, "shaped");
GDVIRTUAL_BIND(_shaped_get_span_count, "shaped");
@ -1148,6 +1150,12 @@ void TextServerExtension::shaped_text_clear(const RID &p_shaped) {
GDVIRTUAL_CALL(_shaped_text_clear, p_shaped);
}
RID TextServerExtension::shaped_text_duplicate(const RID &p_shaped) {
RID ret;
GDVIRTUAL_CALL(_shaped_text_duplicate, p_shaped, ret);
return ret;
}
void TextServerExtension::shaped_text_set_direction(const RID &p_shaped, TextServer::Direction p_direction) {
GDVIRTUAL_CALL(_shaped_text_set_direction, p_shaped, p_direction);
}
@ -1246,6 +1254,12 @@ bool TextServerExtension::shaped_text_resize_object(const RID &p_shaped, const V
return ret;
}
bool TextServerExtension::shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const {
bool ret = false;
GDVIRTUAL_CALL(_shaped_text_has_object, p_shaped, p_key, ret);
return ret;
}
String TextServerExtension::shaped_get_text(const RID &p_shaped) const {
String ret;
GDVIRTUAL_CALL(_shaped_get_text, p_shaped, ret);

View file

@ -413,6 +413,9 @@ public:
virtual void shaped_text_clear(const RID &p_shaped) override;
GDVIRTUAL1_REQUIRED(_shaped_text_clear, RID);
virtual RID shaped_text_duplicate(const RID &p_shaped) override;
GDVIRTUAL1R_REQUIRED(RID, _shaped_text_duplicate, RID);
virtual void shaped_text_set_direction(const RID &p_shaped, Direction p_direction = DIRECTION_AUTO) override;
virtual Direction shaped_text_get_direction(const RID &p_shaped) const override;
virtual Direction shaped_text_get_inferred_direction(const RID &p_shaped) const override;
@ -456,9 +459,11 @@ public:
virtual bool shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override;
virtual bool shaped_text_add_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int64_t p_length = 1, double p_baseline = 0.0) override;
virtual bool shaped_text_resize_object(const RID &p_shaped, const Variant &p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, double p_baseline = 0.0) override;
virtual bool shaped_text_has_object(const RID &p_shaped, const Variant &p_key) const override;
GDVIRTUAL7R_REQUIRED(bool, _shaped_text_add_string, RID, const String &, const TypedArray<RID> &, int64_t, const Dictionary &, const String &, const Variant &);
GDVIRTUAL6R_REQUIRED(bool, _shaped_text_add_object, RID, const Variant &, const Size2 &, InlineAlignment, int64_t, double);
GDVIRTUAL5R_REQUIRED(bool, _shaped_text_resize_object, RID, const Variant &, const Size2 &, InlineAlignment, double);
GDVIRTUAL2RC_REQUIRED(bool, _shaped_text_has_object, RID, const Variant &);
virtual String shaped_get_text(const RID &p_shaped) const override;
GDVIRTUAL1RC_REQUIRED(String, _shaped_get_text, RID);