Browse Source

LayoutDetails: Cleanup

Michael Ragazzon 5 years ago
parent
commit
4771ced11d

+ 3 - 1
Source/Core/LayoutBlockBox.cpp

@@ -618,7 +618,9 @@ float LayoutBlockBox::GetShrinkToFitWidth() const
 				content_width = Math::Max(content_width, width_value);
 				content_width = Math::Max(content_width, width_value);
 			}
 			}
 
 
-			content_width = LayoutDetails::ClampWidth(content_width, computed, box, block_width);
+			float min_width, max_width;
+			LayoutDetails::GetMinMaxWidth(min_width, max_width, computed, box, block_width);
+			content_width = Math::Clamp(content_width, min_width, max_width);
 		}
 		}
 		else
 		else
 		{
 		{

+ 1 - 1
Source/Core/LayoutBlockBoxSpace.cpp

@@ -100,7 +100,7 @@ float LayoutBlockBoxSpace::PositionBox(float cursor, Element* element)
 
 
 // Determines the appropriate vertical position for an object that is choosing to clear floating elements to the left
 // Determines the appropriate vertical position for an object that is choosing to clear floating elements to the left
 // or right (or both).
 // or right (or both).
-float LayoutBlockBoxSpace::ClearBoxes(float cursor, Style::Clear clear_property)
+float LayoutBlockBoxSpace::ClearBoxes(float cursor, Style::Clear clear_property) const
 {
 {
 	using namespace Style;
 	using namespace Style;
 	// Clear left boxes.
 	// Clear left boxes.

+ 1 - 1
Source/Core/LayoutBlockBoxSpace.h

@@ -73,7 +73,7 @@ public:
 	/// @param[in] cursor The ideal vertical position.
 	/// @param[in] cursor The ideal vertical position.
 	/// @param[in] clear_property The value of the clear property of the clearing object.
 	/// @param[in] clear_property The value of the clear property of the clearing object.
 	/// @return The appropriate vertical position for the clearing object.
 	/// @return The appropriate vertical position for the clearing object.
-	float ClearBoxes(float cursor, Style::Clear clear_property);
+	float ClearBoxes(float cursor, Style::Clear clear_property) const;
 
 
 	/// Returns the top-left corner of the boxes within the space.
 	/// Returns the top-left corner of the boxes within the space.
 	/// @return The space's offset.
 	/// @return The space's offset.

+ 193 - 219
Source/Core/LayoutDetails.cpp

@@ -36,17 +36,14 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
-static inline float BorderWidthToContentWidth(float border_width, const Box& box)
+// Convert width or height of a border box to the width or height of its corresponding content box.
+static inline float BorderSizeToContentSize(float border_size, float border_padding_edges_size)
 {
 {
-	const float border_padding_edges_width = box.GetSizeAcross(Box::HORIZONTAL, Box::BORDER, Box::PADDING);
-	return Math::Max(0.0f, border_width - border_padding_edges_width);
-}
-static inline float BorderHeightToContentHeight(float border_height, const Box& box)
-{
-	const float border_padding_edges_height = box.GetSizeAcross(Box::VERTICAL, Box::BORDER, Box::PADDING);
-	return Math::Max(0.0f, border_height - border_padding_edges_height);
-}
+	if (border_size < 0.0f || border_size == FLT_MAX)
+		return border_size;
 
 
+	return Math::Max(0.0f, border_size - border_padding_edges_size);
+}
 
 
 // Generates the box for an element.
 // Generates the box for an element.
 void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, bool inline_element, float override_shrink_to_fit_width)
 void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, bool inline_element, float override_shrink_to_fit_width)
@@ -71,134 +68,64 @@ void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* eleme
 	box.SetEdge(Box::BORDER, Box::BOTTOM, Math::Max(0.0f, computed.border_bottom_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));
 	box.SetEdge(Box::BORDER, Box::LEFT, Math::Max(0.0f, computed.border_left_width));
 
 
-	// Calculate the size of the content area.
+	// Prepare sizing of the content area.
 	Vector2f content_area(-1, -1);
 	Vector2f content_area(-1, -1);
-	float intrinsic_ratio = -1;
-	bool replaced_element = false;
+	Vector2f min_size = Vector2f(0, 0);
+	Vector2f max_size = Vector2f(FLT_MAX, FLT_MAX);
 
 
-	// 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, intrinsic_ratio))
-	{
-		replaced_element = true;
+	// Intrinsic size for replaced elements.
+	Vector2f intrinsic_size(-1, -1);
+	float intrinsic_ratio = -1;
 
 
-		bool auto_width = false, auto_height = false;
+	const bool replaced_element = element->GetIntrinsicDimensions(intrinsic_size, intrinsic_ratio);
 
 
-		if (computed.width.type == Style::Width::Auto)
-			auto_width = true;
-		else if (computed.box_sizing == Style::BoxSizing::ContentBox)
+	// Calculate the content area and constraints. 'auto' width and height are handled later.
+	// For inline non-replaced elements, width and height are ignored, so we can skip the calculations.
+	if (!inline_element || replaced_element)
+	{
+		if (content_area.x < 0 && computed.width.type != Style::Width::Auto)
 			content_area.x = ResolveValue(computed.width, containing_block.x);
 			content_area.x = ResolveValue(computed.width, containing_block.x);
-		else
-			content_area.x = BorderWidthToContentWidth(ResolveValue(computed.width, containing_block.x), box);
 
 
-		if (computed.height.type == Style::Height::Auto)
-			auto_height = true;
-		else if (computed.box_sizing == Style::BoxSizing::ContentBox)
+		if (content_area.y < 0 && computed.height.type != Style::Width::Auto)
 			content_area.y = ResolveValue(computed.height, containing_block.y);
 			content_area.y = ResolveValue(computed.height, containing_block.y);
-		else
-			content_area.y = BorderHeightToContentHeight(ResolveValue(computed.height, containing_block.y), box);
 
 
-		// Use a fallback size if we still couldn't determine the size.
-		if (content_area.x < 0)
-			content_area.x = 300;
-		if (content_area.y < 0)
-			content_area.y = 150;
-
-		// Resolve the size constraints.
-		float min_width = ResolveValue(computed.min_width, containing_block.x);
-		float max_width = (computed.max_width.value < 0.f ? FLT_MAX : ResolveValue(computed.max_width, containing_block.x));
-		float min_height = ResolveValue(computed.min_height, containing_block.y);
-		float max_height = (computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block.y));
+		min_size = Vector2f(
+			ResolveValue(computed.min_width, containing_block.x),
+			ResolveValue(computed.min_height, containing_block.y)
+		);
+		max_size = Vector2f(
+			(computed.max_width.value < 0.f ? FLT_MAX : ResolveValue(computed.max_width, containing_block.x)),
+			(computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block.y))
+		);
 
 
+		// Adjust sizes for the given box sizing model.
 		if (computed.box_sizing == Style::BoxSizing::BorderBox)
 		if (computed.box_sizing == Style::BoxSizing::BorderBox)
 		{
 		{
-			min_width = BorderWidthToContentWidth(min_width, box);
-			max_width = BorderWidthToContentWidth(max_width, box);
-			min_height = BorderHeightToContentHeight(min_height, box);
-			max_height = BorderHeightToContentHeight(max_height, box);
-		}
+			const float border_padding_width = box.GetSizeAcross(Box::HORIZONTAL, Box::BORDER, Box::PADDING);
+			const float border_padding_height = box.GetSizeAcross(Box::VERTICAL, Box::BORDER, Box::PADDING);
 
 
-		// If we have an intrinsic ratio and one of the dimensions is 'auto', then scale it such that the ratio is preserved.
-		if (intrinsic_ratio > 0)
-		{
-			if (auto_width && !auto_height)
-			{
-				content_area.x = content_area.y * intrinsic_ratio;
-			}
-			else if (auto_height && !auto_width)
-			{
-				content_area.y = content_area.x / intrinsic_ratio;
-			}
-			else if (auto_width && auto_height)
-			{
-				// If both width and height are auto, try to preserve the ratio under the respective min/max constraints.
-				const float w = content_area.x;
-				const float h = content_area.y;
-
-				if ((w < min_width && h > max_height) || (w > max_width && h < min_height))
-				{
-					// Cannot preserve aspect ratio, let it be clamped.
-				}
-				else if (w < min_width && h < min_height)
-				{
-					// Increase the size such that both min-constraints are respected. The non-scaled axis will
-					// be clamped below, preserving the aspect ratio.
-					if (min_width <= min_height * intrinsic_ratio)
-						content_area.x = min_height * intrinsic_ratio;
-					else
-						content_area.y = min_width / intrinsic_ratio;
-				}
-				else if (w > max_width && h > max_height)
-				{
-					// Shrink the size such that both max-constraints are respected. The non-scaled axis will
-					// be clamped below, preserving the aspect ratio.
-					if (max_width <= max_height * intrinsic_ratio)
-						content_area.y = max_width / intrinsic_ratio;
-					else
-						content_area.x = max_height * intrinsic_ratio;
-				}
-				else
-				{
-					// Single constraint violations.
-					if (w < min_width)
-						content_area.y = min_width / intrinsic_ratio;
-					else if (w > max_width)
-						content_area.y = max_width / intrinsic_ratio;
-					else if (h < min_height)
-						content_area.x = min_height * intrinsic_ratio;
-					else if (h > max_height)
-						content_area.x = max_height * intrinsic_ratio;
-				}
-			}
-		}
+			min_size.x = BorderSizeToContentSize(min_size.x, border_padding_width);
+			max_size.x = BorderSizeToContentSize(max_size.x, border_padding_width);
+			content_area.x = BorderSizeToContentSize(content_area.x, border_padding_width);
 
 
-		content_area.x = Math::Clamp(content_area.x, min_width, max_width);
-		content_area.y = Math::Clamp(content_area.y, min_height, max_height);
-	}
+			min_size.y = BorderSizeToContentSize(min_size.y, border_padding_height);
+			max_size.y = BorderSizeToContentSize(max_size.y, border_padding_height);
+			content_area.y = BorderSizeToContentSize(content_area.y, border_padding_height);
+		}
 
 
-	// 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 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);
+		if (content_area.x >= 0)
+			content_area.x = Math::Clamp(content_area.x, min_size.x, max_size.x);
+		if (content_area.y >= 0)
+			content_area.y = Math::Clamp(content_area.y, min_size.y, max_size.y);
 
 
-		// 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));
+		if (replaced_element)
+			content_area = CalculateSizeForReplacedElement(content_area, min_size, max_size, intrinsic_size, intrinsic_ratio);
 	}
 	}
 
 
