Browse Source

Implement flexbox layouting

- WIP Start working on flexbox layouting
- WIP basic flex layouting
- Flexbox algorithm, resolving flexible lengths of items
- Flexbox cross sizing
- Fix flexbox cross size
- Flexbox main axis alignment
- Flexbox cross-axis alignment. With that, flexboxes are feature complete but needs cleanup, much more testing, and lots of fixes.
- Flexbox warning instead of error
- Add visual test for 'flex-direction'
- Support for reverse flow direction and reverse wrap
- Flexbox wrap on exceeded main max size
- Fix auto margins on flex items along cross axis, and wrap reverse should also reverse cross offset within a line
- Update and add new flexbox visual test
- Fix flexbox reverse offseting
- Support absolutely positioned items within flex container
- Update flexbox visual test
- Fix flexbox overflow sizing
- Update flexbox visual tests
- Flexbox baseline alignment
- Add flexbox scrollbar sample
- Add support for scrollbars in flex container
- Fix some compiler warnings
- Update and add new flexbox visual tests
- Cleanup flexbox layout and use common methods with table
- Flex layout use formatting context mode for LayoutDetails::BuildBox, fix final todos from flexbox layout
- Format flex layout
- Fix pixel rounding issues in flexbox layout
- Small fix for flex layout pixel rounding
- Flex layout avoid 0/0 float math
- Update flex visual sample
- Flex layout: Set a definite width when finding the vertical content size.
- Update flex sample with animation
- Add visual test for flex container height constraints
- Flex container now respects min-/max- constraints
Michael Ragazzon 4 years ago
parent
commit
7824726c51

+ 2 - 0
CMake/FileList.cmake

@@ -64,6 +64,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBoxSpace.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBoxSpace.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutDetails.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutDetails.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutEngine.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutEngine.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutFlex.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.h
@@ -339,6 +340,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBoxSpace.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBoxSpace.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutDetails.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutDetails.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutEngine.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutEngine.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutFlex.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.cpp

+ 1 - 1
Source/Core/Element.cpp

@@ -2260,7 +2260,7 @@ void Element::BuildStackingContext(ElementList* new_stacking_context)
 				ordered_child.order = RenderOrder::Positioned;
 				ordered_child.order = RenderOrder::Positioned;
 			else if (child->GetFloat() != Style::Float::None)
 			else if (child->GetFloat() != Style::Float::None)
 				ordered_child.order = RenderOrder::Floating;
 				ordered_child.order = RenderOrder::Floating;
-			else if (child_display == Style::Display::Block || child_display == Style::Display::Table)
+			else if (child_display == Style::Display::Block || child_display == Style::Display::Table || child_display == Style::Display::Flex)
 				ordered_child.order = RenderOrder::Block;
 				ordered_child.order = RenderOrder::Block;
 			else
 			else
 				ordered_child.order = RenderOrder::Inline;
 				ordered_child.order = RenderOrder::Inline;

+ 2 - 0
Source/Core/LayoutBlockBox.cpp

@@ -772,6 +772,8 @@ bool LayoutBlockBox::CatchVerticalOverflow(float cursor)
 			box_cursor = 0;
 			box_cursor = 0;
 			interrupted_chain = nullptr;
 			interrupted_chain = nullptr;
 
 
+			inner_content_size = Vector2f(0);
+
 			return false;
 			return false;
 		}
 		}
 	}
 	}

+ 57 - 2
Source/Core/LayoutEngine.cpp

@@ -29,6 +29,7 @@
 #include "LayoutEngine.h"
 #include "LayoutEngine.h"
 #include "LayoutBlockBoxSpace.h"
 #include "LayoutBlockBoxSpace.h"
 #include "LayoutDetails.h"
 #include "LayoutDetails.h"
+#include "LayoutFlex.h"
 #include "LayoutInlineBoxText.h"
 #include "LayoutInlineBoxText.h"
 #include "LayoutTable.h"
 #include "LayoutTable.h"
 #include "Pool.h"
 #include "Pool.h"
@@ -311,9 +312,63 @@ bool LayoutEngine::FormatElementInlineBlock(LayoutBlockBox* block_context_box, E
 	return true;
 	return true;
 }
 }
 
 
-bool LayoutEngine::FormatElementFlex(LayoutBlockBox* /*block_context_box*/, Element* /*element*/)
+bool LayoutEngine::FormatElementFlex(LayoutBlockBox* block_context_box, Element* element)
 {
 {
-	// TODO
+	const ComputedValues& computed = element->GetComputedValues();
+	const Vector2f containing_block = LayoutDetails::GetContainingBlock(block_context_box);
+	RMLUI_ASSERT(containing_block.x >= 0.f);
+
+	// Build the initial box as specified by the flex's style, as if it was a normal block element.
+	Box box;
+	LayoutDetails::BuildBox(box, containing_block, element, BoxContext::Block);
+
+	Vector2f min_size, max_size;
+	LayoutDetails::GetMinMaxWidth(min_size.x, max_size.x, computed, box, containing_block.x);
+	LayoutDetails::GetMinMaxHeight(min_size.y, max_size.y, computed, box, containing_block.y);
+
+	// Add the flex container element as if it was a normal block element.
+	LayoutBlockBox* flex_block_context_box = block_context_box->AddBlockElement(element, box, min_size.y, max_size.y);
+	if (!flex_block_context_box)
+		return false;
+
+	// Format the flexbox and all its children.
+	ElementList absolutely_positioned_elements;
+	Vector2f formatted_content_size, content_overflow_size;
+	LayoutFlex::Format(
+		box, min_size, max_size, containing_block, element, formatted_content_size, content_overflow_size, absolutely_positioned_elements);
+
+	// Set the box content size to match the one determined by the formatting procedure.
+	flex_block_context_box->GetBox().SetContent(formatted_content_size);
+	// Set the inner content size so that any overflow can be caught.
+	flex_block_context_box->ExtendInnerContentSize(content_overflow_size);
+
+	// Finally, add any absolutely positioned flex children.
+	for (Element* abs_element : absolutely_positioned_elements)
+		flex_block_context_box->AddAbsoluteElement(abs_element);
+
+	// Close the block box, this may result in scrollbars being added to ourself or our parent.
+	const auto close_result = flex_block_context_box->Close();
+	if (close_result == LayoutBlockBox::LAYOUT_PARENT)
+	{
+		// Scollbars added to parent, bail out to reformat all its children.
+		return false;
+	}
+	else if (close_result == LayoutBlockBox::LAYOUT_SELF)
+	{
+		// Scrollbars added to flex container, it needs to be formatted again to account for changed width or height.
+		absolutely_positioned_elements.clear();
+
+		LayoutFlex::Format(
+			box, min_size, max_size, containing_block, element, formatted_content_size, content_overflow_size, absolutely_positioned_elements);
+
+		flex_block_context_box->GetBox().SetContent(formatted_content_size);
+		flex_block_context_box->ExtendInnerContentSize(content_overflow_size);
+
+		if (flex_block_context_box->Close() == LayoutBlockBox::LAYOUT_PARENT)
+			return false;
+	}
+
+	element->OnLayout();
 
 
 	return true;
 	return true;
 }
 }

+ 848 - 0
Source/Core/LayoutFlex.cpp

