소스 검색

Improve and refactor layout engine, better conformance to CSS specification

- Improve readability and maintainability:
  - Better separation of classes, reduce available state.
  - Make classes better conform to CSS terminology.
  - Improve call-graph, flow from parent to child, avoid mutable calls into ancestors.
- Make layout more conformant to CSS specification.
  - Re-implement inline layouting.
  - Fixed more than a hundred CSS tests, including ACID1.
- Fix long-standing issues.
  - Allow tables and flexboxes to be absolutely positioned or floated.
  - Allow nested flexboxes: Flex items can now be flex containers themselves. #320
  - Better handle block-level boxes in inline formatting contexts. #392

Breaking changes:
- Reworked font engine interface, in particular in terms of font metrics.
- Possible layout changes, usually due to better CSS conformance.

Development commits:
- Add debug dump for layout tree
- Review function naming in layout engine
- WIP Start refactoring LayoutBlockBox (not compiling)
- Format
- Compiles again, all tests pass
- Refactoring
- Remove friend classes
- Remove dead code
- Add test for absolutely positioned and floated flex boxes
- Minor refactoring and formatting
- Format layout files
- Mini cleanup
- More const correctness
- Support for nested flex boxes, as well as flex boxes that are absolutely positioned or floated
- Small cleanup
- Float placement test
- Cleanup inline box
- Move Inline Container to own file
- Minor update display block test
- WIP inline formatting extreme makeover
- DumpTree rename
- WIP inline formatting
- WIP line box
- WIP inline boxes
- New inline formatting context (now compiling)
- Refactoring
- Add basic inline formatting visual test
- Fix formatting of inline block and replaced elements
- Update inline formatting visual test
- Refactoring
- Preparing for split fragments
- Update visual test
- Line wrapping
- Update visual test
- Layout tree dump: Use element attribute instead of id
- Line splitting
- Fix some spacing and line wrapping issues
- Fix placement of inline boxes
- Refactor inline layout
- Update visual tests
- Handle inline element splitting around block-level element, some refactoring
- Line box cleanup
- Refactor inline-level boxes
- Add new visual float test
- Minor
- Floating boxes and wrapping
- Floats can no longer wrap down lines which is set to 'white-space: nowrap'
- Fixes for overflow and scrollable content area
- Implement horizontal alignment in line boxes (text-align)
- Minifix
- Font engine interface: Refactor font metrics [breaking change]
- WIP Implement vertical-align, line box refactoring
- Top and bottom vertical alignment
- Baseline position of inline blocks
- Fix inline box height
- Use baseline position when adding ElementText lines
- Implement aligned subtrees for correct line-relative vertical alignment
- Fix float spacing
- Improved inline-block baseline
- Wrap if there is no available width
- More accurate static position for absolutely positioned elements
- Fix overflow padding sizing by subtracting scrollbar size
- Refactor inline layout
- Some cleanup, fix alignment of children of bottom-aligned inline boxes
- Inline layout cleanup
- Refactor inline layout
- Add new inline formatting test
- Update line box, linked open fragments
- Allow splitting all open inline boxes, collapsible line boxes
- Add tests for vertical-align and inline-block baseline
- Fix inline-block alignment
- Warn on missing font face when placing text, some cleanup
- Floating elements are now placed on the same line if they can fit next to the line's contents
- Add relative position test
- Add any ancestor relative positioning to our own position
- Properly resolve relative position of inline, floated, flex, and table elements
- WIP Formatting contexts
- WIP Make separate flex and table formatting contexts
- Small refactor block container constructor
- Extend overflow test
- WIP refactor overflow
- WIP More formatting contexts
- Refactor flex and table formatting
- Some cleanup
- Formatting context refactoring, remove offset root from block container
- Cleanup inline-block baseline
- Add visual test for static positions, update other tests
- Make containing blocks and offset parents more closely coincide
  - Partly change how absolute offsets and clipping regions are calculated, and enable static positions outside the current block formatting contexts
- Some refactoring
- Refactoring, make containing block more closely follow CSS behavior
- Handle indefinite containing blocks in some situations
- Share float space in block formatting context
- Some cleanup of block container
- Additional layout cleanup
- Add float text wrap test
- Fix repeated words on wrapped text
- Establish absolute positioning containing block when element has local transform or perspective
- Fix possible case of infinite loop in inline layout
- Cleanup floats, add test
- Fix overflowing floated boxes, cleanup, update tests
- Include floating space when determining the shrink-to-fit width
- Shrink-to-fit width uses available size
- Cleanup BuildBox
- Refactor formatting contexts and shrink-to-fit width
- Cleanup absolute positioning
- Refactor Layout
- More layout refactoring
- Cleanup block container
- More layout cleanup
- Formatting context cleanup
- More cleanup
- Further layout cleanup and comments
- Refactor layout pools
- Update Box user for previous change
- Fix some build issues
Michael Ragazzon 3 년 전
부모
커밋
4742dda07d
100개의 변경된 파일6026개의 추가작업 그리고 4263개의 파일을 삭제
  1. 34 20
      CMake/FileList.cmake
  2. 22 7
      Include/RmlUi/Core/ComputedValues.h
  3. 13 16
      Include/RmlUi/Core/Element.h
  4. 4 7
      Include/RmlUi/Core/ElementText.h
  5. 5 23
      Include/RmlUi/Core/FontEngineInterface.h
  6. 49 0
      Include/RmlUi/Core/FontMetrics.h
  7. 5 7
      Include/RmlUi/Core/GeometryUtilities.h
  8. 13 13
      Samples/basic/bitmapfont/src/FontEngineBitmap.cpp
  9. 3 10
      Samples/basic/bitmapfont/src/FontEngineBitmap.h
  10. 2 27
      Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp
  11. 3 12
      Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h
  12. 20 0
      Source/Core/ComputeProperty.cpp
  13. 2 0
      Source/Core/ComputeProperty.h
  14. 6 6
      Source/Core/ComputedValues.cpp
  15. 18 27
      Source/Core/Element.cpp
  16. 1 1
      Source/Core/ElementDocument.cpp
  17. 5 8
      Source/Core/ElementScroll.cpp
  18. 4 1
      Source/Core/ElementStyle.cpp
  19. 67 68
      Source/Core/ElementText.cpp
  20. 5 5
      Source/Core/ElementUtilities.cpp
  21. 10 4
      Source/Core/Elements/WidgetTextInput.cpp
  22. 3 27
      Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp
  23. 2 12
      Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h
  24. 2 28
      Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp
  25. 3 14
      Source/Core/FontEngineDefault/FontFaceHandleDefault.h
  26. 5 5
      Source/Core/FontEngineDefault/FontProvider.cpp
  27. 0 10
      Source/Core/FontEngineDefault/FontTypes.h
  28. 7 12
      Source/Core/FontEngineDefault/FreeTypeInterface.cpp
  29. 1 0
      Source/Core/FontEngineDefault/FreeTypeInterface.h
  30. 3 22
      Source/Core/FontEngineInterface.cpp
  31. 10 25
      Source/Core/GeometryUtilities.cpp
  32. 561 0
      Source/Core/Layout/BlockContainer.cpp
  33. 195 0
      Source/Core/Layout/BlockContainer.h
  34. 295 0
      Source/Core/Layout/BlockFormattingContext.cpp
  35. 71 0
      Source/Core/Layout/BlockFormattingContext.h
  36. 295 0
      Source/Core/Layout/ContainerBox.cpp
  37. 164 0
      Source/Core/Layout/ContainerBox.h
  38. 84 65
      Source/Core/Layout/FlexFormattingContext.cpp
  39. 67 0
      Source/Core/Layout/FlexFormattingContext.h
  40. 72 100
      Source/Core/Layout/FloatedBoxSpace.cpp
  41. 127 0
      Source/Core/Layout/FloatedBoxSpace.h
  42. 75 0
      Source/Core/Layout/FormattingContext.cpp
  43. 67 0
      Source/Core/Layout/FormattingContext.h
  44. 156 0
      Source/Core/Layout/InlineBox.cpp
  45. 99 0
      Source/Core/Layout/InlineBox.h
  46. 315 0
      Source/Core/Layout/InlineContainer.cpp
  47. 135 0
      Source/Core/Layout/InlineContainer.h
  48. 233 0
      Source/Core/Layout/InlineLevelBox.cpp
  49. 145 0
      Source/Core/Layout/InlineLevelBox.h
  50. 69 0
      Source/Core/Layout/InlineTypes.h
  51. 59 0
      Source/Core/Layout/LayoutBox.cpp
  52. 78 0
      Source/Core/Layout/LayoutBox.h
  53. 131 115
      Source/Core/Layout/LayoutDetails.cpp
  54. 28 31
      Source/Core/Layout/LayoutDetails.h
  55. 50 0
      Source/Core/Layout/LayoutEngine.cpp
  56. 53 0
      Source/Core/Layout/LayoutEngine.h
  57. 89 0
      Source/Core/Layout/LayoutPools.cpp
  58. 44 0
      Source/Core/Layout/LayoutPools.h
  59. 429 0
      Source/Core/Layout/LineBox.cpp
  60. 214 0
      Source/Core/Layout/LineBox.h
  61. 125 109
      Source/Core/Layout/TableFormattingContext.cpp
  62. 93 0
      Source/Core/Layout/TableFormattingContext.h
  63. 50 38
      Source/Core/Layout/TableFormattingDetails.cpp
  64. 39 37
      Source/Core/Layout/TableFormattingDetails.h
  65. 0 793
      Source/Core/LayoutBlockBox.cpp
  66. 0 254
      Source/Core/LayoutBlockBox.h
  67. 0 127
      Source/Core/LayoutBlockBoxSpace.h
  68. 0 456
      Source/Core/LayoutEngine.cpp
  69. 0 91
      Source/Core/LayoutEngine.h
  70. 0 76
      Source/Core/LayoutFlex.h
  71. 0 411
      Source/Core/LayoutInlineBox.cpp
  72. 0 176
      Source/Core/LayoutInlineBox.h
  73. 0 217
      Source/Core/LayoutInlineBoxText.cpp
  74. 0 99
      Source/Core/LayoutInlineBoxText.h
  75. 0 408
      Source/Core/LayoutLineBox.cpp
  76. 0 125
      Source/Core/LayoutLineBox.h
  77. 0 104
      Source/Core/LayoutTable.h
  78. 1 1
      Source/Core/WidgetScroll.cpp
  79. 1 0
      Tests/Data/VisualTests/border_radius.rml
  80. 51 0
      Tests/Data/VisualTests/display_block_inside_inline.rml
  81. 1 0
      Tests/Data/VisualTests/flex_02.rml
  82. 50 0
      Tests/Data/VisualTests/flex_absolutely_positioned.rml
  83. 3 4
      Tests/Data/VisualTests/float_basic.rml
  84. 59 0
      Tests/Data/VisualTests/float_excluded_by_flow_root.rml
  85. 59 0
      Tests/Data/VisualTests/float_placement.rml
  86. 42 0
      Tests/Data/VisualTests/float_text_wrap.rml
  87. 55 0
      Tests/Data/VisualTests/inline_formatting_01.rml
  88. 27 0
      Tests/Data/VisualTests/inline_formatting_02.rml
  89. 31 0
      Tests/Data/VisualTests/inline_formatting_03.rml
  90. 66 0
      Tests/Data/VisualTests/inline_formatting_04.rml
  91. 58 0
      Tests/Data/VisualTests/inline_formatting_05.rml
  92. 57 0
      Tests/Data/VisualTests/inline_formatting_06.rml
  93. 55 0
      Tests/Data/VisualTests/inline_formatting_07.rml
  94. 97 0
      Tests/Data/VisualTests/inline_formatting_aligned_subtree.rml
  95. 1 1
      Tests/Data/VisualTests/overflow_hidden.rml
  96. 28 8
      Tests/Data/VisualTests/overflow_nested.rml
  97. 59 0
      Tests/Data/VisualTests/position_absolute_static_position.rml
  98. 61 0
      Tests/Data/VisualTests/position_absolute_transform.rml
  99. 75 0
      Tests/Data/VisualTests/position_relative.rml
  100. 55 0
      Tests/Data/VisualTests/reference/position_absolute_static_position-ref.rml

+ 34 - 20
CMake/FileList.cmake

@@ -60,16 +60,23 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.h
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.h
     ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBox.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBoxSpace.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutDetails.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutEngine.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutFlex.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutTable.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutTableDetails.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockContainer.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockFormattingContext.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/ContainerBox.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/FlexFormattingContext.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/FloatedBoxSpace.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/FormattingContext.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/InlineBox.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/InlineContainer.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/InlineLevelBox.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/InlineTypes.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LayoutBox.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LayoutDetails.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LayoutEngine.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LayoutPools.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LineBox.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingContext.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingDetails.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Memory.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Pool.h
@@ -169,6 +176,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEffectInstancer.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEngineInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontGlyph.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontMetrics.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Geometry.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/GeometryUtilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Header.h
@@ -327,16 +335,22 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBox.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBoxSpace.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutDetails.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutEngine.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutFlex.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBox.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutInlineBoxText.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutLineBox.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutTable.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/LayoutTableDetails.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockContainer.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockFormattingContext.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/ContainerBox.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/FlexFormattingContext.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/FloatedBoxSpace.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/FormattingContext.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/InlineBox.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/InlineContainer.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/InlineLevelBox.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LayoutBox.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LayoutDetails.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LayoutEngine.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LayoutPools.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/LineBox.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingContext.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingDetails.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Log.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Memory.cpp

+ 22 - 7
Include/RmlUi/Core/ComputedValues.h

@@ -127,7 +127,7 @@ namespace Style {
 		Colourb color = Colourb(255, 255, 255);
 
 		FontWeight font_weight;
-		
+
 		FontStyle font_style : 1;
 		bool has_font_effect : 1;
 		PointerEvents pointer_events : 1;
@@ -150,7 +150,8 @@ namespace Style {
 			max_height_type(LengthPercentage::Length),
 
 			perspective_origin_x_type(LengthPercentage::Percentage), perspective_origin_y_type(LengthPercentage::Percentage),
-			transform_origin_x_type(LengthPercentage::Percentage), transform_origin_y_type(LengthPercentage::Percentage),
+			transform_origin_x_type(LengthPercentage::Percentage), transform_origin_y_type(LengthPercentage::Percentage), has_local_transform(false),
+			has_local_perspective(false),
 
 			flex_basis_type(LengthPercentageAuto::Auto), row_gap_type(LengthPercentage::Length), column_gap_type(LengthPercentage::Length),
 
@@ -162,6 +163,7 @@ namespace Style {
 
 		LengthPercentage::Type perspective_origin_x_type : 1, perspective_origin_y_type : 1;
 		LengthPercentage::Type transform_origin_x_type : 1, transform_origin_y_type : 1;
+		bool has_local_transform : 1, has_local_perspective : 1;
 
 		LengthPercentageAuto::Type flex_basis_type : 2;
 		LengthPercentage::Type row_gap_type : 1, column_gap_type : 1;
@@ -267,6 +269,8 @@ namespace Style {
 		TransformOrigin   transform_origin_x()         const { return LengthPercentage(rare.transform_origin_x_type, rare.transform_origin_x); }
 		TransformOrigin   transform_origin_y()         const { return LengthPercentage(rare.transform_origin_y_type, rare.transform_origin_y); }
 		float             transform_origin_z()         const { return rare.transform_origin_z; }
+		bool              has_local_transform()        const { return rare.has_local_transform; }
+		bool              has_local_perspective()      const { return rare.has_local_perspective; }
 		AlignContent      align_content()              const { return GetLocalPropertyKeyword(PropertyId::AlignContent, AlignContent::Stretch); }
 		AlignItems        align_items()                const { return GetLocalPropertyKeyword(PropertyId::AlignItems, AlignItems::Stretch); }
 		AlignSelf         align_self()                 const { return GetLocalPropertyKeyword(PropertyId::AlignSelf, AlignSelf::Auto); }
@@ -354,6 +358,8 @@ namespace Style {
 		void flex_basis                (FlexBasis value)         { rare.flex_basis_type            = value.type; rare.flex_basis                 = value.value; }
 		void transform_origin_z        (float value)             { rare.transform_origin_z         = value; }
 		void perspective               (float value)             { rare.perspective                = value; }
+		void has_local_perspective     (bool value)              { rare.has_local_perspective      = value; }
+		void has_local_transform       (bool value)              { rare.has_local_transform        = value; }
 		void border_top_left_radius    (float value)             { rare.border_top_left_radius     = (int16_t)value; }
 		void border_top_right_radius   (float value)             { rare.border_top_right_radius    = (int16_t)value; }
 		void border_bottom_right_radius(float value)             { rare.border_bottom_right_radius = (int16_t)value; }
@@ -399,11 +405,20 @@ namespace Style {
 
 } // namespace Style
 
-// Resolves a computed LengthPercentage value to the base unit 'px'.
-// Percentages are scaled by the base_value.
-// Note: Auto must be manually handled during layout, here it returns zero.
-RMLUICORE_API float ResolveValue(Style::LengthPercentageAuto length, float base_value);
-RMLUICORE_API float ResolveValue(Style::LengthPercentage length, float base_value);
+// Resolves a computed LengthPercentage(Auto) value to the base unit 'px'.
+// Percentages are scaled by the base value, if definite (>= 0), otherwise return the default value.
+// Auto lengths always return the default value.
+RMLUICORE_API float ResolveValueOr(Style::LengthPercentageAuto length, float base_value, float default_value);
+RMLUICORE_API float ResolveValueOr(Style::LengthPercentage length, float base_value, float default_value);
+
+RMLUICORE_API_INLINE float ResolveValue(Style::LengthPercentageAuto length, float base_value)
+{
+	return ResolveValueOr(length, base_value, 0.f);
+}
+RMLUICORE_API_INLINE float ResolveValue(Style::LengthPercentage length, float base_value)
+{
+	return ResolveValueOr(length, base_value, 0.f);
+}
 
 using ComputedValues = Style::ComputedValues;
 

+ 13 - 16
Include/RmlUi/Core/Element.h

@@ -54,9 +54,9 @@ class ElementDefinition;
 class ElementDocument;
 class ElementScroll;
 class ElementStyle;
-class LayoutEngine;
-class LayoutInlineBox;
-class LayoutBlockBox;
+class FormattingContext;
+class InlineLevelBox;
+class ContainerBox;
 class PropertiesIteratorView;
 class PropertyDictionary;
 class RenderInterface;
@@ -153,11 +153,10 @@ public:
 	/// @return The box area used as the element's client area.
 	Box::Area GetClientArea() const;
 
-	/// Sets the dimensions of the element's internal content. This is the tightest fitting box surrounding all of
-	/// this element's logical children, plus the element's padding.
-	/// @param[in] content_offset The offset of the box's internal content.
-	/// @param[in] content_box The dimensions of the box's internal content.
-	void SetContentBox(Vector2f content_offset, Vector2f content_box);
+	/// Sets the dimensions of the element's scrollable overflow rectangle. This is the tightest fitting box surrounding
+	/// all of this element's logical children, and the element's padding box.
+	/// @param[in] scrollable_overflow_rectangle The dimensions of the box's scrollable content.
+	void SetScrollableOverflowRectangle(Vector2f scrollable_overflow_rectangle);
 	/// Sets the box describing the size of the element, and removes all others.
 	/// @param[in] box The new dimensions box for the element.
 	void SetBox(const Box& box);
@@ -177,8 +176,8 @@ public:
 	/// @return the number of boxes making up this element's geometry.
 	int GetNumBoxes();
 
-	/// Returns the baseline of the element, in pixels offset from the bottom of the element's content area.
-	/// @return The element's baseline. A negative baseline will be further 'up' the element, a positive on further 'down'. The default element will return 0.
+	/// Returns the baseline of the element, in pixel offset from the element's bottom margin edge (positive up).
+	/// @return The element's baseline. The default element will return 0.
 	virtual float GetBaseline() const;
 	/// Gets the intrinsic dimensions of this element, if it is of a type that has an inherent size. This size will
 	/// only be overriden by a styled width or height.
@@ -779,9 +778,8 @@ private:
 	Box main_box;
 	PositionedBoxList additional_boxes;
 
-	// And of the element's internal content.
-	Vector2f content_offset;
-	Vector2f content_box;
+	// And of the element's scrollable content.
+	Vector2f scrollable_overflow_rectangle;
 
 	float baseline;
 	float z_index;
@@ -796,9 +794,8 @@ private:
 
 	friend class Rml::Context;
 	friend class Rml::ElementStyle;
-	friend class Rml::LayoutEngine;
-	friend class Rml::LayoutBlockBox;
-	friend class Rml::LayoutInlineBox;
+	friend class Rml::ContainerBox;
+	friend class Rml::InlineLevelBox;
 	friend class Rml::ElementScroll;
 	friend RMLUICORE_API void Rml::ReleaseFontResources();
 };

+ 4 - 7
Include/RmlUi/Core/ElementText.h

@@ -52,11 +52,6 @@ public:
 	/// Returns the raw string this text element contains.
 	const String& GetText() const;
 
-	/// Generates a token of text from this element, returning only the width.
-	/// @param[out] token_width The window (in pixels) of the token.
-	/// @param[in] token_begin The first character to be included in the token.
-	/// @return True if the token is the end of the element's text, false if not.
-	bool GenerateToken(float& token_width, int token_begin);
 	/// Generates a line of text rendered from this element.
 	/// @param[out] line The characters making up the line, with white-space characters collapsed and endlines processed appropriately.
 	/// @param[out] line_length The number of characters from the source string consumed making up this string; this may very well be different from line.size()!
@@ -66,15 +61,17 @@ public:
 	/// @param[in] right_spacing_width The width (in pixels) of the spacing (consisting of margins, padding, etc) that must be remaining on the right of the line if the last of the text is rendered onto this line.
 	/// @param[in] trim_whitespace_prefix If we're collapsing whitespace, whether or not to remove all prefixing whitespace or collapse it down to a single space.
 	/// @param[in] decode_escape_characters Decode escaped characters such as & into &.
+	/// @param[in] allow_empty Allow no tokens to be consumed from the line.
 	/// @return True if the line reached the end of the element's text, false if not.
-	bool GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix, bool decode_escape_characters);
+	bool GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width,
+		bool trim_whitespace_prefix, bool decode_escape_characters, bool allow_empty);
 
 	/// Clears all lines of generated text and prepares the element for generating new lines.
 	void ClearLines();
 	/// Adds a new line into the text element.
 	/// @param[in] line_position The position of this line, as an offset from the first line.
 	/// @param[in] line The contents of the line.
-	void AddLine(Vector2f line_position, const String& line);
+	void AddLine(Vector2f line_position, String line);
 
 	/// Prevents the element from dirtying its document's layout when its text is changed.
 	void SuppressAutoLayout();

+ 5 - 23
Include/RmlUi/Core/FontEngineInterface.h

@@ -28,8 +28,9 @@
 #ifndef RMLUI_CORE_FONTENGINEINTERFACE_H
 #define RMLUI_CORE_FONTENGINEINTERFACE_H
 
-#include "Header.h"
+#include "FontMetrics.h"
 #include "Geometry.h"
+#include "Header.h"
 #include "StyleTypes.h"
 #include "Types.h"
 
@@ -82,29 +83,10 @@ public:
 	/// @return A handle to the prepared font effects which will be used when generating geometry for a string.
 	virtual FontEffectsHandle PrepareFontEffects(FontFaceHandle handle, const FontEffectList &font_effects);
 
-	/// Should return the point size of this font face.
-	/// @param[in] handle The font handle.
-	/// @return The face's point size.
-	virtual int GetSize(FontFaceHandle handle);
-	/// Should return the pixel height of a lower-case x in this font face.
-	/// @param[in] handle The font handle.
-	/// @return The height of a lower-case x.
-	virtual int GetXHeight(FontFaceHandle handle);
-	/// Should return the default height between this font face's baselines.
-	/// @param[in] handle The font handle.
-	/// @return The default line height.
-	virtual int GetLineHeight(FontFaceHandle handle);
-
-	/// Should return the font's baseline, as a pixel offset from the bottom of the font.
-	/// @param[in] handle The font handle.
-	/// @return The font's baseline.
-	virtual int GetBaseline(FontFaceHandle handle);
-
-	/// Should return the font's underline, as a pixel offset from the bottom of the font.
+	/// Should return the font metrics of the given font face.
 	/// @param[in] handle The font handle.
-	/// @param[out] thickness The font's underline thickness in pixels.
-	/// @return The underline pixel offset.
-	virtual float GetUnderline(FontFaceHandle handle, float &thickness);
+	/// @return The face's metrics.
+	virtual const FontMetrics& GetFontMetrics(FontFaceHandle handle);
 
 	/// Called by RmlUi when it wants to retrieve the width of a string when rendered with this handle.
 	/// @param[in] handle The font handle.

+ 49 - 0
Include/RmlUi/Core/FontMetrics.h

@@ -0,0 +1,49 @@
+/*
+ * 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
+ *
+ * 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_FONTMETRICS_H
+#define RMLUI_CORE_FONTMETRICS_H
+
+#include "Header.h"
+
+namespace Rml {
+
+struct FontMetrics {
+	int size; // Specified font size [px].
+
+	float ascent;       // Distance above the baseline to the upper grid coordinate [px, positive above baseline].
+	float descent;      // Distance below the baseline to the lower grid coordinate [px, positive below baseline].
+	float line_spacing; // Font-specified distance between two consecutive baselines [px].
+
+	float x_height; // Height of the lowercase 'x' character [px].
+
+	float underline_position;  // Position of the underline relative to the baseline [px, positive below baseline].
+	float underline_thickness; // Width of underline [px].
+};
+
+} // namespace Rml
+#endif

+ 5 - 7
Include/RmlUi/Core/GeometryUtilities.h

@@ -67,14 +67,12 @@ public:
 	/// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array.
 	static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, Vector2f top_left_texcoord, Vector2f bottom_right_texcoord, int index_offset = 0);
 
-	/// Generates the geometry required to render a line above, below or through a line of text.
-	/// @param[in] font_face_handle The font face handle to derive the line's metrics from.
+	/// Generates the geometry required to render a line.
 	/// @param[out] geometry The geometry to append the newly created geometry into.
-	/// @param[in] position The position of the baseline of the lined text.
-	/// @param[in] width The width of the string to line.
-	/// @param[in] decoration_type The type for vertical positioning of line.
-	/// @param[in] colour The colour to draw the line in.
-	static void GenerateLine(FontFaceHandle font_face_handle, Geometry* geometry, Vector2f position, int width, Style::TextDecoration decoration_type, Colourb colour);
+	/// @param[in] position The top-left position the line.
+	/// @param[in] position The size of the line.
+	/// @param[in] color The color to draw the line in.
+	static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color);
 
 	/// Generates a geometry in the same way as element backgrounds and borders are generated, with support for the border-radius property.
 	/// Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.

+ 13 - 13
Samples/basic/bitmapfont/src/FontEngineBitmap.cpp

@@ -82,7 +82,7 @@ namespace FontProviderBitmap
 				return false;
 
 			// Fill the remaining metrics
-			parser.metrics.underline_position = -parser.metrics.baseline - 1.f;
+			parser.metrics.underline_position = 3.f;
 			parser.metrics.underline_thickness = 1.f;
 		}
 
@@ -130,8 +130,11 @@ namespace FontProviderBitmap
 }
 
 
-FontFaceBitmap::FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, Texture texture, Vector2f texture_dimensions, FontGlyphs&& glyphs, FontKerning&& kerning)
-	: family(family), style(style), weight(weight), metrics(metrics), texture(texture), texture_dimensions(texture_dimensions), glyphs(std::move(glyphs)), kerning(std::move(kerning)) 
+FontFaceBitmap::FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, Texture texture, Vector2f texture_dimensions,
+	FontGlyphs&& glyphs, FontKerning&& kerning) :
+	family(family),
+	style(style), weight(weight), metrics(metrics), texture(texture), texture_dimensions(texture_dimensions), glyphs(std::move(glyphs)),
+	kerning(std::move(kerning))
 {}
 
 int FontFaceBitmap::GetStringWidth(const String& string, Character previous_character)
@@ -249,8 +252,9 @@ void FontParserBitmap::HandleElementStart(const String& name, const Rml::XMLAttr
 	}
 	else if (name == "common")
 	{
-		metrics.line_height = Get(attributes, "lineHeight", 0);
-		metrics.baseline = metrics.line_height - Get(attributes, "base", 0);
+		metrics.line_spacing = Get(attributes, "lineHeight", 0.f);
+		metrics.ascent = Get(attributes, "base", 0.f);
+		metrics.descent = metrics.line_spacing - metrics.ascent;
 
 		texture_dimensions.x = Get(attributes, "scaleW", 0.f);
 		texture_dimensions.y = Get(attributes, "scaleH", 0.f);
@@ -273,21 +277,17 @@ void FontParserBitmap::HandleElementStart(const String& name, const Rml::XMLAttr
 
 		BitmapGlyph& glyph = glyphs[character];
 
-		glyph.advance = Get(attributes, "xadvance", 0);
-
-		// Shift y-origin from top to baseline
-		float origin_shift_y = float(metrics.baseline - metrics.line_height);
-
 		glyph.offset.x = Get(attributes, "xoffset", 0.f);
-		glyph.offset.y = Get(attributes, "yoffset", 0.f) + origin_shift_y;
-		
+		glyph.offset.y = Get(attributes, "yoffset", 0.f) - metrics.ascent; // Shift y-origin from top to baseline
+
+		glyph.advance = Get(attributes, "xadvance", 0);
 		glyph.position.x = Get(attributes, "x", 0.f);
 		glyph.position.y = Get(attributes, "y", 0.f);
 		glyph.dimension.x = Get(attributes, "width", 0.f);
 		glyph.dimension.y = Get(attributes, "height", 0.f);
 
 		if (character == (Character)'x')
-			metrics.x_height = (int)glyph.dimension.y;
+			metrics.x_height = glyph.dimension.y;
 	}
 	else if (name == "kerning")
 	{

+ 3 - 10
Samples/basic/bitmapfont/src/FontEngineBitmap.h

@@ -51,14 +51,6 @@ struct BitmapGlyph {
 	Vector2f dimension = { 0, 0 };
 };
 
-struct FontMetrics {
-	int size = 0;
-	int x_height = 0;
-	int line_height = 0;
-	int baseline = 0;
-	float underline_position = 0, underline_thickness = 0;
-};
-
 // A mapping of characters to their glyphs.
 using FontGlyphs = Rml::UnorderedMap<Character, BitmapGlyph>;
 
@@ -68,7 +60,8 @@ using FontKerning = Rml::UnorderedMap<std::uint64_t, int>;
 
 class FontFaceBitmap {
 public:
-	FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, Texture texture, Vector2f texture_dimensions, FontGlyphs&& glyphs, FontKerning&& kerning);
+	FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, Texture texture, Vector2f texture_dimensions,
+		FontGlyphs&& glyphs, FontKerning&& kerning);
 
 	// Get width of string.
 	int GetStringWidth(const String& string, Character prior_character);
@@ -124,7 +117,7 @@ public:
 	String texture_name;
 	Vector2f texture_dimensions = { 0, 0 };
 
-	FontMetrics metrics;
+	FontMetrics metrics = {};
 	FontGlyphs glyphs;
 	FontKerning kerning;
 };

+ 2 - 27
Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp

@@ -67,35 +67,10 @@ FontEffectsHandle FontEngineInterfaceBitmap::PrepareFontEffects(FontFaceHandle /
 	return 0;
 }
 
-int FontEngineInterfaceBitmap::GetSize(FontFaceHandle handle)
+const FontMetrics& FontEngineInterfaceBitmap::GetFontMetrics(FontFaceHandle handle)
 {
 	auto handle_bitmap = reinterpret_cast<FontFaceBitmap*>(handle);
-	return handle_bitmap->GetMetrics().size;
-}
-
-int FontEngineInterfaceBitmap::GetXHeight(FontFaceHandle handle)
-{
-	auto handle_bitmap = reinterpret_cast<FontFaceBitmap*>(handle);
-	return handle_bitmap->GetMetrics().x_height;
-}
-
-int FontEngineInterfaceBitmap::GetLineHeight(FontFaceHandle handle)
-{
-	auto handle_bitmap = reinterpret_cast<FontFaceBitmap*>(handle);
-	return handle_bitmap->GetMetrics().line_height;
-}
-
-int FontEngineInterfaceBitmap::GetBaseline(FontFaceHandle handle)
-{
-	auto handle_bitmap = reinterpret_cast<FontFaceBitmap*>(handle);
-	return handle_bitmap->GetMetrics().baseline;
-}
-
-float FontEngineInterfaceBitmap::GetUnderline(FontFaceHandle handle, float& thickness)
-{
-	auto handle_bitmap = reinterpret_cast<FontFaceBitmap*>(handle);
-	thickness = handle_bitmap->GetMetrics().underline_thickness;
-	return handle_bitmap->GetMetrics().underline_position;
+	return handle_bitmap->GetMetrics();
 }
 
 int FontEngineInterfaceBitmap::GetStringWidth(FontFaceHandle handle, const String& string, Character prior_character)

+ 3 - 12
Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h

@@ -48,6 +48,7 @@ using Rml::byte;
 
 using Rml::FontEffectList;
 using Rml::GeometryList;
+using Rml::FontMetrics;
 
 
 class FontEngineInterfaceBitmap : public Rml::FontEngineInterface
@@ -70,18 +71,8 @@ public:
 	/// Called by RmlUi when a list of font effects is resolved for an element with a given font face.
 	FontEffectsHandle PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects) override;
 
-	/// Should return the point size of this font face.
-	int GetSize(FontFaceHandle handle) override;
-	/// Should return the pixel height of a lower-case x in this font face.
-	int GetXHeight(FontFaceHandle handle) override;
-	/// Should return the default height between this font face's baselines.
-	int GetLineHeight(FontFaceHandle handle) override;
-
-	/// Should return the font's baseline, as a pixel offset from the bottom of the font.
-	int GetBaseline(FontFaceHandle handle) override;
-
-	/// Should return the font's underline, as a pixel offset from the bottom of the font.
-	float GetUnderline(FontFaceHandle handle, float& thickness) override;
+	/// Should return the font metrics of the given font face.
+	const FontMetrics& GetFontMetrics(FontFaceHandle handle) override;
 
 	/// Called by RmlUi when it wants to retrieve the width of a string when rendered with this handle.
 	int GetStringWidth(FontFaceHandle handle, const String& string, Character prior_character = Character::Null) override;

+ 20 - 0
Source/Core/ComputeProperty.cpp

@@ -29,6 +29,7 @@
 #include "ComputeProperty.h"
 #include "../../Include/RmlUi/Core/ComputedValues.h"
 #include "../../Include/RmlUi/Core/Property.h"
+#include "../../Include/RmlUi/Core/StringUtilities.h"
 
 namespace Rml {
 
@@ -313,4 +314,23 @@ uint16_t ComputeBorderWidth(float computed_length)
 	return uint16_t(computed_length + 0.5f);
 }
 
+String GetFontFaceDescription(const String& font_family, Style::FontStyle style, Style::FontWeight weight)
+{
+	String font_attributes;
+
+	if (style == Style::FontStyle::Italic)
+		font_attributes += "italic, ";
+	if (weight == Style::FontWeight::Bold)
+		font_attributes += "bold, ";
+	else if (weight != Style::FontWeight::Auto && weight != Style::FontWeight::Normal)
+		font_attributes += "weight=" + ToString((int)weight) + ", ";
+
+	if (font_attributes.empty())
+		font_attributes = "regular";
+	else
+		font_attributes.resize(font_attributes.size() - 2);
+
+	return CreateString(font_attributes.size() + font_family.size() + 8, "'%s' [%s]", font_family.c_str(), font_attributes.c_str());
+}
+
 } // namespace Rml

+ 2 - 0
Source/Core/ComputeProperty.h

@@ -62,6 +62,8 @@ Style::LengthPercentage ComputeMaxSize(const Property* property, float font_size
 
 uint16_t ComputeBorderWidth(float computed_length);
 
+String GetFontFaceDescription(const String& font_family, Style::FontStyle style, Style::FontWeight weight);
+
 extern const Style::ComputedValues DefaultComputedValues;
 
 } // namespace Rml

+ 6 - 6
Source/Core/ComputedValues.cpp

@@ -68,22 +68,22 @@ String Style::ComputedValues::cursor() const
 	return String();
 }
 
-float ResolveValue(Style::LengthPercentageAuto length, float base_value)
+float ResolveValueOr(Style::LengthPercentageAuto length, float base_value, float default_value)
 {
 	if (length.type == Style::LengthPercentageAuto::Length)
 		return length.value;
-	else if (length.type == Style::LengthPercentageAuto::Percentage)
+	else if (length.type == Style::LengthPercentageAuto::Percentage && base_value >= 0.f)
 		return length.value * 0.01f * base_value;
-	return 0.0f;
+	return default_value;
 }
 
-float ResolveValue(Style::LengthPercentage length, float base_value)
+float ResolveValueOr(Style::LengthPercentage length, float base_value, float default_value)
 {
 	if (length.type == Style::LengthPercentage::Length)
 		return length.value;
-	else if (length.type == Style::LengthPercentage::Percentage)
+	else if (length.type == Style::LengthPercentage::Percentage && base_value >= 0.f)
 		return length.value * 0.01f * base_value;
-	return 0.0f;
+	return default_value;
 }
 
 } // namespace Rml

+ 18 - 27
Source/Core/Element.cpp

@@ -53,7 +53,7 @@
 #include "EventDispatcher.h"
 #include "EventSpecification.h"
 #include "ElementDecoration.h"
-#include "LayoutEngine.h"
+#include "Layout/LayoutEngine.h"
 #include "PluginRegistry.h"
 #include "PropertiesIterator.h"
 #include "Pool.h"
@@ -109,10 +109,8 @@ static Pool< ElementMeta > element_meta_chunk_pool(200, true);
 Element::Element(const String& tag) :
 	local_stacking_context(false), local_stacking_context_forced(false), stacking_context_dirty(false), computed_values_are_default_initialized(true),
 	visible(true), offset_fixed(false), absolute_offset_dirty(true), dirty_definition(false), dirty_child_definitions(false), dirty_animation(false),
-	dirty_transition(false), dirty_transform(false), dirty_perspective(false),
-
-	tag(tag), relative_offset_base(0, 0), relative_offset_position(0, 0), absolute_offset(0, 0), scroll_offset(0, 0), content_offset(0, 0),
-	content_box(0, 0)
+	dirty_transition(false), dirty_transform(false), dirty_perspective(false), tag(tag), relative_offset_base(0, 0), relative_offset_position(0, 0),
+	absolute_offset(0, 0), scroll_offset(0, 0)
 {
 	RMLUI_ASSERT(tag == StringUtilities::ToLower(tag));
 	parent = nullptr;
@@ -410,23 +408,22 @@ Vector2f Element::GetAbsoluteOffset(Box::Area area)
 	{
 		absolute_offset_dirty = false;
 
-		if (offset_parent != nullptr)
+		if (offset_parent)
 			absolute_offset = offset_parent->GetAbsoluteOffset(Box::BORDER) + relative_offset_base + relative_offset_position;
 		else
 			absolute_offset = relative_offset_base + relative_offset_position;
 
-		// Add any parent scrolling onto our position as well. Could cache this if required.
 		if (!offset_fixed)
 		{
-			Element* scroll_parent = parent;
-			while (scroll_parent != nullptr)
-			{
-				absolute_offset -= (scroll_parent->scroll_offset + scroll_parent->content_offset);
-				if (scroll_parent == offset_parent)
-					break;
-				else
-					scroll_parent = scroll_parent->parent;
-			}
+			// Add any parent scrolling onto our position as well.
+			if (offset_parent)
+				absolute_offset -= offset_parent->scroll_offset;
+
+			// Finally, there may be relatively positioned elements between ourself and our containing block, add their relative offsets as well.
+			// TODO: Uhm, this doesn't really make sense for absolutely positioned boxes. But then again, if we have a
+			// relative parent, then that should be our containing block. So I guess its fine?
+			for (Element* relative_parent = parent; relative_parent && relative_parent != offset_parent; relative_parent = relative_parent->parent)
+				absolute_offset += relative_parent->relative_offset_position;
 		}
 	}
 
@@ -446,17 +443,11 @@ Box::Area Element::GetClientArea() const
 }
 
 // Sets the dimensions of the element's internal content.
-void Element::SetContentBox(Vector2f _content_offset, Vector2f _content_box)
+void Element::SetScrollableOverflowRectangle(Vector2f _scrollable_overflow_rectangle)
 {
-	if (content_offset != _content_offset ||
-		content_box != _content_box)
+	if (scrollable_overflow_rectangle != _scrollable_overflow_rectangle)
 	{
-		// Seems to be jittering a wee bit; might need to be looked at.
-		scroll_offset.x += (content_offset.x - _content_offset.x);
-		scroll_offset.y += (content_offset.y - _content_offset.y);
-
-		content_offset = _content_offset;
-		content_box = _content_box;
+		scrollable_overflow_rectangle = _scrollable_overflow_rectangle;
 
 		scroll_offset.x = Math::Min(scroll_offset.x, GetScrollWidth() - GetClientWidth());
 		scroll_offset.y = Math::Min(scroll_offset.y, GetScrollHeight() - GetClientHeight());
@@ -1011,13 +1002,13 @@ void Element::SetScrollTop(float scroll_top)
 // Gets the width of the scrollable content of the element; it includes the element padding but not its margin.
 float Element::GetScrollWidth()
 {
-	return Math::Max(content_box.x, GetClientWidth());
+	return Math::Max(scrollable_overflow_rectangle.x, GetClientWidth());
 }
 
 // Gets the height of the scrollable content of the element; it includes the element padding but not its margin.
 float Element::GetScrollHeight()
 {
-	return Math::Max(content_box.y, GetClientHeight());
+	return Math::Max(scrollable_overflow_rectangle.y, GetClientHeight());
 }
 
 // Gets the object representing the declarations of an element's style attributes.

+ 1 - 1
Source/Core/ElementDocument.cpp

@@ -37,7 +37,7 @@
 #include "DocumentHeader.h"
 #include "ElementStyle.h"
 #include "EventDispatcher.h"
-#include "LayoutEngine.h"
+#include "Layout/LayoutEngine.h"
 #include "StreamFile.h"
 #include "StyleSheetFactory.h"
 #include "Template.h"

+ 5 - 8
Source/Core/ElementScroll.cpp

@@ -33,7 +33,7 @@
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/Event.h"
 #include "../../Include/RmlUi/Core/Factory.h"
-#include "LayoutDetails.h"
+#include "Layout/LayoutDetails.h"
 #include "WidgetScroll.h"
 
 namespace Rml {
@@ -91,6 +91,9 @@ void ElementScroll::DisableScrollbar(Orientation orientation)
 	{
 		scrollbars[orientation].element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
 		scrollbars[orientation].enabled = false;
+
+		if (corner)
+			corner->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
 	}
 }
 
@@ -190,8 +193,7 @@ void ElementScroll::FormatScrollbars()
 	}
 
 	// Format the corner, if it is necessary.
-	if (scrollbars[0].enabled &&
-		scrollbars[1].enabled)
+	if (scrollbars[0].enabled && scrollbars[1].enabled)
 	{
 		CreateCorner();
 
@@ -203,11 +205,6 @@ void ElementScroll::FormatScrollbars()
 
 		corner->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible));
 	}
-	else
-	{
-		if (corner != nullptr)
-			corner->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
-	}
 }
 
 // Creates one of the scroll component's scrollbar.

+ 4 - 1
Source/Core/ElementStyle.cpp

@@ -826,6 +826,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 
 		case PropertyId::Perspective:
 			values.perspective(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions));
+			values.has_local_perspective(values.perspective() > 0.f);
 			break;
 		case PropertyId::PerspectiveOriginX:
 			values.perspective_origin_x(ComputeOrigin(p, font_size, document_font_size, dp_ratio, vp_dimensions));
@@ -834,6 +835,9 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			values.perspective_origin_y(ComputeOrigin(p, font_size, document_font_size, dp_ratio, vp_dimensions));
 			break;
 
+		case PropertyId::Transform:
+			values.has_local_transform(p->Get<TransformPtr>() != nullptr);
+			break;
 		case PropertyId::TransformOriginX:
 			values.transform_origin_x(ComputeOrigin(p, font_size, document_font_size, dp_ratio, vp_dimensions));
 			break;
@@ -856,7 +860,6 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 
 		// Fetched from element's properties.
 		case PropertyId::Cursor:
-		case PropertyId::Transform:
 		case PropertyId::Transition:
 		case PropertyId::Animation:
 		case PropertyId::AlignContent:

+ 67 - 68
Source/Core/ElementText.cpp

@@ -27,23 +27,43 @@
  */
 
 #include "../../Include/RmlUi/Core/ElementText.h"
-#include "ElementDefinition.h"
-#include "ElementStyle.h"
-#include "../../Include/RmlUi/Core/Core.h"
 #include "../../Include/RmlUi/Core/Context.h"
+#include "../../Include/RmlUi/Core/Core.h"
 #include "../../Include/RmlUi/Core/ElementDocument.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/Event.h"
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../Include/RmlUi/Core/GeometryUtilities.h"
-#include "../../Include/RmlUi/Core/Property.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
+#include "../../Include/RmlUi/Core/Property.h"
+#include "ComputeProperty.h"
+#include "ElementDefinition.h"
+#include "ElementStyle.h"
 
 namespace Rml {
 
 static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters);
 static bool LastToken(const char* token_begin, const char* string_end, bool collapse_white_space, bool break_at_endline);
 
+void LogMissingFontFace(Element* element)
+{
+	const String font_family_property = element->GetProperty<String>("font-family");
+	if (font_family_property.empty())
+	{
+		Log::Message(Log::LT_WARNING, "No font face defined. Missing 'font-family' property. On element %s", element->GetAddress().c_str());
+	}
+	else
+	{
+		const ComputedValues& computed = element->GetComputedValues();
+		const String font_face_description = GetFontFaceDescription(font_family_property, computed.font_style(), computed.font_weight());
+		Log::Message(Log::LT_WARNING,
+			"No font face defined. Ensure (1) that Context::Update is run after new elements are constructed, before Context::Render, "
+			"and (2) that the specified font face %s has been successfully loaded. "
+			"Please see previous log messages for all successfully loaded fonts. On element %s",
+			font_face_description.c_str(), element->GetAddress().c_str());
+	}
+}
+
 ElementText::ElementText(const String& tag) :
 	Element(tag), colour(255, 255, 255), opacity(1), font_handle_version(0), geometry_dirty(true), dirty_layout_on_change(true),
 	generated_decoration(Style::TextDecoration::None), decoration_property(Style::TextDecoration::None), font_effects_dirty(true),
@@ -113,36 +133,32 @@ void ElementText::OnRender()
 	}
 
 	const Vector2f translation = GetAbsoluteOffset();
-	
+
 	bool render = true;
 	Vector2i clip_origin;
 	Vector2i clip_dimensions;
 	if (GetContext()->GetActiveClipRegion(clip_origin, clip_dimensions))
 	{
+		const FontMetrics& font_metrics = GetFontEngineInterface()->GetFontMetrics(GetFontFaceHandle());
 		float clip_top = (float)clip_origin.y;
 		float clip_left = (float)clip_origin.x;
 		float clip_right = (float)(clip_origin.x + clip_dimensions.x);
 		float clip_bottom = (float)(clip_origin.y + clip_dimensions.y);
-		float line_height = (float)GetFontEngineInterface()->GetLineHeight(GetFontFaceHandle());
-		
+		float ascent = font_metrics.ascent;
+		float descent = font_metrics.descent;
+
 		render = false;
-		for (size_t i = 0; i < lines.size(); ++i)
-		{			
-			const Line& line = lines[i];
-			float x = translation.x + line.position.x;
+		for (const Line& line : lines)
+		{
+			float x_left = translation.x + line.position.x;
+			float x_right = x_left + line.width;
 			float y = translation.y + line.position.y;
-			
-			bool render_line = !(x > clip_right);
-			render_line = render_line && !(x + line.width < clip_left);
-			
-			render_line = render_line && !(y - line_height > clip_bottom);
-			render_line = render_line && !(y < clip_top);
-			
-			if (render_line)
-			{
-				render = true;
+			float y_top = y - ascent;
+			float y_bottom = y + descent;
+
+			render = !(x_left > clip_right || x_right < clip_left || y_top > clip_bottom || y_bottom < clip_top);
+			if (render)
 				break;
-			}
 		}
 	}
 	
@@ -156,40 +172,12 @@ void ElementText::OnRender()
 		decoration->Render(translation);
 }
 
-// Generates a token of text from this element, returning only the width.
-bool ElementText::GenerateToken(float& token_width, int line_begin)
-{
-	RMLUI_ZoneScoped;
-
-	// Bail if we don't have a valid font face.
-	FontFaceHandle font_face_handle = GetFontFaceHandle();
-	if (font_face_handle == 0 || line_begin >= (int)text.size())
-		return false;
-
-	// Determine how we are processing white-space while formatting the text.
-	using namespace Style;
-	auto& computed = GetComputedValues();
-	WhiteSpace white_space_property = computed.white_space();
-	bool collapse_white_space = white_space_property == WhiteSpace::Normal ||
-								white_space_property == WhiteSpace::Nowrap ||
-								white_space_property == WhiteSpace::Preline;
-	bool break_at_endline = white_space_property == WhiteSpace::Pre ||
-							white_space_property == WhiteSpace::Prewrap ||
-							white_space_property == WhiteSpace::Preline;
-
-	const char* token_begin = text.c_str() + line_begin;
-	String token;
-
-	BuildToken(token, token_begin, text.c_str() + text.size(), true, collapse_white_space, break_at_endline, computed.text_transform(), true);
-	token_width = (float) GetFontEngineInterface()->GetStringWidth(font_face_handle, token);
-
-	return LastToken(token_begin, text.c_str() + text.size(), collapse_white_space, break_at_endline);
-}
-
 // Generates a line of text rendered from this element
-bool ElementText::GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width, bool trim_whitespace_prefix, bool decode_escape_characters)
+bool ElementText::GenerateLine(String& line, int& line_length, float& line_width, int line_begin, float maximum_line_width, float right_spacing_width,
+	bool trim_whitespace_prefix, bool decode_escape_characters, bool allow_empty)
 {
 	RMLUI_ZoneScoped;
+	RMLUI_ASSERT(maximum_line_width >= 0.f); // TODO: Check all callers for conformance, check break at line condition below. Possibly check for FLT_MAX.
 
 	FontFaceHandle font_face_handle = GetFontFaceHandle();
 
@@ -200,7 +188,10 @@ bool ElementText::GenerateLine(String& line, int& line_length, float& line_width
 
 	// Bail if we don't have a valid font face.
 	if (font_face_handle == 0)
+	{
+		LogMissingFontFace(GetParentNode() ? GetParentNode() : this);
 		return true;
+	}
 
 	// Determine how we are processing white-space while formatting the text.
 	using namespace Style;
@@ -270,7 +261,7 @@ bool ElementText::GenerateLine(String& line, int& line_length, float& line_width
 						else if (next_token_begin == token_begin)
 						{
 							// This means the first character of the token doesn't fit. Let it overflow into the next line if we can.
-							if (!line.empty())
+							if (allow_empty || !line.empty())
 								return false;
 
 							// Not even the first character of the line fits. Go back to consume the first character even though it will overflow.
@@ -281,7 +272,7 @@ bool ElementText::GenerateLine(String& line, int& line_length, float& line_width
 
 					break_line = true;
 				}
-				else if (!line.empty())
+				else if (allow_empty || !line.empty())
 				{
 					// Let the token overflow into the next line.
 					return false;
@@ -295,7 +286,7 @@ bool ElementText::GenerateLine(String& line, int& line_length, float& line_width
 		line_width += token_width;
 
 		// Break out of the loop if an endline was forced.
-		if (break_line)
+		if (break_line && (allow_empty || !line.empty()))
 			return false;
 
 		// Set the beginning of the next token.
@@ -317,18 +308,12 @@ void ElementText::ClearLines()
 }
 
 // Adds a new line into the text element.
-void ElementText::AddLine(Vector2f line_position, const String& line)
+void ElementText::AddLine(Vector2f line_position, String line)
 {
-	FontFaceHandle font_face_handle = GetFontFaceHandle();
-
-	if (font_face_handle == 0)
-		return;
-
 	if (font_effects_dirty)
 		UpdateFontEffects();
 
-	Vector2f baseline_position = line_position + Vector2f(0.0f, (float)GetFontEngineInterface()->GetLineHeight(font_face_handle) - GetFontEngineInterface()->GetBaseline(font_face_handle));
-	lines.emplace_back(line, baseline_position);
+	lines.emplace_back(std::move(line), line_position);
 
 	geometry_dirty = true;
 }
@@ -485,14 +470,28 @@ void ElementText::GenerateGeometry(const FontFaceHandle font_face_handle, Line&
 		geometry[i].SetHostElement(this);
 }
 
-// Generates any geometry necessary for rendering a line decoration (underline, strike-through, etc).
 void ElementText::GenerateDecoration(const FontFaceHandle font_face_handle)
 {
 	RMLUI_ZoneScopedC(0xA52A2A);
 	RMLUI_ASSERT(decoration);
-	
-	for(const Line& line : lines)
-		GeometryUtilities::GenerateLine(font_face_handle, decoration.get(), line.position, line.width, decoration_property, colour);
+
+	const FontMetrics& metrics = GetFontEngineInterface()->GetFontMetrics(font_face_handle);
+
+	float offset = 0.f;
+	switch (decoration_property)
+	{
+	case Style::TextDecoration::Underline: offset = metrics.underline_position; break;
+	case Style::TextDecoration::Overline: offset = -1.1f * metrics.ascent; break;
+	case Style::TextDecoration::LineThrough: offset = -0.65f * metrics.x_height; break;
+	case Style::TextDecoration::None: return;
+	}
+
+	for (const Line& line : lines)
+	{
+		const Vector2f position = {line.position.x, line.position.y + offset};
+		const Vector2f size = {(float)line.width, metrics.underline_thickness};
+		GeometryUtilities::GenerateLine(decoration.get(), position, size, colour);
+	}
 }
 
 static bool BuildToken(String& token, const char*& token_begin, const char* string_end, bool first_token, bool collapse_white_space, bool break_at_endline, Style::TextTransform text_transformation, bool decode_escape_characters)

+ 5 - 5
Source/Core/ElementUtilities.cpp

@@ -38,8 +38,8 @@
 #include "DataModel.h"
 #include "DataView.h"
 #include "ElementStyle.h"
-#include "LayoutDetails.h"
-#include "LayoutEngine.h"
+#include "Layout/LayoutDetails.h"
+#include "Layout/LayoutEngine.h"
 #include "TransformState.h"
 #include <limits>
 
@@ -179,7 +179,7 @@ bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_d
 	// Search through the element's ancestors, finding all elements that clip their overflow and have overflow to clip.
 	// For each that we find, we combine their clipping region with the existing clipping region, and so build up a
 	// complete clipping region for the element.
-	Element* clipping_element = element->GetParentNode();
+	Element* clipping_element = element->GetOffsetParent();
 
 	while (clipping_element != nullptr)
 	{
@@ -236,7 +236,7 @@ bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_d
 			break;
 
 		// Climb the tree to this region's parent.
-		clipping_element = clipping_element->GetParentNode();
+		clipping_element = clipping_element->GetOffsetParent();
 	}
 	
 	return clip_dimensions.x >= 0 && clip_dimensions.y >= 0;
@@ -303,7 +303,7 @@ void ElementUtilities::FormatElement(Element* element, Vector2f containing_block
 // Generates the box for an element.
 void ElementUtilities::BuildBox(Box& box, Vector2f containing_block, Element* element, bool inline_element)
 {
-	LayoutDetails::BuildBox(box, containing_block, element, inline_element ? BoxContext::Inline : BoxContext::Block);
+	LayoutDetails::BuildBox(box, containing_block, element, inline_element ? BuildBoxMode::Inline : BuildBoxMode::Block);
 }
 
 // Sizes an element, and positions it within its parent offset from the borders of its content area.

+ 10 - 4
Source/Core/Elements/WidgetTextInput.cpp

@@ -34,6 +34,7 @@
 #include "../../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h"
 #include "../../../Include/RmlUi/Core/Factory.h"
+#include "../../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../../Include/RmlUi/Core/GeometryUtilities.h"
 #include "../../../Include/RmlUi/Core/Input.h"
 #include "../../../Include/RmlUi/Core/Math.h"
@@ -977,7 +978,7 @@ void WidgetTextInput::FormatElement()
 			scroll->EnableScrollbar(ElementScroll::HORIZONTAL, width);
 	}
 
-	parent->SetContentBox(Vector2f(0, 0), content_area);
+	parent->SetScrollableOverflowRectangle(content_area);
 	scroll->FormatScrollbars();
 }
 
@@ -985,6 +986,10 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 {
 	Vector2f content_area(0, 0);
 
+	const FontFaceHandle font_handle = parent->GetFontFaceHandle();
+	if (!font_handle)
+		return content_area;
+
 	// Clear the old lines, and all the lines in the text elements.
 	lines.clear();
 	text_element->ClearLines();
@@ -998,12 +1003,13 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 
 	// Determine the line-height of the text element.
 	const float line_height = parent->GetLineHeight();
+	const float font_baseline = GetFontEngineInterface()->GetFontMetrics(font_handle).ascent;
 	// When the selection contains endlines we expand the selection area by this width.
 	const int endline_selection_width = int(0.4f * parent->GetComputedValues().font_size());
 
 	const float client_width = parent->GetClientWidth();
 	int line_begin = 0;
-	Vector2f line_position(0, 0);
+	Vector2f line_position(0, font_baseline);
 	bool last_line = false;
 
 	// Keep generating lines until all the text content is placed.
@@ -1021,7 +1027,7 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 		String line_content;
 
 		// Generate the next line.
-		last_line = text_element->GenerateLine(line_content, line.size, line_width, line_begin, client_width - cursor_size.x, 0, false, false);
+		last_line = text_element->GenerateLine(line_content, line.size, line_width, line_begin, client_width - cursor_size.x, 0, false, false, false);
 
 		// If this line terminates in a soft-return (word wrap), then the line may be leaving a space or two behind as an orphan. If so, we must
 		// append the orphan onto the line even though it will push the line outside of the input field's bounds.
@@ -1101,7 +1107,7 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 			selection_vertices.resize(selection_vertices.size() + 4);
 			selection_indices.resize(selection_indices.size() + 6);
 			GeometryUtilities::GenerateQuad(&selection_vertices[selection_vertices.size() - 4], &selection_indices[selection_indices.size() - 6],
-				line_position, selection_size, selection_colour, (int)selection_vertices.size() - 4);
+				line_position - Vector2f(0.f, font_baseline), selection_size, selection_colour, (int)selection_vertices.size() - 4);
 
 			line_position.x += selection_width;
 		}

+ 3 - 27
Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp

@@ -64,34 +64,10 @@ FontEffectsHandle FontEngineInterfaceDefault::PrepareFontEffects(FontFaceHandle
 	return (FontEffectsHandle)handle_default->GenerateLayerConfiguration(font_effects);
 }
 
-int FontEngineInterfaceDefault::GetSize(FontFaceHandle handle)
+const FontMetrics& FontEngineInterfaceDefault::GetFontMetrics(FontFaceHandle handle)
 {
-	auto handle_default = reinterpret_cast<FontFaceHandleDefault *>(handle);
-	return handle_default->GetSize();
-}
-
-int FontEngineInterfaceDefault::GetXHeight(FontFaceHandle handle)
-{
-	auto handle_default = reinterpret_cast<FontFaceHandleDefault *>(handle);
-	return handle_default->GetXHeight();
-}
-
-int FontEngineInterfaceDefault::GetLineHeight(FontFaceHandle handle)
-{
-	auto handle_default = reinterpret_cast<FontFaceHandleDefault *>(handle);
-	return handle_default->GetLineHeight();
-}
-
-int FontEngineInterfaceDefault::GetBaseline(FontFaceHandle handle)
-{
-	auto handle_default = reinterpret_cast<FontFaceHandleDefault *>(handle);
-	return handle_default->GetBaseline();
-}
-
-float FontEngineInterfaceDefault::GetUnderline(FontFaceHandle handle, float& thickness)
-{
-	auto handle_default = reinterpret_cast<FontFaceHandleDefault *>(handle);
-	return handle_default->GetUnderline(thickness);
+	auto handle_default = reinterpret_cast<FontFaceHandleDefault*>(handle);
+	return handle_default->GetFontMetrics();
 }
 
 int FontEngineInterfaceDefault::GetStringWidth(FontFaceHandle handle, const String& string, Character prior_character)

+ 2 - 12
Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h

@@ -52,18 +52,8 @@ public:
 	/// Prepares for font effects by configuring a new, or returning an existing, layer configuration.
 	FontEffectsHandle PrepareFontEffects(FontFaceHandle, const FontEffectList& font_effects) override;
 
-	/// Returns the point size of this font face.
-	int GetSize(FontFaceHandle) override;
-	/// Returns the pixel height of a lower-case x in this font face.
-	int GetXHeight(FontFaceHandle) override;
-	/// Returns the default height between this font face's baselines.
-	int GetLineHeight(FontFaceHandle) override;
-
-	/// Returns the font's baseline, as a pixel offset from the bottom of the font.
-	int GetBaseline(FontFaceHandle) override;
-
-	/// Returns the font's underline, as a pixel offset from the bottom of the font.
-	float GetUnderline(FontFaceHandle, float& thickness) override;
+	/// Returns the font metrics of the given font face.
+	const FontMetrics& GetFontMetrics(FontFaceHandle handle) override;
 
 	/// Returns the width a string will take up if rendered with this handle.
 	int GetStringWidth(FontFaceHandle, const String& string, Character prior_character) override;

+ 2 - 28
Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp

@@ -71,42 +71,16 @@ bool FontFaceHandleDefault::Initialize(FontFaceHandleFreetype face, int font_siz
 	return true;
 }
 
-// Returns the point size of this font face.
-int FontFaceHandleDefault::GetSize() const
+const FontMetrics& FontFaceHandleDefault::GetFontMetrics() const
 {
-	return metrics.size;
+	return metrics;
 }
 
-// Returns the pixel height of a lower-case x in this font face.
-int FontFaceHandleDefault::GetXHeight() const
-{
-	return metrics.x_height;
-}
-
-// Returns the default height between this font face's baselines.
-int FontFaceHandleDefault::GetLineHeight() const
-{
-	return metrics.line_height;
-}
-
-// Returns the font's baseline.
-int FontFaceHandleDefault::GetBaseline() const
-{
-	return metrics.baseline;
-}
-
-// Returns the font's glyphs.
 const FontGlyphMap& FontFaceHandleDefault::GetGlyphs() const
 {
 	return glyphs;
 }
 
-float FontFaceHandleDefault::GetUnderline(float& thickness) const
-{
-	thickness = metrics.underline_thickness;
-	return metrics.underline_position;
-}
-
 // Returns the width a string will take up if rendered with this handle.
 int FontFaceHandleDefault::GetStringWidth(const String& string, Character prior_character)
 {

+ 3 - 14
Source/Core/FontEngineDefault/FontFaceHandleDefault.h

@@ -32,6 +32,7 @@
 #include "../../../Include/RmlUi/Core/Traits.h"
 #include "../../../Include/RmlUi/Core/FontEffect.h"
 #include "../../../Include/RmlUi/Core/FontGlyph.h"
+#include "../../../Include/RmlUi/Core/FontMetrics.h"
 #include "../../../Include/RmlUi/Core/Geometry.h"
 #include "../../../Include/RmlUi/Core/Texture.h"
 #include "FontTypes.h"
@@ -53,20 +54,8 @@ public:
 
 	bool Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs);
 
-	/// Returns the point size of this font face.
-	int GetSize() const;
-	/// Returns the pixel height of a lower-case x in this font face.
-	int GetXHeight() const;
-	/// Returns the default height between this font face's baselines.
-	int GetLineHeight() const;
-
-	/// Returns the font's baseline, as a pixel offset from the bottom of the font.
-	int GetBaseline() const;
-
-	/// Returns the font's underline, as a pixel offset from the bottom of the font.
-	float GetUnderline(float& thickness) const;
-
-	/// Returns the font's glyphs.
+	const FontMetrics& GetFontMetrics() const;
+	
 	const FontGlyphMap& GetGlyphs() const;
 
 	/// Returns the width a string will take up if rendered with this handle.

+ 5 - 5
Source/Core/FontEngineDefault/FontProvider.cpp

@@ -27,15 +27,15 @@
  */
 
 #include "FontProvider.h"
-#include "FontFace.h"
-#include "FontFamily.h"
-#include "FreeTypeInterface.h"
-#include "../LayoutInlineBoxText.h"
 #include "../../../Include/RmlUi/Core/Core.h"
 #include "../../../Include/RmlUi/Core/FileInterface.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 #include "../../../Include/RmlUi/Core/Math.h"
 #include "../../../Include/RmlUi/Core/StringUtilities.h"
+#include "../ComputeProperty.h"
+#include "FontFace.h"
+#include "FontFamily.h"
+#include "FreeTypeInterface.h"
 #include <algorithm>
 
 namespace Rml {
@@ -217,7 +217,7 @@ bool FontProvider::LoadFontFace(const byte* data, int data_size, bool fallback_f
 			FreeType::GetFaceStyle(ft_face, nullptr, nullptr, &weight);
 
 		const FontWeight variation_weight = (variation.weight == FontWeight::Auto ? weight : variation.weight);
-		const String font_face_description = FontFaceDescription(font_family, style, variation_weight);
+		const String font_face_description = GetFontFaceDescription(font_family, style, variation_weight);
 
 		if (!AddFace(ft_face, font_family, style, variation_weight, fallback_face, std::move(face_memory)))
 		{

+ 0 - 10
Source/Core/FontEngineDefault/FontTypes.h

@@ -37,16 +37,6 @@ namespace Rml {
 
 using FontFaceHandleFreetype = uintptr_t;
 
-struct FontMetrics {
-	int size;
-	int x_height;
-	int line_height;
-	int baseline;
-
-	float underline_position;
-	float underline_thickness;
-};
-
 struct FaceVariation {
 	Style::FontWeight weight;
 	uint16_t width;

+ 7 - 12
Source/Core/FontEngineDefault/FreeTypeInterface.cpp

@@ -28,6 +28,7 @@
 
 #include "FreeTypeInterface.h"
 #include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/FontMetrics.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 #include <algorithm>
 #include <string.h>
@@ -484,26 +485,20 @@ static bool BuildGlyph(FT_Face ft_face, const Character character, FontGlyphMap&
 
 static void GenerateMetrics(FT_Face ft_face, FontMetrics& metrics, float bitmap_scaling_factor)
 {
-	metrics.line_height = ft_face->size->metrics.height >> 6;
-	metrics.baseline = metrics.line_height - (ft_face->size->metrics.ascender >> 6);
+	metrics.ascent = ft_face->size->metrics.ascender * bitmap_scaling_factor / float(1 << 6);
+	metrics.descent = -ft_face->size->metrics.descender * bitmap_scaling_factor / float(1 << 6);
+	metrics.line_spacing = ft_face->size->metrics.height * bitmap_scaling_factor / float(1 << 6);
 
-	metrics.underline_position = FT_MulFix(ft_face->underline_position, ft_face->size->metrics.y_scale) * bitmap_scaling_factor / float(1 << 6);
+	metrics.underline_position = FT_MulFix(-ft_face->underline_position, ft_face->size->metrics.y_scale) * bitmap_scaling_factor / float(1 << 6);
 	metrics.underline_thickness = FT_MulFix(ft_face->underline_thickness, ft_face->size->metrics.y_scale) * bitmap_scaling_factor / float(1 << 6);
 	metrics.underline_thickness = Math::Max(metrics.underline_thickness, 1.0f);
 
 	// Determine the x-height of this font face.
 	FT_UInt index = FT_Get_Char_Index(ft_face, 'x');
 	if (index != 0 && FT_Load_Glyph(ft_face, index, 0) == 0)
-		metrics.x_height = ft_face->glyph->metrics.height >> 6;
+		metrics.x_height = ft_face->glyph->metrics.height * bitmap_scaling_factor / float(1 << 6);
 	else
-		metrics.x_height = metrics.line_height / 2;
-
-	if (bitmap_scaling_factor != 1.f)
-	{
-		metrics.line_height = int(float(metrics.line_height) * bitmap_scaling_factor);
-		metrics.baseline = int(float(metrics.baseline) * bitmap_scaling_factor);
-		metrics.x_height = int(float(metrics.x_height) * bitmap_scaling_factor);
-	}
+		metrics.x_height = 0.5f * metrics.line_spacing;
 }
 
 static bool SetFontSize(FT_Face ft_face, int font_size, float& out_bitmap_scaling_factor)

+ 1 - 0
Source/Core/FontEngineDefault/FreeTypeInterface.h

@@ -30,6 +30,7 @@
 #define RMLUI_CORE_FONTENGINEDEFAULT_FREETYPEINTERFACE_H
 
 #include "FontTypes.h"
+#include "../../../Include/RmlUi/Core/FontMetrics.h"
 
 namespace Rml {
 

+ 3 - 22
Source/Core/FontEngineInterface.cpp

@@ -56,29 +56,10 @@ FontEffectsHandle FontEngineInterface::PrepareFontEffects(FontFaceHandle /*handl
 	return 0;
 }
 
-int FontEngineInterface::GetSize(FontFaceHandle /*handle*/)
+const FontMetrics& FontEngineInterface::GetFontMetrics(FontFaceHandle /*handle*/)
 {
-	return 0;
-}
-
-int FontEngineInterface::GetXHeight(FontFaceHandle /*handle*/)
-{
-	return 0;
-}
-
-int FontEngineInterface::GetLineHeight(FontFaceHandle /*handle*/)
-{
-	return 0;
-}
-
-int FontEngineInterface::GetBaseline(FontFaceHandle /*handle*/)
-{
-	return 0;
-}
-
-float FontEngineInterface::GetUnderline(FontFaceHandle /*handle*/, float& /*thickness*/)
-{
-	return 0;
+	static const FontMetrics metrics = {};
+	return metrics;
 }
 
 int FontEngineInterface::GetStringWidth(FontFaceHandle /*handle*/, const String& /*string*/, Character /*prior_character*/)

+ 10 - 25
Source/Core/GeometryUtilities.cpp

@@ -77,35 +77,20 @@ void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f or
 	indices[5] = index_offset + 2;
 }
 
-// Generates the geometry required to render a line above, below or through a line of text.
-void GeometryUtilities::GenerateLine(FontFaceHandle font_face_handle, Geometry* geometry, Vector2f position, int width, Style::TextDecoration height, Colourb colour)
+void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color)
 {
-	Vector< Vertex >& line_vertices = geometry->GetVertices();
-	Vector< int >& line_indices = geometry->GetIndices();
-	float underline_thickness = 0;
-	float underline_position = GetFontEngineInterface()->GetUnderline(font_face_handle, underline_thickness);
-	int size = GetFontEngineInterface()->GetSize(font_face_handle);
-	int x_height = GetFontEngineInterface()->GetXHeight(font_face_handle);
-
-	float offset;
-	switch (height)
-	{
-		case Style::TextDecoration::Underline:       offset = -underline_position; break;
-		case Style::TextDecoration::Overline:        offset = -underline_position - (float)size; break;
-		case Style::TextDecoration::LineThrough:     offset = -0.65f * (float)x_height; break;
-		default: return;
-	}
+	Math::SnapToPixelGrid(position, size);
+
+	Vector<Vertex>& line_vertices = geometry->GetVertices();
+	Vector<int>& line_indices = geometry->GetIndices();
+
+	const int vertices_i0 = (int)line_vertices.size();
+	const int indices_i0 = (int)line_indices.size();
 
 	line_vertices.resize(line_vertices.size() + 4);
 	line_indices.resize(line_indices.size() + 6);
-	GeometryUtilities::GenerateQuad(
-									&line_vertices[0] + ((int)line_vertices.size() - 4),
-									&line_indices[0] + ((int)line_indices.size() - 6),
-									Vector2f(position.x, position.y + offset).Round(),
-									Vector2f((float) width, underline_thickness),
-									colour,
-									(int)line_vertices.size() - 4
-									);
+
+	GeometryUtilities::GenerateQuad(line_vertices.data() + vertices_i0, line_indices.data() + indices_i0, position, size, color, vertices_i0);
 }
 
 void GeometryUtilities::GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb background_colour, const Colourb* border_colours)

+ 561 - 0
Source/Core/Layout/BlockContainer.cpp

@@ -0,0 +1,561 @@
+/*
+ * 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 "BlockContainer.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/ElementScroll.h"
+#include "../../../Include/RmlUi/Core/Profiling.h"
+#include "FloatedBoxSpace.h"
+#include "InlineContainer.h"
+#include "LayoutDetails.h"
+#include "LineBox.h"
+
+namespace Rml {
+
+BlockContainer::BlockContainer(ContainerBox* _parent_container, FloatedBoxSpace* _space, Element* _element, const Box& _box, float _min_height,
+	float _max_height) :
+	ContainerBox(Type::BlockContainer, _element, _parent_container),
+	box(_box), min_height(_min_height), max_height(_max_height), space(_space)
+{
+	RMLUI_ASSERT(element);
+
+	if (!space)
+	{
+		// We are the root of the formatting context, establish a new space for floated boxes.
+		root_space = MakeUnique<FloatedBoxSpace>();
+		space = root_space.get();
+	}
+}
+
+BlockContainer::~BlockContainer() {}
+
+bool BlockContainer::Close(BlockContainer* parent_block_container)
+{
+	// If the last child of this block box is an inline box, then we haven't closed it; close it now!
+	if (!CloseOpenInlineContainer())
+		return false;
+
+	// Set this box's height, if necessary.
+	if (box.GetSize().y < 0)
+	{
+		float content_height = box_cursor;
+
+		if (!parent_block_container)
+			content_height = Math::Max(content_height, space->GetDimensions(FloatedBoxEdge::Margin).y - (position.y + box.GetPosition().y));
+
+		content_height = Math::Clamp(content_height, min_height, max_height);
+		box.SetContent({box.GetSize().x, content_height});
+	}
+
+	// Find the size of our content.
+	const Vector2f space_box = space->GetDimensions(FloatedBoxEdge::Overflow) - (position + box.GetPosition());
+	Vector2f content_box = Math::Max(inner_content_size, space_box);
+	content_box.y = Math::Max(content_box.y, box_cursor);
+
+	if (!SubmitBox(content_box, box, max_height))
+		return false;
+
+	// If we are the root of our block formatting context, this will be null. Otherwise increment our parent's cursor to account for this box.
+	if (parent_block_container)
+	{
+		RMLUI_ASSERTMSG(GetParent() == parent_block_container, "Mismatched parent box.");
+
+		// If this close fails, it means this block box has caused our parent box to generate an automatic vertical scrollbar.
+		if (!parent_block_container->EncloseChildBox(this, position, box.GetSizeAcross(Box::VERTICAL, Box::BORDER),
+				box.GetEdge(Box::MARGIN, Box::BOTTOM)))
+			return false;
+	}
+
+	// Now that we have been sized, we can proceed with formatting and placing positioned elements that this container
+	// acts as a containing block for.
+	ClosePositionedElements();
+
+	// Find the element baseline which is the distance from the margin bottom of the element to its baseline.
+	float element_baseline = 0;
+
+	// For inline-blocks with visible overflow, this is the baseline of the last line of the element (see CSS2 10.8.1).
+	if (element->GetDisplay() == Style::Display::InlineBlock && !IsScrollContainer())
+	{
+		float baseline = 0;
+		bool found_baseline = GetBaselineOfLastLine(baseline);
+
+		// The retrieved baseline is the vertical distance from the top of our root space (the coordinate system of our
+		// local block formatting context), convert it to the element's local coordinates.
+		if (found_baseline)
+		{
+			const float bottom_position = position.y + box.GetSizeAcross(Box::VERTICAL, Box::BORDER) + box.GetEdge(Box::MARGIN, Box::BOTTOM);
+			element_baseline = bottom_position - baseline;
+		}
+	}
+
+	SetElementBaseline(element_baseline);
+
+	EnsureEmptyInterruptedLineBox();
+
+	SubmitElementLayout();
+
+	return true;
+}
+
+bool BlockContainer::EncloseChildBox(LayoutBox* child, Vector2f child_position, float child_height, float child_margin_bottom)
+{
+	child_position -= (box.GetPosition() + position);
+
+	box_cursor = child_position.y + child_height + child_margin_bottom;
+
+	// Extend the inner content size. The vertical size can be larger than the box_cursor due to overflow.
+	inner_content_size = Math::Max(inner_content_size, child_position + child->GetVisibleOverflowSize());
+
+	const Vector2f content_size = Math::Max(Vector2f{box.GetSize().x, box_cursor}, inner_content_size);
+
+	const bool result = CatchOverflow(content_size, box, max_height);
+
+	return result;
+}
+
+BlockContainer* BlockContainer::OpenBlockBox(Element* child_element, const Box& child_box, float min_height, float max_height)
+{
+	if (!CloseOpenInlineContainer())
+		return nullptr;
+
+	auto child_container_ptr = MakeUnique<BlockContainer>(this, space, child_element, child_box, min_height, max_height);
+	BlockContainer* child_container = child_container_ptr.get();
+
+	child_container->position = NextBoxPosition(child_box, child_element->GetComputedValues().clear());
+	child_element->SetOffset(child_container->position - position, element);
+
+	child_container->ResetScrollbars(child_box);
+
+	// Store relatively positioned elements with their containing block so that their offset can be updated after
+	// their containing block has been sized.
+	if (child_element->GetPosition() == Style::Position::Relative)
+		AddRelativeElement(child_element);
+
+	child_boxes.push_back(std::move(child_container_ptr));
+
+	return child_container;
+}
+
+LayoutBox* BlockContainer::AddBlockLevelBox(UniquePtr<LayoutBox> block_level_box_ptr, Element* child_element, const Box& child_box)
+{
+	RMLUI_ASSERT(child_box.GetSize().y >= 0.f); // Assumes child element already formatted and sized.
+
+	if (!CloseOpenInlineContainer())
+		return nullptr;
+
+	// Clear any floats to avoid overlapping them. In CSS, it is allowed to instead shrink the box and place it next to
+	// any floats, but we keep it simple here for now and just clear them.
+	Vector2f child_position = NextBoxPosition(child_box, Style::Clear::Both);
+
+	child_element->SetOffset(child_position - position, element);
+
+	if (child_element->GetPosition() == Style::Position::Relative)
+		AddRelativeElement(child_element);
+
+	LayoutBox* block_level_box = block_level_box_ptr.get();
+	child_boxes.push_back(std::move(block_level_box_ptr));
+
+	if (!EncloseChildBox(block_level_box, child_position, child_box.GetSizeAcross(Box::VERTICAL, Box::BORDER),
+			child_box.GetEdge(Box::MARGIN, Box::BOTTOM)))
+		return nullptr;
+
+	return block_level_box;
+}
+
+InlineBoxHandle BlockContainer::AddInlineElement(Element* element, const Box& child_box)
+{
+	RMLUI_ZoneScoped;
+
+	// Inline-level elements need to be added to an inline container, open one if needed.
+	InlineContainer* inline_container = EnsureOpenInlineContainer();
+
+	InlineBox* inline_box = inline_container->AddInlineElement(element, child_box);
+
+	if (element->GetPosition() == Style::Position::Relative)
+		AddRelativeElement(element);
+
+	return {inline_box};
+}
+
+void BlockContainer::CloseInlineElement(InlineBoxHandle handle)
+{
+	// If the inline-level element did not generate an inline box, then there is no need to close anything.
+	if (!handle.inline_box)
+		return;
+
+	// Usually the inline container the box was placed in is still the open box, and we can just close the inline
+	// element in it. However, it is possible that an intermediary block-level element was placed, thereby splitting the
+	// inline element into multiple inline containers around the block-level box. If we don't have an open inline
+	// container at all, open a new one, even if the sole purpose of the new line is to close this inline element.
+	EnsureOpenInlineContainer()->CloseInlineElement(handle.inline_box);
+}
+
+void BlockContainer::AddBreak()
+{
+	const float line_height = element->GetLineHeight();
+
+	// Check for an inline box as our last child; if so, we can simply end its line and bail.
+	if (InlineContainer* inline_container = GetOpenInlineContainer())
+	{
+		inline_container->AddBreak(line_height);
+		return;
+	}
+
+	// No inline box as our last child; no problem, just increment the cursor by the line height of this element.
+	box_cursor += line_height;
+}
+
+void BlockContainer::AddFloatElement(Element* element, Vector2f visible_overflow_size)
+{
+	if (InlineContainer* inline_container = GetOpenInlineContainer())
+	{
+		// Try to add the float to our inline container, placing it next to any open line if possible. Otherwise, queue it for later.
+		bool float_placed = false;
+		float line_position_top = 0.f;
+		Vector2f line_size;
+		if (queued_float_elements.empty() && inline_container->GetOpenLineBoxDimensions(line_position_top, line_size))
+		{
+			const Vector2f margin_size = element->GetBox().GetSize(Box::MARGIN);
+			const Style::Float float_property = element->GetComputedValues().float_();
+			const Style::Clear clear_property = element->GetComputedValues().clear();
+
+			float available_width = 0.f;
+			const Vector2f float_position =
+				space->NextFloatPosition(this, available_width, line_position_top, margin_size, float_property, clear_property);
+
+			const float line_position_bottom = line_position_top + line_size.y;
+			const float line_and_element_width = margin_size.x + line_size.x;
+
+			// If the float can be positioned on the open line, and it can fit next to the line's contents, place it now.
+			if (float_position.y < line_position_bottom && line_and_element_width <= available_width)
+			{
+				PlaceFloat(element, line_position_top, visible_overflow_size);
+				inline_container->UpdateOpenLineBoxPlacement();
+				float_placed = true;
+			}
+		}
+
+		if (!float_placed)
+			queued_float_elements.push_back({element, visible_overflow_size});
+	}
+	else
+	{
+		// There is no inline container, so just place it!
+		const Vector2f box_position = NextBoxPosition();
+		PlaceFloat(element, box_position.y, visible_overflow_size);
+	}
+
+	if (element->GetPosition() == Style::Position::Relative)
+		AddRelativeElement(element);
+}
+
+Vector2f BlockContainer::GetOpenStaticPosition(Style::Display display) const
+{
+	// Estimate the next box as if it had static position (10.6.4). If the element is inline-level, position it on the
+	// open line if we have one. Otherwise, block-level elements are positioned on a hypothetical next line.
+	Vector2f static_position = NextBoxPosition();
+
+	if (const InlineContainer* inline_container = GetOpenInlineContainer())
+	{
+		const bool inline_level_element = (display == Style::Display::Inline || display == Style::Display::InlineBlock);
+		static_position += inline_container->GetStaticPositionEstimate(inline_level_element);
+	}
+
+	return static_position;
+}
+
+Vector2f BlockContainer::NextBoxPosition() const
+{
+	Vector2f box_position = position + box.GetPosition();
+	box_position.y += box_cursor;
+	return box_position;
+}
+
+Vector2f BlockContainer::NextBoxPosition(const Box& child_box, Style::Clear clear_property) const
+{
+	const float child_top_margin = child_box.GetEdge(Box::MARGIN, Box::TOP);
+
+	Vector2f box_position = NextBoxPosition();
+
+	box_position.x += child_box.GetEdge(Box::MARGIN, Box::LEFT);
+	box_position.y += child_top_margin;
+
+	float clear_margin = space->DetermineClearPosition(box_position.y, clear_property) - box_position.y;
+	if (clear_margin > 0.f)
+	{
+		box_position.y += clear_margin;
+	}
+	else if (const LayoutBox* block_box = GetOpenLayoutBox())
+	{
+		// Check for a collapsing vertical margin with our last child, which will be vertically adjacent to the new box.
+		if (const Box* open_box = block_box->GetIfBox())
+		{
+			const float open_bottom_margin = open_box->GetEdge(Box::MARGIN, Box::BOTTOM);
+			const float margin_sum = open_bottom_margin + child_top_margin;
+
+			// The collapsed margin size depends on the sign of each margin, according to CSS behavior. The margins have
+			// already been added to the 'box_position', so subtract their sum as needed.
+			const int num_negative_margins = int(child_top_margin < 0.f) + int(open_bottom_margin < 0.f);
+			switch (num_negative_margins)
+			{
+			case 0:
+				// Use the largest margin.
+				box_position.y += Math::Max(child_top_margin, open_bottom_margin) - margin_sum;
+				break;
+			case 1:
+				// Use the sum of the positive and negative margin. These are already added to the position, so do nothing.
+				break;
+			case 2:
+				// Use the most negative margin.
+				box_position.y += Math::Min(child_top_margin, open_bottom_margin) - margin_sum;
+				break;
+			}
+		}
+	}
+
+	return box_position;
+}
+
+void BlockContainer::PlaceQueuedFloats(float vertical_position)
+{
+	if (!queued_float_elements.empty())
+	{
+		for (QueuedFloat entry : queued_float_elements)
+			PlaceFloat(entry.element, vertical_position, entry.visible_overflow_size);
+
+		queued_float_elements.clear();
+	}
+}
+
+float BlockContainer::GetShrinkToFitWidth() const
+{
+	auto& computed = element->GetComputedValues();
+
+	float content_width = 0.0f;
+	if (computed.width().type == Style::Width::Length)
+	{
+		// We have a definite width, so use that size.
+		content_width = computed.width().value;
+	}
+	else
+	{
+		// Nope, then use the largest outer shrink-to-fit width of our children. Percentage sizing would be relative to
+		// our containing block width and is treated just like 'auto' in this context.
+		for (const auto& block_box : child_boxes)
+		{
+			const float child_inner_width = block_box->GetShrinkToFitWidth();
+			float child_edges_width = 0.f;
+			if (const Box* child_box = block_box->GetIfBox())
+				child_edges_width = child_box->GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING);
+
+			content_width = Math::Max(content_width, child_edges_width + child_inner_width);
+		}
+	}
+
+	if (root_space)
+	{
+		// Since we are the root of the block formatting context, add the width contributions of the floated boxes in
+		// our context. The basic algorithm used can produce overestimates, since floats may not be located next to the
+		// rest of the content.
+		const float edge_left = box.GetPosition().x;
+		const float edge_right = edge_left + box.GetSize().x;
+		content_width += space->GetShrinkToFitWidth(edge_left, edge_right);
+	}
+
+	float min_width, max_width;
+	LayoutDetails::GetMinMaxWidth(min_width, max_width, computed, box, 0.f);
+	content_width = Math::Clamp(content_width, min_width, max_width);
+
+	return content_width;
+}
+
+const Box* BlockContainer::GetIfBox() const
+{
+	return &box;
+}
+
+Element* BlockContainer::GetElement() const
+{
+	return element;
+}
+
+const FloatedBoxSpace* BlockContainer::GetBlockBoxSpace() const
+{
+	return space;
+}
+
+Vector2f BlockContainer::GetPosition() const
+{
+	return position;
+}
+
+Box& BlockContainer::GetBox()
+{
+	return box;
+}
+
+const Box& BlockContainer::GetBox() const
+{
+	return box;
+}
+
+void BlockContainer::ResetContents()
+{
+	RMLUI_ZoneScopedC(0xDD3322);
+
+	if (root_space)
+		root_space->Reset();
+
+	child_boxes.clear();
+	queued_float_elements.clear();
+
+	box_cursor = 0;
+	interrupted_line_box.reset();
+
+	inner_content_size = {};
+}
+
+String BlockContainer::DebugDumpTree(int depth) const
+{
+	String value = String(depth * 2, ' ') + "BlockContainer" + " | " + LayoutDetails::GetDebugElementName(element) + '\n';
+
+	for (auto&& block_box : child_boxes)
+		value += block_box->DumpLayoutTree(depth + 1);
+
+	return value;
+}
+
+InlineContainer* BlockContainer::GetOpenInlineContainer()
+{
+	return const_cast<InlineContainer*>(static_cast<const BlockContainer&>(*this).GetOpenInlineContainer());
+}
+
+const InlineContainer* BlockContainer::GetOpenInlineContainer() const
+{
+	if (!child_boxes.empty() && child_boxes.back()->GetType() == Type::InlineContainer)
+		return static_cast<InlineContainer*>(child_boxes.back().get());
+	return nullptr;
+}
+
+InlineContainer* BlockContainer::EnsureOpenInlineContainer()
+{
+	// First check to see if we already have an open inline container.
+	InlineContainer* inline_container = GetOpenInlineContainer();
+
+	// Otherwise, we open a new one.
+	if (!inline_container)
+	{
+		const float scrollbar_width = (IsScrollContainer() ? element->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL) : 0.f);
+		const float available_width = box.GetSize().x - scrollbar_width;
+
+		auto inline_container_ptr = MakeUnique<InlineContainer>(this, available_width);
+		inline_container = inline_container_ptr.get();
+		child_boxes.push_back(std::move(inline_container_ptr));
+
+		if (interrupted_line_box)
+		{
+			inline_container->AddChainedBox(std::move(interrupted_line_box));
+			interrupted_line_box.reset();
+		}
+	}
+
+	return inline_container;
+}
+
+const LayoutBox* BlockContainer::GetOpenLayoutBox() const
+{
+	if (!child_boxes.empty())
+		return child_boxes.back().get();
+	return nullptr;
+}
+
+bool BlockContainer::CloseOpenInlineContainer()
+{
+	if (InlineContainer* inline_container = GetOpenInlineContainer())
+	{
+		EnsureEmptyInterruptedLineBox();
+
+		Vector2f child_position;
+		float child_height = 0.f;
+		inline_container->Close(&interrupted_line_box, child_position, child_height);
+
+		// Increment our cursor. If this close fails, it means this block container generated an automatic scrollbar.
+		if (!EncloseChildBox(inline_container, child_position, child_height, 0.f))
+			return false;
+	}
+
+	return true;
+}
+
+void BlockContainer::EnsureEmptyInterruptedLineBox()
+{
+	if (interrupted_line_box)
+	{
+		RMLUI_ERROR; // Internal error: Interrupted line box leaked.
+		interrupted_line_box.reset();
+	}
+}
+
+void BlockContainer::PlaceFloat(Element* element, float vertical_position, Vector2f visible_overflow_size)
+{
+	const Box& element_box = element->GetBox();
+
+	const Vector2f border_size = element_box.GetSize(Box::BORDER);
+	visible_overflow_size = Math::Max(border_size, visible_overflow_size);
+
+	const Vector2f margin_top_left = {element_box.GetEdge(Box::MARGIN, Box::LEFT), element_box.GetEdge(Box::MARGIN, Box::TOP)};
+	const Vector2f margin_bottom_right = {element_box.GetEdge(Box::MARGIN, Box::RIGHT), element_box.GetEdge(Box::MARGIN, Box::BOTTOM)};
+	const Vector2f margin_size = border_size + margin_top_left + margin_bottom_right;
+
+	Style::Float float_property = element->GetComputedValues().float_();
+	Style::Clear clear_property = element->GetComputedValues().clear();
+
+	float unused_box_width = 0.f;
+	const Vector2f margin_position = space->NextFloatPosition(this, unused_box_width, vertical_position, margin_size, float_property, clear_property);
+	const Vector2f border_position = margin_position + margin_top_left;
+
+	space->PlaceFloat(float_property, margin_position, margin_size, border_position, visible_overflow_size);
+
+	// Shift the offset into this container's space, which acts as the float element's containing block.
+	element->SetOffset(border_position - position, GetElement());
+}
+
+bool BlockContainer::GetBaselineOfLastLine(float& out_baseline) const
+{
+	// Return the baseline of our last child that itself has a baseline.
+	for (int i = (int)child_boxes.size() - 1; i >= 0; i--)
+	{
+		if (child_boxes[i]->GetBaselineOfLastLine(out_baseline))
+			return true;
+	}
+
+	return false;
+}
+
+} // namespace Rml

+ 195 - 0
Source/Core/Layout/BlockContainer.h

@@ -0,0 +1,195 @@
+/*
+ * 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_LAYOUT_BLOCKCONTAINER_H
+#define RMLUI_CORE_LAYOUT_BLOCKCONTAINER_H
+
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "ContainerBox.h"
+
+namespace Rml {
+
+class FloatedBoxSpace;
+class LineBox;
+class InlineBox;
+class InlineContainer;
+struct InlineBoxHandle {
+	InlineBox* inline_box;
+};
+
+/**
+    A container for block-level boxes.
+
+    Takes part in a block formatting context. Block-level boxes are stacked vertically as they are added. Inline-level
+    elements create a new inline container if one is not already open. The inline container itself acts as an anonymous
+    block-level box in this block container.
+
+    Positions are relative to the root of the block formatting context this box takes part in, unless otherwise noted.
+    However, elements themselves are offset relative to their containing block. We act as the containing block for
+    static and relative children, and possibly for absolutely positioned descendants.
+
+ */
+class BlockContainer final : public ContainerBox {
+public:
+	/// Creates a new block box for rendering a block element.
+	/// @param[in] parent The parent of this block box. This will be nullptr for the root element.
+	/// @param[in] element The element this block box is laying out.
+	/// @param[in] box The box used for this block box.
+	/// @param[in] min_height The minimum height of the content box.
+	/// @param[in] max_height The maximum height of the content box.
+	BlockContainer(ContainerBox* parent_container, FloatedBoxSpace* space, Element* element, const Box& box, float min_height, float max_height);
+	~BlockContainer();
+
+	/// Closes the box. This will determine this container's height if it was unspecified.
+	/// @param[in] parent_block_container Our parent which will be sized to contain this box, or nullptr for the root of the block formatting context.
+	/// @return False if the block box caused an automatic vertical scrollbar to appear, forcing a reformat of the current block formatting context.
+	bool Close(BlockContainer* parent_block_container);
+
+	/// Creates and opens a new block box, and adds it as a child of this one.
+	/// @param[in] element The new block element.
+	/// @param[in] box The dimensions of the new box, whose content height may be indefinite (-1).
+	/// @param[in] min_height The minimum height of the content box.
+	/// @param[in] max_height The maximum height of the content box.
+	/// @return The block box representing the element. Once the element's children have been positioned, Close() must be called on it.
+	BlockContainer* OpenBlockBox(Element* element, const Box& box, float min_height, float max_height);
+
+	/// Adds a block-level box whose contents have already been formatted in an independent formatting context.
+	/// @param[in] block_level_box The box to add as a new child of this.
+	/// @param[in] element The element represented by the new box.
+	/// @param[in] box The dimensions of the new box.
+	/// @return A pointer to the added block-level box.
+	LayoutBox* AddBlockLevelBox(UniquePtr<LayoutBox> block_level_box, Element* element, const Box& box);
+
+	// Adds an element to this block container to be handled as a floating element.
+	void AddFloatElement(Element* element, Vector2f visible_overflow_size);
+
+	/// Adds a new inline-level element to this block container.
+	/// @param[in] element The new inline element.
+	/// @param[in] box The box defining the element's bounds.
+	/// @return A handle for the inline element, which must later be submitted to 'CloseInlineElement()'.
+	/// @note Adds a new inline container to this box if needed, which starts a new inline formatting context.
+	InlineBoxHandle AddInlineElement(Element* element, const Box& box);
+	/// Closes a previously added inline element. This must be called after all its children have been added.
+	/// @param[in] handle A handle previously returned from 'AddInlineElement()'.
+	void CloseInlineElement(InlineBoxHandle handle);
+
+	// Adds a line-break to this block box.
+	void AddBreak();
+
+	// Estimates the static position of a hypothetical next element to be placed.
+	Vector2f GetOpenStaticPosition(Style::Display display) const;
+
+	/// Returns the position of a new child box to be placed here, relative to the block formatting context space.
+	Vector2f NextBoxPosition() const;
+	/// Returns the position of a new child box to be placed here. Collapses adjacent margins and optionally clears floats.
+	/// @param[in] child_box The dimensions of the new box.
+	/// @param[in] clear_property Specifies any floated boxes to be cleared (vertically skipped).
+	/// @return The next position in the block formatting context space.
+	Vector2f NextBoxPosition(const Box& child_box, Style::Clear clear_property) const;
+
+	// Places all queued floating elements.
+	void PlaceQueuedFloats(float vertical_position);
+
+	// Resets this box, so that it can be formatted again.
+	void ResetContents();
+
+	Vector2f GetPosition() const;
+	Element* GetElement() const;
+	const FloatedBoxSpace* GetBlockBoxSpace() const;
+
+	Box& GetBox();
+	const Box& GetBox() const;
+
+	// -- Inherited from LayoutBox --
+
+	const Box* GetIfBox() const override;
+	float GetShrinkToFitWidth() const override;
+	bool GetBaselineOfLastLine(float& out_baseline) const override;
+
+private:
+	InlineContainer* EnsureOpenInlineContainer();
+	InlineContainer* GetOpenInlineContainer();
+	const InlineContainer* GetOpenInlineContainer() const;
+
+	const LayoutBox* GetOpenLayoutBox() const;
+
+	/// Increment our cursor and content size, to enclose a sized block-level child box.
+	/// @param[in] child The child box.
+	/// @param[in] child_position The border position of the child, relative to the current block formatting context.
+	/// @param[in] child_size The border size of the child.
+	/// @param[in] child_margin_bottom The bottom margin width of the child.
+	/// @return False if this caused an automatic vertical scrollbar to appear, forcing a reformat of the current block formatting context.
+	bool EncloseChildBox(LayoutBox* child, Vector2f child_position, float child_height, float child_margin_bottom);
+
+	// Closes the inline container if there is one open. Returns false if our formatting context needs to be reformatted.
+	bool CloseOpenInlineContainer();
+
+	// Ensures that the interrupted line box is empty, otherwise it indicates an internal error.
+	void EnsureEmptyInterruptedLineBox();
+
+	// Positions a floating element within this block box.
+	void PlaceFloat(Element* element, float vertical_position, Vector2f visible_overflow_size);
+
+	// Debug dump layout tree.
+	String DebugDumpTree(int depth) const override;
+
+	using LayoutBoxList = Vector<UniquePtr<LayoutBox>>;
+	struct QueuedFloat {
+		Element* element;
+		Vector2f visible_overflow_size;
+	};
+	using QueuedFloatList = Vector<QueuedFloat>;
+
+	// Position of this box, relative to the border area of the root of our block formatting context.
+	Vector2f position;
+
+	Box box;
+	float min_height = 0.f;
+	float max_height = -1.f;
+
+	// The vertical position of the next block box to be added to this box, relative to our box's top content edge.
+	float box_cursor = 0.f;
+
+	// Stores the floated boxes in the current block formatting context, if we are the root of the formatting context.
+	UniquePtr<FloatedBoxSpace> root_space;
+	// Pointer to the floated box space of the current block formatting context. [not-null]
+	FloatedBoxSpace* space;
+	// List of block-level boxes contained in this box.
+	LayoutBoxList child_boxes;
+	// Stores floated elements that are waiting for a line break to be positioned.
+	QueuedFloatList queued_float_elements;
+	// Stores the unplaced part of a line box that was split by a block-level box.
+	UniquePtr<LineBox> interrupted_line_box;
+
+	// The inner content size (excluding any padding/border/margins).
+	// This is extended as child block boxes are closed, or from external formatting contexts.
+	Vector2f inner_content_size;
+};
+
+} // namespace Rml
+#endif

+ 295 - 0
Source/Core/Layout/BlockFormattingContext.cpp

@@ -0,0 +1,295 @@
+/*
+ * 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 "BlockFormattingContext.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/Profiling.h"
+#include "../../../Include/RmlUi/Core/PropertyDefinition.h"
+#include "../../../Include/RmlUi/Core/StyleSheetSpecification.h"
+#include "../../../Include/RmlUi/Core/SystemInterface.h"
+#include "BlockContainer.h"
+#include "FloatedBoxSpace.h"
+#include "LayoutDetails.h"
+
+namespace Rml {
+
+// Table elements should be handled within FormatElementTable, log a warning when it seems like we're encountering table parts in the wild.
+static void LogUnexpectedFlowElement(Element* element, Style::Display display)
+{
+	RMLUI_ASSERT(element);
+	String value = "*unknown";
+	StyleSheetSpecification::GetPropertySpecification().GetProperty(PropertyId::Display)->GetValue(value, Property(display));
+
+	Log::Message(Log::LT_WARNING, "Element has a display type '%s' which cannot be located in normal flow layout. Element will not be formatted: %s",
+		value.c_str(), element->GetAddress().c_str());
+}
+
+#ifdef RMLUI_DEBUG
+static bool g_debug_dumping_layout_tree = false;
+struct DebugDumpLayoutTree {
+	Element* element;
+	BlockContainer* block_box;
+	bool is_printing_tree_root = false;
+
+	DebugDumpLayoutTree(Element* element, BlockContainer* block_box) : element(element), block_box(block_box)
+	{
+		// When an element with this ID is encountered, dump the formatted layout tree (including for all descendant formatting contexts).
+		static const String debug_trigger_id = "rmlui-debug-layout";
+		is_printing_tree_root = element->HasAttribute(debug_trigger_id);
+		if (is_printing_tree_root)
+			g_debug_dumping_layout_tree = true;
+	}
+	~DebugDumpLayoutTree()
+	{
+		if (g_debug_dumping_layout_tree)
+		{
+			const String header = ":: " + LayoutDetails::GetDebugElementName(element) + " ::\n";
+			const String layout_tree = header + block_box->DumpLayoutTree();
+			if (SystemInterface* system_interface = GetSystemInterface())
+				system_interface->LogMessage(Log::LT_INFO, layout_tree);
+
+			if (is_printing_tree_root)
+				g_debug_dumping_layout_tree = false;
+		}
+	}
+};
+#else
+struct DebugDumpLayoutTree {
+	DebugDumpLayoutTree(Element* /*element*/, BlockContainer* /*block_box*/) {}
+};
+#endif
+
+enum class OuterDisplayType { BlockLevel, InlineLevel, Invalid };
+
+static OuterDisplayType GetOuterDisplayType(Style::Display display)
+{
+	switch (display)
+	{
+	case Style::Display::Flex:
+	case Style::Display::Table:
+	case Style::Display::Block: return OuterDisplayType::BlockLevel;
+
+	case Style::Display::InlineBlock:
+	case Style::Display::Inline: return OuterDisplayType::InlineLevel;
+
+	case Style::Display::TableRow:
+	case Style::Display::TableRowGroup:
+	case Style::Display::TableColumn:
+	case Style::Display::TableColumnGroup:
+	case Style::Display::TableCell:
+	case Style::Display::None: break;
+	}
+
+	return OuterDisplayType::Invalid;
+}
+
+UniquePtr<LayoutBox> BlockFormattingContext::Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box)
+{
+	RMLUI_ASSERT(parent_container && element);
+
+#ifdef RMLUI_ENABLE_PROFILING
+	RMLUI_ZoneScopedC(0xB22222);
+	auto name = CreateString(80, "%s %x", element->GetAddress(false, false).c_str(), element);
+	RMLUI_ZoneName(name.c_str(), name.size());
+#endif
+
+	const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size;
+
+	Box box;
+	if (override_initial_box)
+		box = *override_initial_box;
+	else
+		LayoutDetails::BuildBox(box, containing_block, element);
+
+	float min_height, max_height;
+	LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
+
+	UniquePtr<BlockContainer> container = MakeUnique<BlockContainer>(parent_container, nullptr, element, box, min_height, max_height);
+
+	DebugDumpLayoutTree debug_dump_tree(element, container.get());
+
+	container->ResetScrollbars(box);
+
+	// Format the element's children. In rare cases, it is possible that we need three iterations: Once to enable the
+	// horizontal scrollbar, then to enable the vertical scrollbar, and finally to format with both scrollbars enabled.
+	for (int layout_iteration = 0; layout_iteration < 3; layout_iteration++)
+	{
+		bool all_children_formatted = true;
+		for (int i = 0; i < element->GetNumChildren() && all_children_formatted; i++)
+		{
+			if (!FormatBlockContainerChild(container.get(), element->GetChild(i)))
+				all_children_formatted = false;
+		}
+
+		if (all_children_formatted && container->Close(nullptr))
+			// Success, break out of the loop.
+			break;
+
+		// Otherwise, restart formatting now that one or both scrollbars have been enabled.
+		container->ResetContents();
+	}
+
+	return container;
+}
+
+bool BlockFormattingContext::FormatBlockBox(BlockContainer* parent_container, Element* element)
+{
+	RMLUI_ZoneScopedC(0x2F4F4F);
+	const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size;
+
+	Box box;
+	LayoutDetails::BuildBox(box, containing_block, element);
+	float min_height, max_height;
+	LayoutDetails::GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
+
+	BlockContainer* container = parent_container->OpenBlockBox(element, box, min_height, max_height);
+	if (!container)
+		return false;
+
+	// Format our children. This may result in scrollbars being added to our formatting context root, then we need to
+	// bail out and restart formatting for the current block formatting context.
+	for (int i = 0; i < element->GetNumChildren(); i++)
+	{
+		if (!FormatBlockContainerChild(container, element->GetChild(i)))
+			return false;
+	}
+
+	if (!container->Close(parent_container))
+		return false;
+
+	return true;
+}
+
+bool BlockFormattingContext::FormatInlineBox(BlockContainer* parent_container, Element* element)
+{
+	RMLUI_ZoneScopedC(0x3F6F6F);
+	const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size;
+
+	Box box;
+	LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::Inline);
+	auto inline_box_handle = parent_container->AddInlineElement(element, box);
+
+	// Format the element's children.
+	for (int i = 0; i < element->GetNumChildren(); i++)
+	{
+		if (!FormatBlockContainerChild(parent_container, element->GetChild(i)))
+			return false;
+	}
+
+	parent_container->CloseInlineElement(inline_box_handle);
+
+	return true;
+}
+
+bool BlockFormattingContext::FormatBlockContainerChild(BlockContainer* parent_container, Element* element)
+{
+#ifdef RMLUI_ENABLE_PROFILING
+	RMLUI_ZoneScoped;
+	auto name = CreateString(80, ">%s %x", element->GetAddress(false, false).c_str(), element);
+	RMLUI_ZoneName(name.c_str(), name.size());
+#endif
+
+	// Check for special formatting tags.
+	if (element->GetTagName() == "br")
+	{
+		parent_container->AddBreak();
+		return true;
+	}
+
+	auto& computed = element->GetComputedValues();
+	const Style::Display display = computed.display();
+
+	// Don't lay this element out if it is set to a display type of none.
+	if (display == Style::Display::None)
+		return true;
+
+	// Check for absolutely positioned elements: they are removed from the flow and added to the box representing their
+	// containing block, to be layed out and positioned once that box has been closed and sized.
+	const Style::Position position_property = computed.position();
+	if (position_property == Style::Position::Absolute || position_property == Style::Position::Fixed)
+	{
+		const Vector2f static_position = parent_container->GetOpenStaticPosition(display) - parent_container->GetPosition();
+		ContainingBlock containing_block = LayoutDetails::GetContainingBlock(parent_container, position_property);
+		containing_block.container->AddAbsoluteElement(element, static_position, parent_container->GetElement());
+		return true;
+	}
+
+	const OuterDisplayType outer_display = GetOuterDisplayType(display);
+	if (outer_display == OuterDisplayType::Invalid)
+	{
+		LogUnexpectedFlowElement(element, display);
+		return true;
+	}
+
+	// If the element creates an independent formatting context, then format it accordingly.
+	if (UniquePtr<LayoutBox> layout_box = FormattingContext::FormatIndependent(parent_container, element, nullptr, FormattingContextType::None))
+	{
+		// If the element is floating, we remove it from the flow.
+		if (computed.float_() != Style::Float::None)
+		{
+			parent_container->AddFloatElement(element, layout_box->GetVisibleOverflowSize());
+		}
+		// Otherwise, check if we have a sized block-level box.
+		else if (layout_box && outer_display == OuterDisplayType::BlockLevel)
+		{
+			if (!parent_container->AddBlockLevelBox(std::move(layout_box), element, element->GetBox()))
+				return false;
+		}
+		// Nope, then this must be an inline-level box.
+		else
+		{
+			RMLUI_ASSERT(outer_display == OuterDisplayType::InlineLevel);
+			auto inline_box_handle = parent_container->AddInlineElement(element, element->GetBox());
+			parent_container->CloseInlineElement(inline_box_handle);
+		}
+
+		return true;
+	}
+
+	// The element is an in-flow box participating in this same block formatting context.
+	switch (display)
+	{
+	case Style::Display::Block: return FormatBlockBox(parent_container, element);
+	case Style::Display::Inline: return FormatInlineBox(parent_container, element);
+
+	case Style::Display::TableRow:
+	case Style::Display::TableRowGroup:
+	case Style::Display::TableColumn:
+	case Style::Display::TableColumnGroup:
+	case Style::Display::TableCell:
+	case Style::Display::InlineBlock:
+	case Style::Display::Flex:
+	case Style::Display::Table:
+	case Style::Display::None: /* handled above */ RMLUI_ERROR; break;
+	}
+
+	return true;
+}
+
+} // namespace Rml

+ 71 - 0
Source/Core/Layout/BlockFormattingContext.h

@@ -0,0 +1,71 @@
+/*
+ * 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_LAYOUT_BLOCKFORMATTINGCONTEXT_H
+#define RMLUI_CORE_LAYOUT_BLOCKFORMATTINGCONTEXT_H
+
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "FormattingContext.h"
+
+namespace Rml {
+
+class Box;
+class BlockContainer;
+class ContainerBox;
+class LayoutBox;
+
+/*
+    Places boxes according to normal flow, while handling floated boxes.
+
+    A block formatting context (BFC) starts with a root BlockContainer, where child block-level boxes are placed
+    vertically. Descendant elements take part in the same BFC, unless the element has properties causing it to establish
+    an independent formatting context.
+
+    Floated boxes are taken out-of-flow and placed inside the current BFC. Floats are only affected by, and affect only,
+    other boxes within the same BFC.
+*/
+class BlockFormattingContext final : public FormattingContext {
+public:
+	static UniquePtr<LayoutBox> Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box);
+
+private:
+	// Format the element as a block box, including its children.
+	// @return False if the box caused an automatic vertical scrollbar to appear in the block formatting context root, forcing it to be reformatted.
+	static bool FormatBlockBox(BlockContainer* parent_container, Element* element);
+
+	// Format the element as an inline box, including its children.
+	// @return False if the box caused an automatic vertical scrollbar to appear in the block formatting context root, forcing it to be reformatted.
+	static bool FormatInlineBox(BlockContainer* parent_container, Element* element);
+
+	// Determine how to format a child element of a block container, and format it accordingly, possibly including any children.
+	// @return False if the box caused an automatic vertical scrollbar to appear in the block formatting context root, forcing it to be reformatted.
+	static bool FormatBlockContainerChild(BlockContainer* parent_container, Element* element);
+};
+
+} // namespace Rml
+#endif

+ 295 - 0
Source/Core/Layout/ContainerBox.cpp

@@ -0,0 +1,295 @@
+/*
+ * 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 "ContainerBox.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/ElementScroll.h"
+#include "../../../Include/RmlUi/Core/Profiling.h"
+#include "FormattingContext.h"
+#include "LayoutDetails.h"
+#include <algorithm>
+#include <cmath>
+
+namespace Rml {
+
+void ContainerBox::ResetScrollbars(const Box& box)
+{
+	RMLUI_ASSERT(element);
+	if (overflow_x == Style::Overflow::Scroll)
+		element->GetElementScroll()->EnableScrollbar(ElementScroll::HORIZONTAL, box.GetSizeAcross(Box::HORIZONTAL, Box::PADDING));
+	else
+		element->GetElementScroll()->DisableScrollbar(ElementScroll::HORIZONTAL);
+
+	if (overflow_y == Style::Overflow::Scroll)
+		element->GetElementScroll()->EnableScrollbar(ElementScroll::VERTICAL, box.GetSizeAcross(Box::HORIZONTAL, Box::PADDING));
+	else
+		element->GetElementScroll()->DisableScrollbar(ElementScroll::VERTICAL);
+}
+
+void ContainerBox::AddAbsoluteElement(Element* element, Vector2f static_position, Element* static_relative_offset_parent)
+{
+	// We may possibly be adding the same element from a previous layout iteration. If so, this ensures it is updated with the latest static position.
+	absolute_elements[element] = AbsoluteElement{static_position, static_relative_offset_parent};
+}
+
+void ContainerBox::AddRelativeElement(Element* element)
+{
+	// The same relative element may be added multiple times during repeated layout iterations, avoid any duplicates.
+	if (std::find(relative_elements.begin(), relative_elements.end(), element) == relative_elements.end())
+		relative_elements.push_back(element);
+}
+
+void ContainerBox::ClosePositionedElements()
+{
+	// Any relatively positioned elements that we act as containing block for may need to be have their positions
+	// updated to reflect changes to the size of this block box. Update relative offsets before handling absolute
+	// elements, as this may affect the resolved static position of the absolute elements.
+	for (Element* child : relative_elements)
+		child->UpdateOffset();
+
+	relative_elements.clear();
+
+	while (!absolute_elements.empty())
+	{
+		// New absolute elements may be added to this box during formatting below. To avoid invalidated iterators and
+		// references, move the list to a local copy to iterate over, and repeat if new elements are added.
+		AbsoluteElementMap absolute_elements_iterate = std::move(absolute_elements);
+		absolute_elements.clear();
+
+		for (const auto& absolute_element_pair : absolute_elements_iterate)
+		{
+			Element* absolute_element = absolute_element_pair.first;
+			const Vector2f static_position = absolute_element_pair.second.static_position;
+			Element* static_position_offset_parent = absolute_element_pair.second.static_position_offset_parent;
+
+			// Find the static position relative to this containing block. First, calculate the offset from ourself to
+			// the static position's offset parent. Assumes (1) that this container box is part of the containing block
+			// chain of the static position offset parent, and (2) that all offsets in this chain has been set already.
+			Vector2f relative_position;
+			for (Element* ancestor = static_position_offset_parent; ancestor && ancestor != element; ancestor = ancestor->GetOffsetParent())
+				relative_position += ancestor->GetRelativeOffset(Box::BORDER);
+
+			// Now simply add the result to the stored static position to get the static position in our local space.
+			Vector2f offset = relative_position + static_position;
+
+			// Lay out the element.
+			FormattingContext::FormatIndependent(this, absolute_element, nullptr, FormattingContextType::Block);
+
+			// Now that the element's box has been built, we can offset the position we determined was appropriate for
+			// it by the element's margin. This is necessary because the coordinate system for the box begins at the
+			// border, not the margin.
+			offset.x += absolute_element->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
+			offset.y += absolute_element->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+
+			// Set the offset of the element; the element itself will take care of any RCSS-defined positional offsets.
+			absolute_element->SetOffset(offset, element);
+		}
+	}
+}
+
+void ContainerBox::SetElementBaseline(float element_baseline)
+{
+	element->SetBaseline(element_baseline);
+}
+
+void ContainerBox::SubmitElementLayout()
+{
+	element->OnLayout();
+}
+
+ContainerBox::ContainerBox(Type type, Element* element, ContainerBox* parent_container) :
+	LayoutBox(type), element(element), parent_container(parent_container)
+{
+	if (element)
+	{
+		const auto& computed = element->GetComputedValues();
+		overflow_x = computed.overflow_x();
+		overflow_y = computed.overflow_y();
+		position_property = computed.position();
+		has_local_transform_or_perspective = (computed.has_local_transform() || computed.has_local_perspective());
+	}
+}
+
+bool ContainerBox::CatchOverflow(const Vector2f content_overflow_size, const Box& box, const float max_height) const
+{
+	if (!IsScrollContainer())
+		return true;
+
+	const Vector2f padding_bottom_right = {box.GetEdge(Box::PADDING, Box::RIGHT), box.GetEdge(Box::PADDING, Box::BOTTOM)};
+	const float padding_width = box.GetSizeAcross(Box::HORIZONTAL, Box::PADDING);
+
+	Vector2f available_space = box.GetSize();
+	if (available_space.y < 0.f)
+		available_space.y = max_height;
+	if (available_space.y < 0.f)
+		available_space.y = HUGE_VALF;
+
+	RMLUI_ASSERT(available_space.x >= 0.f && available_space.y >= 0.f);
+
+	// Allow overflow onto the padding area.
+	available_space += padding_bottom_right;
+
+	ElementScroll* element_scroll = element->GetElementScroll();
+	bool scrollbar_size_changed = false;
+
+	// @performance If we have auto-height sizing and the horizontal scrollbar is enabled, then we can in principle
+	// simply add the scrollbar size to the height instead of formatting the element all over again.
+	if (overflow_x == Style::Overflow::Auto && content_overflow_size.x > available_space.x + 0.5f)
+	{
+		if (element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL) == 0.f)
+		{
+			element_scroll->EnableScrollbar(ElementScroll::HORIZONTAL, padding_width);
+			const float new_size = element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL);
+			scrollbar_size_changed = (new_size != 0.f);
+			available_space.y -= new_size;
+		}
+	}
+
+	// If we're auto-scrolling and our height is fixed, we have to check if this box has exceeded our client height.
+	if (overflow_y == Style::Overflow::Auto && content_overflow_size.y > available_space.y + 0.5f)
+	{
+		if (element_scroll->GetScrollbarSize(ElementScroll::VERTICAL) == 0.f)
+		{
+			element_scroll->EnableScrollbar(ElementScroll::VERTICAL, padding_width);
+			const float new_size = element_scroll->GetScrollbarSize(ElementScroll::VERTICAL);
+			scrollbar_size_changed |= (new_size != 0.f);
+		}
+	}
+
+	return !scrollbar_size_changed;
+}
+
+bool ContainerBox::SubmitBox(const Vector2f content_overflow_size, const Box& box, const float max_height)
+{
+	Vector2f visible_overflow_size;
+
+	// Set the computed box on the element.
+	if (element)
+	{
+		// Calculate the dimensions of the box's scrollable overflow rectangle. This is the union of the tightest-
+		// fitting box around all of the internal elements, and this element's padding box. We really only care about
+		// overflow on the bottom-right sides, as these are the only ones allowed to be scrolled to in CSS.
+		//
+		// If we are a scroll container (use any other value than 'overflow: visible'), then any overflow outside our
+		// padding box should be caught here. Otherwise, our overflow should be included in the overflow calculations of
+		// our nearest scroll container ancestor.
+
+		// If our content is larger than our padding box, we can add scrollbars if we're set to auto-scrollbars. If
+		// we're set to always use scrollbars, then the scrollbars have already been enabled.
+		if (!CatchOverflow(content_overflow_size, box, max_height))
+			return false;
+
+		const Vector2f padding_top_left = {box.GetEdge(Box::PADDING, Box::LEFT), box.GetEdge(Box::PADDING, Box::TOP)};
+		const Vector2f padding_bottom_right = {box.GetEdge(Box::PADDING, Box::RIGHT), box.GetEdge(Box::PADDING, Box::BOTTOM)};
+		const Vector2f padding_size = box.GetSize() + padding_top_left + padding_bottom_right;
+
+		const bool is_scroll_container = IsScrollContainer();
+		const Vector2f scrollbar_size = {
+			is_scroll_container ? element->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL) : 0.f,
+			is_scroll_container ? element->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL) : 0.f,
+		};
+
+		// Scrollable overflow is the set of things extending our padding area, for which scrolling could be provided.
+		const Vector2f scrollable_overflow_size = Math::Max(padding_size - scrollbar_size, padding_top_left + content_overflow_size);
+
+		element->SetBox(box);
+		element->SetScrollableOverflowRectangle(scrollable_overflow_size);
+
+		const Vector2f border_size = padding_size + box.GetFrameSize(Box::BORDER);
+
+		// Set the visible overflow size so that ancestors can catch any overflow produced by us. That is, hiding it or
+		// providing a scrolling mechanism. If this box is a scroll container, we catch our own overflow here; then,
+		// just use the normal margin box as that will effectively remove the overflow from our ancestor's perspective.
+		if (is_scroll_container)
+		{
+			visible_overflow_size = border_size;
+
+			// Format any scrollbars in case they were enabled on this element.
+			element->GetElementScroll()->FormatScrollbars();
+		}
+		else
+		{
+			const Vector2f border_top_left = {box.GetEdge(Box::BORDER, Box::LEFT), box.GetEdge(Box::BORDER, Box::TOP)};
+			visible_overflow_size = Math::Max(border_size, content_overflow_size + border_top_left + padding_top_left);
+		}
+	}
+
+	SetVisibleOverflowSize(visible_overflow_size);
+
+	return true;
+}
+
+String RootBox::DebugDumpTree(int depth) const
+{
+	return String(depth * 2, ' ') + "RootBox";
+}
+
+FlexContainer::FlexContainer(Element* element, ContainerBox* parent_container) : ContainerBox(Type::FlexContainer, element, parent_container)
+{
+	RMLUI_ASSERT(element);
+}
+
+bool FlexContainer::Close(const Vector2f content_overflow_size, const Box& box)
+{
+	if (!SubmitBox(content_overflow_size, box, -1.f))
+		return false;
+
+	ClosePositionedElements();
+	SubmitElementLayout();
+	return true;
+}
+
+String FlexContainer::DebugDumpTree(int depth) const
+{
+	return String(depth * 2, ' ') + "FlexContainer" + " | " + LayoutDetails::GetDebugElementName(element);
+}
+
+TableWrapper::TableWrapper(Element* element, ContainerBox* parent_container) : ContainerBox(Type::TableWrapper, element, parent_container)
+{
+	RMLUI_ASSERT(element);
+}
+
+void TableWrapper::Close(const Vector2f content_overflow_size, const Box& box)
+{
+	bool result = SubmitBox(content_overflow_size, box, -1.f);
+
+	// Since the table wrapper cannot generate scrollbars, this should always pass.
+	RMLUI_ASSERT(result);
+	(void)result;
+
+	ClosePositionedElements();
+	SubmitElementLayout();
+}
+
+String TableWrapper::DebugDumpTree(int depth) const
+{
+	return String(depth * 2, ' ') + "TableWrapper" + " | " + LayoutDetails::GetDebugElementName(element);
+}
+
+} // namespace Rml

+ 164 - 0
Source/Core/Layout/ContainerBox.h

@@ -0,0 +1,164 @@
+/*
+ * 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_LAYOUT_CONTAINERBOX_H
+#define RMLUI_CORE_LAYOUT_CONTAINERBOX_H
+
+#include "../../../Include/RmlUi/Core/Box.h"
+#include "../../../Include/RmlUi/Core/StyleTypes.h"
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "LayoutBox.h"
+
+namespace Rml {
+
+/**
+    Abstraction for layout boxes that can act as a containing block.
+
+    Implements functionality for catching overflow, and for handling positioned boxes that the box acts as a containing block for.
+*/
+class ContainerBox : public LayoutBox {
+public:
+	bool IsScrollContainer() const { return overflow_x != Style::Overflow::Visible || overflow_y != Style::Overflow::Visible; }
+
+	// Enable or disable scrollbars for the element we represent, preparing it for the first round of layouting, according to our properties.
+	void ResetScrollbars(const Box& box);
+
+	// Adds an absolutely positioned element, to be formatted and positioned when closing this container, see 'ClosePositionedElements'.
+	void AddAbsoluteElement(Element* element, Vector2f static_position, Element* static_relative_offset_parent);
+	// Adds a relatively positioned element which we act as a containing block for.
+	void AddRelativeElement(Element* element);
+
+	ContainerBox* GetParent() { return parent_container; }
+	Element* GetElement() { return element; }
+	Style::Position GetPositionProperty() const { return position_property; }
+	bool HasLocalTransformOrPerspective() const { return has_local_transform_or_perspective; }
+
+protected:
+	ContainerBox(Type type, Element* element, ContainerBox* parent_container);
+
+	/// Checks if we have a new overflow on an auto-scrolling element. If so, our vertical scrollbar will be enabled and
+	/// our block boxes will be destroyed. All content will need to be re-formatted.
+	/// @param[in] content_overflow_size The size of the visible content, relative to our content area.
+	/// @param[in] box The box built for the element, possibly with a non-determinate height.
+	/// @param[in] max_height Maximum height of the content area, if any.
+	/// @returns True if no overflow occured, false if it did.
+	bool CatchOverflow(const Vector2f content_overflow_size, const Box& box, const float max_height) const;
+
+	/// Set the box and scrollable area on our element, possibly catching any overflow.
+	/// @param[in] content_overflow_size The size of the visible content, relative to our content area.
+	/// @param[in] box The box to be set on the element.
+	/// @param[in] max_height Maximum height of the content area, if any.
+	/// @returns True if no overflow occured, false if it did.
+	bool SubmitBox(const Vector2f content_overflow_size, const Box& box, const float max_height);
+
+	/// Formats, sizes, and positions all absolute elements whose containing block is this, and offsets relative elements.
+	void ClosePositionedElements();
+
+	// Set the element's baseline (proxy for private access to Element).
+	void SetElementBaseline(float element_baseline);
+	// Calls Element::OnLayout (proxy for private access to Element).
+	void SubmitElementLayout();
+
+	// The element this box represents, if any.
+	Element* const element;
+
+private:
+	struct AbsoluteElement {
+		Vector2f static_position;               // The hypothetical position of the element as if it was placed in normal flow.
+		Element* static_position_offset_parent; // The element for which the static position is offset from.
+	};
+	using AbsoluteElementMap = SmallUnorderedMap<Element*, AbsoluteElement>;
+
+	AbsoluteElementMap absolute_elements; // List of absolutely positioned elements that we act as a containing block for.
+	ElementList relative_elements;        // List of relatively positioned elements that we act as a containing block for.
+
+	Style::Overflow overflow_x = Style::Overflow::Visible;
+	Style::Overflow overflow_y = Style::Overflow::Visible;
+	Style::Position position_property = Style::Position::Static;
+	bool has_local_transform_or_perspective = false;
+
+	ContainerBox* parent_container = nullptr;
+};
+
+/**
+    The root of a tree of layout boxes, usually represents the root element or viewport.
+*/
+class RootBox final : public ContainerBox {
+public:
+	RootBox(Vector2f containing_block) : ContainerBox(Type::Root, nullptr, nullptr), box(containing_block) {}
+
+	const Box* GetIfBox() const override { return &box; }
+	String DebugDumpTree(int depth) const override;
+
+private:
+	Box box;
+};
+
+/**
+    A box where flexbox layout occurs.
+*/
+class FlexContainer final : public ContainerBox {
+public:
+	FlexContainer(Element* element, ContainerBox* parent_container);
+
+	// Submits the formatted box to the flex container element, and propagates any uncaught overflow to this box.
+	// @returns True if it succeeds, otherwise false if it needs to be formatted again because scrollbars were enabled.
+	bool Close(const Vector2f content_overflow_size, const Box& box);
+
+	const Box* GetIfBox() const override { return &box; }
+	String DebugDumpTree(int depth) const override;
+
+	Box& GetBox() { return box; }
+
+private:
+	Box box;
+};
+
+/**
+    A box where table formatting occurs.
+
+    As opposed to a flex container, the table wrapper cannot contain overflow or produce scrollbars.
+*/
+class TableWrapper final : public ContainerBox {
+public:
+	TableWrapper(Element* element, ContainerBox* parent_container);
+
+	// Submits the formatted box to the table element, and propagates any uncaught overflow to this box.
+	void Close(const Vector2f content_overflow_size, const Box& box);
+
+	const Box* GetIfBox() const override { return &box; }
+	String DebugDumpTree(int depth) const override;
+
+	Box& GetBox() { return box; }
+
+private:
+	Box box;
+};
+
+} // namespace Rml
+#endif

+ 84 - 65
Source/Core/LayoutFlex.cpp → Source/Core/Layout/FlexFormattingContext.cpp

@@ -26,11 +26,12 @@
  *
  */
 
-#include "LayoutFlex.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/Element.h"
-#include "../../Include/RmlUi/Core/ElementScroll.h"
-#include "../../Include/RmlUi/Core/Types.h"
+#include "FlexFormattingContext.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/ElementScroll.h"
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "ContainerBox.h"
 #include "LayoutDetails.h"
 #include "LayoutEngine.h"
 #include <algorithm>
@@ -39,50 +40,76 @@
 
 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)
+UniquePtr<LayoutBox> FlexFormattingContext::Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box)
 {
-	ElementScroll* element_scroll = element_flex->GetElementScroll();
-	const Vector2f scrollbar_size = {element_scroll->GetScrollbarSize(ElementScroll::VERTICAL),
-		element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL)};
+	auto flex_container_box = MakeUnique<FlexContainer>(element, parent_container);
 
-	Vector2f flex_content_offset = box.GetPosition();
+	ElementScroll* element_scroll = element->GetElementScroll();
+	const ComputedValues& computed = element->GetComputedValues();
+
+	const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size;
+	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 = flex_container_box->GetBox();
+	if (override_initial_box)
+		box = *override_initial_box;
+	else
+		LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::Block);
+
+	// Start with any auto-scrollbars off.
+	flex_container_box->ResetScrollbars(box);
+
+	FlexFormattingContext context;
+	context.flex_container_box = flex_container_box.get();
+	context.element_flex = element;
+
+	LayoutDetails::GetMinMaxWidth(context.flex_min_size.x, context.flex_max_size.x, computed, box, containing_block.x);
+	LayoutDetails::GetMinMaxHeight(context.flex_min_size.y, context.flex_max_size.y, computed, box, containing_block.y);
 
 	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;
+	context.flex_content_offset = box.GetPosition();
 
-	if (auto_height)
+	for (int layout_iteration = 0; layout_iteration < 3; layout_iteration++)
 	{
-		flex_available_content_size.y = -1.f; // Negative means infinite space
-		flex_content_containing_block.y = flex_containing_block.y;
-	}
+		// One or both scrollbars can be enabled between iterations.
+		const Vector2f scrollbar_size = {
+			element_scroll->GetScrollbarSize(ElementScroll::VERTICAL),
+			element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL),
+		};
+
+		context.flex_available_content_size = Math::Max(box_content_size - scrollbar_size, Vector2f(0.f));
+		context.flex_content_containing_block = context.flex_available_content_size;
 
-	Math::SnapToPixelGrid(flex_content_offset, flex_available_content_size);
+		if (auto_height)
+		{
+			context.flex_available_content_size.y = -1.f; // Negative means infinite space
+			context.flex_content_containing_block.y = containing_block.y;
+		}
 
-	// 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);
+		Math::SnapToPixelGrid(context.flex_content_offset, context.flex_available_content_size);
 
-	layout_flex.Format();
+		// Format the flexbox and all its children.
+		Vector2f flex_resulting_content_size, content_overflow_size;
+		context.Format(flex_resulting_content_size, content_overflow_size);
 
-	// 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);
+		// Output the size of the formatted flexbox. The width is determined as a normal block box so we don't need to change that.
+		Vector2f formatted_content_size = box_content_size;
+		if (auto_height)
+			formatted_content_size.y = flex_resulting_content_size.y + scrollbar_size.y;
 
-	out_content_overflow_size = layout_flex.flex_content_overflow_size;
-}
+		Box sized_box = box;
+		sized_box.SetContent(formatted_content_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)
-{}
+		// Close the box, and break out of the loop if it did not produce any new scrollbars, otherwise continue to format the flexbox again.
+		if (flex_container_box->Close(content_overflow_size, sized_box))
+			break;
+	}
+
+	return flex_container_box;
+}
 
 struct FlexItem {
 	// In the following, suffix '_a' means flex start edge while '_b' means flex end edge.
@@ -134,7 +161,7 @@ struct FlexLine {
 	float cross_offset = 0;
 };
 
-struct FlexContainer {
+struct FlexLineContainer {
 	Vector<FlexLine> lines;
 };
 
@@ -173,9 +200,9 @@ static void GetItemSizing(FlexItem::Size& destination, const ComputedAxisSize& c
 	}
 }
 
-void LayoutFlex::Format()
+void FlexFormattingContext::Format(Vector2f& flex_resulting_content_size, Vector2f& flex_content_overflow_size) const
 {
-	// The following procedure is generally based on the CSS flexible box layout algorithm.
+	// The following procedure is 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();
@@ -216,13 +243,18 @@ void LayoutFlex::Format()
 		}
 		else if (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed)
 		{
-			absolutely_positioned_elements.push_back(element);
+			ContainerBox* absolute_containing_block = LayoutDetails::GetContainingBlock(flex_container_box, computed.position()).container;
+			absolute_containing_block->AddAbsoluteElement(element, {}, element_flex);
 			continue;
 		}
+		else if (computed.position() == Style::Position::Relative)
+		{
+			flex_container_box->AddRelativeElement(element);
+		}
 
 		FlexItem item = {};
 		item.element = element;
-		LayoutDetails::BuildBox(item.box, flex_content_containing_block, element, BoxContext::FlexOrTable, 0.0f);
+		LayoutDetails::BuildBox(item.box, flex_content_containing_block, element, BuildBoxMode::UnalignedBlock);
 
 		Style::LengthPercentageAuto item_main_size;
 
@@ -278,7 +310,7 @@ void LayoutFlex::Format()
 			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);
+			FormattingContext::FormatIndependent(flex_container_box, element, &format_box, FormattingContextType::Block);
 			item.inner_flex_base_size = element->GetBox().GetSize().y;
 		}
 
@@ -295,7 +327,7 @@ void LayoutFlex::Format()
 	}
 
 	// -- Collect the items into lines --
-	FlexContainer container;
+	FlexLineContainer container;
 
 	if (flex_single_line)
 	{
@@ -512,12 +544,8 @@ void LayoutFlex::Format()
 						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::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;
@@ -564,7 +592,7 @@ void LayoutFlex::Format()
 				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);
+					FormattingContext::FormatIndependent(flex_container_box, item.element, &item.box, FormattingContextType::Block);
 					item.hypothetical_cross_size = item.element->GetBox().GetSize().y + item.cross.sum_edges;
 				}
 				else
@@ -684,12 +712,8 @@ void LayoutFlex::Format()
 				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::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.
@@ -774,12 +798,8 @@ void LayoutFlex::Format()
 					}
 				}
 				//-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::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;
@@ -828,15 +848,14 @@ void LayoutFlex::Format()
 
 			item.box.SetContent(item_size);
 
-			Vector2f cell_visible_overflow_size;
-			LayoutEngine::FormatElement(item.element, flex_content_containing_block, &item.box, &cell_visible_overflow_size);
+			UniquePtr<LayoutBox> item_layout_box =
+				FormattingContext::FormatIndependent(flex_container_box, item.element, &item.box, FormattingContextType::Block);
 
 			// 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));
+			const Vector2f overflow_size = item_offset + item_layout_box->GetVisibleOverflowSize();
 
 			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);

+ 67 - 0
Source/Core/Layout/FlexFormattingContext.h

@@ -0,0 +1,67 @@
+/*
+ * 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_LAYOUT_FLEXFORMATTINGCONTEXT_H
+#define RMLUI_CORE_LAYOUT_FLEXFORMATTINGCONTEXT_H
+
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "FormattingContext.h"
+
+namespace Rml {
+
+class LayoutBox;
+class ContainerBox;
+class FlexContainer;
+
+/*
+    Formats a flex container element and its flex items according to flexible box (flexbox) layout rules.
+*/
+class FlexFormattingContext final : public FormattingContext {
+public:
+	static UniquePtr<LayoutBox> Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box);
+
+private:
+	FlexFormattingContext() = default;
+
+	/// Format the flexbox and its children.
+	/// @param[out] flex_resulting_content_size The final content size of the flex container.
+	/// @param[out] flex_content_overflow_size Overflow size in case flex items or their contents overflow the container.
+	void Format(Vector2f& flex_resulting_content_size, Vector2f& flex_content_overflow_size) const;
+
+	Vector2f flex_available_content_size;
+	Vector2f flex_content_containing_block;
+	Vector2f flex_content_offset;
+	Vector2f flex_min_size;
+	Vector2f flex_max_size;
+
+	Element* element_flex = nullptr;
+	FlexContainer* flex_container_box = nullptr;
+};
+
+} // namespace Rml
+#endif

+ 72 - 100
Source/Core/LayoutBlockBoxSpace.cpp → Source/Core/Layout/FloatedBoxSpace.cpp

@@ -15,7 +15,7 @@
  *
  * 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
@@ -26,48 +26,28 @@
  *
  */
 
-#include "LayoutBlockBoxSpace.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/Element.h"
-#include "../../Include/RmlUi/Core/ElementScroll.h"
-#include "LayoutBlockBox.h"
-#include "LayoutEngine.h"
+#include "FloatedBoxSpace.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/ElementScroll.h"
+#include "BlockContainer.h"
+#include "LayoutPools.h"
 #include <float.h>
 
 namespace Rml {
 
-LayoutBlockBoxSpace::LayoutBlockBoxSpace(LayoutBlockBox* _parent) : offset(0, 0), dimensions(0, 0)
-{
-	parent = _parent;
-}
-
-LayoutBlockBoxSpace::~LayoutBlockBoxSpace()
-{
-}
+FloatedBoxSpace::FloatedBoxSpace() {}
 
-// Imports boxes from another block into this space.
-void LayoutBlockBoxSpace::ImportSpace(const LayoutBlockBoxSpace& space)
-{
-	// Copy all the boxes from the parent into this space. Could do some optimisation here!
-	for (int i = 0; i < NUM_ANCHOR_EDGES; ++i)
-	{
-		for (size_t j = 0; j < space.boxes[i].size(); ++j)
-			boxes[i].push_back(space.boxes[i][j]);
-	}
-}
+FloatedBoxSpace::~FloatedBoxSpace() {}
 
-// Generates the position for a box of a given size within a containing block box.
-void LayoutBlockBoxSpace::PositionBox(Vector2f& box_position, float& box_width, float cursor, const Vector2f dimensions) const
+Vector2f FloatedBoxSpace::NextBoxPosition(const BlockContainer* parent, float& box_width, float cursor, const Vector2f dimensions, bool nowrap) const
 {
-	box_width = PositionBox(box_position, cursor, dimensions);
+	return NextBoxPosition(parent, box_width, cursor, dimensions, nowrap, Style::Float::None);
 }
 
-// Generates and sets the position for a floating box of a given size within our block box.
-float LayoutBlockBoxSpace::PositionBox(float cursor, Element* element)
+Vector2f FloatedBoxSpace::NextFloatPosition(const BlockContainer* parent, float& out_box_width, float cursor, Vector2f dimensions,
+	Style::Float float_property, Style::Clear clear_property) const
 {
-	Vector2f element_size = element->GetBox().GetSize(Box::MARGIN);
-	Style::Float float_property = element->GetComputedValues().float_();
-
 	// Shift the cursor down (if necessary) so it isn't placed any higher than a previously-floated box.
 	for (int i = 0; i < NUM_ANCHOR_EDGES; ++i)
 	{
@@ -76,45 +56,38 @@ float LayoutBlockBoxSpace::PositionBox(float cursor, Element* element)
 	}
 
 	// Shift the cursor down past to clear boxes, if necessary.
-	cursor = ClearBoxes(cursor, element->GetComputedValues().clear());
+	cursor = DetermineClearPosition(cursor, clear_property);
 
 	// Find a place to put this box.
-	Vector2f element_offset;
-	PositionBox(element_offset, cursor, element_size, float_property);
+	const bool nowrap = false;
+	const Vector2f margin_offset = NextBoxPosition(parent, out_box_width, cursor, dimensions, nowrap, float_property);
 
-	// It's been placed, so we can now add it to our list of floating boxes.
-	boxes[float_property == Style::Float::Left ? LEFT : RIGHT].push_back(SpaceBox(element_offset, element_size));
-
-	// Set our offset and dimensions (if necessary) so they enclose the new box.
-	Vector2f normalised_offset = element_offset - (parent->GetPosition() + parent->GetBox().GetPosition());
-	offset.x = Math::Min(offset.x, normalised_offset.x);
-	offset.y = Math::Min(offset.y, normalised_offset.y);
-	dimensions.x = Math::Max(dimensions.x, normalised_offset.x + element_size.x);
-	dimensions.y = Math::Max(dimensions.y, normalised_offset.y + element_size.y);
+	return margin_offset;
+}
 
-	// Shift the offset into the correct space relative to the element's offset parent.
-	element_offset += Vector2f(element->GetBox().GetEdge(Box::MARGIN, Box::LEFT), element->GetBox().GetEdge(Box::MARGIN, Box::TOP));
-	element->SetOffset(element_offset - parent->GetOffsetParent()->GetPosition(), parent->GetOffsetParent()->GetElement());
+void FloatedBoxSpace::PlaceFloat(Style::Float float_property, Vector2f margin_position, Vector2f margin_size, Vector2f overflow_position,
+	Vector2f overflow_size)
+{
+	boxes[float_property == Style::Float::Left ? LEFT : RIGHT].push_back(FloatedBox{margin_position, margin_size});
 
-	return element_offset.y + element_size.y;
+	// Set our extents so they enclose the new box.
+	extent_top_left_overflow = Math::Min(extent_top_left_overflow, overflow_position);
+	extent_bottom_right_overflow = Math::Max(extent_bottom_right_overflow, overflow_position + overflow_size);
+	extent_bottom_right_margin = Math::Max(extent_bottom_right_margin, margin_position + margin_size);
 }
 
-// Determines the appropriate vertical position for an object that is choosing to clear floating elements to the left
-// or right (or both).
-float LayoutBlockBoxSpace::ClearBoxes(float cursor, Style::Clear clear_property) const
+float FloatedBoxSpace::DetermineClearPosition(float cursor, Style::Clear clear_property) const
 {
 	using namespace Style;
 	// Clear left boxes.
-	if (clear_property == Clear::Left ||
-		clear_property == Clear::Both)
+	if (clear_property == Clear::Left || clear_property == Clear::Both)
 	{
 		for (size_t i = 0; i < boxes[LEFT].size(); ++i)
 			cursor = Math::Max(cursor, boxes[LEFT][i].offset.y + boxes[LEFT][i].dimensions.y);
 	}
 
 	// Clear right boxes.
-	if (clear_property == Clear::Right ||
-		clear_property == Clear::Both)
+	if (clear_property == Clear::Right || clear_property == Clear::Both)
 	{
 		for (size_t i = 0; i < boxes[RIGHT].size(); ++i)
 			cursor = Math::Max(cursor, boxes[RIGHT][i].offset.y + boxes[RIGHT][i].dimensions.y);
@@ -123,30 +96,27 @@ float LayoutBlockBoxSpace::ClearBoxes(float cursor, Style::Clear clear_property)
 	return cursor;
 }
 
-// Generates the position for an arbitrary box within our space layout, floated against either the left or right edge.
-float LayoutBlockBoxSpace::PositionBox(Vector2f& box_position, float cursor, const Vector2f dimensions, Style::Float float_property) const
+Vector2f FloatedBoxSpace::NextBoxPosition(const BlockContainer* parent, float& maximum_box_width, const float cursor, const Vector2f dimensions,
+	const bool nowrap, const Style::Float float_property) const
 {
-	float parent_scrollbar_width = parent->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
-	float parent_origin = parent->GetPosition().x + parent->GetBox().GetPosition(Box::CONTENT).x;
-	float parent_edge = parent->GetBox().GetSize().x + parent_origin - parent_scrollbar_width;
+	const float parent_scrollbar_width = parent->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
+	const float parent_edge_left = parent->GetPosition().x + parent->GetBox().GetPosition().x;
+	const float parent_edge_right = parent_edge_left + parent->GetBox().GetSize().x - parent_scrollbar_width;
 
-	AnchorEdge box_edge = float_property == Style::Float::Right ? RIGHT : LEFT;
+	const AnchorEdge box_edge = (float_property == Style::Float::Right ? RIGHT : LEFT);
 
-	box_position.y = cursor;
-	box_position.x = parent_origin;
+	Vector2f box_position = {parent_edge_left, cursor};
 
 	if (box_edge == RIGHT)
-		box_position.x += parent->GetBox().GetSize().x - dimensions.x - parent_scrollbar_width;
+		box_position.x = parent_edge_right - dimensions.x;
 
 	float next_cursor = FLT_MAX;
 
 	// First up; we iterate through all boxes that share our edge, pushing ourself to the side of them if we intersect
 	// them. We record the height of the lowest box that gets in our way; in the event we can't be positioned at this
 	// height, we'll reposition ourselves at that height for the next iteration.
-	for (size_t i = 0; i < boxes[box_edge].size(); ++i)
+	for (const FloatedBox& fixed_box : boxes[box_edge])
 	{
-		const SpaceBox& fixed_box = boxes[box_edge][i];
-
 		// If the fixed box's bottom edge is above our top edge, then we can safely skip it.
 		if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
 			continue;
@@ -173,26 +143,22 @@ float LayoutBlockBoxSpace::PositionBox(Vector2f& box_position, float cursor, con
 
 		// If there was a collision, then we *might* want to remember the height of this box if it is the earliest-
 		// terminating box we've collided with so far.
-		if (collision)
+		if (collision && !nowrap)
 		{
 			next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y);
 
 			// Were we pushed out of our containing box? If so, try again at the next cursor position.
-			float normalised_position = box_position.x - parent_origin;
-			if (normalised_position < 0 ||
-				normalised_position + dimensions.x > parent->GetBox().GetSize().x)
-				return PositionBox(box_position, next_cursor + 0.01f, dimensions, float_property);
+			if (box_position.x < parent_edge_left || box_position.x + dimensions.x > parent_edge_right)
+				return NextBoxPosition(parent, maximum_box_width, next_cursor + 0.01f, dimensions, nowrap, float_property);
 		}
 	}
 
 	// Second; we go through all of the boxes on the other edge, checking for horizontal collisions and determining the
 	// maximum width the box can stretch to, if it is placed at this location.
-	float maximum_box_width = box_edge == LEFT ? parent_edge - box_position.x : box_position.x + dimensions.x;
+	maximum_box_width = (box_edge == LEFT ? parent_edge_right - box_position.x : box_position.x + dimensions.x);
 
-	for (size_t i = 0; i < boxes[1 - box_edge].size(); ++i)
+	for (const FloatedBox& fixed_box : boxes[1 - box_edge])
 	{
-		const SpaceBox& fixed_box = boxes[1 - box_edge][i];
-
 		// If the fixed box's bottom edge is above our top edge, then we can safely skip it.
 		if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
 			continue;
@@ -216,20 +182,22 @@ float LayoutBlockBoxSpace::PositionBox(Vector2f& box_position, float cursor, con
 
 		// If we collided with this box ... d'oh! We'll try again lower down the page, at the highest bottom-edge of
 		// any of the boxes we've been pushed around by so far.
-		if (collision)
+		if (collision && !nowrap)
 		{
 			next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y);
-			return PositionBox(box_position, next_cursor + 0.01f, dimensions, float_property);
+			return NextBoxPosition(parent, maximum_box_width, next_cursor + 0.01f, dimensions, nowrap, float_property);
 		}
 	}
 
+	// If we are restricted from wrapping the position down, then we are already done now that we've shifted horizontally.
+	if (nowrap)
+		return box_position;
+
 	// Third; we go through all of the boxes (on both sides), checking for vertical collisions.
 	for (int i = 0; i < 2; ++i)
 	{
-		for (size_t j = 0; j < boxes[i].size(); ++j)
+		for (const FloatedBox& fixed_box : boxes[i])
 		{
-			const SpaceBox& fixed_box = boxes[i][j];
-
 			// If the fixed box's bottom edge is above our top edge, then we can safely skip it.
 			if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
 				continue;
@@ -250,42 +218,46 @@ float LayoutBlockBoxSpace::PositionBox(Vector2f& box_position, float cursor, con
 			// D'oh! We hit this box. Ah well; we'll try again lower down the page, at the highest bottom-edge of any
 			// of the boxes we've been pushed around by so far.
 			next_cursor = Math::Min(next_cursor, fixed_box.offset.y + fixed_box.dimensions.y);
-			return PositionBox(box_position, next_cursor + 0.01f, dimensions, float_property);
+			return NextBoxPosition(parent, maximum_box_width, next_cursor + 0.01f, dimensions, nowrap, float_property);
 		}
 	}
 
 	// Looks like we've found a winner!
-	return maximum_box_width;
+	return box_position;
 }
 
-// Returns the top-left offset of the boxes within the space.
-Vector2f LayoutBlockBoxSpace::GetOffset() const
+Vector2f FloatedBoxSpace::GetDimensions(FloatedBoxEdge edge) const
 {
-	return offset;
+	// For now, we don't really use the top-left extent, because it is not allowed in CSS to scroll to content located
+	// to the top or left, and thus we have no use for it currently. We could use it later to help detect overflow on
+	// the top-left sides. For example so we can hide parts of floats pushing outside the top-left sides of its parent
+	// which is set to 'overflow: auto'.
+	return edge == FloatedBoxEdge::Margin ? extent_bottom_right_margin : extent_bottom_right_overflow;
 }
 
-// Returns the dimensions of the boxes within the space.
-Vector2f LayoutBlockBoxSpace::GetDimensions() const
+float FloatedBoxSpace::GetShrinkToFitWidth(float edge_left, float edge_right) const
 {
-	return dimensions - offset;
-}
+	// For the left-anchored boxes: Find the right-most edge of the boxes, relative to our parent's left edge.
+	float left_shrink_width = 0.f;
+	for (const FloatedBox& box : boxes[LEFT])
+		left_shrink_width = Math::Max(left_shrink_width, box.offset.x - edge_left + box.dimensions.x);
 
-void* LayoutBlockBoxSpace::operator new(size_t size)
-{
-	return LayoutEngine::AllocateLayoutChunk(size);
-}
+	// Conversely, for the right-anchored boxes: Find the left-most edge, relative to our parent's right edge.
+	float right_shrink_width = 0.f;
+	for (const FloatedBox& box : boxes[RIGHT])
+		right_shrink_width = Math::Max(right_shrink_width, edge_right - box.offset.x);
 
-void LayoutBlockBoxSpace::operator delete(void* chunk, size_t size)
-{
-	LayoutEngine::DeallocateLayoutChunk(chunk, size);
+	return left_shrink_width + right_shrink_width;
 }
 
-LayoutBlockBoxSpace::SpaceBox::SpaceBox() : offset(0, 0), dimensions(0, 0)
+void* FloatedBoxSpace::operator new(size_t size)
 {
+	return LayoutPools::AllocateLayoutChunk(size);
 }
 
-LayoutBlockBoxSpace::SpaceBox::SpaceBox(const Vector2f offset, const Vector2f dimensions) : offset(offset), dimensions(dimensions)
+void FloatedBoxSpace::operator delete(void* chunk, size_t size)
 {
+	LayoutPools::DeallocateLayoutChunk(chunk, size);
 }
 
 } // namespace Rml

+ 127 - 0
Source/Core/Layout/FloatedBoxSpace.h

@@ -0,0 +1,127 @@
+/*
+ * 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_LAYOUT_FLOATEDBOXSPACE_H
+#define RMLUI_CORE_LAYOUT_FLOATEDBOXSPACE_H
+
+#include "../../../Include/RmlUi/Core/StyleTypes.h"
+#include "../../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+
+class Element;
+class BlockContainer;
+
+enum class FloatedBoxEdge {
+	Margin,   // The box's margin area, used for layout sizing.
+	Overflow, // Includes the box's border box plus any visible overflow, used to determine overflow and scrolling area.
+};
+
+/**
+    Each block box has a space object for managing the space occupied by its floating elements, and those of its
+    ancestors as relevant.
+ */
+
+class FloatedBoxSpace {
+public:
+	FloatedBoxSpace();
+	~FloatedBoxSpace();
+
+	/// Generates the position for a box of a given size within our block box.
+	/// @param[out] box_width The available width for the box.
+	/// @param[in] cursor The ideal vertical position for the box.
+	/// @param[in] dimensions The minimum available space required for the box.
+	/// @param[in] nowrap Restrict from wrapping down, returned vertical position always placed at ideal cursor.
+	/// @return The generated position for the box.
+	Vector2f NextBoxPosition(const BlockContainer* parent, float& box_width, float cursor, Vector2f dimensions, bool nowrap) const;
+
+	/// Determines the position of a floated element within our block box.
+	/// @param[out] box_width The available width for the box.
+	/// @param[in] cursor The ideal vertical position for the box.
+	/// @param[in] dimensions The floated element's margin size.
+	/// @param[in] float_property The element's computed float property.
+	/// @param[in] clear_property The element's computed clear property.
+	/// @return The next placement position for the float at its top-left margin position.
+	Vector2f NextFloatPosition(const BlockContainer* parent, float& box_width, float cursor, Vector2f dimensions, Style::Float float_property,
+		Style::Clear clear_property) const;
+
+	/// Add a new entry into our list of floated boxes.
+	void PlaceFloat(Style::Float float_property, Vector2f margin_position, Vector2f margin_size, Vector2f overflow_position, Vector2f overflow_size);
+
+	/// Determines the appropriate vertical position for an object that is choosing to clear floating elements to
+	/// the left or right (or both).
+	/// @param[in] cursor The ideal vertical position.
+	/// @param[in] clear_property The value of the clear property of the clearing object.
+	/// @return The appropriate vertical position for the clearing object.
+	float DetermineClearPosition(float cursor, Style::Clear clear_property) const;
+
+	/// Returns the size of the rectangle encompassing all boxes within the space, relative to the block formatting context space.
+	/// @param[in] edges Which edge of the boxes to encompass.
+	Vector2f GetDimensions(FloatedBoxEdge edge) const;
+
+	/// Get the width of the floated boxes for calculating the shrink-to-fit width.
+	float GetShrinkToFitWidth(float edge_left, float edge_right) const;
+
+	/// Clear all floating boxes placed in this space.
+	void Reset()
+	{
+		for (auto& box_list : boxes)
+			box_list.clear();
+		extent_bottom_right_overflow = {};
+		extent_bottom_right_overflow = {};
+		extent_bottom_right_margin = {};
+	}
+
+	void* operator new(size_t size);
+	void operator delete(void* chunk, size_t size);
+
+private:
+	enum AnchorEdge { LEFT = 0, RIGHT = 1, NUM_ANCHOR_EDGES = 2 };
+
+	// Generates the position for an arbitrary box within our space layout, floated against either the left or right edge.
+	Vector2f NextBoxPosition(const BlockContainer* parent, float& maximum_box_width, float cursor, Vector2f dimensions, bool nowrap,
+		Style::Float float_property) const;
+
+	struct FloatedBox {
+		Vector2f offset;
+		Vector2f dimensions;
+	};
+
+	using FloatedBoxList = Vector<FloatedBox>;
+
+	// The boxes floating in our space.
+	FloatedBoxList boxes[NUM_ANCHOR_EDGES];
+
+	// The rectangle encompassing all boxes added specifically into this space, relative to our block formatting context space.
+	Vector2f extent_top_left_overflow;
+	Vector2f extent_bottom_right_overflow;
+	Vector2f extent_bottom_right_margin;
+};
+
+} // namespace Rml
+#endif

+ 75 - 0
Source/Core/Layout/FormattingContext.cpp

@@ -0,0 +1,75 @@
+/*
+ * 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 "FormattingContext.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "BlockFormattingContext.h"
+#include "FlexFormattingContext.h"
+#include "LayoutBox.h"
+#include "TableFormattingContext.h"
+
+namespace Rml {
+
+UniquePtr<LayoutBox> FormattingContext::FormatIndependent(ContainerBox* parent_container, Element* element, const Box* override_initial_box,
+	FormattingContextType backup_context)
+{
+	using namespace Style;
+	auto& computed = element->GetComputedValues();
+
+	const Display display = computed.display();
+
+	FormattingContextType type = backup_context;
+
+	if (display == Display::Flex)
+	{
+		type = FormattingContextType::Flex;
+	}
+	else if (display == Display::Table)
+	{
+		type = FormattingContextType::Table;
+	}
+	else if (computed.float_() != Float::None || computed.position() == Position::Absolute || computed.position() == Position::Fixed ||
+		computed.display() == Display::InlineBlock || computed.display() == Display::TableCell || computed.overflow_x() != Overflow::Visible ||
+		computed.overflow_y() != Overflow::Visible || !element->GetParentNode() || element->GetParentNode()->GetDisplay() == Display::Flex)
+	{
+		type = FormattingContextType::Block;
+	}
+
+	switch (type)
+	{
+	case FormattingContextType::Block: return BlockFormattingContext::Format(parent_container, element, override_initial_box);
+	case FormattingContextType::Table: return TableFormattingContext::Format(parent_container, element, override_initial_box);
+	case FormattingContextType::Flex: return FlexFormattingContext::Format(parent_container, element, override_initial_box);
+	case FormattingContextType::None: break;
+	}
+
+	return nullptr;
+}
+
+} // namespace Rml

+ 67 - 0
Source/Core/Layout/FormattingContext.h

@@ -0,0 +1,67 @@
+/*
+ * 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_LAYOUT_FORMATTINGCONTEXT_H
+#define RMLUI_CORE_LAYOUT_FORMATTINGCONTEXT_H
+
+#include "../../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+
+class Box;
+class ContainerBox;
+class LayoutBox;
+
+enum class FormattingContextType {
+	Block,
+	Table,
+	Flex,
+	None,
+};
+
+/*
+    An environment in which related boxes are layed out.
+*/
+class FormattingContext {
+public:
+	/// Format the element in an independent formatting context, generating a new layout box.
+	/// @param[in] parent_container The container box which should act as the new box's parent.
+	/// @param[in] element The element to be formatted.
+	/// @param[in] override_initial_box Optionally set the initial box dimensions, otherwise one will be generated based on the element's properties.
+	/// @param[in] backup_context If a formatting context can not be determined from the element's properties, use this context.
+	/// @return A new, fully formatted layout box, or nullptr if its formatting context could not be determined, or if formatting was unsuccessful.
+	static UniquePtr<LayoutBox> FormatIndependent(ContainerBox* parent_container, Element* element, const Box* override_initial_box,
+		FormattingContextType backup_context);
+
+protected:
+	FormattingContext() = default;
+	~FormattingContext() = default;
+};
+
+} // namespace Rml
+#endif

+ 156 - 0
Source/Core/Layout/InlineBox.cpp

@@ -0,0 +1,156 @@
+/*
+ * 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 "InlineBox.h"
+#include "../../../Include/RmlUi/Core/Box.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/FontMetrics.h"
+
+namespace Rml {
+
+static void ZeroBoxEdge(Box& box, Box::Edge edge)
+{
+	box.SetEdge(Box::PADDING, edge, 0.f);
+	box.SetEdge(Box::BORDER, edge, 0.f);
+	box.SetEdge(Box::MARGIN, edge, 0.f);
+}
+
+InlineLevelBox* InlineBoxBase::AddChild(UniquePtr<InlineLevelBox> child)
+{
+	auto result = child.get();
+	children.push_back(std::move(child));
+	return result;
+}
+
+void InlineBoxBase::GetStrut(float& out_total_height_above, float& out_total_depth_below) const
+{
+	const FontMetrics& font_metrics = GetFontMetrics();
+	const float line_height = GetElement()->GetLineHeight();
+
+	const float half_leading = 0.5f * (line_height - (font_metrics.ascent + font_metrics.descent));
+	out_total_height_above = font_metrics.ascent + half_leading;
+	out_total_depth_below = line_height - out_total_height_above;
+}
+
+String InlineBoxBase::DebugDumpTree(int depth) const
+{
+	String value = InlineLevelBox::DebugDumpTree(depth);
+	for (auto&& child : children)
+		value += child->DebugDumpTree(depth + 1);
+	return value;
+}
+
+InlineBoxBase::InlineBoxBase(Element* element) : InlineLevelBox(element) {}
+
+InlineBoxRoot::InlineBoxRoot(Element* element) : InlineBoxBase(element)
+{
+	float height_above_baseline, depth_below_baseline;
+	GetStrut(height_above_baseline, depth_below_baseline);
+	SetHeight(height_above_baseline, depth_below_baseline);
+}
+
+FragmentConstructor InlineBoxRoot::CreateFragment(InlineLayoutMode /*mode*/, float /*available_width*/, float /*right_spacing_width*/,
+	bool /*first_box*/, LayoutOverflowHandle /*overflow_handle*/)
+{
+	RMLUI_ERROR;
+	return {};
+}
+
+void InlineBoxRoot::Submit(const PlacedFragment& /*placed_fragment*/)
+{
+	RMLUI_ERROR;
+}
+
+InlineBox::InlineBox(const InlineLevelBox* parent, Element* element, const Box& _box) : InlineBoxBase(element), box(_box)
+{
+	RMLUI_ASSERT(box.GetSize().x < 0.f && box.GetSize().y < 0.f);
+
+	const FontMetrics& font_metrics = GetFontMetrics();
+
+	// The inline box content height does not depend on the 'line-height' property, only on the font, and is not exactly
+	// specified by CSS. Here we choose to size the content height equal to the default line-height for the font-size.
+	const float inner_height = 1.2f * (float)font_metrics.size;
+	box.SetContent(Vector2f(-1.f, inner_height));
+
+	float height_above_baseline, depth_below_baseline;
+	GetStrut(height_above_baseline, depth_below_baseline);
+	SetHeightAndVerticalAlignment(height_above_baseline, depth_below_baseline, parent);
+
+	const float edge_left = box.GetCumulativeEdge(Box::PADDING, Box::LEFT);
+	const float edge_right = box.GetCumulativeEdge(Box::PADDING, Box::RIGHT);
+	SetInlineBoxSpacing(edge_left, edge_right);
+
+	// Vertically position the box so that its content box is equally spaced around its font ascent and descent metrics.
+	const float half_leading = 0.5f * (inner_height - (font_metrics.ascent + font_metrics.descent));
+	baseline_to_border_height = font_metrics.ascent + half_leading + box.GetEdge(Box::BORDER, Box::TOP) + box.GetEdge(Box::PADDING, Box::TOP);
+}
+
+FragmentConstructor InlineBox::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool /*first_box*/,
+	LayoutOverflowHandle /*overflow_handle*/)
+{
+	if (mode != InlineLayoutMode::WrapAny || right_spacing_width <= available_width + GetSpacingLeft())
+		return FragmentConstructor{FragmentType::InlineBox, -1.f, {}, {}};
+
+	return {};
+}
+
+void InlineBox::Submit(const PlacedFragment& placed_fragment)
+{
+	Element* element = GetElement();
+	RMLUI_ASSERT(element && element != placed_fragment.offset_parent);
+
+	Box element_box = box;
+	element_box.SetContent({placed_fragment.layout_width, element_box.GetSize().y});
+
+	if (placed_fragment.split_left)
+		ZeroBoxEdge(element_box, Box::LEFT);
+	if (placed_fragment.split_right)
+		ZeroBoxEdge(element_box, Box::RIGHT);
+
+	// In inline layout, fragments are positioned in terms of (x: margin edge, y: baseline), while element offsets are
+	// specified relative to their border box. Thus, find the offset from the fragment position to the border edge.
+	const Vector2f border_position = placed_fragment.position + Vector2f{element_box.GetEdge(Box::MARGIN, Box::LEFT), -baseline_to_border_height};
+
+	// We can determine the principal fragment based on its left split: Only the principal one has its left side intact,
+	// subsequent fragments have their left side split.
+	const bool principal_box = !placed_fragment.split_left;
+
+	if (principal_box)
+	{
+		element_offset = border_position;
+		element->SetOffset(border_position, placed_fragment.offset_parent);
+		element->SetBox(element_box);
+		SubmitElementOnLayout();
+	}
+	else
+	{
+		element->AddBox(element_box, border_position - element_offset);
+	}
+}
+
+} // namespace Rml

+ 99 - 0
Source/Core/Layout/InlineBox.h

@@ -0,0 +1,99 @@
+/*
+ * 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_LAYOUT_INLINEBOX_H
+#define RMLUI_CORE_LAYOUT_INLINEBOX_H
+
+#include "../../../Include/RmlUi/Core/Box.h"
+#include "InlineLevelBox.h"
+
+namespace Rml {
+
+class InlineBoxBase : public InlineLevelBox {
+public:
+	InlineLevelBox* AddChild(UniquePtr<InlineLevelBox> child);
+
+	String DebugDumpTree(int depth) const override;
+
+protected:
+	InlineBoxBase(Element* element);
+
+	// Get the total height above and depth below the baseline based on this element's line-height and font.
+	void GetStrut(float& out_total_height_above, float& out_total_depth_below) const;
+
+private:
+	using InlineLevelBoxList = Vector<UniquePtr<InlineLevelBox>>;
+
+	// @performance Use first_child, next_sibling instead to build the tree?
+	InlineLevelBoxList children;
+};
+
+/**
+    Inline boxes are inline-level boxes whose contents (child boxes) participate in the same inline formatting context
+    as the box itself.
+
+    An inline box initially creates an unsized open fragment, since its width depends on its children. The fragment is
+    sized and placed later on, either when its line needs to be split or when its element is closed, which happens after
+    all its children in the element tree have already been placed.
+ */
+class InlineBox final : public InlineBoxBase {
+public:
+	InlineBox(const InlineLevelBox* parent, Element* element, const Box& box);
+
+	FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box,
+		LayoutOverflowHandle overflow_handle) override;
+
+	void Submit(const PlacedFragment& placed_fragment) override;
+
+	String DebugDumpNameValue() const override { return "InlineBox"; }
+
+private:
+	float baseline_to_border_height = 0;
+	Vector2f element_offset;
+	Box box;
+};
+
+/**
+    The root inline box is contained directly within its inline container.
+
+    All content in the current inline formatting context is contained either within the root or one of its decendants.
+ */
+class InlineBoxRoot final : public InlineBoxBase {
+public:
+	InlineBoxRoot(Element* element);
+
+	FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box,
+		LayoutOverflowHandle overflow_handle) override;
+
+	void Submit(const PlacedFragment& placed_fragment) override;
+
+	String DebugDumpNameValue() const override { return "InlineBoxRoot"; }
+};
+
+} // namespace Rml
+#endif

+ 315 - 0
Source/Core/Layout/InlineContainer.cpp

@@ -0,0 +1,315 @@
+/*
+ * 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 "InlineContainer.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/ElementScroll.h"
+#include "../../../Include/RmlUi/Core/ElementText.h"
+#include "../../../Include/RmlUi/Core/ElementUtilities.h"
+#include "../../../Include/RmlUi/Core/Profiling.h"
+#include "../../../Include/RmlUi/Core/Property.h"
+#include "BlockContainer.h"
+#include "FloatedBoxSpace.h"
+#include "InlineLevelBox.h"
+#include "LayoutDetails.h"
+#include "LineBox.h"
+
+namespace Rml {
+
+InlineContainer::InlineContainer(BlockContainer* _parent, float _available_width) :
+	LayoutBox(Type::InlineContainer), parent(_parent), root_inline_box(_parent->GetElement())
+{
+	RMLUI_ASSERT(_parent);
+
+	box_size = {_available_width, -1.f};
+	position = parent->NextBoxPosition();
+
+	const auto& computed = parent->GetElement()->GetComputedValues();
+	element_line_height = computed.line_height().value;
+	wrap_content = (computed.white_space() != Style::WhiteSpace::Nowrap);
+	text_align = computed.text_align();
+}
+
+InlineContainer::~InlineContainer() {}
+
+InlineBox* InlineContainer::AddInlineElement(Element* element, const Box& box)
+{
+	RMLUI_ASSERT(element);
+
+	InlineBox* inline_box = nullptr;
+	InlineLevelBox* inline_level_box = nullptr;
+	InlineBoxBase* parent_box = GetOpenInlineBox();
+
+	if (auto text_element = rmlui_dynamic_cast<ElementText*>(element))
+	{
+		inline_level_box = parent_box->AddChild(MakeUnique<InlineLevelBox_Text>(text_element));
+	}
+	else if (box.GetSize().x >= 0.f)
+	{
+		inline_level_box = parent_box->AddChild(MakeUnique<InlineLevelBox_Atomic>(parent_box, element, box));
+	}
+	else
+	{
+		auto inline_box_ptr = MakeUnique<InlineBox>(parent_box, element, box);
+		inline_box = inline_box_ptr.get();
+		inline_level_box = parent_box->AddChild(std::move(inline_box_ptr));
+	}
+
+	const float minimum_line_height = Math::Max(element_line_height, (box.GetSize().y >= 0.f ? box.GetSizeAcross(Box::VERTICAL, Box::MARGIN) : 0.f));
+
+	LayoutOverflowHandle overflow_handle = {};
+	float minimum_width_next = 0.f;
+
+	while (true)
+	{
+		LineBox* line_box = EnsureOpenLineBox();
+
+		UpdateLineBoxPlacement(line_box, minimum_width_next, minimum_line_height);
+
+		InlineLayoutMode layout_mode = InlineLayoutMode::Nowrap;
+		if (wrap_content)
+		{
+			const bool line_shrinked_by_floats = (line_box->GetLineWidth() + 0.5f < box_size.x && minimum_width_next < box_size.x);
+			const bool can_wrap_any = (line_shrinked_by_floats || line_box->HasContent());
+			layout_mode = (can_wrap_any ? InlineLayoutMode::WrapAny : InlineLayoutMode::WrapAfterContent);
+		}
+
+		const bool add_new_line = line_box->AddBox(inline_level_box, layout_mode, overflow_handle);
+		if (!add_new_line)
+			break;
+
+		minimum_width_next = (line_box->HasContent() ? 0.f : line_box->GetLineWidth() + 1.f);
+
+		// Keep adding boxes on a new line, either because the box couldn't fit on the current line at all, or because it had to be split.
+		CloseOpenLineBox(false);
+	}
+
+	return inline_box;
+}
+
+void InlineContainer::CloseInlineElement(InlineBox* inline_box)
+{
+	if (LineBox* line_box = GetOpenLineBox())
+	{
+		line_box->CloseInlineBox(inline_box);
+	}
+	else
+	{
+		RMLUI_ERROR;
+	}
+}
+
+void InlineContainer::AddBreak(float line_height)
+{
+	// Simply end the line if one is open, otherwise increment by the line height.
+	if (GetOpenLineBox())
+		CloseOpenLineBox(true);
+	else
+		box_cursor += line_height;
+}
+
+void InlineContainer::AddChainedBox(UniquePtr<LineBox> open_line_box)
+{
+	RMLUI_ASSERT(line_boxes.empty());
+	RMLUI_ASSERT(open_line_box && !open_line_box->IsClosed());
+	line_boxes.push_back(std::move(open_line_box));
+}
+
+void InlineContainer::Close(UniquePtr<LineBox>* out_open_line_box, Vector2f& out_position, float& out_height)
+{
+	RMLUI_ZoneScoped;
+
+	// The parent container may need the open line box to be split and resumed.
+	CloseOpenLineBox(true, out_open_line_box);
+
+	// It is possible that floats were queued between closing the last line and closing this container, if so place them now.
+	parent->PlaceQueuedFloats(position.y + box_cursor);
+
+	// Set this box's height.
+	box_size.y = Math::Max(box_cursor, 0.f);
+
+	// Find the overflow size for our content, relative to our local space.
+	Vector2f visible_overflow_size = {0.f, box_size.y};
+
+	for (const auto& line_box : line_boxes)
+	{
+		visible_overflow_size.x = Math::Max(visible_overflow_size.x, line_box->GetPosition().x - position.x + line_box->GetExtentRight());
+	}
+
+	visible_overflow_size.x = Math::RoundDownFloat(visible_overflow_size.x);
+	SetVisibleOverflowSize(visible_overflow_size);
+
+	out_position = position;
+	out_height = box_size.y;
+}
+
+void InlineContainer::CloseOpenLineBox(bool split_all_open_boxes, UniquePtr<LineBox>* out_split_line)
+{
+	if (LineBox* line_box = GetOpenLineBox())
+	{
+		float height_of_line = 0.f;
+		UniquePtr<LineBox> split_line_box = line_box->DetermineVerticalPositioning(&root_inline_box, split_all_open_boxes, height_of_line);
+
+		// If the final height of the line is larger than previously considered, we might need to push the line down to
+		// clear overlapping floats.
+		if (height_of_line > line_box->GetLineMinimumHeight())
+			UpdateLineBoxPlacement(line_box, 0.f, height_of_line);
+
+		// Now that the line has been given a final position and size, close the line box to submit all the fragments.
+		// Our parent block container acts as the containing block for our inline boxes.
+		line_box->Close(parent->GetElement(), parent->GetPosition(), text_align);
+
+		// Move the cursor down, unless we should collapse the line.
+		if (!line_box->CanCollapseLine())
+			box_cursor = (line_box->GetPosition().y - position.y) + height_of_line;
+
+		// If we have any pending floating elements for our parent, then this would be an ideal time to place them.
+		parent->PlaceQueuedFloats(position.y + box_cursor);
+
+		if (split_line_box)
+		{
+			if (out_split_line)
+				*out_split_line = std::move(split_line_box);
+			else
+				line_boxes.push_back(std::move(split_line_box));
+		}
+	}
+}
+
+bool InlineContainer::GetOpenLineBoxDimensions(float& out_vertical_position, Vector2f& out_tentative_size) const
+{
+	if (LineBox* line_box = GetOpenLineBox())
+	{
+		out_vertical_position = position.y + box_cursor;
+		out_tentative_size = {line_box->GetBoxCursor(), line_box->GetLineMinimumHeight()};
+		return true;
+	}
+	return false;
+}
+
+void InlineContainer::UpdateOpenLineBoxPlacement()
+{
+	if (LineBox* line_box = GetOpenLineBox())
+		UpdateLineBoxPlacement(line_box, 0.f, element_line_height);
+}
+
+void InlineContainer::UpdateLineBoxPlacement(LineBox* line_box, float minimum_width, float minimum_height)
+{
+	RMLUI_ASSERT(line_box);
+
+	Vector2f minimum_dimensions = {
+		Math::Max(minimum_width, line_box->GetBoxCursor()),
+		Math::Max(minimum_height, line_box->GetLineMinimumHeight()),
+	};
+
+	// @performance: We might benefit from doing this search only when the minimum dimensions change, or if we get new inline floats.
+	const float ideal_position_y = position.y + box_cursor;
+	float available_width = 0.f;
+	const Vector2f line_position =
+		parent->GetBlockBoxSpace()->NextBoxPosition(parent, available_width, ideal_position_y, minimum_dimensions, !wrap_content);
+	available_width = Math::Max(available_width, 0.f);
+
+	line_box->SetLineBox(line_position, available_width, minimum_dimensions.y);
+}
+
+float InlineContainer::GetShrinkToFitWidth() const
+{
+	float content_width = 0.0f;
+
+	// Simply find our widest line.
+	for (const auto& line_box : line_boxes)
+		content_width = Math::Max(content_width, line_box->GetBoxCursor());
+
+	return content_width;
+}
+
+Vector2f InlineContainer::GetStaticPositionEstimate(bool inline_level_box) const
+{
+	Vector2f result = {0.f, box_cursor};
+
+	if (const LineBox* line_box = GetOpenLineBox())
+	{
+		if (inline_level_box)
+			result.x += line_box->GetBoxCursor();
+		else
+			result.y += element_line_height;
+	}
+
+	return result;
+}
+
+bool InlineContainer::GetBaselineOfLastLine(float& out_baseline) const
+{
+	if (!line_boxes.empty())
+	{
+		out_baseline = line_boxes.back()->GetPosition().y + line_boxes.back()->GetBaseline();
+		return true;
+	}
+	return false;
+}
+
+LineBox* InlineContainer::EnsureOpenLineBox()
+{
+	if (line_boxes.empty() || line_boxes.back()->IsClosed())
+	{
+		line_boxes.push_back(MakeUnique<LineBox>());
+	}
+	return line_boxes.back().get();
+}
+
+LineBox* InlineContainer::GetOpenLineBox() const
+{
+	if (line_boxes.empty() || line_boxes.back()->IsClosed())
+		return nullptr;
+	return line_boxes.back().get();
+}
+
+InlineBoxBase* InlineContainer::GetOpenInlineBox()
+{
+	if (LineBox* line_box = GetOpenLineBox())
+	{
+		if (InlineBox* inline_box = line_box->GetOpenInlineBox())
+			return inline_box;
+	}
+	return &root_inline_box;
+}
+
+String InlineContainer::DebugDumpTree(int depth) const
+{
+	String value = String(depth * 2, ' ') + "InlineContainer" + '\n';
+
+	value += root_inline_box.DebugDumpTree(depth + 1);
+
+	for (const auto& line_box : line_boxes)
+		value += line_box->DebugDumpTree(depth + 1);
+
+	return value;
+}
+
+} // namespace Rml

+ 135 - 0
Source/Core/Layout/InlineContainer.h

@@ -0,0 +1,135 @@
+/*
+ * 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_LAYOUT_INLINECONTAINER_H
+#define RMLUI_CORE_LAYOUT_INLINECONTAINER_H
+
+#include "../../../Include/RmlUi/Core/Box.h"
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "InlineBox.h"
+#include "LayoutBox.h"
+
+namespace Rml {
+
+class BlockContainer;
+class LineBox;
+
+/**
+    A container for inline-level boxes.
+
+    Always a direct child of a block container where it acts as a block-level box, and starts a new inline formatting
+    context. It maintains a stack of line boxes in which fragments generated from inline-level boxes are placed within.
+    Not a proper CSS term, but effectively a "block container that only contains inline-level boxes".
+ */
+class InlineContainer final : public LayoutBox {
+public:
+	/// Creates a new block box in an inline context.
+	InlineContainer(BlockContainer* parent, float available_width);
+	~InlineContainer();
+
+	/// Adds a new inline-level element to this inline-context box.
+	/// @param[in] element The new inline-level element.
+	/// @param[in] box The box defining the element's bounds.
+	/// @return The inline box if one was generated for the elmeent, otherwise nullptr.
+	/// @note Any non-null return value must be closed with a call to CloseInlineElement().
+	InlineBox* AddInlineElement(Element* element, const Box& box);
+
+	/// Closes the previously added inline box.
+	/// @param[in] inline_box The box to close.
+	/// @note Calls to this function should be submitted in reverse order to AddInlineElement().
+	void CloseInlineElement(InlineBox* inline_box);
+
+	/// Add a break to the last line.
+	void AddBreak(float line_height);
+	/// Adds a line box for resuming one that was previously split.
+	/// @param[in] open_line_box The line box overflowing from a previous inline container.
+	void AddChainedBox(UniquePtr<LineBox> open_line_box);
+
+	/// Closes the box. This will determine the element's height (if it was unspecified).
+	/// @param[out] out_open_line_box Optionally, output the open inline box.
+	/// @param[out] out_position Outputs the position of this container.
+	/// @param[out] out_height Outputs the height of this container.
+	void Close(UniquePtr<LineBox>* out_open_line_box, Vector2f& out_position, float& out_height);
+
+	/// Update the placement of the open line box, e.g. to account for newly placed floats.
+	void UpdateOpenLineBoxPlacement();
+
+	/// Returns the position and tentative size of the currently open line box, if any.
+	bool GetOpenLineBoxDimensions(float& out_vertical_position, Vector2f& out_tentative_size) const;
+	/// Returns an estimate for the position of a hypothetical next box to be placed, relative to the content box of this container.
+	Vector2f GetStaticPositionEstimate(bool inline_level_box) const;
+
+	// -- Inherited from LayoutBox --
+
+	float GetShrinkToFitWidth() const override;
+	bool GetBaselineOfLastLine(float& out_baseline) const override;
+	String DebugDumpTree(int depth) const override;
+
+private:
+	using LineBoxList = Vector<UniquePtr<LineBox>>;
+
+	LineBox* EnsureOpenLineBox();
+	LineBox* GetOpenLineBox() const;
+	InlineBoxBase* GetOpenInlineBox();
+
+	/// Close any open line box.
+	/// @param[in] split_all_open_boxes Split all open inline boxes, even if they have no content.
+	/// @param[out] out_split_line Optionally return any resulting split line, otherwise it will be added as a new line box to this container.
+	void CloseOpenLineBox(bool split_all_open_boxes, UniquePtr<LineBox>* out_split_line = nullptr);
+
+	/// Find and set the position and line width for the currently open line box.
+	/// @param[in] line_box The currently open line box.
+	/// @param[in] minimum_width The minimum line width to consider for this search.
+	/// @param[in] minimum_height The minimum line height to to be considered for this and future searches.
+	void UpdateLineBoxPlacement(LineBox* line_box, float minimum_width, float minimum_height);
+
+	BlockContainer* parent; // [not-null]
+
+	Vector2f position;
+	Vector2f box_size;
+
+	// The element's computed line-height. Not necessarily the same as the height of our lines.
+	float element_line_height = 0.f;
+	// True if content should wrap instead of overflowing the line box.
+	bool wrap_content = false;
+	// The element's text-align property.
+	Style::TextAlign text_align = {};
+
+	// The vertical position of the currently open line, or otherwise the next one to be placed, relative to the top of this box.
+	float box_cursor = 0;
+
+	// The root of the tree of inline boxes located in this inline container.
+	InlineBoxRoot root_inline_box;
+
+	// The list of line boxes, each of which flows fragments generated by inline boxes.
+	// @performance Investigate using a list backed by the pools instead, to avoid allocations.
+	LineBoxList line_boxes;
+};
+
+} // namespace Rml
+#endif

+ 233 - 0
Source/Core/Layout/InlineLevelBox.cpp

@@ -0,0 +1,233 @@
+/*
+ * 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 "InlineLevelBox.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Core.h"
+#include "../../../Include/RmlUi/Core/ElementText.h"
+#include "../../../Include/RmlUi/Core/FontEngineInterface.h"
+#include "LayoutDetails.h"
+#include "LayoutPools.h"
+
+namespace Rml {
+
+void* InlineLevelBox::operator new(size_t size)
+{
+	return LayoutPools::AllocateLayoutChunk(size);
+}
+
+void InlineLevelBox::operator delete(void* chunk, size_t size)
+{
+	LayoutPools::DeallocateLayoutChunk(chunk, size);
+}
+
+InlineLevelBox::~InlineLevelBox() {}
+
+void InlineLevelBox::SubmitElementOnLayout()
+{
+	element->OnLayout();
+}
+
+const FontMetrics& InlineLevelBox::GetFontMetrics() const
+{
+	if (FontFaceHandle handle = element->GetFontFaceHandle())
+		return GetFontEngineInterface()->GetFontMetrics(handle);
+
+	// If there is no font face defined then we provide zero'd out font metrics. This situation can affect the layout,
+	// in particular in terms of inline box sizing and vertical alignment. Thus, this is potentially a situation where
+	// we might want to log a warning. However, in many cases it will produce the same layout with or without the font,
+	// so in that sense the warnings can produce false positives.
+	//
+	// For now, we wait until we try to actually place text before producing any warnings, since that is a clear
+	// erroneous situation producing no text. See 'LogMissingFontFace' in ElementText.cpp, which also lists some
+	// possible reasons for the missing font face.
+
+	static const FontMetrics font_metrics = {};
+	return font_metrics;
+}
+
+void InlineLevelBox::SetHeightAndVerticalAlignment(float _height_above_baseline, float _depth_below_baseline, const InlineLevelBox* parent)
+{
+	RMLUI_ASSERT(parent);
+	using Style::VerticalAlign;
+
+	SetHeight(_height_above_baseline, _depth_below_baseline);
+
+	const Style::VerticalAlign vertical_align = element->GetComputedValues().vertical_align();
+	vertical_align_type = vertical_align.type;
+
+	// Determine the offset from the parent baseline.
+	float parent_baseline_offset = 0.f; // The anchor on the parent, as an offset from its baseline.
+	float self_baseline_offset = 0.f;   // The offset from the parent anchor to our baseline.
+
+	switch (vertical_align.type)
+	{
+	case VerticalAlign::Baseline: parent_baseline_offset = 0.f; break;
+	case VerticalAlign::Length: parent_baseline_offset = -vertical_align.value; break;
+	case VerticalAlign::Sub: parent_baseline_offset = (1.f / 5.f) * (float)parent->GetFontMetrics().size; break;
+	case VerticalAlign::Super: parent_baseline_offset = (-1.f / 3.f) * (float)parent->GetFontMetrics().size; break;
+	case VerticalAlign::TextTop:
+		parent_baseline_offset = -parent->GetFontMetrics().ascent;
+		self_baseline_offset = height_above_baseline;
+		break;
+	case VerticalAlign::TextBottom:
+		parent_baseline_offset = parent->GetFontMetrics().descent;
+		self_baseline_offset = -depth_below_baseline;
+		break;
+	case VerticalAlign::Middle:
+		parent_baseline_offset = -0.5f * parent->GetFontMetrics().x_height;
+		self_baseline_offset = 0.5f * (height_above_baseline - depth_below_baseline);
+		break;
+	case VerticalAlign::Top:
+	case VerticalAlign::Bottom:
+		// These are relative to the line box and handled later.
+		break;
+	}
+
+	vertical_offset_from_parent = parent_baseline_offset + self_baseline_offset;
+}
+
+void InlineLevelBox::SetHeight(float _height_above_baseline, float _depth_below_baseline)
+{
+	height_above_baseline = _height_above_baseline;
+	depth_below_baseline = _depth_below_baseline;
+}
+
+void InlineLevelBox::SetInlineBoxSpacing(float _spacing_left, float _spacing_right)
+{
+	spacing_left = _spacing_left;
+	spacing_right = _spacing_right;
+}
+
+String InlineLevelBox::DebugDumpTree(int depth) const
+{
+	String value = String(depth * 2, ' ') + DebugDumpNameValue() + " | " + LayoutDetails::GetDebugElementName(GetElement()) + '\n';
+	return value;
+}
+
+InlineLevelBox_Atomic::InlineLevelBox_Atomic(const InlineLevelBox* parent, Element* element, const Box& box) : InlineLevelBox(element), box(box)
+{
+	RMLUI_ASSERT(parent && element);
+	RMLUI_ASSERT(box.GetSize().x >= 0.f && box.GetSize().y >= 0.f);
+
+	const float outer_height = box.GetSizeAcross(Box::VERTICAL, Box::MARGIN);
+
+	const float descent = GetElement()->GetBaseline();
+	const float ascent = outer_height - descent;
+	SetHeightAndVerticalAlignment(ascent, descent, parent);
+}
+
+FragmentConstructor InlineLevelBox_Atomic::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool /*first_box*/,
+	LayoutOverflowHandle /*overflow_handle*/)
+{
+	const float outer_width = box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN);
+
+	if (mode != InlineLayoutMode::WrapAny || outer_width + right_spacing_width <= available_width)
+		return FragmentConstructor{FragmentType::SizedBox, outer_width, {}, {}};
+
+	return {};
+}
+
+void InlineLevelBox_Atomic::Submit(const PlacedFragment& placed_fragment)
+{
+	const Vector2f margin_position = {placed_fragment.position.x, placed_fragment.position.y - GetHeightAboveBaseline()};
+	const Vector2f margin_edge = {box.GetEdge(Box::MARGIN, Box::LEFT), box.GetEdge(Box::MARGIN, Box::TOP)};
+	const Vector2f border_position = margin_position + margin_edge;
+
+	GetElement()->SetOffset(border_position, placed_fragment.offset_parent);
+	GetElement()->SetBox(box);
+	SubmitElementOnLayout();
+}
+
+InlineLevelBox_Text::InlineLevelBox_Text(ElementText* element) : InlineLevelBox(element) {}
+
+FragmentConstructor InlineLevelBox_Text::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box,
+	LayoutOverflowHandle in_overflow_handle)
+{
+	ElementText* text_element = GetTextElement();
+
+	const bool allow_empty = (mode == InlineLayoutMode::WrapAny);
+	const bool decode_escape_characters = true;
+
+	String line_contents;
+	int line_begin = in_overflow_handle;
+	int line_length = 0;
+	float line_width = 0.f;
+	bool overflow = !text_element->GenerateLine(line_contents, line_length, line_width, line_begin, available_width, right_spacing_width, first_box,
+		decode_escape_characters, allow_empty);
+
+	if (overflow && line_contents.empty())
+		// We couldn't fit anything on this line.
+		return {};
+
+	LayoutOverflowHandle out_overflow_handle = {};
+	if (overflow)
+		out_overflow_handle = line_begin + line_length;
+
+	LayoutFragmentHandle fragment_handle = (LayoutFragmentHandle)fragments.size();
+	fragments.push_back(std::move(line_contents));
+
+	return FragmentConstructor{FragmentType::TextRun, line_width, fragment_handle, out_overflow_handle};
+}
+
+void InlineLevelBox_Text::Submit(const PlacedFragment& placed_fragment)
+{
+	RMLUI_ASSERT((size_t)placed_fragment.handle < fragments.size());
+
+	const int fragment_index = (int)placed_fragment.handle;
+	const bool principal_box = (fragment_index == 0);
+
+	ElementText* text_element = GetTextElement();
+	Vector2f line_offset;
+
+	if (principal_box)
+	{
+		element_offset = placed_fragment.position;
+		text_element->SetOffset(placed_fragment.position, placed_fragment.offset_parent);
+		text_element->ClearLines();
+	}
+	else
+	{
+		line_offset = placed_fragment.position - element_offset;
+	}
+
+	text_element->AddLine(line_offset, std::move(fragments[fragment_index]));
+}
+
+String InlineLevelBox_Text::DebugDumpNameValue() const
+{
+	return "InlineLevelBox_Text";
+}
+
+ElementText* InlineLevelBox_Text::GetTextElement()
+{
+	RMLUI_ASSERT(rmlui_dynamic_cast<ElementText*>(GetElement()));
+
+	return static_cast<ElementText*>(GetElement());
+}
+} // namespace Rml

+ 145 - 0
Source/Core/Layout/InlineLevelBox.h

@@ -0,0 +1,145 @@
+/*
+ * 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_LAYOUT_INLINELEVELBOX_H
+#define RMLUI_CORE_LAYOUT_INLINELEVELBOX_H
+
+#include "../../../Include/RmlUi/Core/Box.h"
+#include "../../../Include/RmlUi/Core/StyleTypes.h"
+#include "InlineTypes.h"
+
+namespace Rml {
+
+class ElementText;
+struct FontMetrics;
+
+/**
+    A box that takes part in inline layout.
+
+    The inline-level box is used to generate fragments that are placed within line boxes.
+ */
+class InlineLevelBox {
+public:
+	virtual ~InlineLevelBox();
+
+	// Create a fragment from this box, if it can fit within the available width.
+	virtual FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box,
+		LayoutOverflowHandle overflow_handle) = 0;
+
+	// Submit a fragment's position and size to be displayed on the underlying element.
+	virtual void Submit(const PlacedFragment& placed_fragment) = 0;
+
+	float GetHeightAboveBaseline() const { return height_above_baseline; }
+	float GetDepthBelowBaseline() const { return depth_below_baseline; }
+	Style::VerticalAlign::Type GetVerticalAlign() const { return vertical_align_type; }
+	float GetVerticalOffsetFromParent() const { return vertical_offset_from_parent; }
+	float GetSpacingLeft() const { return spacing_left; }
+	float GetSpacingRight() const { return spacing_right; }
+
+	virtual String DebugDumpNameValue() const = 0;
+	virtual String DebugDumpTree(int depth) const;
+
+	void* operator new(size_t size);
+	void operator delete(void* chunk, size_t size);
+
+protected:
+	InlineLevelBox(Element* element) : element(element) { RMLUI_ASSERT(element); }
+
+	Element* GetElement() const { return element; }
+	const FontMetrics& GetFontMetrics() const;
+
+	// Set the height used for inline layout, and the vertical offset relative to our parent box.
+	void SetHeightAndVerticalAlignment(float height_above_baseline, float depth_below_baseline, const InlineLevelBox* parent);
+
+	// Set the height used for inline layout.
+	void SetHeight(float height_above_baseline, float depth_below_baseline);
+
+	// Set the inner-to-outer spacing (margin + border + padding) for inline boxes.
+	void SetInlineBoxSpacing(float spacing_left, float spacing_right);
+
+	// Calls Element::OnLayout (proxy for private access to Element).
+	void SubmitElementOnLayout();
+
+private:
+	float height_above_baseline = 0.f;
+	float depth_below_baseline = 0.f;
+
+	Style::VerticalAlign::Type vertical_align_type = {};
+	float vertical_offset_from_parent = 0.f;
+
+	float spacing_left = 0.f;  // Left margin-border-padding for inline boxes.
+	float spacing_right = 0.f; // Right margin-border-padding for inline boxes.
+
+	Element* element;
+};
+
+/**
+    Atomic inline-level boxes are sized boxes that cannot be split.
+
+    This includes inline-block elements, replaced inline-level elements, inline tables, and inline flex containers.
+ */
+class InlineLevelBox_Atomic final : public InlineLevelBox {
+public:
+	InlineLevelBox_Atomic(const InlineLevelBox* parent, Element* element, const Box& box);
+
+	FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box,
+		LayoutOverflowHandle overflow_handle) override;
+
+	void Submit(const PlacedFragment& placed_fragment) override;
+
+	String DebugDumpNameValue() const override { return "InlineLevelBox_Atomic"; }
+
+private:
+	Box box;
+};
+
+/**
+    Inline-level text boxes represent text nodes.
+
+    Generates fragments to display its text, splitting it up as necessary to fit in the available space.
+ */
+class InlineLevelBox_Text final : public InlineLevelBox {
+public:
+	InlineLevelBox_Text(ElementText* element);
+
+	FragmentConstructor CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool first_box,
+		LayoutOverflowHandle overflow_handle) override;
+
+	void Submit(const PlacedFragment& placed_fragment) override;
+
+	String DebugDumpNameValue() const override;
+
+private:
+	ElementText* GetTextElement();
+
+	Vector2f element_offset;
+	StringList fragments;
+};
+
+} // namespace Rml
+#endif

+ 69 - 0
Source/Core/Layout/InlineTypes.h

@@ -0,0 +1,69 @@
+/*
+ * 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_LAYOUT_INLINETYPES_H
+#define RMLUI_CORE_LAYOUT_INLINETYPES_H
+
+#include "../../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+
+using LayoutOverflowHandle = int;
+using LayoutFragmentHandle = int;
+
+enum class InlineLayoutMode {
+	WrapAny,          // Allow wrapping to avoid overflow, even if nothing is placed.
+	WrapAfterContent, // Allow wrapping to avoid overflow, but first place at least *some* content on this line.
+	Nowrap,           // Place all content on this line, regardless of overflow.
+};
+
+enum class FragmentType : byte {
+	Invalid,   // Could not be placed.
+	InlineBox, // An inline box.
+	SizedBox,  // Sized inline-level boxes that are not inline-boxes.
+	TextRun,   // Text runs.
+};
+
+struct FragmentConstructor {
+	FragmentType type = FragmentType::Invalid;
+	float layout_width = 0.f;
+	LayoutFragmentHandle fragment_handle = {}; // Handle to enable the inline-level box to reference any fragment-specific data.
+	LayoutOverflowHandle overflow_handle = {}; // Overflow handle is non-zero when there is another fragment to be layed out.
+};
+
+struct PlacedFragment {
+	Element* offset_parent;
+	LayoutFragmentHandle handle;
+	Vector2f position;
+	float layout_width;
+	bool split_left;
+	bool split_right;
+};
+
+} // namespace Rml
+#endif

+ 59 - 0
Source/Core/Layout/LayoutBox.cpp

@@ -0,0 +1,59 @@
+/*
+ * 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 "LayoutBox.h"
+#include "LayoutPools.h"
+
+namespace Rml {
+
+const Box* LayoutBox::GetIfBox() const
+{
+	return nullptr;
+}
+
+bool LayoutBox::GetBaselineOfLastLine(float& /*out_baseline*/) const
+{
+	return false;
+}
+
+float LayoutBox::GetShrinkToFitWidth() const
+{
+	return 0.f;
+}
+void* LayoutBox::operator new(size_t size)
+{
+	void* memory = LayoutPools::AllocateLayoutChunk(size);
+	return memory;
+}
+
+void LayoutBox::operator delete(void* chunk, size_t size)
+{
+	LayoutPools::DeallocateLayoutChunk(chunk, size);
+}
+
+} // namespace Rml

+ 78 - 0
Source/Core/Layout/LayoutBox.h

@@ -0,0 +1,78 @@
+/*
+ * 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_LAYOUT_LAYOUTBOX_H
+#define RMLUI_CORE_LAYOUT_LAYOUTBOX_H
+
+#include "../../../Include/RmlUi/Core/Box.h"
+#include "../../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+
+/*
+    A box used to represent the formatting structure of the document, taking part in the box tree.
+*/
+class LayoutBox {
+public:
+	enum class Type { Root, BlockContainer, InlineContainer, FlexContainer, TableWrapper };
+
+	virtual ~LayoutBox() = default;
+
+	Type GetType() const { return type; }
+	Vector2f GetVisibleOverflowSize() const { return visible_overflow_size; }
+
+	// Return a pointer to the dimensions box if this layout box has one.
+	virtual const Box* GetIfBox() const;
+	// Return the baseline of the last line of this box, if any. Returns true if a baseline was found, otherwise false.
+	virtual bool GetBaselineOfLastLine(float& out_baseline) const;
+	// Calculate the box's inner content width, i.e. the size used to calculate the shrink-to-fit width.
+	virtual float GetShrinkToFitWidth() const;
+
+	// Debug dump layout tree.
+	String DumpLayoutTree(int depth = 0) const { return DebugDumpTree(depth); }
+
+	void* operator new(size_t size);
+	void operator delete(void* chunk, size_t size);
+
+protected:
+	LayoutBox(Type type) : type(type) {}
+
+	void SetVisibleOverflowSize(Vector2f size) { visible_overflow_size = size; }
+
+	virtual String DebugDumpTree(int depth) const = 0;
+
+private:
+	Type type;
+
+	// Visible overflow size is the border size of this box, plus any overflowing content. If this is a scroll
+	// container, then any overflow should be caught here, and not contribute to our visible overflow size.
+	Vector2f visible_overflow_size;
+};
+
+} // namespace Rml
+#endif

+ 131 - 115
Source/Core/LayoutDetails.cpp → Source/Core/Layout/LayoutDetails.cpp

@@ -15,7 +15,7 @@
  *
  * 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
@@ -27,11 +27,14 @@
  */
 
 #include "LayoutDetails.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/Element.h"
-#include "../../Include/RmlUi/Core/ElementScroll.h"
-#include "../../Include/RmlUi/Core/Math.h"
-#include "../../Include/RmlUi/Core/Profiling.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/ElementScroll.h"
+#include "../../../Include/RmlUi/Core/ElementText.h"
+#include "../../../Include/RmlUi/Core/Math.h"
+#include "../../../Include/RmlUi/Core/Profiling.h"
+#include "ContainerBox.h"
+#include "FormattingContext.h"
 #include "LayoutEngine.h"
 #include <float.h>
 
@@ -40,14 +43,14 @@ namespace Rml {
 // 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)
 {
-	if (border_size < 0.0f || border_size == FLT_MAX)
+	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.
-void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, BoxContext box_context, float override_shrink_to_fit_width)
+void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, BuildBoxMode box_context)
 {
 	if (!element)
 	{
@@ -82,22 +85,19 @@ void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* eleme
 
 	// 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 (box_context == BoxContext::Block || box_context == BoxContext::FlexOrTable || replaced_element)
+	if (box_context == BuildBoxMode::Block || box_context == BuildBoxMode::UnalignedBlock || replaced_element)
 	{
-		if (content_area.x < 0 && computed.width().type != Style::Width::Auto)
-			content_area.x = ResolveValue(computed.width(), containing_block.x);
-
-		if (content_area.y < 0 && computed.height().type != Style::Width::Auto)
-			content_area.y = ResolveValue(computed.height(), containing_block.y);
-
-		min_size = Vector2f(
-			ResolveValue(computed.min_width(), containing_block.x),
-			ResolveValue(computed.min_height(), containing_block.y)
-		);
-		max_size = Vector2f(
-			ResolveValue(computed.max_width(), containing_block.x),
-			ResolveValue(computed.max_height(), containing_block.y)
-		);
+		content_area.x = ResolveValueOr(computed.width(), containing_block.x, -1.f);
+		content_area.y = ResolveValueOr(computed.height(), containing_block.y, -1.f);
+
+		min_size = Vector2f{
+			ResolveValueOr(computed.min_width(), containing_block.x, 0.f),
+			ResolveValueOr(computed.min_height(), containing_block.y, 0.f),
+		};
+		max_size = Vector2f{
+			ResolveValueOr(computed.max_width(), containing_block.x, FLT_MAX),
+			ResolveValueOr(computed.max_height(), containing_block.y, FLT_MAX),
+		};
 
 		// Adjust sizes for the given box sizing model.
 		if (computed.box_sizing() == Style::BoxSizing::BorderBox)
@@ -126,27 +126,13 @@ void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* eleme
 	box.SetContent(content_area);
 
 	// Evaluate the margins, and width and height if they are auto.
-	BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element, box_context, replaced_element, override_shrink_to_fit_width);
-}
-
-// Generates the box for an element placed in a block box.
-void LayoutDetails::BuildBox(Box& box, float& min_height, float& max_height, LayoutBlockBox* containing_box, Element* element, BoxContext box_context,
-	float override_shrink_to_fit_width)
-{
-	Vector2f containing_block = LayoutDetails::GetContainingBlock(containing_box);
-
-	BuildBox(box, containing_block, element, box_context, override_shrink_to_fit_width);
-
-	if (element)
-		GetDefiniteMinMaxHeight(min_height, max_height, element->GetComputedValues(), box, containing_block.y);
-	else
-		min_height = max_height = box.GetSize().y;
+	BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element, box_context, replaced_element);
 }
 
 void LayoutDetails::GetMinMaxWidth(float& min_width, float& max_width, const ComputedValues& computed, const Box& box, float containing_block_width)
 {
-	min_width = ResolveValue(computed.min_width(), containing_block_width);
-	max_width = ResolveValue(computed.max_width(), containing_block_width);
+	min_width = ResolveValueOr(computed.min_width(), containing_block_width, 0.f);
+	max_width = ResolveValueOr(computed.max_width(), containing_block_width, FLT_MAX);
 
 	if (computed.box_sizing() == Style::BoxSizing::BorderBox)
 	{
@@ -156,10 +142,11 @@ void LayoutDetails::GetMinMaxWidth(float& min_width, float& max_width, const Com
 	}
 }
 
-void LayoutDetails::GetMinMaxHeight(float& min_height, float& max_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)
 {
-	min_height = ResolveValue(computed.min_height(), containing_block_height);
-	max_height = ResolveValue(computed.max_height(), containing_block_height);
+	min_height = ResolveValueOr(computed.min_height(), containing_block_height, 0.f);
+	max_height = ResolveValueOr(computed.max_height(), containing_block_height, FLT_MAX);
 
 	if (computed.box_sizing() == Style::BoxSizing::BorderBox)
 	{
@@ -169,7 +156,8 @@ void LayoutDetails::GetMinMaxHeight(float& min_height, float& max_height, const
 	}
 }
 
-void LayoutDetails::GetDefiniteMinMaxHeight(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;
 	if (box_height < 0)
@@ -183,47 +171,63 @@ void LayoutDetails::GetDefiniteMinMaxHeight(float& min_height, float& max_height
 	}
 }
 
-// Returns the fully-resolved, fixed-width and -height containing block from a block box.
-Vector2f LayoutDetails::GetContainingBlock(const LayoutBlockBox* containing_box)
+ContainingBlock LayoutDetails::GetContainingBlock(ContainerBox* parent_container, const Style::Position position)
 {
-	RMLUI_ASSERT(containing_box);
-
-	Vector2f containing_block;
+	RMLUI_ASSERT(parent_container);
+	using Style::Position;
 
-	containing_block.x = containing_box->GetBox().GetSize(Box::CONTENT).x;
-	if (containing_box->GetElement() != nullptr)
-		containing_block.x -= containing_box->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
+	ContainerBox* container = parent_container;
+	Box::Area area = Box::CONTENT;
 
-	while ((containing_block.y = containing_box->GetBox().GetSize(Box::CONTENT).y) < 0)
+	// For absolutely positioned boxes we look for the first positioned ancestor. We deviate from the CSS specs by using
+	// the same rules for fixed boxes, as that is particularly helpful on handles and other widgets that should not
+	// scroll with the window. This is a common design pattern in target applications for this library, although this
+	// behavior may be reconsidered in the future.
+	if (position == Position::Absolute || position == Position::Fixed)
 	{
-		containing_box = containing_box->GetParent();
-		if (!containing_box)
-		{
-			RMLUI_ERROR;
-			containing_block.y = 0;
-			break;
-		}
+		area = Box::PADDING;
+
+		auto EstablishesAbsoluteContainingBlock = [](ContainerBox* container) -> bool {
+			return container->GetPositionProperty() != Position::Static || container->HasLocalTransformOrPerspective();
+		};
+		while (container && container->GetParent() && !EstablishesAbsoluteContainingBlock(container))
+			container = container->GetParent();
 	}
 
-	if (containing_box)
+	const Box* box = container->GetIfBox();
+	if (!box)
 	{
-	    if (Element* element = containing_box->GetElement())
-	        containing_block.y -= element->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL);
+		RMLUI_ERROR;
+		return {container, {}};
 	}
 
-	containing_block.x = Math::Max(0.0f, containing_block.x);
-	containing_block.y = Math::Max(0.0f, containing_block.y);
+	Vector2f containing_block = box->GetSize(area);
 
-	return containing_block;
-}
+	if (position == Position::Static || position == Position::Relative)
+	{
+		// For static elements we subtract the scrollbar size so that elements normally don't overlap their parent's
+		// scrollbars. In CSS, this would also be done for absolutely positioned elements, we might want to copy that
+		// behavior in the future. If so, we would also need to change the element offset behavior, and ideally also
+		// make positioned boxes contribute to the scrollable area.
+		if (Element* element = container->GetElement())
+		{
+			ElementScroll* element_scroll = element->GetElementScroll();
+			if (containing_block.x >= 0.f)
+				containing_block.x = Math::Max(containing_block.x - element_scroll->GetScrollbarSize(ElementScroll::VERTICAL), 0.f);
+			if (containing_block.y >= 0.f)
+				containing_block.y = Math::Max(containing_block.y - element_scroll->GetScrollbarSize(ElementScroll::HORIZONTAL), 0.f);
+		}
+	}
 
+	return {container, containing_block};
+}
 
 void LayoutDetails::BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f max_size, Vector2f containing_block, Element* element,
-	BoxContext box_context, bool replaced_element, float override_shrink_to_fit_width)
+	BuildBoxMode box_context, bool replaced_element)
 {
 	const ComputedValues& computed = element->GetComputedValues();
 
-	if (box_context == BoxContext::Inline || box_context == BoxContext::FlexOrTable)
+	if (box_context == BuildBoxMode::Inline || box_context == BuildBoxMode::UnalignedBlock)
 	{
 		// 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.
@@ -235,7 +239,7 @@ void LayoutDetails::BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f
 	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);
+		BuildBoxWidth(box, computed, min_size.x, max_size.x, containing_block, element, replaced_element);
 		BuildBoxHeight(box, computed, min_size.y, max_size.y, containing_block.y);
 	}
 }
@@ -244,32 +248,35 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b
 {
 	RMLUI_ASSERT(element);
 
+	// @performance Can we lay out the elements directly using a fit-content size mode, instead of fetching the
+	// shrink-to-fit width first? Use a non-definite placeholder for the box content width, and available width as a
+	// maximum constraint.
 	Box box;
 	float min_height, max_height;
-	LayoutDetails::BuildBox(box, containing_block, element, BoxContext::Block, containing_block.x);
+	LayoutDetails::BuildBox(box, containing_block, element, BuildBoxMode::UnalignedBlock);
 	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.
-	LayoutBlockBox containing_block_box(nullptr, nullptr, Box(containing_block), 0.0f, FLT_MAX);
+	// Currently we don't support shrink-to-fit width for flexboxes or tables. Just return a zero-sized width.
+	const Style::Display display = element->GetDisplay();
+	if (display == Style::Display::Flex || display == Style::Display::Table)
+		return 0.f;
 
-	// Here we fix the element's width to its containing block so that any content is wrapped at this width.
-	// We can consider to instead set this to infinity and clamp it to the available width later after formatting,
-	// but right now the formatting procedure doesn't work well with such numbers.
-	LayoutBlockBox* block_context_box = containing_block_box.AddBlockElement(element, box, min_height, max_height);
+	// Use a large size for the box content width, so that it is practically unconstrained. This makes the formatting
+	// procedure act as if under a maximum content constraint. Children with percentage sizing values may be scaled
+	// based on this width (such as 'width' or 'margin'), if so, the layout is considered undefined like in CSS 2.
+	const float max_content_constraint_width = containing_block.x + 1000.f;
+	box.SetContent({max_content_constraint_width, box.GetSize().y});
 
-	// @performance. Some formatting can be simplified, eg. absolute elements do not contribute to the shrink-to-fit width.
-	// Also, children of elements with a fixed width and height don't need to be formatted further.
-	for (int i = 0; i < element->GetNumChildren(); i++)
-	{
-		if (!LayoutEngine::FormatElement(block_context_box, element->GetChild(i)))
-			i = -1;
-	}
+	// First, format the element under the above generated box. Then we ask the resulting box for its shrink-to-fit
+	// width. For block containers, this is essentially its largest line or child box.
+	// @performance. Some formatting can be simplified, e.g. absolute elements do not contribute to the shrink-to-fit
+	// width. Also, children of elements with a fixed width and height don't need to be formatted further.
+	RootBox root(Math::Max(containing_block, Vector2f(0.f)));
+	UniquePtr<LayoutBox> layout_box = FormattingContext::FormatIndependent(&root, element, &box, FormattingContextType::Block);
 
-	// We only do layouting to get the fit-to-shrink width here, and for this purpose we may get
-	// away with not closing the boxes. This is avoided for performance reasons.
-	//block_context_box->Close();
+	const float available_width = Math::Max(0.f, containing_block.x - box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING));
 
-	return Math::Min(containing_block.x, block_context_box->GetShrinkToFitWidth());
+	return Math::Min(available_width, layout_box->GetShrinkToFitWidth());
 }
 
 ComputedAxisSize LayoutDetails::BuildComputedHorizontalSize(const ComputedValues& computed)
@@ -294,7 +301,19 @@ void LayoutDetails::GetEdgeSizes(float& margin_a, float& margin_b, float& paddin
 	padding_border_b = Math::Max(0.0f, ResolveValue(computed_size.padding_b, base_value)) + Math::Max(0.0f, computed_size.border_b);
 }
 
-Vector2f LayoutDetails::CalculateSizeForReplacedElement(const Vector2f specified_content_size, const Vector2f min_size, const Vector2f max_size, const Vector2f intrinsic_size, const float intrinsic_ratio)
+String LayoutDetails::GetDebugElementName(Element* element)
+{
+	if (!element)
+		return "nullptr";
+	if (!element->GetId().empty())
+		return '#' + element->GetId();
+	if (auto element_text = rmlui_dynamic_cast<ElementText*>(element))
+		return '\"' + StringUtilities::StripWhitespace(element_text->GetText()).substr(0, 20) + '\"';
+	return element->GetAddress(false, false);
+}
+
+Vector2f LayoutDetails::CalculateSizeForReplacedElement(const Vector2f specified_content_size, const Vector2f min_size, const Vector2f max_size,
+	const Vector2f intrinsic_size, const float intrinsic_ratio)
 {
 	// 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.
@@ -382,7 +401,8 @@ Vector2f LayoutDetails::CalculateSizeForReplacedElement(const Vector2f specified
 }
 
 // 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)
+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;
 
@@ -421,34 +441,32 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 					(computed.left().type == Style::Left::Auto || computed.right().type == Style::Right::Auto)) ||
 				(computed.display() == Style::Display::InlineBlock));
 
-		float left = 0.0f, right = 0.0f;
-		// If we are dealing with an absolutely positioned element we need to
-		// consider if the left and right properties are set, since the width can be affected.
-		if (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed)
+		if (!shrink_to_fit)
 		{
-			if (computed.left().type != Style::Left::Auto)
-				left = ResolveValue(computed.left(), containing_block.x);
-			if (computed.right().type != Style::Right::Auto)
-				right = ResolveValue(computed.right(), containing_block.x);
-		}
+			float left = 0.0f, right = 0.0f;
+			// If we are dealing with an absolutely positioned element we need to
+			// consider if the left and right properties are set, since the width can be affected.
+			if (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed)
+			{
+				if (computed.left().type != Style::Left::Auto)
+					left = ResolveValue(computed.left(), containing_block.x);
+				if (computed.right().type != Style::Right::Auto)
+					right = ResolveValue(computed.right(), containing_block.x);
+			}
 
-		if (shrink_to_fit && override_shrink_to_fit_width < 0)
-		{
-			content_area.x = GetShrinkToFitWidth(element, containing_block);
-			override_shrink_to_fit_width = content_area.x;
+			// We resolve any auto margins to 0 and the width is set to whatever is left of the containing block.
+			content_area.x = containing_block.x -
+				(left + box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) + box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT) + right);
+			content_area.x = Math::Max(0.0f, content_area.x);
 		}
-		else if (shrink_to_fit)
+		else if (override_shrink_to_fit_width >= 0)
 		{
 			content_area.x = override_shrink_to_fit_width;
 		}
 		else
 		{
-			// We resolve any auto margins to 0 and the width is set to whatever is left of the containing block.
-			content_area.x = containing_block.x - (left +
-				box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) +
-				box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT) +
-				right);
-			content_area.x = Math::Max(0.0f, content_area.x);
+			content_area.x = GetShrinkToFitWidth(element, containing_block);
+			override_shrink_to_fit_width = content_area.x;
 		}
 	}
 	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
@@ -471,7 +489,7 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 		box.SetContent(content_area);
 
 		if (num_auto_margins > 0)
-			BuildBoxWidth(box, computed, min_width, max_width, containing_block, element, replaced_element, override_shrink_to_fit_width);
+			BuildBoxWidth(box, computed, min_width, max_width, containing_block, element, replaced_element, clamped_width);
 	}
 	else
 		box.SetContent(content_area);
@@ -524,10 +542,8 @@ void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, flo
 				bottom = ResolveValue(computed.bottom(), containing_block_height);
 
 				// The height gets resolved to whatever is left of the containing block
-				content_area.y = containing_block_height - (top +
-				                                            box.GetCumulativeEdge(Box::CONTENT, Box::TOP) +
-				                                            box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM) +
-				                                            bottom);
+				content_area.y = containing_block_height -
+					(top + box.GetCumulativeEdge(Box::CONTENT, Box::TOP) + box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM) + bottom);
 				content_area.y = Math::Max(0.0f, content_area.y);
 			}
 		}

+ 28 - 31
Source/Core/LayoutDetails.h → Source/Core/Layout/LayoutDetails.h

@@ -26,16 +26,17 @@
  *
  */
 
-#ifndef RMLUI_CORE_LAYOUTDETAILS_H
-#define RMLUI_CORE_LAYOUTDETAILS_H
+#ifndef RMLUI_CORE_LAYOUT_LAYOUTDETAILS_H
+#define RMLUI_CORE_LAYOUT_LAYOUTDETAILS_H
 
-#include "../../Include/RmlUi/Core/StyleTypes.h"
-#include "../../Include/RmlUi/Core/Types.h"
+#include "../../../Include/RmlUi/Core/StyleTypes.h"
+#include "../../../Include/RmlUi/Core/Types.h"
 
 namespace Rml {
 
 class Box;
-class LayoutBlockBox;
+class BlockContainer;
+class ContainerBox;
 
 /**
     ComputedAxisSize is an abstraction of an element's computed size properties along a single axis, either horizontally or vertically,
@@ -50,7 +51,16 @@ struct ComputedAxisSize {
 	Style::BoxSizing box_sizing;
 };
 
-enum class BoxContext { Block, Inline, FlexOrTable };
+struct ContainingBlock {
+	ContainerBox* container;
+	Vector2f size;
+};
+
+enum class BuildBoxMode {
+	Block,          // Sets edges and size if available, auto width can result in shrink-to-fit width, auto margins are used for alignment.
+	Inline,         // Sets edges, ignores width, height, and auto margins.
+	UnalignedBlock, // Like block, but auto width returns -1, and auto margins are resolved to zero.
+};
 
 /**
     Layout functions for sizing elements.
@@ -59,26 +69,12 @@ enum class BoxContext { Block, Inline, FlexOrTable };
  */
 class LayoutDetails {
 public:
-	/// Generates the box for an element.
+	/// Generates the box dimensions for an element.
 	/// @param[out] box The box to be built.
 	/// @param[in] containing_block The dimensions of the content area of the block containing the element.
 	/// @param[in] element The element to build the box for.
 	/// @param[in] box_context The formatting context in which the box is generated.
-	/// @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, Vector2f containing_block, Element* element, BoxContext box_context = BoxContext::Block,
-		float override_shrink_to_fit_width = -1);
-	/// Generates the box for an element placed in a block box.
-	/// @param[out] box The box to be built.
-	/// @param[out] min_height The minimum height of the element's box.
-	/// @param[out] max_height The maximum height of the element's box.
-	/// @param[in] containing_box The block box containing the element.
-	/// @param[in] element The element to build the box for.
-	/// @param[in] box_context The formatting context in which the box is generated.
-	/// @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,
-		BoxContext box_context = BoxContext::Block, float override_shrink_to_fit_width = -1);
+	static void BuildBox(Box& box, Vector2f containing_block, Element* element, BuildBoxMode box_context = BuildBoxMode::Block);
 
 	// 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);
@@ -91,25 +87,24 @@ public:
 	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.
-	/// @param[in] containing_box The leaf box.
-	/// @return The dimensions of the content area, using the latest fixed dimensions for width and height in the hierarchy.
-	static Vector2f GetContainingBlock(const LayoutBlockBox* containing_box);
+	/// Returns the containing block for a box.
+	/// @param[in] parent_container The parent container of the current box.
+	/// @param[in] child_position The position property of the current box.
+	/// @return The containing block box and size, possibly indefinite along one or both axes.
+	static ContainingBlock GetContainingBlock(ContainerBox* parent_container, Style::Position position);
 
 	/// 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,out] box The box to generate. The padding and borders, in addition to any definite content area, must be set on the box already.
+	/// Auto width and height are specified by negative content size.
 	/// @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] box_context The formatting context in which the box is generated.
 	/// @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,
-		BoxContext box_context, bool replaced_element, float override_shrink_to_fit_width = -1);
+		BuildBoxMode box_context, bool replaced_element);
 
 	/// Formats the element and returns the width of its contents.
 	static float GetShrinkToFitWidth(Element* element, Vector2f containing_block);
@@ -122,6 +117,8 @@ public:
 	static void GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b,
 		const ComputedAxisSize& computed_size, float base_value);
 
+	static String GetDebugElementName(Element* element);
+
 private:
 	/// 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,

+ 50 - 0
Source/Core/Layout/LayoutEngine.cpp

@@ -0,0 +1,50 @@
+/*
+ * 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 "LayoutEngine.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/Log.h"
+#include "ContainerBox.h"
+#include "FormattingContext.h"
+
+namespace Rml {
+
+void LayoutEngine::FormatElement(Element* element, Vector2f containing_block)
+{
+	RMLUI_ASSERT(element && containing_block.x >= 0 && containing_block.y >= 0);
+
+	RootBox root(containing_block);
+
+	auto layout_box = FormattingContext::FormatIndependent(&root, element, nullptr, FormattingContextType::Block);
+	if (!layout_box)
+	{
+		Log::Message(Log::LT_ERROR, "Error while formatting element: %s", element->GetAddress().c_str());
+	}
+}
+
+} // namespace Rml

+ 53 - 0
Source/Core/Layout/LayoutEngine.h

@@ -0,0 +1,53 @@
+/*
+ * 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_LAYOUT_LAYOUTENGINE_H
+#define RMLUI_CORE_LAYOUT_LAYOUTENGINE_H
+
+#include "../../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+
+/**
+    @author Michael R. P. Ragazzon
+
+    Based on original work by: Robert Curry and Peter Curry
+
+    See the CSS glossary for terms used in the layout engine:
+    https://www.w3.org/TR/css-display-3/#glossary
+ */
+class LayoutEngine {
+public:
+	/// Formats the contents for a root-level element, usually a document, or a replaced element with custom formatting.
+	/// @param[in] element The element to lay out.
+	/// @param[in] containing_block The size of the containing block.
+	static void FormatElement(Element* element, Vector2f containing_block);
+};
+
+} // namespace Rml
+#endif

+ 89 - 0
Source/Core/Layout/LayoutPools.cpp

@@ -0,0 +1,89 @@
+/*
+ * 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 "LayoutPools.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../Pool.h"
+#include "BlockContainer.h"
+#include "FloatedBoxSpace.h"
+#include "FormattingContext.h"
+#include "InlineBox.h"
+#include "InlineContainer.h"
+#include "LineBox.h"
+#include <algorithm>
+#include <cstddef>
+
+namespace Rml {
+
+template <size_t Size>
+struct LayoutChunk {
+	alignas(std::max_align_t) byte buffer[Size];
+};
+
+static constexpr std::size_t ChunkSizeBig = std::max({sizeof(BlockContainer)});
+static constexpr std::size_t ChunkSizeMedium =
+	std::max({sizeof(InlineContainer), sizeof(InlineBox), sizeof(RootBox), sizeof(FlexContainer), sizeof(TableWrapper)});
+static constexpr std::size_t ChunkSizeSmall =
+	std::max({sizeof(InlineLevelBox_Text), sizeof(InlineLevelBox_Atomic), sizeof(LineBox), sizeof(FloatedBoxSpace)});
+
+static Pool<LayoutChunk<ChunkSizeBig>> layout_chunk_pool_big(50, true);
+static Pool<LayoutChunk<ChunkSizeMedium>> layout_chunk_pool_medium(50, true);
+static Pool<LayoutChunk<ChunkSizeSmall>> layout_chunk_pool_small(50, true);
+
+void* LayoutPools::AllocateLayoutChunk(size_t size)
+{
+	static_assert(ChunkSizeBig > ChunkSizeMedium && ChunkSizeMedium > ChunkSizeSmall, "The following assumes a strict ordering of the chunk sizes.");
+
+	// Note: If any change is made here, make sure a corresponding change is applied to the deallocation procedure below.
+	if (size <= ChunkSizeSmall)
+		return layout_chunk_pool_small.AllocateAndConstruct();
+	else if (size <= ChunkSizeMedium)
+		return layout_chunk_pool_medium.AllocateAndConstruct();
+	else if (size <= ChunkSizeBig)
+		return layout_chunk_pool_big.AllocateAndConstruct();
+
+	RMLUI_ERROR;
+	return nullptr;
+}
+
+void LayoutPools::DeallocateLayoutChunk(void* chunk, size_t size)
+{
+	// Note: If any change is made here, make sure a corresponding change is applied to the allocation procedure above.
+	if (size <= ChunkSizeSmall)
+		layout_chunk_pool_small.DestroyAndDeallocate((LayoutChunk<ChunkSizeSmall>*)chunk);
+	else if (size <= ChunkSizeMedium)
+		layout_chunk_pool_medium.DestroyAndDeallocate((LayoutChunk<ChunkSizeMedium>*)chunk);
+	else if (size <= ChunkSizeBig)
+		layout_chunk_pool_big.DestroyAndDeallocate((LayoutChunk<ChunkSizeBig>*)chunk);
+	else
+	{
+		RMLUI_ERROR;
+	}
+}
+
+} // namespace Rml

+ 44 - 0
Source/Core/Layout/LayoutPools.h

@@ -0,0 +1,44 @@
+/*
+ * 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_LAYOUT_LAYOUTPOOLS_H
+#define RMLUI_CORE_LAYOUT_LAYOUTPOOLS_H
+
+#include "../../../Include/RmlUi/Core/Types.h"
+
+namespace Rml {
+
+namespace LayoutPools {
+	
+	void* AllocateLayoutChunk(size_t size);
+	void DeallocateLayoutChunk(void* chunk, size_t size);
+
+} // namespace LayoutPools
+
+} // namespace Rml
+#endif

+ 429 - 0
Source/Core/Layout/LineBox.cpp

@@ -0,0 +1,429 @@
+/*
+ * 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 "LineBox.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/StyleTypes.h"
+#include "InlineBox.h"
+#include "InlineLevelBox.h"
+#include "LayoutPools.h"
+#include <float.h>
+#include <numeric>
+
+namespace Rml {
+
+LineBox::~LineBox() {}
+
+void LineBox::SetLineBox(Vector2f _line_position, float _line_width, float _line_minimum_height)
+{
+	line_position = _line_position;
+	line_width = _line_width;
+	line_minimum_height = _line_minimum_height;
+}
+
+bool LineBox::AddBox(InlineLevelBox* box, InlineLayoutMode layout_mode, LayoutOverflowHandle& inout_overflow_handle)
+{
+	RMLUI_ASSERT(!is_closed);
+	const bool first_box = !HasContent();
+
+	// Find the spacing this element must leave on its right side, to account for its parent inline boxes to be closed later.
+	float open_spacing_right = 0.f;
+	ForAllOpenFragments([&](const Fragment& fragment) { open_spacing_right += fragment.box->GetSpacingRight(); });
+
+	const float box_placement_cursor = box_cursor + open_spacing_left;
+
+	float available_width = FLT_MAX;
+	if (layout_mode != InlineLayoutMode::Nowrap)
+	{
+		available_width = Math::RoundUpFloat(line_width - box_placement_cursor);
+		if (available_width < 0.f)
+		{
+			if (layout_mode == InlineLayoutMode::WrapAny)
+				return true;
+			else
+				available_width = 0.f;
+		}
+	}
+
+	FragmentConstructor constructor = box->CreateFragment(layout_mode, available_width, open_spacing_right, first_box, inout_overflow_handle);
+
+	if (constructor.type == FragmentType::Invalid)
+	{
+		// Could not place fragment on this line, try again on a new line.
+		RMLUI_ASSERT(layout_mode == InlineLayoutMode::WrapAny);
+		return true;
+	}
+
+	const FragmentIndex fragment_index = (FragmentIndex)fragments.size();
+
+	fragments.push_back(Fragment{box, constructor, box->GetVerticalAlign(), box_placement_cursor, open_fragments_leaf});
+	fragments.back().aligned_subtree_root = DetermineAlignedSubtreeRoot(fragment_index);
+
+	inout_overflow_handle = {};
+	bool continue_on_new_line = false;
+
+	switch (constructor.type)
+	{
+	case FragmentType::InlineBox:
+	{
+		RMLUI_ASSERT(constructor.layout_width < 0.f);
+		RMLUI_ASSERT(rmlui_dynamic_cast<InlineBox*>(box));
+
+		open_fragments_leaf = fragment_index;
+		open_spacing_left += box->GetSpacingLeft();
+	}
+	break;
+	case FragmentType::SizedBox:
+	case FragmentType::TextRun:
+	{
+		RMLUI_ASSERT(constructor.layout_width >= 0.f);
+
+		box_cursor = box_placement_cursor + constructor.layout_width;
+		open_spacing_left = 0.f;
+
+		if (constructor.overflow_handle)
+		{
+			continue_on_new_line = true;
+			inout_overflow_handle = constructor.overflow_handle;
+		}
+
+		// Mark open fragments as having content so we later know whether we should split or move them in case of overflow.
+		ForAllOpenFragments([&](Fragment& fragment) { fragment.has_content = true; });
+		has_content = true;
+	}
+	break;
+	case FragmentType::Invalid:
+		RMLUI_ERROR; // Handled above;
+		break;
+	}
+
+	return continue_on_new_line;
+}
+
+void LineBox::CloseFragment(Fragment& open_fragment, float right_inner_edge_position)
+{
+	RMLUI_ASSERT(open_fragment.type == FragmentType::InlineBox);
+
+	open_fragment.children_end_index = (int)fragments.size();
+	const float spacing_left = (open_fragment.split_left ? 0.f : open_fragment.box->GetSpacingLeft());
+	open_fragment.layout_width = Math::Max(right_inner_edge_position - open_fragment.position.x - spacing_left, 0.f);
+}
+
+void LineBox::CloseInlineBox(InlineBox* inline_box)
+{
+	if (open_fragments_leaf == RootFragmentIndex || fragments[open_fragments_leaf].box != inline_box)
+	{
+		RMLUI_ERRORMSG("Inline box open/close mismatch.");
+		return;
+	}
+
+	box_cursor += open_spacing_left;
+	open_spacing_left = 0.f;
+
+	Fragment& fragment = fragments[open_fragments_leaf];
+	CloseFragment(fragment, box_cursor);
+	box_cursor += fragment.box->GetSpacingRight();
+
+	open_fragments_leaf = fragment.parent;
+}
+
+UniquePtr<LineBox> LineBox::SplitLine(bool split_all_open_boxes)
+{
+	if (open_fragments_leaf == RootFragmentIndex)
+		return nullptr;
+
+	int num_open_fragments = 0;
+	ForAllOpenFragments([&](const Fragment& /*fragment*/) { num_open_fragments += 1; });
+
+	// Move the box cursor to account for any newly opened inline boxes to be closed.
+	box_cursor += open_spacing_left;
+	open_spacing_left = 0.f;
+
+	// Make a new line with the open fragments.
+	auto new_line = MakeUnique<LineBox>();
+	new_line->fragments.resize(num_open_fragments);
+
+	// Copy all open fragments to the next line. Do it in reverse order of iteration, since we iterate from back to front.
+	FragmentIndex new_index = num_open_fragments;
+	ForAllOpenFragments([&](Fragment& old_fragment) {
+		new_index -= 1;
+		RMLUI_ASSERT((size_t)new_index < new_line->fragments.size() && old_fragment.children_end_index == 0);
+
+		// Copy the old fragment.
+		Fragment& new_fragment = new_line->fragments[new_index];
+		new_fragment = old_fragment;
+		new_fragment.position.x = 0.f;
+		new_fragment.parent = new_index - 1;
+		new_fragment.aligned_subtree_root = new_line->DetermineAlignedSubtreeRoot(new_index);
+
+		// Only fragments with content placed on the previous line is split, otherwise the fragment is moved down.
+		if (new_fragment.has_content || split_all_open_boxes)
+		{
+			new_fragment.split_left = true;
+			new_fragment.has_content = false;
+
+			CloseFragment(old_fragment, box_cursor);
+			old_fragment.split_right = true;
+		}
+		else
+		{
+			const float spacing_left = (new_fragment.split_left ? 0.f : new_fragment.box->GetSpacingLeft());
+			new_line->open_spacing_left += spacing_left;
+
+			// The old fragment is not closed here, which ensures that it will not be placed/submitted on the previous
+			// line. Backtrack the box cursor since this fragment is moved down to the next line.
+			box_cursor -= spacing_left;
+		}
+	});
+
+	new_line->open_fragments_leaf = (int)new_line->fragments.size() - 1;
+	open_fragments_leaf = RootFragmentIndex;
+
+#ifdef RMLUI_DEBUG
+	// Verify integrity of the fragment tree after split.
+	for (int i = 0; i < (int)new_line->fragments.size(); i++)
+	{
+		const Fragment& fragment = new_line->fragments[i];
+		RMLUI_ASSERT(fragment.type == FragmentType::InlineBox);
+		RMLUI_ASSERT(fragment.parent < i);
+		RMLUI_ASSERT(fragment.parent == i - 1);
+		RMLUI_ASSERT(fragment.parent == RootFragmentIndex || new_line->fragments[fragment.parent].type == FragmentType::InlineBox);
+		RMLUI_ASSERT(
+			fragment.aligned_subtree_root == RootFragmentIndex || new_line->IsAlignedSubtreeRoot(new_line->fragments[fragment.aligned_subtree_root]));
+		RMLUI_ASSERT(fragment.children_end_index == 0);
+	}
+#endif
+
+	return new_line;
+}
+
+UniquePtr<LineBox> LineBox::DetermineVerticalPositioning(const InlineBoxRoot* root_inline_box, bool split_all_open_boxes, float& out_height_of_line)
+{
+	RMLUI_ASSERT(!is_closed && !is_vertically_positioned);
+
+	UniquePtr<LineBox> new_line_box = SplitLine(split_all_open_boxes);
+
+	RMLUI_ASSERT(open_fragments_leaf == RootFragmentIndex); // Ensure all open fragments are either closed or split.
+
+	// Vertical alignment and sizing.
+	//
+	// Aligned subtree CSS definition: "The aligned subtree of an inline element contains that element and the aligned
+	// subtrees of all children inline elements whose computed vertical-align value is not 'top' or 'bottom'."
+	//
+	// We have already determined each box's offset relative to its parent baseline, and its layout size above and below
+	// its baseline. Now, for each aligned subtree, place all fragments belonging to that subtree relative to the
+	// subtree root baseline. Simultaneously, consider each fragment and keep track of the maximum height above root
+	// baseline (max_ascent) and maximum depth below root baseline (max_descent). Their sum is the height of that
+	// aligned subtree.
+	//
+	// Next, treat the root inline box like just another aligned subtree. Then the line box height is first determined
+	// by the height of that subtree. Other aligned subtrees might push out that size to make them fit. After the line
+	// box size is determined, position each aligned subtree according to its vertical-align, and then position each
+	// fragment relative to the aligned subtree they belong to.
+	float max_ascent = root_inline_box->GetHeightAboveBaseline();
+	float max_descent = root_inline_box->GetDepthBelowBaseline();
+
+	{
+		const int subtree_root_index = RootFragmentIndex;
+		const int children_end_index = (int)fragments.size();
+		VerticallyAlignSubtree(subtree_root_index, children_end_index, max_ascent, max_descent);
+	}
+
+	// Find all the aligned subtrees, and vertically align each of them independently.
+	for (int i = 0; i < (int)fragments.size(); i++)
+	{
+		Fragment& fragment = fragments[i];
+		if (IsAlignedSubtreeRoot(fragment))
+		{
+			fragment.max_ascent = fragment.box->GetHeightAboveBaseline();
+			fragment.max_descent = fragment.box->GetDepthBelowBaseline();
+
+			if (fragment.type == FragmentType::InlineBox)
+			{
+				const int subtree_root_index = i;
+				VerticallyAlignSubtree(subtree_root_index, fragment.children_end_index, fragment.max_ascent, fragment.max_descent);
+			}
+
+			// Increase the line box size to fit all line-relative aligned fragments.
+			switch (fragment.vertical_align)
+			{
+			case VerticalAlignType::Top: max_descent = Math::Max(max_descent, fragment.max_ascent + fragment.max_descent - max_ascent); break;
+			case VerticalAlignType::Bottom: max_ascent = Math::Max(max_ascent, fragment.max_ascent + fragment.max_descent - max_descent); break;
+			default: RMLUI_ERROR; break;
+			}
+		}
+	}
+
+	// Size the line.
+	out_height_of_line = max_ascent + max_descent;
+	total_height_above_baseline = max_ascent;
+
+	// Now that the line is sized we can set the vertical position of the fragments.
+	for (Fragment& fragment : fragments)
+	{
+		switch (fragment.vertical_align)
+		{
+		case VerticalAlignType::Top: fragment.position.y = fragment.max_ascent; break;
+		case VerticalAlignType::Bottom: fragment.position.y = out_height_of_line - fragment.max_descent; break;
+		default:
+		{
+			RMLUI_ASSERT(!IsAlignedSubtreeRoot(fragment));
+			const float aligned_subtree_baseline =
+				(fragment.aligned_subtree_root < 0 ? max_ascent : fragments[fragment.aligned_subtree_root].position.y);
+			fragment.position.y = aligned_subtree_baseline + fragment.baseline_offset;
+		}
+		break;
+		}
+	}
+
+	is_vertically_positioned = true;
+
+	return new_line_box;
+}
+
+void LineBox::Close(Element* offset_parent, Vector2f offset_parent_position, Style::TextAlign text_align)
+{
+	RMLUI_ASSERT(is_vertically_positioned && !is_closed);
+
+	// Horizontal alignment using available space on our line.
+	if (box_cursor < line_width)
+	{
+		switch (text_align)
+		{
+		case Style::TextAlign::Center: offset_horizontal_alignment = (line_width - box_cursor) * 0.5f; break;
+		case Style::TextAlign::Right: offset_horizontal_alignment = (line_width - box_cursor); break;
+		case Style::TextAlign::Left:    // Already left-aligned.
+		case Style::TextAlign::Justify: // Justification occurs at the text level.
+			break;
+		}
+	}
+
+	// Position and size all inline-level boxes, place geometry boxes.
+	for (const Fragment& fragment : fragments)
+	{
+		// Skip inline boxes which have not been closed (moved down to next line).
+		if (fragment.type == FragmentType::InlineBox && fragment.children_end_index == 0)
+			continue;
+
+		RMLUI_ASSERT(fragment.layout_width >= 0.f);
+
+		const PlacedFragment placed_fragment = {
+			offset_parent,
+			fragment.fragment_handle,
+			line_position - offset_parent_position + fragment.position + Vector2f(offset_horizontal_alignment, 0.f),
+			fragment.layout_width,
+			fragment.split_left,
+			fragment.split_right,
+		};
+		fragment.box->Submit(placed_fragment);
+	}
+
+	is_closed = true;
+}
+
+void LineBox::VerticallyAlignSubtree(const int subtree_root_index, const int children_end_index, float& max_ascent, float& max_descent)
+{
+	const int children_begin_index = subtree_root_index + 1;
+
+	// Iterate all descendant fragments which belong to our aligned subtree.
+	for (int i = children_begin_index; i < children_end_index; i++)
+	{
+		Fragment& fragment = fragments[i];
+		if (fragment.aligned_subtree_root != subtree_root_index)
+			continue;
+
+		// Position the baseline of fragments relative to their subtree root.
+		const float parent_absolute_baseline = (fragment.parent < 0 ? 0.f : fragments[fragment.parent].baseline_offset);
+		fragment.baseline_offset = parent_absolute_baseline + fragment.box->GetVerticalOffsetFromParent();
+
+		// Expand this aligned subtree's height based on the height contributions of its descendants.
+		if (fragment.type != FragmentType::TextRun)
+		{
+			max_ascent = Math::Max(max_ascent, fragment.box->GetHeightAboveBaseline() - fragment.baseline_offset);
+			max_descent = Math::Max(max_descent, fragment.box->GetDepthBelowBaseline() + fragment.baseline_offset);
+		}
+	}
+}
+
+InlineBox* LineBox::GetOpenInlineBox()
+{
+	if (open_fragments_leaf == RootFragmentIndex)
+		return nullptr;
+
+	return static_cast<InlineBox*>(fragments[open_fragments_leaf].box);
+}
+
+bool LineBox::CanCollapseLine() const
+{
+	// Roughly, collapse lines with only empty text fragments or inline boxes not taking up any width or spacing.
+	for (const Fragment& fragment : fragments)
+	{
+		if (fragment.layout_width > 0.f)
+			return false;
+		else if (fragment.type == FragmentType::SizedBox)
+			return false;
+		else if (fragment.type == FragmentType::InlineBox && fragment.children_end_index > 0)
+		{
+			const bool any_spacing_left = (!fragment.split_left && fragment.box->GetSpacingLeft() != 0.f);
+			const bool any_spacing_right = (!fragment.split_right && fragment.box->GetSpacingRight() != 0.f);
+			if (any_spacing_left || any_spacing_right)
+				return false;
+		}
+	}
+	return true;
+}
+
+float LineBox::GetExtentRight() const
+{
+	RMLUI_ASSERT(is_closed);
+	return box_cursor + offset_horizontal_alignment;
+}
+
+float LineBox::GetBaseline() const
+{
+	RMLUI_ASSERT(is_closed);
+	return total_height_above_baseline;
+}
+
+String LineBox::DebugDumpTree(int depth) const
+{
+	const String value = String(depth * 2, ' ') + "LineBox (" + ToString(fragments.size()) + " fragment" + (fragments.size() == 1 ? "" : "s") + ")\n";
+	return value;
+}
+
+void* LineBox::operator new(size_t size)
+{
+	return LayoutPools::AllocateLayoutChunk(size);
+}
+
+void LineBox::operator delete(void* chunk, size_t size)
+{
+	LayoutPools::DeallocateLayoutChunk(chunk, size);
+}
+
+} // namespace Rml

+ 214 - 0
Source/Core/Layout/LineBox.h

@@ -0,0 +1,214 @@
+/*
+ * 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_LAYOUT_LINEBOX_H
+#define RMLUI_CORE_LAYOUT_LINEBOX_H
+
+#include "../../../Include/RmlUi/Core/StyleTypes.h"
+#include "InlineTypes.h"
+
+namespace Rml {
+
+class InlineBox;
+class InlineBoxRoot;
+class InlineLevelBox;
+
+/*
+    Horizontally places fragments generated from inline-level boxes.
+
+    Inline boxes can nest other inline-level boxes, thereby creating a tree of inline-level boxes. This tree structure
+    needs to be considered for the generated fragments as well, since their size and position depend on this tree
+    structure.
+
+    Note that a single inline-level box can generate multiple fragments, possibly placed in different line boxes.
+*/
+class LineBox final {
+public:
+	LineBox() = default;
+	~LineBox();
+
+	// Set the line box position and dimensions.
+	void SetLineBox(Vector2f line_position, float line_width, float line_minimum_height);
+
+	/// Generates a fragment from a box and places it on this line, if possible.
+	/// @param[in] box The inline-level box to be placed.
+	/// @param[in] layout_mode The inline layout mode, which affects the fragment generation.
+	/// @param[in,out] inout_overflow_handle A handle to resume fragment generation from a partially placed box.
+	/// @return True if the box should be placed again on a new line, either because the box could not fit or there is more content to be placed.
+	bool AddBox(InlineLevelBox* box, InlineLayoutMode layout_mode, LayoutOverflowHandle& inout_overflow_handle);
+
+	/// Closes the open inline box.
+	/// @param[in] inline_box The inline box to be closed. Should match the currently open box, strictly used for verification.
+	/// @note Only inline boxes need to be closed, not other inline-level boxes since they do not contain child boxes.
+	void CloseInlineBox(InlineBox* inline_box);
+
+	/// Vertically positions each fragment and sizes the line, after splitting any open inline boxes to be placed on a new line.
+	/// @param[in] root_inline_box The root inline box of our inline container.
+	/// @param[in] split_all_open_boxes Split all open inline boxes, even if they have no content.
+	/// @param[out] out_height_of_line Resulting height of line. This can be different from the element's computed line-height property.
+	/// @return The next line if any open fragments had to be split or wrapped down.
+	UniquePtr<LineBox> DetermineVerticalPositioning(const InlineBoxRoot* root_inline_box, bool split_all_open_boxes, float& out_height_of_line);
+
+	// Closes the line and submits all fragments. Thereby positioning, sizing, and placing their corresponding boxes.
+	// @note The line must have been vertically positioned before closing.
+	void Close(Element* offset_parent, Vector2f offset_parent_position, Style::TextAlign text_align);
+
+	float GetBoxCursor() const { return box_cursor; }
+	Vector2f GetPosition() const { return line_position; }
+	float GetLineWidth() const { return line_width; }
+	float GetLineMinimumHeight() const { return line_minimum_height; }
+
+	InlineBox* GetOpenInlineBox();
+
+	bool IsClosed() const { return is_closed; }
+	bool HasContent() const { return has_content; }
+
+	// Returns true if this line should be collapsed, such that it takes up no height in the inline container.
+	bool CanCollapseLine() const;
+
+	// Returns the width of the contents of the line, relative to the left edge of the line box. Includes spacing due to horizontal alignment.
+	// @note Only available after line has been closed.
+	float GetExtentRight() const;
+
+	// Returns the baseline position, relative to the top of the line box.
+	// @note Only available after line has been closed.
+	float GetBaseline() const;
+
+	String DebugDumpTree(int depth) const;
+
+	void* operator new(size_t size);
+	void operator delete(void* chunk, size_t size);
+
+private:
+	using FragmentIndex = int;
+	using VerticalAlignType = Style::VerticalAlign::Type;
+
+	static constexpr FragmentIndex RootFragmentIndex = -1;
+
+	struct Fragment {
+		Fragment() = default;
+		Fragment(InlineLevelBox* box, FragmentConstructor constructor, VerticalAlignType vertical_align, float position_x, FragmentIndex parent) :
+			box(box), type(constructor.type), fragment_handle(constructor.fragment_handle), vertical_align(vertical_align), position(position_x, 0.f),
+			layout_width(constructor.layout_width), parent(parent)
+		{}
+
+		InlineLevelBox* box = nullptr;
+		FragmentType type = FragmentType::Invalid;
+		LayoutFragmentHandle fragment_handle = {};
+		VerticalAlignType vertical_align = {};
+
+		// Layout state.
+		Vector2f position;        // Position relative to start of the line, disregarding floats, (x: outer-left edge, y: baseline).
+		float layout_width = 0.f; // Inner width for inline boxes, otherwise outer width.
+
+		// Vertical alignment state.
+		FragmentIndex parent = RootFragmentIndex;
+		FragmentIndex aligned_subtree_root = RootFragmentIndex; // Index of the aligned subtree the fragment belongs to.
+		float baseline_offset = 0.f;                            // Vertical offset from aligned subtree root baseline to our baseline.
+
+		// For inline boxes.
+		bool split_left = false;
+		bool split_right = false;
+		bool has_content = false;
+		FragmentIndex children_end_index = 0; // One-past-last-child of this box, as index into fragment list.
+
+		// For aligned subtree root.
+		float max_ascent = 0.f;
+		float max_descent = 0.f;
+	};
+	using FragmentList = Vector<Fragment>;
+
+	// Place an open fragment.
+	void CloseFragment(Fragment& open_fragment, float right_inner_edge_position);
+
+	// Splits the line, returning a new line if there are any open fragments.
+	UniquePtr<LineBox> SplitLine(bool split_all_open_boxes);
+
+	// Vertically align all descendants of the subtree. Returns the ascent of the top-most box, and descent of the bottom-most box.
+	void VerticallyAlignSubtree(int subtree_root_index, int children_end_index, float& max_ascent, float& max_descent);
+
+	// Returns true if the fragment establishes an aligned subtree.
+	static bool IsAlignedSubtreeRoot(const Fragment& fragment)
+	{
+		return (fragment.vertical_align == VerticalAlignType::Top || fragment.vertical_align == VerticalAlignType::Bottom);
+	}
+	// Returns the aligned subtree root for a given fragment, based on its ancestors.
+	FragmentIndex DetermineAlignedSubtreeRoot(FragmentIndex index) const
+	{
+		while (index != RootFragmentIndex)
+		{
+			const Fragment& fragment = fragments[index];
+			if (IsAlignedSubtreeRoot(fragment))
+				return index;
+			index = fragment.parent;
+		}
+		return index;
+	}
+
+	template <typename Func>
+	void ForAllOpenFragments(Func&& func)
+	{
+		FragmentIndex index = open_fragments_leaf;
+		while (index != RootFragmentIndex)
+		{
+			Fragment& fragment = fragments[index];
+			index = fragment.parent;
+			func(fragment);
+		}
+	}
+
+	// Position of the line, relative to our block formatting context root.
+	Vector2f line_position;
+	// Available space for the line. Based on our parent box content width, possibly shrinked due to floating boxes.
+	float line_width = 0.f;
+	// Lower-bound estimate for the line box height.
+	float line_minimum_height = 0.f;
+
+	// The horizontal cursor. This is the outer-right position of the last placed fragment.
+	float box_cursor = 0.f;
+	// The contribution of opened inline boxes to the placement of the next fragment, due to their left edges (margin-border-padding).
+	float open_spacing_left = 0.f;
+
+	// List of fragments in this line box.
+	FragmentList fragments;
+
+	// Index of the last open fragment. The list of open fragments is this leaf and all of its ancestors up to the root.
+	FragmentIndex open_fragments_leaf = RootFragmentIndex;
+
+	bool has_content = false;
+	bool is_vertically_positioned = false;
+	bool is_closed = false;
+
+	// Content offset due to space distribution from 'text-align'. Available after close.
+	float offset_horizontal_alignment = 0.f;
+	// The line box's height above baseline. Available after close.
+	float total_height_above_baseline = 0.f;
+};
+
+} // namespace Rml
+#endif

+ 125 - 109
Source/Core/LayoutTable.cpp → Source/Core/Layout/TableFormattingContext.cpp

@@ -15,7 +15,7 @@
  *
  * 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
@@ -26,93 +26,116 @@
  *
  */
 
-#include "LayoutTable.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/Element.h"
-#include "../../Include/RmlUi/Core/Types.h"
+#include "TableFormattingContext.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "ContainerBox.h"
 #include "LayoutDetails.h"
 #include "LayoutEngine.h"
-#include "LayoutTableDetails.h"
+#include "TableFormattingDetails.h"
 #include <algorithm>
 #include <numeric>
 
 namespace Rml {
 
-Vector2f LayoutTable::FormatTable(Box& box, Vector2f min_size, Vector2f max_size, Element* element_table)
+UniquePtr<LayoutBox> TableFormattingContext::Format(ContainerBox* parent_container, Element* element_table, const Box* override_initial_box)
 {
-	const ComputedValues& computed_table = element_table->GetComputedValues();
-
-	// Scrollbars are illegal in the table element.
-	if (!(computed_table.overflow_x() == Style::Overflow::Visible || computed_table.overflow_x() == Style::Overflow::Hidden) ||
-		!(computed_table.overflow_y() == Style::Overflow::Visible || computed_table.overflow_y() == Style::Overflow::Hidden))
+	auto table_wrapper_box = MakeUnique<TableWrapper>(element_table, parent_container);
+	if (table_wrapper_box->IsScrollContainer())
 	{
-		Log::Message(Log::LT_WARNING, "Table elements can only have 'overflow' property values of 'visible' or 'hidden'. Table will not be formatted: %s.", element_table->GetAddress().c_str());
-		return Vector2f(0);
+		Log::Message(Log::LT_WARNING, "Table elements can only have 'overflow' property values of 'visible'. Table will not be formatted: %s.",
+			element_table->GetAddress().c_str());
+		return table_wrapper_box;
 	}
 
-	const Vector2f box_content_size = box.GetSize();
-	const bool table_auto_height = (box_content_size.y < 0.0f);
+	const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element_table->GetPosition()).size;
+	RMLUI_ASSERT(containing_block.x >= 0.f);
+	const ComputedValues& computed_table = element_table->GetComputedValues();
+
+	// Build the initial box as specified by the table's style, as if it was a normal block element.
+	Box& box = table_wrapper_box->GetBox();
+	if (override_initial_box)
+		box = *override_initial_box;
+	else
+		LayoutDetails::BuildBox(box, containing_block, element_table, BuildBoxMode::Block);
 
-	Vector2f table_content_offset = box.GetPosition();
-	Vector2f table_initial_content_size = Vector2f(box_content_size.x, Math::Max(0.0f, box_content_size.y));
+	TableFormattingContext context;
+	context.element_table = element_table;
+	context.table_wrapper_box = table_wrapper_box.get();
 
-	Math::SnapToPixelGrid(table_content_offset, table_initial_content_size);
+	LayoutDetails::GetMinMaxWidth(context.table_min_size.x, context.table_max_size.x, computed_table, box, containing_block.x);
+	LayoutDetails::GetMinMaxHeight(context.table_min_size.y, context.table_max_size.y, computed_table, box, containing_block.y);
+
+	// Format the table, this may adjust the box content size.
+	const Vector2f initial_content_size = box.GetSize();
+	context.table_auto_height = (initial_content_size.y < 0.0f);
+
+	context.table_content_offset = box.GetPosition();
+	context.table_initial_content_size = Vector2f(initial_content_size.x, Math::Max(0.0f, initial_content_size.y));
+	Math::SnapToPixelGrid(context.table_content_offset, context.table_initial_content_size);
 
 	// When width or height is set, they act as minimum width or height, just as in CSS.
 	if (computed_table.width().type != Style::Width::Auto)
-		min_size.x = Math::Max(min_size.x, table_initial_content_size.x);
+		context.table_min_size.x = Math::Max(context.table_min_size.x, context.table_initial_content_size.x);
 	if (computed_table.height().type != Style::Height::Auto)
-		min_size.y = Math::Max(min_size.y, table_initial_content_size.y);
+		context.table_min_size.y = Math::Max(context.table_min_size.y, context.table_initial_content_size.y);
+
+	context.table_gap = Vector2f(ResolveValue(computed_table.column_gap(), context.table_initial_content_size.x),
+		ResolveValue(computed_table.row_gap(), context.table_initial_content_size.y));
 
-	const Vector2f table_gap = Vector2f(
-		ResolveValue(computed_table.column_gap(), table_initial_content_size.x), 
-		ResolveValue(computed_table.row_gap(), table_initial_content_size.y)
-	);
+	context.grid.Build(element_table, *table_wrapper_box);
 
-	TableGrid grid;
-	grid.Build(element_table);
+	Vector2f table_content_size, table_overflow_size;
 
-	// Construct the layout object and format the table.
-	LayoutTable layout_table(element_table, grid, table_gap, table_content_offset, table_initial_content_size, table_auto_height, min_size, max_size);
+	// Format the table and its children.
+	context.FormatTable(table_content_size, table_overflow_size);
 
-	layout_table.FormatTable();
+	RMLUI_ASSERT(table_content_size.y >= 0);
 
 	// Update the box size based on the new table size.
-	box.SetContent(layout_table.table_resulting_content_size);
+	box.SetContent(table_content_size);
 
-	return layout_table.table_content_overflow_size;
-}
+	if (table_content_size != initial_content_size)
+	{
+		// Perform this step to re-evaluate any auto margins.
+		LayoutDetails::BuildBoxSizeAndMargins(box, context.table_min_size, context.table_max_size, containing_block, element_table,
+			BuildBoxMode::Block, true);
+	}
 
+	table_wrapper_box->Close(table_overflow_size, box);
 
-LayoutTable::LayoutTable(Element* element_table, const TableGrid& grid, Vector2f table_gap, Vector2f table_content_offset,
-	Vector2f table_initial_content_size, bool table_auto_height, Vector2f table_min_size, Vector2f table_max_size)
-	: element_table(element_table), grid(grid), table_auto_height(table_auto_height), table_min_size(table_min_size), table_max_size(table_max_size),
-	table_gap(table_gap), table_content_offset(table_content_offset), table_initial_content_size(table_initial_content_size)
-{
-	table_resulting_content_size = table_initial_content_size;
+	return table_wrapper_box;
 }
 
-void LayoutTable::FormatTable()
+void TableFormattingContext::FormatTable(Vector2f& table_content_size, Vector2f& table_overflow_size) const
 {
-	DetermineColumnWidths();
+	// Defines the boxes for all columns in this table, one entry per table column (spanning columns will add multiple entries).
+	TrackBoxList columns;
+	// Defines the boxes for all rows in this table, one entry per table row.
+	TrackBoxList rows;
+	// Defines the boxes for all cells in this table.
+	BoxList cells;
+
+	DetermineColumnWidths(columns, table_content_size.x);
 
-	InitializeCellBoxes();
+	InitializeCellBoxes(cells, columns);
 
-	DetermineRowHeights();
+	DetermineRowHeights(rows, cells, table_content_size.y);
 
-	FormatRows();
+	FormatRows(rows, table_content_size.x);
 
-	FormatColumns();
+	FormatColumns(columns, table_content_size.y);
 
-	FormatCells();
+	FormatCells(cells, table_overflow_size, rows, columns);
 }
 
-void LayoutTable::DetermineColumnWidths()
+void TableFormattingContext::DetermineColumnWidths(TrackBoxList& columns, float& table_content_width) const
 {
 	// The column widths are determined entirely by any <col> elements preceding the first row, and <td> elements in the first row.
 	// If <col> has a fixed width, that is used. Otherwise, if <td> has a fixed width, that is used. Otherwise the column is 'flexible' width.
 	// All flexible widths are then sized to evenly fill the width of the table.
-	
+
 	// Both <col> and <colgroup> can have border/padding which extend beyond the size of <td> and <col>, respectively.
 	// Margins for <td>, <col>, <colgroup> are merged to produce a single left/right margin for each column, located outside <colgroup>.
 
@@ -169,10 +192,10 @@ void LayoutTable::DetermineColumnWidths()
 	const float columns_full_width = BuildColumnBoxes(columns, column_metrics, grid.columns, table_gap.x);
 
 	// Adjust the table content width based on the accumulated column widths and spacing.
-	table_resulting_content_size.x = Math::Clamp(columns_full_width, table_min_size.x, table_max_size.x);
+	table_content_width = Math::Clamp(columns_full_width, table_min_size.x, table_max_size.x);
 }
 
-void LayoutTable::InitializeCellBoxes()
+void TableFormattingContext::InitializeCellBoxes(BoxList& cells, const TrackBoxList& columns) const
 {
 	// Requires that column boxes are already generated.
 	RMLUI_ASSERT(columns.size() == grid.columns.size());
@@ -184,7 +207,7 @@ void LayoutTable::InitializeCellBoxes()
 		Box& box = cells[i];
 
 		// Determine the cell's box for formatting later, we may get an indefinite (-1) vertical content size.
-		LayoutDetails::BuildBox(box, table_initial_content_size, grid.cells[i].element_cell, BoxContext::FlexOrTable, 0.f);
+		LayoutDetails::BuildBox(box, table_initial_content_size, grid.cells[i].element_cell, BuildBoxMode::UnalignedBlock);
 
 		// Determine the cell's content width. Include any spanning columns in the cell width.
 		const float cell_border_width = GetSpanningCellBorderSize(columns, grid.cells[i].column_begin, grid.cells[i].column_last);
@@ -193,22 +216,22 @@ void LayoutTable::InitializeCellBoxes()
 	}
 }
 
-void LayoutTable::DetermineRowHeights()
+void TableFormattingContext::DetermineRowHeights(TrackBoxList& rows, BoxList& cells, float& table_content_height) const
 {
 	/*
-		The table height algorithm works similar to the table width algorithm. The major difference is that 'auto' row height
-		will use the height of the largest formatted cell in the row.
-		
-		Table row height: 
-		  auto: Height of largest cell in row.
-		  length: Fixed size.
-		  percentage < 100%: Fixed size, resolved against table initial height.
-		  percentage >= 100%: Flexible size.
-
-		Table height:
-		  auto: Height is sum of all rows.
-		  length/percentage: Fixed minimum size. If row height sum is larger, increase table size. If row sum is smaller, try to increase
-		                     row heights, but respect max-heights. If table is still larger than row-sum, leave empty space.
+	    The table height algorithm works similar to the table width algorithm. The major difference is that 'auto' row height
+	    will use the height of the largest formatted cell in the row.
+
+	    Table row height:
+	      auto: Height of largest cell in row.
+	      length: Fixed size.
+	      percentage < 100%: Fixed size, resolved against table initial height.
+	      percentage >= 100%: Flexible size.
+
+	    Table height:
+	      auto: Height is sum of all rows.
+	      length/percentage: Fixed minimum size. If row height sum is larger, increase table size. If row sum is smaller, try to increase
+	                         row heights, but respect max-heights. If table is still larger than row-sum, leave empty space.
 	*/
 
 	// Requires that cell boxes have been initialized.
@@ -234,7 +257,7 @@ void LayoutTable::DetermineRowHeights()
 		{
 			// The padding/border/margin and widths of columns are used.
 			const ComputedAxisSize computed = LayoutDetails::BuildComputedVerticalSize(element_row->GetComputedValues());
-			
+
 			if (computed.size.type == Style::LengthPercentageAuto::Percentage)
 				percentage_size_used = true;
 
@@ -244,11 +267,10 @@ void LayoutTable::DetermineRowHeights()
 
 	if (table_auto_height && percentage_size_used)
 	{
-		Log::Message(Log::LT_WARNING, 
+		Log::Message(Log::LT_WARNING,
 			"Table has one or more rows that use percentages for height. However, initial table height is undefined, thus "
 			"these rows will become flattened. Set a fixed height on the table, or use fixed or 'auto' row heights. In element: %s.",
-			element_table->GetAddress().c_str()
-		);
+			element_table->GetAddress().c_str());
 	}
 
 	// Next, find the height of rows that use auto height.
@@ -260,8 +282,8 @@ void LayoutTable::DetermineRowHeights()
 		if (row_metric.sizing_mode == TrackSizingMode::Auto)
 		{
 			struct CellLastRowComp {
-				bool operator() (const TableGrid::Cell& cell, int i) const { return cell.row_last < i; }
-				bool operator() (int i, const TableGrid::Cell& cell) const { return i < cell.row_last; }
+				bool operator()(const TableGrid::Cell& cell, int i) const { return cell.row_last < i; }
+				bool operator()(int i, const TableGrid::Cell& cell) const { return i < cell.row_last; }
 			};
 
 			// Determine which cells end at this row.
@@ -278,19 +300,20 @@ void LayoutTable::DetermineRowHeights()
 				// If both the row and the cell heights are 'auto', we need to format the cell to get its height.
 				if (box.GetSize().y < 0)
 				{
-					LayoutEngine::FormatElement(element_cell, table_initial_content_size, &box);
+					FormattingContext::FormatIndependent(table_wrapper_box, element_cell, &box, FormattingContextType::Block);
 					box.SetContent(element_cell->GetBox().GetSize());
 				}
 
-				// Find the height of the cell which applies only to this row. 
+				// Find the height of the cell which applies only to this row.
 				// In case it spans multiple rows, we must first subtract the height of any previous rows it spans. It is
 				// unsupported if any spanning rows are flexibly sized, in which case we consider their size to be zero.
 				const float gap_from_spanning_rows = table_gap.y * float(grid_cell.row_last - grid_cell.row_begin);
 
-				const float height_from_spanning_rows = std::accumulate(row_metrics.begin() + grid_cell.row_begin, row_metrics.begin() + grid_cell.row_last, gap_from_spanning_rows, [](float acc_height, const TrackMetric& metric) {
-					return acc_height + metric.fixed_size + metric.column_padding_border_a + metric.column_padding_border_b
-						+ metric.group_padding_border_a + metric.group_padding_border_b + metric.sum_margin_a + metric.sum_margin_b;
-				});
+				const float height_from_spanning_rows = std::accumulate(row_metrics.begin() + grid_cell.row_begin,
+					row_metrics.begin() + grid_cell.row_last, gap_from_spanning_rows, [](float acc_height, const TrackMetric& metric) {
+						return acc_height + metric.fixed_size + metric.column_padding_border_a + metric.column_padding_border_b +
+							metric.group_padding_border_a + metric.group_padding_border_b + metric.sum_margin_a + metric.sum_margin_b;
+					});
 
 				const float cell_inrow_height = box.GetSizeAcross(Box::VERTICAL, Box::BORDER) - height_from_spanning_rows;
 
@@ -307,26 +330,23 @@ void LayoutTable::DetermineRowHeights()
 	// Now all heights should be either fixed or flexible, resolve all flexible heights to fixed.
 	sizing.ResolveFlexibleSize();
 
-	// Generate the column results based on the metrics.
+	// Generate the row boxes based on the metrics.
 	const float rows_full_height = BuildRowBoxes(rows, row_metrics, grid.rows, table_gap.y);
 
-	// Adjust the table content width based on the accumulated column widths and spacing.
-	table_resulting_content_size.y = Math::Clamp(Math::Max(rows_full_height, table_initial_content_size.y), table_min_size.y, table_max_size.y);
+	// Adjust the table content height based on the accumulated row heights and spacing.
+	table_content_height = Math::Clamp(Math::Max(rows_full_height, table_initial_content_size.y), table_min_size.y, table_max_size.y);
 }
 
-void LayoutTable::FormatRows()
+void TableFormattingContext::FormatRows(const TrackBoxList& rows, float table_content_width) const
 {
 	RMLUI_ASSERT(rows.size() == grid.rows.size());
 
 	// Size and position the row and row group elements.
-	auto FormatRow = [this](Element* element, float content_height, float offset_y) {
+	auto FormatRow = [this, table_content_width](Element* element, float content_height, float offset_y) {
 		Box box;
-		// We use inline context here because we only care about padding, border, and (non-auto) margin.
-		LayoutDetails::BuildBox(box, table_initial_content_size, element, BoxContext::Inline, 0.0f);
-		const Vector2f content_size(
-			table_resulting_content_size.x - box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING),
-			content_height
-		);
+		// We use inline build mode here because we only care about padding, border, and (non-auto) margin.
+		LayoutDetails::BuildBox(box, table_initial_content_size, element, BuildBoxMode::Inline);
+		const Vector2f content_size(table_content_width - box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING), content_height);
 		box.SetContent(content_size);
 		element->SetBox(box);
 
@@ -346,19 +366,16 @@ void LayoutTable::FormatRows()
 	}
 }
 
-void LayoutTable::FormatColumns()
+void TableFormattingContext::FormatColumns(const TrackBoxList& columns, float table_content_height) const
 {
 	RMLUI_ASSERT(columns.size() == grid.columns.size());
 
 	// Size and position the column and column group elements.
-	auto FormatColumn = [this](Element* element, float content_width, float offset_x) {
+	auto FormatColumn = [this, table_content_height](Element* element, float content_width, float offset_x) {
 		Box box;
-		// We use inline context here because we only care about padding, border, and (non-auto) margin.
-		LayoutDetails::BuildBox(box, table_initial_content_size, element, BoxContext::Inline, 0.0f);
-		const Vector2f content_size(
-			content_width,
-			table_resulting_content_size.y - box.GetSizeAcross(Box::VERTICAL, Box::MARGIN, Box::PADDING)
-		);
+		// We use inline build mode here because we only care about padding, border, and (non-auto) margin.
+		LayoutDetails::BuildBox(box, table_initial_content_size, element, BuildBoxMode::Inline);
+		const Vector2f content_size(content_width, table_content_height - box.GetSizeAcross(Box::VERTICAL, Box::MARGIN, Box::PADDING));
 		box.SetContent(content_size);
 		element->SetBox(box);
 
@@ -378,7 +395,7 @@ void LayoutTable::FormatColumns()
 	}
 }
 
-void LayoutTable::FormatCells()
+void TableFormattingContext::FormatCells(BoxList& cells, Vector2f& table_overflow_size, const TrackBoxList& rows, const TrackBoxList& columns) const
 {
 	RMLUI_ASSERT(cells.size() == grid.cells.size());
 
@@ -391,11 +408,9 @@ void LayoutTable::FormatCells()
 		Style::VerticalAlign vertical_align = element_cell->GetComputedValues().vertical_align();
 
 		const float cell_border_height = GetSpanningCellBorderSize(rows, grid_cell.row_begin, grid_cell.row_last);
-		const Vector2f cell_offset = table_content_offset + Vector2f(
-			columns[grid_cell.column_begin].cell_offset,
-			rows[grid_cell.row_begin].cell_offset
-		);
-		
+		const Vector2f cell_offset =
+			table_content_offset + Vector2f(columns[grid_cell.column_begin].cell_offset, rows[grid_cell.row_begin].cell_offset);
+
 		// Determine the height of the cell.
 		if (box.GetSize().y < 0)
 		{
@@ -403,13 +418,14 @@ void LayoutTable::FormatCells()
 			if (is_aligned)
 			{
 				// We need to format the cell to know how much padding to add.
-				LayoutEngine::FormatElement(element_cell, table_initial_content_size, &box);
+				FormattingContext::FormatIndependent(table_wrapper_box, element_cell, &box, FormattingContextType::Block);
 				box.SetContent(element_cell->GetBox().GetSize());
 			}
 			else
 			{
 				// We don't need to add any padding and can thus avoid formatting, just set the height to the row height.
-				box.SetContent(Vector2f(box.GetSize().x, Math::Max(0.0f, cell_border_height - box.GetSizeAcross(Box::VERTICAL, Box::BORDER, Box::PADDING))));
+				box.SetContent(
+					Vector2f(box.GetSize().x, Math::Max(0.0f, cell_border_height - box.GetSizeAcross(Box::VERTICAL, Box::BORDER, Box::PADDING))));
 			}
 		}
 
@@ -435,6 +451,7 @@ void LayoutTable::FormatCells()
 			default:
 				add_padding_top = 0.0f;
 				add_padding_bottom = available_height;
+				break;
 			}
 
 			box.SetEdge(Box::PADDING, Box::TOP, box.GetEdge(Box::PADDING, Box::TOP) + add_padding_top);
@@ -445,17 +462,16 @@ void LayoutTable::FormatCells()
 		// @performance: We may have already formatted the element during the above procedures without the extra padding. In that case, we may
 		//   instead set the new box and offset all descending elements whose offset parent is the cell, to account for the new padding box.
 		//   That should be faster than formatting the element again, but there may be edge-cases not accounted for.
-		Vector2f cell_visible_overflow_size;
-		LayoutEngine::FormatElement(element_cell, table_initial_content_size, &box, &cell_visible_overflow_size);
+		auto cell_box = FormattingContext::FormatIndependent(table_wrapper_box, element_cell, &box, FormattingContextType::Block);
+		Vector2f cell_visible_overflow_size = cell_box->GetVisibleOverflowSize();
 
 		// Set the position of the element within the the table container
 		element_cell->SetOffset(cell_offset, element_table);
 
 		// The cell contents may overflow, propagate this to the table.
-		table_content_overflow_size.x = Math::Max(table_content_overflow_size.x, cell_offset.x - table_content_offset.x + cell_visible_overflow_size.x);
-		table_content_overflow_size.y = Math::Max(table_content_overflow_size.y, cell_offset.y - table_content_offset.y + cell_visible_overflow_size.y);
+		table_overflow_size.x = Math::Max(table_overflow_size.x, cell_offset.x - table_content_offset.x + cell_visible_overflow_size.x);
+		table_overflow_size.y = Math::Max(table_overflow_size.y, cell_offset.y - table_content_offset.y + cell_visible_overflow_size.y);
 	}
 }
 
-
 } // namespace Rml

+ 93 - 0
Source/Core/Layout/TableFormattingContext.h

@@ -0,0 +1,93 @@
+/*
+ * 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_LAYOUT_TABLEFORMATTINGCONTEXT_H
+#define RMLUI_CORE_LAYOUT_TABLEFORMATTINGCONTEXT_H
+
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "FormattingContext.h"
+#include "TableFormattingDetails.h"
+
+namespace Rml {
+
+class Box;
+class TableGrid;
+class TableWrapper;
+struct TrackBox;
+using TrackBoxList = Vector<TrackBox>;
+
+/*
+    Formats a table element and its parts according to table layout rules.
+*/
+class TableFormattingContext final : public FormattingContext {
+public:
+	static UniquePtr<LayoutBox> Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box);
+
+private:
+	TableFormattingContext() = default;
+
+	using BoxList = Vector<Box>;
+
+	/// Format the table and its children.
+	/// @param[out] table_content_size The final size of the table which will be determined by the size of its columns, rows, and spacing.
+	/// @param[out] table_overflow_size Overflow size in case the contents of any cells overflow their cell box (without being caught by the cell).
+	/// @note Expects the table grid to have been built, and all table parameters to be set already.
+	void FormatTable(Vector2f& table_content_size, Vector2f& table_overflow_size) const;
+
+	// Determines the column widths, and populates the columns.
+	void DetermineColumnWidths(TrackBoxList& columns, float& table_content_width) const;
+
+	// Generate the initial boxes for all cells, content height may be indeterminate for now (-1).
+	void InitializeCellBoxes(BoxList& cells, const TrackBoxList& columns) const;
+
+	// Determines the row heights, and populates the rows.
+	void DetermineRowHeights(TrackBoxList& rows, BoxList& cells, float& table_content_height) const;
+
+	// Format the table row and row group elements.
+	void FormatRows(const TrackBoxList& rows, float table_content_width) const;
+
+	// Format the table row and row group elements.
+	void FormatColumns(const TrackBoxList& columns, float table_content_height) const;
+
+	// Format the table cell elements.
+	void FormatCells(BoxList& cells, Vector2f& table_overflow_size, const TrackBoxList& rows, const TrackBoxList& columns) const;
+
+	Element* element_table = nullptr;
+	TableWrapper* table_wrapper_box = nullptr;
+
+	TableGrid grid;
+
+	bool table_auto_height;
+	Vector2f table_min_size, table_max_size;
+	Vector2f table_gap;
+	Vector2f table_content_offset;
+	Vector2f table_initial_content_size;
+};
+
+} // namespace Rml
+#endif

+ 50 - 38
Source/Core/LayoutTableDetails.cpp → Source/Core/Layout/TableFormattingDetails.cpp

@@ -15,7 +15,7 @@
  *
  * 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
@@ -26,17 +26,19 @@
  *
  */
 
-#include "LayoutTableDetails.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/Element.h"
+#include "TableFormattingDetails.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "ContainerBox.h"
 #include "LayoutDetails.h"
 #include <algorithm>
 #include <float.h>
 
 namespace Rml {
 
-bool TableGrid::Build(Element* element_table)
+bool TableGrid::Build(Element* element_table, TableWrapper& table_wrapper)
 {
+	RMLUI_ASSERT(rows.empty() && columns.empty() && cells.empty() && open_cells.empty());
 	ElementList non_parented_cell_elements;
 
 	const int num_table_children = element_table->GetNumChildren();
@@ -52,7 +54,7 @@ bool TableGrid::Build(Element* element_table)
 
 		if (!non_parented_cell_elements.empty() && display != Display::TableCell)
 		{
-			PushRow(nullptr, std::move(non_parented_cell_elements));
+			PushRow(nullptr, std::move(non_parented_cell_elements), table_wrapper);
 			non_parented_cell_elements.clear();
 		}
 
@@ -62,7 +64,7 @@ bool TableGrid::Build(Element* element_table)
 		}
 		else if (display == Display::TableRow)
 		{
-			PushRow(element, {});
+			PushRow(element, {}, table_wrapper);
 		}
 		else if (display == Display::TableRowGroup)
 		{
@@ -72,8 +74,6 @@ bool TableGrid::Build(Element* element_table)
 
 			for (int j = 0; j < num_row_group_children; j++)
 			{
-				using Display = Style::Display;
-
 				Element* element_row = element->GetChild(j);
 				const Display display_row = element_row->GetDisplay();
 
@@ -81,12 +81,13 @@ bool TableGrid::Build(Element* element_table)
 				{
 					if (display_row != Display::None)
 					{
-						Log::Message(Log::LT_WARNING, "Only table rows are valid children of table row groups. Ignoring element %s.", element_row->GetAddress().c_str());
+						Log::Message(Log::LT_WARNING, "Only table rows are valid children of table row groups. Ignoring element %s.",
+							element_row->GetAddress().c_str());
 					}
 					continue;
 				}
 
-				PushRow(element_row, {});
+				PushRow(element_row, {}, table_wrapper);
 				num_rows_added += 1;
 			}
 
@@ -95,6 +96,8 @@ bool TableGrid::Build(Element* element_table)
 				rows[row_group_index].element_group = element;
 				rows[row_group_index].group_span = num_rows_added;
 			}
+			if (element->GetPosition() == Style::Position::Relative)
+				table_wrapper.AddRelativeElement(element);
 		}
 		else if (rows.empty() && display == Display::TableColumn)
 		{
@@ -108,26 +111,30 @@ bool TableGrid::Build(Element* element_table)
 		else
 		{
 			if (display == Display::TableColumn || display == Display::TableColumnGroup)
-				Log::Message(Log::LT_WARNING, "Table columns and column groups must precede any table rows. Ignoring element %s.", element->GetAddress().c_str());
+				Log::Message(Log::LT_WARNING, "Table columns and column groups must precede any table rows. Ignoring element %s.",
+					element->GetAddress().c_str());
 			else
-				Log::Message(Log::LT_WARNING, "Only table columns, column groups, rows, row groups, and cells are valid children of tables. Ignoring element %s.", element->GetAddress().c_str());
+				Log::Message(Log::LT_WARNING,
+					"Only table columns, column groups, rows, row groups, and cells are valid children of tables. Ignoring element %s.",
+					element->GetAddress().c_str());
 		}
 	}
 
 	if (!non_parented_cell_elements.empty())
 	{
-		PushRow(nullptr, std::move(non_parented_cell_elements));
+		PushRow(nullptr, std::move(non_parented_cell_elements), table_wrapper);
 		non_parented_cell_elements.clear();
 	}
 
 	// Sort cells by the last row they span.
-	std::sort(cells.begin(), cells.end(), [](const Cell& a, const Cell& b) {
-		return a.row_last < b.row_last;
-	});
+	std::sort(cells.begin(), cells.end(), [](const Cell& a, const Cell& b) { return a.row_last < b.row_last; });
 
 	if (!open_cells.empty())
 	{
-		Log::Message(Log::LT_WARNING, "One or more cells span below the last row in table %s. They will not be formatted. Add additional rows, or adjust the rowspan attribute.", element_table->GetAddress().c_str());
+		Log::Message(Log::LT_WARNING,
+			"One or more cells span below the last row in table %s. They will not be formatted. Add additional rows, or adjust the rowspan "
+			"attribute.",
+			element_table->GetAddress().c_str());
 	}
 	open_cells.clear();
 	open_cells.shrink_to_fit();
@@ -135,7 +142,6 @@ bool TableGrid::Build(Element* element_table)
 	return true;
 }
 
-
 void TableGrid::PushColumn(Element* element_column, int span)
 {
 	Column column;
@@ -206,7 +212,7 @@ void TableGrid::PushOrMergeColumnsFromFirstRow(Element* element_cell, int column
 	}
 }
 
-void TableGrid::PushRow(Element* element_row, ElementList cell_elements)
+void TableGrid::PushRow(Element* element_row, ElementList cell_elements, TableWrapper& table_wrapper)
 {
 	const int row_index = (int)rows.size();
 
@@ -231,9 +237,12 @@ void TableGrid::PushRow(Element* element_row, ElementList cell_elements)
 				Log::Message(Log::LT_WARNING, "Only table cells are allowed as children of table rows. %s", element_cell->GetAddress().c_str());
 			}
 		}
+
+		if (element_row->GetPosition() == Style::Position::Relative)
+			table_wrapper.AddRelativeElement(element_row);
 	}
 
-	rows.push_back(Row{ element_row, nullptr, 0 });
+	rows.push_back(Row{element_row, nullptr, 0});
 
 	const int num_cells_spanning_this_row = (int)open_cells.size();
 
@@ -252,7 +261,7 @@ void TableGrid::PushRow(Element* element_row, ElementList cell_elements)
 		}
 
 		// Offset the column if we have any rowspan elements from previous rows overlapping with the current column.
-		for (bool continue_offset_column = true; continue_offset_column; )
+		for (bool continue_offset_column = true; continue_offset_column;)
 		{
 			continue_offset_column = false;
 			for (int k = 0; k < num_cells_spanning_this_row; k++)
@@ -270,7 +279,9 @@ void TableGrid::PushRow(Element* element_row, ElementList cell_elements)
 
 		if (column_last >= (int)columns.size())
 		{
-			Log::Message(Log::LT_WARNING, "Too many columns in table row %d while encountering cell: %s\nThe number of columns is %d, as determined by the table columns or the first table row.",
+			Log::Message(Log::LT_WARNING,
+				"Too many columns in table row %d while encountering cell: %s\nThe number of columns is %d, as determined by the table columns or "
+				"the first table row.",
 				row_index + 1, element_cell->GetAddress().c_str(), (int)columns.size());
 			break;
 		}
@@ -285,20 +296,23 @@ void TableGrid::PushRow(Element* element_row, ElementList cell_elements)
 		cell.column_begin = column;
 		cell.column_last = column_last;
 
+		if (element_cell->GetPosition() == Style::Position::Relative)
+			table_wrapper.AddRelativeElement(element_cell);
+
 		column += col_span;
 	}
 
-
 	// Partition the cells to determine those who end at this row.
-	const auto it_cells_in_row_end = std::partition(open_cells.begin(), open_cells.end(), [row_index](const Cell& cell) { return cell.row_last == row_index; });
-
+	const auto it_cells_in_row_end =
+		std::partition(open_cells.begin(), open_cells.end(), [row_index](const Cell& cell) { return cell.row_last == row_index; });
 
 	// Close cells ending at this row.
 	cells.insert(cells.end(), open_cells.begin(), it_cells_in_row_end);
 	open_cells.erase(open_cells.begin(), it_cells_in_row_end);
 }
 
-void TracksSizing::GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b, const ComputedAxisSize& computed) const
+void TracksSizing::GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b,
+	const ComputedAxisSize& computed) const
 {
 	LayoutDetails::GetEdgeSizes(margin_a, margin_b, padding_border_a, padding_border_b, computed, table_initial_content_size);
 }
@@ -401,7 +415,8 @@ void TracksSizing::ApplyCellElement(const int index, const int span, const Compu
 	}
 }
 
-void TracksSizing::InitializeSize(TrackMetric& metric, float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b, const ComputedAxisSize& computed, const int span, const Style::BoxSizing target_box) const
+void TracksSizing::InitializeSize(TrackMetric& metric, float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b,
+	const ComputedAxisSize& computed, const int span, const Style::BoxSizing target_box) const
 {
 	RMLUI_ASSERT(span >= 1);
 
@@ -483,15 +498,15 @@ void TracksSizing::ResolveFlexibleSize()
 	float table_available_size = 0.0f;
 
 	// Convert any flexible sizes to fixed sizes by filling up the size of the table.
-	for (bool continue_iteration = true; continue_iteration; )
+	for (bool continue_iteration = true; continue_iteration;)
 	{
 		continue_iteration = false;
 		float fr_to_px_ratio = 0;
 
 		// Calculate the fr/px-ratio. [fr] is here the unit for flexible width.
 		{
-			float sum_fixed_size = sum_fixed_spacing;  // [px]
-			float sum_flex_size = 0;                   // [fr]
+			float sum_fixed_size = sum_fixed_spacing; // [px]
+			float sum_flex_size = 0;                  // [fr]
 
 			for (const TrackMetric& metric : metrics)
 			{
@@ -544,9 +559,8 @@ void TracksSizing::ResolveFlexibleSize()
 		}
 
 		// Sort the tracks by available size, smallest to largest. This lets us "fill up" the most constrained tracks first.
-		std::sort(track_available_sizes.begin(), track_available_sizes.end(), [](const TrackAvailableSize& c1, const TrackAvailableSize& c2) {
-			return c1.available_size < c2.available_size;
-		});
+		std::sort(track_available_sizes.begin(), track_available_sizes.end(),
+			[](const TrackAvailableSize& c1, const TrackAvailableSize& c2) { return c1.available_size < c2.available_size; });
 
 		for (int i = 0; i < num_tracks; i++)
 		{
@@ -565,8 +579,6 @@ void TracksSizing::ResolveFlexibleSize()
 	}
 }
 
-
-
 static float InitializeTrackBoxes(TrackBoxList& boxes, const TrackMetricList& metrics, const float table_gap)
 {
 	boxes.resize(metrics.size());
@@ -606,7 +618,8 @@ static void SnapTrackBoxesToPixelGrid(TrackBoxList& boxes)
 	}
 }
 
-float BuildColumnBoxes(TrackBoxList& column_boxes, const TrackMetricList& column_metrics, const TableGrid::ColumnList& grid_columns, const float table_gap_x)
+float BuildColumnBoxes(TrackBoxList& column_boxes, const TrackMetricList& column_metrics, const TableGrid::ColumnList& grid_columns,
+	const float table_gap_x)
 {
 	const float columns_width = InitializeTrackBoxes(column_boxes, column_metrics, table_gap_x);
 	const int num_columns = (int)column_metrics.size();
@@ -660,5 +673,4 @@ float BuildRowBoxes(TrackBoxList& row_boxes, const TrackMetricList& row_metrics,
 	return rows_height;
 }
 
-
 } // namespace Rml

+ 39 - 37
Source/Core/LayoutTableDetails.h → Source/Core/Layout/TableFormattingDetails.h

@@ -15,7 +15,7 @@
  *
  * 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
@@ -26,39 +26,41 @@
  *
  */
 
-#ifndef RMLUI_CORE_LAYOUTTABLEDETAILS_H
-#define RMLUI_CORE_LAYOUTTABLEDETAILS_H
+#ifndef RMLUI_CORE_LAYOUT_TABLEDETAILS_H
+#define RMLUI_CORE_LAYOUT_TABLEDETAILS_H
 
-#include "../../Include/RmlUi/Core/Types.h"
-#include "../../Include/RmlUi/Core/StyleTypes.h"
+#include "../../../Include/RmlUi/Core/StyleTypes.h"
+#include "../../../Include/RmlUi/Core/Types.h"
 #include <float.h>
 
 namespace Rml {
 
+class TableWrapper;
 struct ComputedAxisSize;
 
 /*
-	TableGrid builds the structure of the table, that is a list of rows, columns, and cells, taking
-	spanning attributes into account to position cells.
+TableGrid builds the structure of the table, that is a list of rows, columns, and cells, taking
+spanning attributes into account to position cells.
 */
-class TableGrid
-{
+class TableGrid {
 public:
 	// Build a list of columns, rows, and cells in this table.
-	bool Build(Element* element_table);
+	bool Build(Element* element_table, TableWrapper& table_wrapper);
 
 	struct Column {
-		Element* element_column = nullptr;  // The '<col>' element which begins at this column, or nullptr if there is no such element or if the column is spanned from a previous column.
-		Element* element_group = nullptr;   // The '<colgroup>' element which begins at this column, otherwise nullptr.
-		Element* element_cell = nullptr;    // The '<td>' element of the first row which begins at this column, otherwise nullptr.
-		int column_span = 0;                // The span of the '<col>' element.
-		int group_span = 0;                 // The span of the '<colgroup>' element.
-		int cell_span = 0;                  // The colspan of the '<td>' element.
+		Element* element_column = nullptr; // The '<col>' element which begins at this column, or nullptr if there is no such element or if the column
+		                                   // is spanned from a previous column.
+		Element* element_group = nullptr;  // The '<colgroup>' element which begins at this column, otherwise nullptr.
+		Element* element_cell = nullptr;   // The '<td>' element of the first row which begins at this column, otherwise nullptr.
+		int column_span = 0;               // The span of the '<col>' element.
+		int group_span = 0;                // The span of the '<colgroup>' element.
+		int cell_span = 0;                 // The colspan of the '<td>' element.
 	};
 	struct Row {
-		Element* element_row = nullptr;     // The '<tr>' element which begins at this column, or nullptr if there is no such element or if the column is spanned from a previous column.
-		Element* element_group = nullptr;   // The '<tbody>' element which begins at this column, otherwise nullptr.
-		int group_span = 0;                 // The span of the '<tbody>' element.
+		Element* element_row = nullptr; // The '<tr>' element which begins at this column, or nullptr if there is no such element or if the column is
+		                                // spanned from a previous column.
+		Element* element_group = nullptr; // The '<tbody>' element which begins at this column, otherwise nullptr.
+		int group_span = 0;               // The span of the '<tbody>' element.
 	};
 	struct Cell {
 		Element* element_cell = nullptr;       // The <td> element.
@@ -81,7 +83,7 @@ private:
 
 	void PushOrMergeColumnsFromFirstRow(Element* element_cell, int column_begin, int span);
 
-	void PushRow(Element* element_row, ElementList cell_elements);
+	void PushRow(Element* element_row, ElementList cell_elements, TableWrapper& table_wrapper);
 
 	CellList open_cells;
 };
@@ -89,7 +91,7 @@ private:
 enum class TrackSizingMode { Auto, Fixed, Flexible };
 
 /*
-	TrackMetric describes the size and the edges of a given track (row or column) in the table.
+    TrackMetric describes the size and the edges of a given track (row or column) in the table.
 */
 struct TrackMetric {
 	// All sizes are defined in terms of the border size of cells in the row or column.
@@ -112,11 +114,12 @@ struct TrackMetric {
 using TrackMetricList = Vector<TrackMetric>;
 
 /*
-	TracksSizing is a helper class for building the track metrics, with methods applicable to both rows and columns sizing.
+    TracksSizing is a helper class for building the track metrics, with methods applicable to both rows and columns sizing.
 */
 class TracksSizing {
 public:
-	TracksSizing(TrackMetricList& metrics, float table_initial_content_size, float table_gap) : metrics(metrics), table_initial_content_size(table_initial_content_size), table_gap(table_gap)
+	TracksSizing(TrackMetricList& metrics, float table_initial_content_size, float table_gap) :
+		metrics(metrics), table_initial_content_size(table_initial_content_size), table_gap(table_gap)
 	{}
 
 	// Apply group element. This sets the initial size of edges.
@@ -133,34 +136,33 @@ private:
 	void GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b, const ComputedAxisSize& computed) const;
 
 	// Fill the track metric with fixed, flexible and min/max size, based on the element's computed values.
-	void InitializeSize(TrackMetric& metric, float& margin_a, float& margin_b, float& padding_border_a,
-		float& padding_border_b, const ComputedAxisSize& computed, const int span, const Style::BoxSizing target_box) const;
+	void InitializeSize(TrackMetric& metric, float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b,
+		const ComputedAxisSize& computed, const int span, const Style::BoxSizing target_box) const;
 
 	TrackMetricList& metrics;
 	const float table_initial_content_size;
 	const float table_gap;
 };
 
-
 /*
-	TrackBox represents the size and offset of any given track (row or column).
-	  Rows:    Sizes == Heights. Offsets along vertical axis.
-	  Columns: Sizes == Widths.  Offsets along horizontal axis.
+    TrackBox represents the size and offset of any given track (row or column).
+      Rows:    Sizes == Heights. Offsets along vertical axis.
+      Columns: Sizes == Widths.  Offsets along horizontal axis.
 */
 struct TrackBox {
-	float cell_size = 0;             // The *border* size of cells in this track, does not account for spanning cells.
-	float cell_offset = 0;           // Offset from the table content box to the border box of cells in this track.
-	float track_size = 0;            // The *content* size of the row/column element, which may span multiple tracks.
-	float track_offset = 0;          // Offset from the table content box to the border box of the row/column element.
-	float group_size = 0;            // The *content* size of the group element, which may span multiple tracks.
-	float group_offset = 0;          // Offset from the table content box to the border box of the group element.
+	float cell_size = 0;    // The *border* size of cells in this track, does not account for spanning cells.
+	float cell_offset = 0;  // Offset from the table content box to the border box of cells in this track.
+	float track_size = 0;   // The *content* size of the row/column element, which may span multiple tracks.
+	float track_offset = 0; // Offset from the table content box to the border box of the row/column element.
+	float group_size = 0;   // The *content* size of the group element, which may span multiple tracks.
+	float group_offset = 0; // Offset from the table content box to the border box of the group element.
 };
 using TrackBoxList = Vector<TrackBox>;
 
-
 // Build a list of column boxes from the provided metrics.
 // @return The accumulated width of all columns.
-float BuildColumnBoxes(TrackBoxList& column_boxes, const TrackMetricList& column_metrics, const TableGrid::ColumnList& grid_columns, float table_gap_x);
+float BuildColumnBoxes(TrackBoxList& column_boxes, const TrackMetricList& column_metrics, const TableGrid::ColumnList& grid_columns,
+	float table_gap_x);
 
 // Build a list of row boxes from the provided metrics.
 // @return The accumulated height of all rows.

+ 0 - 793
Source/Core/LayoutBlockBox.cpp

@@ -1,793 +0,0 @@
-/*
- * 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 "LayoutBlockBox.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/Element.h"
-#include "../../Include/RmlUi/Core/ElementScroll.h"
-#include "../../Include/RmlUi/Core/ElementUtilities.h"
-#include "../../Include/RmlUi/Core/Profiling.h"
-#include "../../Include/RmlUi/Core/Property.h"
-#include "LayoutBlockBoxSpace.h"
-#include "LayoutDetails.h"
-#include "LayoutEngine.h"
-#include <float.h>
-
-namespace Rml {
-
-// Creates a new block box for rendering a block element.
-LayoutBlockBox::LayoutBlockBox(LayoutBlockBox* _parent, Element* _element, const Box& _box, float _min_height, float _max_height) 
-	: position(0), box(_box), min_height(_min_height), max_height(_max_height)
-{
-	RMLUI_ZoneScoped;
-
-	space_owner = MakeUnique<LayoutBlockBoxSpace>(this);
-	space = space_owner.get();
-
-	parent = _parent;
-
-	context = BLOCK;
-	element = _element;
-	interrupted_chain = nullptr;
-
-	box_cursor = 0;
-	vertical_overflow = false;
-
-	// Get our offset root from our parent, if it has one; otherwise, our element is the offset parent.
-	if (parent && parent->offset_root->GetElement())
-		offset_root = parent->offset_root;
-	else
-		offset_root = this;
-
-	// Determine the offset parent for this element.
-	LayoutBlockBox* self_offset_parent;
-	if (parent && parent->offset_parent->GetElement())
-		self_offset_parent = parent->offset_parent;
-	else
-		self_offset_parent = this;
-
-	// Determine the offset parent for our children.
-	if (parent &&
-		parent->offset_parent->GetElement() &&
-		(!element || element->GetPosition() == Style::Position::Static))
-		offset_parent = parent->offset_parent;
-	else
-		offset_parent = this;
-
-	// Build the box for our element, and position it if we can.
-	if (parent)
-	{
-		space->ImportSpace(*parent->space);
-
-		// Position ourselves within our containing block (if we have a valid offset parent).
-		if (parent->GetElement())
-		{
-			if (self_offset_parent != this)
-			{
-				// Get the next position within our offset parent's containing block.
-				parent->PositionBlockBox(position, box, element ? element->GetComputedValues().clear() : Style::Clear::None);
-				element->SetOffset(position - (self_offset_parent->GetPosition() - offset_root->GetPosition()), self_offset_parent->GetElement());
-			}
-			else
-				element->SetOffset(position, nullptr);
-		}
-	}
-
-	if (element)
-	{
-		const auto& computed = element->GetComputedValues();
-		wrap_content = computed.white_space() != Style::WhiteSpace::Nowrap;
-
-		// Determine if this element should have scrollbars or not, and create them if so.
-		overflow_x_property = computed.overflow_x();
-		overflow_y_property = computed.overflow_y();
-
-		if (overflow_x_property == Style::Overflow::Scroll)
-			element->GetElementScroll()->EnableScrollbar(ElementScroll::HORIZONTAL, box.GetSize(Box::PADDING).x);
-		else
-			element->GetElementScroll()->DisableScrollbar(ElementScroll::HORIZONTAL);
-
-		if (overflow_y_property == Style::Overflow::Scroll)
-			element->GetElementScroll()->EnableScrollbar(ElementScroll::VERTICAL, box.GetSize(Box::PADDING).x);
-		else
-			element->GetElementScroll()->DisableScrollbar(ElementScroll::VERTICAL);
-
-		// Store relatively positioned elements with their containing block so that their offset can be updated after their containing block has been
-		// sized.
-		if (self_offset_parent != this && computed.position() == Style::Position::Relative)
-			self_offset_parent->relative_elements.push_back(element);
-	}
-	else
-	{
-		wrap_content = true;
-		overflow_x_property = Style::Overflow::Visible;
-		overflow_y_property = Style::Overflow::Visible;
-	}
-}
-
-// Creates a new block box in an inline context.
-LayoutBlockBox::LayoutBlockBox(LayoutBlockBox* _parent) : position(-1, -1)
-{
-	RMLUI_ASSERT(_parent);
-
-	parent = _parent;
-	offset_parent = parent->offset_parent;
-	offset_root = parent->offset_root;
-
-	space = _parent->space;
-
-	context = INLINE;
-	line_boxes.push_back(MakeUnique<LayoutLineBox>(this));
-	wrap_content = parent->wrap_content;
-
-	element = nullptr;
-	interrupted_chain = nullptr;
-
-	box_cursor = 0;
-	vertical_overflow = false;
-
-	const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent);
-	box.SetContent(Vector2f(containing_block.x, -1));
-	parent->PositionBlockBox(position, box, Style::Clear::None);
-
-	// Reset the min and max heights; they're not valid for inline block boxes.
-	min_height = 0;
-	max_height = FLT_MAX;
-}
-
-// Releases the block box.
-LayoutBlockBox::~LayoutBlockBox()
-{
-}
-
-// Closes the box.
-LayoutBlockBox::CloseResult LayoutBlockBox::Close()
-{
-	// If the last child of this block box is an inline box, then we haven't closed it; close it now!
-	if (context == BLOCK)
-	{
-		CloseResult result = CloseInlineBlockBox();
-		if (result != OK)
-			return LAYOUT_SELF;
-	}
-	// Otherwise, we're an inline context box; so close our last line, which will still be open.
-	else
-	{
-		line_boxes.back()->Close();
-
-		// Expand our content area if any line boxes had to push themselves out.
-		Vector2f content_area = box.GetSize();
-		for (size_t i = 0; i < line_boxes.size(); i++)
-			content_area.x = Math::Max(content_area.x, line_boxes[i]->GetDimensions().x);
-
-		box.SetContent(content_area);
-	}
-
-	// Set this box's height, if necessary.
-	if (box.GetSize(Box::CONTENT).y < 0)
-	{
-		Vector2f content_area = box.GetSize();
-		content_area.y = Math::Clamp(box_cursor, min_height, max_height);
-
-		if (element != nullptr)
-			content_area.y = Math::Max(content_area.y, space->GetDimensions().y);
-
-		box.SetContent(content_area);
-	}
-	
-	visible_overflow_size = Vector2f(0);
-	RMLUI_ASSERTMSG(!(context == INLINE && element), "The following assumes inline contexts do not represent a particular element.");
-
-	// Set the computed box on the element.
-	if (context == BLOCK && element)
-	{
-		// Calculate the dimensions of the box's *internal* content; this is the tightest-fitting box around all of the
-		// internal elements, plus this element's padding.
-
-		// Start with the inner content size, as set by the child blocks boxes or external formatting contexts.
-		Vector2f content_box = inner_content_size;
-
-		// Check how big our floated area is.
-		const Vector2f space_box = space->GetDimensions();
-		content_box.x = Math::Max(content_box.x, space_box.x);
-
-		// If our content is larger than our window, we can enable the horizontal scrollbar if
-		// we're set to auto-scrollbars. If we're set to always use scrollbars, then the horiontal
-		// scrollbar will already have been enabled in the constructor.
-		if (content_box.x > box.GetSize().x + 0.5f)
-		{
-			if (overflow_x_property == Style::Overflow::Auto)
-			{
-				element->GetElementScroll()->EnableScrollbar(ElementScroll::HORIZONTAL, box.GetSize(Box::PADDING).x);
-
-				if (!CatchVerticalOverflow())
-					return LAYOUT_SELF;
-			}
-		}
-
-		content_box.y = Math::Max(content_box.y, box_cursor);
-		content_box.y = Math::Max(content_box.y, space_box.y);
-		if (!CatchVerticalOverflow(content_box.y))
-			return LAYOUT_SELF;
-
-		const Vector2f padding_edges = Vector2f(
-			box.GetEdge(Box::PADDING, Box::LEFT) + box.GetEdge(Box::PADDING, Box::RIGHT),
-			box.GetEdge(Box::PADDING, Box::TOP) + box.GetEdge(Box::PADDING, Box::BOTTOM)
-		);
-
-		element->SetBox(box);
-		element->SetContentBox(space->GetOffset(), content_box + padding_edges);
-
-		const Vector2f margin_size = box.GetSize(Box::MARGIN);
-
-		// Set the visible overflow size so that ancestors can catch any overflow produced by us. That is, hiding it or providing a scrolling mechanism.
-		// If we catch our own overflow here, then just use the normal margin box as that will effectively remove the overflow from our ancestor's perspective.
-		if (overflow_x_property != Style::Overflow::Visible)
-			visible_overflow_size.x = margin_size.x;
-		else
-			visible_overflow_size.x = Math::Max(margin_size.x, content_box.x + box.GetEdge(Box::MARGIN, Box::LEFT) + box.GetEdge(Box::BORDER, Box::LEFT) + box.GetEdge(Box::PADDING, Box::LEFT));
-
-		if (overflow_y_property != Style::Overflow::Visible)
-			visible_overflow_size.y = margin_size.y;
-		else
-			visible_overflow_size.y = Math::Max(margin_size.y, content_box.y + box.GetEdge(Box::MARGIN, Box::TOP) + box.GetEdge(Box::BORDER, Box::TOP) + box.GetEdge(Box::PADDING, Box::TOP));
-
-		// Format any scrollbars which were enabled on this element.
-		element->GetElementScroll()->FormatScrollbars();
-	}
-	else if (context == INLINE)
-	{
-		// Find the largest line in this layout block
-		for (size_t i = 0; i < line_boxes.size(); i++)
-		{
-			LayoutLineBox* line_box = line_boxes[i].get();
-			visible_overflow_size.x = Math::Max(visible_overflow_size.x, line_box->GetBoxCursor());
-		}
-	}
-
-	// Increment the parent's cursor.
-	if (parent != nullptr)
-	{
-		// If this close fails, it means this block box has caused our parent block box to generate an automatic vertical scrollbar.
-		if (!parent->CloseBlockBox(this))
-			return LAYOUT_PARENT;
-	}
-
-	if (context == BLOCK && element)
-	{
-		// If we represent a positioned element, then we can now (as we've been sized) act as the containing block for all
-		// the absolutely-positioned elements of our descendants.
-		if (element->GetPosition() != Style::Position::Static)
-			CloseAbsoluteElements();
-
-		// Any relatively positioned elements that we act as containing block for may also need to be have their positions
-		// updated to reflect changes to the size of this block box.
-		for (Element* child : relative_elements)
-			child->UpdateOffset();
-
-		// Set the baseline for inline-block elements to the baseline of the last line of the element.
-		// This is a special rule for inline-blocks (see CSS 2.1 Sec. 10.8.1).
-		if (element->GetDisplay() == Style::Display::InlineBlock)
-		{
-			bool found_baseline = false;
-			float baseline = 0;
-
-			for (int i = (int)block_boxes.size() - 1; i >= 0; i--)
-			{
-				if (block_boxes[i]->context == INLINE)
-				{
-					const LineBoxList& line_boxes = block_boxes[i]->line_boxes;
-					for (int j = (int)line_boxes.size() - 1; j >= 0; j--)
-					{
-						found_baseline = line_boxes[j]->GetBaselineOfLastLine(baseline);
-						if (found_baseline)
-							break;
-					}
-					if (found_baseline)
-						break;
-				}
-			}
-
-			if (found_baseline)
-			{
-				if (baseline < 0 && (overflow_x_property != Style::Overflow::Visible || overflow_y_property != Style::Overflow::Visible))
-				{
-					baseline = 0;
-				}
-
-				element->SetBaseline(baseline);
-			}
-		}
-	}
-
-	return OK;
-}
-
-// Called by a closing block box child.
-bool LayoutBlockBox::CloseBlockBox(LayoutBlockBox* child)
-{
-	RMLUI_ASSERT(context == BLOCK);
-	
-	const float child_position_y = child->GetPosition().y - child->box.GetEdge(Box::MARGIN, Box::TOP) - (box.GetPosition().y + position.y);
-	
-	box_cursor = child_position_y + child->GetBox().GetSize(Box::MARGIN).y;
-	
-	// Extend the inner content size. The vertical size can be larger than the box_cursor due to overflow.
-	inner_content_size.x = Math::Max(inner_content_size.x, child->visible_overflow_size.x);
-	inner_content_size.y = Math::Max(inner_content_size.y, child_position_y + child->visible_overflow_size.y);
-
-	return CatchVerticalOverflow();
-}
-
-// Called by a closing line box child.
-LayoutInlineBox* LayoutBlockBox::CloseLineBox(LayoutLineBox* child, UniquePtr<LayoutInlineBox> overflow, LayoutInlineBox* overflow_chain)
-{
-	RMLUI_ZoneScoped;
-
-	RMLUI_ASSERT(context == INLINE);
-	if (child->GetDimensions().x > 0)
-		box_cursor = (child->GetPosition().y - (box.GetPosition().y + position.y)) + child->GetDimensions().y;
-
-	// If we have any pending floating elements for our parent, then this would be an ideal time to position them.
-	if (!float_elements.empty())
-	{
-		for (size_t i = 0; i < float_elements.size(); ++i)
-			parent->PositionFloat(float_elements[i], box_cursor);
-
-		float_elements.clear();
-	}
-
-	// Add a new line box.
-	line_boxes.push_back(MakeUnique<LayoutLineBox>(this));
-
-	if (overflow_chain)
-		line_boxes.back()->AddChainedBox(overflow_chain);
-
-	if (overflow)
-		return line_boxes.back()->AddBox(std::move(overflow));
-
-	return nullptr;
-}
-
-// Adds a new block element to this block box.
-LayoutBlockBox* LayoutBlockBox::AddBlockElement(Element* element, const Box& box, float min_height, float max_height)
-{
-	RMLUI_ZoneScoped;
-
-	RMLUI_ASSERT(context == BLOCK);
-
-	// Check if our most previous block box is rendering in an inline context.
-	if (!block_boxes.empty() &&
-		block_boxes.back()->context == INLINE)
-	{
-		LayoutBlockBox* inline_block_box = block_boxes.back().get();
-		LayoutInlineBox* open_inline_box = inline_block_box->line_boxes.back()->GetOpenInlineBox();
-		if (open_inline_box != nullptr)
-		{
-			// There's an open inline box chain, which means this block element is parented to it. The chain needs to
-			// be positioned (if it hasn't already), closed and duplicated after this block box closes. Also, this
-			// block needs to be aware of its parentage, so it can correctly compute its relative position. First of
-			// all, we need to close the inline box; this will position the last line if necessary, but it will also
-			// create a new line in the inline block box; we want this line to be in an inline box after our block
-			// element.
-			if (inline_block_box->Close() != OK)
-				return nullptr;
-
-			interrupted_chain = open_inline_box;
-		}
-		else
-		{
-			// There are no open inline boxes, so this inline box just needs to be closed.
-			if (CloseInlineBlockBox() != OK)
-				return nullptr;
-		}
-	}
-
-	block_boxes.push_back(MakeUnique<LayoutBlockBox>(this, element, box, min_height, max_height));
-	return block_boxes.back().get();
-}
-
-// Adds a new inline element to this inline box.
-LayoutInlineBox* LayoutBlockBox::AddInlineElement(Element* element, const Box& box)
-{
-	RMLUI_ZoneScoped;
-
-	if (context == BLOCK)
-	{
-		LayoutInlineBox* inline_box;
-
-		// If we have an open child rendering in an inline context, we can add this element into it.
-		if (!block_boxes.empty() &&
-			block_boxes.back()->context == INLINE)
-			inline_box = block_boxes.back()->AddInlineElement(element, box);
-
-		// No dice! Ah well, nothing for it but to open a new inline context block box.
-		else
-		{
-			block_boxes.push_back(MakeUnique<LayoutBlockBox>(this));
-
-			if (interrupted_chain != nullptr)
-			{
-				block_boxes.back()->line_boxes.back()->AddChainedBox(interrupted_chain);
-				interrupted_chain = nullptr;
-			}
-
-			inline_box = block_boxes.back()->AddInlineElement(element, box);
-		}
-
-		return inline_box;
-	}
-	else
-	{
-		// We're an inline context box, so we'll add this new inline element into our line boxes.
-		return line_boxes.back()->AddElement(element, box);
-	}
-}
-
-// Adds a line-break to this block box.
-void LayoutBlockBox::AddBreak()
-{
-	float line_height = element->GetLineHeight();
-
-	// Check for an inline box as our last child; if so, we can simply end its line and bail.
-	if (!block_boxes.empty())
-	{
-		LayoutBlockBox* block_box = block_boxes.back().get();
-		if (block_box->context == INLINE)
-		{
-			LayoutLineBox* last_line = block_box->line_boxes.back().get();
-			if (last_line->GetDimensions().y < 0)
-				block_box->box_cursor += line_height;
-			else
-				last_line->Close();
-
-			return;
-		}
-	}
-
-	// No inline box as our last child; no problem, just increment the cursor by the line height of this element.
-	box_cursor += line_height;
-}
-
-// Adds an element to this block box to be handled as a floating element.
-bool LayoutBlockBox::AddFloatElement(Element* element)
-{
-	// If we have an open inline block box, then we have to position the box a little differently.
-	if (!block_boxes.empty() &&
-		block_boxes.back()->context == INLINE)
-		block_boxes.back()->float_elements.push_back(element);
-
-	// Nope ... just place it!
-	else
-		PositionFloat(element);
-
-	return true;
-}
-
-// Adds an element to this block box to be handled as an absolutely-positioned element.
-void LayoutBlockBox::AddAbsoluteElement(Element* element)
-{
-	RMLUI_ASSERT(context == BLOCK);
-
-	AbsoluteElement absolute_element;
-	absolute_element.element = element;
-
-	PositionBox(absolute_element.position, 0);
-
-	// If we have an open inline-context block box as our last child, then the absolute element must appear after it,
-	// but not actually close the box.
-	if (!block_boxes.empty()
-		&& block_boxes.back()->context == INLINE)
-	{
-		LayoutBlockBox* inline_context_box = block_boxes.back().get();
-		float last_line_height = inline_context_box->line_boxes.back()->GetDimensions().y;
-
-		absolute_element.position.y += (inline_context_box->box_cursor + Math::Max(0.0f, last_line_height));
-	}
-
-	// Find the positioned parent for this element.
-	LayoutBlockBox* absolute_parent = this;
-	while (absolute_parent != absolute_parent->offset_parent)
-		absolute_parent = absolute_parent->parent;
-
-	absolute_parent->absolute_elements.push_back(absolute_element);
-}
-
-// Lays out, sizes, and positions all absolute elements in this block relative to the containing block.
-void LayoutBlockBox::CloseAbsoluteElements()
-{
-	if (!absolute_elements.empty())
-	{
-		// The size of the containing box, including the padding. This is used to resolve relative offsets.
-		Vector2f containing_block = GetBox().GetSize(Box::PADDING);
-
-		for (size_t i = 0; i < absolute_elements.size(); i++)
-		{
-			Element* absolute_element = absolute_elements[i].element;
-			Vector2f absolute_position = absolute_elements[i].position;
-			absolute_position -= position - offset_root->GetPosition();
-
-			// Lay out the element.
-			LayoutEngine::FormatElement(absolute_element, containing_block);
-
-			// Now that the element's box has been built, we can offset the position we determined was appropriate for
-			// it by the element's margin. This is necessary because the coordinate system for the box begins at the
-			// border, not the margin.
-			absolute_position.x += absolute_element->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-			absolute_position.y += absolute_element->GetBox().GetEdge(Box::MARGIN, Box::TOP);
-
-			// Set the offset of the element; the element itself will take care of any RCSS-defined positional offsets.
-			absolute_element->SetOffset(absolute_position, element);
-		}
-
-		absolute_elements.clear();
-	}
-}
-
-// Returns the offset from the top-left corner of this box that the next child box will be positioned at.
-void LayoutBlockBox::PositionBox(Vector2f& box_position, float top_margin, Style::Clear clear_property) const
-{
-	// If our element is establishing a new offset hierarchy, then any children of ours don't inherit our offset.
-	box_position = GetPosition();
-	box_position += box.GetPosition();
-	box_position.y += box_cursor;
-
-	float clear_margin = space->ClearBoxes(box_position.y + top_margin, clear_property) - (box_position.y + top_margin);
-	if (clear_margin > 0)
-		box_position.y += clear_margin;
-	else
-	{
-		// Check for a collapsing vertical margin.
-		if (!block_boxes.empty() &&
-			block_boxes.back()->context == BLOCK)
-		{
-			const float bottom_margin = block_boxes.back()->GetBox().GetEdge(Box::MARGIN, Box::BOTTOM);
-
-			const int num_negative_margins = int(top_margin < 0.f) + int(bottom_margin < 0.f);
-			switch (num_negative_margins)
-			{
-			case 0:
-				// Use the largest margin by subtracting the smallest margin.
-				box_position.y -= Math::Min(top_margin, bottom_margin);
-				break;
-			case 1:
-				// Use the sum of the positive and negative margin, no special behavior needed here.
-				break;
-			case 2:
-				// Use the most negative margin by subtracting the least negative margin.
-				box_position.y -= Math::Max(top_margin, bottom_margin);
-				break;
-			}
-		}
-	}
-}
-
-// Returns the offset from the top-left corner of this box's offset element the next child block box, of the given
-// dimensions, will be positioned at. This will include the margins on the new block box.
-void LayoutBlockBox::PositionBlockBox(Vector2f& box_position, const Box& box, Style::Clear clear_property) const
-{
-	PositionBox(box_position, box.GetEdge(Box::MARGIN, Box::TOP), clear_property);
-	box_position.x += box.GetEdge(Box::MARGIN, Box::LEFT);
-	box_position.y += box.GetEdge(Box::MARGIN, Box::TOP);
-}
-
-// Returns the offset from the top-left corner of this box for the next line.
-void LayoutBlockBox::PositionLineBox(Vector2f& box_position, float& box_width, bool& _wrap_content, const Vector2f dimensions) const
-{
-	Vector2f cursor;
-	PositionBox(cursor);
-
-	space->PositionBox(box_position, box_width, cursor.y, dimensions);
-
-	// Also, probably shouldn't check for widths when positioning the box?
-	_wrap_content = wrap_content;
-}
-
-
-// Calculate the dimensions of the box's internal content width; i.e. the size of the largest line.
-float LayoutBlockBox::GetShrinkToFitWidth() const
-{
-	float content_width = 0.0f;
-
-	if (context == BLOCK)
-	{
-		auto get_content_width_from_children = [this, &content_width]() {
-			for (size_t i = 0; i < block_boxes.size(); i++)
-			{
-				const Box& box = block_boxes[i]->GetBox();
-				const float edge_size = box.GetCumulativeEdge(Box::PADDING, Box::LEFT) + box.GetCumulativeEdge(Box::PADDING, Box::RIGHT);
-				content_width = Math::Max(content_width, block_boxes[i]->GetShrinkToFitWidth() + edge_size);
-			}
-		};
-
-		// Block boxes with definite sizes should use that size. Otherwise, find the maximum content width of our children.
-		//  Alternative solution: Add some 'intrinsic_width' property to every 'LayoutBlockBox' and have that propagate up.
-		if (element)
-		{
-			auto& computed = element->GetComputedValues();
-			const float block_width = box.GetSize(Box::CONTENT).x;
-
-			if (computed.width().type == Style::Width::Auto)
-			{
-				get_content_width_from_children();
-			}
-			else
-			{
-				float width_value = ResolveValue(computed.width(), block_width);
-				content_width = Math::Max(content_width, width_value);
-			}
-
-			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
-		{
-			get_content_width_from_children();
-		}
-
-		// Can add the dimensions of floating elements here if we want to support that.
-	}
-	else
-	{
-		// Find the largest line in this layout block
-		for (size_t i = 0; i < line_boxes.size(); i++)
-		{
-			// Perhaps a more robust solution is to modify how we set the line box dimension on 'line_box->close()'
-			// and use that, or add another value in the line_box ... but seems to work for now.
-			LayoutLineBox* line_box = line_boxes[i].get();
-			content_width = Math::Max(content_width, line_box->GetBoxCursor());
-		}
-		content_width = Math::Min(content_width, box.GetSize(Box::CONTENT).x);
-	}
-
-	return content_width;
-}
-
-Vector2f LayoutBlockBox::GetVisibleOverflowSize() const
-{
-	return visible_overflow_size;
-}
-
-void LayoutBlockBox::ExtendInnerContentSize(Vector2f _inner_content_size)
-{
-	inner_content_size.x = Math::Max(inner_content_size.x, _inner_content_size.x);
-	inner_content_size.y = Math::Max(inner_content_size.y, _inner_content_size.y);
-}
-
-// Returns the block box's element.
-Element* LayoutBlockBox::GetElement() const
-{
-	return element;
-}
-
-// Returns the block box's parent.
-LayoutBlockBox* LayoutBlockBox::GetParent() const
-{
-	return parent;
-}
-
-// Returns the position of the block box, relative to its parent's content area.
-Vector2f LayoutBlockBox::GetPosition() const
-{
-	return position;
-}
-
-// Returns the element against which all positions of boxes in the hierarchy are calculated relative to.
-const LayoutBlockBox* LayoutBlockBox::GetOffsetParent() const
-{
-	return offset_parent;
-}
-
-// Returns the block box against which all positions of boxes in the hierarchy are calculated relative to.
-const LayoutBlockBox* LayoutBlockBox::GetOffsetRoot() const
-{
-	return offset_root;
-}
-
-// Returns the block box's dimension box.
-Box& LayoutBlockBox::GetBox()
-{
-	return box;
-}
-
-// Returns the block box's dimension box.
-const Box& LayoutBlockBox::GetBox() const
-{
-	return box;
-}
-
-void* LayoutBlockBox::operator new(size_t size)
-{
-	void* memory = LayoutEngine::AllocateLayoutChunk(size);
-	return memory;
-}
-
-void LayoutBlockBox::operator delete(void* chunk, size_t size)
-{
-	LayoutEngine::DeallocateLayoutChunk(chunk, size);
-}
-
-// Closes our last block box, if it is an open inline block box.
-LayoutBlockBox::CloseResult LayoutBlockBox::CloseInlineBlockBox()
-{
-	if (!block_boxes.empty() &&
-		block_boxes.back()->context == INLINE)
-		return block_boxes.back()->Close();
-
-	return OK;
-}
-
-// Positions a floating element within this block box.
-void LayoutBlockBox::PositionFloat(Element* element, float offset)
-{
-	Vector2f box_position;
-	PositionBox(box_position);
-
-	space->PositionBox(box_position.y + offset, element);
-}
-
-// Checks if we have a new vertical overflow on an auto-scrolling element.
-bool LayoutBlockBox::CatchVerticalOverflow(float cursor)
-{
-	if (cursor == -1)
-		cursor = Math::Max(box_cursor, inner_content_size.y);
-
-	float box_height = box.GetSize().y;
-	if (box_height < 0)
-		box_height = max_height;
-
-	// If we're auto-scrolling and our height is fixed, we have to check if this box has exceeded our client height.
-	if (!vertical_overflow &&
-		box_height >= 0 &&
-		overflow_y_property == Style::Overflow::Auto)
-	{
-		if (cursor > box_height - element->GetElementScroll()->GetScrollbarSize(ElementScroll::HORIZONTAL) + 0.5f)
-		{
-			RMLUI_ZoneScopedC(0xDD3322);
-			vertical_overflow = true;
-			element->GetElementScroll()->EnableScrollbar(ElementScroll::VERTICAL, box.GetSize(Box::PADDING).x);
-
-			block_boxes.clear();
-
-			space_owner = MakeUnique<LayoutBlockBoxSpace>(this);
-			space = space_owner.get();
-
-			box_cursor = 0;
-			interrupted_chain = nullptr;
-
-			inner_content_size = Vector2f(0);
-
-			return false;
-		}
-	}
-
-	return true;
-}
-
-} // namespace Rml

+ 0 - 254
Source/Core/LayoutBlockBox.h

@@ -1,254 +0,0 @@
-/*
- * 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_LAYOUTBLOCKBOX_H
-#define RMLUI_CORE_LAYOUTBLOCKBOX_H
-
-#include "LayoutLineBox.h"
-#include "../../Include/RmlUi/Core/Box.h"
-#include "../../Include/RmlUi/Core/Types.h"
-
-namespace Rml {
-
-class LayoutBlockBoxSpace;
-class LayoutEngine;
-
-/**
-	@author Peter Curry
- */
-
-class LayoutBlockBox
-{
-public:
-	enum FormattingContext
-	{
-		BLOCK,
-		INLINE
-	};
-
-	enum CloseResult
-	{
-		OK,
-		LAYOUT_SELF,
-		LAYOUT_PARENT
-	};
-
-	/// Creates a new block box for rendering a block element.
-	/// @param parent[in] The parent of this block box. This will be nullptr for the root element.
-	/// @param element[in] The element this block box is laying out.
-	/// @param box[in] The box used for this block box.
-	/// @param min_height[in] The minimum height of the content box.
-	/// @param max_height[in] The maximum height of the content box.
-	LayoutBlockBox(LayoutBlockBox* parent, Element* element, const Box& box, float min_height, float max_height);
-	/// Creates a new block box in an inline context.
-	/// @param parent[in] The parent of this block box.
-	LayoutBlockBox(LayoutBlockBox* parent);
-	/// Releases the block box.
-	~LayoutBlockBox();
-
-	/// Closes the box. This will determine the element's height (if it was unspecified).
-	/// @return The result of the close; this may request a reformat of this element or our parent.
-	CloseResult Close();
-
-	/// Called by a closing block box child. Increments the cursor.
-	/// @param child[in] The closing child block box.
-	/// @return False if the block box caused an automatic vertical scrollbar to appear, forcing an entire reformat of the block box.
-	bool CloseBlockBox(LayoutBlockBox* child);
-	/// Called by a closing line box child. Increments the cursor, and creates a new line box to fit the overflow
-	/// (if any).
-	/// @param child[in] The closing child line box.
-	/// @param overflow[in] The overflow from the closing line box. May be nullptr if there was no overflow.
-	/// @param overflow_chain[in] The end of the chained hierarchy to be spilled over to the new line, as the parent to the overflow box (if one exists).
-	/// @return If the line box had overflow, this will be the last inline box created by the overflow.
-	LayoutInlineBox* CloseLineBox(LayoutLineBox* child, UniquePtr<LayoutInlineBox> overflow, LayoutInlineBox* overflow_chain);
-
-	/// Adds a new block element to this block-context box.
-	/// @param element[in] The new block element.
-	/// @param box[in] The box used for the new block box.
-	/// @param min_height[in] The minimum height of the content box.
-	/// @param max_height[in] The maximum height of the content box.
-	/// @return The block box representing the element. Once the element's children have been positioned, Close() must be called on it.
-	LayoutBlockBox* AddBlockElement(Element* element, const Box& box, float min_height, float max_height);
-	/// Adds a new inline element to this inline-context box.
-	/// @param element[in] The new inline element.
-	/// @param box[in] The box defining the element's bounds.
-	/// @return The inline box representing the element. Once the element's children have been positioned, Close() must be called on it.
-	LayoutInlineBox* AddInlineElement(Element* element, const Box& box);
-	/// Adds a line-break to this block box.
-	void AddBreak();
-
-	/// Adds an element to this block box to be handled as a floating element.
-	bool AddFloatElement(Element* element);
-
-	/// Adds an element to this block box to be handled as an absolutely-positioned element. This element will be
-	/// laid out, sized and positioned appropriately once this box is finished. This should only be called on boxes
-	/// rendering in a block-context.
-	/// @param element[in] The element to be positioned absolutely within this block box.
-	void AddAbsoluteElement(Element* element);
-	/// Formats, sizes, and positions all absolute elements in this block.
-	void CloseAbsoluteElements();
-
-	/// Returns the offset from the top-left corner of this box's offset element the next child box will be
-	/// positioned at.
-	/// @param[out] box_position The box cursor position.
-	/// @param[in] top_margin The top margin of the box. This will be collapsed as appropriate against other block boxes.
-	/// @param[in] clear_property The value of the underlying element's clear property.
-	void PositionBox(Vector2f& box_position, float top_margin = 0, Style::Clear clear_property = Style::Clear::None) const;
-	/// Returns the offset from the top-left corner of this box's offset element the next child block box, of the
-	/// given dimensions, will be positioned at. This will include the margins on the new block box.
-	/// @param[out] box_position The block box cursor position.
-	/// @param[in] box The dimensions of the new box.
-	/// @param[in] clear_property The value of the underlying element's clear property.
-	void PositionBlockBox(Vector2f& box_position, const Box& box, Style::Clear clear_property) const;
-	/// Returns the offset from the top-left corner of this box for the next line.
-	/// @param box_position[out] The line box position.
-	/// @param box_width[out] The available width for the line box.
-	/// @param wrap_content[out] Set to true if the line box should grow to fit inline boxes, false if it should wrap them.
-	/// @param dimensions[in] The minimum dimensions of the line.
-	void PositionLineBox(Vector2f& box_position, float& box_width, bool& wrap_content, Vector2f dimensions) const;
-
-	/// Calculate the dimensions of the box's internal content width; i.e. the size used to calculate the shrink-to-fit width.
-	float GetShrinkToFitWidth() const;
-	/// Get the visible overflow size. Note: This is only well-defined after the layout box has been closed.
-	Vector2f GetVisibleOverflowSize() const;
-	/// Set the inner content size if it is larger than the current value on each axis individually.
-	void ExtendInnerContentSize(Vector2f inner_content_size);
-
-	/// Returns the block box's element.
-	/// @return The block box's element.
-	Element* GetElement() const;
-
-	/// Returns the block box's parent.
-	/// @return The block box's parent.
-	LayoutBlockBox* GetParent() const;
-
-	/// Returns the position of the block box, relative to its parent's content area.
-	/// @return The relative position of the block box.
-	Vector2f GetPosition() const;
-
-	/// Returns the block box against which all positions of boxes in the hierarchy are set relative to.
-	/// @return This box's offset parent.
-	const LayoutBlockBox* GetOffsetParent() const;
-	/// Returns the block box against which all positions of boxes in the hierarchy are calculated relative to.
-	/// @return This box's offset root.
-	const LayoutBlockBox* GetOffsetRoot() const;
-
-
-	/// Returns the block box's dimension box.
-	Box& GetBox();
-	/// Returns the block box's dimension box.
-	const Box& GetBox() const;
-
-	void* operator new(size_t size);
-	void operator delete(void* chunk, size_t size);
-
-private:
-	struct AbsoluteElement
-	{
-		Element* element;
-		Vector2f position;
-	};
-
-	// Closes our last block box, if it is an open inline block box.
-	CloseResult CloseInlineBlockBox();
-
-	// Positions a floating element within this block box.
-	void PositionFloat(Element* element, float offset = 0);
-
-	// Checks if we have a new vertical overflow on an auto-scrolling element. If so, our vertical scrollbar will
-	// be enabled and our block boxes will be destroyed. All content will need to re-formatted. Returns true if no
-	// overflow occured, false if it did.
-	bool CatchVerticalOverflow(float cursor = -1);
-
-	using AbsoluteElementList = Vector< AbsoluteElement >;
-	using BlockBoxList = Vector< UniquePtr<LayoutBlockBox> >;
-	using LineBoxList = Vector< UniquePtr<LayoutLineBox> >;
-
-	// The object managing our space, as occupied by floating elements of this box and our ancestors.
-	LayoutBlockBoxSpace* space;
-
-	// The element this box represents. This will be nullptr for boxes rendering in an inline context.
-	Element* element;
-
-	// The element we'll be computing our offset relative to during layout.
-	const LayoutBlockBox* offset_root;
-	// The element this block box's children are to be offset from.
-	LayoutBlockBox* offset_parent;
-
-	// The box's block parent. This will be nullptr for the root of the box tree.
-	LayoutBlockBox* parent;
-
-	// The context of the box's context; either block or inline.
-	FormattingContext context;
-
-	// The block box's position.
-	Vector2f position;
-	// The block box's size.
-	Box box;
-	float min_height;
-	float max_height;
-
-	// Used by inline contexts only; set to true if the block box's line boxes should stretch to fit their inline content instead of wrapping.
-	bool wrap_content;
-
-	// The vertical position of the next block box to be added to this box, relative to the top of this box.
-	float box_cursor;
-
-	// Used by block contexts only; stores the list of block boxes under this box.
-	BlockBoxList block_boxes;
-	// Used by block contexts only; stores any elements that are to be absolutely positioned within this block box.
-	AbsoluteElementList absolute_elements;
-	// Used by block contexts only; stores any elements that are relatively positioned and whose containing block is this.
-	ElementList relative_elements;
-	// Used by block contexts only; stores the block box space pointed to by the 'space' member.
-	UniquePtr<LayoutBlockBoxSpace> space_owner;
-	// Used by block contexts only; stores an inline element hierarchy that was interrupted by a child block box.
-	// The hierarchy will be resumed in an inline-context box once the intervening block box is completed.
-	LayoutInlineBox* interrupted_chain;
-	// Used by block contexts only; stores the value of the overflow property for the element.
-	Style::Overflow overflow_x_property;
-	Style::Overflow overflow_y_property;
-
-	// The outer size of this box including overflowing content. Similar to scroll width, but shrinked if overflow is caught here. 
-	//   This can be wider than the box if we are overflowing. Only available after the box has been closed. 
-	Vector2f visible_overflow_size;
-	// The inner content size (excluding any padding/border/margins).
-	// This is extended as child block boxes are closed, or from external formatting contexts.
-	Vector2f inner_content_size;
-
-	// Used by block contexts only; if true, we've enabled our vertical scrollbar.
-	bool vertical_overflow;
-
-	// Used by inline contexts only; stores the list of line boxes flowing inline content.
-	LineBoxList line_boxes;
-	// Used by inline contexts only; stores any floating elements that are waiting for a line break to be positioned.
-	ElementList float_elements;
-};
-
-} // namespace Rml
-#endif

+ 0 - 127
Source/Core/LayoutBlockBoxSpace.h

@@ -1,127 +0,0 @@
-/*
- * 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_LAYOUTBLOCKBOXSPACE_H
-#define RMLUI_CORE_LAYOUTBLOCKBOXSPACE_H
-
-#include "../../Include/RmlUi/Core/StyleTypes.h"
-#include "../../Include/RmlUi/Core/Types.h"
-
-namespace Rml {
-
-class Element;
-class LayoutBlockBox;
-
-/**
-	Each block box has a space object for managing the space occupied by its floating elements, and those of its
-	ancestors as relevant.
-
-	@author Peter Curry
- */
-
-class LayoutBlockBoxSpace
-{
-public:
-	LayoutBlockBoxSpace(LayoutBlockBox* parent);
-	~LayoutBlockBoxSpace();
-
-	/// Imports boxes from another block into this space.
-	/// @param[in] space The space to import boxes from.
-	void ImportSpace(const LayoutBlockBoxSpace& space);
-
-	/// Generates the position for a box of a given size within our block box.
-	/// @param[out] box_position The generated position for the box.
-	/// @param[out] box_width The available width for the box.
-	/// @param[in] cursor The ideal vertical position for the box.
-	/// @param[in] dimensions The minimum available space required for the box.
-	void PositionBox(Vector2f& box_position, float& box_width, float cursor, Vector2f dimensions) const;
-
-	/// Generates and sets the position for a floating box of a given size within our block box. The element's box
-	/// is then added into our list of floating boxes.
-	/// @param[in] cursor The ideal vertical position for the box.
-	/// @param[in] element The element to position.
-	/// @return The offset of the bottom outer edge of the element.
-	float PositionBox(float cursor, Element* element);
-
-	/// Determines the appropriate vertical position for an object that is choosing to clear floating elements to
-	/// the left or right (or both).
-	/// @param[in] cursor The ideal vertical position.
-	/// @param[in] clear_property The value of the clear property of the clearing object.
-	/// @return The appropriate vertical position for the clearing object.
-	float ClearBoxes(float cursor, Style::Clear clear_property) const;
-
-	/// Returns the top-left corner of the boxes within the space.
-	/// @return The space's offset.
-	Vector2f GetOffset() const;
-	/// Returns the dimensions of the boxes within the space.
-	/// @return The space's dimensions.
-	Vector2f GetDimensions() const;
-
-	void* operator new(size_t size);
-	void operator delete(void* chunk, size_t size);
-
-private:
-	enum AnchorEdge
-	{
-		LEFT = 0,
-		RIGHT = 1,
-		NUM_ANCHOR_EDGES = 2
-	};
-
-	/// Generates the position for an arbitrary box within our space layout, floated against either the left or
-	/// right edge.
-	/// @param box_position[out] The generated position for the box.
-	/// @param cursor[in] The ideal vertical position for the box.
-	/// @param dimensions[in] The size of the box to place.
-	/// @return The maximum width at the box position.
-	float PositionBox(Vector2f& box_position, float cursor, Vector2f dimensions, Style::Float float_property = Style::Float::None) const;
-
-	struct SpaceBox
-	{
-		SpaceBox();
-		SpaceBox(Vector2f offset, Vector2f dimensions);
-
-		Vector2f offset;
-		Vector2f dimensions;
-	};
-
-	using SpaceBoxList = Vector< SpaceBox >;
-
-	// Our block-box parent.
-	LayoutBlockBox* parent;
-
-	// The boxes floating in our space.
-	SpaceBoxList boxes[NUM_ANCHOR_EDGES];
-
-	// The offset and dimensions of the boxes added specifically into this space.
-	Vector2f offset;
-	Vector2f dimensions;
-};
-
-} // namespace Rml
-#endif

+ 0 - 456
Source/Core/LayoutEngine.cpp

@@ -1,456 +0,0 @@
-/*
- * 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 "LayoutEngine.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/Element.h"
-#include "../../Include/RmlUi/Core/Profiling.h"
-#include "../../Include/RmlUi/Core/Types.h"
-#include "LayoutBlockBoxSpace.h"
-#include "LayoutDetails.h"
-#include "LayoutFlex.h"
-#include "LayoutInlineBoxText.h"
-#include "LayoutTable.h"
-#include "Pool.h"
-#include <cstddef>
-#include <float.h>
-
-namespace Rml {
-
-#define MAX(a, b) (a > b ? a : b)
-
-template <size_t Size>
-struct LayoutChunk {
-	alignas(std::max_align_t) byte buffer[Size];
-};
-
-static constexpr std::size_t ChunkSizeBig = sizeof(LayoutBlockBox);
-static constexpr std::size_t ChunkSizeMedium = MAX(sizeof(LayoutInlineBox), sizeof(LayoutInlineBoxText));
-static constexpr std::size_t ChunkSizeSmall = MAX(sizeof(LayoutLineBox), sizeof(LayoutBlockBoxSpace));
-
-static Pool< LayoutChunk<ChunkSizeBig> > layout_chunk_pool_big(50, true);
-static Pool< LayoutChunk<ChunkSizeMedium> > layout_chunk_pool_medium(50, true);
-static Pool< LayoutChunk<ChunkSizeSmall> > layout_chunk_pool_small(50, true);
-
-static inline bool ValidateTopLevelElement(Element* element)
-{
-	const Style::Display display = element->GetDisplay();
-
-	// Currently we don't support flexboxes or tables in a top-level formatting context. This includes on the <body> element, table cells, the
-	// children of flex containers, and possibly elements with custom formatting such as <select>. See also the related
-	// 'uses_unsupported_display_position_float_combination' below.
-	if (display == Style::Display::Flex || display == Style::Display::Table)
-	{
-		const char* error_msg = "located in a top-level formatting context";
-		if (Element* parent = element->GetParentNode())
-		{
-			if (parent->GetDisplay() == Style::Display::Flex)
-				error_msg = "nested inside a flex container";
-		}
-		const Property* display_property = element->GetProperty(PropertyId::Display);
-		Log::Message(Log::LT_WARNING,
-			"Element with display type '%s' cannot be %s. Instead, wrap it within a parent block element such as a <div>. Element will not be "
-		    "formatted: %s",
-			display_property ? display_property->ToString().c_str() : "*unknown*", error_msg, element->GetAddress().c_str());
-
-		return false;
-	}
-
-	return true;
-}
-
-// Formats the contents for a root-level element (usually a document or floating element).
-void LayoutEngine::FormatElement(Element* element, Vector2f containing_block, const Box* override_initial_box, Vector2f* out_visible_overflow_size)
-{
-	RMLUI_ASSERT(element && containing_block.x >= 0 && containing_block.y >= 0);
-#ifdef RMLUI_ENABLE_PROFILING
-	RMLUI_ZoneScopedC(0xB22222);
-	auto name = CreateString(80, "%s %x", element->GetAddress(false, false).c_str(), element);
-	RMLUI_ZoneName(name.c_str(), name.size());
-#endif
-
-	if (!ValidateTopLevelElement(element))
-		return;
-	
-	auto containing_block_box = MakeUnique<LayoutBlockBox>(nullptr, nullptr, Box(containing_block), 0.0f, FLT_MAX);
-
-	Box box;
-	if (override_initial_box)
-		box = *override_initial_box;
-	else
-		LayoutDetails::BuildBox(box, containing_block, element);
-
-	float min_height, max_height;
-	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);
-
-	for (int layout_iteration = 0; layout_iteration < 2; layout_iteration++)
-	{
-		for (int i = 0; i < element->GetNumChildren(); i++)
-		{
-			if (!FormatElement(block_context_box, element->GetChild(i)))
-				i = -1;
-		}
-
-		if (block_context_box->Close() == LayoutBlockBox::OK)
-			break;
-	}
-
-	block_context_box->CloseAbsoluteElements();
-
-	if (out_visible_overflow_size)
-		*out_visible_overflow_size = block_context_box->GetVisibleOverflowSize();
-
-	element->OnLayout();
-}
-
-void* LayoutEngine::AllocateLayoutChunk(size_t size)
-{
-	static_assert(ChunkSizeBig > ChunkSizeMedium && ChunkSizeMedium > ChunkSizeSmall, "The following assumes a strict ordering of the chunk sizes.");
-
-	// Note: If any change is made here, make sure a corresponding change is applied to the deallocation procedure below.
-	if (size <= ChunkSizeSmall)
-		return layout_chunk_pool_small.AllocateAndConstruct();
-	else if (size <= ChunkSizeMedium)
-		return layout_chunk_pool_medium.AllocateAndConstruct();
-	else if (size <= ChunkSizeBig)
-		return layout_chunk_pool_big.AllocateAndConstruct();
-
-	RMLUI_ERROR;
-	return nullptr;
-}
-
-void LayoutEngine::DeallocateLayoutChunk(void* chunk, size_t size)
-{
-	// Note: If any change is made here, make sure a corresponding change is applied to the allocation procedure above.
-	if (size <= ChunkSizeSmall)
-		layout_chunk_pool_small.DestroyAndDeallocate((LayoutChunk<ChunkSizeSmall>*)chunk);
-	else if (size <= ChunkSizeMedium)
-		layout_chunk_pool_medium.DestroyAndDeallocate((LayoutChunk<ChunkSizeMedium>*)chunk);
-	else if (size <= ChunkSizeBig)
-		layout_chunk_pool_big.DestroyAndDeallocate((LayoutChunk<ChunkSizeBig>*)chunk);
-	else
-	{
-		RMLUI_ERROR;
-	}
-}
-
-// Positions a single element and its children within this layout.
-bool LayoutEngine::FormatElement(LayoutBlockBox* block_context_box, Element* element)
-{
-#ifdef RMLUI_ENABLE_PROFILING
-	RMLUI_ZoneScoped;
-	auto name = CreateString(80, ">%s %x", element->GetAddress(false, false).c_str(), element);
-	RMLUI_ZoneName(name.c_str(), name.size());
-#endif
-
-	auto& computed = element->GetComputedValues();
-
-	// Check if we have to do any special formatting for any elements that don't fit into the standard layout scheme.
-	if (FormatElementSpecial(block_context_box, element))
-		return true;
-
-	const Style::Display display = element->GetDisplay();
-
-	// Fetch the display property, and don't lay this element out if it is set to a display type of none.
-	if (display == Style::Display::None)
-		return true;
-
-	// Tables and flex boxes need to be specially handled when they are absolutely positioned or floated. Currently it is assumed for both
-	// FormatElement(element, containing_block) and GetShrinkToFitWidth(), and possibly others, that they are strictly called on block boxes.
-	// The mentioned functions need to be updated if we want to support all combinations of display, position, and float properties.
-	auto uses_unsupported_display_position_float_combination = [display, element](const char* abs_positioned_or_floated) -> bool {
-		if (display == Style::Display::Flex || display == Style::Display::Table)
-		{
-			const char* element_type = (display == Style::Display::Flex ? "Flex" : "Table");
-			Log::Message(Log::LT_WARNING,
-				"%s elements cannot be %s. Instead, wrap it within a parent block element which is %s. Element will not be formatted: %s",
-				element_type, abs_positioned_or_floated, abs_positioned_or_floated, element->GetAddress().c_str());
-			return true;
-		}
-		return false;
-	};
-
-	// Check for an absolute position; if this has been set, then we remove it from the flow and add it to the current
-	// block box to be laid out and positioned once the block has been closed and sized.
-	if (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed)
-	{
-		if (uses_unsupported_display_position_float_combination("absolutely positioned"))
-			return true;
-
-		// Display the element as a block element.
-		block_context_box->AddAbsoluteElement(element);
-		return true;
-	}
-
-	// If the element is floating, we remove it from the flow.
-	if (computed.float_() != Style::Float::None)
-	{
-		if (uses_unsupported_display_position_float_combination("floated"))
-			return true;
-
-		LayoutEngine::FormatElement(element, LayoutDetails::GetContainingBlock(block_context_box));
-		return block_context_box->AddFloatElement(element);
-	}
-
-	// The element is nothing exceptional, so format it according to its display property.
-	switch (display)
-	{
-		case Style::Display::Block:       return FormatElementBlock(block_context_box, element);
-		case Style::Display::Inline:      return FormatElementInline(block_context_box, element);
-		case Style::Display::InlineBlock: return FormatElementInlineBlock(block_context_box, element);
-		case Style::Display::Flex:        return FormatElementFlex(block_context_box, element);
-		case Style::Display::Table:       return FormatElementTable(block_context_box, element);
-
-		case Style::Display::TableRow:
-		case Style::Display::TableRowGroup:
-		case Style::Display::TableColumn:
-		case Style::Display::TableColumnGroup:
-		case Style::Display::TableCell:
-		{
-			// These elements should have been handled within FormatElementTable, seems like we're encountering table parts in the wild.
-			const Property* display_property = element->GetProperty(PropertyId::Display);
-			Log::Message(Log::LT_WARNING, "Element has a display type '%s', but is not located in a table. Element will not be formatted: %s",
-				display_property ? display_property->ToString().c_str() : "*unknown*",
-				element->GetAddress().c_str()
-			);
-			return true;
-		}
-		case Style::Display::None:        RMLUI_ERROR; /* handled above */ break;
-	}
-
-	return true;
-}
-
-// Formats and positions an element as a block element.
-bool LayoutEngine::FormatElementBlock(LayoutBlockBox* block_context_box, Element* element)
-{
-	RMLUI_ZoneScopedC(0x2F4F4F);
-
-	Box box;
-	float min_height, max_height;
-	LayoutDetails::BuildBox(box, min_height, max_height, block_context_box, element);
-
-	LayoutBlockBox* new_block_context_box = block_context_box->AddBlockElement(element, box, min_height, max_height);
-	if (new_block_context_box == nullptr)
-		return false;
-
-	// Format the element's children.
-	for (int i = 0; i < element->GetNumChildren(); i++)
-	{
-		if (!FormatElement(new_block_context_box, element->GetChild(i)))
-			i = -1;
-	}
-
-	// Close the block box, and check the return code; we may have overflowed either this element or our parent.
-	switch (new_block_context_box->Close())
-	{
-		// We need to reformat ourself; format all of our children again and close the box. No need to check for error
-		// codes, as we already have our vertical slider bar.
-		case LayoutBlockBox::LAYOUT_SELF:
-		{
-			for (int i = 0; i < element->GetNumChildren(); i++)
-				FormatElement(new_block_context_box, element->GetChild(i));
-
-			if (new_block_context_box->Close() == LayoutBlockBox::OK)
-			{
-				element->OnLayout();
-				break;
-			}
-		}
-		//-fallthrough
-		// We caused our parent to add a vertical scrollbar; bail out!
-		case LayoutBlockBox::LAYOUT_PARENT: return false;
-
-		default: element->OnLayout(); break;
-		}
-
-	return true;
-}
-
-// Formats and positions an element as an inline element.
-bool LayoutEngine::FormatElementInline(LayoutBlockBox* block_context_box, Element* element)
-{
-	RMLUI_ZoneScopedC(0x3F6F6F);
-
-	const Vector2f containing_block = LayoutDetails::GetContainingBlock(block_context_box);
-
-	Box box;
-	LayoutDetails::BuildBox(box, containing_block, element, BoxContext::Inline);
-	LayoutInlineBox* inline_box = block_context_box->AddInlineElement(element, box);
-
-	// Format the element's children.
-	for (int i = 0; i < element->GetNumChildren(); i++)
-	{
-		if (!FormatElement(block_context_box, element->GetChild(i)))
-			return false;
-	}
-
-	inline_box->Close();
-
-	return true;
-}
-
-// Positions an element as a sized inline element, formatting its internal hierarchy as a block element.
-bool LayoutEngine::FormatElementInlineBlock(LayoutBlockBox* block_context_box, Element* element)
-{
-	RMLUI_ZoneScopedC(0x1F2F2F);
-
-	// Format the element separately as a block element, then position it inside our own layout as an inline element.
-	Vector2f containing_block_size = LayoutDetails::GetContainingBlock(block_context_box);
-
-	FormatElement(element, containing_block_size);
-
-	block_context_box->AddInlineElement(element, element->GetBox())->Close();
-
-	return true;
-}
-
-bool LayoutEngine::FormatElementFlex(LayoutBlockBox* block_context_box, Element* element)
-{
-	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;
-}
-
-
-bool LayoutEngine::FormatElementTable(LayoutBlockBox* block_context_box, Element* element_table)
-{
-	const ComputedValues& computed_table = element_table->GetComputedValues();
-
-	const Vector2f containing_block = LayoutDetails::GetContainingBlock(block_context_box);
-
-	// Build the initial box as specified by the table's style, as if it was a normal block element.
-	Box box;
-	LayoutDetails::BuildBox(box, containing_block, element_table, BoxContext::Block);
-
-	Vector2f min_size, max_size;
-	LayoutDetails::GetMinMaxWidth(min_size.x, max_size.x, computed_table, box, containing_block.x);
-	LayoutDetails::GetMinMaxHeight(min_size.y, max_size.y, computed_table, box, containing_block.y);
-	const Vector2f initial_content_size = box.GetSize();
-
-	// Format the table, this may adjust the box content size.
-	const Vector2f table_content_overflow_size = LayoutTable::FormatTable(box, min_size, max_size, element_table);
-
-	const Vector2f final_content_size = box.GetSize();
-	RMLUI_ASSERT(final_content_size.y >= 0);
-
-	if (final_content_size != initial_content_size)
-	{
-		// Perform this step to re-evaluate any auto margins.
-		LayoutDetails::BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element_table, BoxContext::Block, true);
-	}
-
-	// Now that the box is finalized, we can add table as a block element. If we did it earlier, eg. just before formatting the table,
-	// then the table element's offset would not be correct in cases where table size and auto-margins were adjusted.
-	LayoutBlockBox* table_block_context_box = block_context_box->AddBlockElement(element_table, box, final_content_size.y, final_content_size.y);
-	if (!table_block_context_box)
-		return false;
-
-	// Set the inner content size so that any overflow can be caught.
-	table_block_context_box->ExtendInnerContentSize(table_content_overflow_size);
-
-	// If the close failed, it probably means that its parent produced scrollbars.
-	if (table_block_context_box->Close() != LayoutBlockBox::OK)
-		return false;
-
-	return true;
-}
-
-// Executes any special formatting for special elements.
-bool LayoutEngine::FormatElementSpecial(LayoutBlockBox* block_context_box, Element* element)
-{
-	static const String br("br");
-	
-	// Check for a <br> tag.
-	if (element->GetTagName() == br)
-	{
-		block_context_box->AddBreak();
-		element->OnLayout();
-		return true;
-	}
-
-	return false;
-}
-
-} // namespace Rml

+ 0 - 91
Source/Core/LayoutEngine.h

@@ -1,91 +0,0 @@
-/*
- * 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_LAYOUTENGINE_H
-#define RMLUI_CORE_LAYOUTENGINE_H
-
-#include "LayoutBlockBox.h"
-#include "../../Include/RmlUi/Core/Types.h"
-
-namespace Rml {
-
-class Box;
-
-/**
-	@author Robert Curry
- */
-
-class LayoutEngine
-{
-public:
-	/// Formats the contents for a root-level element, usually a document, absolutely positioned, floating, or replaced element. Establishes a new
-	/// block formatting context.
-	/// @param[in] element The element to lay out.
-	/// @param[in] containing_block The size of the containing block.
-	/// @param[in] override_initial_box Optional pointer to a box to override the generated box for the element.
-	/// @param[out] visible_overflow_size Optionally output the overflow size of the element.
-	static void FormatElement(Element* element, Vector2f containing_block, const Box* override_initial_box = nullptr, Vector2f* out_visible_overflow_size = nullptr);
-
-	/// Positions a single element and its children within a block formatting context.
-	/// @param[in] block_context_box The open block box to layout the element in.
-	/// @param[in] element The element to lay out.
-	static bool FormatElement(LayoutBlockBox* block_context_box, Element* element);
-
-	static void* AllocateLayoutChunk(size_t size);
-	static void DeallocateLayoutChunk(void* chunk, size_t size);
-
-private:
-	/// Formats and positions an element as a block element.
-	/// @param[in] block_context_box The open block box to layout the element in.
-	/// @param[in] element The block element.
-	static bool FormatElementBlock(LayoutBlockBox* block_context_box, Element* element);
-	/// Formats and positions an element as an inline element.
-	/// @param[in] block_context_box The open block box to layout the element in.
-	/// @param[in] element The inline element.
-	static bool FormatElementInline(LayoutBlockBox* block_context_box, Element* element);
-	/// Positions an element as a sized inline element, formatting its internal hierarchy as a block element.
-	/// @param[in] block_context_box The open block box to layout the element in.
-	/// @param[in] element The inline-block element.
-	static bool FormatElementInlineBlock(LayoutBlockBox* block_context_box, Element* element);
-	/// Formats and positions a flexbox.
-	/// @param[in] block_context_box The open block box to layout the element in.
-	/// @param[in] element The flex container element.
-	static bool FormatElementFlex(LayoutBlockBox* block_context_box, Element* element);
-	/// Formats and positions a table, including all table-rows and table-cells contained within.
-	/// @param[in] block_context_box The open block box to layout the element in.
-	/// @param[in] element The table element.
-	static bool FormatElementTable(LayoutBlockBox* block_context_box, Element* element);
-	/// Executes any formatting for special elements.
-	/// @param[in] block_context_box The open block box to layout the element in.
-	/// @param[in] element The element to parse.
-	/// @return True if the element was parsed as a special element, false otherwise.
-	static bool FormatElementSpecial(LayoutBlockBox* block_context_box, Element* element);
-};
-
-} // namespace Rml
-#endif

+ 0 - 76
Source/Core/LayoutFlex.h

@@ -1,76 +0,0 @@
-/*
- * 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

+ 0 - 411
Source/Core/LayoutInlineBox.cpp

@@ -1,411 +0,0 @@
-/*
- * 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 "LayoutInlineBox.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/Core.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-#include "../../Include/RmlUi/Core/ElementUtilities.h"
-#include "../../Include/RmlUi/Core/FontEngineInterface.h"
-#include "../../Include/RmlUi/Core/Property.h"
-#include "LayoutBlockBox.h"
-#include "LayoutEngine.h"
-
-namespace Rml {
-
-// Constructs a new inline box for an element.
-LayoutInlineBox::LayoutInlineBox(Element* _element, const Box& _box) : position(0, 0), box(_box)
-{
-	line = nullptr;
-
-	parent = nullptr;
-	element = _element;
-
-	width = 0;
-
-	// If this box has intrinsic dimensions, then we set our height to the total height of the element; otherwise, it is zero height.
-	if (box.GetSize().y > 0)
-	{
-		height = box.GetSize(Box::MARGIN).y;
-		baseline = element->GetBaseline() + box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM);
-	}
-	else
-	{
-		FontFaceHandle font_face = element->GetFontFaceHandle();
-		if (font_face != 0)
-		{
-			height = element->GetLineHeight();
-			baseline = (height - GetFontEngineInterface()->GetLineHeight(font_face)) * 0.5f + GetFontEngineInterface()->GetBaseline(font_face);
-		}
-		else
-		{
-			height = 0;
-			baseline = 0;
-		}
-	}
-
-	vertical_align_property = element->GetComputedValues().vertical_align();
-
-	chained = false;
-	chain = nullptr;
-}
-
-// Constructs a new inline box for a split box.
-LayoutInlineBox::LayoutInlineBox(LayoutInlineBox* _chain) : position(0, 0), box(_chain->GetBox())
-{
-	line = nullptr;
-
-	parent = nullptr;
-	element = _chain->element;
-
-	width = 0;
-	height = _chain->height;
-	baseline = _chain->baseline;
-	vertical_align_property = _chain->vertical_align_property;
-
-	_chain->chain = this;
-	chain = nullptr;
-	chained = true;
-
-	// As we're a split box, our left side is cleared and content set back to (-1, -1).
-	box.SetEdge(Box::PADDING, Box::LEFT, 0);
-	box.SetEdge(Box::BORDER, Box::LEFT, 0);
-	box.SetEdge(Box::MARGIN, Box::LEFT, 0);
-	box.SetContent(Vector2f(-1, -1));
-}
-
-LayoutInlineBox::~LayoutInlineBox()
-{
-}
-
-// Sets the inline box's line.
-void LayoutInlineBox::SetLine(LayoutLineBox* _line)
-{
-	line = _line;
-}
-
-// Sets the inline box's parent.
-void LayoutInlineBox::SetParent(LayoutInlineBox* _parent)
-{
-	parent = _parent;
-	if (parent != nullptr)
-		parent->children.push_back(this);
-}
-
-// Closes the box.
-void LayoutInlineBox::Close()
-{
-	if (chain)
-		chain->Close();
-	else
-	{
-		RMLUI_ASSERT(line != nullptr);
-		line->CloseInlineBox(this);
-	}
-}
-
-// Returns true if this box needs to close on its line.
-bool LayoutInlineBox::CanOverflow() const
-{
-	return box.GetSize().x < 0;
-}
-
-// Returns true if this box's element is the last child of its parent.
-bool LayoutInlineBox::IsLastChild() const
-{
-	Element* parent = element->GetParentNode();
-	if (parent == nullptr)
-		return true;
-
-	return parent->GetLastChild() == element;
-}
-
-// Flows the inline box's content into its parent line.
-UniquePtr<LayoutInlineBox> LayoutInlineBox::FlowContent(bool RMLUI_UNUSED_PARAMETER(first_box), float RMLUI_UNUSED_PARAMETER(available_width), float RMLUI_UNUSED_PARAMETER(right_spacing_width))
-{
-	RMLUI_UNUSED(first_box);
-	RMLUI_UNUSED(available_width);
-	RMLUI_UNUSED(right_spacing_width);
-
-	// If we're representing a sized element, then add our element's width onto our parent's.
-	if (parent != nullptr &&
-		box.GetSize().x > 0)
-		parent->width += box.GetSize(Box::MARGIN).x;
-
-	// Nothing else to do here; static elements will automatically be 'flowed' into their lines when they are placed.
-	return nullptr;
-}
-
-// Computes and sets the vertical position of this element, relative to its parent box.
-void LayoutInlineBox::CalculateBaseline(float& ascender, float& descender)
-{
-	using namespace Style;
-	// We're vertically-aligned with one of the standard types.
-	switch (vertical_align_property.type)
-	{
-		// Aligned with our parent box's baseline, our relative vertical position is set to 0.
-		case VerticalAlign::Baseline:
-		{
-			SetVerticalPosition(0);
-		}
-		break;
-
-		// The middle of this box is aligned with the baseline of its parent's plus half an ex.
-		case VerticalAlign::Middle:
-		{
-			FontFaceHandle parent_font = GetParentFont();
-			int x_height = 0;
-			if (parent_font != 0)
-				x_height = GetFontEngineInterface()->GetXHeight(parent_font) / -2;
-
-			SetVerticalPosition(x_height + (height / 2 - baseline));
-		}
-		break;
-
-		// This box's baseline is offset from its parent's so it is appropriate for rendering subscript.
-		case VerticalAlign::Sub:
-		{
-			FontFaceHandle parent_font = GetParentFont();
-			if (parent_font == 0)
-				SetVerticalPosition(0);
-			else
-				SetVerticalPosition(float(GetFontEngineInterface()->GetLineHeight(parent_font)) * 0.2f);
-		}
-		break;
-
-		// This box's baseline is offset from its parent's so it is appropriate for rendering superscript.
-		case VerticalAlign::Super:
-		{
-			FontFaceHandle parent_font = GetParentFont();
-			if (parent_font == 0)
-				SetVerticalPosition(0);
-			else
-				SetVerticalPosition(float(-1 * GetFontEngineInterface()->GetLineHeight(parent_font)) * 0.4f);
-		}
-		break;
-
-		// The top of this box is aligned to the top of its parent's font.
-		case VerticalAlign::TextTop:
-		{
-			FontFaceHandle parent_font = GetParentFont();
-			if (parent_font == 0)
-				SetVerticalPosition(0);
-			else
-				SetVerticalPosition((height - baseline) - (GetFontEngineInterface()->GetLineHeight(parent_font) - GetFontEngineInterface()->GetBaseline(parent_font)));
-		}
-		break;
-
-		// The bottom of this box is aligned to the bottom of its parent's font (not the baseline).
-		case VerticalAlign::TextBottom:
-		{
-			FontFaceHandle parent_font = GetParentFont();
-			if (parent_font == 0)
-				SetVerticalPosition(0);
-			else
-				SetVerticalPosition(GetFontEngineInterface()->GetBaseline(parent_font) - baseline);
-		}
-		break;
-
-		// This box is aligned with the line box, not an inline box, so we can't position it yet.
-		case VerticalAlign::Top:
-		case VerticalAlign::Bottom: break;
-
-		// The baseline of this box is offset by a fixed amount from its parent's baseline.
-		case VerticalAlign::Length: SetVerticalPosition(-1.f * vertical_align_property.value); break;
-		}
-
-	// Set the ascender and descender relative to this element. If we're an unsized element (span, em, etc) then we
-	// have no dimensions ourselves.
-	if (box.GetSize() == Vector2f(-1, -1))
-	{
-		ascender = 0;
-		descender = 0;
-	}
-	else
-	{
-		ascender = height - baseline;
-		descender = height - ascender;
-	}
-
-	for (size_t i = 0; i < children.size(); ++i)
-	{
-		// Don't include any of our children that are aligned relative to the line box; the line box treats them
-		// separately.
-		if (children[i]->GetVerticalAlignProperty().type != Style::VerticalAlign::Top &&
-			children[i]->GetVerticalAlignProperty().type != Style::VerticalAlign::Bottom)
-		{
-			float child_ascender, child_descender;
-			children[i]->CalculateBaseline(child_ascender, child_descender);
-
-			ascender = Math::Max(ascender, child_ascender - children[i]->GetPosition().y);
-			descender = Math::Max(descender, child_descender + children[i]->GetPosition().y);
-		}
-	}
-}
-
-// Offsets the baseline of this box, and all of its children, by the ascender of the parent line box.
-void LayoutInlineBox::OffsetBaseline(float ascender)
-{
-	for (size_t i = 0; i < children.size(); ++i)
-	{
-		// Don't offset any of our children that are aligned relative to the line box; the line box will take care of
-		// them separately.
-		if (children[i]->GetVerticalAlignProperty().type != Style::VerticalAlign::Top &&
-			children[i]->GetVerticalAlignProperty().type != Style::VerticalAlign::Bottom)
-			children[i]->OffsetBaseline(ascender + position.y);
-	}
-
-	position.y += (ascender - (height - baseline));
-}
-
-// Returns the inline box's offset from its parent's content area.
-Vector2f LayoutInlineBox::GetPosition() const
-{
-	return position;
-}
-
-// Sets the inline box's relative horizontal offset from its parent's content area.
-void LayoutInlineBox::SetHorizontalPosition(float _position)
-{
-	position.x = _position;
-}
-
-// Sets the inline box's relative vertical offset from its parent's content area.
-void LayoutInlineBox::SetVerticalPosition(float _position)
-{
-	position.y = _position;
-}
-
-void LayoutInlineBox::PositionElement()
-{
-	if (box.GetSize() == Vector2f(-1, -1))
-	{
-		// If this unsised element has any top margins, border or padding, then shift the position up so the borders
-		// and background will render in the right place.
-		position.y -= box.GetCumulativeEdge(Box::CONTENT, Box::TOP);
-	}
-	// Otherwise; we're a sized element (replaced or inline-block), so we need to offset our element's vertical
-	// position by our top margin (as the origin of an element is the top-left of the border, not the margin).
-	else
-		position.y += box.GetEdge(Box::MARGIN, Box::TOP);
-
-	if (!chained)
-		element->SetOffset(line->GetRelativePosition() + position, line->GetBlockBox()->GetOffsetParent()->GetElement());
-}
-
-// Sizes the inline box's element.
-void LayoutInlineBox::SizeElement(bool split)
-{
-	// Resize the box for an unsized inline element.
-	if (box.GetSize() == Vector2f(-1, -1))
-	{
-		box.SetContent(Vector2f(width, element->GetLineHeight()));
-		if (parent != nullptr)
-			parent->width += width;
-	}
-
-	Box element_box = box;
-	if (split)
-	{
-		element_box.SetEdge(Box::MARGIN, Box::RIGHT, 0);
-		element_box.SetEdge(Box::BORDER, Box::RIGHT, 0);
-		element_box.SetEdge(Box::PADDING, Box::RIGHT, 0);
-	}
-
-	// The elements of a chained box have already had their positions set by the first link.
-	if (chained)
-	{
-		const Vector2f box_offset = (line->GetPosition() + position) - element->GetRelativeOffset(Box::BORDER);
-		element->AddBox(element_box, box_offset);
-
-		if (chain != nullptr)
-			element->OnLayout();
-	}
-	else
-	{
-		element->SetBox(element_box);
-		element->OnLayout();
-	}
-}
-
-// Returns the vertical align property of the box's element.
-Style::VerticalAlign LayoutInlineBox::GetVerticalAlignProperty() const
-{
-	return vertical_align_property;
-}
-
-// Returns the inline box's element.
-Element* LayoutInlineBox::GetElement()
-{
-	return element;
-}
-
-// Returns the inline box's parent.
-LayoutInlineBox* LayoutInlineBox::GetParent()
-{
-	return parent;
-}
-
-// Returns the inline box's dimension box.
-const Box& LayoutInlineBox::GetBox() const
-{
-	return box;
-}
-
-// Returns the height of the inline box.
-float LayoutInlineBox::GetHeight() const
-{
-	return height;
-}
-
-// Returns the baseline of the inline box.
-float LayoutInlineBox::GetBaseline() const
-{
-	return baseline;
-}
-
-void* LayoutInlineBox::operator new(size_t size)
-{
-	return LayoutEngine::AllocateLayoutChunk(size);
-}
-
-void LayoutInlineBox::operator delete(void* chunk, size_t size)
-{
-	LayoutEngine::DeallocateLayoutChunk(chunk, size);
-}
-
-// Returns our parent box's font face handle.
-FontFaceHandle LayoutInlineBox::GetParentFont() const
-{
-	if (parent == nullptr)
-		return line->GetBlockBox()->GetParent()->GetElement()->GetFontFaceHandle();
-	else
-		return parent->GetElement()->GetFontFaceHandle();
-}
-
-} // namespace Rml

+ 0 - 176
Source/Core/LayoutInlineBox.h

@@ -1,176 +0,0 @@
-/*
- * 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_LAYOUTINLINEBOX_H
-#define RMLUI_CORE_LAYOUTINLINEBOX_H
-
-#include "../../Include/RmlUi/Core/Box.h"
-#include "../../Include/RmlUi/Core/StyleTypes.h"
-
-namespace Rml {
-
-class Element;
-class ElementText;
-class FontFaceHandleDefault;
-class LayoutBlockBox;
-class LayoutLineBox;
-
-/**
-	@author Peter Curry
- */
-
-class LayoutInlineBox
-{
-public:
-	/// Constructs a new inline box for an element.
-	/// @param element[in] The element this inline box is flowing.
-	/// @param box[in] The extents of the inline box's element.
-	LayoutInlineBox(Element* element, const Box& box);
-	/// Constructs a new inline box for a split box.
-	/// @param chain[in] The box that has overflowed into us.
-	LayoutInlineBox(LayoutInlineBox* chain);
-	virtual ~LayoutInlineBox();
-
-	/// Sets the inline box's line.
-	/// @param line[in] The line this inline box resides in.
-	void SetLine(LayoutLineBox* line);
-	/// Sets the inline box's parent.
-	/// @param parent[in] The parent this inline box resides in.
-	void SetParent(LayoutInlineBox* parent);
-
-	/// Closes the box.
-	void Close();
-
-	/// Flows the inline box's content into its parent line.
-	/// @param[in] first_box True if this box is the first box containing content to be flowed into this line.
-	/// @param[in] available_width The width available for flowing this box's content. This is measured from the left side of this box's content area.
-	/// @param[in] right_spacing_width The width of the spacing that must be left on the right of the element if no overflow occurs. If overflow occurs, then the entire width can be used.
-	/// @return The overflow box containing any content that spilled over from the flow. This must be nullptr if no overflow occured.
-	virtual UniquePtr<LayoutInlineBox> FlowContent(bool first_box, float available_width, float right_spacing_width);
-
-	/// Computes and sets the vertical position of this element, relative to its parent inline box (or block box,
-	/// for an un-nested inline box).
-	/// @param ascender[out] The maximum ascender of this inline box and all of its children.
-	/// @param descender[out] The maximum descender of this inline box and all of its children.
-	virtual void CalculateBaseline(float& ascender, float& descender);
-	/// Offsets the baseline of this box, and all of its children, by the ascender of the parent line box.
-	/// @param ascender[in] The ascender of the line box.
-	virtual void OffsetBaseline(float ascender);
-
-	/// Returns true if this box is capable of overflowing, or if it must be rendered on a single line.
-	/// @return True if this box can overflow, false otherwise.
-	virtual bool CanOverflow() const;
-	/// Returns true if this box's element is the last child of its parent.
-	/// @param True if this box is a last child.
-	bool IsLastChild() const;
-
-	/// Returns the inline box's offset from its line.
-	/// @return The box's offset from its line.
-	Vector2f GetPosition() const;
-
-	/// Sets the inline box's horizontal offset from its parent's content area.
-	/// @param position[in] The box's horizontal offset.
-	void SetHorizontalPosition(float position);
-	/// Sets the inline box's vertical offset from its parent's content area.
-	/// @param position[in] The box's vertical offset.
-	void SetVerticalPosition(float position);
-
-	/// Positions the inline box's element.
-	virtual void PositionElement();
-	/// Sizes the inline box's element.
-	/// @param split[in] True if this box is split, false otherwise.
-	virtual void SizeElement(bool split);
-
-	/// Returns the vertical align property of the box's element.
-	/// @return the vertical align property, or -1 if it is set to a numerical value.
-	Style::VerticalAlign GetVerticalAlignProperty() const;
-
-	/// Returns the inline box's element.
-	/// @return The inline box's element.
-	Element* GetElement();
-
-	/// Returns the inline box's parent.
-	/// @param The parent of this inline box. This will be nullptr for a root-level inline box (ie, one that has a block element has a parent in the true hierarchy).
-	LayoutInlineBox* GetParent();
-
-	/// Returns the inline box's dimension box.
-	/// @return The inline box's dimension box.
-	const Box& GetBox() const;
-	/// Returns the height of the inline box. This is separate from the the box, as different types of inline
-	/// elements generate different line heights. The possible types are:
-	///  * replaced elements (or inline-block elements), which use their entire box (including margins) as their
-	///    height
-	///  * non-replaced elements, which use the maximum line-height of their children
-	///  * text elements, which use their line-height
-	float GetHeight() const;
-	/// Returns the baseline of the inline box.
-	/// @return The box's baseline.
-	float GetBaseline() const;
-
-	void* operator new(size_t size);
-	void operator delete(void* chunk, size_t size);
-
-protected:
-	/// Returns our parent box's font face handle.
-	/// @return The font face handle of our parent box.
-	FontFaceHandle GetParentFont() const;
-
-	// The box's element.
-	Element* element;
-
-	// The line box's offset relative to its parent block box.
-	Vector2f position;
-	// The element's inline box.
-	Box box;
-	// The inline box's width; note that this is stored separately from the box dimensions. It is only used by
-	// nesting inline boxes, such as HTML spans containing text.
-	float width;
-	// The inline box's height; note that this is stored separately from the box dimensions, as inline elements
-	// don't necessarily have identical relationships between their box dimensions and line height.
-	float height;
-
-	// The value of this box's element's vertical-align property.
-	Style::VerticalAlign vertical_align_property;
-	// The baseline of the inline element.
-	float baseline;
-
-	// The inline box's parent; this will be nullptr if we're not a nested inline element.
-	LayoutInlineBox* parent;
-	// This inline box's line.
-	LayoutLineBox* line;
-
-	Vector< LayoutInlineBox* > children;
-
-	// The next link in our element's chain of inline boxes.
-	LayoutInlineBox* chain;
-	// True if we're a link in a chain of inline boxes flowing from previous lines.
-	bool chained;
-};
-
-} // namespace Rml
-#endif

+ 0 - 217
Source/Core/LayoutInlineBoxText.cpp

@@ -1,217 +0,0 @@
-/*
- * 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 "LayoutInlineBoxText.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/Core.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-#include "../../Include/RmlUi/Core/ElementUtilities.h"
-#include "../../Include/RmlUi/Core/FontEngineInterface.h"
-#include "../../Include/RmlUi/Core/Log.h"
-#include "../../Include/RmlUi/Core/Profiling.h"
-#include "../../Include/RmlUi/Core/Property.h"
-#include "ComputeProperty.h"
-#include "LayoutEngine.h"
-#include "LayoutLineBox.h"
-
-namespace Rml {
-
-String FontFaceDescription(const String& font_family, Style::FontStyle style, Style::FontWeight weight)
-{
-	String font_attributes;
-
-	if (style == Style::FontStyle::Italic)
-		font_attributes += "italic, ";
-	if (weight == Style::FontWeight::Bold)
-		font_attributes += "bold, ";
-	else if (weight != Style::FontWeight::Auto && weight != Style::FontWeight::Normal)
-		font_attributes += "weight=" + ToString((int)weight) + ", ";
-
-	if (font_attributes.empty())
-		font_attributes = "regular";
-	else
-		font_attributes.resize(font_attributes.size() - 2);
-
-	return CreateString(font_attributes.size() + font_family.size() + 8, "'%s' [%s]", font_family.c_str(), font_attributes.c_str());
-}
-
-LayoutInlineBoxText::LayoutInlineBoxText(ElementText* element, int _line_begin) : LayoutInlineBox(static_cast<Element*>(element), Box())
-{
-	line_begin = _line_begin;
-
-	// Build the box to represent the dimensions of the first word.
-	BuildWordBox();
-}
-
-LayoutInlineBoxText::~LayoutInlineBoxText()
-{
-}
-
-// Returns true if this box is capable of overflowing, or if it must be rendered on a single line.
-bool LayoutInlineBoxText::CanOverflow() const
-{
-	return line_segmented;
-}
-
-// Flows the inline box's content into its parent line.
-UniquePtr<LayoutInlineBox> LayoutInlineBoxText::FlowContent(bool first_box, float available_width, float right_spacing_width)
-{
-	ElementText* text_element = GetTextElement();
-	RMLUI_ASSERT(text_element != nullptr);
-
-	int line_length;
-	float line_width;
-	bool overflow = !text_element->GenerateLine(line_contents, line_length, line_width, line_begin, available_width, right_spacing_width, first_box, true);
-
-	Vector2f content_area;
-	content_area.x = line_width;
-	content_area.y = box.GetSize().y;
-	box.SetContent(content_area);
-
-	// Call the base-class's FlowContent() to increment the width of our parent's box.
-	LayoutInlineBox::FlowContent(first_box, available_width, right_spacing_width);
-
-	if (overflow)
-		return MakeUnique<LayoutInlineBoxText>(GetTextElement(), line_begin + line_length);
-
-	return nullptr;
-}
-
-// Computes and sets the vertical position of this element, relative to its parent inline box (or block box, for an un-nested inline box).
-void LayoutInlineBoxText::CalculateBaseline(float& ascender, float& descender)
-{
-	ascender = height - baseline;
-	descender = height - ascender;
-}
-
-// Offsets the baseline of this box, and all of its children, by the ascender of the parent line box.
-void LayoutInlineBoxText::OffsetBaseline(float ascender)
-{
-	// Offset by the ascender.
-	position.y += (ascender - (height - baseline));
-
-	// Calculate the leading (the difference between font height and line height).
-	float leading = 0;
-
-	FontFaceHandle font_face_handle = element->GetFontFaceHandle();
-	if (font_face_handle != 0)
-		leading = height - GetFontEngineInterface()->GetLineHeight(font_face_handle);
-
-	// Offset by the half-leading.
-	position.y += leading * 0.5f;
-}
-
-// Positions the inline box's element.
-void LayoutInlineBoxText::PositionElement()
-{
-	if (line_begin == 0)
-	{
-		LayoutInlineBox::PositionElement();
-
-		GetTextElement()->ClearLines();
-		GetTextElement()->AddLine(Vector2f(0, 0), line_contents);
-	}
-	else
-	{
-		GetTextElement()->AddLine(line->GetRelativePosition() + position - element->GetRelativeOffset(Box::BORDER), line_contents);
-	}
-}
-
-// Sizes the inline box's element.
-void LayoutInlineBoxText::SizeElement(bool RMLUI_UNUSED_PARAMETER(split))
-{
-	RMLUI_UNUSED(split);
-}
-
-void* LayoutInlineBoxText::operator new(size_t size)
-{
-	return LayoutEngine::AllocateLayoutChunk(size);
-}
-
-void LayoutInlineBoxText::operator delete(void* chunk, size_t size)
-{
-	LayoutEngine::DeallocateLayoutChunk(chunk, size);
-}
-
-// Returns the box's element as a text element.
-ElementText* LayoutInlineBoxText::GetTextElement()
-{
-	RMLUI_ASSERT(rmlui_dynamic_cast<ElementText*>(element));
-
-	return static_cast< ElementText* >(element);
-}
-
-// Builds a box for the first word of the element.
-void LayoutInlineBoxText::BuildWordBox()
-{
-	RMLUI_ZoneScoped;
-
-	ElementText* text_element = GetTextElement();
-	RMLUI_ASSERT(text_element != nullptr);
-
-	FontFaceHandle font_face_handle = text_element->GetFontFaceHandle();
-	if (font_face_handle == 0)
-	{
-		height = 0;
-		baseline = 0;
-
-		const ComputedValues& computed = text_element->GetComputedValues();
-		const String font_family_property = computed.font_family();
-
-		if (font_family_property.empty())
-		{
-			Log::Message(
-				Log::LT_WARNING,
-				"No font face defined. Missing 'font-family' property, please add it to your RCSS. On element %s",
-				text_element->GetAddress().c_str()
-			);
-		}
-		else
-		{
-			const String font_face_description = FontFaceDescription(font_family_property, computed.font_style(), computed.font_weight());
-
-			Log::Message(
-				Log::LT_WARNING,
-				"No font face defined. Ensure (1) that Context::Update is run after new elements are constructed, before Context::Render, "
-				"and (2) that the specified font face %s has been successfully loaded. "
-				"Please see previous log messages for all successfully loaded fonts. On element %s",
-				font_face_description.c_str(),
-				text_element->GetAddress().c_str()
-			);
-		}
-
-		return;
-	}
-
-	Vector2f content_area;
-	line_segmented = !text_element->GenerateToken(content_area.x, line_begin);
-	content_area.y = text_element->GetLineHeight();
-	box.SetContent(content_area);
-}
-
-} // namespace Rml

+ 0 - 99
Source/Core/LayoutInlineBoxText.h

@@ -1,99 +0,0 @@
-/*
- * 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_LAYOUTINLINEBOXTEXT_H
-#define RMLUI_CORE_LAYOUTINLINEBOXTEXT_H
-
-#include "LayoutInlineBox.h"
-
-namespace Rml {
-
-/**
-	@author Peter Curry
- */
-
-class LayoutInlineBoxText : public LayoutInlineBox
-{
-public:
-	/// Constructs a new inline box for a text element.
-	/// @param[in] element The text element this inline box is flowing.
-	/// @param[in] line_begin The index of the first character of the element's string this text box will render.
-	LayoutInlineBoxText(ElementText* element, int line_begin = 0);
-	virtual ~LayoutInlineBoxText();
-
-	/// Returns true if this box is capable of overflowing, or if it must be rendered on a single line.
-	/// @return True if this box can overflow, false otherwise.
-	bool CanOverflow() const override;
-
-	/// Flows the inline box's content into its parent line.
-	/// @param[in] first_box True if this box is the first box containing content to be flowed into this line.
-	/// @param available_width[in] The width available for flowing this box's content. This is measured from the left side of this box's content area.
-	/// @param right_spacing_width[in] The width of the spacing that must be left on the right of the element if no overflow occurs. If overflow occurs, then the entire width can be used.
-	/// @return The overflow box containing any content that spilled over from the flow. This must be nullptr if no overflow occured.
-	UniquePtr<LayoutInlineBox> FlowContent(bool first_box, float available_width, float right_spacing_width) override;
-
-	/// Computes and sets the vertical position of this element, relative to its parent inline box (or block box,
-	/// for an un-nested inline box).
-	/// @param ascender[out] The maximum ascender of this inline box and all of its children.
-	/// @param descender[out] The maximum descender of this inline box and all of its children.
-	void CalculateBaseline(float& ascender, float& descender) override;
-	/// Offsets the baseline of this box, and all of its children, by the ascender of the parent line box.
-	/// @param ascender[in] The ascender of the line box.
-	void OffsetBaseline(float ascender) override;
-
-	/// Positions the inline box's element.
-	void PositionElement() override;
-	/// Sizes the inline box's element.
-	void SizeElement(bool split) override;
-
-	void* operator new(size_t size);
-	void operator delete(void* chunk, size_t size);
-
-private:
-	/// Returns the box's element as a text element.
-	/// @return The box's element cast to a text element.
-	ElementText* GetTextElement();
-
-	/// Builds a box for the first word of the element.
-	void BuildWordBox();
-
-	// The index of the first character of this line.
-	int line_begin;
-	// The contents on this line.
-	String line_contents;
-
-	// True if this line can be segmented into parts, false if it consists of only a single word.
-	bool line_segmented;
-};
-
-
-String FontFaceDescription(const String& font_family, Style::FontStyle style, Style::FontWeight weight);
-
-
-} // namespace Rml
-#endif

+ 0 - 408
Source/Core/LayoutLineBox.cpp

@@ -1,408 +0,0 @@
-/*
- * 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 "LayoutLineBox.h"
-#include "../../Include/RmlUi/Core/ComputedValues.h"
-#include "../../Include/RmlUi/Core/ElementText.h"
-#include "../../Include/RmlUi/Core/ElementUtilities.h"
-#include "../../Include/RmlUi/Core/Profiling.h"
-#include "../../Include/RmlUi/Core/Property.h"
-#include "LayoutBlockBox.h"
-#include "LayoutEngine.h"
-#include "LayoutInlineBoxText.h"
-#include <stack>
-
-namespace Rml {
-
-static float GetSpacing(const Box& box, Box::Edge edge)
-{
-	return box.GetEdge(Box::PADDING, edge) +
-		   box.GetEdge(Box::BORDER, edge) +
-		   box.GetEdge(Box::MARGIN, edge);
-}
-
-LayoutLineBox::LayoutLineBox(LayoutBlockBox* _parent) : position(-1, -1), dimensions(-1, -1)
-{
-	parent = _parent;
-
-	box_cursor = 0;
-	open_inline_box = nullptr;
-
-	position_set = false;
-	wrap_content = false;
-}
-
-LayoutLineBox::~LayoutLineBox()
-{
-}
-
-// Closes the line box, positioning all inline elements within it.
-LayoutInlineBox* LayoutLineBox::Close(UniquePtr<LayoutInlineBox> overflow)
-{
-	RMLUI_ZoneScoped;
-
-	// If we haven't positioned this line yet, and it has elements in it, then this is a great opportunity to do so.
-	if (!position_set &&
-		!inline_boxes.empty())
-	{
-		parent->PositionLineBox(position, dimensions.x, wrap_content, Vector2f(0, 0));
-		dimensions.y = 0;
-
-		position_set = true;
-	}
-	// If the line has been positioned and our content is greater than our original size (for example, if we aren't
-	// wrapping or had to render a very long word), then we push our dimensions out to compensate.
-	else
-		dimensions.x = Math::Max(dimensions.x, box_cursor);
-
-	// Now we calculate the baselines of each of our inline boxes relative to their parent box's baseline; either us,
-	// or another of our inline boxes. The maximum distance each element is above and below our baseline is calculated
-	// from that, and therefore our height.
-	float ascender = 0;
-	float descender = 0;
-	float minimum_height = 0;
-
-	for (size_t i = 0; i < inline_boxes.size(); ++i)
-	{
-		LayoutInlineBox* inline_box = inline_boxes[i].get();
-
-		// Check if we've got an element aligned to the line box rather than a baseline.
-		if (inline_box->GetVerticalAlignProperty().type == Style::VerticalAlign::Top ||
-			inline_box->GetVerticalAlignProperty().type == Style::VerticalAlign::Bottom)
-		{
-			// Get this element to calculate the baseline offsets of its children; it can't calculate its own baseline
-			// because we don't know the height of the line box yet. We don't actually care about its ascender or
-			// descender either, just its height.
-			float box_ascender, box_descender;
-			inline_box->CalculateBaseline(box_ascender, box_descender);
-
-			minimum_height = Math::Max(minimum_height, inline_box->GetHeight());
-		}
-		// Otherwise, we have an element anchored to a baseline, so we can fetch its ascender and descender relative
-		// to our baseline.
-		else if (inline_box->GetParent() == nullptr)
-		{
-			float box_ascender, box_descender;
-			inline_box->CalculateBaseline(box_ascender, box_descender);
-
-			ascender = Math::Max(ascender, box_ascender - inline_box->GetPosition().y);
-			descender = Math::Max(descender, box_descender + inline_box->GetPosition().y);
-		}
-	}
-
-	// We've now got the maximum ascender and descender, we can calculate the dimensions of the line box.
-	dimensions.y = Math::Max(minimum_height, ascender + descender);
-	// And from that, we can now set the final baseline of each box.
-	for (size_t i = 0; i < inline_boxes.size(); ++i)
-	{
-		LayoutInlineBox* inline_box = inline_boxes[i].get();
-
-		// Check again if this element is aligned to the line box. We don't need to worry about offsetting an element
-		// tied to the top of the line box, as its position will always stay at exactly 0.
-		if (inline_box->GetVerticalAlignProperty().type == Style::VerticalAlign::Top||
-			inline_box->GetVerticalAlignProperty().type == Style::VerticalAlign::Bottom)
-		{
-			if (inline_box->GetVerticalAlignProperty().type == Style::VerticalAlign::Top)
-				inline_box->OffsetBaseline(inline_box->GetHeight() - inline_box->GetBaseline());
-			else
-				inline_box->OffsetBaseline(dimensions.y - inline_box->GetBaseline());
-		}
-		// Otherwise, this element is tied to a baseline.
-		else if (inline_box->GetParent() == nullptr)
-			inline_box->OffsetBaseline(ascender);
-	}
-
-	// Position all the boxes horizontally in the line. We only need to reposition the elements if they're set to
-	// centre or right; the element are already placed left-aligned, and justification occurs at the text level.
-	Style::TextAlign text_align_property = parent->GetParent()->GetElement()->GetComputedValues().text_align();
-	if (text_align_property == Style::TextAlign::Center ||
-		text_align_property == Style::TextAlign::Right)
-	{
-		float element_offset = 0;
-		switch (text_align_property)
-		{
-			case Style::TextAlign::Center:  element_offset = (dimensions.x - box_cursor) * 0.5f; break;
-			case Style::TextAlign::Right:   element_offset = (dimensions.x - box_cursor); break;
-			default: break;
-		}
-
-		if (element_offset != 0)
-		{
-			for (size_t i = 0; i < inline_boxes.size(); i++)
-				inline_boxes[i]->SetHorizontalPosition(inline_boxes[i]->GetPosition().x + element_offset);
-		}
-	}
-
-	// Get each line box to set the position of their element, relative to their parents.
-	for (int i = (int) inline_boxes.size() - 1; i >= 0; --i)
-	{
-		inline_boxes[i]->PositionElement();
-
-		// Check if this inline box is part of the open box chain.
-		bool inline_box_open = false;
-		LayoutInlineBox* open_box = open_inline_box;
-		while (open_box != nullptr &&
-			   !inline_box_open)
-		{
-			if (inline_boxes[i].get() == open_box)
-				inline_box_open = true;
-
-			open_box = open_box->GetParent();
-		}
-
-		inline_boxes[i]->SizeElement(inline_box_open);
-	}
-
-	return parent->CloseLineBox(this, std::move(overflow), open_inline_box);
-}
-
-// Closes one of the line box's inline boxes.
-void LayoutLineBox::CloseInlineBox(LayoutInlineBox* inline_box)
-{
-	RMLUI_ASSERT(open_inline_box == inline_box);
-
-	open_inline_box = inline_box->GetParent();
-	box_cursor += GetSpacing(inline_box->GetBox(), Box::RIGHT);
-}
-
-// Attempts to add a new element to this line box.
-LayoutInlineBox* LayoutLineBox::AddElement(Element* element, const Box& box)
-{
-	RMLUI_ZoneScoped;
-
-	ElementText* element_text = rmlui_dynamic_cast<ElementText*>(element);
-
-	if (element_text)
-		return AddBox(MakeUnique<LayoutInlineBoxText>(element_text));
-	else
-		return AddBox(MakeUnique<LayoutInlineBox>(element, box));
-}
-
-// Attempts to add a new inline box to this line.
-LayoutInlineBox* LayoutLineBox::AddBox(UniquePtr<LayoutInlineBox> box_ptr)
-{
-	RMLUI_ZoneScoped;
-
-	// Set to true if we're flowing the first box (with content) on the line.
-	bool first_box = false;
-	// The spacing this element must leave on the right of the line, to account not only for its margins and padding,
-	// but also for its parents which will close immediately after it.
-	float right_spacing;
-
-	// If this line is unplaced, then this is the first inline box; if it is sized, then we can place and size this
-	// line.
-	if (!position_set)
-	{
-		// Add the new box to the list of boxes in the line box. As this line box has not been placed, we don't have to
-		// check if it can fit yet.
-		LayoutInlineBox* box = AppendBox(std::move(box_ptr));
-
-		// If the new box has a physical prescence, then we must place this line once we've figured out how wide it has to
-		// be.
-		if (box->GetBox().GetSize().x >= 0)
-		{
-			// Calculate the dimensions for the box we need to fit.
-			Vector2f minimum_dimensions = box->GetBox().GetSize();
-
-			// Add the width of any empty, already closed tags, or still opened spaced tags.
-			minimum_dimensions.x += box_cursor;
-
-			// Calculate the right spacing for the element.
-			right_spacing = GetSpacing(box->GetBox(), Box::RIGHT);
-			// Add the right spacing for any ancestor elements that must close immediately after it.
-			LayoutInlineBox* closing_box = box;
-			while (closing_box && closing_box->IsLastChild())
-			{
-				closing_box = closing_box->GetParent();
-				if (closing_box)
-					right_spacing += GetSpacing(closing_box->GetBox(), Box::RIGHT);
-			}
-
-			if (!box->CanOverflow())
-				minimum_dimensions.x += right_spacing;
-
-			parent->PositionLineBox(position, dimensions.x, wrap_content, minimum_dimensions);
-			dimensions.y = minimum_dimensions.y;
-
-			first_box = true;
-			position_set = true;
-		}
-		else
-			return box;
-	}
-
-	// This line has already been placed and sized, so we'll check if we can fit this new inline box on the line.
-	else
-	{
-		LayoutInlineBox* box = box_ptr.get();
-
-		// Build up the spacing required on the right side of this element. This consists of the right spacing on the
-		// new element, and the right spacing on all parent element that will close next.
-		right_spacing = GetSpacing(box->GetBox(), Box::RIGHT);
-		if (open_inline_box != nullptr &&
-			box->IsLastChild())
-		{
-			LayoutInlineBox* closing_box = open_inline_box;
-			while (closing_box != nullptr &&
-				   closing_box->IsLastChild())
-			{
-				closing_box = closing_box->GetParent();
-				if (closing_box != nullptr)
-					right_spacing += GetSpacing(closing_box->GetBox(), Box::RIGHT);
-			}
-		}
-
-		// Determine the inline box's spacing requirements (before we get onto it's actual content width).
-		float element_width = box->GetBox().GetPosition(Box::CONTENT).x;
-		if (!box->CanOverflow())
-			element_width += right_spacing;
-
-		// Add on the box's content area (if it has content).
-		if (box->GetBox().GetSize().x >= 0)
-			element_width += box->GetBox().GetSize().x;
-
-		if (wrap_content &&
-			box_cursor + element_width > dimensions.x)
-		{
-			// We can't fit the new inline element into this box! So we'll close this line box, and send the inline box
-			// onto the next line.
-			return Close(std::move(box_ptr));
-		}
-		else
-		{
-			// We can fit the new inline element into this box.
-			AppendBox(std::move(box_ptr));
-		}
-	}
-
-	float available_width = -1;
-	if (wrap_content)
-		available_width = Math::RoundUpFloat(dimensions.x - (open_inline_box->GetPosition().x + open_inline_box->GetBox().GetPosition(Box::CONTENT).x));
-
-	// Flow the box's content into the line.
-	UniquePtr<LayoutInlineBox> overflow_box = open_inline_box->FlowContent(first_box, available_width, right_spacing);
-	box_cursor += open_inline_box->GetBox().GetSize().x;
-
-	// If our box overflowed, then we'll close this line (as no more content will fit onto it) and tell our block box
-	// to make a new line.
-	if (overflow_box)
-	{
-		open_inline_box = open_inline_box->GetParent();
-		return Close(std::move(overflow_box));
-	}
-
-	return open_inline_box;
-}
-
-// Adds an inline box as a chained hierarchy overflowing to this line.
-void LayoutLineBox::AddChainedBox(LayoutInlineBox* chained_box)
-{
-	Stack< LayoutInlineBox* > hierarchy;
-	LayoutInlineBox* chain = chained_box;
-	while (chain != nullptr)
-	{
-		hierarchy.push(chain);
-		chain = chain->GetParent();
-	}
-
-	while (!hierarchy.empty())
-	{
-		AddBox(MakeUnique<LayoutInlineBox>(hierarchy.top()));
-		hierarchy.pop();
-	}
-}
-
-// Returns the position of the line box, relative to its parent's block box's content area.
-Vector2f LayoutLineBox::GetPosition() const
-{
-	return position;
-}
-
-// Returns the position of the line box, relative to its parent's block box's offset parent.
-Vector2f LayoutLineBox::GetRelativePosition() const
-{
-	return position - (parent->GetOffsetParent()->GetPosition() - parent->GetOffsetRoot()->GetPosition());
-}
-
-// Returns the dimensions of the line box.
-Vector2f LayoutLineBox::GetDimensions() const
-{
-	return dimensions;
-}
-
-// Returns the line box's open inline box.
-LayoutInlineBox* LayoutLineBox::GetOpenInlineBox()
-{
-	return open_inline_box;
-}
-
-// Returns the line's containing block box.
-LayoutBlockBox* LayoutLineBox::GetBlockBox()
-{
-	return parent;
-}
-
-float LayoutLineBox::GetBoxCursor() const 
-{
-	return box_cursor; 
-}
-
-bool LayoutLineBox::GetBaselineOfLastLine(float& baseline) const
-{
-	if (inline_boxes.empty())
-		return false;
-	baseline = inline_boxes.back()->GetBaseline();
-	return true;
-}
-
-void* LayoutLineBox::operator new(size_t size)
-{
-	return LayoutEngine::AllocateLayoutChunk(size);
-}
-
-void LayoutLineBox::operator delete(void* chunk, size_t size)
-{
-	LayoutEngine::DeallocateLayoutChunk(chunk, size);
-}
-
-// Appends an inline box to the end of the line box's list of inline boxes.
-LayoutInlineBox* LayoutLineBox::AppendBox(UniquePtr<LayoutInlineBox> box_ptr)
-{
-	LayoutInlineBox* box = box_ptr.get();
-	inline_boxes.push_back(std::move(box_ptr));
-
-	box->SetParent(open_inline_box);
-	box->SetLine(this);
-	box->SetHorizontalPosition(box_cursor + box->GetBox().GetEdge(Box::MARGIN, Box::LEFT));
-	box_cursor += GetSpacing(box->GetBox(), Box::LEFT);
-
-	open_inline_box = box;
-	return box;
-}
-
-} // namespace Rml

+ 0 - 125
Source/Core/LayoutLineBox.h

@@ -1,125 +0,0 @@
-/*
- * 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_LAYOUTLINEBOX_H
-#define RMLUI_CORE_LAYOUTLINEBOX_H
-
-#include "LayoutInlineBox.h"
-
-namespace Rml {
-
-class LayoutBlockBox;
-
-/**
-	@author Peter Curry
- */
-
-class LayoutLineBox
-{
-public:
-	LayoutLineBox(LayoutBlockBox* parent);
-	~LayoutLineBox();
-
-	/// Closes the line box, positioning all inline elements within it.
-	/// @param overflow[in] The overflow box from a split inline box that caused this line to close. Leave this as nullptr if we closed naturally.
-	/// @return If there was any overflow, this will be the last element generated by the spilling content. Otherwise, this will be nullptr.
-	LayoutInlineBox* Close(UniquePtr<LayoutInlineBox> overflow = nullptr);
-
-	/// Closes one of the line box's inline boxes.
-	/// @param inline_box[in] The inline box to close. This should always be the line box's open box.
-	void CloseInlineBox(LayoutInlineBox* inline_box);
-
-	/// Attempts to add a new element to this line box. If it can't fit, or needs to be split, new line boxes will
-	/// be created. The inline box for the final section of the element will be returned.
-	/// @param element[in] The element to fit into this line box.
-	/// @param box[in] The element's extents.
-	/// @return The inline box for the element.
-	LayoutInlineBox* AddElement(Element* element, const Box& box);
-
-	/// Attempts to add a new inline box to this line. If it can't fit, or needs to be split, new line boxes will
-	/// be created. The inline box for the final section of the element will be returned.
-	/// @param box[in] The inline box to be added to the line.
-	/// @return The final inline box.
-	LayoutInlineBox* AddBox(UniquePtr<LayoutInlineBox> box);
-	/// Adds an inline box as a chained hierarchy overflowing to this line. The chain will be extended into
-	/// this line box.
-	/// @param split_box[in] The box overflowed from a previous line.
-	void AddChainedBox(LayoutInlineBox* chained_box);
-
-	/// Returns the position of the line box, relative to its parent's block box's content area.
-	/// @return The position of the line box.
-	Vector2f GetPosition() const;
-	/// Returns the position of the line box, relative to its parent's block box's offset parent.
-	/// @return The relative position of the line box.
-	Vector2f GetRelativePosition() const;
-	/// Returns the dimensions of the line box.
-	/// @return The dimensions of the line box.
-	Vector2f GetDimensions() const;
-
-	/// Returns the line box's open inline box.
-	/// @return The line's open inline box, or nullptr if it currently has none.
-	LayoutInlineBox* GetOpenInlineBox();
-	/// Returns the line's containing block box.
-	/// @return The line's block box.
-	LayoutBlockBox* GetBlockBox();
-
-	float GetBoxCursor() const;
-
-	bool GetBaselineOfLastLine(float& baseline) const;
-
-	void* operator new(size_t size);
-	void operator delete(void* chunk, size_t size);
-
-private:
-	/// Appends an inline box to the end of the line box's list of inline boxes. Returns a pointer to the appended box.
-	LayoutInlineBox* AppendBox(UniquePtr<LayoutInlineBox> box);
-
-	using InlineBoxList = Vector< UniquePtr<LayoutInlineBox> >;
-
-	// The block box containing this line.
-	LayoutBlockBox* parent;
-
-	// The top-left position of the line box; this is set when the first inline box is placed.
-	Vector2f position;
-	bool position_set;
-
-	// The width and height of the line box; this is set when the line box is placed.
-	Vector2f dimensions;
-	bool wrap_content;
-
-	// The horizontal cursor. This is where the next inline box will be placed along the line.
-	float box_cursor;
-
-	// The list of inline boxes in this line box. These line boxes may be parented to others in this list.
-	InlineBoxList inline_boxes;
-	// The open inline box; this is nullptr if all inline boxes are closed.
-	LayoutInlineBox* open_inline_box;
-};
-
-} // namespace Rml
-#endif

+ 0 - 104
Source/Core/LayoutTable.h

@@ -1,104 +0,0 @@
-/*
- * 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_LAYOUTTABLE_H
-#define RMLUI_CORE_LAYOUTTABLE_H
-
-#include "../../Include/RmlUi/Core/Types.h"
-
-namespace Rml {
-
-class Box;
-class TableGrid;
-struct TrackBox;
-using TrackBoxList = Vector<TrackBox>;
-
-
-class LayoutTable {
-public:
-	/// Formats and positions a table, including all elements contained within.
-	/// @param[inout] box The box used for dimensioning the table, the resulting table size is set on the box.
-	/// @param[in] min_size Minimum width and height of the table.
-	/// @param[in] max_size Maximum width and height of the table.
-	/// @param[in] element_table The table element.
-	/// @return The content size of the table's overflowing content.
-	static Vector2f FormatTable(Box& box, Vector2f min_size, Vector2f max_size, Element* element_table);
-
-private:
-	LayoutTable(Element* element_table, const TableGrid& grid, Vector2f table_gap, Vector2f table_content_offset,
-		Vector2f table_initial_content_size, bool table_auto_height, Vector2f table_min_size, Vector2f table_max_size);
-
-	// Format the table.
-	void FormatTable();
-
-	// Determines the column widths and populates the 'columns' data member.
-	void DetermineColumnWidths();
-
-	// Generate the initial boxes for all cells, content height may be indeterminate for now (-1).
-	void InitializeCellBoxes();
-
-	// Determines the row heights and populates the 'rows' data member.
-	void DetermineRowHeights();
-
-	// Format the table row and row group elements.
-	void FormatRows();
-
-	// Format the table row and row group elements.
-	void FormatColumns();
-
-	// Format the table cell elements.
-	void FormatCells();
-
-	Element* const element_table;
-
-	const TableGrid& grid;
-
-	const bool table_auto_height;
-	const Vector2f table_min_size, table_max_size;
-	const Vector2f table_gap;
-	const Vector2f table_content_offset;
-	const Vector2f table_initial_content_size;
-
-	// The final size of the table which will be determined by the size of its columns, rows, and spacing.
-	Vector2f table_resulting_content_size;
-	// Overflow size in case the contents of any cells overflow their cell box (without being caught by the cell).
-	Vector2f table_content_overflow_size;
-
-	// Defines the boxes for all columns in this table, one entry per table column (spanning columns will add multiple entries).
-	TrackBoxList columns;
-
-	// Defines the boxes for all rows in this table, one entry per table row.
-	TrackBoxList rows;
-
-	// Defines the boxes for all cells in this table.
-	using BoxList = Vector<Box>;
-	BoxList cells;
-};
-
-} // namespace Rml
-#endif

+ 1 - 1
Source/Core/WidgetScroll.cpp

@@ -33,7 +33,7 @@
 #include "../../Include/RmlUi/Core/Factory.h"
 #include "../../Include/RmlUi/Core/Property.h"
 #include "Clock.h"
-#include "LayoutDetails.h"
+#include "Layout/LayoutDetails.h"
 
 namespace Rml {
 

+ 1 - 0
Tests/Data/VisualTests/border_radius.rml

@@ -40,6 +40,7 @@
 			border-color: #55f #f57 #55f #afa;
 			border-width: 5dp;
 			border-radius: 8dp;
+			padding: 8dp 0;
 		}
 	</style>
 </head>

+ 51 - 0
Tests/Data/VisualTests/display_block_inside_inline.rml

@@ -0,0 +1,51 @@
+<rml>
+<head>
+	<title>CSS Display: Block box within inline</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css2/#box-gen" />
+	<meta name="description" content='"When an inline box contains an in-flow block-level box, the inline box (and its inline ancestors within the same line box) is broken around the block-level box (and any block-level siblings that are consecutive or separated only by collapsible whitespace and/or out-of-flow elements), splitting the inline box into two boxes (even if either side is empty), one on each side of the block-level box(es). The line boxes before the break and after the break are enclosed in anonymous block boxes, and the block-level box becomes a sibling of those anonymous boxes. When such an inline box is affected by relative positioning, any resulting translation also affects the block-level box contained in the inline box."' />
+	<style>
+
+	</style>
+</head>
+<body rmlui-debug-layout>
+
+<span id="wrap">
+	<span id="a">
+		<div id="block">Y</div>
+	</span>
+	<span id="b">Z</span>
+</span>
+
+<!--
+
+<span id="span">
+A
+<div id="div">B</div>
+C
+</span>
+
+<span id="span" style="text-align: center; border: 1px #000; background: #f00;">
+A<br/>B
+<div id="div">C LONG</div>
+D
+</span>
+
+<span id="wrap">
+	<span id="a">
+		<span id="block">Y</span>
+	</span>
+	<span id="b">Z</span>
+</span>
+
+<span id="wrap">
+This is anonymous text before the SPAN.
+<p id="p">This is the content of SPAN.</p>
+This is anonymous text after the SPAN.
+</span>
+
+<div id="outer">A<span id="inner">B</span>C</div>
+
+-->
+</body>
+</rml>

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

@@ -50,6 +50,7 @@
     </div>
 
     <div class="flex-container flex-direction-row-reverse" style="height: 200dp; justify-content: space-around;">
+        <div class="flex-item absolute" style="right: auto; margin-left: auto; margin-right: auto;"><br/>Abs</div>
         <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>

+ 50 - 0
Tests/Data/VisualTests/flex_absolutely_positioned.rml

@@ -0,0 +1,50 @@
+<rml>
+<head>
+	<title>Flex - Absolutely positioned</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<meta name="Description" content="Test absolutely positioned and floated flex containers." />
+	<style>
+		.flex {
+			display: flex;
+			height: 100dp;
+			width: 40%;
+			text-align: center;
+			flex-direction: row;
+		}
+		
+		.flex > :nth-child(1) { width: 20%; background: red; }
+		.flex > :nth-child(2) { width: 60%; background: green; }
+		.flex > :nth-child(3) { width: 20%; background: blue; }
+		
+		.absolute {
+			position: absolute;
+			left: 20dp;
+			bottom: 20dp;
+		}
+		.float {
+			float: right;
+		}
+	</style>
+</head>
+
+<body>
+<div class="flex">
+	<div>1</div>
+	<div>2</div>
+	<div>3</div>
+</div>
+<hr/>
+<div id="absolute" class="flex absolute">
+	<div>1</div>
+	<div>2</div>
+	<div>3</div>
+</div>
+<hr/>
+<div id="float" class="flex float">
+	<div>1</div>
+	<div>2</div>
+	<div>3</div>
+</div>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 3 - 4
Tests/Data/VisualTests/float_basic.rml

@@ -2,9 +2,8 @@
 <head>
     <title>Floats, block formatting contexts</title>
     <link type="text/rcss" href="../style.rcss"/>
-	<link rel="help" href="https://www.w3.org/TR/CSS21/visufx.html#propdef-overflow" />
-	<meta name="Description" content="Nesting divs should still hide overflow. Elements whose containing block is located above the 'overflow: hidden' element should be visible." />
-	<meta name="See also" content="CSS 2.1 'clipping-' and 'overflow-' tests." />
+	<link rel="help" href="https://drafts.csswg.org/css2/#bfc-next-to-float" />
+	<meta name="Description" content="Floated elements should only interact with elements in the same block formatting context. The margin box of floated elements must not overlap with the border box of elements that establish an independent formatting context." />
 	<style>
 		body {
 			background: #ddd;
@@ -46,7 +45,7 @@
 	<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.
 	<div class="float">float: left</div>
 	<span class="red">This</span> is the first word after the float and should flow next to the float.</p>
-	<p style="overflow: auto;">This paragraph should establish a new block formatting context. This element's size and position should not overlap with the float, but still be located next to it.</p>
+	<p style="overflow: auto;">This paragraph establishes a new block formatting context. This paragraph should therefore be placed below the preceding float to avoid overlapping it, but may be placed adjacent to the float if there is sufficient space.</p>
 </div>
 </body>
 </rml>

+ 59 - 0
Tests/Data/VisualTests/float_excluded_by_flow_root.rml

@@ -0,0 +1,59 @@
+<rml>
+<head>
+	<title>Floats excluded by new block formatting context</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="help (CSS2)" href="https://drafts.csswg.org/css2/#floats" />
+	<link rel="help (flow-root)" href="https://www.w3.org/TR/css-display-3/#valdef-display-flow-root" />
+	<link rel="source" href="https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context#exclude_external_floats" />
+	<meta name="Description" content="The bottom block box should be placed next to its float (not overlap it), as it establishes a new block formatting context that prevent it from being affected by external floats." />
+	<style>
+		body {
+			padding: 10px;
+			border: 12px #ddd;
+			border-left: 25px;
+			width: 500px; /* outer width 557px */
+		}
+		section {
+			display: block;
+			min-height: 150px;
+		}
+		p {
+			border: 2px #fff;
+			/*white-space: nowrap;*/
+			/*text-align: right;*/
+		}
+		.box {
+			background-color: #e0cef7;
+			border: 5px #663399;
+		}
+		.box.flow-root {
+			/*display: flow-root;*/  /* Not yet implemented */
+			overflow: hidden;        /* Use this instead to establish a BFC */
+			background-color: #f0f8ff;
+			border-color: #4682b4;
+		}
+		.float {
+			float: left;
+			margin-right: 25px;
+			width: 200px;
+			height: 100px;
+			background-color: #fffa;
+			border: 1px black;
+			padding: 10px;
+			position: relative;
+		}
+	</style>
+</head>
+
+<body>
+<section>
+	<div class="float">Try to resize this outer float<handle size_target="#parent"/></div>
+	<div class="box"><p id="p">Normal text</p></div>
+</section>
+<section>
+	<div class="float">Try to resize this outer float<handle size_target="#parent"/></div>
+	<div class="box flow-root"><p><code>display: flow-root</code></p></div>
+</section>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 59 - 0
Tests/Data/VisualTests/float_placement.rml

@@ -0,0 +1,59 @@
+<rml>
+<head>
+    <title>Floats: placement</title>
+    <link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="http://www.w3.org/TR/CSS21/visuren.html#floats" />
+	<meta name="Description" content="Floating boxes" />
+	<style>
+		body {
+		}
+		hr {
+			clear: both;
+			margin: 15px;
+			display: block;
+		}
+		span {
+			color: red;
+		}
+		.paragraph {
+			margin: 15px 0;
+		}
+		.box {
+		  float: left;
+		  margin-right: 15px;
+		  width: 150px;
+		  height: 70px;
+		  border-radius: 5px;
+		  background-color: rgb(207, 232, 220);
+		  padding: 1em;
+		}
+	</style>
+</head>
+
+<body>
+
+<div class="paragraph">In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+
+<hr/>
+
+<div class="box">Float</div><div class="paragraph"><span>In</span> consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+
+<hr/>
+
+<div class="paragraph"><div class="box">Float</div><span>In</span> consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+
+<hr/>
+
+<div class="paragraph">In consectetur, neque <div class="box">Float</div> <span>dignissim</span> tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis . Quisque maximus  urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+
+<hr/>
+
+<div class="paragraph">In consectetur, neque dignissim tincidunt dapibus, leo metus <div class="box">Float</div> <span>molestie</span> erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+
+<hr/>
+
+<div class="paragraph">In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue <div class="box">Float</div> <span>placerat</span>. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus. Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>

+ 42 - 0
Tests/Data/VisualTests/float_text_wrap.rml

@@ -0,0 +1,42 @@
+<rml>
+<head>
+	<title>Text wrapping around floats</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css2/#floats" />
+	<meta name="Description" content="Ensure the words wrap correctly around the float." />
+	<style>
+		section {
+			display: block;
+			min-height: 150px;
+		}
+		p {
+			border: 2px #fff;
+			/*white-space: nowrap;*/
+			/*text-align: right;*/
+		}
+		.box {
+			background-color: #f0f8ff;
+			border: 5px #4682b4;
+			font-size: 30dp;
+		}
+		.float {
+			float: left;
+			margin-right: 25px;
+			width: 380px;
+			height: 100px;
+			background-color: #fffa;
+			border: 1px black;
+			padding: 10px;
+			position: relative;
+		}
+	</style>
+</head>
+
+<body>
+<section>
+	<div class="float">Try to resize this outer float<handle size_target="#parent"/></div>
+	<div class="box"><p>AA B CCC</p></div>
+</section>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 55 - 0
Tests/Data/VisualTests/inline_formatting_01.rml

@@ -0,0 +1,55 @@
+<rml>
+<head>
+    <title>Inline formatting 01</title>
+    <link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://www.w3.org/TR/css-inline-3/#model" />
+	<meta name="Description" content="Test various inline formatting details." />
+	<style>
+		body {
+			width: 1020px;
+			padding: 0;
+			border: 12px #ddd;
+		}
+		hr {
+			clear: both;
+			margin: 15px;
+			display: block;
+		}
+		span {
+			color: red;
+		}
+		span.outer {
+			padding: 5px;
+			border: 4px #ee0;
+			margin: 5px;
+		}
+		span.inner {
+			padding: 5px;
+			border: 2px #00e;
+			margin: 5px;
+		}
+		.paragraph {
+			margin: 15px 0;
+		}
+		.inner-blocks span.inner {
+			display: inline-block;
+		}
+		.inline-blocks span {
+			display: inline-block;
+		}
+	</style>
+</head>
+
+<body rmlui-debug-layout>
+
+<div class="paragraph" id="plain">In consectetur, neque dignissim tincidunt dapibus, leo metus molestie erat, eu pharetra velit nunc in ipsum. Donec aliquet malesuada iaculis. Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus.<br/>Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam venenatis quis elit vel mattis. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+<hr/>
+<div class="paragraph">In consectetur, neque dignissim tincidunt dapibus, <span class="outer">leo metus <span class="inner">molestie erat</span>, eu pharetra <span class="inner">velit nunc</span> in ipsum. Donec aliquet <span class="inner">malesuada iaculis.</span></span> Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus.<br/>Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam <span class="outer">&nbsp;<span class="inner">venenatis quis</span> <span class="inner">elit vel</span> mattis</span>. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+<hr/>
+<div class="paragraph inner-blocks" id="inner-blocks">In consectetur, neque dignissim tincidunt dapibus, <span class="outer">leo metus <span class="inner">molestie erat</span>, eu pharetra <span class="inner">velit nunc</span> in ipsum. Donec aliquet <span class="inner">malesuada iaculis.</span></span> Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus.<br/>Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam <span class="outer">&nbsp;<span class="inner">venenatis quis</span> <span class="inner">elit vel</span> mattis</span>. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+<hr/>
+<div class="paragraph inline-blocks" id="inline-blocks">In consectetur, neque dignissim tincidunt dapibus, <span class="outer">leo metus <span class="inner">molestie erat</span>, eu pharetra <span class="inner">velit nunc</span> in ipsum. Donec aliquet <span class="inner">malesuada iaculis.</span></span> Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue. Donec posuere ante at orci efficitur porttitor vitae ac tellus.<br/>Nunc nec sapien orci. Sed rhoncus et dui sed sagittis. Nullam <span class="outer">&nbsp;<span class="inner">venenatis quis</span> <span class="inner">elit vel</span> mattis</span>. In mattis erat et blandit aliquam. Aliquam erat volutpat.</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>

+ 27 - 0
Tests/Data/VisualTests/inline_formatting_02.rml

@@ -0,0 +1,27 @@
+<rml>
+<head>
+    <title>Inline formatting 02</title>
+    <link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://www.w3.org/TR/css-inline-3/#model" />
+	<meta name="Description" content="Inline formatting context" />
+	<style>
+		body {
+			width: 70dp;
+			padding: 0;
+			border: 20dp #ddd;
+		}
+		span {
+			padding: 5px;
+			border: 4px #ee0;
+			margin: 5px 50px;
+		}
+	</style>
+</head>
+
+<body rmlui-debug-layout>
+
+<span>A B C D</span>
+
+<handle size_target="#document"/>
+</body>
+</rml>

+ 31 - 0
Tests/Data/VisualTests/inline_formatting_03.rml

@@ -0,0 +1,31 @@
+<rml>
+<head>
+    <title>Inline formatting 03</title>
+    <link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://www.w3.org/TR/css-inline-3/#model" />
+	<meta name="Description" content="Testing inline box split by resizing the document." />
+	<style>
+		body {
+			width: 280px;
+			padding: 0;
+			border: 20dp #ddd;
+		}
+		span {
+			color: red;
+			padding: 5px;
+			border: 4px #ee0;
+			margin: 5px 50px;
+		}
+		.paragraph {
+			margin: 15px 0;
+		}
+	</style>
+</head>
+
+<body rmlui-debug-layout>
+
+<div class="paragraph">In consectetur<span class="outer">A metus</span> Quisque maximus urna in congue placerat. In hac habitasse platea dictumst. Mauris a fringilla augue.</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>

+ 66 - 0
Tests/Data/VisualTests/inline_formatting_04.rml

@@ -0,0 +1,66 @@
+<rml>
+<head>
+	<title>Inline formatting 04</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://www.w3.org/TR/css-inline-3/#line-boxes" />
+	<meta name="Description" content="Split line boxes." />
+	<style>
+		body {
+			padding: 0;
+			border: 12px #ddd;
+		}
+		hr {
+			clear: both;
+			margin: 15px;
+			display: block;
+		}
+		span { color: red; }
+		.wrap {
+			border: 8px #ee0;
+			background: #ada;
+			line-height: 3em;
+		}
+		.inner {
+			color: blue;
+			border: 15px #f663;
+			border-width: 5px 30px;
+		}
+	</style>
+</head>
+
+<body rmlui-debug-layout>
+
+<span><br/></span>
+
+<span class="wrap">
+	Before<span class="inner"> Inner </span>After
+</span>
+
+<hr/>
+
+<span class="wrap">
+	<span class="inner"><span class="inner">
+		<span class="inner">
+			<div>Y</div>
+		</span>
+		<span>Z</span>
+	</span></span>
+</span>
+
+<hr/>
+
+<span class="wrap">
+	<span class="inner"><br/></span>
+	<span>Z</span>
+</span>
+
+<hr/>
+
+<span class="wrap">
+	<span class="inner"><br/>Y</span>
+	<span>Z</span>
+</span>
+
+<handle size_target="#document"/>
+</body>
+</rml>

+ 58 - 0
Tests/Data/VisualTests/inline_formatting_05.rml

@@ -0,0 +1,58 @@
+<rml>
+<head>
+	<title>Inline formatting 05</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css2/#propdef-vertical-align" />
+	<link rel="help" href="https://drafts.csswg.org/css2/#inline-box-height" />
+	<meta name="Description" content="Vertical alignment. Stated 'vertical-align' value applies to the blue box." />
+	<style>
+		p { color: #45e; }
+		span {
+			border-top: 1px #3332;
+			border-bottom: 1px #3332;
+		}
+		sub {
+			font-size: 10px;
+			background: #ffd;
+			vertical-align: sub;
+		}
+		.wrapper {
+			background-color: #eee;
+			border: 3px #bbb;
+			margin-bottom: 2em;
+		}
+		.line {
+			background: #fdd;
+		}
+		.outer {
+			font-size: 30px;
+			background: #ddf;
+		}
+		.inner {
+			font-size: 50px;
+			background: #dfd;
+		}
+		.middle { vertical-align: middle; }
+		.text-top { vertical-align: text-top; }
+	</style>
+</head>
+
+<body>
+<p>vertical-align: baseline</p>
+<div class="wrapper">
+	<span class="line">A <span class="outer"> B<span class="inner">C<sub>d</sub>E</span>F</span> G</span>
+</div>
+
+<p>vertical-align: middle</p>
+<div class="wrapper">
+	<span class="line">A <span class="outer middle"> B<span class="inner">C<sub>d</sub>E</span>F</span> G</span>
+</div>
+
+<p>vertical-align: text-top</p>
+<div class="wrapper">
+	<span class="line">A <span class="outer text-top"> B<span class="inner">C<sub>d</sub>E</span>F</span> G</span>
+</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>

+ 57 - 0
Tests/Data/VisualTests/inline_formatting_06.rml

@@ -0,0 +1,57 @@
+<rml>
+<head>
+	<title>Inline formatting 06</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css2/#propdef-vertical-align" />
+	<meta name="Description" content="Line height with inline-blocks." />
+	<meta name="Assert" content="The baseline of an inline-block is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its overflow property has a computed value other than visible, in which case the baseline is the bottom margin edge." />
+	<style>
+		p.value { color: #45e; margin: -0.5em 0 0.2em 1em; }
+		.wrapper {
+			background-color: #eee;
+			border: 3px #bbb;
+			padding: 8px;
+			margin-bottom: 1.5em;
+		}
+		.wrapper > div {
+			display: inline-block;
+			background: #fcc;
+			border: 1px #999;
+
+			width: 150px;
+			line-height: 35px;
+
+			text-align: center;
+		}
+		.scroll-container > div { overflow: auto; }
+		.offset-up > div { vertical-align: -12px; }
+	</style>
+</head>
+
+<body>
+<p>Normally, the baseline of an inline-block is set to its last line, nicely aligning up with its neighboring text.</p>
+<div class="wrapper">
+	X <div>Start Game</div><br/>
+	X <div>High Scores</div><br/>
+	X <div>Options</div>
+</div>
+
+<p>However, after making the inline-block a scroll container (such as by setting a non-visible overflow value), it is instead aligned to its bottom edge. Due to the line's text descent, there is additional spacing appearing below the box.</p>
+<p class="value">overflow: auto;</p>
+<div class="wrapper scroll-container">
+	X <div>Start Game</div><br/>
+	X <div>High Scores</div><br/>
+	X <div>Options</div>
+</div>
+
+<p>We can manually adjust the vertical alignment to make sure we remove that additional space. However, it won't perfectly line up the baselines anymore.</p>
+<p class="value">overflow: auto;<br/>vertical-align: -12px</p>
+<div class="wrapper scroll-container offset-up">
+	X <div>Start Game</div><br/>
+	X <div>High Scores</div><br/>
+	X <div>Options</div>
+</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>

+ 55 - 0
Tests/Data/VisualTests/inline_formatting_07.rml

@@ -0,0 +1,55 @@
+<rml>
+<head>
+	<title>Inline formatting 07</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css2/#floats" />
+	<meta name="Description" content="Floats placed within line boxes." />
+	<style>
+		hr { clear: both; margin: 0.5em 0; }
+		div { border: 2px #666; }
+
+		.right { float: right; }
+		.left { float: left; }
+		.clear { clear: both; }
+
+		.lines { line-height: 50px; background: #cca; }
+
+		.big { width: 300px; height: 190px; background: #ada; }
+		.small { width: 30px; height: 15px; background: #eaa; line-height: 15px; }
+		.tall { width: 30px; height: 100px; background: #aae; }
+
+		.inline { display: inline-block; overflow: hidden; }
+	</style>
+</head>
+
+<body>
+<div class="big right"> Big </div>
+<div class="lines">
+	First line<br/>
+	Second line ABC DEF
+	<div class="small right">X</div><div class="small right">Y</div> 
+	GHI JKL MNO PQRS TUV
+</div>
+
+<hr/>
+
+<div class="big right"> Big </div>
+<div class="lines">
+	First line<br/>
+	Second line
+	<div class="small left">X</div><div class="small left" style="clear:left;">Y</div><div class="small right">Z</div>
+	ABC DEF  GHI JKL MNO PQRS TUV
+</div>
+
+<hr/>
+
+<div class="big right" style="height: 140px"> Big </div>
+<div class="lines">
+	First line<br/>
+	Second line ABC DEF GHI JKL MNO PQRS TUV<div class="small right">X</div><div class="small right">Y</div><div class="small left">Z</div>
+	ABC DEF GHI JKL MNO PQRS TUV ABC DEF GHI JKL MNO PQRS TUV  ABC DEF GHI JKL MNO PQRS TUV <div class="inline tall">Z</div> 
+</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>

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

@@ -0,0 +1,97 @@
+<rml>
+<head>
+    <title>Inline formatting - Aligned subtree</title>
+    <link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css2/#aligned-subtree" />
+	<meta name="Description" content="Children of an inline element whose 'vertical-align' property is 'top' or 'bottom' is aligned relative to it." />
+	<style>
+		body {
+			background: #ddd;
+			color: #444;
+		}
+		.wrapper {
+			border: 8px #bbb;
+			margin-bottom: 2em;
+			line-height: 2em;
+		}
+		span {
+			border-color: #3332;
+			border-width: 1px;
+			background: #dfd;
+			padding: 0.5em;
+		}
+		span.block {
+			background: #fdf;
+			display: inline-block;
+		}
+		
+		.tall   { line-height: 5em; }
+		.large  { line-height: 3em; }
+		.medium { line-height: 1.5em; }
+		.small  { line-height: 1.2em; }
+		
+		.top     { vertical-align: top; }
+		.bottom  { vertical-align: bottom; }
+		.line-up { vertical-align: -100%; }
+		
+		.split { line-height: 2em; }
+		.split span {
+			line-height: 4em;
+			border-width: 3px;
+			padding: 0;
+			vertical-align: top;
+		}
+		span.yellow, .yellow span { background-color: #ffd; }
+	</style>
+</head>
+
+<body>
+<div class="wrapper">
+	<span class="tall block">A</span>
+	<span class="small top">
+		B
+		<span class="medium block bottom">C</span>
+		D
+	</span>
+	<span class="large">
+		E
+		<span class="medium block">F</span>
+		G
+	</span>
+</div>
+<div class="wrapper">
+	<span class="small top">
+		A
+		<span class="medium block bottom">B</span>
+		<span class="bottom yellow">
+			C
+			<span class="line-up">
+				D
+				<span class="top">E</span>
+			</span>
+		</span>
+	</span>
+	<span class="tall block">F</span>
+</div>
+<div class="wrapper">
+	<span class="small top">
+		A
+		<span class="medium block bottom">
+			B
+			<span class="bottom yellow">
+				C
+				<span class="line-up">
+					D
+					<span class="top">E</span>
+				</span>
+			</span>
+		</span>
+	</span>
+	<span class="tall block">F</span>
+</div>
+<div class="wrapper split">
+	The following text <span>should wrap down to the<br/> next line</span> and produce borders on each line.
+</div>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 1 - 1
Tests/Data/VisualTests/overflow_hidden.rml

@@ -84,7 +84,7 @@
 	<div class="clip-none">
 		Don't clip within a clipping parent using 'clip: none'.
 		LONG_WOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOORD
-		<div class="absolute close green">Should be visible</div>
+		<div class="absolute close green clip-none">Should be visible</div>
 	</div>
 </div>
 <div class="overflow relative clip-always">

+ 28 - 8
Tests/Data/VisualTests/overflow_nested.rml

@@ -2,23 +2,24 @@
 <head>
     <title>Nested overflow</title>
     <link type="text/rcss" href="../style.rcss"/>
-	<meta name="Description" content="The deepest element in the tree should catch the overflow." />
+	<meta name="Description" content="The deepest element in the tree, whose overflow property is not visible, should catch the overflow." />
 	<style>
 		body {
 			display: block;
 			background: #ddd;
 			color: #444;
 		}
+		div.overflow { overflow: auto; }
 		div.outer {
-			overflow: auto;
 			width: 200dp;
-			height: 200dp;
+			height: 150dp;
+			border: 1dp #fff;
+			margin-bottom: 30dp;
 		}
-		div.overflow {
+		div.inner {
 			border: 1dp black;
-			overflow: auto;
 			width: 150dp;
-			height: 150dp;
+			height: 100dp;
 		}
 		div.wide {
 			width: 300dp;
@@ -28,15 +29,34 @@
 			margin: 5dp;
 			background-color: #eee;
 		}
+		div.float {
+			float: left;
+			width: 40dp;
+			height: 70%;
+			border: 1dp black;
+		}
 	</style>
 </head>
 
 <body>
 <p>There should should only be one scroll bar visible, inside the black border.</p>
-<div class="outer">
-	<div class="overflow">
+<div class="outer overflow">
+	<div class="inner overflow">
 		<div class="wide">Wide element</div>
 	</div>
 </div>
+
+<p>There should should only be one scroll bar visible, inside the white border.</p>
+<div class="outer overflow">
+	<div class="inner">
+		<div class="wide">Wide element</div>
+	</div>
+</div>
+
+<div class="outer overflow">
+	<div class="float">
+		The text in this box should overflow its scroll container, thereby producing a scrollbar inside the white border.
+	</div>
+</div>
 </body>
 </rml>

+ 59 - 0
Tests/Data/VisualTests/position_absolute_static_position.rml

@@ -0,0 +1,59 @@
+<rml>
+<head>
+	<title>Static position of absolutely positioned elements</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="CSS 2" href="https://drafts.csswg.org/css2/#static-position" />
+	<link rel="CSS 3" href="https://drafts.csswg.org/css-position-3/#static-position" />
+	<link rel="match" href="reference/position_absolute_static_position-ref.rml"/>
+	<meta name="description" content='Auto-positioned absolute elements should be placed at their static position.' />
+	<meta name="Assert" content='The absolute box should be positioned over the highlighted word when the scrollbox is scrolled to the top.' />
+	<meta name="Assert" content='The absolute box should not move when scrolling the article box, because its containing block is the wrapper box.' />
+	<style>
+		.wrapper {
+			display: block;
+			overflow: auto;
+			border: 5px #bdbdbd;
+			padding: 20px;
+			height: 80%;
+			position: relative;
+		}
+		.article {
+			height: 300px;
+			border: 3px #aaa;
+			padding: 10px;
+			margin-top: 50px;
+			margin-left: 70px;
+		}
+		.absolute {
+			position: absolute;
+			box-sizing: border-box;
+			width: 70px;
+			height: 70px;
+			padding-top: 1.2em;
+			border: 2px #c33;
+			background: #fffa;
+			text-align: center;
+		}
+		.paragraph { margin: 0.7em 0; }
+		.overflow { overflow: auto; }
+		.red { color: #c33; }
+	</style>
+</head>
+<body>
+
+<div class="wrapper">
+Wrapper box.
+	<div class="article overflow">
+		<div class="paragraph">Article box. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vehicula scelerisque congue. Morbi vel leo ut urna elementum cursus id sed justo. In hac habitasse platea dictumst. Curabitur convallis dui ac erat cursus consequat. Phasellus ornare vitae dolor ac semper. Donec finibus, nulla a porta venenatis, metus risus interdum nunc, ac cursus elit mi et orci. Maecenas tincidunt felis vitae est vulputate porta.</div>
+		
+		<div class="absolute">Absolute box</div>
+
+		<div class="paragraph"><span class="red">Interdum</span> et malesuada fames ac ante ipsum primis in faucibus. Vestibulum sed elit dui. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer commodo porttitor luctus. Donec hendrerit ultrices turpis, sed sodales felis auctor a. Aliquam a ipsum nec mauris lacinia consequat at sit amet mi. Nullam sodales enim magna, eget semper libero facilisis sollicitudin.</div>
+
+		<div class="paragraph">Nulla rhoncus, quam quis rutrum egestas, elit velit porta justo, eget dictum mi sapien non nisi. Phasellus ac tortor sed nibh egestas vestibulum. In lobortis eros ante, vel pellentesque leo mattis non. Vivamus non turpis dui. Proin erat tortor, egestas pharetra erat at, iaculis ultricies dolor. Pellentesque et venenatis ligula. Phasellus feugiat ut diam id placerat. Aliquam erat volutpat. Sed at ex lacus. Quisque vestibulum, ante ut elementum blandit, nisl ex laoreet ante, sed accumsan arcu nulla at nibh. Nunc volutpat nibh eget mollis semper. Maecenas lacus augue, venenatis varius nibh consectetur, ornare fringilla libero. Proin sagittis tellus dolor, ac facilisis lorem scelerisque vel.</div>
+	</div>
+</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>

+ 61 - 0
Tests/Data/VisualTests/position_absolute_transform.rml

@@ -0,0 +1,61 @@
+<rml>
+<head>
+	<title>Absolutely positioned box with transformed parent</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://www.w3.org/TR/css-transforms-1/#containing-block-for-all-descendants" />
+	<meta name="Description" content="Elements with a local transform or perspective should act as the containing block for absolutely positioned children. "/>
+	<meta name="Assert" content="The child boxes should fit nicely at the bottom right corner of their parent elements."/>
+	<style>
+		.container {
+			margin: 15dp 0;
+			height: 250dp;
+			background-color: #ddd;
+			border: 3px #888;
+			overflow: auto;
+		}
+		.tall { height: 1000px; }
+		.transform {
+			width: 150dp;
+			height: 120dp;
+			padding: 10dp;
+			margin: auto;
+			background-color: #f0f8ff;
+			border: 5px #4682b4;
+			font-size: 1.2em;
+			transform: translate(15dp,5dp) rotate3d(0.8, 0, 1, 30deg);
+		}
+		.transform:hover { background-color: #fcffff; }
+		.absolute {
+			position: absolute;
+			width: 50%;
+			height: 50%;
+			background-color: #e0cef7;
+			border: 5px #663399;
+			right: 0;
+			bottom: 0;
+		}
+	</style>
+</head>
+
+<body>
+<div class="container">
+	<div class="transform">
+		Transformed element
+		<div class="tall"></div>
+		<div class="absolute">Absolute child</div>
+	</div>
+  <div class="tall"></div>
+</div>
+
+<div class="container">
+	<div class="transform">
+		Transformed element
+		<div class="tall"></div>
+		<div class="absolute" style="position: fixed">Fixed child</div>
+	</div>
+  <div class="tall"></div>
+</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>

+ 75 - 0
Tests/Data/VisualTests/position_relative.rml

@@ -0,0 +1,75 @@
+<rml>
+<head>
+	<title>Relative position</title>
+	<link type="text/rcss" href="../style.rcss"/>
+	<link rel="source" href="https://drafts.csswg.org/css-position-3/#comparison" />
+	<link rel="help" href="https://drafts.csswg.org/css-position-3/#comparison" />
+	<meta name="description" content='Relative positioning with percent offset in different formatting contexts.' />
+	<meta name="assert" content='The green box should be positioned immediately below the purple box, and together they should be vertically centered in the document.' />
+	<meta name="assert" content='The colored boxes should be vertically ordered (with horizontal groups) as follows: white, yellow, purple, green, (orange, red), (blue, pink).' />
+	<style>
+		body {
+			font-size: 16dp;
+		}
+		block {
+			display: block;
+			position: relative;
+			overflow: auto;
+			border: 2px #aaa;
+			height: 90%; top: 5% 
+		}
+		div, td {
+			width: 70dp;
+			height: 70dp;
+		}
+		table, flex { width: 70dp; }
+		.absolute {
+			position: absolute;
+			bottom: 50%;
+			left: 50%;
+		}
+		.relative {
+			position: relative;
+			top: 50%;
+			left: 50%;
+		}
+		.over {
+			position: relative;
+			bottom: 200%;
+			left: 100%;
+		}
+		.float { float: left; }
+		flex { display: flex; }
+		
+		.white { background: #f0f0f0; }
+		.purple { background: #88c; }
+		.green { background: #6c6; }
+		.yellow { background: #dd4; }
+		.orange { background: #da2; }
+		.red { background: #e77; }
+		.blue { background: #5fc4dd; }
+		.pink { background: #cb70e0; }
+	</style>
+</head>
+<body>
+
+<block>
+
+<div class="absolute purple">Absolute</div>
+<div class="relative green">Relative</div>
+
+<p>
+<p class="relative"><span class="white">First line</span></p>
+Start<span id="x" class="relative yellow">Inline</span><span class="relative float yellow">Float</span>After
+</p>
+
+<table class="relative orange"><td>Table container</td></table>
+<flex class="relative blue"><div>Flex container</div></flex>
+
+<table class="relative"><td class="over red">Table cell</td></table>
+<flex class="relative"><div class="over pink">Flex item</div></flex>
+
+</block>
+<handle size_target="#document"/>
+</body>
+</rml>

+ 55 - 0
Tests/Data/VisualTests/reference/position_absolute_static_position-ref.rml

@@ -0,0 +1,55 @@
+<rml>
+<head>
+    <title>Static position of absolutely positioned elements ref</title>
+    <link type="text/rcss" href="../../style.rcss"/>
+	<meta name="Assert" content='The reference absolute box should always be visible even when document is shrinked, while the main absolute box should be hidden outside the wrapper.' />
+	<style>
+		.wrapper {
+			display: block;
+			overflow: auto;
+			border: 5px #bdbdbd;
+			padding: 20px;
+			height: 80%;
+			position: relative;
+		}
+		.article {
+			height: 300px;
+			border: 3px #aaa;
+			padding: 10px;
+			margin-top: 50px;
+			margin-left: 70px;
+			position: relative;
+		}
+		.absolute {
+			position: fixed;
+			box-sizing: border-box;
+			width: 70px;
+			height: 70px;
+			padding-top: 1.2em;
+			border: 2px #c33;
+			background: #fffa;
+			text-align: center;
+		}
+		.paragraph { margin: 0.7em 0; }
+		.overflow { overflow: auto; }
+		.red { color: #c33; }
+	</style>
+</head>
+<body>
+
+<div class="wrapper">
+Wrapper box.
+	<div class="article overflow">
+		<div class="paragraph">Article box. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vehicula scelerisque congue. Morbi vel leo ut urna elementum cursus id sed justo. In hac habitasse platea dictumst. Curabitur convallis dui ac erat cursus consequat. Phasellus ornare vitae dolor ac semper. Donec finibus, nulla a porta venenatis, metus risus interdum nunc, ac cursus elit mi et orci. Maecenas tincidunt felis vitae est vulputate porta.</div>
+		
+		<div class="absolute">Absolute box</div>
+
+		<div class="paragraph"><span class="red">Interdum</span> et malesuada fames ac ante ipsum primis in faucibus. Vestibulum sed elit dui. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer commodo porttitor luctus. Donec hendrerit ultrices turpis, sed sodales felis auctor a. Aliquam a ipsum nec mauris lacinia consequat at sit amet mi. Nullam sodales enim magna, eget semper libero facilisis sollicitudin.</div>
+
+		<div class="paragraph">Nulla rhoncus, quam quis rutrum egestas, elit velit porta justo, eget dictum mi sapien non nisi. Phasellus ac tortor sed nibh egestas vestibulum. In lobortis eros ante, vel pellentesque leo mattis non. Vivamus non turpis dui. Proin erat tortor, egestas pharetra erat at, iaculis ultricies dolor. Pellentesque et venenatis ligula. Phasellus feugiat ut diam id placerat. Aliquam erat volutpat. Sed at ex lacus. Quisque vestibulum, ante ut elementum blandit, nisl ex laoreet ante, sed accumsan arcu nulla at nibh. Nunc volutpat nibh eget mollis semper. Maecenas lacus augue, venenatis varius nibh consectetur, ornare fringilla libero. Proin sagittis tellus dolor, ac facilisis lorem scelerisque vel.</div>
+	</div>
+</div>
+
+<handle size_target="#document"/>
+</body>
+</rml>

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.