-	// 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, replaced_element, override_shrink_to_fit_width);
-		BuildBoxHeight(box, computed, containing_block.y);
-	}
+	box.SetContent(content_area);
+
+	// Evaluate the margins, and width and height if they are auto.
+	BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element, inline_element, replaced_element, override_shrink_to_fit_width);
 }
 }
 
 
 // Generates the box for an element placed in a block box.
 // Generates the box for an element placed in a block box.
@@ -209,52 +136,44 @@ void LayoutDetails::BuildBox(Box& box, float& min_height, float& max_height, Lay
 	BuildBox(box, containing_block, element, inline_element, override_shrink_to_fit_width);
 	BuildBox(box, containing_block, element, inline_element, override_shrink_to_fit_width);
 
 
 	if (element)
 	if (element)
-		GetMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
+		GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
 	else
 	else
 		min_height = max_height = box.GetSize().y;
 		min_height = max_height = box.GetSize().y;
 }
 }
 
 
-float LayoutDetails::ClampWidth(float width, const ComputedValues& computed, const Box& box, float containing_block_width)
+void LayoutDetails::GetMinMaxWidth(float& min_width, float& max_width, const ComputedValues& computed, const Box& box, 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));
+	min_width = ResolveValue(computed.min_width, containing_block_width);
+	max_width = (computed.max_width.value < 0.f ? FLT_MAX : ResolveValue(computed.max_width, containing_block_width));
 
 
 	if (computed.box_sizing == Style::BoxSizing::BorderBox)
 	if (computed.box_sizing == Style::BoxSizing::BorderBox)
 	{
 	{
-		min_width = BorderWidthToContentWidth(min_width, box);
-		max_width = BorderWidthToContentWidth(max_width, box);
+		const float border_padding_width = box.GetSizeAcross(Box::HORIZONTAL, Box::BORDER, Box::PADDING);
+		min_width = BorderSizeToContentSize(min_width, border_padding_width);
+		max_width = BorderSizeToContentSize(max_width, border_padding_width);
 	}
 	}
