Browse Source

Merge pull request #52890 from bruvzg/rtl_effects_connected

Improve connected grapheme handling in the RTL CharFX.
Rémi Verschelde 4 years ago
parent
commit
8085affdb1

+ 6 - 0
doc/classes/CharFXTransform.xml

@@ -28,6 +28,12 @@
 		<member name="font" type="RID" setter="set_font" getter="get_font">
 			Font resource used to render glyph.
 		</member>
+		<member name="glyph_count" type="int" setter="set_glyph_count" getter="get_glyph_count" default="0">
+			Number of glyphs in the grapheme cluster. This value is set in the first glyph of a cluster. Setting this property won't affect drawing.
+		</member>
+		<member name="glyph_flags" type="int" setter="set_glyph_flags" getter="get_glyph_flags" default="0">
+			Glyph flags. See [enum TextServer.GraphemeFlag] for more info. Setting this property won't affect drawing.
+		</member>
 		<member name="glyph_index" type="int" setter="set_glyph_index" getter="get_glyph_index" default="0">
 			Font specific glyph index.
 		</member>

+ 6 - 0
doc/classes/TextServer.xml

@@ -1247,6 +1247,12 @@
 		<constant name="GRAPHEME_IS_PUNCTUATION" value="256" enum="GraphemeFlag">
 			Grapheme is punctuation character.
 		</constant>
+		<constant name="GRAPHEME_IS_UNDERSCORE" value="512" enum="GraphemeFlag">
+			Grapheme is underscore character.
+		</constant>
+		<constant name="GRAPHEME_IS_CONNECTED" value="1024" enum="GraphemeFlag">
+			Grapheme is connected to the previous grapheme. Breaking line before this grapheme is not safe.
+		</constant>
 		<constant name="HINTING_NONE" value="0" enum="Hinting">
 			Disables font hinting (smoother but less crisp).
 		</constant>

+ 18 - 4
modules/text_server_adv/text_server_adv.cpp

