Browse Source

Merge branch 'develop'

Michael Ragazzon 2 years ago
parent
commit
21a0ec2acc
100 changed files with 5583 additions and 4467 deletions
  1. 3 0
      Backends/RmlUi_Backend_SDL_GL2.cpp
  2. 3 0
      Backends/RmlUi_Backend_SDL_GL3.cpp
  3. 3 0
      Backends/RmlUi_Backend_SDL_SDLrenderer.cpp
  4. 3 0
      Backends/RmlUi_Backend_SDL_VK.cpp
  5. 36 20
      CMake/FileList.cmake
  6. 8 0
      CMakeLists.txt
  7. 8 4
      Include/RmlUi/Core/Box.h
  8. 22 7
      Include/RmlUi/Core/ComputedValues.h
  9. 22 21
      Include/RmlUi/Core/Element.h
  10. 4 7
      Include/RmlUi/Core/ElementText.h
  11. 5 23
      Include/RmlUi/Core/FontEngineInterface.h
  12. 49 0
      Include/RmlUi/Core/FontMetrics.h
  13. 5 7
      Include/RmlUi/Core/GeometryUtilities.h
  14. 17 2
      Include/RmlUi/Core/StyleTypes.h
  15. 3 1
      Include/RmlUi/Core/Vector2.h
  16. 5 0
      Include/RmlUi/Core/Vector2.inl
  17. 3 1
      Include/RmlUi/Core/Vector3.h
  18. 5 0
      Include/RmlUi/Core/Vector3.inl
  19. 3 1
      Include/RmlUi/Core/Vector4.h
  20. 5 0
      Include/RmlUi/Core/Vector4.inl
  21. 1 2
      Samples/assets/invader.rcss
  22. 13 13
      Samples/basic/bitmapfont/src/FontEngineBitmap.cpp
  23. 3 10
      Samples/basic/bitmapfont/src/FontEngineBitmap.h
  24. 2 27
      Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp
  25. 3 12
      Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h
  26. 1 0
      Samples/basic/lottie/data/lottie.rml
  27. 9 4
      Samples/invaders/data/background.rml
  28. 1 0
      Samples/invaders/data/game.rml
  29. 9 4
      Samples/luainvaders/data/background.rml
  30. 1 0
      Samples/luainvaders/data/game.rml
  31. 17 6
      Source/Core/Box.cpp
  32. 21 1
      Source/Core/ComputeProperty.cpp
  33. 2 0
      Source/Core/ComputeProperty.h
  34. 6 6
      Source/Core/ComputedValues.cpp
  35. 155 130
      Source/Core/Element.cpp
  36. 15 12
      Source/Core/ElementDocument.cpp
  37. 41 34
      Source/Core/ElementHandle.cpp
  38. 8 11
      Source/Core/ElementScroll.cpp
  39. 4 1
      Source/Core/ElementStyle.cpp
  40. 67 68
      Source/Core/ElementText.cpp
  41. 5 5
      Source/Core/ElementUtilities.cpp
  42. 11 5
      Source/Core/Elements/WidgetTextInput.cpp
  43. 3 27
      Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp
  44. 2 12
      Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h
  45. 2 28
      Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp
  46. 3 14
      Source/Core/FontEngineDefault/FontFaceHandleDefault.h
  47. 5 5
      Source/Core/FontEngineDefault/FontProvider.cpp
  48. 0 10
      Source/Core/FontEngineDefault/FontTypes.h
  49. 7 12
      Source/Core/FontEngineDefault/FreeTypeInterface.cpp
  50. 1 0
      Source/Core/FontEngineDefault/FreeTypeInterface.h
  51. 3 22
      Source/Core/FontEngineInterface.cpp
  52. 10 25
      Source/Core/GeometryUtilities.cpp
  53. 561 0
      Source/Core/Layout/BlockContainer.cpp
  54. 195 0
      Source/Core/Layout/BlockContainer.h
  55. 291 0
      Source/Core/Layout/BlockFormattingContext.cpp
  56. 71 0
      Source/Core/Layout/BlockFormattingContext.h
  57. 299 0
      Source/Core/Layout/ContainerBox.cpp
  58. 165 0
      Source/Core/Layout/ContainerBox.h
  59. 99 67
      Source/Core/Layout/FlexFormattingContext.cpp
  60. 68 0
      Source/Core/Layout/FlexFormattingContext.h
  61. 72 100
      Source/Core/Layout/FloatedBoxSpace.cpp
  62. 127 0
      Source/Core/Layout/FloatedBoxSpace.h
  63. 78 0
      Source/Core/Layout/FormattingContext.cpp
  64. 67 0
      Source/Core/Layout/FormattingContext.h
  65. 156 0
      Source/Core/Layout/InlineBox.cpp
  66. 99 0
      Source/Core/Layout/InlineBox.h
  67. 315 0
      Source/Core/Layout/InlineContainer.cpp
  68. 135 0
      Source/Core/Layout/InlineContainer.h
  69. 234 0
      Source/Core/Layout/InlineLevelBox.cpp
  70. 145 0
      Source/Core/Layout/InlineLevelBox.h
  71. 69 0
      Source/Core/Layout/InlineTypes.h
  72. 59 0
      Source/Core/Layout/LayoutBox.cpp
  73. 78 0
      Source/Core/Layout/LayoutBox.h
  74. 149 139
      Source/Core/Layout/LayoutDetails.cpp
  75. 28 31
      Source/Core/Layout/LayoutDetails.h
  76. 50 0
      Source/Core/Layout/LayoutEngine.cpp
  77. 53 0
      Source/Core/Layout/LayoutEngine.h
  78. 90 0
      Source/Core/Layout/LayoutPools.cpp
  79. 44 0
      Source/Core/Layout/LayoutPools.h
  80. 443 0
      Source/Core/Layout/LineBox.cpp
  81. 215 0
      Source/Core/Layout/LineBox.h
  82. 79 0
      Source/Core/Layout/ReplacedFormattingContext.cpp
  83. 65 0
      Source/Core/Layout/ReplacedFormattingContext.h
  84. 139 109
      Source/Core/Layout/TableFormattingContext.cpp
  85. 95 0
      Source/Core/Layout/TableFormattingContext.h
  86. 68 47
      Source/Core/Layout/TableFormattingDetails.cpp
  87. 39 37
      Source/Core/Layout/TableFormattingDetails.h
  88. 0 793
      Source/Core/LayoutBlockBox.cpp
  89. 0 254
      Source/Core/LayoutBlockBox.h
  90. 0 127
      Source/Core/LayoutBlockBoxSpace.h
  91. 0 456
      Source/Core/LayoutEngine.cpp
  92. 0 91
      Source/Core/LayoutEngine.h
  93. 0 76
      Source/Core/LayoutFlex.h
  94. 0 411
      Source/Core/LayoutInlineBox.cpp
  95. 0 176
      Source/Core/LayoutInlineBox.h
  96. 0 217
      Source/Core/LayoutInlineBoxText.cpp
  97. 0 99
      Source/Core/LayoutInlineBoxText.h
  98. 0 408
      Source/Core/LayoutLineBox.cpp
  99. 0 125
      Source/Core/LayoutLineBox.h
  100. 0 104
      Source/Core/LayoutTable.h

+ 3 - 0
Backends/RmlUi_Backend_SDL_GL2.cpp

@@ -166,6 +166,9 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	if (SDL_Init(SDL_INIT_VIDEO) != 0)
 	if (SDL_Init(SDL_INIT_VIDEO) != 0)
 		return false;
 		return false;
 
 
+	// Submit click events when focusing the window.
+	SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
+
 	// Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements.
 	// Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements.
 	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
 	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

+ 3 - 0
Backends/RmlUi_Backend_SDL_GL3.cpp

@@ -126,6 +126,9 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	if (SDL_Init(SDL_INIT_VIDEO) != 0)
 	if (SDL_Init(SDL_INIT_VIDEO) != 0)
 		return false;
 		return false;
 
 
+	// Submit click events when focusing the window.
+	SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
+
 #if defined RMLUI_PLATFORM_EMSCRIPTEN
 #if defined RMLUI_PLATFORM_EMSCRIPTEN
 	// GLES 3.0 (WebGL 2.0)
 	// GLES 3.0 (WebGL 2.0)
 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
 	SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);

+ 3 - 0
Backends/RmlUi_Backend_SDL_SDLrenderer.cpp

@@ -59,6 +59,9 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	if (SDL_Init(SDL_INIT_VIDEO) != 0)
 	if (SDL_Init(SDL_INIT_VIDEO) != 0)
 		return false;
 		return false;
 
 
+	// Submit click events when focusing the window.
+	SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
+
 	const Uint32 window_flags = (allow_resize ? SDL_WINDOW_RESIZABLE : 0);
 	const Uint32 window_flags = (allow_resize ? SDL_WINDOW_RESIZABLE : 0);
 	SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
 	SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
 	if (!window)
 	if (!window)

+ 3 - 0
Backends/RmlUi_Backend_SDL_VK.cpp

@@ -61,6 +61,9 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0)
 	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0)
 		return false;
 		return false;
 
 
+	// Submit click events when focusing the window.
+	SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
+
 	const Uint32 window_flags = (SDL_WINDOW_VULKAN | (allow_resize ? SDL_WINDOW_RESIZABLE : 0));
 	const Uint32 window_flags = (SDL_WINDOW_VULKAN | (allow_resize ? SDL_WINDOW_RESIZABLE : 0));
 
 
 	SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
 	SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);

+ 36 - 20
CMake/FileList.cmake

@@ -59,16 +59,24 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.h
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.h
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.h
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.h
     ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.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/ReplacedFormattingContext.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/Memory.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Pool.h
     ${PROJECT_SOURCE_DIR}/Source/Core/Pool.h
@@ -160,6 +168,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEffectInstancer.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEffectInstancer.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEngineInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEngineInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontGlyph.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/Geometry.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/GeometryUtilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/GeometryUtilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Header.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Header.h
@@ -309,16 +318,23 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.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/ReplacedFormattingContext.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/Log.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Memory.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Memory.cpp

+ 8 - 0
CMakeLists.txt

@@ -890,6 +890,14 @@ if(BUILD_SAMPLES)
 	endif()
 	endif()
 endif()
 endif()
 
 
+#===================================
+# Source grouping for IDEs =========
+#===================================
+
+source_group("Core\\Layout" REGULAR_EXPRESSION "/Core/Layout/")
+source_group("Core\\Elements" REGULAR_EXPRESSION "/Core/Elements/")
+source_group("Core\\FontEngineDefault" REGULAR_EXPRESSION "/Core/FontEngineDefault/")
+
 #===================================
 #===================================
 # Add global options ===============
 # Add global options ===============
 #===================================
 #===================================

+ 8 - 4
Include/RmlUi/Core/Box.h

@@ -106,12 +106,16 @@ public:
 	/// @return The cumulative size of the edge.
 	/// @return The cumulative size of the edge.
 	float GetCumulativeEdge(Area area, Edge edge) const;
 	float GetCumulativeEdge(Area area, Edge edge) const;
 
 
-	/// Returns the size along a single direction of the given 'area', including all inner areas up-to and including 'area_end'.
+	/// Returns the size along a single direction starting at 'area_outer', up-to and including 'area_inner'.
 	/// @example GetSizeAcross(HORIZONTAL, BORDER, PADDING) returns the total width of the horizontal borders and paddings.
 	/// @example GetSizeAcross(HORIZONTAL, BORDER, PADDING) returns the total width of the horizontal borders and paddings.
 	/// @param direction The desired direction.
 	/// @param direction The desired direction.
-	/// @param area The widest area to include.
-	/// @param area_end The last area to include, anything inside this is excluded.
-	float GetSizeAcross(Direction direction, Area area, Area area_end = Area::CONTENT) const;
+	/// @param area_outer The widest area to include.
+	/// @param area_inner The last area to include, anything inside this is excluded.
+	float GetSizeAcross(Direction direction, Area area_outer, Area area_inner = Area::CONTENT) const;
+
+	/// Returns the size of the frame defined by the given area, not including inner areas.
+	/// @param area The area to use.
+	Vector2f GetFrameSize(Area area) const;
 
 
 	/// Compares the size of the content area and the other area edges.
 	/// Compares the size of the content area and the other area edges.
 	/// @return True if the boxes represent the same area.
 	/// @return True if the boxes represent the same area.

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

@@ -127,7 +127,7 @@ namespace Style {
 		Colourb color = Colourb(255, 255, 255);
 		Colourb color = Colourb(255, 255, 255);
 
 
 		FontWeight font_weight;
 		FontWeight font_weight;
-		
+
 		FontStyle font_style : 1;
 		FontStyle font_style : 1;
 		bool has_font_effect : 1;
 		bool has_font_effect : 1;
 		PointerEvents pointer_events : 1;
 		PointerEvents pointer_events : 1;
@@ -150,7 +150,8 @@ namespace Style {
 			max_height_type(LengthPercentage::Length),
 			max_height_type(LengthPercentage::Length),
 
 
 			perspective_origin_x_type(LengthPercentage::Percentage), perspective_origin_y_type(LengthPercentage::Percentage),
 			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),
 			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 perspective_origin_x_type : 1, perspective_origin_y_type : 1;
 		LengthPercentage::Type transform_origin_x_type : 1, transform_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;
 		LengthPercentageAuto::Type flex_basis_type : 2;
 		LengthPercentage::Type row_gap_type : 1, column_gap_type : 1;
 		LengthPercentage::Type row_gap_type : 1, column_gap_type : 1;
@@ -268,6 +270,8 @@ namespace Style {
 		TransformOrigin   transform_origin_x()         const { return LengthPercentage(rare.transform_origin_x_type, rare.transform_origin_x); }
 		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); }
 		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; }
 		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); }
 		AlignContent      align_content()              const { return GetLocalPropertyKeyword(PropertyId::AlignContent, AlignContent::Stretch); }
 		AlignItems        align_items()                const { return GetLocalPropertyKeyword(PropertyId::AlignItems, AlignItems::Stretch); }
 		AlignItems        align_items()                const { return GetLocalPropertyKeyword(PropertyId::AlignItems, AlignItems::Stretch); }
 		AlignSelf         align_self()                 const { return GetLocalPropertyKeyword(PropertyId::AlignSelf, AlignSelf::Auto); }
 		AlignSelf         align_self()                 const { return GetLocalPropertyKeyword(PropertyId::AlignSelf, AlignSelf::Auto); }
