Browse Source

Layout engine (WIP): Move shrink-to-fit calculation into the box size generator. Fixes eg. shrink-to-fit in 'position: absolute'. Split the layout things to do with sizing into 'LayoutDetails'.

Michael Ragazzon 5 years ago
parent
commit
cde8835600

+ 2 - 0
CMake/FileList.cmake

@@ -59,6 +59,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBox.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBoxSpace.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutDetails.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutEngine.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.h
@@ -328,6 +329,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBoxSpace.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutDetails.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutEngine.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.cpp

+ 2 - 2
Source/Core/ElementScroll.cpp

@@ -27,7 +27,7 @@
  */
 
 #include "../../Include/RmlUi/Core/ElementScroll.h"
-#include "LayoutEngine.h"
+#include "LayoutDetails.h"
 #include "WidgetScroll.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
@@ -86,7 +86,7 @@ void ElementScroll::EnableScrollbar(Orientation orientation, float element_width
 
 	// Determine the size of the scrollbar.
 	Box box;
-	LayoutEngine::BuildBox(box, Vector2f(element_width, element_width), scrollbars[orientation].element);
+	LayoutDetails::BuildBox(box, Vector2f(element_width, element_width), scrollbars[orientation].element);
 
 	if (orientation == VERTICAL)
 		scrollbars[orientation].size = box.GetSize(Box::MARGIN).x;

+ 3 - 2
Source/Core/ElementUtilities.cpp

@@ -38,6 +38,7 @@
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../Include/RmlUi/Core/RenderInterface.h"
 #include "ElementStyle.h"
+#include "LayoutDetails.h"
 #include "LayoutEngine.h"
 #include "TransformState.h"
 #include <limits>
@@ -55,7 +56,7 @@ static void SetBox(Element* element)
 	containing_block.y -= parent->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL);
 
 	Box box;
-	LayoutEngine::BuildBox(box, containing_block, element);
+	LayoutDetails::BuildBox(box, containing_block, element);
 
 	if (element->GetComputedValues().height.type != Style::Height::Auto)
 		box.SetContent(Vector2f(box.GetSize().x, containing_block.y));
