[TextServer] Add function to change font, font size, and OpenType features without invalidating line break points, justification points, or recreating shaped text buffer.

This commit is contained in:
bruvzg 2022-01-20 09:30:42 +02:00
parent 050908626f
commit 215bede6ff
No known key found for this signature in database
GPG key ID: 7960FCF39844EC38
22 changed files with 705 additions and 491 deletions

View file

@ -26,6 +26,7 @@
<argument index="2" name="size" type="int" />
<argument index="3" name="opentype_features" type="Dictionary" default="{}" />
<argument index="4" name="language" type="String" default="&quot;&quot;" />
<argument index="5" name="meta" type="Variant" default="null" />
<description>
Adds text span and font to draw it.
</description>

View file

@ -26,6 +26,7 @@
<argument index="2" name="size" type="int" />
<argument index="3" name="opentype_features" type="Dictionary" default="{}" />
<argument index="4" name="language" type="String" default="&quot;&quot;" />
<argument index="5" name="meta" type="Variant" default="null" />
<description>
Adds text span and font to draw it.
</description>

View file

@ -896,6 +896,32 @@
[b]Note:[/b] This function is used by during project export, to include TextServer database.
</description>
</method>
<method name="shaped_get_span_count" qualifiers="const">
<return type="int" />
<argument index="0" name="shaped" type="RID" />
<description>
Returns number of text spans added using [method shaped_text_add_string] or [method shaped_text_add_object].
</description>
</method>
<method name="shaped_get_span_meta" qualifiers="const">
<return type="Variant" />
<argument index="0" name="shaped" type="RID" />
<argument index="1" name="index" type="int" />
<description>
Returns text span metadata.
</description>
</method>
<method name="shaped_set_span_update_font">
<return type="void" />
<argument index="0" name="shaped" type="RID" />
<argument index="1" name="index" type="int" />
<argument index="2" name="fonts" type="Array" />
<argument index="3" name="size" type="int" />
<argument index="4" name="opentype_features" type="Dictionary" default="{}" />
<description>
Changes text span font, font size and OpenType features, without changing the text.
</description>
</method>
<method name="shaped_text_add_object">
<return type="bool" />
<argument index="0" name="shaped" type="RID" />
@ -915,6 +941,7 @@
<argument index="3" name="size" type="int" />
<argument index="4" name="opentype_features" type="Dictionary" default="{}" />
<argument index="5" name="language" type="String" default="&quot;&quot;" />
<argument index="6" name="meta" type="Variant" default="null" />
<description>
Adds text span and font to draw it to the text buffer.
</description>

View file

@ -904,6 +904,32 @@
[b]Note:[/b] This function is used by during project export, to include TextServer database.
</description>
</method>
<method name="_shaped_get_span_count" qualifiers="virtual const">
<return type="int" />
<argument index="0" name="shaped" type="RID" />
<description>
Returns number of text spans added using [method _shaped_text_add_string] or [method _shaped_text_add_object].
</description>
</method>
<method name="_shaped_get_span_meta" qualifiers="virtual const">
<return type="Variant" />
<argument index="0" name="shaped" type="RID" />
<argument index="1" name="index" type="int" />
<description>
Returns text span metadata.
</description>
</method>
<method name="_shaped_set_span_update_font" qualifiers="virtual">
<return type="void" />
<argument index="0" name="shaped" type="RID" />
<argument index="1" name="index" type="int" />
<argument index="2" name="fonts" type="Array" />
<argument index="3" name="size" type="int" />
<argument index="4" name="opentype_features" type="Dictionary" />
<description>
Changes text span font, font size and OpenType features, without changing the text.
</description>
</method>
<method name="_shaped_text_add_object" qualifiers="virtual">
<return type="bool" />
<argument index="0" name="shaped" type="RID" />
@ -923,6 +949,7 @@
<argument index="3" name="size" type="int" />
<argument index="4" name="opentype_features" type="Dictionary" />
<argument index="5" name="language" type="String" />
<argument index="6" name="meta" type="Variant" />
<description>
Adds text span and font to draw it to the text buffer.
</description>

View file