@@ -356,6 +360,8 @@ namespace Style {
 		void flex_basis                (FlexBasis value)         { rare.flex_basis_type            = value.type; rare.flex_basis                 = value.value; }
 		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 transform_origin_z        (float value)             { rare.transform_origin_z         = value; }
 		void perspective               (float value)             { rare.perspective                = 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_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_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; }
 		void border_bottom_right_radius(float value)             { rare.border_bottom_right_radius = (int16_t)value; }
@@ -402,11 +408,20 @@ namespace Style {
 
 
 } // 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;
 using ComputedValues = Style::ComputedValues;
 
 

+ 22 - 21
Include/RmlUi/Core/Element.h

@@ -55,9 +55,9 @@ class ElementDefinition;
 class ElementDocument;
 class ElementDocument;
 class ElementScroll;
 class ElementScroll;
 class ElementStyle;
 class ElementStyle;
-class LayoutEngine;
-class LayoutInlineBox;
-class LayoutBlockBox;
+class ContainerBox;
+class InlineLevelBox;
+class ReplacedBox;
 class PropertiesIteratorView;
 class PropertiesIteratorView;
 class PropertyDictionary;
 class PropertyDictionary;
 class RenderInterface;
 class RenderInterface;
@@ -65,7 +65,7 @@ class StyleSheet;
 class StyleSheetContainer;
 class StyleSheetContainer;
 class TransformState;
 class TransformState;
 struct ElementMeta;
 struct ElementMeta;
-struct StackingOrderedChild;
+struct StackingContextChild;
 
 
 /**
 /**
 	A generic element in the DOM tree.
 	A generic element in the DOM tree.
@@ -137,11 +137,10 @@ public:
 	/// @return The box area used as the element's client area.
 	/// @return The box area used as the element's client area.
 	Box::Area GetClientArea() const;
 	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.
 	/// Sets the box describing the size of the element, and removes all others.
 	/// @param[in] box The new dimensions box for the element.
 	/// @param[in] box The new dimensions box for the element.
 	void SetBox(const Box& box);
 	void SetBox(const Box& box);
@@ -161,15 +160,18 @@ public:
 	/// @return the number of boxes making up this element's geometry.
 	/// @return the number of boxes making up this element's geometry.
 	int GetNumBoxes();
 	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;
 	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
+	/// Gets the intrinsic dimensions of this element, if it is a replaced element with an inherent size. This size will
 	/// only be overriden by a styled width or height.
 	/// only be overriden by a styled width or height.
 	/// @param[out] dimensions The dimensions to size, if appropriate.
 	/// @param[out] dimensions The dimensions to size, if appropriate.
 	/// @param[out] ratio The intrinsic ratio (width/height), if appropriate.
 	/// @param[out] ratio The intrinsic ratio (width/height), if appropriate.
-	/// @return True if the element has intrinsic dimensions, false otherwise. The default element will return false.
+	/// @return True if the element is a replaced element with intrinsic dimensions, false otherwise.
 	virtual bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio);
 	virtual bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio);
+	/// Returns true if the element is replaced, thereby handling its own rendering.
+	/// @return True if the element is a replaced element.
+	bool IsReplaced();
 
 
 	/// Checks if a given point in screen coordinates lies within the bordered area of this element.
 	/// Checks if a given point in screen coordinates lies within the bordered area of this element.
 	/// @param[in] point The point to test.
 	/// @param[in] point The point to test.
@@ -674,8 +676,8 @@ private:
 	void SetBaseline(float baseline);
 	void SetBaseline(float baseline);
 
 
 	void BuildLocalStackingContext();
 	void BuildLocalStackingContext();
-	void BuildStackingContext(ElementList* stacking_context);
-	static void BuildStackingContextForTable(Vector<StackingOrderedChild>& ordered_children, Element* child);
+	void AddChildrenToStackingContext(Vector<StackingContextChild>& stacking_children);
+	void AddToStackingContext(Vector<StackingContextChild>& stacking_children, bool is_flex_item, bool is_non_dom_element);
 	void DirtyStackingContext();
 	void DirtyStackingContext();
 
 
 	void UpdateDefinition();
 	void UpdateDefinition();
@@ -771,9 +773,8 @@ private:
 	Box main_box;
 	Box main_box;
 	PositionedBoxList additional_boxes;
 	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 baseline;
 	float z_index;
 	float z_index;
@@ -788,9 +789,9 @@ private:
 
 
 	friend class Rml::Context;
 	friend class Rml::Context;
 	friend class Rml::ElementStyle;
 	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::ReplacedBox;
 	friend class Rml::ElementScroll;
 	friend class Rml::ElementScroll;
 	friend RMLUICORE_API void Rml::ReleaseFontResources();
 	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.
 	/// Returns the raw string this text element contains.
 	const String& GetText() const;
 	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.
 	/// 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 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()!
 	/// @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] 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] 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 &amp; into &.
 	/// @param[in] decode_escape_characters Decode escaped characters such as &amp; 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.
 	/// @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.
 	/// Clears all lines of generated text and prepares the element for generating new lines.
 	void ClearLines();
 	void ClearLines();
 	/// Adds a new line into the text element.
 	/// 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_position The position of this line, as an offset from the first line.
 	/// @param[in] line The contents of the 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.
 	/// Prevents the element from dirtying its document's layout when its text is changed.
 	void SuppressAutoLayout();
 	void SuppressAutoLayout();

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

@@ -28,8 +28,9 @@
 #ifndef RMLUI_CORE_FONTENGINEINTERFACE_H
 #ifndef RMLUI_CORE_FONTENGINEINTERFACE_H
 #define RMLUI_CORE_FONTENGINEINTERFACE_H
 #define RMLUI_CORE_FONTENGINEINTERFACE_H
 
 
-#include "Header.h"
+#include "FontMetrics.h"
 #include "Geometry.h"
 #include "Geometry.h"
+#include "Header.h"
 #include "StyleTypes.h"
 #include "StyleTypes.h"
 #include "Types.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.
 	/// @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);
 	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[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.
 	/// Called by RmlUi when it wants to retrieve the width of a string when rendered with this handle.
 	/// @param[in] handle The font 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.
 	/// @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);
 	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[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.
 	/// 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.
 	/// Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.

+ 17 - 2
Include/RmlUi/Core/StyleTypes.h

@@ -56,7 +56,22 @@ namespace Style {
 	using Margin = LengthPercentageAuto;
 	using Margin = LengthPercentageAuto;
 	using Padding = LengthPercentage;
 	using Padding = LengthPercentage;
 
 
-	enum class Display : uint8_t { None, Block, Inline, InlineBlock, Flex, Table, TableRow, TableRowGroup, TableColumn, TableColumnGroup, TableCell };
+	enum class Display : uint8_t {
+		None,
+		Block,
+		Inline,
+		InlineBlock,
+		FlowRoot,
+		Flex,
+		InlineFlex,
+		Table,
+		InlineTable,
+		TableRow,
+		TableRowGroup,
+		TableColumn,
+		TableColumnGroup,
+		TableCell
+	};
 	enum class Position : uint8_t { Static, Relative, Absolute, Fixed };
 	enum class Position : uint8_t { Static, Relative, Absolute, Fixed };
 
 
 	using Top = LengthPercentageAuto;
 	using Top = LengthPercentageAuto;
@@ -89,7 +104,7 @@ namespace Style {
 		{}
 		{}
 	};
 	};
 	struct VerticalAlign {
 	struct VerticalAlign {
-		enum Type : uint8_t { Baseline, Middle, Sub, Super, TextTop, TextBottom, Top, Bottom, Length } type;
+		enum Type : uint8_t { Baseline, Middle, Sub, Super, TextTop, TextBottom, Top, Center, Bottom, Length } type;
 		float value; // For length type
 		float value; // For length type
 		VerticalAlign(Type type = Baseline) : type(type), value(0) {}
 		VerticalAlign(Type type = Baseline) : type(type), value(0) {}
 		VerticalAlign(float value) : type(Length), value(value) {}
 		VerticalAlign(float value) : type(Length), value(value) {}

+ 3 - 1
Include/RmlUi/Core/Vector2.h

@@ -43,9 +43,11 @@ template < typename Type >
 class Vector2
 class Vector2
 {
 {
 	public:
 	public:
+		/// Default constructor.
+		inline Vector2();
 		/// Initialising constructor.
 		/// Initialising constructor.
 		/// @param[in] v Initial value of each element in the vector.
 		/// @param[in] v Initial value of each element in the vector.
-		explicit inline Vector2(Type v = Type{ 0 });
+		explicit inline Vector2(Type v);
 		/// Initialising constructor.
 		/// Initialising constructor.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] y Initial y-value of the vector.
 		/// @param[in] y Initial y-value of the vector.

+ 5 - 0
Include/RmlUi/Core/Vector2.inl

@@ -28,6 +28,11 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
+// Default constructor.
+template <typename Type>
+Vector2<Type>::Vector2() : x{0}, y{0}
+{}
+
 // Initialising constructor.
 // Initialising constructor.
 template < typename Type >
 template < typename Type >
 Vector2< Type >::Vector2(Type v) : x(v), y(v)
 Vector2< Type >::Vector2(Type v) : x(v), y(v)

+ 3 - 1
Include/RmlUi/Core/Vector3.h

@@ -43,9 +43,11 @@ template < typename Type >
 class Vector3
 class Vector3
 {
 {
 	public:
 	public:
+		/// Default constructor.
+		inline Vector3();
 		/// Initialising constructor.
 		/// Initialising constructor.
 		/// @param[in] v Initial value of each element in the vector.
 		/// @param[in] v Initial value of each element in the vector.
-		explicit inline Vector3(Type v = Type{ 0 });
+		explicit inline Vector3(Type v);
 		/// Initialising constructor.
 		/// Initialising constructor.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] y Initial y-value of the vector.
 		/// @param[in] y Initial y-value of the vector.

+ 5 - 0
Include/RmlUi/Core/Vector3.inl

@@ -30,6 +30,11 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
+// Default constructor.
+template <typename Type>
+Vector3<Type>::Vector3() : x{0}, y{0}, z{0}
+{}
+
 // Initialising constructor.
 // Initialising constructor.
 template < typename Type >
 template < typename Type >
 Vector3< Type >::Vector3(Type v) : x(v), y(v), z(v)
 Vector3< Type >::Vector3(Type v) : x(v), y(v), z(v)

+ 3 - 1
Include/RmlUi/Core/Vector4.h

@@ -44,9 +44,11 @@ template < typename Type >
 class Vector4
 class Vector4
 {
 {
 	public:
 	public:
+		/// Default constructor.
+		inline Vector4();
 		/// Initialising constructor.
 		/// Initialising constructor.
 		/// @param[in] v Initial value of each element in the vector.
 		/// @param[in] v Initial value of each element in the vector.
-		explicit inline Vector4(Type v = Type{ 0 });
+		explicit inline Vector4(Type v);
 		/// Initialising constructor.
 		/// Initialising constructor.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] x Initial x-value of the vector.
 		/// @param[in] y Initial y-value of the vector.
 		/// @param[in] y Initial y-value of the vector.

+ 5 - 0
Include/RmlUi/Core/Vector4.inl

@@ -30,6 +30,11 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
+// Default constructor.
+template <typename Type>
+Vector4<Type>::Vector4() : x{0}, y{0}, z{0}, w{0}
+{}
+
 // Initialising constructor.
 // Initialising constructor.
 template < typename Type >
 template < typename Type >
 Vector4< Type >::Vector4(Type v) 
 Vector4< Type >::Vector4(Type v) 

+ 1 - 2
Samples/assets/invader.rcss

@@ -197,6 +197,7 @@ div#title_bar span
 div#window
 div#window
 {
 {
 	width: auto;
 	width: auto;
+	height: 100%;
 	padding: 10dp 15dp;
 	padding: 10dp 15dp;
 	
 	
 	decorator: tiled-box(
 	decorator: tiled-box(
@@ -260,9 +261,7 @@ input.submit
 
 
 	width: 159dp;
 	width: 159dp;
 	height: 33dp;
 	height: 33dp;
-
 	padding-top: 12dp;
 	padding-top: 12dp;
-	vertical-align: -18dp;
 
 
 	font-size: 16dp;
 	font-size: 16dp;
 	text-align: center;
 	text-align: center;

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

@@ -82,7 +82,7 @@ namespace FontProviderBitmap
 				return false;
 				return false;
 
 
 			// Fill the remaining metrics
 			// 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;
 			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)
 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")
 	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.x = Get(attributes, "scaleW", 0.f);
 		texture_dimensions.y = Get(attributes, "scaleH", 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];
 		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.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.x = Get(attributes, "x", 0.f);
 		glyph.position.y = Get(attributes, "y", 0.f);
 		glyph.position.y = Get(attributes, "y", 0.f);
 		glyph.dimension.x = Get(attributes, "width", 0.f);
 		glyph.dimension.x = Get(attributes, "width", 0.f);
 		glyph.dimension.y = Get(attributes, "height", 0.f);
 		glyph.dimension.y = Get(attributes, "height", 0.f);
 
 
 		if (character == (Character)'x')
 		if (character == (Character)'x')
-			metrics.x_height = (int)glyph.dimension.y;
+			metrics.x_height = glyph.dimension.y;
 	}
 	}
 	else if (name == "kerning")
 	else if (name == "kerning")
 	{
 	{

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

@@ -51,14 +51,6 @@ struct BitmapGlyph {
 	Vector2f dimension = { 0, 0 };
 	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.
 // A mapping of characters to their glyphs.
 using FontGlyphs = Rml::UnorderedMap<Character, BitmapGlyph>;
 using FontGlyphs = Rml::UnorderedMap<Character, BitmapGlyph>;
 
 
@@ -68,7 +60,8 @@ using FontKerning = Rml::UnorderedMap<std::uint64_t, int>;
 
 
 class FontFaceBitmap {
 class FontFaceBitmap {
 public:
 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.
 	// Get width of string.
 	int GetStringWidth(const String& string, Character prior_character);
 	int GetStringWidth(const String& string, Character prior_character);
@@ -124,7 +117,7 @@ public:
 	String texture_name;
 	String texture_name;
 	Vector2f texture_dimensions = { 0, 0 };
 	Vector2f texture_dimensions = { 0, 0 };
 
 
-	FontMetrics metrics;
+	FontMetrics metrics = {};
 	FontGlyphs glyphs;
 	FontGlyphs glyphs;
 	FontKerning kerning;
 	FontKerning kerning;
 };
 };

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

@@ -67,35 +67,10 @@ FontEffectsHandle FontEngineInterfaceBitmap::PrepareFontEffects(FontFaceHandle /
 	return 0;
 	return 0;
 }
 }
 
 
-int FontEngineInterfaceBitmap::GetSize(FontFaceHandle handle)
+const FontMetrics& FontEngineInterfaceBitmap::GetFontMetrics(FontFaceHandle handle)
 {
 {
 	auto handle_bitmap = reinterpret_cast<FontFaceBitmap*>(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)
 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::FontEffectList;
 using Rml::GeometryList;
 using Rml::GeometryList;
+using Rml::FontMetrics;
 
 
 
 
 class FontEngineInterfaceBitmap : public Rml::FontEngineInterface
 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.
 	/// 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;
 	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.
 	/// 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;
 	int GetStringWidth(FontFaceHandle handle, const String& string, Character prior_character = Character::Null) override;

+ 1 - 0
Samples/basic/lottie/data/lottie.rml

@@ -21,6 +21,7 @@
 				text-align: left;
 				text-align: left;
 			}
 			}
 			lottie {
 			lottie {
+				display: block;
 				width: 100%;
 				width: 100%;
 				max-height: 100%;
 				max-height: 100%;
 			}
 			}

+ 9 - 4
Samples/invaders/data/background.rml

@@ -18,14 +18,19 @@
 				bottom-density: 20;
 				bottom-density: 20;
 			}
 			}
 
 
-			starfield
+			img, starfield
 			{
 			{
 				display: block;
 				display: block;
+				position: absolute;
+				top: 0;
+				left: 0;
+			}
+			
+			starfield
+			{
+				decorator: stars;
 				width: 100%;
 				width: 100%;
 				height: 100%;
 				height: 100%;
-				z-index: 1;
-
-				decorator: stars;
 			}
 			}
 		</style>
 		</style>
 	</head>
 	</head>

+ 1 - 0
Samples/invaders/data/game.rml

@@ -23,6 +23,7 @@
 			
 			
 			div
 			div
 			{
 			{
+				position: relative;
 				height: 43dp;
 				height: 43dp;
 				padding: 12dp 0 0 72dp;
 				padding: 12dp 0 0 72dp;
 				margin: 0 20dp;
 				margin: 0 20dp;

+ 9 - 4
Samples/luainvaders/data/background.rml

@@ -18,14 +18,19 @@
 				bottom-density: 20;
 				bottom-density: 20;
 			}
 			}
 
 
-			starfield
+			img, starfield
 			{
 			{
 				display: block;
 				display: block;
+				position: absolute;
+				top: 0;
+				left: 0;
+			}
+			
+			starfield
+			{
+				decorator: stars;
 				width: 100%;
 				width: 100%;
 				height: 100%;
 				height: 100%;
-				z-index: 1;
-
-				decorator: stars;
 			}
 			}
 		</style>
 		</style>
 	</head>
 	</head>

+ 1 - 0
Samples/luainvaders/data/game.rml

@@ -22,6 +22,7 @@
 
 
 			div
 			div
 			{
 			{
+				position: relative;
 				height: 43dp;
 				height: 43dp;
 				padding: 12dp 0 0 72dp;
 				padding: 12dp 0 0 72dp;
 				margin: 0 20dp;
 				margin: 0 20dp;

+ 17 - 6
Source/Core/Box.cpp

@@ -15,7 +15,7 @@
  *
  *
  * The above copyright notice and this permission notice shall be included in
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -95,22 +95,33 @@ float Box::GetCumulativeEdge(Area area, Edge edge) const
 	return size;
 	return size;
 }
 }
 
 
-float Box::GetSizeAcross(Direction direction, Area area, Area area_end) const
+float Box::GetSizeAcross(Direction direction, Area area_outer, Area area_inner) const
 {
 {
 	static_assert(HORIZONTAL == 1 && VERTICAL == 0, "");
 	static_assert(HORIZONTAL == 1 && VERTICAL == 0, "");
-	RMLUI_ASSERT(area <= area_end && direction <= 1);
+	RMLUI_ASSERT(area_outer <= area_inner && direction <= 1);
 
 
 	float size = 0.0f;
 	float size = 0.0f;
 
 
-	if (area_end == CONTENT)
+	if (area_inner == CONTENT)
 		size = (direction == HORIZONTAL ? content.x : content.y);
 		size = (direction == HORIZONTAL ? content.x : content.y);
-	
-	for (int i = area; i <= area_end && i < CONTENT; i++)
+
+	for (int i = area_outer; i <= area_inner && i < CONTENT; i++)
 		size += (area_edges[i][TOP + (int)direction] + area_edges[i][BOTTOM + (int)direction]);
 		size += (area_edges[i][TOP + (int)direction] + area_edges[i][BOTTOM + (int)direction]);
 
 
 	return size;
 	return size;
 }
 }
 
 
+Vector2f Box::GetFrameSize(Area area) const
+{
+	if (area == CONTENT)
+		return content;
+
+	return {
+		area_edges[area][RIGHT] + area_edges[area][LEFT],
+		area_edges[area][TOP] + area_edges[area][BOTTOM],
+	};
+}
+
 // Compares the size of the content area and the other area edges.
 // Compares the size of the content area and the other area edges.
 bool Box::operator==(const Box& rhs) const
 bool Box::operator==(const Box& rhs) const
 {
 {

+ 21 - 1
Source/Core/ComputeProperty.cpp

@@ -29,6 +29,7 @@
 #include "ComputeProperty.h"
 #include "ComputeProperty.h"
 #include "../../Include/RmlUi/Core/ComputedValues.h"
 #include "../../Include/RmlUi/Core/ComputedValues.h"
 #include "../../Include/RmlUi/Core/Property.h"
 #include "../../Include/RmlUi/Core/Property.h"
+#include "../../Include/RmlUi/Core/StringUtilities.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
@@ -238,7 +239,7 @@ Style::VerticalAlign ComputeVerticalAlign(const Property* property, float line_h
 	}
 	}
 	else if (property->unit & Property::PERCENT)
 	else if (property->unit & Property::PERCENT)
 	{
 	{
-		return Style::VerticalAlign(property->Get<float>() * line_height);
+		return Style::VerticalAlign(property->Get<float>() * line_height * 0.01f);
 	}
 	}
 
 
 	RMLUI_ASSERT(property->unit & Property::KEYWORD);
 	RMLUI_ASSERT(property->unit & Property::KEYWORD);
@@ -313,4 +314,23 @@ uint16_t ComputeBorderWidth(float computed_length)
 	return uint16_t(computed_length + 0.5f);
 	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
 } // 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);
 uint16_t ComputeBorderWidth(float computed_length);
 
 
+String GetFontFaceDescription(const String& font_family, Style::FontStyle style, Style::FontWeight weight);
+
 extern const Style::ComputedValues DefaultComputedValues;
 extern const Style::ComputedValues DefaultComputedValues;
 
 
 } // namespace Rml
 } // namespace Rml

+ 6 - 6
Source/Core/ComputedValues.cpp

@@ -68,22 +68,22 @@ String Style::ComputedValues::cursor() const
 	return String();
 	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)
 	if (length.type == Style::LengthPercentageAuto::Length)
 		return length.value;
 		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 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)
 	if (length.type == Style::LengthPercentage::Length)
 		return length.value;
 		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 length.value * 0.01f * base_value;
-	return 0.0f;
+	return default_value;
 }
 }
 
 
 } // namespace Rml
 } // namespace Rml

+ 155 - 130
Source/Core/Element.cpp

@@ -53,7 +53,7 @@
 #include "EventDispatcher.h"
 #include "EventDispatcher.h"
 #include "EventSpecification.h"
 #include "EventSpecification.h"
 #include "ElementDecoration.h"
 #include "ElementDecoration.h"
-#include "LayoutEngine.h"
+#include "Layout/LayoutEngine.h"
 #include "PluginRegistry.h"
 #include "PluginRegistry.h"
 #include "PropertiesIterator.h"
 #include "PropertiesIterator.h"
 #include "Pool.h"
 #include "Pool.h"
@@ -109,10 +109,8 @@ static Pool< ElementMeta > element_meta_chunk_pool(200, true);
 Element::Element(const String& tag) :
 Element::Element(const String& tag) :
 	local_stacking_context(false), local_stacking_context_forced(false), stacking_context_dirty(false), computed_values_are_default_initialized(true),
 	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),
 	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));
 	RMLUI_ASSERT(tag == StringUtilities::ToLower(tag));
 	parent = nullptr;
 	parent = nullptr;
@@ -232,11 +230,6 @@ void Element::Render()
 
 
 	UpdateTransformState();
 	UpdateTransformState();
 
 
-	// Render all elements in our local stacking context that have a z-index beneath our local index of 0.
-	size_t i = 0;
-	for (; i < stacking_context.size() && stacking_context[i]->z_index < 0; ++i)
-		stacking_context[i]->Render();
-
 	// Apply our transform
 	// Apply our transform
 	ElementUtilities::ApplyTransform(*this);
 	ElementUtilities::ApplyTransform(*this);
 
 
@@ -253,9 +246,9 @@ void Element::Render()
 		}
 		}
 	}
 	}
 
 
-	// Render the rest of the elements in the stacking context.
-	for (; i < stacking_context.size(); ++i)
-		stacking_context[i]->Render();
+	// Render all elements in our local stacking context.
+	for (Element* element : stacking_context)
+		element->Render();
 }
 }
 
 
 // Clones this element, returning a new, unparented element.
 // Clones this element, returning a new, unparented element.
@@ -415,23 +408,20 @@ Vector2f Element::GetAbsoluteOffset(Box::Area area)
 	{
 	{
 		absolute_offset_dirty = false;
 		absolute_offset_dirty = false;
 
 
-		if (offset_parent != nullptr)
+		if (offset_parent)
 			absolute_offset = offset_parent->GetAbsoluteOffset(Box::BORDER) + relative_offset_base + relative_offset_position;
 			absolute_offset = offset_parent->GetAbsoluteOffset(Box::BORDER) + relative_offset_base + relative_offset_position;
 		else
 		else
 			absolute_offset = relative_offset_base + relative_offset_position;
 			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)
 		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.
+			for (Element* ancestor = parent; ancestor && ancestor != offset_parent; ancestor = ancestor->parent)
+				absolute_offset += ancestor->relative_offset_position;
 		}
 		}
 	}
 	}
 
 
@@ -451,17 +441,11 @@ Box::Area Element::GetClientArea() const
 }
 }
 
 
 // Sets the dimensions of the element's internal content.
 // 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.x = Math::Min(scroll_offset.x, GetScrollWidth() - GetClientWidth());
 		scroll_offset.y = Math::Min(scroll_offset.y, GetScrollHeight() - GetClientHeight());
 		scroll_offset.y = Math::Min(scroll_offset.y, GetScrollHeight() - GetClientHeight());
@@ -540,6 +524,13 @@ bool Element::GetIntrinsicDimensions(Vector2f& RMLUI_UNUSED_PARAMETER(dimensions
 	return false;
 	return false;
 }
 }
 
 
+bool Element::IsReplaced()
+{
+	Vector2f unused_dimensions;
+	float unused_ratio = 0.f;
+	return GetIntrinsicDimensions(unused_dimensions, unused_ratio);
+}
+
 // Checks if a given point in screen coordinates lies within the bordered area of this element.
 // Checks if a given point in screen coordinates lies within the bordered area of this element.
 bool Element::IsPointWithinElement(const Vector2f point)
 bool Element::IsPointWithinElement(const Vector2f point)
 {
 {
@@ -1025,13 +1016,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.
 // Gets the width of the scrollable content of the element; it includes the element padding but not its margin.
 float Element::GetScrollWidth()
 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.
 // Gets the height of the scrollable content of the element; it includes the element padding but not its margin.
 float Element::GetScrollHeight()
 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.
 // Gets the object representing the declarations of an element's style attributes.
@@ -2289,128 +2280,162 @@ void Element::SetBaseline(float in_baseline)
 	baseline = in_baseline;
 	baseline = in_baseline;
 }
 }
 
 
-void Element::BuildLocalStackingContext()
+enum class RenderOrder {
+	StackNegative, // Local stacking context with z < 0.
+	Block,
+	TableColumnGroup,
+	TableColumn,
+	TableRowGroup,
+	TableRow,
+	TableCell,
+	Floating,
+	Inline,
+	Positioned,    // Positioned element, or local stacking context with z == 0.
+	StackPositive, // Local stacking context with z > 0.
+};
+struct StackingContextChild {
+	Element* element = nullptr;
+	RenderOrder order = {};
+};
+static bool operator<(const StackingContextChild& lhs, const StackingContextChild& rhs)
 {
 {
-	stacking_context_dirty = false;
-	stacking_context.clear();
-
-	BuildStackingContext(&stacking_context);
-	std::stable_sort(stacking_context.begin(), stacking_context.end(), [](const Element* lhs, const Element* rhs) { return lhs->GetZIndex() < rhs->GetZIndex(); });
+	if (int(lhs.order) == int(rhs.order))
+		return lhs.element->GetZIndex() < rhs.element->GetZIndex();
+	return int(lhs.order) < int(rhs.order);
 }
 }
 
 
-enum class RenderOrder { Block, TableColumnGroup, TableColumn, TableRowGroup, TableRow, TableCell, Inline, Floating, Positioned };
-struct StackingOrderedChild {
-	Element* element;
-	RenderOrder order;
-	bool include_children;
-};
-
-void Element::BuildStackingContext(ElementList* new_stacking_context)
+// Treat all children in the range [index_begin, end) as if the parent created a new stacking context, by sorting them
+// separately and then assigning their parent's paint order. However, positioned and descendants which create a new
+// stacking context should be considered part of the parent stacking context. See CSS 2, Appendix E.
+static void StackingContext_MakeAtomicRange(Vector<StackingContextChild>& stacking_children, size_t index_begin, RenderOrder parent_render_order)
 {
 {
-	RMLUI_ZoneScoped;
-
-	// Build the list of ordered children. Our child list is sorted within the stacking context so stacked elements
-	// will render in the right order; ie, positioned elements will render on top of inline elements, which will render
-	// on top of floated elements, which will render on top of block elements.
-	Vector< StackingOrderedChild > ordered_children;
+	std::stable_sort(stacking_children.begin() + index_begin, stacking_children.end());
 
 
-	const size_t num_children = children.size();
-	ordered_children.reserve(num_children);
-
-	if (GetDisplay() == Style::Display::Table)
+	for (auto it = stacking_children.begin() + index_begin; it != stacking_children.end(); ++it)
 	{
 	{
-		BuildStackingContextForTable(ordered_children, this);
+		auto order = it->order;
+		if (order != RenderOrder::StackNegative && order != RenderOrder::Positioned && order != RenderOrder::StackPositive)
+			it->order = parent_render_order;
 	}
 	}
-	else
-	{
-		for (size_t i = 0; i < num_children; ++i)
-		{
-			Element* child = children[i].get();
-
-			if (!child->IsVisible())
-				continue;
-
-			ordered_children.emplace_back();
-			StackingOrderedChild& ordered_child = ordered_children.back();
-
-			ordered_child.element = child;
-			ordered_child.order = RenderOrder::Inline;
-			ordered_child.include_children = !child->local_stacking_context;
+}
 
 
-			const Style::Display child_display = child->GetDisplay();
+void Element::BuildLocalStackingContext()
+{
+	stacking_context_dirty = false;
 
 
-			if (child->GetPosition() != Style::Position::Static)
-				ordered_child.order = RenderOrder::Positioned;
-			else if (child->GetFloat() != Style::Float::None)
-				ordered_child.order = RenderOrder::Floating;
-			else if (child_display == Style::Display::Block || child_display == Style::Display::Table || child_display == Style::Display::Flex)
-				ordered_child.order = RenderOrder::Block;
-			else
-				ordered_child.order = RenderOrder::Inline;
-		}
-	}
+	Vector<StackingContextChild> stacking_children;
+	AddChildrenToStackingContext(stacking_children);
+	std::stable_sort(stacking_children.begin(), stacking_children.end());
 
 
-	// Sort the list!
-	std::stable_sort(ordered_children.begin(), ordered_children.end(), [](const StackingOrderedChild& lhs, const StackingOrderedChild& rhs) { return int(lhs.order) < int(rhs.order); });
+	stacking_context.resize(stacking_children.size());
+	for (size_t i = 0; i < stacking_children.size(); i++)
+		stacking_context[i] = stacking_children[i].element;
+}
 
 
-	// Add the list of ordered children into the stacking context in order.
-	for (size_t i = 0; i < ordered_children.size(); ++i)
+void Element::AddChildrenToStackingContext(Vector<StackingContextChild>& stacking_children)
+{
+	bool is_flex_container = (GetDisplay() == Style::Display::Flex);
+	const int num_children = (int)children.size();
+	for (int i = 0; i < num_children; ++i)
 	{
 	{
-		new_stacking_context->push_back(ordered_children[i].element);
-
-		if (ordered_children[i].include_children)
-			ordered_children[i].element->BuildStackingContext(new_stacking_context);
+		const bool is_non_dom_element = (i >= num_children - num_non_dom_children);
+		children[i]->AddToStackingContext(stacking_children, is_flex_container, is_non_dom_element);
 	}
 	}
 }
 }
 
 
-void Element::BuildStackingContextForTable(Vector<StackingOrderedChild>& ordered_children, Element* parent)
+void Element::AddToStackingContext(Vector<StackingContextChild>& stacking_children, bool is_flex_item, bool is_non_dom_element)
 {
 {
-	const size_t num_children = parent->children.size();
+	using Style::Display;
 
 
-	for (size_t i = 0; i < num_children; ++i)
-	{
-		Element* child = parent->children[i].get();
+	if (!IsVisible())
+		return;
 
 
-		if (!child->IsVisible())
-			continue;
+	const Display display = GetDisplay();
 
 
-		ordered_children.emplace_back();
-		StackingOrderedChild& ordered_child = ordered_children.back();
-		ordered_child.element = child;
-		ordered_child.order = RenderOrder::Inline;
-		ordered_child.include_children = false;
+	RenderOrder order = RenderOrder::Inline;
+	bool include_children = true;
+	bool render_as_atomic_unit = false;
 
 
-		bool recurse_into_children = false;
+	if (local_stacking_context)
+	{
+		if (z_index > 0.f)
+			order = RenderOrder::StackPositive;
+		else if (z_index < 0.f)
+			order = RenderOrder::StackNegative;
+		else
+			order = RenderOrder::Positioned;
 
 
-		switch (child->GetDisplay())
+		include_children = false;
+	}
+	else if (display == Display::TableRow || display == Display::TableRowGroup || display == Display::TableColumn ||
+		display == Display::TableColumnGroup)
+	{
+		// Handle internal display values taking priority over position and float.
+		switch (display)
 		{
 		{
-		case Style::Display::TableRow:
-			ordered_child.order = RenderOrder::TableRow;
-			recurse_into_children = true;
-			break;
-		case Style::Display::TableRowGroup:
-			ordered_child.order = RenderOrder::TableRowGroup;
-			recurse_into_children = true;
-			break;
-		case Style::Display::TableColumn:
-			ordered_child.order = RenderOrder::TableColumn;
-			break;
-		case Style::Display::TableColumnGroup:
-			ordered_child.order = RenderOrder::TableColumnGroup;
-			recurse_into_children = true;
+		case Display::TableRow: order = RenderOrder::TableRow; break;
+		case Display::TableRowGroup: order = RenderOrder::TableRowGroup; break;
+		case Display::TableColumn: order = RenderOrder::TableColumn; break;
+		case Display::TableColumnGroup: order = RenderOrder::TableColumnGroup; break;
+		default: break;
+		}
+	}
+	else if (GetPosition() != Style::Position::Static)
+	{
+		order = RenderOrder::Positioned;
+		render_as_atomic_unit = true;
+	}
+	else if (GetFloat() != Style::Float::None)
+	{
+		order = RenderOrder::Floating;
+		render_as_atomic_unit = true;
+	}
+	else
+	{
+		switch (display)
+		{
+		case Display::Block:
+		case Display::FlowRoot:
+		case Display::Table:
+		case Display::Flex:
+			order = RenderOrder::Block;
+			render_as_atomic_unit = (display == Display::Table || is_flex_item);
 			break;
 			break;
-		case Style::Display::TableCell:
-			ordered_child.order = RenderOrder::TableCell;
-			ordered_child.include_children = !child->local_stacking_context;
+
+		case Display::Inline:
+		case Display::InlineBlock:
+		case Display::InlineFlex:
+		case Display::InlineTable:
+			order = RenderOrder::Inline;
+			render_as_atomic_unit = (display != Display::Inline || is_flex_item);
 			break;
 			break;
-		default:
-			ordered_child.order = RenderOrder::Positioned;
-			ordered_child.include_children = !child->local_stacking_context;
+
+		case Display::TableCell:
+			order = RenderOrder::TableCell;
+			render_as_atomic_unit = true;
 			break;
 			break;
+
+		case Display::TableRow:
+		case Display::TableRowGroup:
+		case Display::TableColumn:
+		case Display::TableColumnGroup:
+		case Display::None: RMLUI_ERROR; break; // Handled above.
 		}
 		}
+	}
+
+	if (is_non_dom_element)
+		render_as_atomic_unit = true;
+
+	stacking_children.push_back(StackingContextChild{this, order});
+
+	if (include_children && !children.empty())
+	{
+		const size_t index_child_begin = stacking_children.size();
+
+		AddChildrenToStackingContext(stacking_children);
 
 
-		if (recurse_into_children)
-			BuildStackingContextForTable(ordered_children, child);
+		if (render_as_atomic_unit)
+			StackingContext_MakeAtomicRange(stacking_children, index_child_begin, order);
 	}
 	}
 }
 }
 
 

+ 15 - 12
Source/Core/ElementDocument.cpp

@@ -37,7 +37,7 @@
 #include "DocumentHeader.h"
 #include "DocumentHeader.h"
 #include "ElementStyle.h"
 #include "ElementStyle.h"
 #include "EventDispatcher.h"
 #include "EventDispatcher.h"
-#include "LayoutEngine.h"
+#include "Layout/LayoutEngine.h"
 #include "StreamFile.h"
 #include "StreamFile.h"
 #include "StyleSheetFactory.h"
 #include "StyleSheetFactory.h"
 #include "Template.h"
 #include "Template.h"
@@ -440,7 +440,7 @@ void ElementDocument::UpdateLayout()
 // Updates the position of the document based on the style properties.
 // Updates the position of the document based on the style properties.
 void ElementDocument::UpdatePosition()
 void ElementDocument::UpdatePosition()
 {
 {
-	if(position_dirty)
+	if (position_dirty)
 	{
 	{
 		RMLUI_ZoneScoped;
 		RMLUI_ZoneScoped;
 
 
@@ -448,29 +448,32 @@ void ElementDocument::UpdatePosition()
 
 
 		Element* root = GetParentNode();
 		Element* root = GetParentNode();
 
 
-		// We only position ourselves if we are a child of our context's root element. That is, we don't want to proceed if we are unparented or an iframe document.
+		// We only position ourselves if we are a child of our context's root element. That is, we don't want to proceed
+		// if we are unparented or an iframe document.
 		if (!root || !context || (root != context->GetRootElement()))
 		if (!root || !context || (root != context->GetRootElement()))
 			return;
 			return;
 
 
-		Vector2f position;
 		// Work out our containing block; relative offsets are calculated against it.
 		// Work out our containing block; relative offsets are calculated against it.
-		Vector2f containing_block = root->GetBox().GetSize(Box::CONTENT);
-
+		const Vector2f containing_block = root->GetBox().GetSize();
 		auto& computed = GetComputedValues();
 		auto& computed = GetComputedValues();
+		const Box& box = GetBox();
+
+		Vector2f position;
 
 
 		if (computed.left().type != Style::Left::Auto)
 		if (computed.left().type != Style::Left::Auto)
 			position.x = ResolveValue(computed.left(), containing_block.x);
 			position.x = ResolveValue(computed.left(), containing_block.x);
 		else if (computed.right().type != Style::Right::Auto)
 		else if (computed.right().type != Style::Right::Auto)
-			position.x = (containing_block.x - GetBox().GetSize(Box::MARGIN).x) - ResolveValue(computed.right(), containing_block.x);
-		else
-			position.x = GetBox().GetEdge(Box::MARGIN, Box::LEFT);
+			position.x = containing_block.x - (box.GetSize(Box::MARGIN).x + ResolveValue(computed.right(), containing_block.x));
 
 
 		if (computed.top().type != Style::Top::Auto)
 		if (computed.top().type != Style::Top::Auto)
 			position.y = ResolveValue(computed.top(), containing_block.y);
 			position.y = ResolveValue(computed.top(), containing_block.y);
 		else if (computed.bottom().type != Style::Bottom::Auto)
 		else if (computed.bottom().type != Style::Bottom::Auto)
-			position.y = (containing_block.y - GetBox().GetSize(Box::MARGIN).y) - ResolveValue(computed.bottom(), containing_block.y);
-		else
-			position.y = GetBox().GetEdge(Box::MARGIN, Box::TOP);
+			position.y = containing_block.y - (box.GetSize(Box::MARGIN).y + ResolveValue(computed.bottom(), containing_block.y));
+
+		// Add the margin edge to the position, since inset properties (top/right/bottom/left) set the margin edge
+		// position, while offsets use the border edge.
+		position.x += box.GetEdge(Box::MARGIN, Box::LEFT);
+		position.y += box.GetEdge(Box::MARGIN, Box::TOP);
 
 
 		SetOffset(position, nullptr);
 		SetOffset(position, nullptr);
 	}
 	}

+ 41 - 34
Source/Core/ElementHandle.cpp

@@ -83,8 +83,24 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 			initialised = true;
 			initialised = true;
 		}
 		}
 
 
-		auto GetSize = [](Element* element, const ComputedValues& computed) {
-			return element->GetBox().GetSize((computed.box_sizing() == Style::BoxSizing::BorderBox) ? Box::BORDER : Box::CONTENT);
+		auto GetSize = [](const Box& box, const ComputedValues& computed) {
+			return box.GetSize((computed.box_sizing() == Style::BoxSizing::BorderBox) ? Box::BORDER : Box::CONTENT);
+		};
+
+		// Set any auto margins to their current value, since auto-margins may affect the size and position of an element.
+		auto SetDefiniteMargins = [](Element* element, const ComputedValues& computed) {
+			auto SetDefiniteMargin = [](Element* element, PropertyId margin_id, Box::Edge edge) {
+				element->SetProperty(margin_id, Property(Math::RoundFloat(element->GetBox().GetEdge(Box::MARGIN, edge)), Property::PX));
+			};
+			using Style::Margin;
+			if (computed.margin_top().type == Margin::Auto)
+				SetDefiniteMargin(element, PropertyId::MarginTop, Box::TOP);
+			if (computed.margin_right().type == Margin::Auto)
+				SetDefiniteMargin(element, PropertyId::MarginRight, Box::RIGHT);
+			if (computed.margin_bottom().type == Margin::Auto)
+				SetDefiniteMargin(element, PropertyId::MarginBottom, Box::BOTTOM);
+			if (computed.margin_left().type == Margin::Auto)
+				SetDefiniteMargin(element, PropertyId::MarginLeft, Box::LEFT);
 		};
 		};
 
 
 		if (event == EventId::Dragstart)
 		if (event == EventId::Dragstart)
@@ -95,31 +111,39 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 			// Store current element position and size
 			// Store current element position and size
 			if (move_target)
 			if (move_target)
 			{
 			{
-				move_original_position.x = move_target->GetOffsetLeft();
-				move_original_position.y = move_target->GetOffsetTop();
+				using namespace Style;
+				const Box& box = move_target->GetBox();
+				const auto& computed = move_target->GetComputedValues();
+
+				// Store the initial margin edge position, since top/left properties determine the margin position.
+				move_original_position.x = move_target->GetOffsetLeft() - box.GetEdge(Box::MARGIN, Box::LEFT);
+				move_original_position.y = move_target->GetOffsetTop() - box.GetEdge(Box::MARGIN, Box::TOP);
+
+				// Check if we have auto-size together with definite right/bottom; if so, the size needs to be fixed to the current size.
+				if (computed.width().type == Width::Auto && computed.right().type != Right::Auto)
+					move_target->SetProperty(PropertyId::Width, Property(Math::RoundFloat(GetSize(box, computed).x), Property::PX));
+				if (computed.height().type == Height::Auto && computed.bottom().type != Bottom::Auto)
+					move_target->SetProperty(PropertyId::Height, Property(Math::RoundFloat(GetSize(box, computed).y), Property::PX));
+
+				SetDefiniteMargins(move_target, computed);
 			}
 			}
 			if (size_target)
 			if (size_target)
 			{
 			{
-				size_original_size = GetSize(size_target, size_target->GetComputedValues());
+				using namespace Style;
+				const Box& box = size_target->GetBox();
+				const auto& computed = size_target->GetComputedValues();
+
+				size_original_size = GetSize(box, computed);
+
+				SetDefiniteMargins(size_target, computed);
 			}
 			}
 		}
 		}
 		else if (event == EventId::Drag)
 		else if (event == EventId::Drag)
 		{
 		{
-			// Work out the delta
-			Vector2f delta = event.GetUnprojectedMouseScreenPos() - drag_start;
+			const Vector2f delta = event.GetUnprojectedMouseScreenPos() - drag_start;
 
 
-			// Update the move and size objects
 			if (move_target)
 			if (move_target)
 			{
 			{
-				using namespace Style;
-				const auto& computed = move_target->GetComputedValues();
-
-				// Check if we have auto-size together with definite right/bottom; if so, the size needs to be fixed to the current size.
-				if (computed.width().type == Width::Auto && computed.right().type != Top::Auto)
-					move_target->SetProperty(PropertyId::Width, Property(Math::RoundFloat(GetSize(move_target, computed).x), Property::PX));
-				if (computed.height().type == Width::Auto && computed.bottom().type != Top::Auto)
-					move_target->SetProperty(PropertyId::Height, Property(Math::RoundFloat(GetSize(move_target, computed).y), Property::PX));
-
 				const Vector2f new_position = (move_original_position + delta).Round();
 				const Vector2f new_position = (move_original_position + delta).Round();
 				move_target->SetProperty(PropertyId::Left, Property(new_position.x, Property::PX));
 				move_target->SetProperty(PropertyId::Left, Property(new_position.x, Property::PX));
 				move_target->SetProperty(PropertyId::Top, Property(new_position.y, Property::PX));
 				move_target->SetProperty(PropertyId::Top, Property(new_position.y, Property::PX));
@@ -127,23 +151,6 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 
 
 			if (size_target)
 			if (size_target)
 			{
 			{
-				auto SetDefiniteMargin = [](Element* element, PropertyId margin_id, Box::Edge edge) {
-					element->SetProperty(margin_id, Property(Math::RoundFloat(element->GetBox().GetEdge(Box::MARGIN, edge)), Property::PX));
-				};
-
-				using namespace Style;
-				const auto& computed = size_target->GetComputedValues();
-
-				// Check if we have auto-margins; if so, they have to be set to the current margins.
-				if (computed.margin_top().type == Margin::Auto)
-					SetDefiniteMargin(size_target, PropertyId::MarginTop, Box::TOP);
-				if (computed.margin_right().type == Margin::Auto)
-					SetDefiniteMargin(size_target, PropertyId::MarginRight, Box::RIGHT);
-				if (computed.margin_bottom().type == Margin::Auto)
-					SetDefiniteMargin(size_target, PropertyId::MarginBottom, Box::BOTTOM);
-				if (computed.margin_left().type == Margin::Auto)
-					SetDefiniteMargin(size_target, PropertyId::MarginLeft, Box::LEFT);
-
 				const Vector2f new_size = Math::Max((size_original_size + delta).Round(), Vector2f(0.f));
 				const Vector2f new_size = Math::Max((size_original_size + delta).Round(), Vector2f(0.f));
 				size_target->SetProperty(PropertyId::Width, Property(new_size.x, Property::PX));
 				size_target->SetProperty(PropertyId::Width, Property(new_size.x, Property::PX));
 				size_target->SetProperty(PropertyId::Height, Property(new_size.y, Property::PX));
 				size_target->SetProperty(PropertyId::Height, Property(new_size.y, Property::PX));

+ 8 - 11
Source/Core/ElementScroll.cpp

@@ -33,7 +33,7 @@
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/Event.h"
 #include "../../Include/RmlUi/Core/Event.h"
 #include "../../Include/RmlUi/Core/Factory.h"
 #include "../../Include/RmlUi/Core/Factory.h"
-#include "LayoutDetails.h"
+#include "Layout/LayoutDetails.h"
 #include "WidgetScroll.h"
 #include "WidgetScroll.h"
 
 
 namespace Rml {
 namespace Rml {
@@ -91,6 +91,9 @@ void ElementScroll::DisableScrollbar(Orientation orientation)
 	{
 	{
 		scrollbars[orientation].element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
 		scrollbars[orientation].element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
 		scrollbars[orientation].enabled = false;
 		scrollbars[orientation].enabled = false;
+
+		if (corner)
+			corner->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
 	}
 	}
 }
 }
 
 
@@ -189,8 +192,7 @@ void ElementScroll::FormatScrollbars()
 	}
 	}
 
 
 	// Format the corner, if it is necessary.
 	// Format the corner, if it is necessary.
-	if (scrollbars[0].enabled &&
-		scrollbars[1].enabled)
+	if (scrollbars[0].enabled && scrollbars[1].enabled)
 	{
 	{
 		CreateCorner();
 		CreateCorner();
 
 
@@ -198,15 +200,9 @@ void ElementScroll::FormatScrollbars()
 		corner_box.SetContent(Vector2f(scrollbars[VERTICAL].size, scrollbars[HORIZONTAL].size));
 		corner_box.SetContent(Vector2f(scrollbars[VERTICAL].size, scrollbars[HORIZONTAL].size));
 		corner->SetBox(corner_box);
 		corner->SetBox(corner_box);
 		corner->SetOffset(containing_block + element_box.GetPosition(Box::PADDING) - Vector2f(scrollbars[VERTICAL].size, scrollbars[HORIZONTAL].size), element, true);
 		corner->SetOffset(containing_block + element_box.GetPosition(Box::PADDING) - Vector2f(scrollbars[VERTICAL].size, scrollbars[HORIZONTAL].size), element, true);
-		corner->SetProperty(PropertyId::Clip, Property(1, Property::NUMBER));
 
 
 		corner->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible));
 		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.
 // Creates one of the scroll component's scrollbar.
@@ -215,7 +211,7 @@ bool ElementScroll::CreateScrollbar(Orientation orientation)
 	if (scrollbars[orientation].element &&
 	if (scrollbars[orientation].element &&
 		scrollbars[orientation].widget)
 		scrollbars[orientation].widget)
 		return true;
 		return true;
-
+	
 	ElementPtr scrollbar_element = Factory::InstanceElement(element, "*", orientation == VERTICAL ? "scrollbarvertical" : "scrollbarhorizontal", XMLAttributes());
 	ElementPtr scrollbar_element = Factory::InstanceElement(element, "*", orientation == VERTICAL ? "scrollbarvertical" : "scrollbarhorizontal", XMLAttributes());
 	scrollbars[orientation].element = scrollbar_element.get();
 	scrollbars[orientation].element = scrollbar_element.get();
 	scrollbars[orientation].element->SetProperty(PropertyId::Clip, Property(1, Property::NUMBER));
 	scrollbars[orientation].element->SetProperty(PropertyId::Clip, Property(1, Property::NUMBER));
@@ -238,8 +234,9 @@ bool ElementScroll::CreateCorner()
 
 
 	ElementPtr corner_element = Factory::InstanceElement(element, "*", "scrollbarcorner", XMLAttributes());
 	ElementPtr corner_element = Factory::InstanceElement(element, "*", "scrollbarcorner", XMLAttributes());
 	corner = corner_element.get();
 	corner = corner_element.get();
-	Element* child = element->AppendChild(std::move(corner_element), false);
+	corner->SetProperty(PropertyId::Clip, Property(1, Property::NUMBER));
 
 
+	Element* child = element->AppendChild(std::move(corner_element), false);
 	UpdateScrollElementProperties(child);
 	UpdateScrollElementProperties(child);
 
 
 	return true;
 	return true;

+ 4 - 1
Source/Core/ElementStyle.cpp

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

+ 67 - 68
Source/Core/ElementText.cpp

@@ -27,23 +27,43 @@
  */
  */
 
 
 #include "../../Include/RmlUi/Core/ElementText.h"
 #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/Context.h"
+#include "../../Include/RmlUi/Core/Core.h"
 #include "../../Include/RmlUi/Core/ElementDocument.h"
 #include "../../Include/RmlUi/Core/ElementDocument.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../Include/RmlUi/Core/Event.h"
 #include "../../Include/RmlUi/Core/Event.h"
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../Include/RmlUi/Core/GeometryUtilities.h"
 #include "../../Include/RmlUi/Core/GeometryUtilities.h"
-#include "../../Include/RmlUi/Core/Property.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
+#include "../../Include/RmlUi/Core/Property.h"
+#include "ComputeProperty.h"
+#include "ElementDefinition.h"
+#include "ElementStyle.h"
 
 
 namespace Rml {
 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 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);
 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) :
 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),
 	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),
 	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();
 	const Vector2f translation = GetAbsoluteOffset();