@@ -3804,7 +3804,12 @@ bool TextServerAdvanced::shaped_text_update_breaks(RID p_shaped) {
 						gl.font_rid = sd_glyphs[i].font_rid;
 						gl.font_size = sd_glyphs[i].font_size;
 						gl.flags = GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL;
-						sd->glyphs.insert(i + sd_glyphs[i].count, gl); // Insert after.
+						if (sd->glyphs[i].flags & GRAPHEME_IS_RTL) {
+							gl.flags |= GRAPHEME_IS_RTL;
+							sd->glyphs.insert(i, gl); // Insert before.
+						} else {
+							sd->glyphs.insert(i + sd_glyphs[i].count, gl); // Insert after.
+						}
 
 						// Update write pointer and size.
 						sd_size = sd->glyphs.size();
@@ -3998,7 +4003,12 @@ bool TextServerAdvanced::shaped_text_update_justification_ops(RID p_shaped) {
 						gl.font_rid = sd->glyphs[i].font_rid;
 						gl.font_size = sd->glyphs[i].font_size;
 						gl.flags = GRAPHEME_IS_SPACE | GRAPHEME_IS_VIRTUAL;
-						sd->glyphs.insert(i + sd->glyphs[i].count, gl); // Insert after.
+						if (sd->glyphs[i].flags & GRAPHEME_IS_RTL) {
+							gl.flags |= GRAPHEME_IS_RTL;
+							sd->glyphs.insert(i, gl); // Insert before.
+						} else {
+							sd->glyphs.insert(i + sd->glyphs[i].count, gl); // Insert after.
+						}
 						i += sd->glyphs[i].count;
 						continue;
 					}
@@ -4147,7 +4157,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star
 					}
 				}
 				if (p_direction == HB_DIRECTION_RTL || p_direction == HB_DIRECTION_BTT) {
-					w[last_cluster_index].flags |= TextServer::GRAPHEME_IS_RTL;
+					w[last_cluster_index].flags |= GRAPHEME_IS_RTL;
 				}
 				if (last_cluster_valid) {
 					w[last_cluster_index].flags |= GRAPHEME_IS_VALID;
@@ -4169,6 +4179,10 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star
 			gl.font_rid = p_fonts[p_fb_index];
 			gl.font_size = fs;
 
+			if (glyph_info[i].mask & HB_GLYPH_FLAG_DEFINED) {
+				gl.flags |= GRAPHEME_IS_CONNECTED;
+			}
+
 			gl.index = glyph_info[i].codepoint;
 			if (gl.index != 0) {
 				real_t scale = font_get_scale(f, fs);
@@ -4199,7 +4213,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int32_t p_star
 		}
 		w[last_cluster_index].count = glyph_count - last_cluster_index;
 		if (p_direction == HB_DIRECTION_RTL || p_direction == HB_DIRECTION_BTT) {
-			w[last_cluster_index].flags |= TextServer::GRAPHEME_IS_RTL;
+			w[last_cluster_index].flags |= GRAPHEME_IS_RTL;
 		}
 		if (last_cluster_valid) {
 			w[last_cluster_index].flags |= GRAPHEME_IS_VALID;

+ 8 - 0
scene/gui/rich_text_effect.cpp

@@ -90,6 +90,12 @@ void CharFXTransform::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_glyph_index"), &CharFXTransform::get_glyph_index);
 	ClassDB::bind_method(D_METHOD("set_glyph_index", "glyph_index"), &CharFXTransform::set_glyph_index);
 
+	ClassDB::bind_method(D_METHOD("get_glyph_count"), &CharFXTransform::get_glyph_count);
+	ClassDB::bind_method(D_METHOD("set_glyph_count", "glyph_count"), &CharFXTransform::set_glyph_count);
+
+	ClassDB::bind_method(D_METHOD("get_glyph_flags"), &CharFXTransform::get_glyph_flags);
+	ClassDB::bind_method(D_METHOD("set_glyph_flags", "glyph_flags"), &CharFXTransform::set_glyph_flags);
+
 	ClassDB::bind_method(D_METHOD("get_font"), &CharFXTransform::get_font);
 	ClassDB::bind_method(D_METHOD("set_font", "font"), &CharFXTransform::set_font);
 
@@ -101,5 +107,7 @@ void CharFXTransform::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
 	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "env"), "set_environment", "get_environment");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_index"), "set_glyph_index", "get_glyph_index");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_count"), "set_glyph_count", "get_glyph_count");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_flags"), "set_glyph_flags", "get_glyph_flags");
 	ADD_PROPERTY(PropertyInfo(Variant::RID, "font"), "set_font", "get_font");
 }

+ 14 - 0
scene/gui/rich_text_effect.h

@@ -50,6 +50,8 @@ public:
 	double elapsed_time = 0.0f;
 	Dictionary environment;
 	uint32_t glyph_index = 0;
+	uint16_t glyph_flags = 0;
+	uint8_t glyph_count = 0;
 	RID font;
 
 	CharFXTransform();
@@ -57,19 +59,31 @@ public:
 
 	Vector2i get_range() { return range; }
 	void set_range(const Vector2i &p_range) { range = p_range; }
+
 	double get_elapsed_time() { return elapsed_time; }
 	void set_elapsed_time(double p_elapsed_time) { elapsed_time = p_elapsed_time; }
+
 	bool is_visible() { return visibility; }
 	void set_visibility(bool p_visibility) { visibility = p_visibility; }
+
 	bool is_outline() { return outline; }
 	void set_outline(bool p_outline) { outline = p_outline; }
+
 	Point2 get_offset() { return offset; }
 	void set_offset(Point2 p_offset) { offset = p_offset; }
+
 	Color get_color() { return color; }
 	void set_color(Color p_color) { color = p_color; }
 
 	uint32_t get_glyph_index() const { return glyph_index; };
 	void set_glyph_index(uint32_t p_glyph_index) { glyph_index = p_glyph_index; };
+
+	uint16_t get_glyph_flags() const { return glyph_index; };
+	void set_glyph_flags(uint16_t p_glyph_flags) { glyph_flags = p_glyph_flags; };
+
+	uint8_t get_glyph_count() const { return glyph_count; };
+	void set_glyph_count(uint8_t p_glyph_count) { glyph_count = p_glyph_count; };
+
 	RID get_font() const { return font; };
 	void set_font(RID p_font) { font = p_font; };
 

+ 78 - 26
scene/gui/rich_text_label.cpp

@@ -852,6 +852,21 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 			Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off);
 			RID frid = glyphs[i].font_rid;
 			uint32_t gl = glyphs[i].index;