@ -2901,7 +2901,7 @@ void TextServerAdvanced::font_set_global_oversampling(float p_oversampling) {
List<RID> text_bufs;
shaped_owner.get_owned_list(&text_bufs);
for (const RID &E : text_bufs) {
invalidate(shaped_owner.get_or_null(E));
invalidate(shaped_owner.get_or_null(E), false);
}
}
}
@ -2936,7 +2936,7 @@ int TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int
return limit;
}
void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped) {
void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) {
p_shaped->valid = false;
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
@ -2951,27 +2951,32 @@ void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *
p_shaped->glyphs_logical.clear();
p_shaped->overrun_trim_data = TrimData();
p_shaped->utf16 = Char16String();
if (p_shaped->script_iter != nullptr) {
memdelete(p_shaped->script_iter);
p_shaped->script_iter = nullptr;
}
for (int i = 0; i < p_shaped->bidi_iter.size(); i++) {
ubidi_close(p_shaped->bidi_iter[i]);
}
p_shaped->bidi_iter.clear();
if (p_text) {
if (p_shaped->script_iter != nullptr) {
memdelete(p_shaped->script_iter);
p_shaped->script_iter = nullptr;
}
p_shaped->break_ops_valid = false;
p_shaped->js_ops_valid = false;
}
}
void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) {
ShapedTextDataAdvanced *parent = shaped_owner.get_or_null(p_shaped->parent);
for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) {
for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : parent->objects) {
if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) {
p_shaped->objects[E.key] = E.value;
}
}
for (int k = 0; k < parent->spans.size(); k++) {
ShapedTextDataAdvanced::Span span = parent->spans[k];
for (int i = 0; i < parent->spans.size(); i++) {
ShapedTextDataAdvanced::Span span = parent->spans[i];
if (span.start >= p_shaped->end || span.end <= p_shaped->start) {
continue;
}
@ -3004,7 +3009,7 @@ void TextServerAdvanced::shaped_text_clear(RID p_shaped) {
sd->spans.clear();
sd->objects.clear();
sd->bidi_override.clear();
invalidate(sd);
invalidate(sd, true);
}
void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Direction p_direction) {
@ -3017,7 +3022,7 @@ void TextServerAdvanced::shaped_text_set_direction(RID p_shaped, TextServer::Dir
full_copy(sd);
}
sd->direction = p_direction;
invalidate(sd);
invalidate(sd, false);
}
}
@ -3047,7 +3052,7 @@ void TextServerAdvanced::shaped_text_set_custom_punctuation(RID p_shaped, const
full_copy(sd);
}
sd->custom_punct = p_punct;
invalidate(sd);
invalidate(sd, false);
}
}
@ -3070,7 +3075,7 @@ void TextServerAdvanced::shaped_text_set_bidi_override(RID p_shaped, const Array
for (int i = 0; i < p_override.size(); i++) {
sd->bidi_override.push_back(p_override[i]);
}
invalidate(sd);
invalidate(sd, false);
}
void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) {
@ -3083,7 +3088,7 @@ void TextServerAdvanced::shaped_text_set_orientation(RID p_shaped, TextServer::O
full_copy(sd);
}
sd->orientation = p_orientation;
invalidate(sd);
invalidate(sd, false);
}
}
@ -3095,7 +3100,7 @@ void TextServerAdvanced::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e
ERR_FAIL_COND(sd->parent != RID());
if (sd->preserve_invalid != p_enabled) {
sd->preserve_invalid = p_enabled;
invalidate(sd);
invalidate(sd, false);
}
}
@ -3117,7 +3122,7 @@ void TextServerAdvanced::shaped_text_set_preserve_control(RID p_shaped, bool p_e
full_copy(sd);
}
sd->preserve_control = p_enabled;
invalidate(sd);
invalidate(sd, false);
}
}
@ -3137,7 +3142,41 @@ TextServer::Orientation TextServerAdvanced::shaped_text_get_orientation(RID p_sh
return sd->orientation;
}
bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
int TextServerAdvanced::shaped_get_span_count(RID p_shaped) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0);
return sd->spans.size();
}
Variant TextServerAdvanced::shaped_get_span_meta(RID p_shaped, int p_index) const {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Variant());
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
return sd->spans[p_index].meta;
}
void TextServerAdvanced::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
ERR_FAIL_INDEX(p_index, sd->spans.size());
ShapedTextDataAdvanced::Span &span = sd->spans.write[p_index];
bool changed = (span.font_size != p_size) || (span.features != p_opentype_features) || (p_fonts.size() != span.fonts.size());
if (!changed) {
for (int i = 0; i < p_fonts.size(); i++) {
changed = changed || (span.fonts[i] != p_fonts[i]);
}
}
if (changed) {
span.fonts = p_fonts;
span.font_size = p_size;
span.features = p_opentype_features;
invalidate(sd, false);
}
}
bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
ERR_FAIL_COND_V(p_size <= 0, false);
@ -3162,11 +3201,12 @@ bool TextServerAdvanced::shaped_text_add_string(RID p_shaped, const String &p_te
span.font_size = p_size;
span.language = p_language;
span.features = p_opentype_features;
span.meta = p_meta;
sd->spans.push_back(span);
sd->text += p_text;
sd->end += p_text.length();
invalidate(sd);
invalidate(sd, true);
return true;
}
@ -3196,13 +3236,13 @@ bool TextServerAdvanced::shaped_text_add_object(RID p_shaped, Variant p_key, con
sd->text += String::chr(0xfffc).repeat(p_length);
sd->end += p_length;
sd->objects[p_key] = obj;
invalidate(sd);
invalidate(sd, true);
return true;
}
bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@ -3222,7 +3262,7 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key,
Glyph gl = sd->glyphs[i];
Variant key;
if (gl.count == 1) {
for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) {
if (E.value.pos == gl.start) {
key = E.key;
break;
@ -3262,77 +3302,80 @@ bool TextServerAdvanced::shaped_text_resize_object(RID p_shaped, Variant p_key,
sd->width += gl.advance * gl.repeat;
}
}
// Align embedded objects to baseline.
float full_ascent = sd->ascent;
float full_descent = sd->descent;
for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.y = -sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.y = (-sd->ascent + sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.y = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.y = sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.y -= E.value.rect.size.y;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.y -= E.value.rect.size.y / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.y);
full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
} else {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.x = -sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.x = (-sd->ascent + sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.x = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.x = sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.x -= E.value.rect.size.x;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.x -= E.value.rect.size.x / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.x);
full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
}
}
}
sd->ascent = full_ascent;
sd->descent = full_descent;
_realign(sd);
}
return true;
}
void TextServerAdvanced::_realign(ShapedTextDataAdvanced *p_sd) const {
// Align embedded objects to baseline.
float full_ascent = p_sd->ascent;
float full_descent = p_sd->descent;
for (KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) {
if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) {
if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.y = -p_sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.y = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.y = p_sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.y -= E.value.rect.size.y;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.y -= E.value.rect.size.y / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.y);
full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
} else {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.x = -p_sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.x = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.x = p_sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.x -= E.value.rect.size.x;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.x -= E.value.rect.size.x / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.x);
full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
}
}
}
p_sd->ascent = full_ascent;
p_sd->descent = full_descent;
}
RID TextServerAdvanced::shaped_text_substr(RID p_shaped, int p_start, int p_length) const {
const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, RID());
@ -3423,7 +3466,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
Variant key;
bool find_embedded = false;
if (gl.count == 1) {
for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_sd->objects) {
for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) {
if (E.value.pos == gl.start) {
find_embedded = true;
key = E.key;
@ -3466,72 +3509,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
}
}
// Align embedded objects to baseline.
float full_ascent = p_new_sd->ascent;
float full_descent = p_new_sd->descent;
for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : p_new_sd->objects) {
if ((E.value.pos >= p_new_sd->start) && (E.value.pos < p_new_sd->end)) {
if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.y = -p_new_sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.y = (-p_new_sd->ascent + p_new_sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.y = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.y = p_new_sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.y -= E.value.rect.size.y;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.y -= E.value.rect.size.y / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.y);
full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
} else {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.x = -p_new_sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.x = (-p_new_sd->ascent + p_new_sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.x = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.x = p_new_sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.x -= E.value.rect.size.x;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.x -= E.value.rect.size.x / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.x);
full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
}
}
}
p_new_sd->ascent = full_ascent;
p_new_sd->descent = full_descent;
_realign(p_new_sd);
}
p_new_sd->valid = true;
@ -3968,40 +3946,43 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
const UChar *data = sd->utf16.ptr();
HashMap<int, bool> breaks;
UErrorCode err = U_ZERO_ERROR;
int i = 0;
while (i < sd->spans.size()) {
String language = sd->spans[i].language;
int r_start = sd->spans[i].start;
while (i + 1 < sd->spans.size() && language == sd->spans[i + 1].language) {
if (!sd->break_ops_valid) {
sd->breaks.clear();
UErrorCode err = U_ZERO_ERROR;
int i = 0;
while (i < sd->spans.size()) {
String language = sd->spans[i].language;
int r_start = sd->spans[i].start;
while (i + 1 < sd->spans.size() && language == sd->spans[i + 1].language) {
i++;
}
int r_end = sd->spans[i].end;
UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err);
if (U_FAILURE(err)) {
// No data loaded - use fallback.
for (int j = r_start; j < r_end; j++) {
char32_t c = sd->text[j - sd->start];
if (is_whitespace(c)) {
sd->breaks[j + 1] = false;
}
if (is_linebreak(c)) {
sd->breaks[j + 1] = true;
}
}
} else {
while (ubrk_next(bi) != UBRK_DONE) {
int pos = _convert_pos(sd, ubrk_current(bi)) + r_start;
if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) {
sd->breaks[pos] = true;
} else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) {
sd->breaks[pos] = false;
}
}
}
ubrk_close(bi);
i++;
}
int r_end = sd->spans[i].end;
UBreakIterator *bi = ubrk_open(UBRK_LINE, language.ascii().get_data(), data + _convert_pos_inv(sd, r_start), _convert_pos_inv(sd, r_end - r_start), &err);
if (U_FAILURE(err)) {
// No data loaded - use fallback.
for (int j = r_start; j < r_end; j++) {
char32_t c = sd->text[j - sd->start];
if (is_whitespace(c)) {
breaks[j + 1] = false;
}
if (is_linebreak(c)) {
breaks[j + 1] = true;
}
}
} else {
while (ubrk_next(bi) != UBRK_DONE) {
int pos = _convert_pos(sd, ubrk_current(bi)) + r_start;
if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_HARD) && (ubrk_getRuleStatus(bi) < UBRK_LINE_HARD_LIMIT)) {
breaks[pos] = true;
} else if ((ubrk_getRuleStatus(bi) >= UBRK_LINE_SOFT) && (ubrk_getRuleStatus(bi) < UBRK_LINE_SOFT_LIMIT)) {
breaks[pos] = false;
}
}
}
ubrk_close(bi);
i++;
sd->break_ops_valid = true;
}
sd->sort_valid = false;
@ -4013,7 +3994,7 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
int c_punct_size = sd->custom_punct.length();
const char32_t *c_punct = sd->custom_punct.ptr();
for (i = 0; i < sd_size; i++) {
for (int i = 0; i < sd_size; i++) {
if (sd_glyphs[i].count > 0) {
char32_t c = ch[sd_glyphs[i].start - sd->start];
if (c == 0xfffc) {
@ -4040,8 +4021,8 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
if (is_underscore(c)) {
sd_glyphs[i].flags |= GRAPHEME_IS_UNDERSCORE;
}
if (breaks.has(sd_glyphs[i].end)) {
if (breaks[sd_glyphs[i].end] && (is_linebreak(c))) {
if (sd->breaks.has(sd_glyphs[i].end)) {
if (sd->breaks[sd_glyphs[i].end] && (is_linebreak(c))) {
sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_HARD;
} else if (is_whitespace(c)) {
sd_glyphs[i].flags |= GRAPHEME_IS_BREAK_SOFT;
@ -4186,41 +4167,45 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) {
const UChar *data = sd->utf16.ptr();
int32_t data_size = sd->utf16.length();
Map<int, bool> jstops;
if (!sd->js_ops_valid) {
sd->jstops.clear();
// Use ICU word iterator and custom kashida detection.
UErrorCode err = U_ZERO_ERROR;
UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err);
if (U_FAILURE(err)) {
// No data - use fallback.
int limit = 0;
for (int i = 0; i < sd->text.length(); i++) {
if (is_whitespace(data[i])) {
int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start;
if (ks != -1) {
jstops[ks] = true;
// Use ICU word iterator and custom kashida detection.
UErrorCode err = U_ZERO_ERROR;
UBreakIterator *bi = ubrk_open(UBRK_WORD, "", data, data_size, &err);
if (U_FAILURE(err)) {
// No data - use fallback.
int limit = 0;
for (int i = 0; i < sd->text.length(); i++) {
if (is_whitespace(data[i])) {
int ks = _generate_kashida_justification_opportunies(sd->text, limit, i) + sd->start;
if (ks != -1) {
sd->jstops[ks] = true;
}
limit = i + 1;
}
limit = i + 1;
}
}
int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start;
if (ks != -1) {
jstops[ks] = true;
}
} else {
int limit = 0;
while (ubrk_next(bi) != UBRK_DONE) {
if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) {
int i = _convert_pos(sd, ubrk_current(bi));
jstops[i + sd->start] = false;
int ks = _generate_kashida_justification_opportunies(sd->text, limit, i);
if (ks != -1) {
jstops[ks + sd->start] = true;
int ks = _generate_kashida_justification_opportunies(sd->text, limit, sd->text.length()) + sd->start;
if (ks != -1) {
sd->jstops[ks] = true;
}
} else {
int limit = 0;
while (ubrk_next(bi) != UBRK_DONE) {
if (ubrk_getRuleStatus(bi) != UBRK_WORD_NONE) {
int i = _convert_pos(sd, ubrk_current(bi));
sd->jstops[i + sd->start] = false;
int ks = _generate_kashida_justification_opportunies(sd->text, limit, i);
if (ks != -1) {
sd->jstops[ks + sd->start] = true;
}
limit = i;
}
limit = i;
}
ubrk_close(bi);
}
ubrk_close(bi);
sd->js_ops_valid = true;
}
sd->sort_valid = false;
@ -4228,18 +4213,18 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) {
Glyph *sd_glyphs = sd->glyphs.ptrw();
int sd_size = sd->glyphs.size();
if (jstops.size() > 0) {
if (sd->jstops.size() > 0) {
for (int i = 0; i < sd_size; i++) {
if (sd_glyphs[i].count > 0) {
char32_t c = sd->text[sd_glyphs[i].start - sd->start];
if (c == 0x0640) {
sd_glyphs[i].flags |= GRAPHEME_IS_ELONGATION;
}
if (jstops.has(sd_glyphs[i].start)) {
if (sd->jstops.has(sd_glyphs[i].start)) {
if (c == 0xfffc) {
continue;
}
if (jstops[sd_glyphs[i].start]) {
if (sd->jstops[sd_glyphs[i].start]) {
if (c != 0x0640) {
if (sd_glyphs[i].font_rid != RID()) {
Glyph gl = _shape_single_glyph(sd, 0x0640, HB_SCRIPT_ARABIC, HB_DIRECTION_RTL, sd->glyphs[i].font_rid, sd->glyphs[i].font_size);
@ -4574,7 +4559,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) {
return true;
}
invalidate(sd);
invalidate(sd, false);
if (sd->parent != RID()) {
shaped_text_shape(sd->parent);
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
@ -4733,70 +4718,7 @@ bool TextServerAdvanced::shaped_text_shape(RID p_shaped) {
}
}
// Align embedded objects to baseline.
float full_ascent = sd->ascent;
float full_descent = sd->descent;
for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.y = -sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.y = (-sd->ascent + sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.y = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.y = sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.y -= E.value.rect.size.y;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.y -= E.value.rect.size.y / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.y);
full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
} else {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.x = -sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.x = (-sd->ascent + sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.x = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.x = sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.x -= E.value.rect.size.x;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.x -= E.value.rect.size.x / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.x);
full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
}
}
sd->ascent = full_ascent;
sd->descent = full_descent;
_realign(sd);
sd->valid = true;
return sd->valid;
}
@ -4863,7 +4785,7 @@ Array TextServerAdvanced::shaped_text_get_objects(RID p_shaped) const {
ERR_FAIL_COND_V(!sd, ret);
MutexLock lock(sd->mutex);
for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) {
ret.push_back(E.key);
}

View file

@ -242,6 +242,21 @@ class TextServerAdvanced : public TextServer {
// Shaped text cache data.
struct ShapedTextDataAdvanced : public ShapedTextData {
struct Span {
int start = -1;
int end = -1;
Vector<RID> fonts;
int font_size = 0;
Variant embedded_key;
String language;
Dictionary features;
Variant meta;
};
Vector<Span> spans;
/* Intermediate data */
Char16String utf16;
Vector<UBiDi *> bidi_iter;
@ -249,6 +264,11 @@ class TextServerAdvanced : public TextServer {
ScriptIterator *script_iter = nullptr;
hb_buffer_t *hb_buffer = nullptr;
HashMap<int, bool> jstops;
HashMap<int, bool> breaks;
bool break_ops_valid = false;
bool js_ops_valid = false;
~ShapedTextDataAdvanced() {
for (int i = 0; i < bidi_iter.size(); i++) {
ubidi_close(bidi_iter[i]);
@ -268,6 +288,7 @@ class TextServerAdvanced : public TextServer {
mutable RID_PtrOwner<FontDataAdvanced> font_owner;
mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner;
void _realign(ShapedTextDataAdvanced *p_sd) const;
int _convert_pos(const ShapedTextDataAdvanced *p_sd, int p_pos) const;
int _convert_pos_inv(const ShapedTextDataAdvanced *p_sd, int p_pos) const;
bool _shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int p_start, int p_length) const;
@ -302,7 +323,7 @@ protected:
static void _bind_methods(){};
void full_copy(ShapedTextDataAdvanced *p_shaped);
void invalidate(ShapedTextDataAdvanced *p_shaped);
void invalidate(ShapedTextDataAdvanced *p_shaped, bool p_text = false);
public:
virtual bool has_feature(Feature p_feature) const override;
@ -482,10 +503,14 @@ public:
virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override;
virtual bool shaped_text_get_preserve_control(RID p_shaped) const override;
virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override;
virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override;
virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override;
virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override;
virtual int shaped_get_span_count(RID p_shaped) const override;
virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override;
virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override;
virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override;
virtual RID shaped_text_get_parent(RID p_shaped) const override;

View file

@ -91,7 +91,7 @@ void TextServerFallback::free(RID p_rid) {
font_owner.free(p_rid);
memdelete(fd);
} else if (shaped_owner.owns(p_rid)) {
ShapedTextData *sd = shaped_owner.get_or_null(p_rid);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid);
shaped_owner.free(p_rid);
memdelete(sd);
}
@ -2061,7 +2061,7 @@ void TextServerFallback::font_set_global_oversampling(float p_oversampling) {
/* Shaped text buffer interface */
/*************************************************************************/
void TextServerFallback::invalidate(ShapedTextData *p_shaped) {
void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) {
p_shaped->valid = false;
p_shaped->sort_valid = false;
p_shaped->line_breaks_valid = false;
@ -2075,17 +2075,17 @@ void TextServerFallback::invalidate(ShapedTextData *p_shaped) {
p_shaped->glyphs_logical.clear();
}
void TextServerFallback::full_copy(ShapedTextData *p_shaped) {
ShapedTextData *parent = shaped_owner.get_or_null(p_shaped->parent);
void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) {
ShapedTextDataFallback *parent = shaped_owner.get_or_null(p_shaped->parent);
for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : parent->objects) {
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : parent->objects) {
if (E.value.pos >= p_shaped->start && E.value.pos < p_shaped->end) {
p_shaped->objects[E.key] = E.value;
}
}
for (int k = 0; k < parent->spans.size(); k++) {
ShapedTextData::Span span = parent->spans[k];
ShapedTextDataFallback::Span span = parent->spans[k];
if (span.start >= p_shaped->end || span.end <= p_shaped->start) {
continue;
}
@ -2099,7 +2099,7 @@ void TextServerFallback::full_copy(ShapedTextData *p_shaped) {
RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, TextServer::Orientation p_orientation) {
_THREAD_SAFE_METHOD_
ShapedTextData *sd = memnew(ShapedTextData);
ShapedTextDataFallback *sd = memnew(ShapedTextDataFallback);
sd->direction = p_direction;
sd->orientation = p_orientation;
@ -2107,7 +2107,7 @@ RID TextServerFallback::create_shaped_text(TextServer::Direction p_direction, Te
}
void TextServerFallback::shaped_text_clear(RID p_shaped) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
@ -2136,7 +2136,7 @@ TextServer::Direction TextServerFallback::shaped_text_get_inferred_direction(RID
void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const String &p_punct) {
_THREAD_SAFE_METHOD_
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
if (sd->custom_punct != p_punct) {
@ -2150,13 +2150,13 @@ void TextServerFallback::shaped_text_set_custom_punctuation(RID p_shaped, const
String TextServerFallback::shaped_text_get_custom_punctuation(RID p_shaped) const {
_THREAD_SAFE_METHOD_
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, String());
return sd->custom_punct;
}
void TextServerFallback::shaped_text_set_orientation(RID p_shaped, TextServer::Orientation p_orientation) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
@ -2174,7 +2174,7 @@ void TextServerFallback::shaped_text_set_bidi_override(RID p_shaped, const Array
}
TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, TextServer::ORIENTATION_HORIZONTAL);
MutexLock lock(sd->mutex);
@ -2182,7 +2182,7 @@ TextServer::Orientation TextServerFallback::shaped_text_get_orientation(RID p_sh
}
void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
MutexLock lock(sd->mutex);
ERR_FAIL_COND(!sd);
@ -2196,7 +2196,7 @@ void TextServerFallback::shaped_text_set_preserve_invalid(RID p_shaped, bool p_e
}
bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@ -2204,7 +2204,7 @@ bool TextServerFallback::shaped_text_get_preserve_invalid(RID p_shaped) const {
}
void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
MutexLock lock(sd->mutex);
@ -2218,15 +2218,52 @@ void TextServerFallback::shaped_text_set_preserve_control(RID p_shaped, bool p_e
}
bool TextServerFallback::shaped_text_get_preserve_control(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
return sd->preserve_control;
}
bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
int TextServerFallback::shaped_get_span_count(RID p_shaped) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0);
return sd->spans.size();
}
Variant TextServerFallback::shaped_get_span_meta(RID p_shaped, int p_index) const {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Variant());
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
return sd->spans[p_index].meta;
}
void TextServerFallback::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND(!sd);
ERR_FAIL_INDEX(p_index, sd->spans.size());
ShapedTextDataFallback::Span &span = sd->spans.write[p_index];
span.fonts.clear();
// Pre-sort fonts, push fonts with the language support first.
Vector<RID> fonts_no_match;
int font_count = p_fonts.size();
for (int i = 0; i < font_count; i++) {
if (font_is_language_supported(p_fonts[i], span.language)) {
span.fonts.push_back(p_fonts[i]);
} else {
fonts_no_match.push_back(p_fonts[i]);
}
}
span.fonts.append_array(fonts_no_match);
span.font_size = p_size;
span.features = p_opentype_features;
sd->valid = false;
}
bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@ -2244,7 +2281,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te
full_copy(sd);
}
ShapedTextData::Span span;
ShapedTextDataFallback::Span span;
span.start = sd->text.length();
span.end = span.start + p_text.length();
@ -2263,6 +2300,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te
ERR_FAIL_COND_V(span.fonts.is_empty(), false);
span.font_size = p_size;
span.language = p_language;
span.meta = p_meta;
sd->spans.push_back(span);
sd->text += p_text;
@ -2273,7 +2311,7 @@ bool TextServerFallback::shaped_text_add_string(RID p_shaped, const String &p_te
}
bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align, int p_length) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@ -2284,12 +2322,12 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con
full_copy(sd);
}
ShapedTextData::Span span;
ShapedTextDataFallback::Span span;
span.start = sd->start + sd->text.length();
span.end = span.start + p_length;
span.embedded_key = p_key;
ShapedTextData::EmbeddedObject obj;
ShapedTextDataFallback::EmbeddedObject obj;
obj.inline_align = p_inline_align;
obj.rect.size = p_size;
obj.pos = span.start;
@ -2304,7 +2342,7 @@ bool TextServerFallback::shaped_text_add_object(RID p_shaped, Variant p_key, con
}
bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@ -2324,7 +2362,7 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key,
Glyph gl = sd->glyphs[i];
Variant key;
if (gl.count == 1) {
for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
if (E.value.pos == gl.start) {
key = E.key;
break;
@ -2364,79 +2402,82 @@ bool TextServerFallback::shaped_text_resize_object(RID p_shaped, Variant p_key,
sd->width += gl.advance * gl.repeat;
}
}
// Align embedded objects to baseline.
float full_ascent = sd->ascent;
float full_descent = sd->descent;
for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
if ((E.value.pos >= sd->start) && (E.value.pos < sd->end)) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.y = -sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.y = (-sd->ascent + sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.y = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.y = sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.y -= E.value.rect.size.y;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.y -= E.value.rect.size.y / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.y);
full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
} else {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.x = -sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.x = (-sd->ascent + sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.x = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.x = sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.x -= E.value.rect.size.x;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.x -= E.value.rect.size.x / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.x);
full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
}
}
}
sd->ascent = full_ascent;
sd->descent = full_descent;
_realign(sd);
}
return true;
}
void TextServerFallback::_realign(ShapedTextDataFallback *p_sd) const {
// Align embedded objects to baseline.
float full_ascent = p_sd->ascent;
float full_descent = p_sd->descent;
for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : p_sd->objects) {
if ((E.value.pos >= p_sd->start) && (E.value.pos < p_sd->end)) {
if (p_sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.y = -p_sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.y = (-p_sd->ascent + p_sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.y = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.y = p_sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.y -= E.value.rect.size.y;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.y -= E.value.rect.size.y / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.y);
full_descent = MAX(full_descent, E.value.rect.position.y + E.value.rect.size.y);
} else {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
E.value.rect.position.x = -p_sd->ascent;
} break;
case INLINE_ALIGNMENT_TO_CENTER: {
E.value.rect.position.x = (-p_sd->ascent + p_sd->descent) / 2;
} break;
case INLINE_ALIGNMENT_TO_BASELINE: {
E.value.rect.position.x = 0;
} break;
case INLINE_ALIGNMENT_TO_BOTTOM: {
E.value.rect.position.x = p_sd->descent;
} break;
}
switch (E.value.inline_align & INLINE_ALIGNMENT_IMAGE_MASK) {
case INLINE_ALIGNMENT_BOTTOM_TO: {
E.value.rect.position.x -= E.value.rect.size.x;
} break;
case INLINE_ALIGNMENT_CENTER_TO: {
E.value.rect.position.x -= E.value.rect.size.x / 2;
} break;
case INLINE_ALIGNMENT_TOP_TO: {
// NOP
} break;
}
full_ascent = MAX(full_ascent, -E.value.rect.position.x);
full_descent = MAX(full_descent, E.value.rect.position.x + E.value.rect.size.x);
}
}
}
p_sd->ascent = full_ascent;
p_sd->descent = full_descent;
}
RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_length) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, RID());
MutexLock lock(sd->mutex);
@ -2450,7 +2491,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng
ERR_FAIL_COND_V(sd->start > p_start || sd->end < p_start, RID());
ERR_FAIL_COND_V(sd->end < p_start + p_length, RID());
ShapedTextData *new_sd = memnew(ShapedTextData);
ShapedTextDataFallback *new_sd = memnew(ShapedTextDataFallback);
new_sd->parent = p_shaped;
new_sd->start = p_start;
new_sd->end = p_start + p_length;
@ -2476,7 +2517,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng
Variant key;
bool find_embedded = false;
if (gl.count == 1) {
for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
if (E.value.pos == gl.start) {
find_embedded = true;
key = E.key;
@ -2520,7 +2561,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng
// Align embedded objects to baseline.
float full_ascent = new_sd->ascent;
float full_descent = new_sd->descent;
for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : new_sd->objects) {
for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : new_sd->objects) {
if ((E.value.pos >= new_sd->start) && (E.value.pos < new_sd->end)) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
@ -2590,7 +2631,7 @@ RID TextServerFallback::shaped_text_substr(RID p_shaped, int p_start, int p_leng
}
RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, RID());
MutexLock lock(sd->mutex);
@ -2598,7 +2639,7 @@ RID TextServerFallback::shaped_text_get_parent(RID p_shaped) const {
}
float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width, uint16_t /*JustificationFlag*/ p_jst_flags) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@ -2707,7 +2748,7 @@ float TextServerFallback::shaped_text_fit_to_width(RID p_shaped, float p_width,
}
float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat32Array &p_tab_stops) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@ -2763,7 +2804,7 @@ float TextServerFallback::shaped_text_tab_align(RID p_shaped, const PackedFloat3
}
bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@ -2819,7 +2860,7 @@ bool TextServerFallback::shaped_text_update_breaks(RID p_shaped) {
}
bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@ -2835,7 +2876,7 @@ bool TextServerFallback::shaped_text_update_justification_ops(RID p_shaped) {
}
void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, float p_width, uint16_t p_trim_flags) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped_line);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped_line);
ERR_FAIL_COND_MSG(!sd, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
@ -2863,9 +2904,9 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
return;
}
Vector<ShapedTextData::Span> &spans = sd->spans;
Vector<ShapedTextDataFallback::Span> &spans = sd->spans;
if (sd->parent != RID()) {
ShapedTextData *parent_sd = shaped_owner.get_or_null(sd->parent);
ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent);
ERR_FAIL_COND(!parent_sd->valid);
spans = parent_sd->spans;
}
@ -2987,39 +3028,39 @@ void TextServerFallback::shaped_text_overrun_trim_to_width(RID p_shaped_line, fl
}
int TextServerFallback::shaped_text_get_trim_pos(RID p_shaped) const {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid.");
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.trim_pos;
}
int TextServerFallback::shaped_text_get_ellipsis_pos(RID p_shaped) const {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextData invalid.");
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, -1, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_pos;
}
const Glyph *TextServerFallback::shaped_text_get_ellipsis_glyphs(RID p_shaped) const {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextData invalid.");
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, nullptr, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.ptr();
}
int TextServerFallback::shaped_text_get_ellipsis_glyph_count(RID p_shaped) const {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextData invalid.");
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V_MSG(!sd, 0, "ShapedTextDataFallback invalid.");
MutexLock lock(sd->mutex);
return sd->overrun_trim_data.ellipsis_glyph_buf.size();
}
bool TextServerFallback::shaped_text_shape(RID p_shaped) {
ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@ -3046,7 +3087,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
// "Shape" string.
for (int i = 0; i < sd->spans.size(); i++) {
const ShapedTextData::Span &span = sd->spans[i];
const ShapedTextDataFallback::Span &span = sd->spans[i];
if (span.embedded_key != Variant()) {
// Embedded object.
if (sd->orientation == ORIENTATION_HORIZONTAL) {
@ -3146,7 +3187,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
// Align embedded objects to baseline.
float full_ascent = sd->ascent;
float full_descent = sd->descent;
for (KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
for (KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
if (sd->orientation == ORIENTATION_HORIZONTAL) {
switch (E.value.inline_align & INLINE_ALIGNMENT_TEXT_MASK) {
case INLINE_ALIGNMENT_TO_TOP: {
@ -3212,7 +3253,7 @@ bool TextServerFallback::shaped_text_shape(RID p_shaped) {
}
bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, false);
MutexLock lock(sd->mutex);
@ -3220,7 +3261,7 @@ bool TextServerFallback::shaped_text_is_ready(RID p_shaped) const {
}
const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, nullptr);
MutexLock lock(sd->mutex);
@ -3231,7 +3272,7 @@ const Glyph *TextServerFallback::shaped_text_get_glyphs(RID p_shaped) const {
}
int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0);
MutexLock lock(sd->mutex);
@ -3242,7 +3283,7 @@ int TextServerFallback::shaped_text_get_glyph_count(RID p_shaped) const {
}
const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, nullptr);
MutexLock lock(sd->mutex);
@ -3254,7 +3295,7 @@ const Glyph *TextServerFallback::shaped_text_sort_logical(RID p_shaped) {
}
Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Vector2i());
MutexLock lock(sd->mutex);
@ -3263,11 +3304,11 @@ Vector2i TextServerFallback::shaped_text_get_range(RID p_shaped) const {
Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const {
Array ret;
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, ret);
MutexLock lock(sd->mutex);
for (const KeyValue<Variant, ShapedTextData::EmbeddedObject> &E : sd->objects) {
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
ret.push_back(E.key);
}
@ -3275,7 +3316,7 @@ Array TextServerFallback::shaped_text_get_objects(RID p_shaped) const {
}
Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_key) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Rect2());
MutexLock lock(sd->mutex);
@ -3287,7 +3328,7 @@ Rect2 TextServerFallback::shaped_text_get_object_rect(RID p_shaped, Variant p_ke
}
Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, Size2());
MutexLock lock(sd->mutex);
@ -3302,7 +3343,7 @@ Size2 TextServerFallback::shaped_text_get_size(RID p_shaped) const {
}
float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@ -3313,7 +3354,7 @@ float TextServerFallback::shaped_text_get_ascent(RID p_shaped) const {
}
float TextServerFallback::shaped_text_get_descent(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@ -3324,7 +3365,7 @@ float TextServerFallback::shaped_text_get_descent(RID p_shaped) const {
}
float TextServerFallback::shaped_text_get_width(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@ -3335,7 +3376,7 @@ float TextServerFallback::shaped_text_get_width(RID p_shaped) const {
}
float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);
@ -3347,7 +3388,7 @@ float TextServerFallback::shaped_text_get_underline_position(RID p_shaped) const
}
float TextServerFallback::shaped_text_get_underline_thickness(RID p_shaped) const {
const ShapedTextData *sd = shaped_owner.get_or_null(p_shaped);
const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
ERR_FAIL_COND_V(!sd, 0.f);
MutexLock lock(sd->mutex);

View file

@ -203,17 +203,38 @@ class TextServerFallback : public TextServer {
}
}
// Shaped text cache data.
struct ShapedTextDataFallback : public ShapedTextData {
struct Span {
int start = -1;
int end = -1;
Vector<RID> fonts;
int font_size = 0;
Variant embedded_key;
String language;
Dictionary features;
Variant meta;
};
Vector<Span> spans;
};
// Common data.
float oversampling = 1.f;
mutable RID_PtrOwner<FontDataFallback> font_owner;
mutable RID_PtrOwner<ShapedTextData> shaped_owner;
mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner;
void _realign(ShapedTextDataFallback *p_sd) const;
protected:
static void _bind_methods(){};
void full_copy(ShapedTextData *p_shaped);
void invalidate(ShapedTextData *p_shaped);
void full_copy(ShapedTextDataFallback *p_shaped);
void invalidate(ShapedTextDataFallback *p_shaped);
public:
virtual bool has_feature(Feature p_feature) const override;
@ -391,10 +412,14 @@ public:
virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) override;
virtual bool shaped_text_get_preserve_control(RID p_shaped) const override;
virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override;
virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override;
virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override;
virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override;
virtual int shaped_get_span_count(RID p_shaped) const override;
virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override;
virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override;
virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override;
virtual RID shaped_text_get_parent(RID p_shaped) const override;

View file

@ -82,9 +82,11 @@ void Label::_shape() {
Ref<StyleBox> style = get_theme_stylebox(SNAME("normal"), SNAME("Label"));
int width = (get_size().width - style->get_minimum_size().width);
if (dirty) {
if (dirty || font_dirty) {
String lang = (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale();
TS->shaped_text_clear(text_rid);
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 {
@ -97,9 +99,17 @@ void Label::_shape() {
if (visible_chars >= 0 && visible_chars_behavior == VC_CHARS_BEFORE_SHAPING) {
text = text.substr(0, visible_chars);
}
TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang);
if (dirty) {
TS->shaped_text_add_string(text_rid, text, font->get_rids(), font_size, opentype_features, lang);
} else {
int spans = TS->shaped_get_span_count(text_rid);
for (int i = 0; i < spans; i++) {
TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, opentype_features);
}
}
TS->shaped_text_set_bidi_override(text_rid, structured_text_parser(st_parser, st_args, text));
dirty = false;
font_dirty = false;
lines_dirty = true;
}
@ -276,7 +286,7 @@ void Label::_notification(int p_what) {
RenderingServer::get_singleton()->canvas_item_set_clip(get_canvas_item(), true);
}
if (dirty || lines_dirty) {
if (dirty || font_dirty || lines_dirty) {
_shape();
}
@ -521,7 +531,7 @@ void Label::_notification(int p_what) {
}
if (p_what == NOTIFICATION_THEME_CHANGED) {
dirty = true;
font_dirty = true;
update();
}
if (p_what == NOTIFICATION_RESIZED) {
@ -531,7 +541,7 @@ void Label::_notification(int p_what) {
Size2 Label::get_minimum_size() const {
// don't want to mutable everything
if (dirty || lines_dirty) {
if (dirty || font_dirty || lines_dirty) {
const_cast<Label *>(this)->_shape();
}
@ -555,7 +565,7 @@ int Label::get_line_count() const {
if (!is_inside_tree()) {
return 1;
}
if (dirty || lines_dirty) {
if (dirty || font_dirty || lines_dirty) {
const_cast<Label *>(this)->_shape();
}
@ -630,7 +640,7 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
dirty = true;
font_dirty = true;
update();
}
}
@ -638,7 +648,7 @@ void Label::set_text_direction(Control::TextDirection p_text_direction) {
void Label::set_structured_text_bidi_override(Control::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
dirty = true;
font_dirty = true;
update();
}
}
@ -649,7 +659,7 @@ Control::StructuredTextParser Label::get_structured_text_bidi_override() const {
void Label::set_structured_text_bidi_override_options(Array p_args) {
st_args = p_args;
dirty = true;
font_dirty = true;
update();
}
@ -663,7 +673,7 @@ Control::TextDirection Label::get_text_direction() const {
void Label::clear_opentype_features() {
opentype_features.clear();
dirty = true;
font_dirty = true;
update();
}
@ -671,7 +681,7 @@ void Label::set_opentype_feature(const String &p_name, int p_value) {
int32_t tag = TS->name_to_tag(p_name);
if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) {
opentype_features[tag] = p_value;
dirty = true;
font_dirty = true;
update();
}
}
@ -798,7 +808,7 @@ int Label::get_max_lines_visible() const {
}
int Label::get_total_character_count() const {
if (dirty || lines_dirty) {
if (dirty || font_dirty || lines_dirty) {
const_cast<Label *>(this)->_shape();
}
@ -814,13 +824,13 @@ bool Label::_set(const StringName &p_name, const Variant &p_value) {
if (value == -1) {
if (opentype_features.has(tag)) {
opentype_features.erase(tag);
dirty = true;
font_dirty = true;
update();
}
} else {
if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) {
opentype_features[tag] = value;
dirty = true;
font_dirty = true;
update();
}
}

View file

@ -73,6 +73,7 @@ private:
bool lines_dirty = true;
bool dirty = true;
bool font_dirty = true;
RID text_rid;
Vector<RID> lines_rid;

View file

@ -205,6 +205,49 @@ String RichTextLabel::_letters(int p_num, bool p_capitalize) const {
return s;
}
void RichTextLabel::_update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size) {
ERR_FAIL_COND(p_frame == nullptr);
ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
Line &l = p_frame->lines.write[p_line];
RID t = l.text_buf->get_rid();
int spans = TS->shaped_get_span_count(t);
for (int i = 0; i < spans; i++) {
ItemText *it = (ItemText *)(uint64_t)TS->shaped_get_span_meta(t, i);
if (it) {
Ref<Font> font = _find_font(it);
if (font.is_null()) {
font = p_base_font;
}
int font_size = _find_font_size(it);
if (font_size == -1) {
font_size = p_base_font_size;
}
Dictionary font_ftr = _find_font_features(it);
TS->shaped_set_span_update_font(t, i, font->get_rids(), font_size, font_ftr);
}
}
Item *it_to = (p_line + 1 < 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) {
case ITEM_TABLE: {
ItemTable *table = static_cast<ItemTable *>(it);
for (Item *E : table->subitems) {
ERR_CONTINUE(E->type != ITEM_FRAME); // Children should all be frames.
ItemFrame *frame = static_cast<ItemFrame *>(E);
for (int i = 0; i < frame->lines.size(); i++) {
_update_line_font(frame, i, p_base_font, p_base_font_size);
}
}
} break;
default:
break;
}
}
}
void RichTextLabel::_resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width) {
ERR_FAIL_COND(p_frame == nullptr);
ERR_FAIL_COND(p_line < 0 || p_line >= p_frame->lines.size());
@ -449,7 +492,7 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
}
remaining_characters -= tx.length();
l.text_buf->add_string(tx, font, font_size, font_ftr, lang);
l.text_buf->add_string(tx, font, font_size, font_ftr, lang, (uint64_t)it);
text += tx;
l.char_count += tx.length();
} break;
@ -1448,7 +1491,10 @@ void RichTextLabel::_notification(int p_what) {
update();
} break;
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
main->first_invalid_font_line = 0; //invalidate ALL
update();
} break;
case NOTIFICATION_ENTER_TREE: {
if (!text.is_empty()) {
set_text(text);
@ -1545,6 +1591,10 @@ Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const
return get_default_cursor_shape(); //invalid
}
if (main->first_invalid_font_line < main->lines.size()) {
return get_default_cursor_shape(); //invalid
}
if (main->first_resized_line < main->lines.size()) {
return get_default_cursor_shape(); //invalid
}
@ -1569,6 +1619,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
if (main->first_invalid_line < main->lines.size()) {
return;
}
if (main->first_invalid_font_line < main->lines.size()) {
return;
}
if (main->first_resized_line < main->lines.size()) {
return;
}
@ -1732,6 +1785,9 @@ void RichTextLabel::gui_input(const Ref<InputEvent> &p_event) {
if (main->first_invalid_line < main->lines.size()) {
return;
}
if (main->first_invalid_font_line < main->lines.size()) {
return;
}
if (main->first_resized_line < main->lines.size()) {
return;
}
@ -2184,6 +2240,18 @@ bool RichTextLabel::_find_layout_subitem(Item *from, Item *to) {
void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
if (p_frame->first_invalid_line == p_frame->lines.size()) {
Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
// Update fonts.
if (p_frame->first_invalid_font_line != p_frame->lines.size()) {
for (int i = p_frame->first_invalid_font_line; i < p_frame->lines.size(); i++) {
_update_line_font(p_frame, i, base_font, base_font_size);
}
p_frame->first_resized_line = p_frame->first_invalid_font_line;
p_frame->first_invalid_font_line = p_frame->lines.size();
}
if (p_frame->first_resized_line == p_frame->lines.size()) {
return;
}
@ -2191,9 +2259,6 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
// Resize lines without reshaping.
Rect2 text_rect = _get_text_rect();
Ref<Font> base_font = get_theme_font(SNAME("normal_font"));
int base_font_size = get_theme_font_size(SNAME("normal_font_size"));
for (int i = p_frame->first_resized_line; i < p_frame->lines.size(); i++) {
_resize_line(p_frame, i, base_font, base_font_size, text_rect.get_size().width - scroll_w);
}
@ -2237,6 +2302,7 @@ void RichTextLabel::_validate_line_caches(ItemFrame *p_frame) {
p_frame->first_invalid_line = p_frame->lines.size();
p_frame->first_resized_line = p_frame->lines.size();
p_frame->first_invalid_font_line = p_frame->lines.size();
updating_scroll = true;
vscroll->set_max(total_height);
@ -4494,6 +4560,7 @@ RichTextLabel::RichTextLabel() {
main->lines.write[0].from = main;
main->first_invalid_line = 0;
main->first_resized_line = 0;
main->first_invalid_font_line = 0;
current_frame = main;
vscroll = memnew(VScrollBar);

View file

@ -129,6 +129,7 @@ private:
Vector<Line> lines;
int first_invalid_line = 0;
int first_invalid_font_line = 0;
int first_resized_line = 0;
ItemFrame *parent_frame = nullptr;
@ -414,6 +415,7 @@ private:
void _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, int *r_char_offset);
void _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width);
void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr);

View file

@ -187,29 +187,44 @@ void TextEdit::Text::_calculate_max_line_width() {
max_width = width;
}
void TextEdit::Text::invalidate_cache(int p_line, int p_column, const String &p_ime_text, const Array &p_bidi_override) {
void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) {
ERR_FAIL_INDEX(p_line, text.size());
if (font.is_null() || font_size <= 0) {
return; // Not in tree?
}
text.write[p_line].data_buf->clear();
if (p_text_changed) {
text.write[p_line].data_buf->clear();
}
text.write[p_line].data_buf->set_width(width);
text.write[p_line].data_buf->set_direction((TextServer::Direction)direction);
text.write[p_line].data_buf->set_preserve_control(draw_control_chars);
if (p_ime_text.length() > 0) {
text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language);
if (p_text_changed) {
text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, opentype_features, language);
}
if (!p_bidi_override.is_empty()) {
TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override);
}
} else {
text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language);
if (p_text_changed) {
text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, opentype_features, language);
}
if (!text[p_line].bidi_override.is_empty()) {
TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override);
}
}
if (!p_text_changed) {
RID r = text.write[p_line].data_buf->get_rid();
int spans = TS->shaped_get_span_count(r);
for (int i = 0; i < spans; i++) {
TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, opentype_features);
}
}
// Apply tab align.
if (tab_size > 0) {
Vector<float> tabs;
@ -266,6 +281,24 @@ void TextEdit::Text::invalidate_all_lines() {
}
}
void TextEdit::Text::invalidate_font() {
if (!is_dirty) {
return;
}
max_width = -1;
line_height = -1;
if (!font.is_null() && font_size > 0) {
font_height = font->get_height(font_size);
}
for (int i = 0; i < text.size(); i++) {
invalidate_cache(i, -1, false);
}
is_dirty = false;
}
void TextEdit::Text::invalidate_all() {
if (!is_dirty) {
return;
@ -279,7 +312,7 @@ void TextEdit::Text::invalidate_all() {
}
for (int i = 0; i < text.size(); i++) {
invalidate_cache(i);
invalidate_cache(i, -1, true);
}
is_dirty = false;
}
@ -294,7 +327,7 @@ void TextEdit::Text::clear() {
line.gutters.resize(gutter_count);
line.data = "";
text.insert(0, line);
invalidate_cache(0);
invalidate_cache(0, -1, true);
}
int TextEdit::Text::get_max_width() const {
@ -306,7 +339,7 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o
text.write[p_line].data = p_text;
text.write[p_line].bidi_override = p_bidi_override;
invalidate_cache(p_line);
invalidate_cache(p_line, -1, true);
}
void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) {
@ -331,7 +364,7 @@ void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector
line.data = p_text[i];
line.bidi_override = p_bidi_override[i];
text.write[p_at + i] = line;
invalidate_cache(p_at + i);
invalidate_cache(p_at + i, -1, true);
}
}
@ -1446,9 +1479,11 @@ void TextEdit::_notification(int p_what) {
DisplayServer::get_singleton()->window_set_ime_position(Point2(), get_viewport()->get_window_id());
DisplayServer::get_singleton()->window_set_ime_active(false, get_viewport()->get_window_id());
}
ime_text = "";
ime_selection = Point2();
text.invalidate_cache(caret.line, caret.column, ime_text);
if (!ime_text.is_empty()) {
ime_text = "";
ime_selection = Point2();
text.invalidate_cache(caret.line, caret.column, true, ime_text);
}
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
@ -1470,7 +1505,7 @@ void TextEdit::_notification(int p_what) {
t = ime_text;
}
text.invalidate_cache(caret.line, caret.column, t, structured_text_parser(st_parser, st_args, t));
text.invalidate_cache(caret.line, caret.column, true, t, structured_text_parser(st_parser, st_args, t));
update();
}
} break;
@ -2538,7 +2573,7 @@ void TextEdit::_update_caches() {
text.set_draw_control_chars(draw_control_chars);
text.set_font(font);
text.set_font_size(font_size);
text.invalidate_all();
text.invalidate_font();
_update_placeholder();
/* Syntax highlighting. */
@ -2718,7 +2753,7 @@ void TextEdit::set_text_direction(Control::TextDirection p_text_direction) {
dir = (TextServer::Direction)text_direction;
}
text.set_direction_and_language(dir, (!language.is_empty()) ? language : TranslationServer::get_singleton()->get_tool_locale());
text.invalidate_all();
text.invalidate_font();
_update_placeholder();
if (menu_dir) {
@ -2740,7 +2775,7 @@ void TextEdit::set_opentype_feature(const String &p_name, int p_value) {
if (!opentype_features.has(tag) || (int)opentype_features[tag] != p_value) {
opentype_features[tag] = p_value;
text.set_font_features(opentype_features);
text.invalidate_all();
text.invalidate_font();
_update_placeholder();
update();
}
@ -2757,7 +2792,7 @@ int TextEdit::get_opentype_feature(const String &p_name) const {
void TextEdit::clear_opentype_features() {
opentype_features.clear();
text.set_font_features(opentype_features);
text.invalidate_all();
text.invalidate_font();
_update_placeholder();
update();
}
@ -4852,7 +4887,7 @@ void TextEdit::set_draw_control_chars(bool p_enabled) {
menu->set_item_checked(menu->get_item_index(MENU_DISPLAY_UCC), draw_control_chars);
}
text.set_draw_control_chars(draw_control_chars);
text.invalidate_all();
text.invalidate_font();
_update_placeholder();
update();
}
@ -5319,7 +5354,7 @@ bool TextEdit::_set(const StringName &p_name, const Variant &p_value) {
if (opentype_features.has(tag)) {
opentype_features.erase(tag);
text.set_font_features(opentype_features);
text.invalidate_all();
text.invalidate_font();
_update_placeholder();
update();
}
@ -5327,7 +5362,7 @@ bool TextEdit::_set(const StringName &p_name, const Variant &p_value) {
if (!opentype_features.has(tag) || (int)opentype_features[tag] != value) {
opentype_features[tag] = value;
text.set_font_features(opentype_features);
text.invalidate_all();
text.invalidate_font();
_update_placeholder();
update();
}

View file

@ -210,7 +210,8 @@ private:
int size() const { return text.size(); }
void clear();
void invalidate_cache(int p_line, int p_column = -1, const String &p_ime_text = String(), const Array &p_bidi_override = Array());
void invalidate_cache(int p_line, int p_column = -1, bool p_text_changed = false, const String &p_ime_text = String(), const Array &p_bidi_override = Array());
void invalidate_font();
void invalidate_all();
void invalidate_all_lines();

View file

@ -55,7 +55,7 @@ void TextLine::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bidi_override", "override"), &TextLine::set_bidi_override);
ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language"), &TextLine::add_string, DEFVAL(Dictionary()), DEFVAL(""));
ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language", "meta"), &TextLine::add_string, DEFVAL(Dictionary()), DEFVAL(""), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextLine::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1));
ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextLine::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER));
@ -200,9 +200,9 @@ void TextLine::set_bidi_override(const Array &p_override) {
dirty = true;
}
bool TextLine::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
bool TextLine::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ERR_FAIL_COND_V(p_fonts.is_null(), false);
bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language);
bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language, p_meta);
spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP);
spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM);
dirty = true;

View file

@ -86,7 +86,7 @@ public:
void set_preserve_control(bool p_enabled);
bool get_preserve_control() const;
bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "");
bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), 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);
bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER);

View file

@ -63,7 +63,7 @@ void TextParagraph::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_dropcap", "text", "fonts", "size", "dropcap_margins", "opentype_features", "language"), &TextParagraph::set_dropcap, DEFVAL(Rect2()), DEFVAL(Dictionary()), DEFVAL(""));
ClassDB::bind_method(D_METHOD("clear_dropcap"), &TextParagraph::clear_dropcap);
ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language"), &TextParagraph::add_string, DEFVAL(Dictionary()), DEFVAL(""));
ClassDB::bind_method(D_METHOD("add_string", "text", "fonts", "size", "opentype_features", "language", "meta"), &TextParagraph::add_string, DEFVAL(Dictionary()), DEFVAL(""), DEFVAL(Variant()));
ClassDB::bind_method(D_METHOD("add_object", "key", "size", "inline_align", "length"), &TextParagraph::add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1));
ClassDB::bind_method(D_METHOD("resize_object", "key", "size", "inline_align"), &TextParagraph::resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER));
@ -344,9 +344,9 @@ void TextParagraph::clear_dropcap() {
lines_dirty = true;
}
bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
bool TextParagraph::add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
ERR_FAIL_COND_V(p_fonts.is_null(), false);
bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language);
bool res = TS->shaped_text_add_string(rid, p_text, p_fonts->get_rids(), p_size, p_opentype_features, p_language, p_meta);
spacing_top = p_fonts->get_spacing(TextServer::SPACING_TOP);
spacing_bottom = p_fonts->get_spacing(TextServer::SPACING_BOTTOM);
lines_dirty = true;