-	
+
 	bool render = true;
 	bool render = true;
 	Vector2i clip_origin;
 	Vector2i clip_origin;
 	Vector2i clip_dimensions;
 	Vector2i clip_dimensions;
 	if (GetContext()->GetActiveClipRegion(clip_origin, 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_top = (float)clip_origin.y;
 		float clip_left = (float)clip_origin.x;
 		float clip_left = (float)clip_origin.x;
 		float clip_right = (float)(clip_origin.x + clip_dimensions.x);
 		float clip_right = (float)(clip_origin.x + clip_dimensions.x);
 		float clip_bottom = (float)(clip_origin.y + clip_dimensions.y);
 		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;
 		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;
 			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;
 				break;
-			}
 		}
 		}
 	}
 	}
 	
 	
@@ -156,40 +172,12 @@ void ElementText::OnRender()
 		decoration->Render(translation);
 		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
 // 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_ZoneScoped;
+	RMLUI_ASSERT(maximum_line_width >= 0.f); // TODO: Check all callers for conformance, check break at line condition below. Possibly check for FLT_MAX.
 
 
 	FontFaceHandle font_face_handle = GetFontFaceHandle();
 	FontFaceHandle font_face_handle = GetFontFaceHandle();
 
 
@@ -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.
 	// Bail if we don't have a valid font face.
 	if (font_face_handle == 0)
 	if (font_face_handle == 0)
