瀏覽代碼

[Label] Add get_character_bounds method to get bounding rectangles of the characters.

bruvzg 1 年之前
父節點
當前提交
e9c219cff2
共有 3 個文件被更改,包括 132 次插入0 次删除
  1. 7 0
      doc/classes/Label.xml
  2. 123 0
      scene/gui/label.cpp
  3. 2 0
      scene/gui/label.h

+ 7 - 0
doc/classes/Label.xml

@@ -10,6 +10,13 @@
 		<link title="2D Dodge The Creeps Demo">https://godotengine.org/asset-library/asset/515</link>
 	</tutorials>
 	<methods>
+		<method name="get_character_bounds" qualifiers="const">
+			<return type="Rect2" />
+			<param index="0" name="pos" type="int" />
+			<description>
+				Returns the bounding rectangle of the character at position [param pos]. If the character is a non-visual character or [param pos] is outside the valid range, an empty [Rect2] is returned. If the character is a part of a composite grapheme, the bounding rectangle of the whole grapheme is returned.
+			</description>
+		</method>
 		<method name="get_line_count" qualifiers="const">
 			<return type="int" />
 			<description>

+ 123 - 0
scene/gui/label.cpp

@@ -668,6 +668,127 @@ void Label::_notification(int p_what) {
 	}
 }
 
+Rect2 Label::get_character_bounds(int p_pos) const {
+	if (dirty || font_dirty || lines_dirty) {
+		const_cast<Label *>(this)->_shape();
+	}
+
+	bool has_settings = settings.is_valid();
+	Size2 size = get_size();
+	Ref<StyleBox> style = theme_cache.normal_style;
+	int line_spacing = has_settings ? settings->get_line_spacing() : theme_cache.line_spacing;
+	bool rtl = (TS->shaped_text_get_inferred_direction(text_rid) == TextServer::DIRECTION_RTL);
+	bool rtl_layout = is_layout_rtl();
+
+	float total_h = 0.0;
+	int lines_visible = 0;
+
+	// Get number of lines to fit to the height.
+	for (int64_t i = lines_skipped; i < lines_rid.size(); i++) {
+		total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
+		if (total_h > (get_size().height - style->get_minimum_size().height + line_spacing)) {
+			break;
+		}
+		lines_visible++;
+	}
+
+	if (max_lines_visible >= 0 && lines_visible > max_lines_visible) {
+		lines_visible = max_lines_visible;
+	}
+
+	int last_line = MIN(lines_rid.size(), lines_visible + lines_skipped);
+
+	// Get real total height.
+	total_h = 0;
+	for (int64_t i = lines_skipped; i < last_line; i++) {
+		total_h += TS->shaped_text_get_size(lines_rid[i]).y + line_spacing;
+	}
+
+	total_h += style->get_margin(SIDE_TOP) + style->get_margin(SIDE_BOTTOM);
+
+	int vbegin = 0, vsep = 0;
+	if (lines_visible > 0) {
+		switch (vertical_alignment) {
+			case VERTICAL_ALIGNMENT_TOP: {
+				// Nothing.
+			} break;
+			case VERTICAL_ALIGNMENT_CENTER: {
+				vbegin = (size.y - (total_h - line_spacing)) / 2;
+				vsep = 0;
+
+			} break;
+			case VERTICAL_ALIGNMENT_BOTTOM: {
+				vbegin = size.y - (total_h - line_spacing);
+				vsep = 0;
+
+			} break;
+			case VERTICAL_ALIGNMENT_FILL: {
+				vbegin = 0;
+				if (lines_visible > 1) {
+					vsep = (size.y - (total_h - line_spacing)) / (lines_visible - 1);
+				} else {
+					vsep = 0;
+				}
+
+			} break;
+		}
+	}
+
+	Vector2 ofs;
+	ofs.y = style->get_offset().y + vbegin;
+	for (int i = lines_skipped; i < last_line; i++) {
+		Size2 line_size = TS->shaped_text_get_size(lines_rid[i]);
+		ofs.x = 0;
+		switch (horizontal_alignment) {
+			case HORIZONTAL_ALIGNMENT_FILL:
+				if (rtl && autowrap_mode != TextServer::AUTOWRAP_OFF) {
+					ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
+				} else {
+					ofs.x = style->get_offset().x;
+				}
+				break;
+			case HORIZONTAL_ALIGNMENT_LEFT: {
+				if (rtl_layout) {
+					ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
+				} else {
+					ofs.x = style->get_offset().x;
+				}
+			} break;
+			case HORIZONTAL_ALIGNMENT_CENTER: {
+				ofs.x = int(size.width - line_size.width) / 2;
+			} break;
+			case HORIZONTAL_ALIGNMENT_RIGHT: {
+				if (rtl_layout) {
+					ofs.x = style->get_offset().x;
+				} else {
+					ofs.x = int(size.width - style->get_margin(SIDE_RIGHT) - line_size.width);
+				}
+			} break;
+		}
+		int v_size = TS->shaped_text_get_glyph_count(lines_rid[i]);
+		const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]);
+
+		float gl_off = 0.0f;
+		for (int j = 0; j < v_size; j++) {
+			if ((glyphs[j].count > 0) && ((glyphs[j].index != 0) || ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE))) {
+				if (p_pos >= glyphs[j].start && p_pos < glyphs[j].end) {
+					float advance = 0.f;
+					for (int k = 0; k < glyphs[j].count; k++) {
+						advance += glyphs[j + k].advance;
+					}
+					Rect2 rect;
+					rect.position = ofs + Vector2(gl_off, 0);
+					rect.size = Vector2(advance, TS->shaped_text_get_size(lines_rid[i]).y);
+					return rect;
+				}
+			}
+			gl_off += glyphs[j].advance * glyphs[j].repeat;
+		}
+		ofs.y += TS->shaped_text_get_ascent(lines_rid[i]) + TS->shaped_text_get_descent(lines_rid[i]) + vsep + line_spacing;
+	}
+	return Rect2();
+}
+
 Size2 Label::get_minimum_size() const {
 	// don't want to mutable everything
 	if (dirty || font_dirty || lines_dirty) {
@@ -1055,6 +1176,8 @@ void Label::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &Label::set_structured_text_bidi_override_options);
 	ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &Label::get_structured_text_bidi_override_options);
 
+	ClassDB::bind_method(D_METHOD("get_character_bounds", "pos"), &Label::get_character_bounds);
+
 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "label_settings", PROPERTY_HINT_RESOURCE_TYPE, "LabelSettings"), "set_label_settings", "get_label_settings");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment");

+ 2 - 0
scene/gui/label.h

@@ -161,6 +161,8 @@ public:
 	int get_line_count() const;
 	int get_visible_line_count() const;
 
+	Rect2 get_character_bounds(int p_pos) const;
+
 	Label(const String &p_text = String());
 	~Label();
 };