-
-	return Math::Clamp(width, min_width, max_width);
 }
 }
 
 
-float LayoutDetails::ClampHeight(float height, const ComputedValues& computed, const Box& box, float containing_block_height)
+
+void LayoutDetails::GetMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, 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));
+	min_height = ResolveValue(computed.min_height, containing_block_height);
+	max_height = (computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block_height));
 
 
 	if (computed.box_sizing == Style::BoxSizing::BorderBox)
 	if (computed.box_sizing == Style::BoxSizing::BorderBox)
 	{
 	{
-		min_height = BorderHeightToContentHeight(min_height, box);
-		max_height = BorderHeightToContentHeight(max_height, box);
+		const float border_padding_height = box.GetSizeAcross(Box::VERTICAL, Box::BORDER, Box::PADDING);
+		min_height = BorderSizeToContentSize(min_height, border_padding_height);
+		max_height = BorderSizeToContentSize(max_height, border_padding_height);
 	}
 	}
-
-	return Math::Clamp(height, min_height, max_height);
 }
 }
 
 
-void LayoutDetails::GetMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, float containing_block_height)
+void LayoutDetails::GetDefiniteMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, float containing_block_height)
 {
 {
 	const float box_height = box.GetSize().y;
 	const float box_height = box.GetSize().y;
 	if (box_height < 0)
 	if (box_height < 0)
 	{
 	{
-		min_height = ResolveValue(computed.min_height, containing_block_height);
-		max_height = (computed.max_height.value < 0.f ? FLT_MAX : ResolveValue(computed.max_height, containing_block_height));
-
-		if (computed.box_sizing == Style::BoxSizing::BorderBox)
-		{
-			min_height = BorderHeightToContentHeight(min_height, box);
-			max_height = BorderHeightToContentHeight(max_height, box);
-		}
+		GetMinMaxHeight(min_height, max_height, computed, box, containing_block_height);
 	}
 	}
 	else
 	else
 	{
 	{
@@ -294,6 +213,27 @@ Vector2f LayoutDetails::GetContainingBlock(const LayoutBlockBox* containing_box)
 }
 }
 
 
 
 
+void LayoutDetails::BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f max_size, Vector2f containing_block, Element* element, bool inline_element, bool replaced_element, float override_shrink_to_fit_width)
+{
+	const ComputedValues& computed = element->GetComputedValues();
+
+	if (inline_element)
+	{
+		// For inline elements, their calculations are straightforward. No worrying about auto margins and dimensions, etc.
+		// 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));
+	}
+	else
+	{
+		// The element is block, so we need to run the box through the ringer to potentially evaluate auto margins and dimensions.
+		BuildBoxWidth(box, computed, min_size.x, max_size.x, containing_block, element, replaced_element, override_shrink_to_fit_width);
+		BuildBoxHeight(box, computed, min_size.y, max_size.y, containing_block.y);
+	}
+}
+
 float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_block)
 float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_block)
 {
 {
 	RMLUI_ASSERT(element);
 	RMLUI_ASSERT(element);
@@ -301,7 +241,7 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b
 	Box box;
 	Box box;
 	float min_height, max_height;
 	float min_height, max_height;
 	LayoutDetails::BuildBox(box, containing_block, element, false, containing_block.x);
 	LayoutDetails::BuildBox(box, containing_block, element, false, containing_block.x);
-	LayoutDetails::GetMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
+	LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
 
 
 	// First we need to format the element, then we get the shrink-to-fit width based on the largest line or box.
 	// First we need to format the element, then we get the shrink-to-fit width based on the largest line or box.
 	LayoutBlockBox containing_block_box(nullptr, nullptr, Box(containing_block), 0.0f, FLT_MAX);
 	LayoutBlockBox containing_block_box(nullptr, nullptr, Box(containing_block), 0.0f, FLT_MAX);
@@ -326,44 +266,122 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b
 	return Math::Min(containing_block.x, block_context_box->GetShrinkToFitWidth());
 	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 replaced_element, float override_shrink_to_fit_width)
+Vector2f LayoutDetails::CalculateSizeForReplacedElement(const Vector2f specified_content_size, const Vector2f min_size, const Vector2f max_size, const Vector2f intrinsic_size, const float intrinsic_ratio)
 {
 {
-	RMLUI_ZoneScoped;
+	// Start with the element's specified width and height. If any of them are auto, use the element's intrinsic
+	// dimensions and ratio to find a suitable content size.
+	Vector2f content_size = specified_content_size;
 
 
-	Vector2f content_area = box.GetSize();
+	const bool auto_width = (content_size.x < 0);
+	const bool auto_height = (content_size.y < 0);
+
+	if (auto_width)
+		content_size.x = intrinsic_size.x;
+
+	if (auto_height)
+		content_size.y = intrinsic_size.y;
 
 
-	// Determine if the element has an automatic width, and if not calculate it.
-	bool width_auto = false;
-	if (content_area.x < 0)
+	// Use a fallback size if we still couldn't determine the size.
+	if (content_size.x < 0)
+		content_size.x = 300;
+	if (content_size.y < 0)
+		content_size.y = 150;
+
+	// Resolve the size constraints.
+	const float min_width = min_size.x;
+	const float max_width = max_size.x;
+	const float min_height = min_size.y;
+	const float max_height = max_size.y;
+
+	// If we have an intrinsic ratio and one of the dimensions is 'auto', then scale it such that the ratio is preserved.
+	if (intrinsic_ratio > 0)
 	{
 	{
-		if (computed.width.type == Style::Width::Auto)
-			width_auto = true;
-		else if (computed.box_sizing == Style::BoxSizing::ContentBox)
-			content_area.x = ResolveValue(computed.width, containing_block.x);
-		else
-			content_area.x = BorderWidthToContentWidth(ResolveValue(computed.width, containing_block.x), box);
+		if (auto_width && !auto_height)
+		{
+			content_size.x = content_size.y * intrinsic_ratio;
+		}
+		else if (auto_height && !auto_width)
+		{
+			content_size.y = content_size.x / intrinsic_ratio;
+		}
+		else if (auto_width && auto_height)
+		{
+			// If both width and height are auto, try to preserve the ratio under the respective min/max constraints.
+			const float w = content_size.x;
+			const float h = content_size.y;
+
+			if ((w < min_width && h > max_height) || (w > max_width && h < min_height))
+			{
+				// Cannot preserve aspect ratio, let it be clamped.
+			}
+			else if (w < min_width && h < min_height)
+			{
+				// Increase the size such that both min-constraints are respected. The non-scaled axis will
+				// be clamped below, preserving the aspect ratio.
+				if (min_width <= min_height * intrinsic_ratio)
+					content_size.x = min_height * intrinsic_ratio;
+				else
+					content_size.y = min_width / intrinsic_ratio;
+			}
+			else if (w > max_width && h > max_height)
+			{
+				// Shrink the size such that both max-constraints are respected. The non-scaled axis will
+				// be clamped below, preserving the aspect ratio.
+				if (max_width <= max_height * intrinsic_ratio)
+					content_size.y = max_width / intrinsic_ratio;
+				else
+					content_size.x = max_height * intrinsic_ratio;
+			}
+			else
+			{
+				// Single constraint violations.
+				if (w < min_width)
+					content_size.y = min_width / intrinsic_ratio;
+				else if (w > max_width)
+					content_size.y = max_width / intrinsic_ratio;
+				else if (h < min_height)
+					content_size.x = min_height * intrinsic_ratio;
+				else if (h > max_height)
+					content_size.x = max_height * intrinsic_ratio;
+			}
+		}
 	}
 	}
 
 
+	content_size.x = Math::Clamp(content_size.x, min_width, max_width);
+	content_size.y = Math::Clamp(content_size.y, min_height, max_height);
+
+	return content_size;
+}
+
+// Builds the block-specific width and horizontal margins of a Box.
+void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, float min_width, float max_width, Vector2f containing_block, Element* element, bool replaced_element, float override_shrink_to_fit_width)
+{
+	RMLUI_ZoneScoped;
+
+	Vector2f content_area = box.GetSize();
+
 	// Determine if the element has automatic margins.
 	// Determine if the element has automatic margins.
 	bool margins_auto[2];
 	bool margins_auto[2];
 	int num_auto_margins = 0;
 	int num_auto_margins = 0;
 
 
 	for (int i = 0; i < 2; ++i)
 	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)
+		const Style::Margin& margin_value = (i == 0 ? computed.margin_left : computed.margin_right);
+		if (margin_value.type == Style::Margin::Auto)
 		{
 		{
 			margins_auto[i] = true;
 			margins_auto[i] = true;
 			num_auto_margins++;
 			num_auto_margins++;
+			box.SetEdge(Box::MARGIN, i == 0 ? Box::LEFT : Box::RIGHT, 0);
 		}
 		}
 		else
 		else
 		{
 		{
 			margins_auto[i] = false;
 			margins_auto[i] = false;
-			box.SetEdge(Box::MARGIN, i == 0 ? Box::LEFT : Box::RIGHT, ResolveValue(*margin_value, containing_block.x));
+			box.SetEdge(Box::MARGIN, i == 0 ? Box::LEFT : Box::RIGHT, ResolveValue(margin_value, containing_block.x));
 		}
 		}
 	}
 	}
 
 
