瀏覽代碼

Skip trailing white space in some cases to more accurately center and right-adjust text, see #383

However, removes trailing spaces in some cases where it shouldn't, see visual test case 'white_space' .
Michael Ragazzon 3 年之前
父節點
當前提交
9f74f5bfd4

+ 3 - 1
Include/RmlUi/Core/ElementText.h

@@ -65,9 +65,11 @@ public:
 	/// @param[in] maximum_line_width The width (in pixels) of space allowed for the line, or -1 for unlimited space.
 	/// @param[in] right_spacing_width The width (in pixels) of the spacing (consisting of margins, padding, etc) that must be remaining on the right of the line if the last of the text is rendered onto this line.
 	/// @param[in] trim_whitespace_prefix If we're collapsing whitespace, whether or not to remove all prefixing whitespace or collapse it down to a single space.
+	/// @param[in] trim_whitespace_suffix If we're collapsing whitespace, whether or not to remove all suffixing whitespace or collapse it down to a single space.
 	/// @param[in] decode_escape_characters Decode escaped characters such as & into &.
 	/// @return True if the line reached the end of the element's text, false if not.
-	bool GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix, bool decode_escape_characters);
+	bool GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width,
+		bool trim_whitespace_prefix, bool trim_whitespace_suffix, bool decode_escape_characters);
 
 	/// Clears all lines of generated text and prepares the element for generating new lines.
 	void ClearLines();

+ 13 - 8
Source/Core/ElementText.cpp

@@ -41,7 +41,8 @@
 
 namespace Rml {
 
-static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters);
+static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool skip_trailing_whitespace,
+	bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters);
 static bool LastToken(const char* token_begin, const char* string_end, bool collapse_white_space, bool break_at_endline);
 
 ElementText::ElementText(const String& tag) :
@@ -180,14 +181,15 @@ bool ElementText::GenerateToken(float& token_width, int line_begin)
 	const char* token_begin = text.c_str() + line_begin;
 	String token;
 
-	BuildToken(token, token_begin, text.c_str() + text.size(), true, collapse_white_space, break_at_endline, computed.text_transform(), true);
+	BuildToken(token, token_begin, text.c_str() + text.size(), true, true, collapse_white_space, break_at_endline, computed.text_transform(), true);
 	token_width = (float) GetFontEngineInterface()->GetStringWidth(font_face_handle, token);
 
 	return LastToken(token_begin, text.c_str() + text.size(), collapse_white_space, break_at_endline);
 }
 
 // Generates a line of text rendered from this element