View file

@ -102,7 +102,7 @@ public:
bool set_dropcap(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "");
void clear_dropcap();
bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "");
bool add_string(const String &p_text, const Ref<Font> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), 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);
bool resize_object(Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER);

View file

@ -210,10 +210,14 @@ void TextServerExtension::_bind_methods() {
GDVIRTUAL_BIND(_shaped_text_set_preserve_control, "shaped", "enabled");
GDVIRTUAL_BIND(_shaped_text_get_preserve_control, "shaped");
GDVIRTUAL_BIND(_shaped_text_add_string, "shaped", "text", "fonts", "size", "opentype_features", "language");
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");
GDVIRTUAL_BIND(_shaped_text_resize_object, "shaped", "key", "size", "inline_align");
GDVIRTUAL_BIND(_shaped_get_span_count, "shaped");
GDVIRTUAL_BIND(_shaped_get_span_meta, "shaped", "index");
GDVIRTUAL_BIND(_shaped_set_span_update_font, "shaped", "index", "fonts", "size", "opentype_features");
GDVIRTUAL_BIND(_shaped_text_substr, "shaped", "start", "length");
GDVIRTUAL_BIND(_shaped_text_get_parent, "shaped");
@ -1018,13 +1022,13 @@ bool TextServerExtension::shaped_text_get_preserve_control(RID p_shaped) const {
return false;
}
bool TextServerExtension::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language) {
bool TextServerExtension::shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) {
bool ret;
Array fonts;
for (int i = 0; i < p_fonts.size(); i++) {
fonts.push_back(p_fonts[i]);
}
if (GDVIRTUAL_CALL(_shaped_text_add_string, p_shaped, p_text, fonts, p_size, p_opentype_features, p_language, ret)) {
if (GDVIRTUAL_CALL(_shaped_text_add_string, p_shaped, p_text, fonts, p_size, p_opentype_features, p_language, p_meta, ret)) {
return ret;
}
return false;
@ -1046,6 +1050,30 @@ bool TextServerExtension::shaped_text_resize_object(RID p_shaped, Variant p_key,
return false;
}
int TextServerExtension::shaped_get_span_count(RID p_shaped) const {
int ret;
if (GDVIRTUAL_CALL(_shaped_get_span_count, p_shaped, ret)) {
return ret;
}
return 0;
}
Variant TextServerExtension::shaped_get_span_meta(RID p_shaped, int p_index) const {
Variant ret;
if (GDVIRTUAL_CALL(_shaped_get_span_meta, p_shaped, p_index, ret)) {
return ret;
}
return false;
}
void TextServerExtension::shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features) {
Array fonts;
for (int i = 0; i < p_fonts.size(); i++) {
fonts.push_back(p_fonts[i]);
}
GDVIRTUAL_CALL(_shaped_set_span_update_font, p_shaped, p_index, fonts, p_size, p_opentype_features);
}
RID TextServerExtension::shaped_text_substr(RID p_shaped, int p_start, int p_length) const {
RID ret;
if (GDVIRTUAL_CALL(_shaped_text_substr, p_shaped, p_start, p_length, ret)) {

View file

@ -343,13 +343,20 @@ public:
GDVIRTUAL2(_shaped_text_set_preserve_control, RID, bool);
GDVIRTUAL1RC(bool, _shaped_text_get_preserve_control, RID);
virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") override;
virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) override;
virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) override;
virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) override;
GDVIRTUAL6R(bool, _shaped_text_add_string, RID, const String &, const Array &, int, const Dictionary &, const String &);
GDVIRTUAL7R(bool, _shaped_text_add_string, RID, const String &, const Array &, int, const Dictionary &, const String &, const Variant &);
GDVIRTUAL5R(bool, _shaped_text_add_object, RID, Variant, const Size2 &, InlineAlignment, int);
GDVIRTUAL4R(bool, _shaped_text_resize_object, RID, Variant, const Size2 &, InlineAlignment);
virtual int shaped_get_span_count(RID p_shaped) const override;
virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const override;
virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) override;
GDVIRTUAL1RC(int, _shaped_get_span_count, RID);
GDVIRTUAL2RC(Variant, _shaped_get_span_meta, RID, int);
GDVIRTUAL5(_shaped_set_span_update_font, RID, int, const Array &, int, const Dictionary &);
virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const override;
virtual RID shaped_text_get_parent(RID p_shaped) const override;
GDVIRTUAL3RC(RID, _shaped_text_substr, RID, int, int);