+	const bool width_auto = (content_area.x < 0);
+
 	// If the width is set to auto, we need to calculate the width
 	// If the width is set to auto, we need to calculate the width
 	if (width_auto)
 	if (width_auto)
 	{
 	{
@@ -388,11 +406,6 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, Vect
 				right = ResolveValue(computed.right, containing_block.x);
 				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 && override_shrink_to_fit_width < 0)
 		if (shrink_to_fit && override_shrink_to_fit_width < 0)
 		{
 		{
 			content_area.x = GetShrinkToFitWidth(element, containing_block);
 			content_area.x = GetShrinkToFitWidth(element, containing_block);
@@ -415,9 +428,7 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, Vect
 	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
 	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
 	else if (num_auto_margins > 0)
 	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;
+		const float margin = (containing_block.x - box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN)) / float(num_auto_margins);
 
 
 		if (margins_auto[0])
 		if (margins_auto[0])
 			box.SetEdge(Box::MARGIN, Box::LEFT, margin);
 			box.SetEdge(Box::MARGIN, Box::LEFT, margin);
@@ -427,74 +438,51 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, Vect
 
 
 	// Clamp the calculated width; if the width is changed by the clamp, then the margins need to be recalculated if
 	// 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.
 	// they were set to auto.
-	float clamped_width = ClampWidth(content_area.x, computed, box, containing_block.x);
+	const float clamped_width = Math::Clamp(content_area.x, min_width, max_width);
 	if (clamped_width != content_area.x)
 	if (clamped_width != content_area.x)
 	{
 	{
 		content_area.x = clamped_width;
 		content_area.x = clamped_width;
 		box.SetContent(content_area);
 		box.SetContent(content_area);
 
 
 		if (num_auto_margins > 0)
 		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, replaced_element, override_shrink_to_fit_width);
-		}
+			BuildBoxWidth(box, computed, min_width, max_width, containing_block, element, replaced_element, override_shrink_to_fit_width);
 	}
 	}
 	else
 	else
 		box.SetContent(content_area);
 		box.SetContent(content_area);
 }
 }
 
 
 // Builds the block-specific height and vertical margins of a Box.
 // Builds the block-specific height and vertical margins of a Box.