+	{
+		LogMissingFontFace(GetParentNode() ? GetParentNode() : this);
 		return true;
 		return true;
+	}
 
 
 	// Determine how we are processing white-space while formatting the text.
 	// Determine how we are processing white-space while formatting the text.
 	using namespace Style;
 	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)
 						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.
 							// 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;
 								return false;
 
 
 							// Not even the first character of the line fits. Go back to consume the first character even though it will overflow.
 							// 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;
 					break_line = true;
 				}
 				}
-				else if (!line.empty())
+				else if (allow_empty || !line.empty())
 				{
 				{
 					// Let the token overflow into the next line.
 					// Let the token overflow into the next line.
 					return false;
 					return false;
@@ -295,7 +286,7 @@ bool ElementText::GenerateLine(String& line, int& line_length, float& line_width
 		line_width += token_width;
 		line_width += token_width;
 
 
 		// Break out of the loop if an endline was forced.
 		// Break out of the loop if an endline was forced.
-		if (break_line)
+		if (break_line && (allow_empty || !line.empty()))
 			return false;
 			return false;
 
 
 		// Set the beginning of the next token.
 		// Set the beginning of the next token.
@@ -317,18 +308,12 @@ void ElementText::ClearLines()
 }
 }
 
 
 // Adds a new line into the text element.
 // 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)
 	if (font_effects_dirty)
 		UpdateFontEffects();
 		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;
 	geometry_dirty = true;
 }
 }