@@ -309,7 +310,7 @@ bool ElementUtilities::FormatElement(Element* element, const Vector2f& containin
 // Generates the box for an element.
 void ElementUtilities::BuildBox(Box& box, const Vector2f& containing_block, Element* element, bool inline_element)
 {
-	LayoutEngine::BuildBox(box, containing_block, element, inline_element);
+	LayoutDetails::BuildBox(box, containing_block, element, inline_element);
 }
 
 // Sizes an element, and positions it within its parent offset from the borders of its content area.

+ 34 - 38
Source/Core/LayoutBlockBox.cpp

@@ -29,6 +29,7 @@
 #include "LayoutBlockBox.h"
 #include "LayoutBlockBoxSpace.h"
 #include "LayoutEngine.h"
+#include "LayoutDetails.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/ElementScroll.h"
@@ -39,7 +40,7 @@
 namespace Rml {
 
 // Creates a new block box for rendering a block element.
-LayoutBlockBox::LayoutBlockBox(LayoutBlockBox* _parent, Element* _element) : position(0), visible_outer_width(0)
+LayoutBlockBox::LayoutBlockBox(LayoutBlockBox* _parent, Element* _element, bool allow_shrink) : position(0), visible_outer_width(0)
 {
 	RMLUI_ZoneScoped;
 
@@ -84,7 +85,8 @@ LayoutBlockBox::LayoutBlockBox(LayoutBlockBox* _parent, Element* _element) : pos
 		space->ImportSpace(*parent->space);
 
 		// Build our box if possible; if not, it will have to be set up manually.
-		LayoutEngine::BuildBox(box, min_height, max_height, parent, element);
+		// TODO: Calculate outside and pass the box into the constructor.
+		LayoutDetails::BuildBox(box, min_height, max_height, parent, element, false, allow_shrink);
 
 		// Position ourselves within our containing block (if we have a valid offset parent).
 		if (parent->GetElement() != nullptr)
@@ -128,7 +130,7 @@ LayoutBlockBox::LayoutBlockBox(LayoutBlockBox* _parent, Element* _element) : pos
 }
 
 // Creates a new block box in an inline context.
-LayoutBlockBox::LayoutBlockBox(LayoutBlockBox* _parent) : position(-1, -1)
+LayoutBlockBox::LayoutBlockBox(LayoutBlockBox* _parent, bool allow_shrink) : position(-1, -1)
 {
 	parent = _parent;
 	offset_parent = parent->offset_parent;
@@ -146,7 +148,8 @@ LayoutBlockBox::LayoutBlockBox(LayoutBlockBox* _parent) : position(-1, -1)
 	box_cursor = 0;
 	vertical_overflow = false;
 
-	LayoutEngine::BuildBox(box, min_height, max_height, parent, nullptr);
+	// TODO: Calculate outside and pass the box into the constructor.
+	LayoutDetails::BuildBox(box, min_height, max_height, parent, nullptr, false, allow_shrink);
 	parent->PositionBlockBox(position, box, Style::Clear::None);
 	box.SetContent(Vector2f(box.GetSize(Box::CONTENT).x, -1));
 
@@ -318,7 +321,7 @@ LayoutInlineBox* LayoutBlockBox::CloseLineBox(LayoutLineBox* child, UniquePtr<La
 }
 
 // Adds a new block element to this block box.
-LayoutBlockBox* LayoutBlockBox::AddBlockElement(Element* element)
+LayoutBlockBox* LayoutBlockBox::AddBlockElement(Element* element, bool allow_shrink)
 {
 	RMLUI_ZoneScoped;
 
@@ -351,7 +354,7 @@ LayoutBlockBox* LayoutBlockBox::AddBlockElement(Element* element)
 		}
 	}
 
-	block_boxes.push_back(MakeUnique<LayoutBlockBox>(this, element));
+	block_boxes.push_back(MakeUnique<LayoutBlockBox>(this, element, allow_shrink));
 	return block_boxes.back().get();
 }
 
@@ -475,19 +478,8 @@ void LayoutBlockBox::CloseAbsoluteElements()
 			Vector2f absolute_position = absolute_elements[i].position;
 			absolute_position -= position - offset_root->GetPosition();
 
-			// See CSS 2.1 spec 10.3.7
-			const ComputedValues& computed = absolute_element->GetComputedValues();
-			bool shrink_to_fit = (computed.width.type == Style::Width::Auto && (computed.left.type == Style::Width::Auto || computed.right.type == Style::Width::Auto));
-
-			// Don't shrink replaced elements.
-			if (shrink_to_fit)
-			{
-				Vector2f unused_intrinsic_dimensions;
-				shrink_to_fit = !absolute_element->GetIntrinsicDimensions(unused_intrinsic_dimensions);
-			}
-
 			// Lay out the element.
-			LayoutEngine::FormatElement(absolute_element, containing_block, shrink_to_fit);
+			LayoutEngine::FormatElement(absolute_element, containing_block);
 
 			// Now that the element's box has been built, we can offset the position we determined was appropriate for
 			// it by the element's margin. This is necessary because the coordinate system for the box begins at the
@@ -548,43 +540,47 @@ void LayoutBlockBox::PositionLineBox(Vector2f& box_position, float& box_width, b
 }
 
 
-// Calculate the dimensions of the box's internal width; i.e. the size of the largest line, plus this element's padding.
-float LayoutBlockBox::InternalContentWidth() const
+// Calculate the dimensions of the box's internal content width; i.e. the size of the largest line.
+float LayoutBlockBox::GetShrinkToFitWidth() const
 {
 	float content_width = 0.0f;
 
 	if (context == BLOCK)
 	{
+		auto get_content_width_from_children = [this, &content_width]() {
+			for (size_t i = 0; i < block_boxes.size(); i++)
+			{
+				const Box& box = block_boxes[i]->GetBox();
+				const float edge_size = box.GetCumulativeEdge(Box::PADDING, Box::LEFT) + box.GetCumulativeEdge(Box::PADDING, Box::RIGHT);
+				content_width = Math::Max(content_width, block_boxes[i]->GetShrinkToFitWidth() + edge_size);
+			}
+		};
 
-		for (size_t i = 0; i < block_boxes.size(); i++)
-		{
-			content_width = Math::Max(content_width, block_boxes[i]->InternalContentWidth());
-		}
-
-		// Work-around for supporting 'width' specification of 'display:block' elements inside 'display:inline-block'.
-		//  Alternative solution: Add some 'intrinsic_width' property to  every 'LayoutBlockBox' and have that propagate up to the nearest 'inline-block'.
+		// Block boxes with definite sizes should use that size. Otherwise, find the maximum content width of our children.
+		//  Alternative solution: Add some 'intrinsic_width' property to every 'LayoutBlockBox' and have that propagate up.
 		if (element)
 		{
 			auto& computed = element->GetComputedValues();
 			const float block_width = box.GetSize(Box::CONTENT).x;
 
-			if(computed.width.type != Style::Width::Auto)
+			if(computed.width.type == Style::Width::Auto)
 			{
-				float w_value = ResolveValue(computed.width, block_width);
-				content_width = Math::Max(content_width, w_value);
+				get_content_width_from_children();
 			}
-
-			float min_width = ResolveValue(computed.min_width, block_width);
-			content_width = Math::Max(content_width, min_width);
-			
-			if (computed.max_width.value >= 0.f)
+			else
 			{
-				float value = ResolveValue(computed.max_width, block_width);
-				content_width = Math::Min(content_width, value);
+				float width_value = ResolveValue(computed.width, block_width);
+				content_width = Math::Max(content_width, width_value);
 			}
+
+			content_width = LayoutDetails::ClampWidth(content_width, computed, block_width);
+		}
+		else
+		{
+			get_content_width_from_children();
 		}
 
-		content_width += box.GetCumulativeEdge(Box::PADDING, Box::LEFT) + box.GetCumulativeEdge(Box::PADDING, Box::RIGHT);
+		// Can add the dimensions of floating elements here if we want to support that.
 	}
 	else
 	{

+ 5 - 4
Source/Core/LayoutBlockBox.h

@@ -62,11 +62,11 @@ public:
 	/// @param layout_engine[in] The layout engine that created this block box.
 	/// @param parent[in] The parent of this block box. This will be nullptr for the root element.
 	/// @param element[in] The element this block box is laying out.
-	LayoutBlockBox(LayoutBlockBox* parent, Element* element);
+	LayoutBlockBox(LayoutBlockBox* parent, Element* element, bool allow_shrink = true);
 	/// Creates a new block box in an inline context.
 	/// @param layout_engine[in] The layout engine that created this block box.
 	/// @param parent[in] The parent of this block box.
-	LayoutBlockBox(LayoutBlockBox* parent);
+	LayoutBlockBox(LayoutBlockBox* parent, bool allow_shrink = true);
 	/// Releases the block box.
 	~LayoutBlockBox();
 
@@ -90,7 +90,7 @@ public:
 	/// @param element[in] The new block element.
 	/// @param placed[in] True if the element is to be placed, false otherwise.
 	/// @return The block box representing the element. Once the element's children have been positioned, Close() must be called on it.
-	LayoutBlockBox* AddBlockElement(Element* element);
+	LayoutBlockBox* AddBlockElement(Element* element, bool allow_shrink = true);
 	/// Adds a new inline element to this inline-context box.
 	/// @param element[in] The new inline element.
 	/// @param box[in] The box defining the element's bounds.
@@ -129,7 +129,8 @@ public:
 	/// @param dimensions[in] The minimum dimensions of the line.
 	void PositionLineBox(Vector2f& box_position, float& box_width, bool& wrap_content, const Vector2f& dimensions) const;
 
-	float InternalContentWidth() const;
+	/// Calculate the dimensions of the box's internal content width; i.e. the size used to calculate the shrink-to-fit width.
+	float GetShrinkToFitWidth() const;
 
 	/// Returns the block box's element.
 	/// @return The block box's element.

+ 476 - 0
Source/Core/LayoutDetails.cpp

@@ -0,0 +1,476 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "LayoutDetails.h"
+#include "LayoutEngine.h"
+#include "../../Include/RmlUi/Core/Math.h"
+#include <float.h>
+
+namespace Rml {
+
+// Generates the box for an element.
+void LayoutDetails::BuildBox(Box& box, const Vector2f& containing_block, Element* element, bool inline_element, bool allow_shrink)
+{
+	if (element == nullptr)
+	{
+		box.SetContent(containing_block);
+		return;
+	}
+
+	const ComputedValues& computed = element->GetComputedValues();
+
+	// Calculate the padding area.
+	float padding = ResolveValue(computed.padding_top, containing_block.x);
+	box.SetEdge(Box::PADDING, Box::TOP, Math::Max(0.0f, padding));
+	padding = ResolveValue(computed.padding_right, containing_block.x);
+	box.SetEdge(Box::PADDING, Box::RIGHT, Math::Max(0.0f, padding));
+	padding = ResolveValue(computed.padding_bottom, containing_block.x);
+	box.SetEdge(Box::PADDING, Box::BOTTOM, Math::Max(0.0f, padding));
+	padding = ResolveValue(computed.padding_left, containing_block.x);
+	box.SetEdge(Box::PADDING, Box::LEFT, Math::Max(0.0f, padding));
+
+	// Calculate the border area.
+	box.SetEdge(Box::BORDER, Box::TOP, Math::Max(0.0f, computed.border_top_width));
+	box.SetEdge(Box::BORDER, Box::RIGHT, Math::Max(0.0f, computed.border_right_width));
+	box.SetEdge(Box::BORDER, Box::BOTTOM, Math::Max(0.0f, computed.border_bottom_width));
+	box.SetEdge(Box::BORDER, Box::LEFT, Math::Max(0.0f, computed.border_left_width));
+
+	// Calculate the size of the content area.
+	Vector2f content_area(-1, -1);
+	bool replaced_element = false;
+
+	// If the element has intrinsic dimensions, then we use those as the basis for the content area and only adjust
+	// them if a non-auto style has been applied to them.
+	if (element->GetIntrinsicDimensions(content_area))
+	{
+		replaced_element = true;
+
+		Vector2f original_content_area = content_area;
+
+		// The element has resized itself, so we only resize it if a RCSS width or height was set explicitly. A value of
+		// 'auto' (or 'auto-fit', ie, both keywords) means keep (or adjust) the intrinsic dimensions.
+		bool auto_width = false, auto_height = false;
+
+		if (computed.width.type != Style::Width::Auto)
+			content_area.x = ResolveValue(computed.width, containing_block.x);
+		else
+			auto_width = true;
+
+		if (computed.height.type != Style::Height::Auto)
+			content_area.y = ResolveValue(computed.height, containing_block.y);
+		else
+			auto_height = true;
+
+		// If one of the dimensions is 'auto' then we need to scale it such that the original ratio is preserved.
+		if (auto_width && !auto_height)
+			content_area.x = (content_area.y / original_content_area.y) * original_content_area.x;
+		else if (auto_height && !auto_width)
+			content_area.y = (content_area.x / original_content_area.x) * original_content_area.y;
+
+		// Reduce the width and height to make up for borders and padding.
+		content_area.x -= (box.GetEdge(Box::BORDER, Box::LEFT) +
+						   box.GetEdge(Box::PADDING, Box::LEFT) +
+						   box.GetEdge(Box::BORDER, Box::RIGHT) +
+						   box.GetEdge(Box::PADDING, Box::RIGHT));
+		content_area.y -= (box.GetEdge(Box::BORDER, Box::TOP) +
+						   box.GetEdge(Box::PADDING, Box::TOP) +
+						   box.GetEdge(Box::BORDER, Box::BOTTOM) +
+						   box.GetEdge(Box::PADDING, Box::BOTTOM));
+
+		content_area.x = Math::Max(content_area.x, 0.0f);
+		content_area.y = Math::Max(content_area.y, 0.0f);
+	}
+
+	// If the element is inline, then its calculations are much more straightforward (no worrying about auto margins
+	// and dimensions, etc). All we do is calculate the margins, set the content area and bail.
+	if (inline_element)
+	{
+		if (replaced_element)
+		{
+			content_area.x = ClampWidth(content_area.x, computed, containing_block.x);
+			content_area.y = ClampHeight(content_area.y, computed, containing_block.y);
+		}
+
+		// If the element was not replaced, then we leave its dimension as unsized (-1, -1) and ignore the width and
+		// height properties.
+		box.SetContent(content_area);
+
+		// Evaluate the margins. Any declared as 'auto' will resolve to 0.
+		box.SetEdge(Box::MARGIN, Box::TOP, ResolveValue(computed.margin_top, containing_block.x));
+		box.SetEdge(Box::MARGIN, Box::RIGHT, ResolveValue(computed.margin_right, containing_block.x));
+		box.SetEdge(Box::MARGIN, Box::BOTTOM, ResolveValue(computed.margin_bottom, containing_block.x));
+		box.SetEdge(Box::MARGIN, Box::LEFT, ResolveValue(computed.margin_left, containing_block.x));
+	}
+
+	// The element is block, so we need to run the box through the ringer to potentially evaluate auto margins and
+	// dimensions.
+	else
+	{
+		box.SetContent(content_area);
+		BuildBoxWidth(box, computed, containing_block, element, allow_shrink, replaced_element);
+		BuildBoxHeight(box, computed, containing_block.y);
+	}
+}
+
+// Generates the box for an element placed in a block box.
+void LayoutDetails::BuildBox(Box& box, float& min_height, float& max_height, LayoutBlockBox* containing_box, Element* element, bool inline_element, bool allow_shrink)
+{
+	Vector2f containing_block = GetContainingBlock(containing_box);
+	BuildBox(box, containing_block, element, inline_element, allow_shrink);
+
+	float box_height = box.GetSize().y;
+	if (box_height < 0)
+	{
+		auto& computed = element->GetComputedValues();
+		min_height = ResolveValue(computed.min_height, containing_block.y);
+		max_height = (computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block.y));
+	}
+	else
+	{
+		min_height = box_height;
+		max_height = box_height;
+	}
+}
+
+// Clamps the width of an element based from its min-width and max-width properties.
+float LayoutDetails::ClampWidth(float width, const ComputedValues& computed, float containing_block_width)
+{
+	float min_width = ResolveValue(computed.min_width, containing_block_width);
+	float max_width = (computed.max_width.value < 0.f ? FLT_MAX : ResolveValue(computed.max_width, containing_block_width));
+
+	return Math::Clamp(width, min_width, max_width);
+}
+
+// Clamps the height of an element based from its min-height and max-height properties.
+float LayoutDetails::ClampHeight(float height, const ComputedValues& computed, float containing_block_height)
+{
+	float min_height = ResolveValue(computed.min_height, containing_block_height);
+	float max_height = (computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block_height));
+
+	return Math::Clamp(height, min_height, max_height);
+}
+
+// Returns the fully-resolved, fixed-width and -height containing block from a block box.
+Vector2f LayoutDetails::GetContainingBlock(const LayoutBlockBox* containing_box)
+{
+	Vector2f containing_block;
+
+	containing_block.x = containing_box->GetBox().GetSize(Box::CONTENT).x;
+	if (containing_box->GetElement() != nullptr)
+		containing_block.x -= containing_box->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
+
+	while ((containing_block.y = containing_box->GetBox().GetSize(Box::CONTENT).y) < 0)
+	{
+		containing_box = containing_box->GetParent();
+		if (containing_box == nullptr)
+		{
+			RMLUI_ERROR;
+			containing_block.y = 0;
+		}
+	}
+	if (containing_box != nullptr &&
+		containing_box->GetElement() != nullptr)
+		containing_block.y -= containing_box->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL);
+
+	containing_block.x = Math::Max(0.0f, containing_block.x);
+	containing_block.y = Math::Max(0.0f, containing_block.y);
+
+	return containing_block;
+}
+
+
+float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_block)
+{
+	LayoutBlockBox containing_block_box(nullptr, nullptr);
+	containing_block_box.GetBox().SetContent(containing_block);
+
+	LayoutBlockBox* block_context_box = containing_block_box.AddBlockElement(element, false);
+
+	for (int i = 0; i < element->GetNumChildren(); i++)
+	{
+		if (!LayoutEngine::FormatElement(block_context_box, element->GetChild(i)))
+			i = -1;
+	}
+
+	// We only do layouting to get the fit-to-shrink width here, and for this purpose we may get
+	// away with not closing the boxes. This is avoided for performance reasons.
+	//block_context_box->Close();
+	//block_context_box->CloseAbsoluteElements();
+
+	return Math::Min(containing_block.x, block_context_box->GetShrinkToFitWidth());
+}
+
+// Builds the block-specific width and horizontal margins of a Box.
+void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, Vector2f containing_block, Element* element, bool allow_shrink, bool replaced_element)
+{
+	RMLUI_ZoneScoped;
+
+	Vector2f content_area = box.GetSize();
+
+	// Determine if the element has an automatic width, and if not calculate it.
+	bool width_auto;
+	if (content_area.x >= 0)
+	{
+		width_auto = false;
+	}
+	else
+	{
+		if (computed.width.type == Style::Width::Auto)
+		{
+			width_auto = true;
+		}
+		else
+		{
+			width_auto = false;
+			content_area.x = ResolveValue(computed.width, containing_block.x);
+		}
+	}
+
+	// Determine if the element has automatic margins.
+	bool margins_auto[2];
+	int num_auto_margins = 0;
+
+	for (int i = 0; i < 2; ++i)
+	{
+		auto* margin_value = (i == 0 ? &computed.margin_left : &computed.margin_right);
+		if (margin_value->type == Style::Margin::Auto)
+		{
+			margins_auto[i] = true;
+			num_auto_margins++;
+		}
+		else
+		{
+			margins_auto[i] = false;
+			box.SetEdge(Box::MARGIN, i == 0 ? Box::LEFT : Box::RIGHT, ResolveValue(*margin_value, containing_block.x));
+		}
+	}
+
+	// If the width is set to auto, we need to calculate the width
+	if (width_auto)
+	{
+		// Apply the shrink-to-fit algorithm here to find the width of the element.
+		// See CSS 2.1 section 10.3.7 for when this should be applied.
+		const bool shrink_to_fit = allow_shrink && !replaced_element &&
+			(
+				(computed.float_ != Style::Float::None) ||
+				((computed.position == Style::Position::Absolute || computed.position == Style::Position::Fixed) && (computed.left.type == Style::Left::Auto || computed.right.type == Style::Right::Auto)) ||
+				(computed.display == Style::Display::InlineBlock)
+			);
+
+		
+		float left = 0.0f, right = 0.0f;
+		// If we are dealing with an absolutely positioned element we need to
+		// consider if the left and right properties are set, since the width can be affected.
+		if (computed.position == Style::Position::Absolute || computed.position == Style::Position::Fixed)
+		{
+			if (computed.left.type != Style::Left::Auto)
+				left = ResolveValue(computed.left, containing_block.x);
+			if (computed.right.type != Style::Right::Auto)
+				right = ResolveValue(computed.right, containing_block.x);
+		}
+
+		if (margins_auto[0])
+			box.SetEdge(Box::MARGIN, Box::LEFT, 0);
+		if (margins_auto[1])
+			box.SetEdge(Box::MARGIN, Box::RIGHT, 0);
+
+		if (shrink_to_fit)
+		{
+			content_area.x = GetShrinkToFitWidth(element, containing_block);
+		}
+		else
+		{
+			// We resolve any auto margins to 0 and the width is set to whatever is left of the containing block.
+			content_area.x = containing_block.x - (left +
+				box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) +
+				box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT) +
+				right);
+			content_area.x = Math::Max(0.0f, content_area.x);
+		}
+	}
+	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
+	else if (num_auto_margins > 0)
+	{
+		float margin = (containing_block.x - (box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) +
+												  box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT) +
+												  content_area.x)) / num_auto_margins;
+
+		if (margins_auto[0])
+			box.SetEdge(Box::MARGIN, Box::LEFT, margin);
+		if (margins_auto[1])
+			box.SetEdge(Box::MARGIN, Box::RIGHT, margin);
+	}
+
+	// Clamp the calculated width; if the width is changed by the clamp, then the margins need to be recalculated if
+	// they were set to auto.
+	float clamped_width = ClampWidth(content_area.x, computed, containing_block.x);
+	if (clamped_width != content_area.x)
+	{
+		content_area.x = clamped_width;
+		box.SetContent(content_area);
+
+		if (num_auto_margins > 0)
+		{
+			// Reset the automatic margins.
+			if (margins_auto[0])
+				box.SetEdge(Box::MARGIN, Box::LEFT, 0);
+			if (margins_auto[1])
+				box.SetEdge(Box::MARGIN, Box::RIGHT, 0);
+
+			BuildBoxWidth(box, computed, containing_block, element, allow_shrink, replaced_element);
+		}
+	}
+	else
+		box.SetContent(content_area);
+}
+
+// Builds the block-specific height and vertical margins of a Box.
+void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, float containing_block_height)
+{
+	RMLUI_ZoneScoped;
+
+	Vector2f content_area = box.GetSize();
+
+	// Determine if the element has an automatic height, and if not calculate it.
+	bool height_auto;
+	if (content_area.y >= 0)
+	{
+		height_auto = false;
+	}
+	else
+	{
+		if (computed.height.type == Style::Height::Auto)
+		{
+			height_auto = true;
+		}
+		else
+		{
+			height_auto = false;
+			content_area.y = ResolveValue(computed.height, containing_block_height);
+		}
+	}
+
+	// Determine if the element has automatic margins.
+	bool margins_auto[2];
+	int num_auto_margins = 0;
+
+	for (int i = 0; i < 2; ++i)
+	{
+		auto* margin_value = (i == 0 ? &computed.margin_top : &computed.margin_bottom);
+		if (margin_value->type == Style::Margin::Auto)
+		{
+			margins_auto[i] = true;
+			num_auto_margins++;
+		}
+		else
+		{
+			margins_auto[i] = false;
+			box.SetEdge(Box::MARGIN, i == 0 ? Box::TOP : Box::BOTTOM, ResolveValue(*margin_value, containing_block_height));
+		}
+	}
+
+	// If the height is set to auto, we need to calculate the height
+	if (height_auto)
+	{
+		// We resolve any auto margins to 0
+		if (margins_auto[0])
+			box.SetEdge(Box::MARGIN, Box::TOP, 0);
+		if (margins_auto[1])
+			box.SetEdge(Box::MARGIN, Box::BOTTOM, 0);
+
+		// If the height is set to auto for a box in normal flow, the height is set to -1.
+		content_area.y = -1;
+
+		// But if we are dealing with an absolutely positioned element we need to
+		// consider if the top and bottom properties are set, since the height can be affected.
+		if (computed.position == Style::Position::Absolute || computed.position == Style::Position::Fixed)
+		{
+			float top = 0.0f, bottom = 0.0f;
+
+			if (computed.top.type != Style::Top::Auto && computed.bottom.type != Style::Bottom::Auto)
+			{
+				top = ResolveValue(computed.top, containing_block_height );
+				bottom = ResolveValue(computed.bottom, containing_block_height );
+
+				// The height gets resolved to whatever is left of the containing block
+				content_area.y = containing_block_height - (top +
+				                                            box.GetCumulativeEdge(Box::CONTENT, Box::TOP) +
+				                                            box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM) +
+				                                            bottom);
+				content_area.y = Math::Max(0.0f, content_area.y);
+			}
+		}
+	}
+	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
+	else if (num_auto_margins > 0)
+	{
+		float margin;
+		if (content_area.y >= 0)
+		{
+			margin = (containing_block_height - (box.GetCumulativeEdge(Box::CONTENT, Box::TOP) +
+												 box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM) +
+												 content_area.y)) / num_auto_margins;
+		}
+		else
+			margin = 0;
+
+		if (margins_auto[0])
+			box.SetEdge(Box::MARGIN, Box::TOP, margin);
+		if (margins_auto[1])
+			box.SetEdge(Box::MARGIN, Box::BOTTOM, margin);
+	}
+
+	if (content_area.y >= 0)
+	{
+		// Clamp the calculated height; if the height is changed by the clamp, then the margins need to be recalculated if
+		// they were set to auto.
+		float clamped_height = ClampHeight(content_area.y, computed, containing_block_height);
+		if (clamped_height != content_area.y)
+		{
+			content_area.y = clamped_height;
+			box.SetContent(content_area);
+
+			if (num_auto_margins > 0)
+			{
+				// Reset the automatic margins.
+				if (margins_auto[0])
+					box.SetEdge(Box::MARGIN, Box::TOP, 0);
+				if (margins_auto[1])
+					box.SetEdge(Box::MARGIN, Box::BOTTOM, 0);
+
+				BuildBoxHeight(box, computed, containing_block_height);
+			}
+
+			return;
+		}
+	}
+
+	box.SetContent(content_area);
+}
+
+} // namespace Rml