-void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, float containing_block_height)
+void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, float min_height, float max_height, float containing_block_height)
 {
 {
 	RMLUI_ZoneScoped;
 	RMLUI_ZoneScoped;
 
 
 	Vector2f content_area = box.GetSize();
 	Vector2f content_area = box.GetSize();
 
 
-	// Determine if the element has an automatic height, and if not calculate it.
-	bool height_auto = false;
-	if (content_area.y < 0)
-	{
-		if (computed.height.type == Style::Width::Auto)
-			height_auto = true;
-		else if (computed.box_sizing == Style::BoxSizing::ContentBox)
-			content_area.y = ResolveValue(computed.height, containing_block_height);
-		else
-			content_area.y = BorderHeightToContentHeight(ResolveValue(computed.height, containing_block_height), box);
-	}
-
 	// Determine if the element has automatic margins.
 	// Determine if the element has automatic margins.
 	bool margins_auto[2];
 	bool margins_auto[2];
 	int num_auto_margins = 0;
 	int num_auto_margins = 0;
 
 
 	for (int i = 0; i < 2; ++i)
 	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)
+		const Style::Margin& margin_value = (i == 0 ? computed.margin_top : computed.margin_bottom);
+		if (margin_value.type == Style::Margin::Auto)
 		{
 		{
 			margins_auto[i] = true;
 			margins_auto[i] = true;
 			num_auto_margins++;
 			num_auto_margins++;
+			box.SetEdge(Box::MARGIN, i == 0 ? Box::TOP : Box::BOTTOM, 0);
 		}
 		}
 		else
 		else
 		{
 		{
 			margins_auto[i] = false;
 			margins_auto[i] = false;
-			box.SetEdge(Box::MARGIN, i == 0 ? Box::TOP : Box::BOTTOM, ResolveValue(*margin_value, containing_block_height));
+			box.SetEdge(Box::MARGIN, i == 0 ? Box::TOP : Box::BOTTOM, ResolveValue(margin_value, containing_block_height));
 		}
 		}
 	}
 	}
 
 
