[TextServer] Improve embedded objects handling performance.
This commit is contained in:
parent
b2b13d46c2
commit
df69cd6759
|
@ -1181,6 +1181,14 @@
|
|||
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_embedded_object" qualifiers="const">
|
||||
<return type="Variant" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="index" type="int" />
|
||||
<description>
|
||||
Returns text embedded object key.
|
||||
</description>
|
||||
</method>
|
||||
<method name="shaped_get_span_meta" qualifiers="const">
|
||||
<return type="Variant" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
|
|
@ -1290,6 +1290,15 @@
|
|||
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_embedded_object" qualifiers="virtual const">
|
||||
<return type="Variant" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
<param index="1" name="index" type="int" />
|
||||
<description>
|
||||
[b]Required.[/b]
|
||||
Returns text embedded object key.
|
||||
</description>
|
||||
</method>
|
||||
<method name="_shaped_get_span_meta" qualifiers="virtual const">
|
||||
<return type="Variant" />
|
||||
<param index="0" name="shaped" type="RID" />
|
||||
|
|
|
@ -369,7 +369,7 @@ void EditorSpinSlider::_draw_spin_slider() {
|
|||
}
|
||||
if (glyphs[i].font_rid != RID()) {
|
||||
TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
|
||||
} else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) {
|
||||
} else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
|
||||
TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4165,15 +4165,14 @@ void TextServerAdvanced::full_copy(ShapedTextDataAdvanced *p_shaped) {
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < parent->spans.size(); i++) {
|
||||
for (int i = p_shaped->first_span; i <= p_shaped->last_span; i++) {
|
||||
ShapedTextDataAdvanced::Span span = parent->spans[i];
|
||||
if (span.start >= p_shaped->end || span.end <= p_shaped->start) {
|
||||
continue;
|
||||
}
|
||||
span.start = MAX(p_shaped->start, span.start);
|
||||
span.end = MIN(p_shaped->end, span.end);
|
||||
p_shaped->spans.push_back(span);
|
||||
}
|
||||
p_shaped->first_span = 0;
|
||||
p_shaped->last_span = 0;
|
||||
|
||||
p_shaped->parent = RID();
|
||||
}
|
||||
|
@ -4199,6 +4198,8 @@ void TextServerAdvanced::_shaped_text_clear(const RID &p_shaped) {
|
|||
sd->end = 0;
|
||||
sd->text = String();
|
||||
sd->spans.clear();
|
||||
sd->first_span = 0;
|
||||
sd->last_span = 0;
|
||||
sd->objects.clear();
|
||||
sd->bidi_override.clear();
|
||||
invalidate(sd, true);
|
||||
|
@ -4383,19 +4384,48 @@ TextServer::Orientation TextServerAdvanced::_shaped_text_get_orientation(const R
|
|||
int64_t TextServerAdvanced::_shaped_get_span_count(const RID &p_shaped) const {
|
||||
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_NULL_V(sd, 0);
|
||||
return sd->spans.size();
|
||||
|
||||
if (sd->parent != RID()) {
|
||||
return sd->last_span - sd->first_span + 1;
|
||||
} else {
|
||||
return sd->spans.size();
|
||||
}
|
||||
}
|
||||
|
||||
Variant TextServerAdvanced::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const {
|
||||
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_NULL_V(sd, Variant());
|
||||
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
|
||||
return sd->spans[p_index].meta;
|
||||
if (sd->parent != RID()) {
|
||||
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
|
||||
ERR_FAIL_COND_V(!parent_sd->valid.is_set(), Variant());
|
||||
ERR_FAIL_INDEX_V(p_index + sd->first_span, parent_sd->spans.size(), Variant());
|
||||
return parent_sd->spans[p_index + sd->first_span].meta;
|
||||
} else {
|
||||
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
|
||||
return sd->spans[p_index].meta;
|
||||
}
|
||||
}
|
||||
|
||||
Variant TextServerAdvanced::_shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const {
|
||||
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_NULL_V(sd, Variant());
|
||||
if (sd->parent != RID()) {
|
||||
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
|
||||
ERR_FAIL_COND_V(!parent_sd->valid.is_set(), Variant());
|
||||
ERR_FAIL_INDEX_V(p_index + sd->first_span, parent_sd->spans.size(), Variant());
|
||||
return parent_sd->spans[p_index + sd->first_span].embedded_key;
|
||||
} else {
|
||||
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
|
||||
return sd->spans[p_index].embedded_key;
|
||||
}
|
||||
}
|
||||
|
||||
void TextServerAdvanced::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) {
|
||||
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_NULL(sd);
|
||||
if (sd->parent != RID()) {
|
||||
full_copy(sd);
|
||||
}
|
||||
ERR_FAIL_INDEX(p_index, sd->spans.size());
|
||||
|
||||
ShapedTextDataAdvanced::Span &span = sd->spans.ptrw()[p_index];
|
||||
|
@ -4489,18 +4519,22 @@ bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const V
|
|||
sd->width = 0;
|
||||
sd->upos = 0;
|
||||
sd->uthk = 0;
|
||||
|
||||
Vector<ShapedTextDataAdvanced::Span> &spans = sd->spans;
|
||||
if (sd->parent != RID()) {
|
||||
ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent);
|
||||
ERR_FAIL_COND_V(!parent_sd->valid.is_set(), false);
|
||||
spans = parent_sd->spans;
|
||||
}
|
||||
|
||||
int sd_size = sd->glyphs.size();
|
||||
int span_size = spans.size();
|
||||
|
||||
for (int i = 0; i < sd_size; i++) {
|
||||
Glyph gl = sd->glyphs[i];
|
||||
Variant key;
|
||||
if (gl.count == 1) {
|
||||
for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : sd->objects) {
|
||||
if (E.value.start == gl.start) {
|
||||
key = E.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((gl.flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && gl.span_index + sd->first_span >= 0 && gl.span_index + sd->first_span < span_size) {
|
||||
key = spans[gl.span_index + sd->first_span].embedded_key;
|
||||
}
|
||||
if (key != Variant()) {
|
||||
if (sd->orientation == ORIENTATION_HORIZONTAL) {
|
||||
|
@ -4671,6 +4705,20 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
|
|||
p_new_sd->utf16 = p_new_sd->text.utf16();
|
||||
p_new_sd->script_iter = memnew(ScriptIterator(p_new_sd->text, 0, p_new_sd->text.length()));
|
||||
|
||||
int span_size = p_sd->spans.size();
|
||||
|
||||
p_new_sd->first_span = 0;
|
||||
p_new_sd->last_span = span_size - 1;
|
||||
for (int i = 0; i < span_size; i++) {
|
||||
const ShapedTextDataAdvanced::Span &span = p_sd->spans[i];
|
||||
if (span.end <= p_start) {
|
||||
p_new_sd->first_span = i + 1;
|
||||
} else if (span.start >= p_start + p_length) {
|
||||
p_new_sd->last_span = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int sd_size = p_sd->glyphs.size();
|
||||
const Glyph *sd_glyphs = p_sd->glyphs.ptr();
|
||||
for (int ov = 0; ov < p_sd->bidi_override.size(); ov++) {
|
||||
|
@ -4749,31 +4797,27 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
|
|||
if ((sd_glyphs[j].start >= bidi_run_start) && (sd_glyphs[j].end <= bidi_run_end)) {
|
||||
// Copy glyphs.
|
||||
Glyph gl = sd_glyphs[j];
|
||||
if (gl.span_index >= 0) {
|
||||
gl.span_index -= p_new_sd->first_span;
|
||||
}
|
||||
if (gl.end == p_start + p_length && ((gl.flags & GRAPHEME_IS_SOFT_HYPHEN) == GRAPHEME_IS_SOFT_HYPHEN)) {
|
||||
uint32_t index = font_get_glyph_index(gl.font_rid, gl.font_size, 0x00ad, 0);
|
||||
float w = font_get_glyph_advance(gl.font_rid, gl.font_size, index)[(p_new_sd->orientation == ORIENTATION_HORIZONTAL) ? 0 : 1];
|
||||
gl.index = index;
|
||||
gl.advance = w;
|
||||
}
|
||||
Variant key;
|
||||
bool find_embedded = false;
|
||||
if (gl.count == 1) {
|
||||
for (const KeyValue<Variant, ShapedTextDataAdvanced::EmbeddedObject> &E : p_sd->objects) {
|
||||
if (E.value.start == gl.start) {
|
||||
find_embedded = true;
|
||||
key = E.key;
|
||||
p_new_sd->objects[key] = E.value;
|
||||
break;
|
||||
if ((gl.flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && gl.span_index >= 0 && gl.span_index < span_size) {
|
||||
Variant key = p_sd->spans[gl.span_index].embedded_key;
|
||||
if (key != Variant()) {
|
||||
ShapedTextDataAdvanced::EmbeddedObject obj = p_sd->objects[key];
|
||||
if (p_new_sd->orientation == ORIENTATION_HORIZONTAL) {
|
||||
obj.rect.position.x = p_new_sd->width;
|
||||
p_new_sd->width += obj.rect.size.x;
|
||||
} else {
|
||||
obj.rect.position.y = p_new_sd->width;
|
||||
p_new_sd->width += obj.rect.size.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (find_embedded) {
|
||||
if (p_new_sd->orientation == ORIENTATION_HORIZONTAL) {
|
||||
p_new_sd->objects[key].rect.position.x = p_new_sd->width;
|
||||
p_new_sd->width += p_new_sd->objects[key].rect.size.x;
|
||||
} else {
|
||||
p_new_sd->objects[key].rect.position.y = p_new_sd->width;
|
||||
p_new_sd->width += p_new_sd->objects[key].rect.size.y;
|
||||
p_new_sd->objects[key] = obj;
|
||||
}
|
||||
} else {
|
||||
if (gl.font_rid.is_valid()) {
|
||||
|
@ -5226,7 +5270,8 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
spans = parent_sd->spans;
|
||||
}
|
||||
|
||||
if (spans.size() == 0) {
|
||||
int span_size = spans.size();
|
||||
if (span_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -5237,7 +5282,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
// Find usable fonts, if fonts from the last glyph do not have required chars.
|
||||
RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
|
||||
if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
|
||||
const Array &fonts = spans[spans.size() - 1].fonts;
|
||||
const Array &fonts = spans[span_size - 1].fonts;
|
||||
for (int i = 0; i < fonts.size(); i++) {
|
||||
if (_font_has_char(fonts[i], sd->el_char)) {
|
||||
dot_gl_font_rid = fonts[i];
|
||||
|
@ -5247,7 +5292,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
}
|
||||
if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
|
||||
const char32_t u32str[] = { sd->el_char, 0 };
|
||||
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
|
||||
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[span_size - 1].language, u32str);
|
||||
if (rid.is_valid()) {
|
||||
dot_gl_font_rid = rid;
|
||||
found_el_char = true;
|
||||
|
@ -5260,7 +5305,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
bool found_dot_char = false;
|
||||
dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
|
||||
if (!_font_has_char(dot_gl_font_rid, '.')) {
|
||||
const Array &fonts = spans[spans.size() - 1].fonts;
|
||||
const Array &fonts = spans[span_size - 1].fonts;
|
||||
for (int i = 0; i < fonts.size(); i++) {
|
||||
if (_font_has_char(fonts[i], '.')) {
|
||||
dot_gl_font_rid = fonts[i];
|
||||
|
@ -5269,7 +5314,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
}
|
||||
}
|
||||
if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
|
||||
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
|
||||
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[span_size - 1].language, ".");
|
||||
if (rid.is_valid()) {
|
||||
dot_gl_font_rid = rid;
|
||||
}
|
||||
|
@ -5278,7 +5323,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
}
|
||||
RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
|
||||
if (!_font_has_char(whitespace_gl_font_rid, ' ')) {
|
||||
const Array &fonts = spans[spans.size() - 1].fonts;
|
||||
const Array &fonts = spans[span_size - 1].fonts;
|
||||
for (int i = 0; i < fonts.size(); i++) {
|
||||
if (_font_has_char(fonts[i], ' ')) {
|
||||
whitespace_gl_font_rid = fonts[i];
|
||||
|
@ -5437,7 +5482,8 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const {
|
|||
spans = parent_sd->spans;
|
||||
}
|
||||
|
||||
while (i < spans.size()) {
|
||||
int span_size = spans.size();
|
||||
while (i < span_size) {
|
||||
if (spans[i].start > p_sd->end) {
|
||||
break;
|
||||
}
|
||||
|
@ -5448,7 +5494,7 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const {
|
|||
|
||||
int r_start = MAX(0, spans[i].start - p_sd->start);
|
||||
String language = spans[i].language;
|
||||
while (i + 1 < spans.size() && language == spans[i + 1].language) {
|
||||
while (i + 1 < span_size && language == spans[i + 1].language) {
|
||||
i++;
|
||||
}
|
||||
int r_end = MIN(spans[i].end - p_sd->start, p_sd->text.length());
|
||||
|
@ -5510,10 +5556,11 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
|
|||
sd->break_inserts = 0;
|
||||
UErrorCode err = U_ZERO_ERROR;
|
||||
int i = 0;
|
||||
while (i < sd->spans.size()) {
|
||||
int span_size = sd->spans.size();
|
||||
while (i < span_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) {
|
||||
while (i + 1 < span_size && language == sd->spans[i + 1].language) {
|
||||
i++;
|
||||
}
|
||||
int r_end = sd->spans[i].end;
|
||||
|
@ -5637,6 +5684,7 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
|
|||
}
|
||||
}
|
||||
Glyph gl;
|
||||
gl.span_index = sd_glyphs[i].span_index;
|
||||
gl.start = sd_glyphs[i].start;
|
||||
gl.end = sd_glyphs[i].end;
|
||||
gl.count = 1;
|
||||
|
@ -5885,6 +5933,7 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap
|
|||
}
|
||||
// Inject virtual space for alignment.
|
||||
Glyph gl;
|
||||
gl.span_index = sd_glyphs[i].span_index;
|
||||
gl.start = sd_glyphs[i].start;
|
||||
gl.end = sd_glyphs[i].end;
|
||||
gl.count = 1;
|
||||
|
@ -6031,6 +6080,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
|
|||
for (int i = fb_from; i != fb_to; i += fb_delta) {
|
||||
if (p_sd->preserve_invalid || (p_sd->preserve_control && is_control(p_sd->text[i]))) {
|
||||
Glyph gl;
|
||||
gl.span_index = p_span;
|
||||
gl.start = i + p_sd->start;
|
||||
gl.end = i + 1 + p_sd->start;
|
||||
gl.count = 1;
|
||||
|
@ -6187,6 +6237,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
|
|||
Glyph &gl = w[i];
|
||||
gl = Glyph();
|
||||
|
||||
gl.span_index = p_span;
|
||||
gl.start = glyph_info[i].cluster;
|
||||
gl.end = end;
|
||||
gl.count = 0;
|
||||
|
@ -6489,6 +6540,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
|
|||
gl.start = span.start;
|
||||
gl.end = span.end;
|
||||
gl.count = 1;
|
||||
gl.span_index = k;
|
||||
gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT;
|
||||
if (sd->orientation == ORIENTATION_HORIZONTAL) {
|
||||
gl.advance = sd->objects[span.embedded_key].rect.size.x;
|
||||
|
|
|
@ -479,6 +479,8 @@ class TextServerAdvanced : public TextServerExtension {
|
|||
Variant meta;
|
||||
};
|
||||
Vector<Span> spans;
|
||||
int first_span = 0; // First span in the parent ShapedTextData.
|
||||
int last_span = 0;
|
||||
|
||||
struct EmbeddedObject {
|
||||
int start = -1;
|
||||
|
@ -941,6 +943,7 @@ public:
|
|||
|
||||
MODBIND1RC(int64_t, shaped_get_span_count, const RID &);
|
||||
MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t);
|
||||
MODBIND2RC(Variant, shaped_get_span_embedded_object, const RID &, int64_t);
|
||||
MODBIND5(shaped_set_span_update_font, const RID &, int64_t, const TypedArray<RID> &, int64_t, const Dictionary &);
|
||||
|
||||
MODBIND3RC(RID, shaped_text_substr, const RID &, int64_t, int64_t);
|
||||
|
|
|
@ -3045,15 +3045,14 @@ void TextServerFallback::full_copy(ShapedTextDataFallback *p_shaped) {
|
|||
}
|
||||
}
|
||||
|
||||
for (int k = 0; k < parent->spans.size(); k++) {
|
||||
ShapedTextDataFallback::Span span = parent->spans[k];
|
||||
if (span.start >= p_shaped->end || span.end <= p_shaped->start) {
|
||||
continue;
|
||||
}
|
||||
for (int i = p_shaped->first_span; i <= p_shaped->last_span; i++) {
|
||||
ShapedTextDataFallback::Span span = parent->spans[i];
|
||||
span.start = MAX(p_shaped->start, span.start);
|
||||
span.end = MIN(p_shaped->end, span.end);
|
||||
p_shaped->spans.push_back(span);
|
||||
}
|
||||
p_shaped->first_span = 0;
|
||||
p_shaped->last_span = 0;
|
||||
|
||||
p_shaped->parent = RID();
|
||||
}
|
||||
|
@ -3079,6 +3078,8 @@ void TextServerFallback::_shaped_text_clear(const RID &p_shaped) {
|
|||
sd->end = 0;
|
||||
sd->text = String();
|
||||
sd->spans.clear();
|
||||
sd->first_span = 0;
|
||||
sd->last_span = 0;
|
||||
sd->objects.clear();
|
||||
invalidate(sd);
|
||||
}
|
||||
|
@ -3231,19 +3232,48 @@ int64_t TextServerFallback::_shaped_text_get_spacing(const RID &p_shaped, Spacin
|
|||
int64_t TextServerFallback::_shaped_get_span_count(const RID &p_shaped) const {
|
||||
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_NULL_V(sd, 0);
|
||||
return sd->spans.size();
|
||||
|
||||
if (sd->parent != RID()) {
|
||||
return sd->last_span - sd->first_span + 1;
|
||||
} else {
|
||||
return sd->spans.size();
|
||||
}
|
||||
}
|
||||
|
||||
Variant TextServerFallback::_shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const {
|
||||
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_NULL_V(sd, Variant());
|
||||
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
|
||||
return sd->spans[p_index].meta;
|
||||
if (sd->parent != RID()) {
|
||||
ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent);
|
||||
ERR_FAIL_COND_V(!parent_sd->valid.is_set(), Variant());
|
||||
ERR_FAIL_INDEX_V(p_index + sd->first_span, parent_sd->spans.size(), Variant());
|
||||
return parent_sd->spans[p_index + sd->first_span].meta;
|
||||
} else {
|
||||
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
|
||||
return sd->spans[p_index].meta;
|
||||
}
|
||||
}
|
||||
|
||||
Variant TextServerFallback::_shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const {
|
||||
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_NULL_V(sd, Variant());
|
||||
if (sd->parent != RID()) {
|
||||
ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent);
|
||||
ERR_FAIL_COND_V(!parent_sd->valid.is_set(), Variant());
|
||||
ERR_FAIL_INDEX_V(p_index + sd->first_span, parent_sd->spans.size(), Variant());
|
||||
return parent_sd->spans[p_index + sd->first_span].embedded_key;
|
||||
} else {
|
||||
ERR_FAIL_INDEX_V(p_index, sd->spans.size(), Variant());
|
||||
return sd->spans[p_index].embedded_key;
|
||||
}
|
||||
}
|
||||
|
||||
void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) {
|
||||
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped);
|
||||
ERR_FAIL_NULL(sd);
|
||||
if (sd->parent != RID()) {
|
||||
full_copy(sd);
|
||||
}
|
||||
ERR_FAIL_INDEX(p_index, sd->spans.size());
|
||||
|
||||
ShapedTextDataFallback::Span &span = sd->spans.ptrw()[p_index];
|
||||
|
@ -3365,18 +3395,22 @@ bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const V
|
|||
sd->width = 0;
|
||||
sd->upos = 0;
|
||||
sd->uthk = 0;
|
||||
|
||||
Vector<ShapedTextDataFallback::Span> &spans = sd->spans;
|
||||
if (sd->parent != RID()) {
|
||||
ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent);
|
||||
ERR_FAIL_COND_V(!parent_sd->valid.is_set(), false);
|
||||
spans = parent_sd->spans;
|
||||
}
|
||||
|
||||
int sd_size = sd->glyphs.size();
|
||||
int span_size = spans.size();
|
||||
|
||||
for (int i = 0; i < sd_size; i++) {
|
||||
Glyph gl = sd->glyphs[i];
|
||||
Variant key;
|
||||
if (gl.count == 1) {
|
||||
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
|
||||
if (E.value.start == gl.start) {
|
||||
key = E.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((gl.flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && gl.span_index + sd->first_span >= 0 && gl.span_index + sd->first_span < span_size) {
|
||||
key = spans[gl.span_index + sd->first_span].embedded_key;
|
||||
}
|
||||
if (key != Variant()) {
|
||||
if (sd->orientation == ORIENTATION_HORIZONTAL) {
|
||||
|
@ -3528,35 +3562,46 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start
|
|||
|
||||
if (p_length > 0) {
|
||||
new_sd->text = sd->text.substr(p_start - sd->start, p_length);
|
||||
|
||||
int span_size = sd->spans.size();
|
||||
|
||||
new_sd->first_span = 0;
|
||||
new_sd->last_span = span_size - 1;
|
||||
for (int i = 0; i < span_size; i++) {
|
||||
const ShapedTextDataFallback::Span &span = sd->spans[i];
|
||||
if (span.end <= p_start) {
|
||||
new_sd->first_span = i + 1;
|
||||
} else if (span.start >= p_start + p_length) {
|
||||
new_sd->last_span = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int sd_size = sd->glyphs.size();
|
||||
const Glyph *sd_glyphs = sd->glyphs.ptr();
|
||||
|
||||
for (int i = 0; i < sd_size; i++) {
|
||||
if ((sd_glyphs[i].start >= new_sd->start) && (sd_glyphs[i].end <= new_sd->end)) {
|
||||
Glyph gl = sd_glyphs[i];
|
||||
if (gl.span_index >= 0) {
|
||||
gl.span_index -= new_sd->first_span;
|
||||
}
|
||||
if (gl.end == p_start + p_length && ((gl.flags & GRAPHEME_IS_SOFT_HYPHEN) == GRAPHEME_IS_SOFT_HYPHEN)) {
|
||||
gl.index = 0x00ad;
|
||||
gl.advance = font_get_glyph_advance(gl.font_rid, gl.font_size, 0x00ad).x;
|
||||
}
|
||||
Variant key;
|
||||
bool find_embedded = false;
|
||||
if (gl.count == 1) {
|
||||
for (const KeyValue<Variant, ShapedTextDataFallback::EmbeddedObject> &E : sd->objects) {
|
||||
if (E.value.start == gl.start) {
|
||||
find_embedded = true;
|
||||
key = E.key;
|
||||
new_sd->objects[key] = E.value;
|
||||
break;
|
||||
if ((gl.flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && gl.span_index >= 0 && gl.span_index < span_size) {
|
||||
Variant key = sd->spans[gl.span_index].embedded_key;
|
||||
if (key != Variant()) {
|
||||
ShapedTextDataFallback::EmbeddedObject obj = sd->objects[key];
|
||||
if (new_sd->orientation == ORIENTATION_HORIZONTAL) {
|
||||
obj.rect.position.x = new_sd->width;
|
||||
new_sd->width += obj.rect.size.x;
|
||||
} else {
|
||||
obj.rect.position.y = new_sd->width;
|
||||
new_sd->width += obj.rect.size.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (find_embedded) {
|
||||
if (new_sd->orientation == ORIENTATION_HORIZONTAL) {
|
||||
new_sd->objects[key].rect.position.x = new_sd->width;
|
||||
new_sd->width += new_sd->objects[key].rect.size.x;
|
||||
} else {
|
||||
new_sd->objects[key].rect.position.y = new_sd->width;
|
||||
new_sd->width += new_sd->objects[key].rect.size.y;
|
||||
new_sd->objects[key] = obj;
|
||||
}
|
||||
} else {
|
||||
if (gl.font_rid.is_valid()) {
|
||||
|
@ -4041,7 +4086,8 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
spans = parent_sd->spans;
|
||||
}
|
||||
|
||||
if (spans.size() == 0) {
|
||||
int span_size = spans.size();
|
||||
if (span_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4052,7 +4098,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
// Find usable fonts, if fonts from the last glyph do not have required chars.
|
||||
RID dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
|
||||
if (!_font_has_char(dot_gl_font_rid, sd->el_char)) {
|
||||
const Array &fonts = spans[spans.size() - 1].fonts;
|
||||
const Array &fonts = spans[span_size - 1].fonts;
|
||||
for (int i = 0; i < fonts.size(); i++) {
|
||||
if (_font_has_char(fonts[i], sd->el_char)) {
|
||||
dot_gl_font_rid = fonts[i];
|
||||
|
@ -4062,7 +4108,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
}
|
||||
if (!found_el_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
|
||||
const char32_t u32str[] = { sd->el_char, 0 };
|
||||
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, u32str);
|
||||
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[span_size - 1].language, u32str);
|
||||
if (rid.is_valid()) {
|
||||
dot_gl_font_rid = rid;
|
||||
found_el_char = true;
|
||||
|
@ -4075,7 +4121,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
bool found_dot_char = false;
|
||||
dot_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
|
||||
if (!_font_has_char(dot_gl_font_rid, '.')) {
|
||||
const Array &fonts = spans[spans.size() - 1].fonts;
|
||||
const Array &fonts = spans[span_size - 1].fonts;
|
||||
for (int i = 0; i < fonts.size(); i++) {
|
||||
if (_font_has_char(fonts[i], '.')) {
|
||||
dot_gl_font_rid = fonts[i];
|
||||
|
@ -4084,7 +4130,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
}
|
||||
}
|
||||
if (!found_dot_char && OS::get_singleton()->has_feature("system_fonts") && fonts.size() > 0 && _font_is_allow_system_fallback(fonts[0])) {
|
||||
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[spans.size() - 1].language, ".");
|
||||
RID rid = _find_sys_font_for_text(fonts[0], String(), spans[span_size - 1].language, ".");
|
||||
if (rid.is_valid()) {
|
||||
dot_gl_font_rid = rid;
|
||||
}
|
||||
|
@ -4093,7 +4139,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_
|
|||
}
|
||||
RID whitespace_gl_font_rid = sd_glyphs[sd_size - 1].font_rid;
|
||||
if (!_font_has_char(whitespace_gl_font_rid, ' ')) {
|
||||
const Array &fonts = spans[spans.size() - 1].fonts;
|
||||
const Array &fonts = spans[span_size - 1].fonts;
|
||||
for (int i = 0; i < fonts.size(); i++) {
|
||||
if (_font_has_char(fonts[i], ' ')) {
|
||||
whitespace_gl_font_rid = fonts[i];
|
||||
|
@ -4265,6 +4311,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
|
|||
sd->width += sd->objects[span.embedded_key].rect.size.y;
|
||||
}
|
||||
Glyph gl;
|
||||
gl.span_index = i;
|
||||
gl.start = span.start;
|
||||
gl.end = span.end;
|
||||
gl.count = 1;
|
||||
|
@ -4281,6 +4328,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) {
|
|||
RID prev_font;
|
||||
for (int j = span.start; j < span.end; j++) {
|
||||
Glyph gl;
|
||||
gl.span_index = i;
|
||||
gl.start = j;
|
||||
gl.end = j + 1;
|
||||
gl.count = 1;
|
||||
|
|
|
@ -425,6 +425,8 @@ class TextServerFallback : public TextServerExtension {
|
|||
Variant meta;
|
||||
};
|
||||
Vector<Span> spans;
|
||||
int first_span = 0; // First span in the parent ShapedTextData.
|
||||
int last_span = 0;
|
||||
|
||||
struct EmbeddedObject {
|
||||
int start = -1;
|
||||
|
@ -809,6 +811,7 @@ public:
|
|||
|
||||
MODBIND1RC(int64_t, shaped_get_span_count, const RID &);
|
||||
MODBIND2RC(Variant, shaped_get_span_meta, const RID &, int64_t);
|
||||
MODBIND2RC(Variant, shaped_get_span_embedded_object, const RID &, int64_t);
|
||||
MODBIND5(shaped_set_span_update_font, const RID &, int64_t, const TypedArray<RID> &, int64_t, const Dictionary &);
|
||||
|
||||
MODBIND3RC(RID, shaped_text_substr, const RID &, int64_t, int64_t);
|
||||
|
|
|
@ -344,7 +344,7 @@ void Label3D::_generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset,
|
|||
gl_uv = TS->font_get_glyph_uv_rect(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
|
||||
texs = TS->font_get_glyph_texture_size(p_glyph.font_rid, Vector2i(p_glyph.font_size, p_outline_size), p_glyph.index);
|
||||
}
|
||||
} else {
|
||||
} else if (((p_glyph.flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((p_glyph.flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
|
||||
gl_sz = TS->get_hex_code_box_size(p_glyph.font_size, p_glyph.index) * pixel_size;
|
||||
gl_of = Vector2(0, -gl_sz.y);
|
||||
}
|
||||
|
|
|
@ -311,7 +311,7 @@ void Label::_update_visible() {
|
|||
inline void draw_glyph(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Vector2 &p_ofs) {
|
||||
if (p_gl.font_rid != RID()) {
|
||||
TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color);
|
||||
} else {
|
||||
} else if (((p_gl.flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((p_gl.flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
|
||||
TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -549,7 +549,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
|
|||
if (font_size_it && font_size_it->font_size > 0) {
|
||||
font_size = font_size_it->font_size;
|
||||
}
|
||||
l.text_buf->add_string(String::chr(0x200B), font, font_size);
|
||||
l.text_buf->add_string(String::chr(0x200B), font, font_size, String(), it->rid);
|
||||
txt += "\n";
|
||||
l.char_count++;
|
||||
remaining_characters--;
|
||||
|
@ -791,7 +791,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
|
|||
MutexLock lock(l.text_buf->get_mutex());
|
||||
|
||||
Item *it_from = l.from;
|
||||
Item *it_to = (p_line + 1 < (int)p_frame->lines.size()) ? p_frame->lines[p_line + 1].from : nullptr;
|
||||
|
||||
if (it_from == nullptr) {
|
||||
return 0;
|
||||
|
@ -1030,9 +1029,26 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
|
|||
float box_start = 0.0;
|
||||
Color last_color = Color(0, 0, 0, 0);
|
||||
|
||||
Item *it = it_from;
|
||||
int span = -1;
|
||||
for (int i = 0; i < gl_size; i++) {
|
||||
bool selected = selection.active && (sel_start != -1) && (glyphs[i].start >= sel_start) && (glyphs[i].end <= sel_end);
|
||||
Item *it = _get_item_at_pos(it_from, it_to, glyphs[i].start);
|
||||
if (glyphs[i].span_index != span) {
|
||||
span = glyphs[i].span_index;
|
||||
if (span >= 0) {
|
||||
if ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) == TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) {
|
||||
Item *new_it = items.get_or_null(TS->shaped_get_span_embedded_object(rid, span));
|
||||
if (new_it) {
|
||||
it = new_it;
|
||||
}
|
||||
} else {
|
||||
Item *new_it = items.get_or_null(TS->shaped_get_span_meta(rid, span));
|
||||
if (new_it) {
|
||||
it = new_it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Color font_color = (step == DRAW_STEP_SHADOW || step == DRAW_STEP_OUTLINE || step == DRAW_STEP_TEXT) ? _find_color(it, p_base_color) : Color();
|
||||
int outline_size = (step == DRAW_STEP_OUTLINE) ? _find_outline_size(it, p_outline_size) : 0;
|
||||
|
|
|
@ -99,6 +99,7 @@ public:
|
|||
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 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(); }
|
||||
virtual void shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) override {}
|
||||
virtual RID shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const override { return RID(); }
|
||||
virtual RID shaped_text_get_parent(const RID &p_shaped) const override { return RID(); }
|
||||
|
|
|
@ -264,6 +264,7 @@ void TextServerExtension::_bind_methods() {
|
|||
|
||||
GDVIRTUAL_BIND(_shaped_get_span_count, "shaped");
|
||||
GDVIRTUAL_BIND(_shaped_get_span_meta, "shaped", "index");
|
||||
GDVIRTUAL_BIND(_shaped_get_span_embedded_object, "shaped", "index");
|
||||
GDVIRTUAL_BIND(_shaped_set_span_update_font, "shaped", "index", "fonts", "size", "opentype_features");
|
||||
|
||||
GDVIRTUAL_BIND(_shaped_text_substr, "shaped", "start", "length");
|
||||
|
@ -1167,11 +1168,17 @@ int64_t TextServerExtension::shaped_get_span_count(const RID &p_shaped) const {
|
|||
}
|
||||
|
||||
Variant TextServerExtension::shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const {
|
||||
Variant ret = false;
|
||||
Variant ret;
|
||||
GDVIRTUAL_CALL(_shaped_get_span_meta, p_shaped, p_index, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Variant TextServerExtension::shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const {
|
||||
Variant ret;
|
||||
GDVIRTUAL_CALL(_shaped_get_span_embedded_object, p_shaped, p_index, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void TextServerExtension::shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features) {
|
||||
GDVIRTUAL_CALL(_shaped_set_span_update_font, p_shaped, p_index, p_fonts, p_size, p_opentype_features);
|
||||
}
|
||||
|
|
|
@ -437,9 +437,11 @@ public:
|
|||
|
||||
virtual int64_t shaped_get_span_count(const RID &p_shaped) const override;
|
||||
virtual Variant shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const override;
|
||||
virtual Variant shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const override;
|
||||
virtual void shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary()) override;
|
||||
GDVIRTUAL1RC_REQUIRED(int64_t, _shaped_get_span_count, RID);
|
||||
GDVIRTUAL2RC_REQUIRED(Variant, _shaped_get_span_meta, RID, int64_t);
|
||||
GDVIRTUAL2RC_REQUIRED(Variant, _shaped_get_span_embedded_object, RID, int64_t);
|
||||
GDVIRTUAL5_REQUIRED(_shaped_set_span_update_font, RID, int64_t, const TypedArray<RID> &, int64_t, const Dictionary &);
|
||||
|
||||
virtual RID shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const override;
|
||||
|
|
|
@ -420,6 +420,7 @@ void TextServer::_bind_methods() {
|
|||
|
||||
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_get_span_embedded_object", "shaped", "index"), &TextServer::shaped_get_span_embedded_object);
|
||||
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);
|
||||
|
|
|
@ -464,6 +464,7 @@ public:
|
|||
|
||||
virtual int64_t shaped_get_span_count(const RID &p_shaped) const = 0;
|
||||
virtual Variant shaped_get_span_meta(const RID &p_shaped, int64_t p_index) const = 0;
|
||||
virtual Variant shaped_get_span_embedded_object(const RID &p_shaped, int64_t p_index) const = 0;
|
||||
virtual void shaped_set_span_update_font(const RID &p_shaped, int64_t p_index, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features = Dictionary()) = 0;
|
||||
|
||||
virtual RID shaped_text_substr(const RID &p_shaped, int64_t p_start, int64_t p_length) const = 0; // Copy shaped substring (e.g. line break) without reshaping, but correctly reordered, preservers range.
|
||||
|
@ -578,9 +579,11 @@ struct Glyph {
|
|||
float advance = 0.f; // Advance to the next glyph along baseline(x for horizontal layout, y for vertical).
|
||||
|
||||
RID font_rid; // Font resource.
|
||||
int font_size = 0; // Font size;
|
||||
int font_size = 0; // Font size.
|
||||
int32_t index = 0; // Glyph index (font specific) or UTF-32 codepoint (for the invalid glyphs).
|
||||
|
||||
int span_index = -1;
|
||||
|
||||
bool operator==(const Glyph &p_a) const;
|
||||
bool operator!=(const Glyph &p_a) const;
|
||||
|
||||
|
|
Loading…
Reference in New Issue