View file

@ -363,10 +363,14 @@ void TextServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("shaped_text_set_preserve_control", "shaped", "enabled"), &TextServer::shaped_text_set_preserve_control);
ClassDB::bind_method(D_METHOD("shaped_text_get_preserve_control", "shaped"), &TextServer::shaped_text_get_preserve_control);
ClassDB::bind_method(D_METHOD("shaped_text_add_string", "shaped", "text", "fonts", "size", "opentype_features", "language"), &TextServer::shaped_text_add_string, DEFVAL(Dictionary()), DEFVAL(""));
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"), &TextServer::shaped_text_add_object, DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(1));
ClassDB::bind_method(D_METHOD("shaped_text_resize_object", "shaped", "key", "size", "inline_align"), &TextServer::shaped_text_resize_object, DEFVAL(INLINE_ALIGNMENT_CENTER));
ClassDB::bind_method(D_METHOD("shaped_get_span_count", "shaped"), &TextServer::shaped_get_span_count);
ClassDB::bind_method(D_METHOD("shaped_get_span_meta", "shaped", "index"), &TextServer::shaped_get_span_meta);
ClassDB::bind_method(D_METHOD("shaped_set_span_update_font", "shaped", "index", "fonts", "size", "opentype_features"), &TextServer::shaped_set_span_update_font, DEFVAL(Dictionary()));
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));