+	const bool height_auto = (content_area.y < 0);
+
 	// If the height is set to auto, we need to calculate the height
 	// If the height is set to auto, we need to calculate the height
 	if (height_auto)
 	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.
 		// If the height is set to auto for a box in normal flow, the height is set to -1.
 		content_area.y = -1;
 		content_area.y = -1;
 
 
@@ -521,15 +509,9 @@ void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, flo
 	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
 	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
 	else if (num_auto_margins > 0)
 	else if (num_auto_margins > 0)
 	{
 	{
-		float margin;
+		float margin = 0;
 		if (content_area.y >= 0)
 		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;
+			margin = (containing_block_height - box.GetSizeAcross(Box::VERTICAL, Box::MARGIN)) / num_auto_margins;
 
 
 		if (margins_auto[0])
 		if (margins_auto[0])
 			box.SetEdge(Box::MARGIN, Box::TOP, margin);
 			box.SetEdge(Box::MARGIN, Box::TOP, margin);
@@ -541,22 +523,14 @@ void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, flo
 	{
 	{
 		// Clamp the calculated height; if the height is changed by the clamp, then the margins need to be recalculated if
 		// 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.
 		// they were set to auto.
-		float clamped_height = ClampHeight(content_area.y, computed, box, containing_block_height);
+		float clamped_height = Math::Clamp(content_area.y, min_height, max_height);
 		if (clamped_height != content_area.y)
 		if (clamped_height != content_area.y)
 		{
 		{
 			content_area.y = clamped_height;
 			content_area.y = clamped_height;
 			box.SetContent(content_area);
 			box.SetContent(content_area);
 
 
 			if (num_auto_margins > 0)
 			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);
-			}
+				BuildBoxHeight(box, computed, min_height, max_height, containing_block_height);
 
 
 			return;
 			return;
 		}
 		}

+ 31 - 11
Source/Core/LayoutDetails.h

@@ -61,35 +61,55 @@ public:
 	/// @param[in] override_shrink_to_fit_width Provide a fixed shrink-to-fit width instead of formatting the element when its properties allow shrinking.
 	/// @param[in] override_shrink_to_fit_width Provide a fixed shrink-to-fit width instead of formatting the element when its properties allow shrinking.
 	static void BuildBox(Box& box, float& min_height, float& max_height, LayoutBlockBox* containing_box, Element* element, bool inline_element, float override_shrink_to_fit_width = -1);
 	static void BuildBox(Box& box, float& min_height, float& max_height, LayoutBlockBox* containing_box, Element* element, bool inline_element, float override_shrink_to_fit_width = -1);
 
 
-	// Returns the clamped width based on the min-/max-width and box-sizing computed values.
-	static float ClampWidth(float width, const ComputedValues& computed, const Box& box, float containing_block_width);
-	// Returns the clamped height based on the min-/max-height and box-sizing computed values.
-	static float ClampHeight(float height, const ComputedValues& computed, const Box& box, float containing_block_height);
+	// Retrieves the minimum and maximum width from an element's computed values.
+	static void GetMinMaxWidth(float& min_width, float& max_width, const ComputedValues& computed, const Box& box, float containing_block_width);
 
 
-	// Retrieves the minimum and maximum height from on an element's computed values if the box has an indefinite height.
+	// Retrieves the minimum and maximum height from an element's computed values.
 	static void GetMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, float containing_block_height);
 	static void GetMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, float containing_block_height);
 
 
