Browse Source

Consistently define indefinite size as -1 throughout the layout engine

Allows one to avoid formatting with a fixed, large containing block value.

Enables conditionals based on indefinite sizes, such as during max-content sizing.
Michael Ragazzon 6 months ago
parent
commit
ef533f61d6

+ 1 - 2
Source/Core/ElementText.cpp

@@ -183,9 +183,8 @@ void ElementText::OnRender()
 bool ElementText::GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width,
 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 allow_empty)
 	bool trim_whitespace_prefix, bool decode_escape_characters, bool allow_empty)
 {
 {
+	RMLUI_ASSERT(maximum_line_width < FLT_MAX); // Infinity should be given by -1.
 	RMLUI_ZoneScoped;
 	RMLUI_ZoneScoped;
-	RMLUI_ASSERT(
-		maximum_line_width >= 0.f); // TODO: Check all callers for conformance, check break at line condition below. Possibly check for FLT_MAX.
 
 
 	FontFaceHandle font_face_handle = GetFontFaceHandle();
 	FontFaceHandle font_face_handle = GetFontFaceHandle();
 
 

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

@@ -1275,8 +1275,8 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 		String line_content;
 		String line_content;
 
 
 		// Generate the next line.
 		// Generate the next line.
-		last_line =
-			text_element->GenerateLine(line_content, line.size, line_width, line_begin, available_width - cursor_size.x, 0, false, false, false);
+		last_line = text_element->GenerateLine(line_content, line.size, line_width, line_begin, Math::Max(available_width - cursor_size.x, 0.f), 0,
+			false, false, false);
 
 
 		// Check if the editable length needs to be truncated to dodge a trailing endline.
 		// Check if the editable length needs to be truncated to dodge a trailing endline.
 		line.editable_length = (int)line_content.size();
 		line.editable_length = (int)line_content.size();

+ 3 - 3
Source/Core/Layout/BlockContainer.cpp

@@ -44,7 +44,7 @@ BlockContainer::BlockContainer(ContainerBox* _parent_container, FloatedBoxSpace*
 	max_height(_max_height), space(_space)
 	max_height(_max_height), space(_space)
 {
 {
 	RMLUI_ASSERT(element);
 	RMLUI_ASSERT(element);
-	RMLUI_ASSERT(box.GetSize().x >= 0.f);
+	RMLUI_ASSERT(box.GetSize().x >= 0.f || GetFormattingMode().constraint == FormattingMode::Constraint::MaxContent);
 
 
 	if (!space)
 	if (!space)
 	{
 	{
@@ -384,7 +384,7 @@ float BlockContainer::GetShrinkToFitWidth() const
 		// our context. The basic algorithm used can produce overestimates, since floats may not be located next to the
 		// our context. The basic algorithm used can produce overestimates, since floats may not be located next to the
 		// rest of the content.
 		// rest of the content.
 		const float edge_left = box.GetPosition().x;
 		const float edge_left = box.GetPosition().x;
-		const float edge_right = edge_left + box.GetSize().x;
+		const float edge_right = (box.GetSize().x < 0.f ? FloatedBoxSpace::edge_right_position_for_indefinite_size : edge_left + box.GetSize().x);
 		content_width += space->GetShrinkToFitWidth(edge_left, edge_right);
 		content_width += space->GetShrinkToFitWidth(edge_left, edge_right);
 	}
 	}
 
 
@@ -472,7 +472,7 @@ InlineContainer* BlockContainer::EnsureOpenInlineContainer()
 	if (!inline_container)
 	if (!inline_container)
 	{
 	{
 		const float scrollbar_width = (IsScrollContainer() ? element->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL) : 0.f);
 		const float scrollbar_width = (IsScrollContainer() ? element->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL) : 0.f);
-		const float available_width = box.GetSize().x - scrollbar_width;
+		const float available_width = (box.GetSize().x < 0.f ? -1.f : box.GetSize().x - scrollbar_width);
 
 
 		auto inline_container_ptr = MakeUnique<InlineContainer>(this, available_width);
 		auto inline_container_ptr = MakeUnique<InlineContainer>(this, available_width);
 		inline_container = inline_container_ptr.get();
 		inline_container = inline_container_ptr.get();

+ 4 - 1
Source/Core/Layout/BlockFormattingContext.cpp

@@ -166,8 +166,11 @@ bool BlockFormattingContext::FormatBlockBox(BlockContainer* parent_container, El
 	RMLUI_ZoneScopedC(0x2F4F4F);
 	RMLUI_ZoneScopedC(0x2F4F4F);
 	const Vector2f containing_block = parent_container->GetContainingBlockSize(element->GetPosition());
 	const Vector2f containing_block = parent_container->GetContainingBlockSize(element->GetPosition());
 
 
+	const BuildBoxMode build_box_mode =
+		(parent_container->GetFormattingMode().constraint == FormattingMode::Constraint::MaxContent ? BuildBoxMode::UnalignedBlock
+																									: BuildBoxMode::Block);
 	Box box;
 	Box box;
-	LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::ShrinkableBlock, &parent_container->GetFormattingMode());
+	LayoutDetails::BuildBox(box, containing_block, element, build_box_mode);
 	float min_height, max_height;
 	float min_height, max_height;
 	LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
 	LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
 
 

+ 1 - 1
Source/Core/Layout/ContainerBox.cpp

@@ -84,7 +84,7 @@ Vector2f ContainerBox::GetContainingBlockSize(Style::Position position) const
 			RMLUI_ERROR;
 			RMLUI_ERROR;
 			return {};
 			return {};
 		}
 		}
-		result = box->GetSize(BoxArea::Content);
+		result = box->GetSize();
 		if (element)
 		if (element)
 		{
 		{
 			// For static elements we subtract the scrollbar size so that elements normally don't overlap their parent's
 			// For static elements we subtract the scrollbar size so that elements normally don't overlap their parent's

+ 1 - 1
Source/Core/Layout/FlexFormattingContext.cpp

@@ -49,7 +49,7 @@ UniquePtr<LayoutBox> FlexFormattingContext::Format(ContainerBox* parent_containe
 	const ComputedValues& computed = element->GetComputedValues();
 	const ComputedValues& computed = element->GetComputedValues();
 
 
 	const Vector2f containing_block = parent_container->GetContainingBlockSize(element->GetPosition());
 	const Vector2f containing_block = parent_container->GetContainingBlockSize(element->GetPosition());
-	RMLUI_ASSERT(containing_block.x >= 0.f);
+	RMLUI_ASSERT(containing_block.x >= 0.f || parent_container->GetFormattingMode().constraint == FormattingMode::Constraint::MaxContent);
 
 
 	// Build the initial box as specified by the flex's style, as if it was a normal block element.
 	// Build the initial box as specified by the flex's style, as if it was a normal block element.
 	Box& box = flex_container_box->GetBox();
 	Box& box = flex_container_box->GetBox();

+ 2 - 1
Source/Core/Layout/FloatedBoxSpace.cpp

@@ -101,7 +101,8 @@ Vector2f FloatedBoxSpace::NextBoxPosition(const BlockContainer* parent, float& m
 {
 {
 	const float parent_scrollbar_width = parent->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
 	const float parent_scrollbar_width = parent->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
 	const float parent_edge_left = parent->GetPosition().x + parent->GetBox().GetPosition().x;
 	const float parent_edge_left = parent->GetPosition().x + parent->GetBox().GetPosition().x;
-	const float parent_edge_right = parent_edge_left + parent->GetBox().GetSize().x - parent_scrollbar_width;
+	const float parent_edge_right = (parent->GetBox().GetSize().x < 0.f ? FloatedBoxSpace::edge_right_position_for_indefinite_size
+																		: parent_edge_left + parent->GetBox().GetSize().x - parent_scrollbar_width);
 
 
 	const AnchorEdge box_edge = (float_property == Style::Float::Right ? RIGHT : LEFT);
 	const AnchorEdge box_edge = (float_property == Style::Float::Right ? RIGHT : LEFT);
 
 

+ 3 - 1
Source/Core/Layout/FloatedBoxSpace.h

@@ -81,7 +81,7 @@ public:
 	float DetermineClearPosition(float cursor, Style::Clear clear_property) const;
 	float DetermineClearPosition(float cursor, Style::Clear clear_property) const;
 
 
 	/// Returns the size of the rectangle encompassing all boxes within the space, relative to the block formatting context space.
 	/// Returns the size of the rectangle encompassing all boxes within the space, relative to the block formatting context space.
-	/// @param[in] edges Which edge of the boxes to encompass.
+	/// @param[in] edge Which edge of the boxes to encompass.
 	Vector2f GetDimensions(FloatedBoxEdge edge) const;
 	Vector2f GetDimensions(FloatedBoxEdge edge) const;
 
 
 	/// Get the width of the floated boxes for calculating the shrink-to-fit width.
 	/// Get the width of the floated boxes for calculating the shrink-to-fit width.
@@ -97,6 +97,8 @@ public:
 		extent_bottom_right_margin = {};
 		extent_bottom_right_margin = {};
 	}
 	}
 
 
+	static constexpr float edge_right_position_for_indefinite_size = 10'000.f;
+
 	void* operator new(size_t size);
 	void* operator new(size_t size);
 	void operator delete(void* chunk, size_t size);
 	void operator delete(void* chunk, size_t size);
 
 

+ 1 - 1
Source/Core/Layout/InlineBox.cpp

@@ -114,7 +114,7 @@ InlineBox::InlineBox(const InlineLevelBox* parent, Element* element, const Box&
 FragmentConstructor InlineBox::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool /*first_box*/,
 FragmentConstructor InlineBox::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool /*first_box*/,
 	LayoutOverflowHandle /*overflow_handle*/)
 	LayoutOverflowHandle /*overflow_handle*/)
 {
 {
-	if (mode != InlineLayoutMode::WrapAny || right_spacing_width <= available_width + GetSpacingLeft())
+	if (available_width < 0.f || mode != InlineLayoutMode::WrapAny || right_spacing_width <= available_width + GetSpacingLeft())
 		return FragmentConstructor{FragmentType::InlineBox, -1.f, {}, {}};
 		return FragmentConstructor{FragmentType::InlineBox, -1.f, {}, {}};
 
 
 	return {};
 	return {};

+ 3 - 2
Source/Core/Layout/InlineContainer.cpp

@@ -96,7 +96,8 @@ InlineBox* InlineContainer::AddInlineElement(Element* element, const Box& box)
 		InlineLayoutMode layout_mode = InlineLayoutMode::Nowrap;
 		InlineLayoutMode layout_mode = InlineLayoutMode::Nowrap;
 		if (wrap_content)
 		if (wrap_content)
 		{
 		{
-			const bool line_shrinked_by_floats = (line_box->GetLineWidth() + 0.5f < box_size.x && minimum_width_next < box_size.x);
+			const bool line_shrinked_by_floats =
+				(box_size.x >= 0.0f && line_box->GetLineWidth() + 0.5f < box_size.x && minimum_width_next < box_size.x);
 			const bool can_wrap_any = (line_shrinked_by_floats || line_box->HasContent());
 			const bool can_wrap_any = (line_shrinked_by_floats || line_box->HasContent());
 			layout_mode = (can_wrap_any ? InlineLayoutMode::WrapAny : InlineLayoutMode::WrapAfterContent);
 			layout_mode = (can_wrap_any ? InlineLayoutMode::WrapAny : InlineLayoutMode::WrapAfterContent);
 		}
 		}
@@ -234,7 +235,7 @@ void InlineContainer::UpdateLineBoxPlacement(LineBox* line_box, float minimum_wi
 	float available_width = 0.f;
 	float available_width = 0.f;
 	const Vector2f line_position =
 	const Vector2f line_position =
 		parent->GetBlockBoxSpace()->NextBoxPosition(parent, available_width, ideal_position_y, minimum_dimensions, !wrap_content);
 		parent->GetBlockBoxSpace()->NextBoxPosition(parent, available_width, ideal_position_y, minimum_dimensions, !wrap_content);
-	available_width = Math::Max(available_width, 0.f);
+	available_width = (box_size.x < 0.f ? -1.f : Math::Max(available_width, 0.f));
 
 
 	line_box->SetLineBox(line_position, available_width, minimum_dimensions.y);
 	line_box->SetLineBox(line_position, available_width, minimum_dimensions.y);
 }
 }

+ 1 - 1
Source/Core/Layout/InlineContainer.h

@@ -111,7 +111,7 @@ private:
 	BlockContainer* parent; // [not-null]
 	BlockContainer* parent; // [not-null]
 
 
 	Vector2f position;
 	Vector2f position;
-	Vector2f box_size;
+	Vector2f box_size; // -1 for indefinite size
 
 
 	// The element's computed line-height. Not necessarily the same as the height of our lines.
 	// The element's computed line-height. Not necessarily the same as the height of our lines.
 	float element_line_height = 0.f;
 	float element_line_height = 0.f;

+ 1 - 1
Source/Core/Layout/InlineLevelBox.cpp

@@ -143,7 +143,7 @@ FragmentConstructor InlineLevelBox_Atomic::CreateFragment(InlineLayoutMode mode,
 {
 {
 	const float outer_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin);
 	const float outer_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin);
 
 
-	if (mode != InlineLayoutMode::WrapAny || outer_width + right_spacing_width <= available_width)
+	if (available_width < 0.f || mode != InlineLayoutMode::WrapAny || outer_width + right_spacing_width <= available_width)
 		return FragmentConstructor{FragmentType::SizedBox, outer_width, {}, {}};
 		return FragmentConstructor{FragmentType::SizedBox, outer_width, {}, {}};
 
 
 	return {};
 	return {};

+ 0 - 6
Source/Core/Layout/LayoutDetails.cpp

@@ -242,12 +242,6 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b
 		return 0.f;
 		return 0.f;
 	}
 	}
 
 
-	// Use a large size for the box content width, so that it is practically unconstrained. This makes the formatting
-	// procedure act as if under a maximum content constraint. Children with percentage sizing values may be scaled
-	// based on this width (such as 'width' or 'margin'), if so, the layout is considered undefined like in CSS 2.
-	const float max_content_constraint_width = containing_block.x + 10'000.f;
-	box.SetContent({max_content_constraint_width, box.GetSize().y});
-
 	FormattingMode formatting_mode = current_formatting_mode;
 	FormattingMode formatting_mode = current_formatting_mode;
 	formatting_mode.constraint = FormattingMode::Constraint::MaxContent;
 	formatting_mode.constraint = FormattingMode::Constraint::MaxContent;
 
 

+ 3 - 3
Source/Core/Layout/LineBox.cpp

@@ -57,8 +57,8 @@ bool LineBox::AddBox(InlineLevelBox* box, InlineLayoutMode layout_mode, LayoutOv
 
 
 	const float box_placement_cursor = box_cursor + open_spacing_left;
 	const float box_placement_cursor = box_cursor + open_spacing_left;
 
 
-	float available_width = FLT_MAX;
-	if (layout_mode != InlineLayoutMode::Nowrap)
+	float available_width = -1.f;
+	if (line_width >= 0.f && layout_mode != InlineLayoutMode::Nowrap)
 	{
 	{
 		available_width = Math::RoundUp(line_width - box_placement_cursor);
 		available_width = Math::RoundUp(line_width - box_placement_cursor);
 		if (available_width < 0.f)
 		if (available_width < 0.f)
@@ -325,7 +325,7 @@ void LineBox::Close(Element* offset_parent, Vector2f offset_parent_position, Sty
 	RMLUI_ASSERT(is_vertically_positioned && !is_closed);
 	RMLUI_ASSERT(is_vertically_positioned && !is_closed);
 
 
 	// Horizontal alignment using available space on our line.
 	// Horizontal alignment using available space on our line.
-	if (box_cursor < line_width)
+	if (line_width >= 0.f && box_cursor < line_width)
 	{
 	{
 		switch (text_align)
 		switch (text_align)
 		{
 		{

+ 1 - 1
Source/Core/Layout/LineBox.h

@@ -185,7 +185,7 @@ private:
 
 
 	// Position of the line, relative to our block formatting context root.
 	// Position of the line, relative to our block formatting context root.
 	Vector2f line_position;
 	Vector2f line_position;
-	// Available space for the line. Based on our parent box content width, possibly shrinked due to floating boxes.
+	// Available space for the line, -1 if indefinite. Based on our parent box content width, possibly shrunk due to floating boxes.
 	float line_width = 0.f;
 	float line_width = 0.f;
 	// Lower-bound estimate for the line box height.
 	// Lower-bound estimate for the line box height.
 	float line_minimum_height = 0.f;
 	float line_minimum_height = 0.f;

+ 1 - 1
Source/Core/Layout/TableFormattingContext.cpp

@@ -50,7 +50,7 @@ UniquePtr<LayoutBox> TableFormattingContext::Format(ContainerBox* parent_contain
 	}
 	}
 
 
 	const Vector2f containing_block = parent_container->GetContainingBlockSize(element_table->GetPosition());
 	const Vector2f containing_block = parent_container->GetContainingBlockSize(element_table->GetPosition());
-	RMLUI_ASSERT(containing_block.x >= 0.f);
+	RMLUI_ASSERT(containing_block.x >= 0.f || parent_container->GetFormattingMode().constraint == FormattingMode::Constraint::MaxContent);
 	const ComputedValues& computed_table = element_table->GetComputedValues();
 	const ComputedValues& computed_table = element_table->GetComputedValues();
 
 
 	// Build the initial box as specified by the table's style, as if it was a normal block element.
 	// Build the initial box as specified by the table's style, as if it was a normal block element.