+			uint16_t gl_fl = glyphs[i].flags;
+			uint8_t gl_cn = glyphs[i].count;
+			bool cprev = false;
+			if (gl_cn == 0) { // Parts of the same cluster, always connected.
+				cprev = true;
+			}
+			if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected.
+				if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) {
+					cprev = true;
+				}
+			} else {
+				if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
+					cprev = true;
+				}
+			}
 
 			//Apply fx.
 			float faded_visibility = 1.0f;
@@ -880,6 +895,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 						charfx->outline = true;
 						charfx->font = frid;
 						charfx->glyph_index = gl;
+						charfx->glyph_flags = gl_fl;
+						charfx->glyph_count = gl_cn;
 						charfx->offset = fx_offset;
 						charfx->color = font_color;
 
@@ -895,25 +912,34 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 				} else if (item_fx->type == ITEM_SHAKE) {
 					ItemShake *item_shake = static_cast<ItemShake *>(item_fx);
 
-					uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
-					uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
-					uint64_t max_rand = 2147483647;
-					double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
-					double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
-					double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
-					n_time = (n_time > 1.0) ? 1.0 : n_time;
-					fx_offset += Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f;
+					if (!cprev) {
+						uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
+						uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
+						uint64_t max_rand = 2147483647;
+						double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+						double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+						double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
+						n_time = (n_time > 1.0) ? 1.0 : n_time;
+						item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f;
+					}
+					fx_offset += item_shake->prev_off;
 				} else if (item_fx->type == ITEM_WAVE) {
 					ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
 
-					double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f);
-					fx_offset += Point2(0, 1) * value;
+					if (!cprev) {
+						double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_wave->amplitude / 10.0f);
+						item_wave->prev_off = Point2(0, 1) * value;
+					}
+					fx_offset += item_wave->prev_off;
 				} else if (item_fx->type == ITEM_TORNADO) {
 					ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
 
-					double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
-					double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
-					fx_offset += Point2(torn_x, torn_y);
+					if (!cprev) {
+						double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
+						double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + gloff.x) / 50)) * (item_tornado->radius);
+						item_tornado->prev_off = Point2(torn_x, torn_y);
+					}
+					fx_offset += item_tornado->prev_off;
 				} else if (item_fx->type == ITEM_RAINBOW) {
 					ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx);
 
@@ -1004,6 +1030,21 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 			Point2 fx_offset = Vector2(glyphs[i].x_off, glyphs[i].y_off);
 			RID frid = glyphs[i].font_rid;
 			uint32_t gl = glyphs[i].index;
+			uint16_t gl_fl = glyphs[i].flags;
+			uint8_t gl_cn = glyphs[i].count;
+			bool cprev = false;
+			if (gl_cn == 0) { // Parts of the same grapheme cluster, always connected.
+				cprev = true;
+			}
+			if (gl_fl & TextServer::GRAPHEME_IS_RTL) { // Check if previous grapheme cluster is connected.
+				if (i > 0 && (glyphs[i - 1].flags & TextServer::GRAPHEME_IS_CONNECTED)) {
+					cprev = true;
+				}
+			} else {
+				if (glyphs[i].flags & TextServer::GRAPHEME_IS_CONNECTED) {
+					cprev = true;
+				}
+			}
 
 			//Apply fx.
 			float faded_visibility = 1.0f;
@@ -1032,6 +1073,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 						charfx->outline = false;
 						charfx->font = frid;
 						charfx->glyph_index = gl;
+						charfx->glyph_flags = gl_fl;
+						charfx->glyph_count = gl_cn;
 						charfx->offset = fx_offset;
 						charfx->color = font_color;
 
