Browse Source

Fix input text selection when font has kerning

Michael Ragazzon 5 years ago
parent
commit
9f195446f8

+ 2 - 2
Include/RmlUi/Core/ElementText.h

@@ -93,7 +93,7 @@ private:
 	// Used to store the position and length of each line we have geometry for.
 	struct Line
 	{
-		Line(const String& text, const Vector2f& position) : text(text), position(position), width(0) {}
+		Line(const String& text, Vector2f position) : text(text), position(position), width(0) {}
 		String text;
 		Vector2f position;
 		int width;
@@ -108,7 +108,7 @@ private:
 
 	String text;
 
-	typedef Vector< Line > LineList;
+	using LineList = Vector< Line >;
 	LineList lines;
 
 	bool dirty_layout_on_change;

+ 2 - 1
Include/RmlUi/Core/ElementUtilities.h

@@ -83,8 +83,9 @@ public:
 	/// Returns the width of a string rendered within the context of the given element.
 	/// @param[in] element The element to measure the string from.
 	/// @param[in] string The string to measure.
+	/// @param[in] prior_character The character placed just before this string, used for kerning.
 	/// @return The string width, in pixels.
-	static int GetStringWidth(Element* element, const String& string);
+	static int GetStringWidth(Element* element, const String& string, Character prior_character = Character::Null);
 
 	/// Bind and instance all event attributes on the given element onto the element
 	/// @param element Element to bind events on

+ 5 - 0
Samples/basic/demo/data/demo.rml

@@ -512,6 +512,11 @@ progressbar {
 	font-effect: outline(2px #006600);
 	color: #ddd;
 }
+#controls textarea selection
+{
+	background-color: #aca;
+	color: #999;
+}
 
 
 /***  Sandbox  ***/

+ 1 - 1
Source/Core/ElementText.cpp

@@ -331,7 +331,7 @@ void ElementText::AddLine(const Vector2f& line_position, const String& line)
 		UpdateFontEffects();
 
 	Vector2f baseline_position = line_position + Vector2f(0.0f, (float)GetFontEngineInterface()->GetLineHeight(font_face_handle) - GetFontEngineInterface()->GetBaseline(font_face_handle));
-	lines.push_back(Line(line, baseline_position));
+	lines.emplace_back(line, baseline_position);
 
 	geometry_dirty = true;
 }

+ 2 - 2
Source/Core/ElementUtilities.cpp

@@ -154,13 +154,13 @@ float ElementUtilities::GetDensityIndependentPixelRatio(Element * element)
 }
 
 // Returns the width of a string rendered within the context of the given element.
-int ElementUtilities::GetStringWidth(Element* element, const String& string)
+int ElementUtilities::GetStringWidth(Element* element, const String& string, Character prior_character)
 {
 	FontFaceHandle font_face_handle = element->GetFontFaceHandle();
 	if (font_face_handle == 0)
 		return 0;
 
-	return GetFontEngineInterface()->GetStringWidth(font_face_handle, string);
+	return GetFontEngineInterface()->GetStringWidth(font_face_handle, string, prior_character);
 }
 
 void ElementUtilities::BindEventAttributes(Element* element)

+ 18 - 1
Source/Core/Elements/WidgetTextInput.cpp

@@ -1056,12 +1056,26 @@ Vector2f WidgetTextInput::FormatText()
 			line_position.x += ElementUtilities::GetStringWidth(text_element, pre_selection);
 		}
 
+		// Return the extra kerning that would result in joining two strings.
+		auto GetKerningBetween = [this](const String& left, const String& right) -> float {
+			if (left.empty() || right.empty())
+				return 0.0f;
+			// We could join the whole string, and compare the result of the joined width to the individual widths of each string. Instead, we just take the
+			// two neighboring characters from each string and compare the string width with and without kerning, which should be much faster.
+			const Character left_back = StringUtilities::ToCharacter(StringUtilities::SeekBackwardUTF8(&left.back(), &left.front()));
+			const String right_front_u8 = right.substr(0, size_t(StringUtilities::SeekForwardUTF8(right.c_str() + 1, right.c_str() + right.size()) - right.c_str()));
+			const int width_kerning = ElementUtilities::GetStringWidth(text_element, right_front_u8, left_back);
+			const int width_no_kerning = ElementUtilities::GetStringWidth(text_element, right_front_u8, Character::Null);
+			return float(width_kerning - width_no_kerning);
+		};
+
 		// If there is any selected text on this line, place it in the selected text element and
 		// generate the geometry for its background.
 		if (!selection.empty())
 		{
+			line_position.x += GetKerningBetween(pre_selection, selection);
 			selected_text_element->AddLine(line_position, selection);
-			int selection_width = ElementUtilities::GetStringWidth(selected_text_element, selection);
+			const int selection_width = ElementUtilities::GetStringWidth(selected_text_element, selection);
 
 			selection_vertices.resize(selection_vertices.size() + 4);
 			selection_indices.resize(selection_indices.size() + 6);
@@ -1073,7 +1087,10 @@ Vector2f WidgetTextInput::FormatText()
 		// If there is any unselected text after the selection on this line, place it in the
 		// standard text element after the selected text.
 		if (!post_selection.empty())
+		{
+			line_position.x += GetKerningBetween(selection, post_selection);
 			text_element->AddLine(line_position, post_selection);
+		}
 
 
 		// Update variables for the next line.