+ 95 - 0
Source/Core/LayoutDetails.h

@@ -0,0 +1,95 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_LAYOUTDETAILS_H
+#define RMLUI_CORE_LAYOUTDETAILS_H
+
+#include "LayoutBlockBox.h"
+
+namespace Rml {
+
+class Box;
+
+/**
+	Layout functions for sizing elements.
+	
+	Corresponds to the CSS 2.1 specification, '10. Visual formatting model details'.
+ */
+
+class LayoutDetails
+{
+public:
+	/// Generates the box for an element.
+	/// @param[out] box The box to be built.
+	/// @param[in] containing_block The dimensions of the content area of the block containing the element.
+	/// @param[in] element The element to build the box for.
+	/// @param[in] inline_element True if the element is placed in an inline context, false if not.
+	static void BuildBox(Box& box, const Vector2f& containing_block, Element* element, bool inline_element = false, bool allow_shrink = true);
+	/// Generates the box for an element placed in a block box.
+	/// @param[out] box The box to be built.
+	/// @param[out] min_height The minimum height of the element's box.
+	/// @param[out] max_height The maximum height of the element's box.
+	/// @param[in] containing_box The block box containing the element.
+	/// @param[in] element The element to build the box for.
+	/// @param[in] inline_element True if the element is placed in an inline context, false if not.
+	static void BuildBox(Box& box, float& min_height, float& max_height, LayoutBlockBox* containing_box, Element* element, bool inline_element = false, bool allow_shrink = true);
+
+	/// Clamps the width of an element based from its min-width and max-width properties.
+	/// @param[in] width The width to clamp.
+	/// @param[in] element The element to read the properties from.
+	/// @param[in] containing_block_width The width of the element's containing block.
+	/// @return The clamped width.
+	static float ClampWidth(float width, const ComputedValues& computed, float containing_block_width);
+	/// Clamps the height of an element based from its min-height and max-height properties.
+	/// @param[in] height The height to clamp.
+	/// @param[in] element The element to read the properties from.
+	/// @param[in] containing_block_height The height of the element's containing block.
+	/// @return The clamped height.
+	static float ClampHeight(float height, const ComputedValues& computed, float containing_block_height);
+
+	/// Returns the fully-resolved, fixed-width and -height containing block from a block box.
+	/// @param[in] containing_box The leaf box.
+	/// @return The dimensions of the content area, using the latest fixed dimensions for width and height in the hierarchy.
+	static Vector2f GetContainingBlock(const LayoutBlockBox* containing_box);
+
+	static float GetShrinkToFitWidth(Element* element, Vector2f containing_block);
+
+	/// Builds the block-specific width and horizontal margins of a Box.
+	/// @param[in,out] box The box to generate. The padding and borders must be set on the box already. If the content area is sized, then it will be used instead of the width property.
+	/// @param[in] element The element the box is being generated for.
+	/// @param[in] containing_block_width The width of the containing block.
+	static void BuildBoxWidth(Box& box, const ComputedValues& computed, Vector2f containing_block_width, Element* element, bool allow_shrink, bool replaced_element);
+	/// Builds the block-specific height and vertical margins of a Box.
+	/// @param[in,out] box The box to generate. The padding and borders must be set on the box already. If the content area is sized, then it will be used instead of the height property.
+	/// @param[in] element The element the box is being generated for.
+	/// @param[in] containing_block_height The height of the containing block.
+	static void BuildBoxHeight(Box& box, const ComputedValues& computed, float containing_block_height);
+};
+
+} // namespace Rml
+#endif