View file

@ -155,20 +155,6 @@ protected:
TextServer::Direction direction = DIRECTION_LTR; // Desired text direction.
TextServer::Orientation orientation = ORIENTATION_HORIZONTAL;
struct Span {
int start = -1;
int end = -1;
Vector<RID> fonts;
int font_size = 0;
Variant embedded_key;
String language;
Dictionary features;
};
Vector<Span> spans;
struct EmbeddedObject {
int pos = 0;
InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
@ -387,10 +373,14 @@ public:
virtual void shaped_text_set_preserve_control(RID p_shaped, bool p_enabled) = 0;
virtual bool shaped_text_get_preserve_control(RID p_shaped) const = 0;
virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "") = 0;
virtual bool shaped_text_add_string(RID p_shaped, const String &p_text, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary(), const String &p_language = "", const Variant &p_meta = Variant()) = 0;
virtual bool shaped_text_add_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER, int p_length = 1) = 0;
virtual bool shaped_text_resize_object(RID p_shaped, Variant p_key, const Size2 &p_size, InlineAlignment p_inline_align = INLINE_ALIGNMENT_CENTER) = 0;
virtual int shaped_get_span_count(RID p_shaped) const = 0;
virtual Variant shaped_get_span_meta(RID p_shaped, int p_index) const = 0;
virtual void shaped_set_span_update_font(RID p_shaped, int p_index, const Vector<RID> &p_fonts, int p_size, const Dictionary &p_opentype_features = Dictionary()) = 0;
virtual RID shaped_text_substr(RID p_shaped, int p_start, int p_length) const = 0; // Copy shaped substring (e.g. line break) without reshaping, but correctly reordered, preservers range.
virtual RID shaped_text_get_parent(RID p_shaped) const = 0;