+	// Retrieves the minimum and maximum height, set to the box's content height if it is definite (>= 0), otherwise retrieves the minimum and maximum heights from an element's computed values.
+	static void GetDefiniteMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box, float containing_block_height);
+
 	/// Returns the fully-resolved, fixed-width and -height containing block from a block box.
 	/// Returns the fully-resolved, fixed-width and -height containing block from a block box.
 	/// @param[in] containing_box The leaf 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.
 	/// @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 Vector2f GetContainingBlock(const LayoutBlockBox* containing_box);
 
 
+	/// Builds margins of a Box, and resolves any auto width or height for non-inline elements. The height may be left unresolved if it depends on the element's children.
+	/// @param[in,out] box The box to generate. The padding and borders must be set on the box already. The content area is used instead of the width and height properties, and -1 means auto width/height.
+	/// @param[in] min_size The element's minimum width and height.
+	/// @param[in] max_size The element's maximum width and height.
+	/// @param[in] containing_block The size of the containing block.
+	/// @param[in] element The element the box is being generated for.
+	/// @param[in] inline_element True when the element is an inline element.
+	/// @param[in] replaced_element True when the element is a replaced element.
+	/// @param[in] override_shrink_to_fit_width Provide a fixed shrink-to-fit width instead of formatting the element when its properties allow shrinking.
+	static void BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f max_size, Vector2f containing_block, Element* element, bool inline_element, bool replaced_element, float override_shrink_to_fit_width = -1);
+
 private:
 private:
 	/// Formats the element and returns the width of its contents.
 	/// Formats the element and returns the width of its contents.
 	static float GetShrinkToFitWidth(Element* element, Vector2f containing_block);
 	static float GetShrinkToFitWidth(Element* element, Vector2f containing_block);
 
 