@@ -0,0 +1,848 @@
+/*
+ * 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 "LayoutFlex.h"
+#include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/ElementScroll.h"
+#include "../../Include/RmlUi/Core/Types.h"
+#include "LayoutDetails.h"
+#include "LayoutEngine.h"
+#include <algorithm>
+#include <float.h>
+#include <numeric>
+
+namespace Rml {
+
+void LayoutFlex::Format(const Box& box, const Vector2f min_size, const Vector2f max_size, const Vector2f flex_containing_block, Element* element_flex,
+	Vector2f& out_formatted_content_size, Vector2f& out_content_overflow_size, ElementList& out_absolutely_positioned_elements)
+{
+	ElementScroll* element_scroll = element_flex->GetElementScroll();
+	const Vector2f scrollbar_size = {element_scroll->GetScrollbarSize(ElementScroll::VERTICAL),
+		element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL)};
+
+	Vector2f flex_content_offset = box.GetPosition();
+
+	const Vector2f box_content_size = box.GetSize();
+	const bool auto_height = (box_content_size.y < 0.0f);
+
+	Vector2f flex_available_content_size = Math::Max(box_content_size - scrollbar_size, Vector2f(0.f));
+	Vector2f flex_content_containing_block = flex_available_content_size;
+
+	if (auto_height)
+	{
+		flex_available_content_size.y = -1.f; // Negative means infinite space
+		flex_content_containing_block.y = flex_containing_block.y;
+	}
+
+	Math::SnapToPixelGrid(flex_content_offset, flex_available_content_size);
+
+	// Construct the layout object and format the table.
+	LayoutFlex layout_flex(element_flex, flex_available_content_size, flex_content_containing_block, flex_content_offset, min_size, max_size,
+		out_absolutely_positioned_elements);
+
+	layout_flex.Format();
+
+	// Output the size of the formatted flexbox. The width is determined as a normal block box so we don't need to change that.
+	out_formatted_content_size = box_content_size;
+	if (auto_height)
+		out_formatted_content_size = Vector2f(box_content_size.x, layout_flex.flex_resulting_content_size.y + scrollbar_size.y);
+
+	out_content_overflow_size = layout_flex.flex_content_overflow_size;
+}
+
+LayoutFlex::LayoutFlex(Element* element_flex, Vector2f flex_available_content_size, Vector2f flex_content_containing_block,
+	Vector2f flex_content_offset, Vector2f flex_min_size, Vector2f flex_max_size, ElementList& absolutely_positioned_elements) :
+	element_flex(element_flex),
+	flex_available_content_size(flex_available_content_size), flex_content_containing_block(flex_content_containing_block),
+	flex_content_offset(flex_content_offset), flex_min_size(flex_min_size), flex_max_size(flex_max_size),
+	absolutely_positioned_elements(absolutely_positioned_elements)
+{}
+
+struct FlexItem {
+	// In the following, suffix '_a' means flex start edge while '_b' means flex end edge.
+	struct Size {
+		bool auto_margin_a, auto_margin_b;
+		bool auto_size;
+		float margin_a, margin_b;
+		float sum_edges_a;        // Start edge: margin (non-auto) + border + padding
+		float sum_edges;          // Inner->outer size
+		float min_size, max_size; // Inner size
+	};
+
+	Element* element;
+	Box box;
+
+	// Filled during the build step.
+	Size main;
+	Size cross;
+	float flex_shrink_factor;
+	float flex_grow_factor;
+	Style::AlignSelf align_self; // 'Auto' is replaced by container's 'align-items' value
+
+	float inner_flex_base_size;   // Inner size
+	float flex_base_size;         // Outer size
+	float hypothetical_main_size; // Outer size
+
+	// Used for resolving flexible length
+	enum class Violation : std::uint8_t { None = 0, Min, Max };
+	bool frozen;
+	Violation violation;
+	float target_main_size; // Outer size
+	float used_main_size;   // Outer size (without auto margins)
+	float main_auto_margin_size_a, main_auto_margin_size_b;
+	float main_offset;
+
+	// Used for resolving cross size
+	float hypothetical_cross_size; // Outer size
+	float used_cross_size;         // Outer size
+	float cross_offset;            // Offset within line
+	float cross_baseline_top;      // Only used for baseline cross alignment
+};
+
+struct FlexLine {
+	FlexLine(Vector<FlexItem>&& items) : items(std::move(items)) {}
+	Vector<FlexItem> items;
+	float accumulated_hypothetical_main_size = 0;
+	float cross_size = 0; // Excludes line spacing
+	float cross_spacing_a = 0, cross_spacing_b = 0;
+	float cross_offset = 0;
+};
+
+struct FlexContainer {
+	Vector<FlexLine> lines;
+};
+
+static void GetItemSizing(FlexItem::Size& destination, const ComputedAxisSize& computed_size, const float base_value, const bool direction_reverse)
+{
+	float margin_a, margin_b, padding_border_a, padding_border_b;
+	LayoutDetails::GetEdgeSizes(margin_a, margin_b, padding_border_a, padding_border_b, computed_size, base_value);
+
+	const float padding_border = padding_border_a + padding_border_b;
+	const float margin = margin_a + margin_b;
+
+	destination.auto_margin_a = (computed_size.margin_a.type == Style::Margin::Auto);
+	destination.auto_margin_b = (computed_size.margin_b.type == Style::Margin::Auto);
+
+	destination.auto_size = (computed_size.size.type == Style::LengthPercentageAuto::Auto);
+
+	destination.margin_a = margin_a;
+	destination.margin_b = margin_b;
+	destination.sum_edges = padding_border + margin;
+	destination.sum_edges_a = (direction_reverse ? padding_border_b + margin_b : padding_border_a + margin_a);
+
+	destination.min_size = ResolveValue(computed_size.min_size, base_value);
+	destination.max_size = (computed_size.max_size.value < 0.f ? FLT_MAX : ResolveValue(computed_size.max_size, base_value));
+
+	if (computed_size.box_sizing == Style::BoxSizing::BorderBox)
+	{
+		destination.min_size = Math::Max(0.0f, destination.min_size - padding_border);
+		if (destination.max_size < FLT_MAX)
+			destination.max_size = Math::Max(0.0f, destination.max_size - padding_border);
+	}
+
+	if (direction_reverse)
+	{
+		std::swap(destination.auto_margin_a, destination.auto_margin_b);
+		std::swap(destination.margin_a, destination.margin_b);
+	}
+}
+
+void LayoutFlex::Format()
+{
+	// The following procedure is generally based on the CSS flexible box layout algorithm.
+	// For details, see https://drafts.csswg.org/css-flexbox/#layout-algorithm
+
+	const ComputedValues& computed_flex = element_flex->GetComputedValues();
+	const Style::FlexDirection direction = computed_flex.flex_direction;
+
+	const bool main_axis_horizontal = (direction == Style::FlexDirection::Row || direction == Style::FlexDirection::RowReverse);
+	const bool direction_reverse = (direction == Style::FlexDirection::RowReverse || direction == Style::FlexDirection::ColumnReverse);
+	const bool flex_single_line = (computed_flex.flex_wrap == Style::FlexWrap::Nowrap);
+	const bool wrap_reverse = (computed_flex.flex_wrap == Style::FlexWrap::WrapReverse);
+
+	const float main_available_size = (main_axis_horizontal ? flex_available_content_size.x : flex_available_content_size.y);
+	const float cross_available_size = (!main_axis_horizontal ? flex_available_content_size.x : flex_available_content_size.y);
+
+	const float main_min_size = (main_axis_horizontal ? flex_min_size.x : flex_min_size.y);
+	const float main_max_size = (main_axis_horizontal ? flex_max_size.x : flex_max_size.y);
+	const float cross_min_size = (main_axis_horizontal ? flex_min_size.y : flex_min_size.x);
+	const float cross_max_size = (main_axis_horizontal ? flex_max_size.y : flex_max_size.x);
+
+	// For the purpose of placing items we make infinite size a big value.
+	const float main_wrap_size = Math::Clamp(main_available_size < 0.0f ? FLT_MAX : main_available_size, main_min_size, main_max_size);
+
+	// For the purpose of resolving lengths, infinite main size becomes zero.
+	const float main_size_base_value = (main_available_size < 0.0f ? 0.0f : main_available_size);
+	const float cross_size_base_value = (cross_available_size < 0.0f ? 0.0f : cross_available_size);
+
+	// -- Build a list of all flex items with base size information --
+	Vector<FlexItem> items;
+
+	const int num_flex_children = element_flex->GetNumChildren();
+	for (int i = 0; i < num_flex_children; i++)
+	{
+		Element* element = element_flex->GetChild(i);
+		const ComputedValues& computed = element->GetComputedValues();
+
+		if (computed.display == Style::Display::None)
+		{
+			continue;
+		}
+		else if (computed.position == Style::Position::Absolute || computed.position == Style::Position::Fixed)
+		{
+			absolutely_positioned_elements.push_back(element);
+			continue;
+		}
+
+		FlexItem item = {};
+		item.element = element;
+		LayoutDetails::BuildBox(item.box, flex_content_containing_block, element, BoxContext::FlexOrTable, 0.0f);
+
+		Style::LengthPercentageAuto item_main_size;
+
+		{
+			const ComputedAxisSize computed_main_size =
+				main_axis_horizontal ? LayoutDetails::BuildComputedHorizontalSize(computed) : LayoutDetails::BuildComputedVerticalSize(computed);
+			const ComputedAxisSize computed_cross_size =
+				!main_axis_horizontal ? LayoutDetails::BuildComputedHorizontalSize(computed) : LayoutDetails::BuildComputedVerticalSize(computed);
+
+			GetItemSizing(item.main, computed_main_size, main_size_base_value, direction_reverse);
+			GetItemSizing(item.cross, computed_cross_size, cross_size_base_value, wrap_reverse);
+
+			item_main_size = computed_main_size.size;
+		}
+
+		item.flex_shrink_factor = computed.flex_shrink;
+		item.flex_grow_factor = computed.flex_grow;
+		item.align_self = computed.align_self;
+
+		static_assert(int(Style::AlignSelf::FlexStart) == int(Style::AlignItems::FlexStart) + 1 &&
+				int(Style::AlignSelf::Stretch) == int(Style::AlignItems::Stretch) + 1,
+			"It is assumed below that align items is a shifted version (no auto value) of align self.");
+
+		// Use the container's align-items property if align-self is auto.
+		if (item.align_self == Style::AlignSelf::Auto)
+			item.align_self = static_cast<Style::AlignSelf>(static_cast<int>(computed_flex.align_items) + 1);
+
+		const float sum_padding_border = item.main.sum_edges - (item.main.margin_a + item.main.margin_b);
+
+		// Find the flex base size (possibly negative when using border box sizing)
+		if (computed.flex_basis.type != Style::FlexBasis::Auto)
+		{
+			item.inner_flex_base_size = ResolveValue(computed.flex_basis, main_size_base_value);
+			if (computed.box_sizing == Style::BoxSizing::BorderBox)
+				item.inner_flex_base_size -= sum_padding_border;
+		}
+		else if (!item.main.auto_size)
+		{
+			item.inner_flex_base_size = ResolveValue(item_main_size, main_size_base_value);
+			if (computed.box_sizing == Style::BoxSizing::BorderBox)
+				item.inner_flex_base_size -= sum_padding_border;
+		}
+		else if (main_axis_horizontal)
+		{
+			item.inner_flex_base_size = LayoutDetails::GetShrinkToFitWidth(element, flex_content_containing_block);
+		}
+		else
+		{
+			const Vector2f initial_box_size = item.box.GetSize();
+			RMLUI_ASSERT(initial_box_size.y < 0.f);
+
+			Box format_box = item.box;
+			if (initial_box_size.x < 0.f)
+				format_box.SetContent(Vector2f(flex_available_content_size.x - item.cross.sum_edges, initial_box_size.y));
+
+			LayoutEngine::FormatElement(element, flex_content_containing_block, &format_box);
+			item.inner_flex_base_size = element->GetBox().GetSize().y;
+		}
+
+		// Calculate the hypothetical main size (clamped flex base size).
+		item.hypothetical_main_size = Math::Clamp(item.inner_flex_base_size, item.main.min_size, item.main.max_size) + item.main.sum_edges;
+		item.flex_base_size = item.inner_flex_base_size + item.main.sum_edges;
+
+		items.push_back(std::move(item));
+	}
+
+	if (items.empty())
+	{
+		return;
+	}
+
+	// -- Collect the items into lines --
+	FlexContainer container;
+
+	if (flex_single_line)
+	{
+		container.lines.emplace_back(std::move(items));
+	}
+	else
+	{
+		float cursor = 0;
+
+		Vector<FlexItem> line_items;
+
+		for (FlexItem& item : items)
+		{
+			cursor += item.hypothetical_main_size;
+
+			if (!line_items.empty() && cursor > main_wrap_size)
+			{
+				// Break into new line.
+				container.lines.emplace_back(std::move(line_items));
+				cursor = item.hypothetical_main_size;
+				line_items = {std::move(item)};
+			}
+			else
+			{
+				// Add item to current line.
+				line_items.push_back(std::move(item));
+			}
+		}
+
+		if (!line_items.empty())
+			container.lines.emplace_back(std::move(line_items));
+
+		items.clear();
+		items.shrink_to_fit();
+	}
+
+	for (FlexLine& line : container.lines)
+	{
+		line.accumulated_hypothetical_main_size = std::accumulate(line.items.begin(), line.items.end(), 0.0f,
+			[](float value, const FlexItem& item) { return value + item.hypothetical_main_size; });
+	}
+
+	// If the available main size is infinite, the used main size becomes the accumulated outer size of all items of the widest line.
+	const float used_main_size_unconstrained = main_available_size >= 0.f
+		? main_available_size
+		: std::max_element(container.lines.begin(), container.lines.end(), [](const FlexLine& a, const FlexLine& b) {
+			  return a.accumulated_hypothetical_main_size < b.accumulated_hypothetical_main_size;
+		  })->accumulated_hypothetical_main_size;
+
+	const float used_main_size = Math::Clamp(used_main_size_unconstrained, main_min_size, main_max_size);
+
+	// -- Determine main size --
+	// Resolve flexible lengths to find the used main size of all items.
+	for (FlexLine& line : container.lines)
+	{
+		const float available_flex_space = used_main_size - line.accumulated_hypothetical_main_size; // Possibly negative
+
+		const bool flex_mode_grow = (available_flex_space > 0.f);
+
+		auto FlexFactor = [flex_mode_grow](const FlexItem& item) { return (flex_mode_grow ? item.flex_grow_factor : item.flex_shrink_factor); };
+
+		// Initialize items and freeze inflexible items.
+		for (FlexItem& item : line.items)
+		{
+			item.target_main_size = item.flex_base_size;
+
+			if (FlexFactor(item) == 0.f || (flex_mode_grow && item.flex_base_size > item.hypothetical_main_size) ||
+				(!flex_mode_grow && item.flex_base_size < item.hypothetical_main_size))
+			{
+				item.frozen = true;
+				item.target_main_size = item.hypothetical_main_size;
+			}
+		}
+
+		auto RemainingFreeSpace = [used_main_size, &line]() {
+			return used_main_size - std::accumulate(line.items.begin(), line.items.end(), 0.f, [](float value, const FlexItem& item) {
+				return value + (item.frozen ? item.target_main_size : item.flex_base_size);
+			});
+		};
+
+		const float initial_free_space = RemainingFreeSpace();
+
+		// Now iteratively distribute or shrink the size of all the items, until all the items are frozen.
+		while (!std::all_of(line.items.begin(), line.items.end(), [](const FlexItem& item) { return item.frozen; }))
+		{
+			float remaining_free_space = RemainingFreeSpace();
+
+			const float flex_factor_sum = std::accumulate(line.items.begin(), line.items.end(), 0.f,
+				[&FlexFactor](float value, const FlexItem& item) { return value + (item.frozen ? 0.0f : FlexFactor(item)); });
+
+			if (flex_factor_sum < 1.f)
+			{
+				const float scaled_initial_free_space = initial_free_space * flex_factor_sum;
+				if (Math::AbsoluteValue(scaled_initial_free_space) < Math::AbsoluteValue(remaining_free_space))
+					remaining_free_space = scaled_initial_free_space;
+			}
+
+			if (remaining_free_space != 0.f)
+			{
+				// Distribute free space proportionally to flex factors
+				if (flex_mode_grow)
+				{
+					for (FlexItem& item : line.items)
+					{
+						if (!item.frozen)
+						{
+							const float distribute_ratio = item.flex_grow_factor / flex_factor_sum;
+							item.target_main_size = item.flex_base_size + distribute_ratio * remaining_free_space;
+						}
+					}
+				}
+				else
+				{
+					const float scaled_flex_shrink_factor_sum =
+						std::accumulate(line.items.begin(), line.items.end(), 0.f, [](float value, const FlexItem& item) {
+							return value + (item.frozen ? 0.0f : item.flex_shrink_factor * item.inner_flex_base_size);
+						});
+					const float scaled_flex_shrink_factor_sum_nonzero = (scaled_flex_shrink_factor_sum == 0 ? 1 : scaled_flex_shrink_factor_sum);
+
+					for (FlexItem& item : line.items)
+					{
+						if (!item.frozen)
+						{
+							const float scaled_flex_shrink_factor = item.flex_shrink_factor * item.inner_flex_base_size;
+							const float distribute_ratio = scaled_flex_shrink_factor / scaled_flex_shrink_factor_sum_nonzero;
+							item.target_main_size = item.flex_base_size - distribute_ratio * Math::AbsoluteValue(remaining_free_space);
+						}
+					}
+				}
+			}
+
+			// Clamp min/max violations
+			float total_minmax_violation = 0.f;
+
+			for (FlexItem& item : line.items)
+			{
+				if (!item.frozen)
+				{
+					const float inner_target_main_size = Math::Max(0.0f, item.target_main_size - item.main.sum_edges);
+					const float clamped_target_main_size =
+						Math::Clamp(inner_target_main_size, item.main.min_size, item.main.max_size) + item.main.sum_edges;
+
+					const float violation_diff = clamped_target_main_size - item.target_main_size;
+					item.violation = (violation_diff > 0.0f ? FlexItem::Violation::Min
+															: (violation_diff < 0.f ? FlexItem::Violation::Max : FlexItem::Violation::None));
+					item.target_main_size = clamped_target_main_size;
+
+					total_minmax_violation += violation_diff;
+				}
+			}
+
+			for (FlexItem& item : line.items)
+			{
+				if (total_minmax_violation > 0.0f)
+					item.frozen |= (item.violation == FlexItem::Violation::Min);
+				else if (total_minmax_violation < 0.0f)
+					item.frozen |= (item.violation == FlexItem::Violation::Max);
+				else
+					item.frozen = true;
+			}
+		}
+
+		// Now, each item's used main size is found!
+		for (FlexItem& item : line.items)
+			item.used_main_size = item.target_main_size;
+	}
+
+	// -- Align main axis (§9.5) --
+	// Main alignment is done before cross sizing. Due to rounding to the pixel grid, the main size can
+	// change slightly after main alignment/offseting. Also, the cross sizing depends on the main sizing
+	// so doing it in this order ensures no surprises (overflow/wrapping issues) due to pixel rounding.
+	for (FlexLine& line : container.lines)
+	{
+		const float remaining_free_space = used_main_size -
+			std::accumulate(line.items.begin(), line.items.end(), 0.f, [](float value, const FlexItem& item) { return value + item.used_main_size; });
+
+		if (remaining_free_space > 0.0f)
+		{
+			const int num_auto_margins = std::accumulate(line.items.begin(), line.items.end(), 0,
+				[](int value, const FlexItem& item) { return value + int(item.main.auto_margin_a) + int(item.main.auto_margin_b); });
+
+			if (num_auto_margins > 0)
+			{
+				// Distribute the remaining space to the auto margins.
+				const float space_per_auto_margin = remaining_free_space / float(num_auto_margins);
+				for (FlexItem& item : line.items)
+				{
+					if (item.main.auto_margin_a)
+						item.main_auto_margin_size_a = space_per_auto_margin;
+					if (item.main.auto_margin_b)
+						item.main_auto_margin_size_b = space_per_auto_margin;
+				}
+			}
+			else
+			{
+				// Distribute the remaining space based on the 'justify-content' property.
+				using Style::JustifyContent;
+				const int num_items = int(line.items.size());
+
+				switch (computed_flex.justify_content)
+				{
+				case JustifyContent::SpaceBetween:
+					if (num_items > 1)
+					{
+						const float space_per_edge = remaining_free_space / float(2 * num_items - 2);
+						for (int i = 0; i < num_items; i++)
+						{
+							FlexItem& item = line.items[i];
+							if (i > 0)
+								item.main_auto_margin_size_a = space_per_edge;
+							if (i < num_items - 1)
+								item.main_auto_margin_size_b = space_per_edge;
+						}
+						break;
+					}
+					//-fallthrough
+				case JustifyContent::FlexStart:
+					line.items.back().main_auto_margin_size_b = remaining_free_space;
+					break;
+				case JustifyContent::FlexEnd:
+					line.items.front().main_auto_margin_size_a = remaining_free_space;
+					break;
+				case JustifyContent::Center:
+					line.items.front().main_auto_margin_size_a = 0.5f * remaining_free_space;
+					line.items.back().main_auto_margin_size_b = 0.5f * remaining_free_space;
+					break;
+				case JustifyContent::SpaceAround:
+				{
+					const float space_per_edge = remaining_free_space / float(2 * num_items);
+					for (FlexItem& item : line.items)
+					{
+						item.main_auto_margin_size_a = space_per_edge;
+						item.main_auto_margin_size_b = space_per_edge;
+					}
+				}
+				break;
+				}
+			}
+		}
+
+		// Now find the offset and snap the outer edges to the pixel grid.
+		float cursor = 0.0f;
+		for (FlexItem& item : line.items)
+		{
+			if (direction_reverse)
+				item.main_offset = used_main_size - (cursor + item.used_main_size + item.main_auto_margin_size_a - item.main.margin_b);
+			else
+				item.main_offset = cursor + item.main.margin_a + item.main_auto_margin_size_a;
+
+			cursor += item.used_main_size + item.main_auto_margin_size_a + item.main_auto_margin_size_b;
+			Math::SnapToPixelGrid(item.main_offset, item.used_main_size);
+		}
+	}
+
+	// -- Determine cross size (§9.4) --
+	// First, determine the cross size of each item, format it if necessary.
+	for (FlexLine& line : container.lines)
+	{
+		for (FlexItem& item : line.items)
+		{
+			const Vector2f content_size = item.box.GetSize();
+			const float used_main_size_inner = item.used_main_size - item.main.sum_edges;
+
+			if (main_axis_horizontal)
+			{
+				if (content_size.y < 0.0f)
+				{
+					item.box.SetContent(Vector2f(used_main_size_inner, content_size.y));
+					LayoutEngine::FormatElement(item.element, flex_content_containing_block, &item.box);
+					item.hypothetical_cross_size = item.element->GetBox().GetSize().y + item.cross.sum_edges;
+				}
+				else
+				{
+					item.hypothetical_cross_size = content_size.y + item.cross.sum_edges;
+				}
+			}
+			else
+			{
+				if (content_size.x < 0.0f || item.cross.auto_size)
+				{
+					item.box.SetContent(Vector2f(content_size.x, used_main_size_inner));
+					item.hypothetical_cross_size =
+						LayoutDetails::GetShrinkToFitWidth(item.element, flex_content_containing_block) + item.cross.sum_edges;
+				}
+				else
+				{
+					item.hypothetical_cross_size = content_size.x + item.cross.sum_edges;
+				}
+			}
+		}
+	}
+
+	// Determine cross size of each line.
+	if (cross_available_size >= 0.f && flex_single_line && container.lines.size() == 1)
+	{
+		container.lines[0].cross_size = cross_available_size;
+	}
+	else
+	{
+		for (FlexLine& line : container.lines)
+		{
+			const float largest_hypothetical_cross_size =
+				std::max_element(line.items.begin(), line.items.end(), [](const FlexItem& a, const FlexItem& b) {
+					return a.hypothetical_cross_size < b.hypothetical_cross_size;
+				})->hypothetical_cross_size;
+
+			// Currently, we don't handle the case where baseline alignment could extend the line's cross size, see CSS specs 9.4.8.
+			line.cross_size = Math::Max(0.0f, Math::RoundFloat(largest_hypothetical_cross_size));
+
+			if (flex_single_line)
+				line.cross_size = Math::Clamp(line.cross_size, cross_min_size, cross_max_size);
+		}
+	}
+
+	// Stretch out the lines if we have extra space.
+	if (cross_available_size >= 0.f && computed_flex.align_content == Style::AlignContent::Stretch)
+	{
+		int remaining_space = static_cast<int>(cross_available_size -
+			std::accumulate(container.lines.begin(), container.lines.end(), 0.f,
+				[](float value, const FlexLine& line) { return value + line.cross_size; }));
+
+		if (remaining_space > 0)
+		{
+			// Here we use integer math to ensure all space is distributed to pixel boundaries.
+			const int num_lines = (int)container.lines.size();
+			for (int i = 0; i < num_lines; i++)
+			{
+				const int add_space_to_line = remaining_space / (num_lines - i);
+				remaining_space -= add_space_to_line;
+				container.lines[i].cross_size += static_cast<float>(add_space_to_line);
+			}
+		}
+	}
+
+	// Determine the used cross size of items.
+	for (FlexLine& line : container.lines)
+	{
+		for (FlexItem& item : line.items)
+		{
+			const bool stretch_item = (item.align_self == Style::AlignSelf::Stretch);
+			if (stretch_item && item.cross.auto_size && !item.cross.auto_margin_a && !item.cross.auto_margin_b)
+			{
+				item.used_cross_size =
+					Math::Clamp(line.cross_size - item.cross.sum_edges, item.cross.min_size, item.cross.max_size) + item.cross.sum_edges;
+				// Here we are supposed to re-format the item with the new size, so that percentages can be resolved, see CSS specs Sec. 9.4.11. Seems
+				// very slow, we skip this for now.
+			}
+			else
+			{
+				item.used_cross_size = item.hypothetical_cross_size;
+			}
+		}
+	}
+
+	// -- Align cross axis (§9.6) --
+	for (FlexLine& line : container.lines)
+	{
+		constexpr float UndefinedBaseline = -FLT_MAX;
+		float max_baseline_edge_distance = UndefinedBaseline;
+		FlexItem* max_baseline_item = nullptr;
+
+		for (FlexItem& item : line.items)
+		{
+			const float remaining_space = line.cross_size - item.used_cross_size;
+
+			item.cross_offset = item.cross.margin_a;
+			item.cross_baseline_top = UndefinedBaseline;
+
+			const int num_auto_margins = int(item.cross.auto_margin_a) + int(item.cross.auto_margin_b);
+			if (num_auto_margins > 0)
+			{
+				const float space_per_auto_margin = Math::Max(remaining_space, 0.0f) / float(num_auto_margins);
+				item.cross_offset = item.cross.margin_a + (item.cross.auto_margin_a ? space_per_auto_margin : 0.f);
+			}
+			else
+			{
+				using Style::AlignSelf;
+				const AlignSelf align_self = item.align_self;
+
+				switch (align_self)
+				{
+				case AlignSelf::Auto:
+					// Never encountered here: should already have been replaced by container's align-items property.
+					RMLUI_ERROR;
+					break;
+				case AlignSelf::FlexStart:
+					// Do nothing, cross offset set above with this behavior.
+					break;
+				case AlignSelf::FlexEnd:
+					item.cross_offset = item.cross.margin_a + remaining_space;
+					break;
+				case AlignSelf::Center:
+					item.cross_offset = item.cross.margin_a + 0.5f * remaining_space;
+					break;
+				case AlignSelf::Baseline:
+				{
+					// We don't currently have a good way to get the true baseline here, so we make a very rough zero-effort approximation.
+					const float baseline_heuristic = 0.5f * item.element->GetLineHeight();
+					const float sum_edges_top = (wrap_reverse ? item.cross.sum_edges - item.cross.sum_edges_a : item.cross.sum_edges_a);
+
+					item.cross_baseline_top = sum_edges_top + baseline_heuristic;
+
+					const float baseline_edge_distance = (wrap_reverse ? item.used_cross_size - item.cross_baseline_top : item.cross_baseline_top);
+					if (baseline_edge_distance > max_baseline_edge_distance)
+					{
+						max_baseline_item = &item;
+						max_baseline_edge_distance = baseline_edge_distance;
+					}
+				}
+				break;
+				case AlignSelf::Stretch:
+					// Handled above
+					break;
+				}
+			}
+
+			if (wrap_reverse)
+			{
+				const float reverse_offset = line.cross_size - item.used_cross_size + item.cross.margin_a + item.cross.margin_b;
+				item.cross_offset = reverse_offset - item.cross_offset;
+			}
+		}
+
+		if (max_baseline_item)
+		{
+			// Align all baseline items such that their baselines are aligned with the one with the max. baseline distance.
+			// Cross offset for all baseline items are currently set as in 'flex-start'.
+			const float max_baseline_margin_top = (wrap_reverse ? max_baseline_item->cross.margin_b : max_baseline_item->cross.margin_a);
+			const float line_top_to_baseline_distance =
+				max_baseline_item->cross_offset - max_baseline_margin_top + max_baseline_item->cross_baseline_top;
+
+			for (FlexItem& item : line.items)
+			{
+				if (item.cross_baseline_top != UndefinedBaseline)
+				{
+					const float margin_top = (wrap_reverse ? item.cross.margin_b : item.cross.margin_a);
+					item.cross_offset = line_top_to_baseline_distance - item.cross_baseline_top + margin_top;
+				}
+			}
+		}
+
+		// Snap the outer item cross edges to the pixel grid.
+		for (FlexItem& item : line.items)
+			Math::SnapToPixelGrid(item.cross_offset, item.used_cross_size);
+	}
+
+	const float accumulated_lines_cross_size = std::accumulate(container.lines.begin(), container.lines.end(), 0.f,
+		[](float value, const FlexLine& line) { return value + line.cross_size; });
+
+	// If the available cross size is infinite, the used cross size becomes the accumulated line cross size.
+	const float used_cross_size_unconstrained = cross_available_size >= 0.f ? cross_available_size : accumulated_lines_cross_size;
+	const float used_cross_size = Math::Clamp(used_cross_size_unconstrained, cross_min_size, cross_max_size);
+
+	// Align the lines along the cross-axis.
+	{
+		const float remaining_free_space = used_cross_size - accumulated_lines_cross_size;
+		const int num_lines = int(container.lines.size());
+
+		if (remaining_free_space > 0.f)
+		{
+			using Style::AlignContent;
+
+			switch (computed_flex.align_content)
+			{
+			case AlignContent::SpaceBetween:
+				if (num_lines > 1)
+				{
+					const float space_per_edge = remaining_free_space / float(2 * num_lines - 2);
+					for (int i = 0; i < num_lines; i++)
+					{
+						FlexLine& line = container.lines[i];
+						if (i > 0)
+							line.cross_spacing_a = space_per_edge;
+						if (i < num_lines - 1)
+							line.cross_spacing_b = space_per_edge;
+					}
+				}
+				//-fallthrough
+			case AlignContent::FlexStart:
+				container.lines.back().cross_spacing_b = remaining_free_space;
+				break;
+			case AlignContent::FlexEnd:
+				container.lines.front().cross_spacing_a = remaining_free_space;
+				break;
+			case AlignContent::Center:
+				container.lines.front().cross_spacing_a = 0.5f * remaining_free_space;
+				container.lines.back().cross_spacing_b = 0.5f * remaining_free_space;
+				break;
+			case AlignContent::SpaceAround:
+			{
+				const float space_per_edge = remaining_free_space / float(2 * num_lines);
+				for (FlexLine& line : container.lines)
+				{
+					line.cross_spacing_a = space_per_edge;
+					line.cross_spacing_b = space_per_edge;
+				}
+			}
+			break;
+			case AlignContent::Stretch:
+				// Handled above.
+				break;
+			}
+		}
+
+		// Now find the offset and snap the line edges to the pixel grid.
+		float cursor = 0.f;
+		for (FlexLine& line : container.lines)
+		{
+			if (wrap_reverse)
+				line.cross_offset = used_cross_size - (cursor + line.cross_spacing_a + line.cross_size);
+			else
+				line.cross_offset = cursor + line.cross_spacing_a;
+
+			cursor += line.cross_spacing_a + line.cross_size + line.cross_spacing_b;
+			Math::SnapToPixelGrid(line.cross_offset, line.cross_size);
+		}
+	}
+
+	auto MainCrossToVec2 = [main_axis_horizontal](const float v_main, const float v_cross) {
+		return main_axis_horizontal ? Vector2f(v_main, v_cross) : Vector2f(v_cross, v_main);
+	};
+
+	// -- Format items --
+	for (FlexLine& line : container.lines)
+	{
+		for (FlexItem& item : line.items)
+		{
+			const Vector2f item_size = MainCrossToVec2(item.used_main_size - item.main.sum_edges, item.used_cross_size - item.cross.sum_edges);
+			const Vector2f item_offset = MainCrossToVec2(item.main_offset, line.cross_offset + item.cross_offset);
+
+			item.box.SetContent(item_size);
+
+			Vector2f cell_visible_overflow_size;
+			LayoutEngine::FormatElement(item.element, flex_content_containing_block, &item.box, &cell_visible_overflow_size);
+
+			// Set the position of the element within the the flex container
+			item.element->SetOffset(flex_content_offset + item_offset, element_flex);
+
+			// The cell contents may overflow, propagate this to the flex container.
+			const Vector2f overflow_size = item_offset + cell_visible_overflow_size -
+				Vector2f(item.box.GetEdge(Box::MARGIN, Box::LEFT), item.box.GetEdge(Box::MARGIN, Box::TOP));
+
+			flex_content_overflow_size.x = Math::Max(flex_content_overflow_size.x, overflow_size.x);
+			flex_content_overflow_size.y = Math::Max(flex_content_overflow_size.y, overflow_size.y);
+		}
+	}
+
+	flex_resulting_content_size = MainCrossToVec2(used_main_size, used_cross_size);
+}
+
+} // namespace Rml

+ 76 - 0
Source/Core/LayoutFlex.h

@@ -0,0 +1,76 @@
+/*
+ * 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_LAYOUTFLEX_H
+#define RMLUI_CORE_LAYOUTFLEX_H
+
+#include "../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+
+class Box;
+
+class LayoutFlex {
+public:
+	/// Formats a flexible box, including all elements contained within.
+	/// @param[in] box The box used for dimensioning the flex container.
+	/// @param[in] min_size Minimum width and height of the flexbox.
+	/// @param[in] max_size Maximum width and height of the flexbox.
+	/// @param[in] containing_block Flexbox's containing block size.
+	/// @param[in] element_flex The flex container element.
+	/// @param[out] out_formatted_content_size The flex container element's used size.
+	/// @param[out] out_content_overflow_size  The content size of the flexbox's overflowing content.
+	/// @param[out] out_absolutely_positioned_elements List of absolutely positioned elements within the flexbox.
+	static void Format(const Box& box, Vector2f min_size, Vector2f max_size, Vector2f containing_block, Element* element_flex,
+		Vector2f& out_formatted_content_size, Vector2f& out_content_overflow_size, ElementList& out_absolutely_positioned_elements);
+
+private:
+	LayoutFlex(Element* element_flex, Vector2f flex_available_content_size, Vector2f flex_content_containing_block, Vector2f flex_content_offset,
+		Vector2f flex_min_size, Vector2f flex_max_size, ElementList& absolutely_positioned_elements);
+
+	// Format the flexbox.
+	void Format();
+
+	Element* const element_flex;
+
+	const Vector2f flex_available_content_size;
+	const Vector2f flex_content_containing_block;
+	const Vector2f flex_content_offset;
+	const Vector2f flex_min_size;
+	const Vector2f flex_max_size;
+
+	// The final size of the table which will be determined by the size of its columns, rows, and spacing.
+	Vector2f flex_resulting_content_size;
+	// Overflow size in case flex items overflow the container or contents of any flex items overflow their box (without being caught by the item).
+	Vector2f flex_content_overflow_size;
+
+	ElementList& absolutely_positioned_elements;
+};
+
+} // namespace Rml
+#endif

+ 71 - 0
Tests/Data/VisualTests/flex_01.rml

@@ -0,0 +1,71 @@
+<rml>
+<head>
+    <title>Flex 01 - Basic flexbox layout</title>
+    <link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css-flexbox/" />
+	<meta name="Description" content="Basic flexible box layout." />
+	<meta name="Animation" content="Hover over the articles to animate their size." />
+	<style>
+		header, article { display: block; }
+		h1 { font-size: 1.5em; }
+		h2 { font-size: 1.3em; }
+		
+		header {
+			background-color: #9777d9;
+			border: 5dp #666;
+		}
+		h1 {
+			text-align: center;
+			color: white;
+			line-height: 100dp;
+		}
+		section {
+			display: flex;
+			background: #666;
+			border: 5dp #666;
+		}
+		article {
+			padding: 10dp;
+			margin: 0 5dp;
+			background-color: #edd3c0;
+			flex: 1;
+			transition: 0.3s flex-grow cubic-out;
+		}
+		article:hover {
+			flex: 1.2;
+			background-color: #fde3d0;
+		}
+		h2 {
+			text-align: center;
+			background-color: #eb6e14;
+			margin: -10dp -10dp 0;
+			padding: 10dp 0;
+		}
+		article:hover h2 {
+			background-color: #fb7e24;
+		}
+	</style>
+</head>
+
+<body>
+<header>
+	<h1>Header</h1>
+</header>
+<section>
+	<article>
+		<h2>First article</h2>
+		<p>Etiam libero lorem, lacinia non augue lobortis, tincidunt consequat justo. Sed id enim tempor, feugiat tortor id, rhoncus enim. Quisque pretium neque eu felis tincidunt fringilla. Mauris laoreet enim neque, iaculis cursus lorem mollis sed. Nulla pretium euismod nulla sed convallis. Curabitur in tempus sem. Phasellus suscipit vitae nulla nec ultricies.</p>
+	</article>
+	<article>
+		<h2>Second article</h2>
+		<p>Ut volutpat, odio et facilisis molestie, lacus elit euismod enim, et tempor lacus sapien finibus ipsum. Aliquam erat volutpat. Nullam risus turpis, hendrerit ac fermentum in, dapibus non risus.</p>
+	</article>
+	<article>
+		<h2>Third article</h2>
+		<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed aliquet commodo nisi, id cursus enim eleifend vitae. Praesent turpis lorem, commodo id tempus sit amet, faucibus et libero. Aliquam malesuada ultrices leo, ut molestie tortor posuere sit amet. Proin vitae tortor a sem consequat gravida. Maecenas sed egestas dolor.</p>
+		<p>In gravida ligula in turpis molestie varius. Ut sed velit id tellus aliquet aliquet. Nulla et leo tellus. Ut a convallis dolor, eu rutrum enim. Nam vitae ultrices dui. Aliquam semper eros ut ultrices rutrum.</p>
+	</article>
+</section>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 72 - 0
Tests/Data/VisualTests/flex_02.rml

@@ -0,0 +1,72 @@
+<rml>
+<head>
+    <link type="text/rcss" href="/../Tests/Data/style.rcss" />
+    <title>Flex 02 - Various features</title>
+	<meta name="Description" content="Testing various flexible box layout features. Should be comparable to the output in a web browser." />
+    <style>
+        .flex-container {
+            display: flex;
+            margin: 10dp 20dp;
+            background-color: #333;
+            max-height: 210dp;
+            flex-wrap: wrap-reverse;
+        }
+
+        .flex-item {
+            width: 50dp;
+            margin: 20dp;
+            background-color: #eee;
+            height: 50dp;
+            text-align: center;
+        }
+
+        .flex-direction-row {
+            flex-direction: row;
+        }
+        .flex-direction-row-reverse {
+            flex-direction: row-reverse;
+        }
+        .flex-direction-column {
+            flex-direction: column;
+        }
+        .flex-direction-column-reverse {
+            flex-direction: column-reverse;
+        }
+        .absolute {
+            margin: 0;
+            position: absolute;
+            right: 0;
+            bottom: 10dp;
+        }
+    </style>
+</head>
+
+<body>
+    <div class="flex-container flex-direction-row" style="position: relative">
+        <div class="flex-item absolute">Abs</div>
+        <div class="flex-item" style="margin: 50dp;">1</div>
+        <div class="flex-item" style="margin-top: auto">2</div>
+        <div class="flex-item" style="margin: auto">3</div>
+    </div>
+
+    <div class="flex-container flex-direction-row-reverse" style="height: 200dp; justify-content: space-around;">
+        <div class="flex-item">1</div>
+        <div class="flex-item" style="margin-bottom: auto;">2</div>
+        <div class="flex-item" style="margin-right: 40dp;">3</div>
+    </div>
+
+    <div class="flex-container flex-direction-column">
+        <div class="flex-item" id="test" style="margin-right: auto">1</div>
+        <div class="flex-item">2</div>
+        <div class="flex-item">3</div>
+    </div>
+
+    <div class="flex-container flex-direction-column-reverse">
+        <div class="flex-item">1</div>
+        <div class="flex-item">2 LONG_OVERFLOWING_WORD</div>
+        <div class="flex-item">3</div>
+    </div>
+
+    <handle size_target="#document"/>
+</body>
+</rml>

+ 73 - 0
Tests/Data/VisualTests/flex_03_scroll.rml

@@ -0,0 +1,73 @@
+<rml>
+<head>
+	<title>Flex 03 - Scrolling container</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css-flexbox/" />
+	<meta name="Description" content="Flex container with scrollbars." />
+	<style>
+		.flex {
+			display: flex;
+			background-color: #555;
+			margin: 5dp 20dp 15dp;
+			border: 2dp #333;
+			justify-content: space-between;
+			color: #d44fff;
+		}
+		.auto {
+			overflow: auto;
+		}
+		.scroll {
+			overflow: scroll;
+		}
+		.flex div {
+			flex: 0 1 auto;
+			width: 50dp;
+			height: 50dp;
+			margin: 20dp;
+			background-color: #eee;
+			line-height: 50dp;
+			text-align: center;
+		}
+		.flex div.tall {
+			height: 80dp;
+			width: 15dp;
+			margin: 0;
+			border: 2dp #d44fff;
+		}
+	</style>
+</head>
+
+<body>
+overflow: scroll
+<div class="flex scroll" id="scroll">
+	<div>Hello<div class="tall"/></div>
+	<div>big world!</div>
+	<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+</div>
+overflow: auto
+<div class="flex auto" id="auto">
+	<div>Hello<div class="tall"/></div>
+	<div>big world!</div>
+	<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+</div>
+overflow: auto - only vertical overflow
+<div class="flex auto" id="vertical">
+	<div>Hello<div class="tall"/></div>
+	<div>big world!</div>
+	<div>LONG</div>
+</div>
+overflow: auto - only horizontal overflow
+<div class="flex auto" id="horizontal">
+	<div>Hello</div>
+	<div>big</div>
+	<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+</div>
+overflow: visible
+<div class="flex" id="visible">
+	<div>Hello<div class="tall"/></div>
+	<div>big world!</div>
+	<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+</div>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 113 - 0
Tests/Data/VisualTests/flex_04.rml

@@ -0,0 +1,113 @@
+<rml>
+<head>
+    <title>Flex 04 - Flex shorthand</title>
+    <link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css-flexbox/#flex-property" />
+	<meta name="Description" content="The default value of flex should allow the items to shrink but not grow. With 'flex: auto' the items should fill the container and also allow shrinking. With 'flex: none' the items neither shrink nor grow with the container size. Finally, with 'flex: &amp;lt;number&amp;gt;' each item should be given that proportion of the available space." />
+	<meta name="Instructions" content="Resize the document to see the resizing behavior of the flexboxes." />
+	<style>
+		h1 {
+			text-align: center;
+			margin-bottom: 5dp;
+			padding: 0.25em 0;
+			border-color: #aaa;
+			border-width: 1dp 0;
+		}
+		h1:first-child { margin-top: 0; }
+		h1, h2 { white-space: nowrap; }
+		.flex {
+			display: flex;
+			margin: 3dp 0 7dp;
+			border: 2dp #333;
+			overflow: hidden;
+			justify-content: space-around;
+			align-items: flex-end;
+			flex-wrap: wrap;
+		}
+		.flex > div {
+			border: 2dp #d66;
+			background: #fff;
+			text-align: center;
+		}
+		.flex.nowrap {
+			flex-wrap: nowrap;
+		}
+		.flex.auto > div {
+			flex: 1 1 auto;
+		}
+		.flex.none > div {
+			flex: none;
+		}
+		.flex.number > div {
+			flex: 1;
+		}
+		.wrapper {
+			min-height: 165dp;
+		}
+	</style>
+</head>
+
+<body>
+<h1>flex: <em>default value</em> (0 1 auto)</h1>
+<div class="wrapper">
+	<h2>flex-wrap: wrap</h2>
+	<div class="flex">
+		<div>Hello</div>
+		<div>big world!</div>
+		<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+	</div>
+	<h2>flex-wrap: nowrap</h2>
+	<div class="flex nowrap">
+		<div>Hello</div>
+		<div>big world!</div>
+		<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+	</div>
+</div>
+<h1>flex: auto (1 1 auto)</h1>
+<div class="wrapper">
+	<h2>flex-wrap: wrap</h2>
+	<div class="flex auto">
+		<div>Hello</div>
+		<div>big world!</div>
+		<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+	</div>
+	<h2>flex-wrap: nowrap</h2>
+	<div class="flex nowrap auto">
+		<div>Hello</div>
+		<div>big world!</div>
+		<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+	</div>
+</div>
+<h1>flex: none (0 0 auto)</h1>
+<div class="wrapper">
+	<h2>flex-wrap: wrap</h2>
+	<div class="flex none">
+		<div>Hello</div>
+		<div>big world!</div>
+		<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+	</div>
+	<h2>flex-wrap: nowrap</h2>
+	<div class="flex nowrap none">
+		<div>Hello</div>
+		<div>big world!</div>
+		<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+	</div>
+</div>
+<h1>flex: 1 (1 1 0)</h1>
+<div class="wrapper">
+	<h2>flex-wrap: wrap</h2>
+	<div class="flex number">
+		<div>Hello</div>
+		<div>big world!</div>
+		<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+	</div>
+	<h2>flex-wrap: nowrap</h2>
+	<div class="flex nowrap number">
+		<div>Hello</div>
+		<div>big world!</div>
+		<div>LOOOOOOOOOOOOOOOOOOOOONG</div>
+	</div>
+</div>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 97 - 0
Tests/Data/VisualTests/flex_05.rml

@@ -0,0 +1,97 @@
+<rml>
+<head>
+    <title>Flex 05 - Height constraints</title>
+    <link type="text/rcss" href="../style.rcss"/>
+    <link rel="match" href="reference/flex_05-ref.rml"/>
+	<link rel="help" href="https://drafts.csswg.org/css-flexbox/" />
+	<meta name="Description" content="The flex container should always respect (min-/max-) height constraints." />
+	<style>
+		h1 {
+			text-align: center;
+			margin-bottom: 5dp;
+			padding: 0.25em 0;
+			border-color: #333;
+			border-width: 1dp 0;
+		}
+		h1:first-child { margin-top: 0; }
+		h1, h2 { white-space: nowrap; }
+		.flex {
+			display: flex;
+			margin: 1dp 0 3dp;
+			border: 1dp #999;
+			overflow: hidden;
+			justify-content: space-around;
+			align-items: flex-start;
+		}
+		.flex > div {
+			border: 2dp #d66;
+			background: #fff;
+			text-align: center;
+		}
+		.wrap {
+			flex-wrap: wrap;
+		}
+		.column {
+			flex-direction: column;
+		}
+		.height {
+			height: 40dp;
+		}
+		.min-height {
+			min-height: 40dp;
+		}
+		.max-height {
+			max-height: 40dp;
+		}
+	</style>
+</head>
+
+<body>
+<h1>height, rows</h1>
+<div class="flex wrap height">
+	<div>Hello<br/>world!<br/>More<br/>Text</div><div>More Text</div>
+	<div>Hello world!</div><div>More Text</div>
+</div>
+<div class="flex height">
+	<div>Hello world!</div><div>More Text</div>
+</div>
+<h1>height, columns</h1>
+<div class="flex column wrap height">
+	<div>Hello<br/>world!<br/>More<br/>Text</div><div>More Text</div>
+	<div>Hello world!</div><div>More Text</div>
+</div>
+<div class="flex column height">
+	<div>Hello<br/>world!</div><div>More<br/>Text</div>
+</div>
+<h1>min-height, rows</h1>
+<div class="flex wrap min-height">
+	<div>Hello world!</div>
+</div>
+<div class="flex min-height">
+	<div>Hello world!</div>
+</div>
+<h1>min-height, columns</h1>
+<div class="flex column wrap min-height">
+	<div>Hello world!</div>
+</div>
+<div class="flex column min-height">
+	<div>Hello world!</div>
+</div>
+<h1>max-height, rows</h1>
+<div class="flex wrap max-height">
+	<div>Hello<br/>world!<br/>More<br/>Text</div>
+</div>
+<div class="flex max-height">
+	<div>Hello<br/>world!<br/>More<br/>Text</div>
+</div>
+<h1>max-height, columns</h1>
+<div class="flex column wrap max-height">
+	<div>Hello<br/>world!<br/>More<br/>Text</div><div>More<br/>Text</div>
+	<div>Hello<br/>world!</div><div>More<br/>Text</div>
+</div>
+<div class="flex column max-height">
+	<div>Hello<br/>world!</div><div>More<br/>Text</div>
+</div>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 71 - 0
Tests/Data/VisualTests/flex_direction.rml

@@ -0,0 +1,71 @@
+<rml>
+<head>
+    <link type="text/rcss" href="/../Tests/Data/style.rcss" />
+    <title>CSS Test: flex flow direction</title>
+    <link href="https://test.csswg.org/suites/css-flexbox-1_dev/nightly-unstable/xhtml1/flex-direction.xht" rel="source" />
+    <link href="http://www.github.com/sskyy" rel="author" title="houzhenyu" />
+    <link href="http://www.w3.org/TR/css-flexbox-1/#flex-direction" rel="help" />
+    <link href="reference/flex_direction-ref.rml" rel="match" />
+    <meta content="The flow of flex items in the the flex container should observe the flex-direction property." name="assert" />
+    <style>
+        .flex-container {
+            display: flex;
+            margin: 20dp;
+            background-color: #333;
+        }
+        
+        .flex-item {
+            width: 50dp;
+            height: 50dp;
+            margin: 20dp;
+            background-color: #eee;
+            line-height: 50dp;
+            text-align: center;
+        }
+        
+        .flex-container.flex-direction-row {
+            flex-direction: row;
+        }
+        .flex-container.flex-direction-row-reverse {
+            flex-direction: row-reverse;
+        }
+        .flex-container.flex-direction-column {
+            flex-direction: column;
+        }
+        .flex-container.flex-direction-column-reverse {
+            flex-direction: column-reverse;
+        }
+    </style>
+</head>
+<body>
+    <h1>flex-direction:row</h1>
+    <div class="flex-container flex-direction-row">
+        <div class="flex-item">1</div>
+        <div class="flex-item">2</div>
+        <div class="flex-item">3</div>
+    </div>
+
+    <h1>flex-direction:row-reverse</h1>
+    <div class="flex-container flex-direction-row-reverse">
+        <div class="flex-item">1</div>
+        <div class="flex-item">2</div>
+        <div class="flex-item">3</div>
+    </div>
+
+    <h1>flex-direction:column</h1>
+    <div class="flex-container flex-direction-column">
+        <div class="flex-item">1</div>
+        <div class="flex-item">2</div>
+        <div class="flex-item">3</div>
+    </div>
+
+    <h1>flex-direction:column-reverse</h1>
+    <div class="flex-container flex-direction-column-reverse">
+        <div class="flex-item">1</div>
+        <div class="flex-item">2</div>
+        <div class="flex-item">3</div>
+    </div>
+    
+    <handle size_target="#document"/>
+</body>
+</rml>

+ 63 - 0
Tests/Data/VisualTests/flex_wrap_column_reverse.rml

@@ -0,0 +1,63 @@
+<rml>
+<head>
+  <link type="text/rcss" href="/../Tests/Data/style.rcss" />
+  <title>CSS Test: flex container multiline wrapping in column-reverse direction</title>
+  <link href="https://test.csswg.org/suites/css-flexbox-1_dev/nightly-unstable/xhtml1/multi-line-wrap-with-column-reverse.xht" rel="source" />
+  <link href="mailto:[email protected]" rel="author" title="tmtysk" />
+  <link href="mailto:[email protected]" rel="reviewer" title="Tab Atkins" />
+  <link href="http://www.w3.org/TR/css-flexbox-1/#flex-wrap-property" rel="help" />
+  <link href="http://www.w3.org/TR/css-flexbox-1/#flex-direction-property" rel="help" />
+  <link href="reference/flex_wrap_column_reverse-ref.rml" rel="match" />
+  <meta content="This test check that a flex container wraps blocks multiline in column-reverse direction." name="assert" />
+  <style>
+    *, p { margin:0; padding:0; font-size:100%; line-height:1; }
+
+    #test {
+      display: flex;
+      flex-direction: column-reverse;
+      flex-wrap: wrap;
+      height: 300dp;
+    }
+
+    p {
+      margin-top: 10dp;
+      margin-right: 10dp;
+      background-color: #eee;
+    }
+
+    #col1-row1 {
+        height: 90dp;
+    }
+
+    #col1-row2 {
+        height: 90dp;
+    }
+
+    #col1-row3 {
+        height: 90dp;
+    }
+
+    #col2-row1 {
+        height: 140dp;
+    }
+
+    #col2-row2 {
+        height: 140dp;
+    }
+
+    #col3-row1 {
+        height: 290dp;
+    }
+  </style>
+</head>
+<body>
+  <div id="test">
+    <p id="col1-row3">1-3</p>
+    <p id="col1-row2">1-2</p>
+    <p id="col1-row1">1-1</p>
+    <p id="col2-row2">2-2</p>
+    <p id="col2-row1">2-1</p>
+    <p id="col3-row1">3-1</p>
+  </div>
+</body>
+</rml>

+ 72 - 0
Tests/Data/VisualTests/reference/flex_05-ref.rml

@@ -0,0 +1,72 @@
+<rml>
+<head>
+    <title>Flex 05 ref</title>
+    <link type="text/rcss" href="../../style.rcss"/>
+	<meta name="Reference" content="The size of the flex containers should match the size of the block boxes in the reference." />
+	<style>
+		h1 {
+			text-align: center;
+			margin-bottom: 5dp;
+			padding: 0.25em 0;
+			border-color: #333;
+			border-width: 1dp 0;
+		}
+		h1:first-child { margin-top: 0; }
+		h1, h2 { white-space: nowrap; }
+		.flex {
+			display: block;
+			margin: 1dp 0 3dp;
+			border: 1dp #999;
+		}
+		.wrap {
+			flex-wrap: wrap;
+		}
+		.column {
+			flex-direction: column;
+		}
+		.height {
+			height: 40dp;
+		}
+		.min-height {
+			height: 40dp;
+		}
+		.max-height {
+			height: 40dp;
+		}
+	</style>
+</head>
+
+<body>
+<h1>height, rows</h1>
+<div class="flex wrap height">
+</div>
+<div class="flex height">
+</div>
+<h1>height, columns</h1>
+<div class="flex column wrap height">
+</div>
+<div class="flex column height">
+</div>
+<h1>min-height, rows</h1>
+<div class="flex wrap min-height">
+</div>
+<div class="flex min-height">
+</div>
+<h1>min-height, columns</h1>
+<div class="flex column wrap min-height">
+</div>
+<div class="flex column min-height">
+</div>
+<h1>max-height, rows</h1>
+<div class="flex wrap max-height">
+</div>
+<div class="flex max-height">
+</div>
+<h1>max-height, columns</h1>
+<div class="flex column wrap max-height">
+</div>
+<div class="flex column max-height">
+</div>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 80 - 0
Tests/Data/VisualTests/reference/flex_direction-ref.rml

@@ -0,0 +1,80 @@
+<rml>
+<head>
+    <link type="text/rcss" href="/../Tests/Data/style.rcss" />
+    <title>CSS Test: flex flow direction</title>
+    <link href="https://test.csswg.org/suites/css-flexbox-1_dev/nightly-unstable/xhtml1/reference/flex-direction.xht" rel="source" />
+    <link href="http://www.github.com/sskyy" rel="author" title="houzhenyu" />
+    <style>
+        .flex-container{
+            display:block;
+            margin:20px;
+            background-color: #333;
+            line-height: 0px;
+        }
+        .flex-item{
+            display: inline-block;
+            width:50px;
+            height:50px;
+            margin:20px 20px;
+            background-color: #eee;
+            line-height: 50px;
+            text-align: center;
+        }
+
+        .flex-container.flex-direction-row{
+            flex-direction : row;
+        }
+        .flex-container.flex-direction-row-reverse{
+            text-align: right;
+        }
+
+        .flex-container.flex-direction-column{
+            padding:20px 0px;
+        }
+
+        .flex-container.flex-direction-column .flex-item{
+            display: block;
+            margin:40px 20px;
+        }
+
+        .flex-container.flex-direction-column .flex-item.first{
+            margin-top:0px;
+        }
+        .flex-container.flex-direction-column .flex-item.last{
+            margin-bottom:0px;
+        }
+
+        .flex-container.flex-direction-column-reverse{
+            padding:20px 0px;
+        }
+
+        .flex-container.flex-direction-column-reverse .flex-item{
+            display: block;
+            margin:40px 20px;
+        }
+
+        .flex-container.flex-direction-column-reverse .flex-item.first{
+            margin-top:0px;
+        }
+        .flex-container.flex-direction-column-reverse .flex-item.last{
+            margin-bottom:0px;
+        }
+    </style>
+</head>
+
+<body>
+    <h1>flex-direction:row</h1>
+    <div class="flex-container flex-direction-row"><div class="flex-item">1</div><div class="flex-item">2</div><div class="flex-item">3</div></div>
+
+    <h1>flex-direction:row-reverse</h1>
+    <div class="flex-container flex-direction-row-reverse"><div class="flex-item">3</div><div class="flex-item">2</div><div class="flex-item">1</div></div>
+
+    <h1>flex-direction:column</h1>
+    <div class="flex-container flex-direction-column"><div class="flex-item first">1</div><div class="flex-item">2</div><div class="flex-item last">3</div></div>
+
+    <h1>flex-direction:column-reverse</h1>
+    <div class="flex-container flex-direction-column-reverse"><div class="flex-item first">3</div><div class="flex-item">2</div><div class="flex-item last">1</div></div>
+
+    <handle size_target="#document"/>
+</body>
+</rml>

+ 71 - 0
Tests/Data/VisualTests/reference/flex_wrap_column_reverse-ref.rml

@@ -0,0 +1,71 @@
+<rml>
+<head>
+  <link type="text/rcss" href="/../Tests/Data/style.rcss" />
+  <title>CSS Test Reference: flex container multiline wrapping in column-reverse direction</title>
+  <link href="mailto:[email protected]" rel="author" title="tmtysk" />
+  <link href="mailto:[email protected]" rel="reviewer" title="Tab Atkins" />
+  <style>
+    * {
+      margin: 0;
+      padding: 0;
+      font-size: 100%;
+      line-height: 1;
+    }
+
+    .test {
+      width: 300px;
+      float: left;
+      width: 33.3333%;
+    }
+
+    p {
+      margin-top: 10px;
+      margin-right: 10px;
+      background-color: #eee;
+    }
+
+    #row1-col1 {
+      height: 90px;
+    }
+
+    #row1-col2 {
+      height: 90px;
+    }
+
+    #row1-col3 {
+      height: 90px;
+    }
+
+    #row2-col1 {
+      height: 140px;
+    }
+
+    #row2-col2 {
+      height: 140px;
+    }
+
+    #row3-col1 {
+      height: 290px;
+    }
+
+    .clear {
+      clear: both;
+    }
+  </style>
+</head>
+<body>
+  <div class="test">
+    <p id="row1-col1">1-1</p>
+    <p id="row1-col2">1-2</p>
+    <p id="row1-col3">1-3</p>
+  </div>
+  <div class="test">
+    <p id="row2-col1">2-1</p>
+    <p id="row2-col2">2-2</p>
+  </div>
+  <div class="test">
+    <p id="row3-col1">3-1</p>
+  </div>
+  <div class="clear"></div>
+</body>
+</rml>