@@ -485,14 +470,28 @@ void ElementText::GenerateGeometry(const FontFaceHandle font_face_handle, Line&
 		geometry[i].SetHostElement(this);
 		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)
 void ElementText::GenerateDecoration(const FontFaceHandle font_face_handle)
 {
 {
 	RMLUI_ZoneScopedC(0xA52A2A);
 	RMLUI_ZoneScopedC(0xA52A2A);
 	RMLUI_ASSERT(decoration);
 	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)
 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 "DataModel.h"
 #include "DataView.h"
 #include "DataView.h"
 #include "ElementStyle.h"
 #include "ElementStyle.h"
-#include "LayoutDetails.h"
-#include "LayoutEngine.h"
+#include "Layout/LayoutDetails.h"
+#include "Layout/LayoutEngine.h"
 #include "TransformState.h"
 #include "TransformState.h"
 #include <limits>
 #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.
 	// 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
 	// 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.
 	// complete clipping region for the element.
-	Element* clipping_element = element->GetParentNode();
+	Element* clipping_element = element->GetOffsetParent();
 
 
 	while (clipping_element != nullptr)
 	while (clipping_element != nullptr)
 	{
 	{
@@ -236,7 +236,7 @@ bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_d
 			break;
 			break;
 
 
 		// Climb the tree to this region's parent.
 		// 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;
 	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.
 // Generates the box for an element.
 void ElementUtilities::BuildBox(Box& box, Vector2f containing_block, Element* element, bool inline_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.
 // Sizes an element, and positions it within its parent offset from the borders of its content area.

+ 11 - 5
Source/Core/Elements/WidgetTextInput.cpp

@@ -34,6 +34,7 @@
 #include "../../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../../Include/RmlUi/Core/ElementUtilities.h"
 #include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h"
 #include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h"
 #include "../../../Include/RmlUi/Core/Factory.h"
 #include "../../../Include/RmlUi/Core/Factory.h"
+#include "../../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../../Include/RmlUi/Core/GeometryUtilities.h"
 #include "../../../Include/RmlUi/Core/GeometryUtilities.h"
 #include "../../../Include/RmlUi/Core/Input.h"
 #include "../../../Include/RmlUi/Core/Input.h"
 #include "../../../Include/RmlUi/Core/Math.h"
 #include "../../../Include/RmlUi/Core/Math.h"
@@ -1056,7 +1057,7 @@ void WidgetTextInput::FormatElement()
 			scroll->EnableScrollbar(ElementScroll::HORIZONTAL, width);
 			scroll->EnableScrollbar(ElementScroll::HORIZONTAL, width);
 	}
 	}
 
 
-	parent->SetContentBox(Vector2f(0, 0), content_area);
+	parent->SetScrollableOverflowRectangle(content_area);
 	scroll->FormatScrollbars();
 	scroll->FormatScrollbars();
 }
 }
 
 