+ 10 - 453
Source/Core/LayoutEngine.cpp

@@ -30,6 +30,7 @@
 #include "../../Include/RmlUi/Core/Math.h"
 #include "Pool.h"
 #include "LayoutBlockBoxSpace.h"
+#include "LayoutDetails.h"
 #include "LayoutInlineBoxText.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/ElementScroll.h"
@@ -53,7 +54,7 @@ struct LayoutChunk
 static Pool< LayoutChunk > layout_chunk_pool(200, true);
 
 // Formats the contents for a root-level element (usually a document or floating element).
-bool LayoutEngine::FormatElement(Element* element, Vector2f containing_block, bool shrink_to_fit)
+bool LayoutEngine::FormatElement(Element* element, Vector2f containing_block)
 {
 #ifdef RMLUI_ENABLE_PROFILING
 	RMLUI_ZoneScopedC(0xB22222);
@@ -61,10 +62,10 @@ bool LayoutEngine::FormatElement(Element* element, Vector2f containing_block, bo
 	RMLUI_ZoneName(name.c_str(), name.size());
 #endif
 
-	auto containing_block_box = MakeUnique<LayoutBlockBox>(nullptr, nullptr);
-	containing_block_box->GetBox().SetContent(containing_block);
+	LayoutBlockBox containing_block_box(nullptr, nullptr);
+	containing_block_box.GetBox().SetContent(containing_block);
 
-	LayoutBlockBox* block_context_box = containing_block_box->AddBlockElement(element);
+	LayoutBlockBox* block_context_box = containing_block_box.AddBlockElement(element);
 
 	for (int i = 0; i < element->GetNumChildren(); i++)
 	{
@@ -72,30 +73,6 @@ bool LayoutEngine::FormatElement(Element* element, Vector2f containing_block, bo
 			i = -1;
 	}
 
-	if (shrink_to_fit)
-	{
-		// For inline blocks with 'auto' width, we want to shrink the box back to its inner content width, recreating the LayoutBlockBox.
-		const float content_width = block_context_box->InternalContentWidth();
-
-		if (content_width < containing_block.x)
-		{
-			RMLUI_ZoneScopedNC("shrink_to_fit", 0xB27222);
-
-			const Vector2f shrinked_block_size(content_width, containing_block.y);
-			
-			containing_block_box = MakeUnique<LayoutBlockBox>(nullptr, nullptr);
-			containing_block_box->GetBox().SetContent(shrinked_block_size);
-
-			block_context_box = containing_block_box->AddBlockElement(element);
-
-			for (int i = 0; i < element->GetNumChildren(); i++)
-			{
-				if (!FormatElement(block_context_box, element->GetChild(i)))
-					i = -1;
-			}
-		}
-	}
-
 	block_context_box->Close();
 	block_context_box->CloseAbsoluteElements();
 
@@ -104,148 +81,6 @@ bool LayoutEngine::FormatElement(Element* element, Vector2f containing_block, bo
 	return true;
 }
 
-// Generates the box for an element.
-void LayoutEngine::BuildBox(Box& box, const Vector2f& containing_block, Element* element, bool inline_element)
-{
-	if (element == nullptr)
-	{
-		box.SetContent(containing_block);
-		return;
-	}
-
-	const ComputedValues& computed = element->GetComputedValues();
-
-	// Calculate the padding area.
-	float padding = ResolveValue(computed.padding_top, containing_block.x);
-	box.SetEdge(Box::PADDING, Box::TOP, Math::Max(0.0f, padding));
-	padding = ResolveValue(computed.padding_right, containing_block.x);
-	box.SetEdge(Box::PADDING, Box::RIGHT, Math::Max(0.0f, padding));
-	padding = ResolveValue(computed.padding_bottom, containing_block.x);
-	box.SetEdge(Box::PADDING, Box::BOTTOM, Math::Max(0.0f, padding));
-	padding = ResolveValue(computed.padding_left, containing_block.x);
-	box.SetEdge(Box::PADDING, Box::LEFT, Math::Max(0.0f, padding));
-
-	// Calculate the border area.
-	box.SetEdge(Box::BORDER, Box::TOP, Math::Max(0.0f, computed.border_top_width));
-	box.SetEdge(Box::BORDER, Box::RIGHT, Math::Max(0.0f, computed.border_right_width));
-	box.SetEdge(Box::BORDER, Box::BOTTOM, Math::Max(0.0f, computed.border_bottom_width));
-	box.SetEdge(Box::BORDER, Box::LEFT, Math::Max(0.0f, computed.border_left_width));
-
-	// Calculate the size of the content area.
-	Vector2f content_area(-1, -1);
-	bool replaced_element = false;
-
-	// If the element has intrinsic dimensions, then we use those as the basis for the content area and only adjust
-	// them if a non-auto style has been applied to them.
-	if (element->GetIntrinsicDimensions(content_area))
-	{
-		replaced_element = true;
-
-		Vector2f original_content_area = content_area;
-
-		// The element has resized itself, so we only resize it if a RCSS width or height was set explicitly. A value of
-		// 'auto' (or 'auto-fit', ie, both keywords) means keep (or adjust) the intrinsic dimensions.
-		bool auto_width = false, auto_height = false;
-
-		if (computed.width.type != Style::Width::Auto)
-			content_area.x = ResolveValue(computed.width, containing_block.x);
-		else
-			auto_width = true;
-
-		if (computed.height.type != Style::Height::Auto)
-			content_area.y = ResolveValue(computed.height, containing_block.y);
-		else
-			auto_height = true;
-
-		// If one of the dimensions is 'auto' then we need to scale it such that the original ratio is preserved.
-		if (auto_width && !auto_height)
-			content_area.x = (content_area.y / original_content_area.y) * original_content_area.x;
-		else if (auto_height && !auto_width)
-			content_area.y = (content_area.x / original_content_area.x) * original_content_area.y;
-
-		// Reduce the width and height to make up for borders and padding.
-		content_area.x -= (box.GetEdge(Box::BORDER, Box::LEFT) +
-						   box.GetEdge(Box::PADDING, Box::LEFT) +
-						   box.GetEdge(Box::BORDER, Box::RIGHT) +
-						   box.GetEdge(Box::PADDING, Box::RIGHT));
-		content_area.y -= (box.GetEdge(Box::BORDER, Box::TOP) +
-						   box.GetEdge(Box::PADDING, Box::TOP) +
-						   box.GetEdge(Box::BORDER, Box::BOTTOM) +
-						   box.GetEdge(Box::PADDING, Box::BOTTOM));
-
-		content_area.x = Math::Max(content_area.x, 0.0f);
-		content_area.y = Math::Max(content_area.y, 0.0f);
-	}
-
-	// If the element is inline, then its calculations are much more straightforward (no worrying about auto margins
-	// and dimensions, etc). All we do is calculate the margins, set the content area and bail.
-	if (inline_element)
-	{
-		if (replaced_element)
-		{
-			content_area.x = ClampWidth(content_area.x, computed, containing_block.x);
-			content_area.y = ClampHeight(content_area.y, computed, containing_block.y);
-		}
-
-		// If the element was not replaced, then we leave its dimension as unsized (-1, -1) and ignore the width and
-		// height properties.
-		box.SetContent(content_area);
-
-		// Evaluate the margins. Any declared as 'auto' will resolve to 0.
-		box.SetEdge(Box::MARGIN, Box::TOP, ResolveValue(computed.margin_top, containing_block.x));
-		box.SetEdge(Box::MARGIN, Box::RIGHT, ResolveValue(computed.margin_right, containing_block.x));
-		box.SetEdge(Box::MARGIN, Box::BOTTOM, ResolveValue(computed.margin_bottom, containing_block.x));
-		box.SetEdge(Box::MARGIN, Box::LEFT, ResolveValue(computed.margin_left, containing_block.x));
-	}
-
-	// The element is block, so we need to run the box through the ringer to potentially evaluate auto margins and
-	// dimensions.
-	else
-	{
-		box.SetContent(content_area);
-		BuildBoxWidth(box, computed, containing_block.x);
-		BuildBoxHeight(box, computed, containing_block.y);
-	}
-}
-
-// Generates the box for an element placed in a block box.
-void LayoutEngine::BuildBox(Box& box, float& min_height, float& max_height, LayoutBlockBox* containing_box, Element* element, bool inline_element)
-{
-	Vector2f containing_block = GetContainingBlock(containing_box);
-	BuildBox(box, containing_block, element, inline_element);
-
-	float box_height = box.GetSize().y;
-	if (box_height < 0)
-	{
-		auto& computed = element->GetComputedValues();
-		min_height = ResolveValue(computed.min_height, containing_block.y);
-		max_height = (computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block.y));
-	}
-	else
-	{
-		min_height = box_height;
-		max_height = box_height;
-	}
-}
-
-// Clamps the width of an element based from its min-width and max-width properties.
-float LayoutEngine::ClampWidth(float width, const ComputedValues& computed, float containing_block_width)
-{
-	float min_width = ResolveValue(computed.min_width, containing_block_width);
-	float max_width = (computed.max_width.value < 0.f ? FLT_MAX : ResolveValue(computed.max_width, containing_block_width));
-
-	return Math::Clamp(width, min_width, max_width);
-}
-
-// Clamps the height of an element based from its min-height and max-height properties.
-float LayoutEngine::ClampHeight(float height, const ComputedValues& computed, float containing_block_height)
-{
-	float min_height = ResolveValue(computed.min_height, containing_block_height);
-	float max_height = (computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block_height));
-
-	return Math::Clamp(height, min_height, max_height);
-}
-
 void* LayoutEngine::AllocateLayoutChunk(size_t size)
 {
 	RMLUI_ASSERT(size <= LayoutChunk::size);
@@ -290,18 +125,7 @@ bool LayoutEngine::FormatElement(LayoutBlockBox* block_context_box, Element* ele
 	// If the element is floating, we remove it from the flow.
 	if (computed.float_ != Style::Float::None)
 	{
-		// Format the element as a block element.
-		bool shrink_to_fit = (computed.width.type == Style::Width::Auto);
-		
-		// Don't shrink replaced elements.
-		if (shrink_to_fit)
-		{
-			Vector2f unused_intrinsic_dimensions;
-			shrink_to_fit = !element->GetIntrinsicDimensions(unused_intrinsic_dimensions);
-		}
-
-		LayoutEngine::FormatElement(element, GetContainingBlock(block_context_box), shrink_to_fit);
-
+		LayoutEngine::FormatElement(element, LayoutDetails::GetContainingBlock(block_context_box));
 		return block_context_box->AddFloatElement(element);
 	}
 
@@ -365,6 +189,7 @@ bool LayoutEngine::FormatElementBlock(LayoutBlockBox* block_context_box, Element
 			element->OnLayout();
 	}
 
+	// TODO: This will have no effect?
 	block_context_box = new_block_context_box;
 	return true;
 }
@@ -376,7 +201,7 @@ bool LayoutEngine::FormatElementInline(LayoutBlockBox* block_context_box, Elemen
 
 	Box box;
 	float min_height, max_height;
-	BuildBox(box, min_height, max_height, block_context_box, element, true);
+	LayoutDetails::BuildBox(box, min_height, max_height, block_context_box, element, true);
 	LayoutInlineBox* inline_box = block_context_box->AddInlineElement(element, box);
 
 	// Format the element's children.
@@ -397,18 +222,9 @@ bool LayoutEngine::FormatElementInlineBlock(LayoutBlockBox* block_context_box, E
 	RMLUI_ZoneScopedC(0x1F2F2F);
 
 	// Format the element separately as a block element, then position it inside our own layout as an inline element.
-	Vector2f containing_block_size = GetContainingBlock(block_context_box);
+	Vector2f containing_block_size = LayoutDetails::GetContainingBlock(block_context_box);
 
-	bool shrink_to_fit = element->GetComputedValues().width.type == Style::Width::Auto;
-	
-	// Don't shrink replaced elements.
-	if (shrink_to_fit)
-	{
-		Vector2f unused_intrinsic_dimensions;
-		shrink_to_fit = !element->GetIntrinsicDimensions(unused_intrinsic_dimensions);
-	}
-
-	FormatElement(element, containing_block_size, shrink_to_fit);
+	FormatElement(element, containing_block_size);
 
 	block_context_box->AddInlineElement(element, element->GetBox())->Close();
 
@@ -431,263 +247,4 @@ bool LayoutEngine::FormatElementSpecial(LayoutBlockBox* block_context_box, Eleme
 	return false;
 }
 
-// Returns the fully-resolved, fixed-width and -height containing block from a block box.
-Vector2f LayoutEngine::GetContainingBlock(const LayoutBlockBox* containing_box)
-{
-	Vector2f containing_block;
-
-	containing_block.x = containing_box->GetBox().GetSize(Box::CONTENT).x;
-	if (containing_box->GetElement() != nullptr)
-		containing_block.x -= containing_box->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
-
-	while ((containing_block.y = containing_box->GetBox().GetSize(Box::CONTENT).y) < 0)
-	{
-		containing_box = containing_box->GetParent();
-		if (containing_box == nullptr)
-		{
-			RMLUI_ERROR;
-			containing_block.y = 0;
-		}
-	}
-	if (containing_box != nullptr &&
-		containing_box->GetElement() != nullptr)
-		containing_block.y -= containing_box->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL);
-
-	containing_block.x = Math::Max(0.0f, containing_block.x);
-	containing_block.y = Math::Max(0.0f, containing_block.y);
-
-	return containing_block;
-}
-
-// Builds the block-specific width and horizontal margins of a Box.
-void LayoutEngine::BuildBoxWidth(Box& box, const ComputedValues& computed, float containing_block_width)
-{
-	RMLUI_ZoneScoped;
-
-	Vector2f content_area = box.GetSize();
-
-	// Determine if the element has an automatic width, and if not calculate it.
-	bool width_auto;
-	if (content_area.x >= 0)
-	{
-		width_auto = false;
-	}
-	else
-	{
-		if (computed.width.type == Style::Width::Auto)
-		{
-			width_auto = true;
-		}
-		else
-		{
-			width_auto = false;
-			content_area.x = ResolveValue(computed.width, containing_block_width);
-		}
-	}
-
-	// Determine if the element has automatic margins.
-	bool margins_auto[2];
-	int num_auto_margins = 0;
-
-	for (int i = 0; i < 2; ++i)
-	{
-		auto* margin_value = (i == 0 ? &computed.margin_left : &computed.margin_right);
-		if (margin_value->type == Style::Margin::Auto)
-		{
-			margins_auto[i] = true;
-			num_auto_margins++;
-		}
-		else
-		{
-			margins_auto[i] = false;
-			box.SetEdge(Box::MARGIN, i == 0 ? Box::LEFT : Box::RIGHT, ResolveValue(*margin_value, containing_block_width));
-		}
-	}
-
-	// If the width is set to auto, we need to calculate the width
-	if (width_auto)
-	{
-		float left = 0.0f, right = 0.0f;
-		// If we are dealing with an absolutely positioned element we need to
-		// consider if the left and right properties are set, since the width can be affected.
-		if (computed.position == Style::Position::Absolute || computed.position == Style::Position::Fixed)
-		{
-			if (computed.left.type != Style::Left::Auto)
-				left = ResolveValue(computed.left, containing_block_width );
-			if (computed.right.type != Style::Right::Auto)
-				right = ResolveValue(computed.right, containing_block_width);
-		}
-
-		// We resolve any auto margins to 0 and the width is set to whatever is left of the containing block.
-		if (margins_auto[0])
-			box.SetEdge(Box::MARGIN, Box::LEFT, 0);
-		if (margins_auto[1])
-			box.SetEdge(Box::MARGIN, Box::RIGHT, 0);
-
-		content_area.x = containing_block_width - (left +
-		                                           box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) +
-		                                           box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT) +
-		                                           right);
-		content_area.x = Math::Max(0.0f, content_area.x);
-	}
-	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
-	else if (num_auto_margins > 0)
-	{
-		float margin = (containing_block_width - (box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) +
-												  box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT) +
-												  content_area.x)) / num_auto_margins;
-
-		if (margins_auto[0])
-			box.SetEdge(Box::MARGIN, Box::LEFT, margin);
-		if (margins_auto[1])
-			box.SetEdge(Box::MARGIN, Box::RIGHT, margin);
-	}
-
-	// Clamp the calculated width; if the width is changed by the clamp, then the margins need to be recalculated if
-	// they were set to auto.
-	float clamped_width = ClampWidth(content_area.x, computed, containing_block_width);
-	if (clamped_width != content_area.x)
-	{
-		content_area.x = clamped_width;
-		box.SetContent(content_area);
-
-		if (num_auto_margins > 0)
-		{
-			// Reset the automatic margins.
-			if (margins_auto[0])
-				box.SetEdge(Box::MARGIN, Box::LEFT, 0);
-			if (margins_auto[1])
-				box.SetEdge(Box::MARGIN, Box::RIGHT, 0);
-
-			BuildBoxWidth(box, computed, containing_block_width);
-		}
-	}
-	else
-		box.SetContent(content_area);
-}
-
-// Builds the block-specific height and vertical margins of a Box.
-void LayoutEngine::BuildBoxHeight(Box& box, const ComputedValues& computed, float containing_block_height)
-{
-	RMLUI_ZoneScoped;
-
-	Vector2f content_area = box.GetSize();
-
-	// Determine if the element has an automatic height, and if not calculate it.
-	bool height_auto;
-	if (content_area.y >= 0)
-	{
-		height_auto = false;
-	}
-	else
-	{
-		if (computed.height.type == Style::Height::Auto)
-		{
-			height_auto = true;
-		}
-		else
-		{
-			height_auto = false;
-			content_area.y = ResolveValue(computed.height, containing_block_height);
-		}
-	}
-
-	// Determine if the element has automatic margins.
-	bool margins_auto[2];
-	int num_auto_margins = 0;
-
-	for (int i = 0; i < 2; ++i)
-	{
-		auto* margin_value = (i == 0 ? &computed.margin_top : &computed.margin_bottom);
-		if (margin_value->type == Style::Margin::Auto)
-		{
-			margins_auto[i] = true;
-			num_auto_margins++;
-		}
-		else
-		{
-			margins_auto[i] = false;
-			box.SetEdge(Box::MARGIN, i == 0 ? Box::TOP : Box::BOTTOM, ResolveValue(*margin_value, containing_block_height));
-		}
-	}
-
-	// If the height is set to auto, we need to calculate the height
-	if (height_auto)
-	{
-		// We resolve any auto margins to 0
-		if (margins_auto[0])
-			box.SetEdge(Box::MARGIN, Box::TOP, 0);
-		if (margins_auto[1])
-			box.SetEdge(Box::MARGIN, Box::BOTTOM, 0);
-
-		// If the height is set to auto for a box in normal flow, the height is set to -1.
-		content_area.y = -1;
-
-		// But if we are dealing with an absolutely positioned element we need to
-		// consider if the top and bottom properties are set, since the height can be affected.
-		if (computed.position == Style::Position::Absolute || computed.position == Style::Position::Fixed)
-		{
-			float top = 0.0f, bottom = 0.0f;
-
-			if (computed.top.type != Style::Top::Auto && computed.bottom.type != Style::Bottom::Auto)
-			{
-				top = ResolveValue(computed.top, containing_block_height );
-				bottom = ResolveValue(computed.bottom, containing_block_height );
-
-				// The height gets resolved to whatever is left of the containing block
-				content_area.y = containing_block_height - (top +
-				                                            box.GetCumulativeEdge(Box::CONTENT, Box::TOP) +
-				                                            box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM) +
-				                                            bottom);
-				content_area.y = Math::Max(0.0f, content_area.y);
-			}
-		}
-	}
-	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
-	else if (num_auto_margins > 0)
-	{
-		float margin;
-		if (content_area.y >= 0)
-		{
-			margin = (containing_block_height - (box.GetCumulativeEdge(Box::CONTENT, Box::TOP) +
-												 box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM) +
-												 content_area.y)) / num_auto_margins;
-		}
-		else
-			margin = 0;
-
-		if (margins_auto[0])
-			box.SetEdge(Box::MARGIN, Box::TOP, margin);
-		if (margins_auto[1])
-			box.SetEdge(Box::MARGIN, Box::BOTTOM, margin);
-	}
-
-	if (content_area.y >= 0)
-	{
-		// Clamp the calculated height; if the height is changed by the clamp, then the margins need to be recalculated if
-		// they were set to auto.
-		float clamped_height = ClampHeight(content_area.y, computed, containing_block_height);
-		if (clamped_height != content_area.y)
-		{
-			content_area.y = clamped_height;
-			box.SetContent(content_area);
-
-			if (num_auto_margins > 0)
-			{
-				// Reset the automatic margins.
-				if (margins_auto[0])
-					box.SetEdge(Box::MARGIN, Box::TOP, 0);
-				if (margins_auto[1])
-					box.SetEdge(Box::MARGIN, Box::BOTTOM, 0);
-
-				BuildBoxHeight(box, computed, containing_block_height);
-			}
-
-			return;
-		}
-	}
-
-	box.SetContent(content_area);
-}
-
 } // namespace Rml