+	/// Calculates and returns the content size for replaced elements.
+	static Vector2f CalculateSizeForReplacedElement(Vector2f specified_content_size, Vector2f min_size, Vector2f max_size, Vector2f intrinsic_size, float intrinsic_ratio);
+
 	/// Builds the block-specific width and horizontal margins of a 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,out] box The box to generate. The padding and borders must be set on the box already. The content area is used instead of the width property, and -1 means auto width.
+	/// @param[in] computed The computed values of the element the box is being generated for.
+	/// @param[in] min_width The minimum content width of the element.
+	/// @param[in] max_width The maximum content width of the element.
+	/// @param[in] containing_block The size of the containing block.
 	/// @param[in] element The element the box is being generated for.
 	/// @param[in] element The element the box is being generated for.
-	/// @param[in] containing_block_width The width of the containing block.
 	/// @param[in] replaced_element True when the element is a replaced element.
 	/// @param[in] replaced_element True when the element is a replaced element.
 	/// @param[in] override_shrink_to_fit_width Provide a fixed shrink-to-fit width instead of formatting the element when its properties allow shrinking.
 	/// @param[in] override_shrink_to_fit_width Provide a fixed shrink-to-fit width instead of formatting the element when its properties allow shrinking.
-	static void BuildBoxWidth(Box& box, const ComputedValues& computed, Vector2f containing_block_width, Element* element, bool replaced_element, float override_shrink_to_fit_width = -1);
+	static void BuildBoxWidth(Box& box, const ComputedValues& computed, float min_width, float max_width, Vector2f containing_block, Element* element, bool replaced_element, float override_shrink_to_fit_width = -1);
 	/// Builds the block-specific height and vertical margins of a Box.
 	/// 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,out] box The box to generate. The padding and borders must be set on the box already. The content area is used instead of the height property, and -1 means auto height.
+	/// @param[in] computed The computed values of the element the box is being generated for.
+	/// @param[in] min_height The minimum content height of the element.
+	/// @param[in] max_height The maximum content height of the element.
 	/// @param[in] containing_block_height The height of the containing block.
 	/// @param[in] containing_block_height The height of the containing block.
-	static void BuildBoxHeight(Box& box, const ComputedValues& computed, float containing_block_height);
+	static void BuildBoxHeight(Box& box, const ComputedValues& computed, float min_height, float max_height, float containing_block_height);
 };
 };
 
 
 } // namespace Rml
 } // namespace Rml

+ 1 - 1
Source/Core/LayoutEngine.cpp

@@ -69,7 +69,7 @@ void LayoutEngine::FormatElement(Element* element, Vector2f containing_block, co
 		LayoutDetails::BuildBox(box, containing_block, element, false);
 		LayoutDetails::BuildBox(box, containing_block, element, false);
 
 
 	float min_height, max_height;
 	float min_height, max_height;
-	LayoutDetails::GetMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
+	LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
 
 
 	LayoutBlockBox* block_context_box = containing_block_box.AddBlockElement(element, box, min_height, max_height);
 	LayoutBlockBox* block_context_box = containing_block_box.AddBlockElement(element, box, min_height, max_height);