@@ -1064,6 +1065,10 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 {
 {
 	Vector2f content_area(0, 0);
 	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.
 	// Clear the old lines, and all the lines in the text elements.
 	lines.clear();
 	lines.clear();
 	text_element->ClearLines();
 	text_element->ClearLines();
@@ -1077,12 +1082,13 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 
 
 	// Determine the line-height of the text element.
 	// Determine the line-height of the text element.
 	const float line_height = parent->GetLineHeight();
 	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.
 	// 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 int endline_selection_width = int(0.4f * parent->GetComputedValues().font_size());
 
 
 	const float client_width = parent->GetClientWidth();
 	const float client_width = parent->GetClientWidth();
 	int line_begin = 0;
 	int line_begin = 0;
-	Vector2f line_position(0, 0);
+	Vector2f line_position(0, font_baseline);
 	bool last_line = false;
 	bool last_line = false;
 
 
 	// Keep generating lines until all the text content is placed.
 	// Keep generating lines until all the text content is placed.
@@ -1100,7 +1106,7 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 		String line_content;
 		String line_content;
 
 
 		// Generate the next line.
 		// Generate the next line.
-		last_line = text_element->GenerateLine(line_content, line.size, line_width, line_begin, 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
 		// 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.
 		// append the orphan onto the line even though it will push the line outside of the input field's bounds.
@@ -1180,7 +1186,7 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 			selection_vertices.resize(selection_vertices.size() + 4);
 			selection_vertices.resize(selection_vertices.size() + 4);
 			selection_indices.resize(selection_indices.size() + 6);
 			selection_indices.resize(selection_indices.size() + 6);
 			GeometryUtilities::GenerateQuad(&selection_vertices[selection_vertices.size() - 4], &selection_indices[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;
 			line_position.x += selection_width;
 		}
 		}
@@ -1200,7 +1206,7 @@ Vector2f WidgetTextInput::FormatText(float height_constraint)
 
 
 		// Grow the content area width-wise if this line is the longest so far, and push the height out.
 		// Grow the content area width-wise if this line is the longest so far, and push the height out.
 		content_area.x = Math::Max(content_area.x, line_width + cursor_size.x);
 		content_area.x = Math::Max(content_area.x, line_width + cursor_size.x);
-		content_area.y = line_position.y;
+		content_area.y = line_position.y - font_baseline;
 
 
 		// Finally, push the new line into our array of lines.
 		// Finally, push the new line into our array of lines.
 		lines.push_back(std::move(line));
 		lines.push_back(std::move(line));

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

@@ -64,34 +64,10 @@ FontEffectsHandle FontEngineInterfaceDefault::PrepareFontEffects(FontFaceHandle
 	return (FontEffectsHandle)handle_default->GenerateLayerConfiguration(font_effects);
 	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)
 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.
 	/// Prepares for font effects by configuring a new, or returning an existing, layer configuration.
 	FontEffectsHandle PrepareFontEffects(FontFaceHandle, const FontEffectList& font_effects) override;
 	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.
 	/// Returns the width a string will take up if rendered with this handle.
 	int GetStringWidth(FontFaceHandle, const String& string, Character prior_character) override;
 	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;
 	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
 const FontGlyphMap& FontFaceHandleDefault::GetGlyphs() const
 {
 {
 	return glyphs;
 	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.
 // Returns the width a string will take up if rendered with this handle.
 int FontFaceHandleDefault::GetStringWidth(const String& string, Character prior_character)
 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/Traits.h"
 #include "../../../Include/RmlUi/Core/FontEffect.h"
 #include "../../../Include/RmlUi/Core/FontEffect.h"
 #include "../../../Include/RmlUi/Core/FontGlyph.h"
 #include "../../../Include/RmlUi/Core/FontGlyph.h"
+#include "../../../Include/RmlUi/Core/FontMetrics.h"
 #include "../../../Include/RmlUi/Core/Geometry.h"
 #include "../../../Include/RmlUi/Core/Geometry.h"
 #include "../../../Include/RmlUi/Core/Texture.h"
 #include "../../../Include/RmlUi/Core/Texture.h"
 #include "FontTypes.h"
 #include "FontTypes.h"
@@ -53,20 +54,8 @@ public:
 
 
 	bool Initialize(FontFaceHandleFreetype face, int font_size, bool load_default_glyphs);
 	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;
 	const FontGlyphMap& GetGlyphs() const;
 
 
 	/// Returns the width a string will take up if rendered with this handle.
 	/// 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 "FontProvider.h"
-#include "FontFace.h"
-#include "FontFamily.h"
-#include "FreeTypeInterface.h"
-#include "../LayoutInlineBoxText.h"
 #include "../../../Include/RmlUi/Core/Core.h"
 #include "../../../Include/RmlUi/Core/Core.h"
 #include "../../../Include/RmlUi/Core/FileInterface.h"
 #include "../../../Include/RmlUi/Core/FileInterface.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 #include "../../../Include/RmlUi/Core/Math.h"
 #include "../../../Include/RmlUi/Core/Math.h"
 #include "../../../Include/RmlUi/Core/StringUtilities.h"
 #include "../../../Include/RmlUi/Core/StringUtilities.h"
+#include "../ComputeProperty.h"
+#include "FontFace.h"
+#include "FontFamily.h"
+#include "FreeTypeInterface.h"
 #include <algorithm>
 #include <algorithm>
 
 
 namespace Rml {
 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);
 			FreeType::GetFaceStyle(ft_face, nullptr, nullptr, &weight);
 
 
 		const FontWeight variation_weight = (variation.weight == FontWeight::Auto ? weight : variation.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)))
 		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;
 using FontFaceHandleFreetype = uintptr_t;
 
 
-struct FontMetrics {
-	int size;
-	int x_height;
-	int line_height;
-	int baseline;
-
-	float underline_position;
-	float underline_thickness;
-};
-
 struct FaceVariation {
 struct FaceVariation {
 	Style::FontWeight weight;
 	Style::FontWeight weight;
 	uint16_t width;
 	uint16_t width;

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

@@ -28,6 +28,7 @@
 
 
 #include "FreeTypeInterface.h"
 #include "FreeTypeInterface.h"
 #include "../../../Include/RmlUi/Core/ComputedValues.h"
 #include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/FontMetrics.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 #include "../../../Include/RmlUi/Core/Log.h"
 #include <algorithm>
 #include <algorithm>
 #include <string.h>
 #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)
 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 = 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);
 	metrics.underline_thickness = Math::Max(metrics.underline_thickness, 1.0f);
 
 
 	// Determine the x-height of this font face.
 	// Determine the x-height of this font face.
 	FT_UInt index = FT_Get_Char_Index(ft_face, 'x');
 	FT_UInt index = FT_Get_Char_Index(ft_face, 'x');
 	if (index != 0 && FT_Load_Glyph(ft_face, index, 0) == 0)
 	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
 	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)
 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
 #define RMLUI_CORE_FONTENGINEDEFAULT_FREETYPEINTERFACE_H
 
 
 #include "FontTypes.h"
 #include "FontTypes.h"
+#include "../../../Include/RmlUi/Core/FontMetrics.h"
 
 
 namespace Rml {
 namespace Rml {
 
 

+ 3 - 22
Source/Core/FontEngineInterface.cpp

@@ -56,29 +56,10 @@ FontEffectsHandle FontEngineInterface::PrepareFontEffects(FontFaceHandle /*handl
 	return 0;
 	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*/)
 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;
 	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_vertices.resize(line_vertices.size() + 4);
 	line_indices.resize(line_indices.size() + 6);
 	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)
 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

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

@@ -0,0 +1,291 @@
+/*
+ * 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::Block:
+	case Style::Display::FlowRoot:
+	case Style::Display::Flex:
+	case Style::Display::Table: return OuterDisplayType::BlockLevel;
+
+	case Style::Display::Inline:
+	case Style::Display::InlineBlock:
+	case Style::Display::InlineFlex:
+	case Style::Display::InlineTable: 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);
+	default:
+		RMLUI_ERROR; // Should have been handled above.
+		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

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

@@ -0,0 +1,299 @@
+/*
+ * 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, float element_baseline)
+{
+	if (!SubmitBox(content_overflow_size, box, -1.f))
+		return false;
+
+	ClosePositionedElements();
+
+	SubmitElementLayout();
+	SetElementBaseline(element_baseline);
+	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, float element_baseline)
+{
+	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();
+	SetElementBaseline(element_baseline);
+}
+
+String TableWrapper::DebugDumpTree(int depth) const
+{
+	return String(depth * 2, ' ') + "TableWrapper" + " | " + LayoutDetails::GetDebugElementName(element);
+}
+
+} // namespace Rml

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

@@ -0,0 +1,165 @@
+/*
+ * 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) {}
+	RootBox(const Box& box) : ContainerBox(Type::Root, nullptr, nullptr), box(box) {}
+
+	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, float element_baseline);
+
+	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, float element_baseline);
+
+	const Box* GetIfBox() const override { return &box; }
+	String DebugDumpTree(int depth) const override;
+
+	Box& GetBox() { return box; }
+
+private:
+	Box box;
+};
+
+} // namespace Rml
+#endif

+ 99 - 67
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 "LayoutDetails.h"
 #include "LayoutEngine.h"
 #include "LayoutEngine.h"
 #include <algorithm>
 #include <algorithm>
@@ -39,50 +40,81 @@
 
 
 namespace Rml {
 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 Vector2f box_content_size = box.GetSize();
 	const bool auto_height = (box_content_size.y < 0.0f);
 	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),
+		};
 
 
-	Math::SnapToPixelGrid(flex_content_offset, flex_available_content_size);
+		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;
 
 
-	// 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);
+		if (auto_height)
+		{
+			context.flex_available_content_size.y = -1.f; // Negative means infinite space
+			context.flex_content_containing_block.y = containing_block.y;
+		}
 
 
-	layout_flex.Format();
+		Math::SnapToPixelGrid(context.flex_content_offset, context.flex_available_content_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);
+		// Format the flexbox and all its children.
+		Vector2f flex_resulting_content_size, content_overflow_size;
+		float flex_baseline = 0.f;
+		context.Format(flex_resulting_content_size, content_overflow_size, flex_baseline);
 
 
-	out_content_overflow_size = layout_flex.flex_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.
+		Vector2f formatted_content_size = box_content_size;
+		if (auto_height)
+			formatted_content_size.y = flex_resulting_content_size.y + scrollbar_size.y;
+
+		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)
-{}
+		// Change the flex baseline coordinates to the element baseline, which is defined as the distance from the element's bottom margin edge.
+		const float element_baseline =
+			sized_box.GetSizeAcross(Box::VERTICAL, Box::BORDER) + sized_box.GetEdge(Box::MARGIN, Box::BOTTOM) - flex_baseline;
+
+		// 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, element_baseline))
+			break;
+	}
+
+	return flex_container_box;
+}
 
 
 struct FlexItem {
 struct FlexItem {
 	// In the following, suffix '_a' means flex start edge while '_b' means flex end edge.
 	// In the following, suffix '_a' means flex start edge while '_b' means flex end edge.
@@ -134,7 +166,7 @@ struct FlexLine {
 	float cross_offset = 0;
 	float cross_offset = 0;
 };
 };
 
 
-struct FlexContainer {
+struct FlexLineContainer {
 	Vector<FlexLine> lines;
 	Vector<FlexLine> lines;
 };
 };
 
 
@@ -173,9 +205,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, float& flex_baseline) 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
 	// For details, see https://drafts.csswg.org/css-flexbox/#layout-algorithm
 
 
 	const ComputedValues& computed_flex = element_flex->GetComputedValues();
 	const ComputedValues& computed_flex = element_flex->GetComputedValues();
@@ -216,13 +248,18 @@ void LayoutFlex::Format()
 		}
 		}
 		else if (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed)
 		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;
 			continue;
 		}
 		}
+		else if (computed.position() == Style::Position::Relative)
+		{
+			flex_container_box->AddRelativeElement(element);
+		}
 
 
 		FlexItem item = {};
 		FlexItem item = {};
 		item.element = element;
 		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;
 		Style::LengthPercentageAuto item_main_size;
 
 
@@ -278,7 +315,7 @@ void LayoutFlex::Format()
 			if (initial_box_size.x < 0.f)
 			if (initial_box_size.x < 0.f)
 				format_box.SetContent(Vector2f(flex_available_content_size.x - item.cross.sum_edges, initial_box_size.y));
 				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;
 			item.inner_flex_base_size = element->GetBox().GetSize().y;
 		}
 		}
 
 
@@ -295,7 +332,7 @@ void LayoutFlex::Format()
 	}
 	}
 
 
 	// -- Collect the items into lines --
 	// -- Collect the items into lines --
-	FlexContainer container;
+	FlexLineContainer container;
 
 
 	if (flex_single_line)
 	if (flex_single_line)
 	{
 	{
@@ -512,12 +549,8 @@ void LayoutFlex::Format()
 						break;
 						break;
 					}
 					}
 					//-fallthrough
 					//-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:
 				case JustifyContent::Center:
 					line.items.front().main_auto_margin_size_a = 0.5f * remaining_free_space;
 					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;
 					line.items.back().main_auto_margin_size_b = 0.5f * remaining_free_space;
@@ -564,7 +597,7 @@ void LayoutFlex::Format()
 				if (content_size.y < 0.0f)
 				if (content_size.y < 0.0f)
 				{
 				{
 					item.box.SetContent(Vector2f(used_main_size_inner, content_size.y));
 					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;
 					item.hypothetical_cross_size = item.element->GetBox().GetSize().y + item.cross.sum_edges;
 				}
 				}
 				else
 				else
@@ -684,12 +717,8 @@ void LayoutFlex::Format()
 				case AlignSelf::FlexStart:
 				case AlignSelf::FlexStart:
 					// Do nothing, cross offset set above with this behavior.
 					// Do nothing, cross offset set above with this behavior.
 					break;
 					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:
 				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.
 					// 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 +803,8 @@ void LayoutFlex::Format()
 					}
 					}
 				}
 				}
 				//-fallthrough
 				//-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:
 			case AlignContent::Center:
 				container.lines.front().cross_spacing_a = 0.5f * remaining_free_space;
 				container.lines.front().cross_spacing_a = 0.5f * remaining_free_space;
 				container.lines.back().cross_spacing_b = 0.5f * remaining_free_space;
 				container.lines.back().cross_spacing_b = 0.5f * remaining_free_space;
@@ -818,6 +843,8 @@ void LayoutFlex::Format()
 		return main_axis_horizontal ? Vector2f(v_main, v_cross) : Vector2f(v_cross, v_main);
 		return main_axis_horizontal ? Vector2f(v_main, v_cross) : Vector2f(v_cross, v_main);
 	};
 	};
 
 
+	bool baseline_set = false;
+
 	// -- Format items --
 	// -- Format items --
 	for (FlexLine& line : container.lines)
 	for (FlexLine& line : container.lines)
 	{
 	{
@@ -828,18 +855,23 @@ void LayoutFlex::Format()
 
 
 			item.box.SetContent(item_size);
 			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
 			// Set the position of the element within the the flex container
 			item.element->SetOffset(flex_content_offset + item_offset, element_flex);
 			item.element->SetOffset(flex_content_offset + item_offset, element_flex);
 
 
+			// The flex container baseline is simply set to the first flex item that has a baseline.
+			if (!baseline_set && item_layout_box->GetBaselineOfLastLine(flex_baseline))
+			{
+				flex_baseline += flex_content_offset.y + item_offset.y;
+				baseline_set = true;
+			}
+
 			// The cell contents may overflow, propagate this to the flex container.
 			// 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);
+			flex_content_overflow_size = Math::Max(flex_content_overflow_size, overflow_size);
 		}
 		}
 	}
 	}
 
 

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

@@ -0,0 +1,68 @@
+/*
+ * 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.
+	/// @param[out] flex_baseline The baseline of the flex contaienr, in terms of the vertical distance from its top-left border corner.
+	void Format(Vector2f& flex_resulting_content_size, Vector2f& flex_content_overflow_size, float& flex_baseline) 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
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * 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>
 #include <float.h>
 
 
 namespace Rml {
 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.
 	// 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)
 	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.
 	// 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.
 	// 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;
 	using namespace Style;
 	// Clear left boxes.
 	// 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)
 		for (size_t i = 0; i < boxes[LEFT].size(); ++i)
 			cursor = Math::Max(cursor, boxes[LEFT][i].offset.y + boxes[LEFT][i].dimensions.y);
 			cursor = Math::Max(cursor, boxes[LEFT][i].offset.y + boxes[LEFT][i].dimensions.y);
 	}
 	}
 
 
 	// Clear right boxes.
 	// 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)
 		for (size_t i = 0; i < boxes[RIGHT].size(); ++i)
 			cursor = Math::Max(cursor, boxes[RIGHT][i].offset.y + boxes[RIGHT][i].dimensions.y);
 			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;
 	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)
 	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;
 	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
 	// 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
 	// 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.
 	// 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 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)
 		if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
 			continue;
 			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-
 		// 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.
 		// 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);
 			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.
 			// 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
 	// 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.
 	// 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 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)
 		if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
 			continue;
 			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
 		// 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.
 		// 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);
 			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.
 	// Third; we go through all of the boxes (on both sides), checking for vertical collisions.
 	for (int i = 0; i < 2; ++i)
 	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 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)
 			if (fixed_box.offset.y + fixed_box.dimensions.y <= box_position.y)
 				continue;
 				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
 			// 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.
 			// 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);
 			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!
 	// 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
 } // 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

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

@@ -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.
+ *
+ */
+
+#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 "ReplacedFormattingContext.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;
+
+	if (element->IsReplaced())
+		return ReplacedFormattingContext::Format(parent_container, element, override_initial_box);
+
+	FormattingContextType type = backup_context;
+
+	auto& computed = element->GetComputedValues();
+	const Display display = computed.display();
+	if (display == Display::Flex || display == Display::InlineFlex)
+	{
+		type = FormattingContextType::Flex;
+	}
+	else if (display == Display::Table || display == Display::InlineTable)
+	{
+		type = FormattingContextType::Table;
+	}
+	else if (display == Display::InlineBlock || display == Display::FlowRoot || display == Display::TableCell || computed.float_() != Float::None ||
+		computed.position() == Position::Absolute || computed.position() == Position::Fixed || 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

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

@@ -0,0 +1,234 @@
+/*
+ * 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::Center:
+	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, Replaced };
+
+	virtual ~LayoutBox() = default;
+
+	Type GetType() const { return type; }
+	Vector2f GetVisibleOverflowSize() const { return visible_overflow_size; }
+
+	// Returns a pointer to the dimensions box if this layout box has one.
+	virtual const Box* GetIfBox() const;
+	// Returns 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;
+	// Calculates 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

+ 149 - 139
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
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -27,11 +27,14 @@
  */
  */
 
 
 #include "LayoutDetails.h"
 #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 "LayoutEngine.h"
 #include <float.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.
 // 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)
 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 border_size;
 
 
 	return Math::Max(0.0f, border_size - border_padding_edges_size);
 	return Math::Max(0.0f, border_size - border_padding_edges_size);
 }
 }
 
 
 // Generates the box for an element.
 // Generates the box for an element.
-void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, BoxContext box_context, float override_shrink_to_fit_width)
+void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* element, BuildBoxMode box_context)
 {
 {
 	if (!element)
 	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.
 	// 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.
 	// 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.
 		// Adjust sizes for the given box sizing model.
 		if (computed.box_sizing() == Style::BoxSizing::BorderBox)
 		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);
 	box.SetContent(content_area);
 
 
 	// Evaluate the margins, and width and height if they are auto.
 	// 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)
 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)
 	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)
 	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;
 	const float box_height = box.GetSize().y;
 	if (box_height < 0)
 	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);
+	RMLUI_ASSERT(parent_container);
+	using Style::Position;
 
 
-	Vector2f containing_block;
+	ContainerBox* container = parent_container;
+	Box::Area area = Box::CONTENT;
 
 
-	containing_block.x = containing_box->GetBox().GetSize(Box::CONTENT).x;
-	if (containing_box->GetElement() != nullptr)
-		containing_block.x -= containing_box->GetElement()->GetElementScroll()->GetScrollbarSize(ElementScroll::VERTICAL);
-
-	while ((containing_block.y = containing_box->GetBox().GetSize(Box::CONTENT).y) < 0)
+	// 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,
 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();
 	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.
 		// 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.
 		// 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
 	else
 	{
 	{
 		// The element is block, so we need to run the box through the ringer to potentially evaluate auto margins and dimensions.
 		// 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);
 		BuildBoxHeight(box, computed, min_size.y, max_size.y, containing_block.y);
 	}
 	}
 }
 }