+ 7 - 51
Source/Core/LayoutEngine.h

@@ -42,49 +42,21 @@ class Box;
 class LayoutEngine
 {
 public:
-	/// Formats the contents for a root-level element (usually a document, floating or replaced element).
+	/// Formats the contents for a root-level element (usually a document, floating or replaced element). Establishes a new block formatting context.
 	/// @param element[in] The element to lay out.
 	/// @param containing_block[in] The size of the containing block.
 	/// @param shrink_to_fit[in] True to shrink the element to the width of its contents.
-	static bool FormatElement(Element* element, Vector2f containing_block, bool shrink_to_fit = false);
+	static bool FormatElement(Element* element, Vector2f containing_block);
 
-	/// Generates the box for an element.
-	/// @param[out] box The box to be built.
-	/// @param[in] containing_block The dimensions of the content area of the block containing the element.
-	/// @param[in] element The element to build the box for.
-	/// @param[in] inline_element True if the element is placed in an inline context, false if not.
-	static void BuildBox(Box& box, const Vector2f& containing_block, Element* element, bool inline_element = false);
-	/// Generates the box for an element placed in a block box.
-	/// @param[out] box The box to be built.
-	/// @param[out] min_height The minimum height of the element's box.
-	/// @param[out] max_height The maximum height of the element's box.
-	/// @param[in] containing_box The block box containing the element.
-	/// @param[in] element The element to build the box for.
-	/// @param[in] inline_element True if the element is placed in an inline context, false if not.
-	static void BuildBox(Box& box, float& min_height, float& max_height, LayoutBlockBox* containing_box, Element* element, bool inline_element = false);
-
-	/// Clamps the width of an element based from its min-width and max-width properties.
-	/// @param[in] width The width to clamp.
-	/// @param[in] element The element to read the properties from.
-	/// @param[in] containing_block_width The width of the element's containing block.
-	/// @return The clamped width.
-	static float ClampWidth(float width, const ComputedValues& computed, float containing_block_width);
-	/// Clamps the height of an element based from its min-height and max-height properties.
-	/// @param[in] height The height to clamp.
-	/// @param[in] element The element to read the properties from.
-	/// @param[in] containing_block_height The height of the element's containing block.
-	/// @return The clamped height.
-	static float ClampHeight(float height, const ComputedValues& computed, float containing_block_height);
+	/// Positions a single element and its children within a block formatting context.
+	/// @param[in] block_context_box The open block box to layout the element in.
+	/// @param[in] element The element to lay out.
+	static bool FormatElement(LayoutBlockBox* block_context_box, Element* element);
 
 	static void* AllocateLayoutChunk(size_t size);
 	static void DeallocateLayoutChunk(void* chunk);
 
 private:
-	/// Positions a single element and its children within this layout.
-	/// @param[in] block_context_box The open block box to layout the element in.
-	/// @param[in] element The element to lay out.
-	static bool FormatElement(LayoutBlockBox* block_context_box, Element* element);
-
 	/// Formats and positions an element as a block element.
 	/// @param[in] block_context_box The open block box to layout the element in.
 	/// @param[in] element The block element.
@@ -95,29 +67,13 @@ private:
 	static bool FormatElementInline(LayoutBlockBox* block_context_box, Element* element);
 	/// Positions an element as a sized inline element, formatting its internal hierarchy as a block element.
 	/// @param[in] block_context_box The open block box to layout the element in.
-	/// @param[in] element The replaced element.
+	/// @param[in] element The inline-block element.
 	static bool FormatElementInlineBlock(LayoutBlockBox* block_context_box, Element* element);
 	/// Executes any special formatting for special elements.
 	/// @param[in] block_context_box The open block box to layout the element in.
 	/// @param[in] element The element to parse.
 	/// @return True if the element was parsed as a special element, false otherwise.
 	static bool FormatElementSpecial(LayoutBlockBox* block_context_box, Element* element);
-
-	/// Returns the fully-resolved, fixed-width and -height containing block from a block box.
-	/// @param[in] containing_box The leaf box.
-	/// @return The dimensions of the content area, using the latest fixed dimensions for width and height in the hierarchy.
-	static Vector2f GetContainingBlock(const LayoutBlockBox* containing_box);
-
-	/// Builds the block-specific width and horizontal margins of a Box.
-	/// @param[in,out] box The box to generate. The padding and borders must be set on the box already. If the content area is sized, then it will be used instead of the width property.
-	/// @param[in] element The element the box is being generated for.
-	/// @param[in] containing_block_width The width of the containing block.
-	static void BuildBoxWidth(Box& box, const ComputedValues& computed, float containing_block_width);
-	/// Builds the block-specific height and vertical margins of a Box.
-	/// @param[in,out] box The box to generate. The padding and borders must be set on the box already. If the content area is sized, then it will be used instead of the height property.
-	/// @param[in] element The element the box is being generated for.
-	/// @param[in] containing_block_height The height of the containing block.
-	static void BuildBoxHeight(Box& box, const ComputedValues& computed, float containing_block_height);
 };
 
 } // namespace Rml