@@ -1047,25 +1090,34 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o
 				} else if (item_fx->type == ITEM_SHAKE) {
 					ItemShake *item_shake = static_cast<ItemShake *>(item_fx);
 
-					uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
-					uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
-					uint64_t max_rand = 2147483647;
-					double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
-					double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
-					double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
-					n_time = (n_time > 1.0) ? 1.0 : n_time;
-					fx_offset += Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f;
+					if (!cprev) {
+						uint64_t char_current_rand = item_shake->offset_random(glyphs[i].start);
+						uint64_t char_previous_rand = item_shake->offset_previous_random(glyphs[i].start);
+						uint64_t max_rand = 2147483647;
+						double current_offset = Math::range_lerp(char_current_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+						double previous_offset = Math::range_lerp(char_previous_rand % max_rand, 0, max_rand, 0.0f, 2.f * (float)Math_PI);
+						double n_time = (double)(item_shake->elapsed_time / (0.5f / item_shake->rate));
+						n_time = (n_time > 1.0) ? 1.0 : n_time;
+						item_shake->prev_off = Point2(Math::lerp(Math::sin(previous_offset), Math::sin(current_offset), n_time), Math::lerp(Math::cos(previous_offset), Math::cos(current_offset), n_time)) * (float)item_shake->strength / 10.0f;
+					}
+					fx_offset += item_shake->prev_off;
 				} else if (item_fx->type == ITEM_WAVE) {
 					ItemWave *item_wave = static_cast<ItemWave *>(item_fx);
 
-					double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f);
-					fx_offset += Point2(0, 1) * value;
+					if (!cprev) {
+						double value = Math::sin(item_wave->frequency * item_wave->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_wave->amplitude / 10.0f);
+						item_wave->prev_off = Point2(0, 1) * value;
+					}
+					fx_offset += item_wave->prev_off;
 				} else if (item_fx->type == ITEM_TORNADO) {
 					ItemTornado *item_tornado = static_cast<ItemTornado *>(item_fx);
 
-					double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
-					double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
-					fx_offset += Point2(torn_x, torn_y);
+					if (!cprev) {
+						double torn_x = Math::sin(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
+						double torn_y = Math::cos(item_tornado->frequency * item_tornado->elapsed_time + ((p_ofs.x + off.x) / 50)) * (item_tornado->radius);
+						item_tornado->prev_off = Point2(torn_x, torn_y);
+					}
+					fx_offset += item_tornado->prev_off;
 				} else if (item_fx->type == ITEM_RAINBOW) {
 					ItemRainbow *item_rainbow = static_cast<ItemRainbow *>(item_fx);
 

+ 3 - 0
scene/gui/rich_text_label.h

@@ -267,6 +267,7 @@ private:
 		float rate = 0.0f;
 		uint64_t _current_rng = 0;
 		uint64_t _previous_rng = 0;
+		Vector2 prev_off;
 
 		ItemShake() { type = ITEM_SHAKE; }
 
@@ -289,6 +290,7 @@ private:
 	struct ItemWave : public ItemFX {
 		float frequency = 1.0f;
 		float amplitude = 1.0f;
+		Vector2 prev_off;
 
 		ItemWave() { type = ITEM_WAVE; }
 	};
@@ -296,6 +298,7 @@ private:
 	struct ItemTornado : public ItemFX {
 		float radius = 1.0f;
 		float frequency = 1.0f;
+		Vector2 prev_off;
 
 		ItemTornado() { type = ITEM_TORNADO; }
 	};

+ 2 - 0
servers/text_server.cpp

@@ -447,6 +447,8 @@ void TextServer::_bind_methods() {
 	BIND_ENUM_CONSTANT(GRAPHEME_IS_TAB);
 	BIND_ENUM_CONSTANT(GRAPHEME_IS_ELONGATION);
 	BIND_ENUM_CONSTANT(GRAPHEME_IS_PUNCTUATION);
+	BIND_ENUM_CONSTANT(GRAPHEME_IS_UNDERSCORE);
+	BIND_ENUM_CONSTANT(GRAPHEME_IS_CONNECTED);
 
 	/* Hinting */
 	BIND_ENUM_CONSTANT(HINTING_NONE);

+ 1 - 0
servers/text_server.h

@@ -91,6 +91,7 @@ public:
 		GRAPHEME_IS_ELONGATION = 1 << 7, // Elongation (e.g. kashida), glyph can be duplicated or truncated to fit line to width.
 		GRAPHEME_IS_PUNCTUATION = 1 << 8, // Punctuation, except underscore (can be used as word break, but not line break or justifiction).
 		GRAPHEME_IS_UNDERSCORE = 1 << 9, // Underscore (can be used as word break).
+		GRAPHEME_IS_CONNECTED = 1 << 10, // Connected to previous grapheme.
 	};
 
 	enum Hinting {