@@ -244,32 +248,36 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b
 {
 {
 	RMLUI_ASSERT(element);
 	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;
 	Box box;
 	float min_height, max_height;
 	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);
 	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::InlineFlex || display == Style::Display::Table ||
+		display == Style::Display::InlineTable)
+		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)
 ComputedAxisSize LayoutDetails::BuildComputedHorizontalSize(const ComputedValues& computed)
@@ -294,7 +302,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);
 	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
 	// 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.
 	// dimensions and ratio to find a suitable content size.
@@ -382,7 +402,8 @@ Vector2f LayoutDetails::CalculateSizeForReplacedElement(const Vector2f specified
 }
 }
 
 
 // Builds the block-specific width and horizontal margins of a Box.
 // 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;
 	RMLUI_ZoneScoped;
 
 
@@ -408,53 +429,46 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 		}
 		}
 	}
 	}
 
 
+	const bool absolutely_positioned = (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed);
+	const bool inset_auto = (computed.left().type == Style::Left::Auto || computed.right().type == Style::Right::Auto);
 	const bool width_auto = (content_area.x < 0);
 	const bool width_auto = (content_area.x < 0);
 
 
-	// If the width is set to auto, we need to calculate the width
+	auto GetInsetWidth = [&] {
+		// For absolutely positioned elements (and only those), the 'left' and 'right' values are part of the box's width constraint.
+		if (absolutely_positioned)
+			return ResolveValue(computed.left(), containing_block.x) + ResolveValue(computed.right(), containing_block.x);
+		return 0.f;
+	};
+
+	// If the width is set to auto, we need to calculate the width.
 	if (width_auto)
 	if (width_auto)
 	{
 	{
 		// Apply the shrink-to-fit algorithm here to find the width of the element.
 		// Apply the shrink-to-fit algorithm here to find the width of the element.
 		// See CSS 2.1 section 10.3.7 for when this should be applied.
 		// See CSS 2.1 section 10.3.7 for when this should be applied.
 		const bool shrink_to_fit = !replaced_element &&
 		const bool shrink_to_fit = !replaced_element &&
-			((computed.float_() != Style::Float::None) ||
-				((computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed) &&
-					(computed.left().type == Style::Left::Auto || computed.right().type == Style::Right::Auto)) ||
+			((computed.float_() != Style::Float::None) || (absolutely_positioned && inset_auto) ||
 				(computed.display() == Style::Display::InlineBlock));
 				(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);
-		}
-
-		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;
+			// The width is set to whatever remains of the containing block.
+			content_area.x = containing_block.x - (GetInsetWidth() + box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING));
+			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;
 			content_area.x = override_shrink_to_fit_width;
 		}
 		}
 		else
 		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.
 	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
 	else if (num_auto_margins > 0)
 	else if (num_auto_margins > 0)
 	{
 	{
-		const float margin = (containing_block.x - box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN)) / float(num_auto_margins);
+		const float margin = (containing_block.x - (GetInsetWidth() + box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN))) / float(num_auto_margins);
 
 
 		if (margins_auto[0])
 		if (margins_auto[0])
 			box.SetEdge(Box::MARGIN, Box::LEFT, margin);
 			box.SetEdge(Box::MARGIN, Box::LEFT, margin);
@@ -471,7 +485,7 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 		box.SetContent(content_area);
 		box.SetContent(content_area);
 
 
 		if (num_auto_margins > 0)
 		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
 	else
 		box.SetContent(content_area);
 		box.SetContent(content_area);
@@ -504,40 +518,36 @@ void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, flo
 		}
 		}
 	}
 	}
 
 
+	const bool absolutely_positioned = (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed);
+	const bool inset_auto = (computed.top().type == Style::Top::Auto || computed.bottom().type == Style::Bottom::Auto);
 	const bool height_auto = (content_area.y < 0);
 	const bool height_auto = (content_area.y < 0);
 
 
-	// If the height is set to auto, we need to calculate the height
+	auto GetInsetHeight = [&] {
+		// For absolutely positioned elements (and only those), the 'top' and 'bottom' values are part of the box's height constraint.
+		if (absolutely_positioned)
+			return ResolveValue(computed.top(), containing_block_height) + ResolveValue(computed.bottom(), containing_block_height);
+		return 0.f;
+	};
+
+	// If the height is set to auto, we need to calculate the height.
 	if (height_auto)
 	if (height_auto)
 	{
 	{
 		// If the height is set to auto for a box in normal flow, the height is set to -1.
 		// If the height is set to auto for a box in normal flow, the height is set to -1.
 		content_area.y = -1;
 		content_area.y = -1;
 
 
-		// But if we are dealing with an absolutely positioned element we need to
-		// consider if the top and bottom properties are set, since the height can be affected.
-		if (computed.position() == Style::Position::Absolute || computed.position() == Style::Position::Fixed)
+		// But if we are dealing with an absolutely positioned element we need to consider if the top and bottom
+		// properties are set, since the height can be affected.
+		if (absolutely_positioned && !inset_auto)
 		{
 		{
-			float top = 0.0f, bottom = 0.0f;
-
-			if (computed.top().type != Style::Top::Auto && computed.bottom().type != Style::Bottom::Auto)
-			{
-				top = ResolveValue(computed.top(), containing_block_height);
-				bottom = ResolveValue(computed.bottom(), containing_block_height);
-
-				// The height gets resolved to whatever is left of the containing block
-				content_area.y = containing_block_height - (top +
-				                                            box.GetCumulativeEdge(Box::CONTENT, Box::TOP) +
-				                                            box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM) +
-				                                            bottom);
-				content_area.y = Math::Max(0.0f, content_area.y);
-			}
+			// The height is set to whatever remains of the containing block.
+			content_area.y = containing_block_height - (GetInsetHeight() + box.GetSizeAcross(Box::VERTICAL, Box::MARGIN, Box::PADDING));
+			content_area.y = Math::Max(0.0f, content_area.y);
 		}
 		}
 	}
 	}
-	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
+	// Otherwise, the margins that are set to auto will pick up the remaining height of the containing block.
 	else if (num_auto_margins > 0)
 	else if (num_auto_margins > 0)
 	{
 	{
-		float margin = 0;
-		if (content_area.y >= 0)
-			margin = (containing_block_height - box.GetSizeAcross(Box::VERTICAL, Box::MARGIN)) / num_auto_margins;
+		const float margin = (containing_block_height - (GetInsetHeight() + box.GetSizeAcross(Box::VERTICAL, Box::MARGIN))) / float(num_auto_margins);
 
 
 		if (margins_auto[0])
 		if (margins_auto[0])
 			box.SetEdge(Box::MARGIN, Box::TOP, margin);
 			box.SetEdge(Box::MARGIN, Box::TOP, margin);

+ 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 {
 namespace Rml {
 
 
 class Box;
 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,
     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;
 	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.
     Layout functions for sizing elements.
@@ -59,26 +69,12 @@ enum class BoxContext { Block, Inline, FlexOrTable };
  */
  */
 class LayoutDetails {
 class LayoutDetails {
 public:
 public:
-	/// Generates the box for an element.
+	/// Generates the box dimensions for an element.
 	/// @param[out] box The box to be built.
 	/// @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] 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] element The element to build the box for.
 	/// @param[in] box_context The formatting context in which the box is generated.
 	/// @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.
 	// 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);
 	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,
 	static void GetDefiniteMinMaxHeight(float& min_height, float& max_height, const ComputedValues& computed, const Box& box,
 		float containing_block_height);
 		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
 	/// 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.
 	/// 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] min_size The element's minimum width and height.
 	/// @param[in] max_size The element's maximum 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] containing_block The size of the containing block.
 	/// @param[in] element The element the box is being generated for.
 	/// @param[in] element The element the box is being generated for.
 	/// @param[in] box_context The formatting context in which the box is generated.
 	/// @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] 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,
 	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.
 	/// Formats the element and returns the width of its contents.
 	static float GetShrinkToFitWidth(Element* element, Vector2f containing_block);
 	static float GetShrinkToFitWidth(Element* element, Vector2f containing_block);
@@ -122,6 +117,8 @@ public:
 	static void GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b,
 	static void GetEdgeSizes(float& margin_a, float& margin_b, float& padding_border_a, float& padding_border_b,
 		const ComputedAxisSize& computed_size, float base_value);
 		const ComputedAxisSize& computed_size, float base_value);
 
 
+	static String GetDebugElementName(Element* element);
+
 private:
 private:
 	/// Calculates and returns the content size for replaced elements.
 	/// 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,
 	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

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

@@ -0,0 +1,90 @@
+/*
+ * 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 "ReplacedFormattingContext.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(ReplacedBox), 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

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

@@ -0,0 +1,443 @@
+/*
+ * 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);
+			}
+
+			const float subtree_height = 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, subtree_height - max_ascent); break;
+			case VerticalAlignType::Bottom: max_ascent = Math::Max(max_ascent, subtree_height - max_descent); break;
+			case VerticalAlignType::Center:
+			{
+				// Distribute the subtree's height equally to the ascent and descent.
+				const float distribute_height = 0.5f * (subtree_height - (max_ascent + max_descent));
+				if (distribute_height > 0.f)
+				{
+					max_ascent += distribute_height;
+					max_descent += distribute_height;
+				}
+			}
+			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;
+		case VerticalAlignType::Center: fragment.position.y = 0.5f * (fragment.max_ascent - fragment.max_descent + out_height_of_line); 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

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

@@ -0,0 +1,215 @@
+/*
+ * 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::Center ||
+			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

+ 79 - 0
Source/Core/Layout/ReplacedFormattingContext.cpp

@@ -0,0 +1,79 @@
+/*
+ * 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 "ReplacedFormattingContext.h"
+#include "../../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../../Include/RmlUi/Core/Element.h"
+#include "BlockFormattingContext.h"
+#include "ContainerBox.h"
+#include "LayoutDetails.h"
+
+namespace Rml {
+
+UniquePtr<LayoutBox> ReplacedFormattingContext::Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box)
+{
+	RMLUI_ASSERT(element->IsReplaced());
+
+	// Replaced elements provide their own rendering, we just set their box here and notify them that the element has been sized.
+	auto replaced_box = MakeUnique<ReplacedBox>(element);
+	Box& box = replaced_box->GetBox();
+	if (override_initial_box)
+		box = *override_initial_box;
+	else
+	{
+		const Vector2f containing_block = LayoutDetails::GetContainingBlock(parent_container, element->GetPosition()).size;
+		LayoutDetails::BuildBox(box, containing_block, element);
+	}
+
+	// Submit the box and notify the element.
+	replaced_box->Close();
+
+	// Usually, replaced elements add children to the hidden DOM. If we happen to have any normal DOM children, e.g.
+	// added by the user, we format them using normal block formatting rules. Since replaced elements provide their
+	// own rendering, this could cause conflicting or strange layout results, and is done at the user's own risk.
+	if (element->HasChildNodes())
+	{
+		RootBox root(box);
+		BlockFormattingContext::Format(&root, element, &box);
+	}
+
+	return replaced_box;
+}
+
+void ReplacedBox::Close()
+{
+	element->SetBox(box);
+	element->OnLayout();
+}
+
+String ReplacedBox::DebugDumpTree(int depth) const
+{
+	return String(depth * 2, ' ') + "ReplacedBox";
+}
+
+} // namespace Rml

+ 65 - 0
Source/Core/Layout/ReplacedFormattingContext.h

@@ -0,0 +1,65 @@
+/*
+ * 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_REPLACEDFORMATTINGCONTEXT_H
+#define RMLUI_CORE_LAYOUT_REPLACEDFORMATTINGCONTEXT_H
+
+#include "../../../Include/RmlUi/Core/Types.h"
+#include "FormattingContext.h"
+#include "LayoutBox.h"
+
+namespace Rml {
+
+/*
+    A formatting context that handles replaced elements.
+
+    Replaced elements normally take care of their own layouting, so this is only responsible for setting thei box
+    dimensions and notifying the element.
+*/
+class ReplacedFormattingContext final : public FormattingContext {
+public:
+	static UniquePtr<LayoutBox> Format(ContainerBox* parent_container, Element* element, const Box* override_initial_box);
+};
+
+class ReplacedBox : public LayoutBox {
+public:
+	ReplacedBox(Element* element) : LayoutBox(Type::Replaced), element(element) {}
+
+	void Close();
+	Box& GetBox() { return box; }
+
+	const Box* GetIfBox() const override { return &box; }
+	String DebugDumpTree(int depth) const override;
+
+private:
+	Element* element;
+	Box box;
+};
+
+} // namespace Rml
+#endif

+ 139 - 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
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -26,93 +26,120 @@
  *
  *
  */
  */
 
 
-#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 "LayoutDetails.h"
 #include "LayoutEngine.h"
 #include "LayoutEngine.h"
-#include "LayoutTableDetails.h"
+#include "TableFormattingDetails.h"
 #include <algorithm>
 #include <algorithm>
 #include <numeric>
 #include <numeric>
 
 
 namespace Rml {
 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);
+
+	TableFormattingContext context;
+	context.element_table = element_table;
+	context.table_wrapper_box = table_wrapper_box.get();
+
+	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);
 
 
-	Vector2f table_content_offset = box.GetPosition();
-	Vector2f table_initial_content_size = Vector2f(box_content_size.x, Math::Max(0.0f, box_content_size.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);
 
 
-	Math::SnapToPixelGrid(table_content_offset, table_initial_content_size);
+	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.
 	// 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)
 	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)
 	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);
 
 
-	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.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));
 
 
-	TableGrid grid;
-	grid.Build(element_table);
+	context.grid.Build(element_table, *table_wrapper_box);
 
 
-	// 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);
+	Vector2f table_content_size, table_overflow_size;
+	float table_baseline = 0.f;
 
 
-	layout_table.FormatTable();
+	// Format the table and its children.
+	context.FormatTable(table_content_size, table_overflow_size, table_baseline);
+
+	RMLUI_ASSERT(table_content_size.y >= 0);
 
 
 	// Update the box size based on the new table size.
 	// 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);
+	}
 
 
+	// Change the table baseline coordinates to the element baseline, which is defined as the distance from the element's bottom margin edge.
+	const float element_baseline = box.GetSizeAcross(Box::VERTICAL, Box::BORDER) + box.GetEdge(Box::MARGIN, Box::BOTTOM) - table_baseline;
 
 
-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;
+	table_wrapper_box->Close(table_overflow_size, box, element_baseline);
+
+	return table_wrapper_box;
 }
 }
 
 
-void LayoutTable::FormatTable()
+void TableFormattingContext::FormatTable(Vector2f& table_content_size, Vector2f& table_overflow_size, float& table_baseline) 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;
 
 
-	InitializeCellBoxes();
+	DetermineColumnWidths(columns, table_content_size.x);
 
 
-	DetermineRowHeights();
+	InitializeCellBoxes(cells, columns);
 
 
-	FormatRows();
+	DetermineRowHeights(rows, cells, table_content_size.y);
 
 
-	FormatColumns();
+	FormatRows(rows, table_content_size.x);
 
 
-	FormatCells();
+	FormatColumns(columns, table_content_size.y);
+
+	FormatCells(cells, table_overflow_size, rows, columns, table_baseline);
 }
 }
 
 