-bool ElementText::GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix, bool decode_escape_characters)
+bool ElementText::GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width,
+	bool trim_whitespace_prefix, bool trim_whitespace_suffix, bool decode_escape_characters)
 {
 	RMLUI_ZoneScoped;
 
@@ -236,7 +238,8 @@ bool ElementText::GenerateLine(String& line, int& line_length, float& line_width
 			previous_codepoint = StringUtilities::ToCharacter(StringUtilities::SeekBackwardUTF8(&line.back(), line.data()));
 
 		// Generate the next token and determine its pixel-length.
-		bool break_line = BuildToken(token, next_token_begin, string_end, line.empty() && trim_whitespace_prefix, collapse_white_space, break_at_endline, text_transform_property, decode_escape_characters);
+		bool break_line = BuildToken(token, next_token_begin, string_end, line.empty() && trim_whitespace_prefix, trim_whitespace_suffix,
+			collapse_white_space, break_at_endline, text_transform_property, decode_escape_characters);
 		int token_width = font_engine_interface->GetStringWidth(font_face_handle, token, previous_codepoint);
 
 		// If we're breaking to fit a line box, check if the token can fit on the line before we add it.
@@ -260,7 +263,8 @@ bool ElementText::GenerateLine(String& line, int& line_length, float& line_width
 						token.clear();
 						next_token_begin = token_begin;
 						const char* partial_string_end = StringUtilities::SeekBackwardUTF8(token_begin + i, token_begin);
-						BuildToken(token, next_token_begin, partial_string_end, line.empty() && trim_whitespace_prefix, collapse_white_space, break_at_endline, text_transform_property, decode_escape_characters);
+						BuildToken(token, next_token_begin, partial_string_end, line.empty() && trim_whitespace_prefix, trim_whitespace_suffix,
+							collapse_white_space, break_at_endline, text_transform_property, decode_escape_characters);
 						token_width = font_engine_interface->GetStringWidth(font_face_handle, token, previous_codepoint);
 
 						if (force_loop_break_after_next || token_width <= max_token_width)
@@ -495,7 +499,8 @@ void ElementText::GenerateDecoration(const FontFaceHandle font_face_handle)
 		GeometryUtilities::GenerateLine(font_face_handle, decoration.get(), line.position, line.width, decoration_property, colour);
 }
 
-static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters)
+static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool skip_trailing_whitespace,
+	bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters)
 {
 	RMLUI_ASSERT(token_begin != string_end);
 
@@ -583,8 +588,8 @@ static bool BuildToken(String& token, const char*& token_begin, const char* stri
 			{
 				// However, if we are the last non-whitespace character in the string, and there are trailing
 				// whitespace characters after this token, then we need to append a single space to the end of this
-				// token.
-				if (token_begin != string_end &&
+				// token. Exception: Skip trailing whitespace if we are at the end of a layout line.
+				if (token_begin != string_end && !skip_trailing_whitespace &&
 					LastToken(token_begin, string_end, collapse_white_space, break_at_endline))
 					token += ' ';
 

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

@@ -1021,7 +1021,7 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 		String line_content;
 
 		// Generate the next line.
-		last_line = text_element->GenerateLine(line_content, line.size, line_width, line_begin, client_width - cursor_size.x, 0, false, false);
+		last_line = text_element->GenerateLine(line_content, line.size, line_width, line_begin, client_width - cursor_size.x, 0, false, false, false);
 
 		// If this line terminates in a soft-return (word wrap), then the line may be leaving a space or two behind as an orphan. If so, we must
 		// append the orphan onto the line even though it will push the line outside of the input field's bounds.

+ 2 - 5
Source/Core/LayoutInlineBox.cpp

@@ -146,12 +146,9 @@ bool LayoutInlineBox::IsLastChild() const
 }
 
 // Flows the inline box's content into its parent line.
-UniquePtr<LayoutInlineBox> LayoutInlineBox::FlowContent(bool RMLUI_UNUSED_PARAMETER(first_box), float RMLUI_UNUSED_PARAMETER(available_width), float RMLUI_UNUSED_PARAMETER(right_spacing_width))
+UniquePtr<LayoutInlineBox> LayoutInlineBox::FlowContent(bool /*first_box*/, bool /*last_box*/, float /*available_width*/,
+	float /*right_spacing_width*/)
 {
-	RMLUI_UNUSED(first_box);
-	RMLUI_UNUSED(available_width);
-	RMLUI_UNUSED(right_spacing_width);
-
 	// If we're representing a sized element, then add our element's width onto our parent's.
 	if (parent != nullptr &&
 		box.GetSize().x > 0)

+ 2 - 1
Source/Core/LayoutInlineBox.h

@@ -68,10 +68,11 @@ public:
 
 	/// Flows the inline box's content into its parent line.
 	/// @param[in] first_box True if this box is the first box containing content to be flowed into this line.
+	/// @param[in] last_box True if this box is the last box containing content to be flowed into this line.
 	/// @param[in] available_width The width available for flowing this box's content. This is measured from the left side of this box's content area.
 	/// @param[in] right_spacing_width The width of the spacing that must be left on the right of the element if no overflow occurs. If overflow occurs, then the entire width can be used.
 	/// @return The overflow box containing any content that spilled over from the flow. This must be nullptr if no overflow occured.
-	virtual UniquePtr<LayoutInlineBox> FlowContent(bool first_box, float available_width, float right_spacing_width);
+	virtual UniquePtr<LayoutInlineBox> FlowContent(bool first_box, bool last_box, float available_width, float right_spacing_width);
 
 	/// Computes and sets the vertical position of this element, relative to its parent inline box (or block box,
 	/// for an un-nested inline box).

+ 3 - 3
Source/Core/LayoutInlineBoxText.cpp

@@ -79,14 +79,14 @@ bool LayoutInlineBoxText::CanOverflow() const
 }
 
 // Flows the inline box's content into its parent line.
-UniquePtr<LayoutInlineBox> LayoutInlineBoxText::FlowContent(bool first_box, float available_width, float right_spacing_width)
+UniquePtr<LayoutInlineBox> LayoutInlineBoxText::FlowContent(bool first_box, bool last_box, float available_width, float right_spacing_width)
 {
 	ElementText* text_element = GetTextElement();
 	RMLUI_ASSERT(text_element != nullptr);
 
 	int line_length;
 	float line_width;
-	bool overflow = !text_element->GenerateLine(line_contents, line_length, line_width, line_begin, available_width, right_spacing_width, first_box, true);
+	bool overflow = !text_element->GenerateLine(line_contents, line_length, line_width, line_begin, available_width, right_spacing_width, first_box, last_box, true);
 
 	Vector2f content_area;
 	content_area.x = line_width;
@@ -94,7 +94,7 @@ UniquePtr<LayoutInlineBox> LayoutInlineBoxText::FlowContent(bool first_box, floa
 	box.SetContent(content_area);
 
 	// Call the base-class's FlowContent() to increment the width of our parent's box.
-	LayoutInlineBox::FlowContent(first_box, available_width, right_spacing_width);
+	LayoutInlineBox::FlowContent(first_box, last_box, available_width, right_spacing_width);
 
 	if (overflow)
 		return MakeUnique<LayoutInlineBoxText>(GetTextElement(), line_begin + line_length);

+ 4 - 3
Source/Core/LayoutInlineBoxText.h

@@ -52,10 +52,11 @@ public:
 
 	/// Flows the inline box's content into its parent line.
 	/// @param[in] first_box True if this box is the first box containing content to be flowed into this line.
-	/// @param available_width[in] The width available for flowing this box's content. This is measured from the left side of this box's content area.
-	/// @param right_spacing_width[in] The width of the spacing that must be left on the right of the element if no overflow occurs. If overflow occurs, then the entire width can be used.
+	/// @param[in] last_box True if this box is the last box containing content to be flowed into this line.
+	/// @param[in] available_width The width available for flowing this box's content. This is measured from the left side of this box's content area.
+	/// @param[in] right_spacing_width The width of the spacing that must be left on the right of the element if no overflow occurs. If overflow occurs, then the entire width can be used.
 	/// @return The overflow box containing any content that spilled over from the flow. This must be nullptr if no overflow occured.
-	UniquePtr<LayoutInlineBox> FlowContent(bool first_box, float available_width, float right_spacing_width) override;
+	UniquePtr<LayoutInlineBox> FlowContent(bool first_box, bool last_box, float available_width, float right_spacing_width) override;
 
 	/// Computes and sets the vertical position of this element, relative to its parent inline box (or block box,
 	/// for an un-nested inline box).

+ 10 - 8
Source/Core/LayoutLineBox.cpp

@@ -210,6 +210,9 @@ LayoutInlineBox* LayoutLineBox::AddBox(UniquePtr<LayoutInlineBox> box_ptr)
 
 	// Set to true if we're flowing the first box (with content) on the line.
 	bool first_box = false;
+	// Set to true for the last box in the line so that we can trim end spacing.
+	bool last_box = false;
+
 	// The spacing this element must leave on the right of the line, to account not only for its margins and padding,
 	// but also for its parents which will close immediately after it.
 	float right_spacing;
@@ -250,6 +253,7 @@ LayoutInlineBox* LayoutLineBox::AddBox(UniquePtr<LayoutInlineBox> box_ptr)
 			dimensions.y = minimum_dimensions.y;
 
 			first_box = true;
+			last_box = box->IsLastChild();
 			position_set = true;
 		}
 		else
@@ -286,18 +290,16 @@ LayoutInlineBox* LayoutLineBox::AddBox(UniquePtr<LayoutInlineBox> box_ptr)
 		if (box->GetBox().GetSize().x >= 0)
 			element_width += box->GetBox().GetSize().x;
 
-		if (wrap_content &&
-			box_cursor + element_width > dimensions.x)
+		if (wrap_content && box_cursor + element_width > dimensions.x)
 		{
 			// We can't fit the new inline element into this box! So we'll close this line box, and send the inline box
 			// onto the next line.
 			return Close(std::move(box_ptr));
 		}
-		else
-		{
-			// We can fit the new inline element into this box.
-			AppendBox(std::move(box_ptr));
-		}
+
+		// We can fit the new inline element into this box.
+		AppendBox(std::move(box_ptr));
+		last_box = box->IsLastChild();
 	}
 
 	float available_width = -1;
@@ -305,7 +307,7 @@ LayoutInlineBox* LayoutLineBox::AddBox(UniquePtr<LayoutInlineBox> box_ptr)
 		available_width = Math::RoundUpFloat(dimensions.x - (open_inline_box->GetPosition().x + open_inline_box->GetBox().GetPosition(Box::CONTENT).x));
 
 	// Flow the box's content into the line.
-	UniquePtr<LayoutInlineBox> overflow_box = open_inline_box->FlowContent(first_box, available_width, right_spacing);
+	UniquePtr<LayoutInlineBox> overflow_box = open_inline_box->FlowContent(first_box, last_box, available_width, right_spacing);
 	box_cursor += open_inline_box->GetBox().GetSize().x;
 
 	// If our box overflowed, then we'll close this line (as no more content will fit onto it) and tell our block box