+ 5 - 5
Source/Core/WidgetScroll.cpp

@@ -28,7 +28,7 @@
 
 #include "WidgetScroll.h"
 #include "Clock.h"
-#include "LayoutEngine.h"
+#include "LayoutDetails.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Event.h"
 #include "../../Include/RmlUi/Core/Factory.h"
@@ -217,7 +217,7 @@ void WidgetScroll::FormatElements(const Vector2f& containing_block, bool resize_
 	// Build the box for the containing slider element. As the containing block is not guaranteed to have a defined
 	// height, we must use the width for both axes.
 	Box parent_box;
-	LayoutEngine::BuildBox(parent_box, Vector2f(containing_block.x, containing_block.x), parent);
+	LayoutDetails::BuildBox(parent_box, Vector2f(containing_block.x, containing_block.x), parent);
 	slider_length -= orientation == VERTICAL ? (parent_box.GetCumulativeEdge(Box::CONTENT, Box::TOP) + parent_box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM)) :
 											   (parent_box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) + parent_box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT));
 
@@ -231,7 +231,7 @@ void WidgetScroll::FormatElements(const Vector2f& containing_block, bool resize_
 
 	// Generate the initial dimensions for the track. It'll need to be cut down to fit the arrows.
 	Box track_box;
-	LayoutEngine::BuildBox(track_box, parent_box.GetSize(), track);
+	LayoutDetails::BuildBox(track_box, parent_box.GetSize(), track);
 	content = track_box.GetSize();
 	content[length_axis] = slider_length -= orientation == VERTICAL ? (track_box.GetCumulativeEdge(Box::CONTENT, Box::TOP) + track_box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM)) :
 																	  (track_box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) + track_box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT));
@@ -245,7 +245,7 @@ void WidgetScroll::FormatElements(const Vector2f& containing_block, bool resize_
 	for (int i = 0; i < 2; i++)
 	{
 		Box arrow_box;
-		LayoutEngine::BuildBox(arrow_box, parent_box.GetSize(), arrows[i]);
+		LayoutDetails::BuildBox(arrow_box, parent_box.GetSize(), arrows[i]);
 
 		// Clamp the size to (0, 0).
 		Vector2f arrow_size = arrow_box.GetSize();
@@ -297,7 +297,7 @@ void WidgetScroll::FormatElements(const Vector2f& containing_block, bool resize_
 void WidgetScroll::FormatBar(float bar_length)
 {
 	Box bar_box;
-	LayoutEngine::BuildBox(bar_box, parent->GetBox().GetSize(), bar);
+	LayoutDetails::BuildBox(bar_box, parent->GetBox().GetSize(), bar);
 
 	const auto& computed = bar->GetComputedValues();