-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.
 	// 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.
 	// 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.
 	// 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.
 	// 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>.
 	// Margins for <td>, <col>, <colgroup> are merged to produce a single left/right margin for each column, located outside <colgroup>.
 
 
@@ -169,10 +196,10 @@ void LayoutTable::DetermineColumnWidths()
 	const float columns_full_width = BuildColumnBoxes(columns, column_metrics, grid.columns, table_gap.x);
 	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.
 	// 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.
 	// Requires that column boxes are already generated.
 	RMLUI_ASSERT(columns.size() == grid.columns.size());
 	RMLUI_ASSERT(columns.size() == grid.columns.size());
@@ -184,7 +211,7 @@ void LayoutTable::InitializeCellBoxes()
 		Box& box = cells[i];
 		Box& box = cells[i];
 
 
 		// Determine the cell's box for formatting later, we may get an indefinite (-1) vertical content size.
 		// 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.
 		// 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);
 		const float cell_border_width = GetSpanningCellBorderSize(columns, grid.cells[i].column_begin, grid.cells[i].column_last);
@@ -193,22 +220,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.
 	// Requires that cell boxes have been initialized.
@@ -234,7 +261,7 @@ void LayoutTable::DetermineRowHeights()
 		{
 		{
 			// The padding/border/margin and widths of columns are used.
 			// The padding/border/margin and widths of columns are used.
 			const ComputedAxisSize computed = LayoutDetails::BuildComputedVerticalSize(element_row->GetComputedValues());
 			const ComputedAxisSize computed = LayoutDetails::BuildComputedVerticalSize(element_row->GetComputedValues());
-			
+
 			if (computed.size.type == Style::LengthPercentageAuto::Percentage)
 			if (computed.size.type == Style::LengthPercentageAuto::Percentage)
 				percentage_size_used = true;
 				percentage_size_used = true;
 
 
@@ -244,11 +271,10 @@ void LayoutTable::DetermineRowHeights()
 
 
 	if (table_auto_height && percentage_size_used)
 	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 "
 			"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.",
 			"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.
 	// Next, find the height of rows that use auto height.
@@ -260,8 +286,8 @@ void LayoutTable::DetermineRowHeights()
 		if (row_metric.sizing_mode == TrackSizingMode::Auto)
 		if (row_metric.sizing_mode == TrackSizingMode::Auto)
 		{
 		{
 			struct CellLastRowComp {
 			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.
 			// Determine which cells end at this row.
@@ -278,19 +304,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 both the row and the cell heights are 'auto', we need to format the cell to get its height.
 				if (box.GetSize().y < 0)
 				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());
 					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
 				// 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.
 				// 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 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;
 				const float cell_inrow_height = box.GetSizeAcross(Box::VERTICAL, Box::BORDER) - height_from_spanning_rows;
 
 
@@ -307,26 +334,23 @@ void LayoutTable::DetermineRowHeights()
 	// Now all heights should be either fixed or flexible, resolve all flexible heights to fixed.
 	// Now all heights should be either fixed or flexible, resolve all flexible heights to fixed.
 	sizing.ResolveFlexibleSize();
 	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);
 	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());
 	RMLUI_ASSERT(rows.size() == grid.rows.size());
 
 
 	// Size and position the row and row group elements.
 	// 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;
 		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);
 		box.SetContent(content_size);
 		element->SetBox(box);
 		element->SetBox(box);
 
 
@@ -346,19 +370,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());
 	RMLUI_ASSERT(columns.size() == grid.columns.size());
 
 
 	// Size and position the column and column group elements.
 	// 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;
 		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);
 		box.SetContent(content_size);
 		element->SetBox(box);
 		element->SetBox(box);
 
 
@@ -378,10 +399,13 @@ void LayoutTable::FormatColumns()
 	}
 	}
 }
 }
 
 
-void LayoutTable::FormatCells()
+void TableFormattingContext::FormatCells(BoxList& cells, Vector2f& table_overflow_size, const TrackBoxList& rows, const TrackBoxList& columns,
+	float& table_baseline) const
 {
 {
 	RMLUI_ASSERT(cells.size() == grid.cells.size());
 	RMLUI_ASSERT(cells.size() == grid.cells.size());
 
 
+	bool baseline_set = false;
+
 	for (int cell_index = 0; cell_index < (int)cells.size(); cell_index++)
 	for (int cell_index = 0; cell_index < (int)cells.size(); cell_index++)
 	{
 	{
 		const TableGrid::Cell& grid_cell = grid.cells[cell_index];
 		const TableGrid::Cell& grid_cell = grid.cells[cell_index];
@@ -391,11 +415,9 @@ void LayoutTable::FormatCells()
 		Style::VerticalAlign vertical_align = element_cell->GetComputedValues().vertical_align();
 		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 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.
 		// Determine the height of the cell.
 		if (box.GetSize().y < 0)
 		if (box.GetSize().y < 0)
 		{
 		{
@@ -403,13 +425,14 @@ void LayoutTable::FormatCells()
 			if (is_aligned)
 			if (is_aligned)
 			{
 			{
 				// We need to format the cell to know how much padding to add.
 				// 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());
 				box.SetContent(element_cell->GetBox().GetSize());
 			}
 			}
 			else
 			else
 			{
 			{
 				// We don't need to add any padding and can thus avoid formatting, just set the height to the row height.
 				// 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 +458,7 @@ void LayoutTable::FormatCells()
 			default:
 			default:
 				add_padding_top = 0.0f;
 				add_padding_top = 0.0f;
 				add_padding_bottom = available_height;
 				add_padding_bottom = available_height;
+				break;
 			}
 			}
 
 
 			box.SetEdge(Box::PADDING, Box::TOP, box.GetEdge(Box::PADDING, Box::TOP) + add_padding_top);
 			box.SetEdge(Box::PADDING, Box::TOP, box.GetEdge(Box::PADDING, Box::TOP) + add_padding_top);
@@ -445,17 +469,23 @@ void LayoutTable::FormatCells()
 		// @performance: We may have already formatted the element during the above procedures without the extra padding. In that case, we may
 		// @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.
 		//   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.
 		//   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
 		// Set the position of the element within the the table container
 		element_cell->SetOffset(cell_offset, element_table);
 		element_cell->SetOffset(cell_offset, element_table);
 
 
+		// The table baseline is simply set to the first cell that has a baseline.
+		if (!baseline_set && cell_box->GetBaselineOfLastLine(table_baseline))
+		{
+			table_baseline += cell_offset.y;
+			baseline_set = true;
+		}
+
 		// The cell contents may overflow, propagate this to the 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
 } // namespace Rml

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

@@ -0,0 +1,95 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_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).
+	/// @param[out] table_baseline The baseline of the table wrapper, in terms of the vertical distance from its top-left border corner.
+	/// @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, float& table_baseline) 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,
+		float& table_baseline) const;
+
+	Element* element_table = nullptr;
+	TableWrapper* table_wrapper_box = nullptr;
+
+	TableGrid grid;
+
+	bool table_auto_height = false;
+	Vector2f table_min_size, table_max_size;
+	Vector2f table_gap;
+	Vector2f table_content_offset;
+	Vector2f table_initial_content_size;
+};
+
+} // namespace Rml
+#endif

+ 68 - 47
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
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * 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 "LayoutDetails.h"
 #include <algorithm>
 #include <algorithm>
 #include <float.h>
 #include <float.h>
 
 
 namespace Rml {
 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;
 	ElementList non_parented_cell_elements;
 
 
 	const int num_table_children = element_table->GetNumChildren();
 	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)
 		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();
 			non_parented_cell_elements.clear();
 		}
 		}
 
 
@@ -62,7 +64,7 @@ bool TableGrid::Build(Element* element_table)
 		}
 		}
 		else if (display == Display::TableRow)
 		else if (display == Display::TableRow)
 		{
 		{
-			PushRow(element, {});
+			PushRow(element, {}, table_wrapper);
 		}
 		}
 		else if (display == Display::TableRowGroup)
 		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++)
 			for (int j = 0; j < num_row_group_children; j++)
 			{
 			{
-				using Display = Style::Display;
-
 				Element* element_row = element->GetChild(j);
 				Element* element_row = element->GetChild(j);
 				const Display display_row = element_row->GetDisplay();
 				const Display display_row = element_row->GetDisplay();
 
 
@@ -81,12 +81,13 @@ bool TableGrid::Build(Element* element_table)
 				{
 				{
 					if (display_row != Display::None)
 					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;
 					continue;
 				}
 				}
 
 
-				PushRow(element_row, {});
+				PushRow(element_row, {}, table_wrapper);
 				num_rows_added += 1;
 				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].element_group = element;
 				rows[row_group_index].group_span = num_rows_added;
 				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)
 		else if (rows.empty() && display == Display::TableColumn)
 		{
 		{
@@ -108,26 +111,30 @@ bool TableGrid::Build(Element* element_table)
 		else
 		else
 		{
 		{
 			if (display == Display::TableColumn || display == Display::TableColumnGroup)
 			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
 			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())
 	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();
 		non_parented_cell_elements.clear();
 	}
 	}
 
 
 	// Sort cells by the last row they span.
 	// 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())
 	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.clear();
 	open_cells.shrink_to_fit();
 	open_cells.shrink_to_fit();
@@ -135,7 +142,6 @@ bool TableGrid::Build(Element* element_table)
 	return true;
 	return true;
 }
 }
 
 
-
 void TableGrid::PushColumn(Element* element_column, int span)
 void TableGrid::PushColumn(Element* element_column, int span)
 {
 {
 	Column column;
 	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();
 	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());
 				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();
 	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.
 		// 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;
 			continue_offset_column = false;
 			for (int k = 0; k < num_cells_spanning_this_row; k++)
 			for (int k = 0; k < num_cells_spanning_this_row; k++)
@@ -270,35 +279,49 @@ void TableGrid::PushRow(Element* element_row, ElementList cell_elements)
 
 
 		if (column_last >= (int)columns.size())
 		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());
 				row_index + 1, element_cell->GetAddress().c_str(), (int)columns.size());
 			break;
 			break;
 		}
 		}
 
 
-		// Add the new cell to our list.
-		open_cells.emplace_back();
-		Cell& cell = open_cells.back();
-
-		cell.element_cell = element_cell;
-		cell.row_begin = row_index;
-		cell.row_last = row_index + row_span - 1;
-		cell.column_begin = column;
-		cell.column_last = column_last;
+		const Style::Position cell_position = element_cell->GetPosition();
+		if (cell_position == Style::Position::Absolute || cell_position == Style::Position::Fixed)
+		{
+			ContainerBox* containing_box = LayoutDetails::GetContainingBlock(&table_wrapper, cell_position).container;
+			containing_box->AddAbsoluteElement(element_cell, {}, table_wrapper.GetElement());
+		}
+		else
+		{
+			// Add the new cell to our list.
+			open_cells.emplace_back();
+			Cell& cell = open_cells.back();
+
+			cell.element_cell = element_cell;
+			cell.row_begin = row_index;
+			cell.row_last = row_index + row_span - 1;
+			cell.column_begin = column;
+			cell.column_last = column_last;
+
+			if (cell_position == Style::Position::Relative)
+				table_wrapper.AddRelativeElement(element_cell);
+		}
 
 
 		column += col_span;
 		column += col_span;
 	}
 	}
 
 
-
 	// Partition the cells to determine those who end at this row.
 	// 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.
 	// Close cells ending at this row.
 	cells.insert(cells.end(), open_cells.begin(), it_cells_in_row_end);
 	cells.insert(cells.end(), open_cells.begin(), it_cells_in_row_end);
 	open_cells.erase(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);
 	LayoutDetails::GetEdgeSizes(margin_a, margin_b, padding_border_a, padding_border_b, computed, table_initial_content_size);
 }
 }
@@ -401,7 +424,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);
 	RMLUI_ASSERT(span >= 1);
 
 
@@ -483,15 +507,15 @@ void TracksSizing::ResolveFlexibleSize()
 	float table_available_size = 0.0f;
 	float table_available_size = 0.0f;
 
 
 	// Convert any flexible sizes to fixed sizes by filling up the size of the table.
 	// 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;
 		continue_iteration = false;
 		float fr_to_px_ratio = 0;
 		float fr_to_px_ratio = 0;
 
 
 		// Calculate the fr/px-ratio. [fr] is here the unit for flexible width.
 		// 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)
 			for (const TrackMetric& metric : metrics)
 			{
 			{
@@ -544,9 +568,8 @@ void TracksSizing::ResolveFlexibleSize()
 		}
 		}
 
 
 		// Sort the tracks by available size, smallest to largest. This lets us "fill up" the most constrained tracks first.
 		// 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++)
 		for (int i = 0; i < num_tracks; i++)
 		{
 		{
@@ -565,8 +588,6 @@ void TracksSizing::ResolveFlexibleSize()
 	}
 	}
 }
 }
 
 
-
-
 static float InitializeTrackBoxes(TrackBoxList& boxes, const TrackMetricList& metrics, const float table_gap)
 static float InitializeTrackBoxes(TrackBoxList& boxes, const TrackMetricList& metrics, const float table_gap)
 {
 {
 	boxes.resize(metrics.size());
 	boxes.resize(metrics.size());
@@ -606,7 +627,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 float columns_width = InitializeTrackBoxes(column_boxes, column_metrics, table_gap_x);
 	const int num_columns = (int)column_metrics.size();
 	const int num_columns = (int)column_metrics.size();
@@ -660,5 +682,4 @@ float BuildRowBoxes(TrackBoxList& row_boxes, const TrackMetricList& row_metrics,
 	return rows_height;
 	return rows_height;
 }
 }
 
 
-
 } // namespace Rml
 } // 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
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  * all copies or substantial portions of the Software.
- * 
+ *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * 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_TABLEFORMATTINGDETAILS_H
+#define RMLUI_CORE_LAYOUT_TABLEFORMATTINGDETAILS_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>
 #include <float.h>
 
 
 namespace Rml {
 namespace Rml {
 
 
+class TableWrapper;
 struct ComputedAxisSize;
 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:
 public:
 	// Build a list of columns, rows, and cells in this table.
 	// 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 {
 	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 {
 	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 {
 	struct Cell {
 		Element* element_cell = nullptr;       // The <td> element.
 		Element* element_cell = nullptr;       // The <td> element.
@@ -81,7 +83,7 @@ private:
 
 
 	void PushOrMergeColumnsFromFirstRow(Element* element_cell, int column_begin, int span);
 	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;
 	CellList open_cells;
 };
 };
@@ -89,7 +91,7 @@ private:
 enum class TrackSizingMode { Auto, Fixed, Flexible };
 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 {
 struct TrackMetric {
 	// All sizes are defined in terms of the border size of cells in the row or column.
 	// 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>;
 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 {
 class TracksSizing {
 public:
 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.
 	// 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;
 	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.
 	// 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;
 	TrackMetricList& metrics;
 	const float table_initial_content_size;
 	const float table_initial_content_size;
 	const float table_gap;
 	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 {
 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>;
 using TrackBoxList = Vector<TrackBox>;
 
 
-
 // Build a list of column boxes from the provided metrics.
 // Build a list of column boxes from the provided metrics.
 // @return The accumulated width of all columns.
 // @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.
 // Build a list of row boxes from the provided metrics.
 // @return The accumulated height of all rows.
 // @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

Some files were not shown because too many files changed in this diff