Yoga layout runtimes

Starting to implement layout component with Yoga in CPP. Layouts works on iOS, macOS, Android and web runtimes.

https://github.com/rive-app/rive/assets/186340/e09e639a-d38e-46b8-951d-a5ecc392b53a

Diffs=
6c76b425f Yoga layout runtimes (#6787)

Co-authored-by: Luigi Rosso <luigi.rosso@gmail.com>
Co-authored-by: Philip Chung <philterdesign@gmail.com>
diff --git a/.rive_head b/.rive_head
index 918acd8..499eaf1 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-097b68f5616951d83b0c5b28345e6bfc9f0b1a5b
+6c76b425f887a8adb3beb8ba8263200b9cdbb2c9
diff --git a/build.sh b/build.sh
index ec40d62..e89795d 100755
--- a/build.sh
+++ b/build.sh
@@ -32,8 +32,8 @@
 else
     build() {
         echo "Building Rive for platform=$platform option=$OPTION"
-        echo premake5 gmake2 --with_rive_text --with_rive_audio=system "$1"
-        PREMAKE="premake5 gmake2 --with_rive_text --with_rive_audio=system $1"
+        echo premake5 gmake2 --with_rive_text --with_rive_audio=system --with_rive_layout "$1"
+        PREMAKE="premake5 gmake2 --with_rive_text --with_rive_audio=system --with_rive_layout $1"
         eval "$PREMAKE"
         if [ "$OPTION" = "clean" ]; then
             make clean
diff --git a/build/premake5.lua b/build/premake5.lua
index dfe2dd1..576761c 100644
--- a/build/premake5.lua
+++ b/build/premake5.lua
@@ -25,11 +25,16 @@
         'MA_NO_RESOURCE_MANAGER',
     })
 end
+filter({ 'options:with_rive_layout' })
+do
+    defines({ 'WITH_RIVE_LAYOUT' })
+end
 filter({})
 
 dofile(path.join(path.getabsolute('../dependencies/'), 'premake5_harfbuzz.lua'))
 dofile(path.join(path.getabsolute('../dependencies/'), 'premake5_sheenbidi.lua'))
 dofile(path.join(path.getabsolute('../dependencies/'), 'premake5_miniaudio.lua'))
+dofile(path.join(path.getabsolute('../dependencies/'), 'premake5_yoga.lua'))
 
 project('rive')
 do
@@ -43,8 +48,11 @@
         harfbuzz .. '/src',
         sheenbidi .. '/Headers',
         miniaudio,
+        yoga
     })
 
+    defines({ 'YOGA_EXPORT=' })
+
     files({ '../src/**.cpp' })
 
     flags({ 'FatalCompileWarnings' })
@@ -205,3 +213,8 @@
     description = 'The audio mode to use.',
     allowed = { { 'disabled' }, { 'system' }, { 'external' } },
 })
+
+newoption({
+    trigger = 'with_rive_layout',
+    description = 'Compiles in layout features.',
+})
\ No newline at end of file
diff --git a/dependencies/premake5_yoga.lua b/dependencies/premake5_yoga.lua
new file mode 100644
index 0000000..d4149e7
--- /dev/null
+++ b/dependencies/premake5_yoga.lua
@@ -0,0 +1,138 @@
+local dependency = require('dependency')
+yoga = dependency.github('rive-app/yoga', 'rive_changes_v2_0_1')
+
+workspace('rive')
+configurations({ 'debug', 'release' })
+
+project('rive_yoga')
+do
+    kind('StaticLib')
+    language('C++')
+    cppdialect('C++11')
+    targetdir('%{cfg.system}/cache/bin/%{cfg.buildcfg}/')
+    objdir('%{cfg.system}/cache/obj/%{cfg.buildcfg}/')
+    warnings('Off')
+
+    defines({ 'YOGA_EXPORT=' })
+
+    includedirs({ yoga })
+
+    files({ 
+        yoga .. '/yoga/Utils.cpp',
+        yoga .. '/yoga/YGConfig.cpp',
+        yoga .. '/yoga/YGLayout.cpp',
+        yoga .. '/yoga/YGEnums.cpp',
+        yoga .. '/yoga/YGNodePrint.cpp',
+        yoga .. '/yoga/YGNode.cpp',
+        yoga .. '/yoga/YGValue.cpp',
+        yoga .. '/yoga/YGStyle.cpp',
+        yoga .. '/yoga/Yoga.cpp',
+        yoga .. '/yoga/event/event.cpp',
+        yoga .. '/yoga/log.cpp',
+    })
+
+    buildoptions({ '-Wall', '-pedantic' })
+
+    linkoptions({ '-r' })
+
+    filter('system:emscripten')
+    do
+        buildoptions({ '-pthread' })
+    end
+
+    filter('configurations:debug')
+    do
+        defines({ 'DEBUG' })
+        symbols('On')
+    end
+
+    filter('toolset:clang')
+    do
+        flags({ 'FatalWarnings' })
+        buildoptions({
+            '-Werror=format',
+            '-Wimplicit-int-conversion',
+            '-Werror=vla',
+        })
+    end
+
+    filter('configurations:release')
+    do
+        buildoptions({ '-Oz' })
+        defines({ 'RELEASE', 'NDEBUG' })
+        optimize('On')
+    end
+
+    filter({ 'system:macosx', 'options:variant=runtime' })
+    do
+        buildoptions({
+            '-Wimplicit-float-conversion -fembed-bitcode -arch arm64 -arch x86_64 -isysroot'
+                .. (os.getenv('MACOS_SYSROOT') or ''),
+        })
+    end
+
+    filter({ 'system:macosx', 'configurations:release' })
+    do
+        buildoptions({ '-flto=full' })
+    end
+
+    filter({ 'system:ios' })
+    do
+        buildoptions({ '-flto=full' })
+    end
+
+    filter('system:windows')
+    do
+        architecture('x64')
+        defines({ '_USE_MATH_DEFINES' })
+    end
+
+    filter({ 'system:ios', 'options:variant=system' })
+    do
+        buildoptions({
+            '-mios-version-min=13.0 -fembed-bitcode -arch arm64 -isysroot '
+                .. (os.getenv('IOS_SYSROOT') or ''),
+        })
+    end
+
+    filter({ 'system:ios', 'options:variant=emulator' })
+    do
+        buildoptions({
+            '--target=arm64-apple-ios13.0.0-simulator',
+            '-mios-version-min=13.0 -arch arm64 -arch x86_64 -isysroot '
+                .. (os.getenv('IOS_SYSROOT') or ''),
+        })
+        targetdir('%{cfg.system}_sim/cache/bin/%{cfg.buildcfg}')
+        objdir('%{cfg.system}_sim/cache/obj/%{cfg.buildcfg}')
+    end
+
+    filter({ 'system:android', 'configurations:release' })
+    do
+        buildoptions({ '-flto=full' })
+    end
+
+    -- Is there a way to pass 'arch' as a variable here?
+    filter({ 'system:android', 'options:arch=x86' })
+    do
+        targetdir('%{cfg.system}/cache/x86/bin/%{cfg.buildcfg}')
+        objdir('%{cfg.system}/cache/x86/obj/%{cfg.buildcfg}')
+    end
+
+    filter({ 'system:android', 'options:arch=x64' })
+    do
+        targetdir('%{cfg.system}/cache/x64/bin/%{cfg.buildcfg}')
+        objdir('%{cfg.system}/cache/x64/obj/%{cfg.buildcfg}')
+    end
+
+    filter({ 'system:android', 'options:arch=arm' })
+    do
+        targetdir('%{cfg.system}/cache/arm/bin/%{cfg.buildcfg}')
+        objdir('%{cfg.system}/cache/arm/obj/%{cfg.buildcfg}')
+    end
+
+    filter({ 'system:android', 'options:arch=arm64' })
+    do
+        targetdir('%{cfg.system}/cache/arm64/bin/%{cfg.buildcfg}')
+        objdir('%{cfg.system}/cache/arm64/obj/%{cfg.buildcfg}')
+    end
+end
diff --git a/dependencies/premake5_yoga_v2.lua b/dependencies/premake5_yoga_v2.lua
new file mode 100644
index 0000000..4583132
--- /dev/null
+++ b/dependencies/premake5_yoga_v2.lua
@@ -0,0 +1,28 @@
+dofile('rive_build_config.lua')
+
+local dependency = require('dependency')
+yoga = dependency.github('rive-app/yoga', 'rive_changes_v2_0_1')
+
+project('rive_yoga')
+do
+    kind('StaticLib')
+    warnings('Off')
+
+    defines({ 'YOGA_EXPORT=' })
+
+    includedirs({ yoga })
+
+    files({
+        yoga .. '/yoga/Utils.cpp',
+        yoga .. '/yoga/YGConfig.cpp',
+        yoga .. '/yoga/YGLayout.cpp',
+        yoga .. '/yoga/YGEnums.cpp',
+        yoga .. '/yoga/YGNodePrint.cpp',
+        yoga .. '/yoga/YGNode.cpp',
+        yoga .. '/yoga/YGValue.cpp',
+        yoga .. '/yoga/YGStyle.cpp',
+        yoga .. '/yoga/Yoga.cpp',
+        yoga .. '/yoga/event/event.cpp',
+        yoga .. '/yoga/log.cpp',
+    })
+end
diff --git a/dev/defs/artboard.json b/dev/defs/artboard.json
index 628ce9b..4d482a9 100644
--- a/dev/defs/artboard.json
+++ b/dev/defs/artboard.json
@@ -4,35 +4,8 @@
     "int": 1,
     "string": "artboard"
   },
-  "extends": "world_transform_component.json",
+  "extends": "layout_component.json",
   "properties": {
-    "clip": {
-      "type": "bool",
-      "initialValue": "true",
-      "key": {
-        "int": 196,
-        "string": "clip"
-      },
-      "description": "True when the artboard bounds clip its contents."
-    },
-    "width": {
-      "type": "double",
-      "initialValue": "0",
-      "key": {
-        "int": 7,
-        "string": "w"
-      },
-      "description": "Width of the artboard."
-    },
-    "height": {
-      "type": "double",
-      "initialValue": "0",
-      "key": {
-        "int": 8,
-        "string": "h"
-      },
-      "description": "Height of the artboard."
-    },
     "x": {
       "type": "double",
       "initialValue": "0",
diff --git a/dev/defs/layout/layout_component_style.json b/dev/defs/layout/layout_component_style.json
new file mode 100644
index 0000000..d3da49c
--- /dev/null
+++ b/dev/defs/layout/layout_component_style.json
@@ -0,0 +1,307 @@
+{
+  "name": "LayoutComponentStyle",
+  "key": {
+    "int": 420,
+    "string": "layoutcomponentstyle"
+  },
+  "extends": "component.json",
+  "properties": {
+    "layoutFlags0": {
+      "type": "uint",
+      "initialValue": "0x5000412",
+      "key": {
+        "int": 495,
+        "string": "layoutflags0"
+      },
+      "description": "First BitFlags for layout styles."
+    },
+    "layoutFlags1": {
+      "type": "uint",
+      "initialValue": "0x00",
+      "key": {
+        "int": 496,
+        "string": "layoutflags1"
+      },
+      "description": "Second BitFlags for layout styles."
+    },
+    "layoutFlags2": {
+      "type": "uint",
+      "initialValue": "0x00",
+      "key": {
+        "int": 497,
+        "string": "layoutflags2"
+      },
+      "description": "Third BitFlags for layout styles."
+    },
+    "gapHorizontal": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 498,
+        "string": "gaphorizontal"
+      },
+      "description": "Horizontal gap between children in layout component"
+    },
+    "gapVertical": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 499,
+        "string": "gapvertical"
+      },
+      "description": "Vertical gap between children in layout component"
+    },
+    "maxWidth": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 500,
+        "string": "maxwidth"
+      },
+      "description": "Max width of the item."
+    },
+    "maxHeight": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 501,
+        "string": "maxheight"
+      },
+      "description": "Max height of the item."
+    },
+    "minWidth": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 502,
+        "string": "minwidth"
+      },
+      "description": "Min width of the item."
+    },
+    "minHeight": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 503,
+        "string": "minheight"
+      },
+      "description": "Min height of the item."
+    },
+    "borderLeft": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 504,
+        "string": "borderleft"
+      },
+      "description": "Left border value."
+    },
+    "borderRight": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 505,
+        "string": "borderright"
+      },
+      "description": "Right border value."
+    },
+    "borderTop": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 506,
+        "string": "bordertop"
+      },
+      "description": "Top border value."
+    },
+    "borderBottom": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 507,
+        "string": "borderbottom"
+      },
+      "description": "Bottom border value."
+    },
+    "marginLeft": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 508,
+        "string": "marginleft"
+      },
+      "description": "Left margin value."
+    },
+    "marginRight": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 509,
+        "string": "marginright"
+      },
+      "description": "Right margin value."
+    },
+    "marginTop": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 510,
+        "string": "margintop"
+      },
+      "description": "Top margin value."
+    },
+    "marginBottom": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 511,
+        "string": "marginbottom"
+      },
+      "description": "Bottom margin value."
+    },
+    "paddingLeft": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 512,
+        "string": "paddingleft"
+      },
+      "description": "Left padding value."
+    },
+    "paddingRight": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 513,
+        "string": "paddingright"
+      },
+      "description": "Right padding value."
+    },
+    "paddingTop": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 514,
+        "string": "paddingtop"
+      },
+      "description": "Top padding value."
+    },
+    "paddingBottom": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 515,
+        "string": "paddingbottom"
+      },
+      "description": "Bottom padding value."
+    },
+    "positionLeft": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 516,
+        "string": "positionleft"
+      },
+      "description": "Left position value."
+    },
+    "positionRight": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 517,
+        "string": "positionright"
+      },
+      "description": "Right position value."
+    },
+    "positionTop": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 518,
+        "string": "positiontop"
+      },
+      "description": "Top position value."
+    },
+    "positionBottom": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 519,
+        "string": "positionbottom"
+      },
+      "description": "Bottom position value."
+    },
+    "flex": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 520,
+        "string": "flex"
+      },
+      "description": "Flex value."
+    },
+    "flexGrow": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 521,
+        "string": "flexgrow"
+      },
+      "description": "Flex grow value."
+    },
+    "flexShrink": {
+      "type": "double",
+      "initialValue": "1",
+      "animates": true,
+      "key": {
+        "int": 522,
+        "string": "flexshrink"
+      },
+      "description": "Flex shrink value."
+    },
+    "flexBasis": {
+      "type": "double",
+      "initialValue": "1",
+      "animates": true,
+      "key": {
+        "int": 523,
+        "string": "flexbasis"
+      },
+      "description": "Flex basis value."
+    },
+    "aspectRatio": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 524,
+        "string": "aspectratio"
+      },
+      "description": "Aspect ratio value."
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev/defs/layout_component.json b/dev/defs/layout_component.json
new file mode 100644
index 0000000..ac24d51
--- /dev/null
+++ b/dev/defs/layout_component.json
@@ -0,0 +1,50 @@
+{
+  "name": "LayoutComponent",
+  "key": {
+    "int": 409,
+    "string": "layoutcomponent"
+  },
+  "extends": "world_transform_component.json",
+  "properties": {
+    "clip": {
+      "type": "bool",
+      "initialValue": "true",
+      "key": {
+        "int": 196,
+        "string": "clip"
+      },
+      "description": "True when the layout component bounds clip its contents."
+    },
+    "width": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 7,
+        "string": "w"
+      },
+      "description": "Initial width of the item."
+    },
+    "height": {
+      "type": "double",
+      "initialValue": "0",
+      "animates": true,
+      "key": {
+        "int": 8,
+        "string": "h"
+      },
+      "description": "Initial height of the item."
+    },
+    "styleId": {
+      "type": "Id",
+      "typeRuntime": "uint",
+      "initialValue": "Core.missingId",
+      "initialValueRuntime": "-1",
+      "key": {
+        "int": 494,
+        "string": "styleid"
+      },
+      "description": "LayoutStyle that defines the styling for this LayoutComponent"
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev/defs/layout_component_absolute.json b/dev/defs/layout_component_absolute.json
new file mode 100644
index 0000000..a239d43
--- /dev/null
+++ b/dev/defs/layout_component_absolute.json
@@ -0,0 +1,19 @@
+{
+  "name": "AbsoluteLayoutComponent",
+  "key": {
+    "int": 423,
+    "string": "absolutelayoutcomponent"
+  },
+  "extends": "layout_component.json",
+  "properties": {
+    "constraints": {
+      "type": "uint",
+      "initialValue": "0",
+      "key": {
+        "int": 535,
+        "string": "constraints"
+      },
+      "runtime": false
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev/defs/nested_artboard.json b/dev/defs/nested_artboard.json
index abe086a..e6a5211 100644
--- a/dev/defs/nested_artboard.json
+++ b/dev/defs/nested_artboard.json
@@ -16,6 +16,22 @@
         "string": "artboardid"
       },
       "description": "Identifier used to track the Artboard nested."
+    },
+    "fit": {
+      "type": "uint",
+      "key": {
+        "int": 538,
+        "string": "fit"
+      },
+      "description": "Fit type for the nested artboard's runtime artboard."
+    },
+    "alignment": {
+      "type": "uint",
+      "key": {
+        "int": 539,
+        "string": "alignment"
+      },
+      "description": "Alignment type for the nested artboard's runtime artboard."
     }
   }
 }
\ No newline at end of file
diff --git a/dev/test.sh b/dev/test.sh
index 4e4047b..0167ca2 100755
--- a/dev/test.sh
+++ b/dev/test.sh
@@ -75,7 +75,7 @@
 popd
 
 export PREMAKE_PATH="$RUNTIME/dependencies/export-compile-commands":"$RUNTIME/build":"$PREMAKE_PATH"
-PREMAKE_COMMANDS="--with_rive_text --with_rive_audio=external --config=$CONFIG"
+PREMAKE_COMMANDS="--with_rive_text --with_rive_audio=external  --with_rive_layout --config=$CONFIG"
 
 out_dir() {
   echo "out/$CONFIG"
diff --git a/dev/test/premake5.lua b/dev/test/premake5.lua
index 7fe5e83..00db702 100644
--- a/dev/test/premake5.lua
+++ b/dev/test/premake5.lua
@@ -7,6 +7,8 @@
     'WITH_RIVE_TEXT',
     'WITH_RIVE_AUDIO',
     'WITH_RIVE_AUDIO_TOOLS',
+    'WITH_RIVE_LAYOUT',
+    'YOGA_EXPORT='
 })
 
 dofile(path.join(path.getabsolute('../../'), 'premake5_v2.lua'))
@@ -22,12 +24,14 @@
         '../../include',
         '../../decoders/include',
         miniaudio,
+        yoga,
     })
 
     links({
         'rive',
         'rive_harfbuzz',
         'rive_sheenbidi',
+        'rive_yoga',
         'rive_decoders',
         'libpng',
         'zlib',
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp
index d93d91d..97d6afd 100644
--- a/include/rive/artboard.hpp
+++ b/include/rive/artboard.hpp
@@ -15,6 +15,7 @@
 #include "rive/math/raw_path.hpp"
 
 #include <queue>
+#include <unordered_set>
 #include <vector>
 
 namespace rive
@@ -64,6 +65,9 @@
     Drawable* m_FirstDrawable = nullptr;
     bool m_IsInstance = false;
     bool m_FrameOrigin = true;
+    std::unordered_set<LayoutComponent*> m_dirtyLayout;
+    float m_originalWidth = 0;
+    float m_originalHeight = 0;
 
 #ifdef EXTERNAL_RIVE_AUDIO_ENGINE
     rcp<AudioEngine> m_audioEngine;
@@ -106,6 +110,11 @@
     void update(ComponentDirt value) override;
     void onDirty(ComponentDirt dirt) override;
 
+    void markLayoutDirty(LayoutComponent* layoutComponent)
+    {
+        m_dirtyLayout.insert(layoutComponent);
+    }
+
     bool advance(double elapsedSeconds);
     bool hasChangedDrawOrderInLastUpdate() { return m_HasChangedDrawOrderInLastUpdate; }
     Drawable* firstDrawable() { return m_FirstDrawable; }
@@ -129,6 +138,9 @@
     NestedArtboard* nestedArtboard(const std::string& name) const;
     NestedArtboard* nestedArtboardAtPath(const std::string& path) const;
 
+    float originalWidth() { return m_originalWidth; }
+    float originalHeight() { return m_originalHeight; }
+
     AABB bounds() const;
 
     // Can we hide these from the public? (they use playable)
@@ -219,6 +231,8 @@
         artboardClone->m_Factory = m_Factory;
         artboardClone->m_FrameOrigin = m_FrameOrigin;
         artboardClone->m_IsInstance = true;
+        artboardClone->m_originalWidth = m_originalWidth;
+        artboardClone->m_originalHeight = m_originalHeight;
 
         std::vector<Core*>& cloneObjects = artboardClone->m_Objects;
         cloneObjects.push_back(artboardClone.get());
@@ -267,6 +281,23 @@
     /// relative to the bounds.
     void frameOrigin(bool value);
 
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        bool result = ArtboardBase::deserialize(propertyKey, reader);
+        switch (propertyKey)
+        {
+            case widthPropertyKey:
+                m_originalWidth = width();
+                break;
+            case heightPropertyKey:
+                m_originalHeight = height();
+                break;
+            default:
+                break;
+        }
+        return result;
+    }
+
     StatusCode import(ImportStack& importStack) override;
 
     float volume() const;
diff --git a/include/rive/bounds_provider.hpp b/include/rive/bounds_provider.hpp
new file mode 100644
index 0000000..7f3ca83
--- /dev/null
+++ b/include/rive/bounds_provider.hpp
@@ -0,0 +1,17 @@
+#ifndef _RIVE_BOUNDS_PROVIDER_HPP_
+#define _RIVE_BOUNDS_PROVIDER_HPP_
+
+#include "rive/math/aabb.hpp"
+#include "rive/math/mat2d.hpp"
+
+namespace rive
+{
+
+class BoundsProvider
+{
+public:
+    virtual ~BoundsProvider() {}
+    virtual AABB computeBounds(Mat2D toParent);
+};
+} // namespace rive
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/artboard_base.hpp b/include/rive/generated/artboard_base.hpp
index feac51f..54a378b 100644
--- a/include/rive/generated/artboard_base.hpp
+++ b/include/rive/generated/artboard_base.hpp
@@ -1,15 +1,14 @@
 #ifndef _RIVE_ARTBOARD_BASE_HPP_
 #define _RIVE_ARTBOARD_BASE_HPP_
-#include "rive/core/field_types/core_bool_type.hpp"
 #include "rive/core/field_types/core_double_type.hpp"
 #include "rive/core/field_types/core_uint_type.hpp"
-#include "rive/world_transform_component.hpp"
+#include "rive/layout_component.hpp"
 namespace rive
 {
-class ArtboardBase : public WorldTransformComponent
+class ArtboardBase : public LayoutComponent
 {
 protected:
-    typedef WorldTransformComponent Super;
+    typedef LayoutComponent Super;
 
 public:
     static const uint16_t typeKey = 1;
@@ -21,6 +20,7 @@
         switch (typeKey)
         {
             case ArtboardBase::typeKey:
+            case LayoutComponentBase::typeKey:
             case WorldTransformComponentBase::typeKey:
             case ContainerComponentBase::typeKey:
             case ComponentBase::typeKey:
@@ -32,9 +32,6 @@
 
     uint16_t coreType() const override { return typeKey; }
 
-    static const uint16_t clipPropertyKey = 196;
-    static const uint16_t widthPropertyKey = 7;
-    static const uint16_t heightPropertyKey = 8;
     static const uint16_t xPropertyKey = 9;
     static const uint16_t yPropertyKey = 10;
     static const uint16_t originXPropertyKey = 11;
@@ -42,9 +39,6 @@
     static const uint16_t defaultStateMachineIdPropertyKey = 236;
 
 private:
-    bool m_Clip = true;
-    float m_Width = 0.0f;
-    float m_Height = 0.0f;
     float m_X = 0.0f;
     float m_Y = 0.0f;
     float m_OriginX = 0.0f;
@@ -52,39 +46,6 @@
     uint32_t m_DefaultStateMachineId = -1;
 
 public:
-    inline bool clip() const { return m_Clip; }
-    void clip(bool value)
-    {
-        if (m_Clip == value)
-        {
-            return;
-        }
-        m_Clip = value;
-        clipChanged();
-    }
-
-    inline float width() const { return m_Width; }
-    void width(float value)
-    {
-        if (m_Width == value)
-        {
-            return;
-        }
-        m_Width = value;
-        widthChanged();
-    }
-
-    inline float height() const { return m_Height; }
-    void height(float value)
-    {
-        if (m_Height == value)
-        {
-            return;
-        }
-        m_Height = value;
-        heightChanged();
-    }
-
     inline float x() const { return m_X; }
     void x(float value)
     {
@@ -143,30 +104,18 @@
     Core* clone() const override;
     void copy(const ArtboardBase& object)
     {
-        m_Clip = object.m_Clip;
-        m_Width = object.m_Width;
-        m_Height = object.m_Height;
         m_X = object.m_X;
         m_Y = object.m_Y;
         m_OriginX = object.m_OriginX;
         m_OriginY = object.m_OriginY;
         m_DefaultStateMachineId = object.m_DefaultStateMachineId;
-        WorldTransformComponent::copy(object);
+        LayoutComponent::copy(object);
     }
 
     bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
     {
         switch (propertyKey)
         {
-            case clipPropertyKey:
-                m_Clip = CoreBoolType::deserialize(reader);
-                return true;
-            case widthPropertyKey:
-                m_Width = CoreDoubleType::deserialize(reader);
-                return true;
-            case heightPropertyKey:
-                m_Height = CoreDoubleType::deserialize(reader);
-                return true;
             case xPropertyKey:
                 m_X = CoreDoubleType::deserialize(reader);
                 return true;
@@ -183,13 +132,10 @@
                 m_DefaultStateMachineId = CoreUintType::deserialize(reader);
                 return true;
         }
-        return WorldTransformComponent::deserialize(propertyKey, reader);
+        return LayoutComponent::deserialize(propertyKey, reader);
     }
 
 protected:
-    virtual void clipChanged() {}
-    virtual void widthChanged() {}
-    virtual void heightChanged() {}
     virtual void xChanged() {}
     virtual void yChanged() {}
     virtual void originXChanged() {}
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index c306c1f..f941648 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -104,6 +104,9 @@
 #include "rive/drawable.hpp"
 #include "rive/event.hpp"
 #include "rive/joystick.hpp"
+#include "rive/layout/layout_component_style.hpp"
+#include "rive/layout_component.hpp"
+#include "rive/layout_component_absolute.hpp"
 #include "rive/nested_animation.hpp"
 #include "rive/nested_artboard.hpp"
 #include "rive/node.hpp"
@@ -183,6 +186,8 @@
                 return new NestedArtboard();
             case SoloBase::typeKey:
                 return new Solo();
+            case LayoutComponentStyleBase::typeKey:
+                return new LayoutComponentStyle();
             case ListenerFireEventBase::typeKey:
                 return new ListenerFireEvent();
             case NestedSimpleAnimationBase::typeKey:
@@ -325,12 +330,16 @@
                 return new DrawRules();
             case CustomPropertyBooleanBase::typeKey:
                 return new CustomPropertyBoolean();
+            case LayoutComponentBase::typeKey:
+                return new LayoutComponent();
             case ArtboardBase::typeKey:
                 return new Artboard();
             case JoystickBase::typeKey:
                 return new Joystick();
             case BackboardBase::typeKey:
                 return new Backboard();
+            case AbsoluteLayoutComponentBase::typeKey:
+                return new AbsoluteLayoutComponent();
             case OpenUrlEventBase::typeKey:
                 return new OpenUrlEvent();
             case WeightBase::typeKey:
@@ -451,12 +460,27 @@
             case NestedArtboardBase::artboardIdPropertyKey:
                 object->as<NestedArtboardBase>()->artboardId(value);
                 break;
+            case NestedArtboardBase::fitPropertyKey:
+                object->as<NestedArtboardBase>()->fit(value);
+                break;
+            case NestedArtboardBase::alignmentPropertyKey:
+                object->as<NestedArtboardBase>()->alignment(value);
+                break;
             case NestedAnimationBase::animationIdPropertyKey:
                 object->as<NestedAnimationBase>()->animationId(value);
                 break;
             case SoloBase::activeComponentIdPropertyKey:
                 object->as<SoloBase>()->activeComponentId(value);
                 break;
+            case LayoutComponentStyleBase::layoutFlags0PropertyKey:
+                object->as<LayoutComponentStyleBase>()->layoutFlags0(value);
+                break;
+            case LayoutComponentStyleBase::layoutFlags1PropertyKey:
+                object->as<LayoutComponentStyleBase>()->layoutFlags1(value);
+                break;
+            case LayoutComponentStyleBase::layoutFlags2PropertyKey:
+                object->as<LayoutComponentStyleBase>()->layoutFlags2(value);
+                break;
             case ListenerFireEventBase::eventIdPropertyKey:
                 object->as<ListenerFireEventBase>()->eventId(value);
                 break;
@@ -604,6 +628,9 @@
             case DrawRulesBase::drawTargetIdPropertyKey:
                 object->as<DrawRulesBase>()->drawTargetId(value);
                 break;
+            case LayoutComponentBase::styleIdPropertyKey:
+                object->as<LayoutComponentBase>()->styleId(value);
+                break;
             case ArtboardBase::defaultStateMachineIdPropertyKey:
                 object->as<ArtboardBase>()->defaultStateMachineId(value);
                 break;
@@ -754,6 +781,87 @@
             case NodeBase::yPropertyKey:
                 object->as<NodeBase>()->y(value);
                 break;
+            case LayoutComponentStyleBase::gapHorizontalPropertyKey:
+                object->as<LayoutComponentStyleBase>()->gapHorizontal(value);
+                break;
+            case LayoutComponentStyleBase::gapVerticalPropertyKey:
+                object->as<LayoutComponentStyleBase>()->gapVertical(value);
+                break;
+            case LayoutComponentStyleBase::maxWidthPropertyKey:
+                object->as<LayoutComponentStyleBase>()->maxWidth(value);
+                break;
+            case LayoutComponentStyleBase::maxHeightPropertyKey:
+                object->as<LayoutComponentStyleBase>()->maxHeight(value);
+                break;
+            case LayoutComponentStyleBase::minWidthPropertyKey:
+                object->as<LayoutComponentStyleBase>()->minWidth(value);
+                break;
+            case LayoutComponentStyleBase::minHeightPropertyKey:
+                object->as<LayoutComponentStyleBase>()->minHeight(value);
+                break;
+            case LayoutComponentStyleBase::borderLeftPropertyKey:
+                object->as<LayoutComponentStyleBase>()->borderLeft(value);
+                break;
+            case LayoutComponentStyleBase::borderRightPropertyKey:
+                object->as<LayoutComponentStyleBase>()->borderRight(value);
+                break;
+            case LayoutComponentStyleBase::borderTopPropertyKey:
+                object->as<LayoutComponentStyleBase>()->borderTop(value);
+                break;
+            case LayoutComponentStyleBase::borderBottomPropertyKey:
+                object->as<LayoutComponentStyleBase>()->borderBottom(value);
+                break;
+            case LayoutComponentStyleBase::marginLeftPropertyKey:
+                object->as<LayoutComponentStyleBase>()->marginLeft(value);
+                break;
+            case LayoutComponentStyleBase::marginRightPropertyKey:
+                object->as<LayoutComponentStyleBase>()->marginRight(value);
+                break;
+            case LayoutComponentStyleBase::marginTopPropertyKey:
+                object->as<LayoutComponentStyleBase>()->marginTop(value);
+                break;
+            case LayoutComponentStyleBase::marginBottomPropertyKey:
+                object->as<LayoutComponentStyleBase>()->marginBottom(value);
+                break;
+            case LayoutComponentStyleBase::paddingLeftPropertyKey:
+                object->as<LayoutComponentStyleBase>()->paddingLeft(value);
+                break;
+            case LayoutComponentStyleBase::paddingRightPropertyKey:
+                object->as<LayoutComponentStyleBase>()->paddingRight(value);
+                break;
+            case LayoutComponentStyleBase::paddingTopPropertyKey:
+                object->as<LayoutComponentStyleBase>()->paddingTop(value);
+                break;
+            case LayoutComponentStyleBase::paddingBottomPropertyKey:
+                object->as<LayoutComponentStyleBase>()->paddingBottom(value);
+                break;
+            case LayoutComponentStyleBase::positionLeftPropertyKey:
+                object->as<LayoutComponentStyleBase>()->positionLeft(value);
+                break;
+            case LayoutComponentStyleBase::positionRightPropertyKey:
+                object->as<LayoutComponentStyleBase>()->positionRight(value);
+                break;
+            case LayoutComponentStyleBase::positionTopPropertyKey:
+                object->as<LayoutComponentStyleBase>()->positionTop(value);
+                break;
+            case LayoutComponentStyleBase::positionBottomPropertyKey:
+                object->as<LayoutComponentStyleBase>()->positionBottom(value);
+                break;
+            case LayoutComponentStyleBase::flexPropertyKey:
+                object->as<LayoutComponentStyleBase>()->flex(value);
+                break;
+            case LayoutComponentStyleBase::flexGrowPropertyKey:
+                object->as<LayoutComponentStyleBase>()->flexGrow(value);
+                break;
+            case LayoutComponentStyleBase::flexShrinkPropertyKey:
+                object->as<LayoutComponentStyleBase>()->flexShrink(value);
+                break;
+            case LayoutComponentStyleBase::flexBasisPropertyKey:
+                object->as<LayoutComponentStyleBase>()->flexBasis(value);
+                break;
+            case LayoutComponentStyleBase::aspectRatioPropertyKey:
+                object->as<LayoutComponentStyleBase>()->aspectRatio(value);
+                break;
             case NestedLinearAnimationBase::mixPropertyKey:
                 object->as<NestedLinearAnimationBase>()->mix(value);
                 break;
@@ -928,11 +1036,11 @@
             case CubicDetachedVertexBase::outDistancePropertyKey:
                 object->as<CubicDetachedVertexBase>()->outDistance(value);
                 break;
-            case ArtboardBase::widthPropertyKey:
-                object->as<ArtboardBase>()->width(value);
+            case LayoutComponentBase::widthPropertyKey:
+                object->as<LayoutComponentBase>()->width(value);
                 break;
-            case ArtboardBase::heightPropertyKey:
-                object->as<ArtboardBase>()->height(value);
+            case LayoutComponentBase::heightPropertyKey:
+                object->as<LayoutComponentBase>()->height(value);
                 break;
             case ArtboardBase::xPropertyKey:
                 object->as<ArtboardBase>()->x(value);
@@ -1171,8 +1279,8 @@
             case CustomPropertyBooleanBase::propertyValuePropertyKey:
                 object->as<CustomPropertyBooleanBase>()->propertyValue(value);
                 break;
-            case ArtboardBase::clipPropertyKey:
-                object->as<ArtboardBase>()->clip(value);
+            case LayoutComponentBase::clipPropertyKey:
+                object->as<LayoutComponentBase>()->clip(value);
                 break;
             case TextModifierRangeBase::clampPropertyKey:
                 object->as<TextModifierRangeBase>()->clamp(value);
@@ -1259,10 +1367,20 @@
                 return object->as<DrawableBase>()->drawableFlags();
             case NestedArtboardBase::artboardIdPropertyKey:
                 return object->as<NestedArtboardBase>()->artboardId();
+            case NestedArtboardBase::fitPropertyKey:
+                return object->as<NestedArtboardBase>()->fit();
+            case NestedArtboardBase::alignmentPropertyKey:
+                return object->as<NestedArtboardBase>()->alignment();
             case NestedAnimationBase::animationIdPropertyKey:
                 return object->as<NestedAnimationBase>()->animationId();
             case SoloBase::activeComponentIdPropertyKey:
                 return object->as<SoloBase>()->activeComponentId();
+            case LayoutComponentStyleBase::layoutFlags0PropertyKey:
+                return object->as<LayoutComponentStyleBase>()->layoutFlags0();
+            case LayoutComponentStyleBase::layoutFlags1PropertyKey:
+                return object->as<LayoutComponentStyleBase>()->layoutFlags1();
+            case LayoutComponentStyleBase::layoutFlags2PropertyKey:
+                return object->as<LayoutComponentStyleBase>()->layoutFlags2();
             case ListenerFireEventBase::eventIdPropertyKey:
                 return object->as<ListenerFireEventBase>()->eventId();
             case LayerStateBase::flagsPropertyKey:
@@ -1361,6 +1479,8 @@
                 return object->as<ImageBase>()->assetId();
             case DrawRulesBase::drawTargetIdPropertyKey:
                 return object->as<DrawRulesBase>()->drawTargetId();
+            case LayoutComponentBase::styleIdPropertyKey:
+                return object->as<LayoutComponentBase>()->styleId();
             case ArtboardBase::defaultStateMachineIdPropertyKey:
                 return object->as<ArtboardBase>()->defaultStateMachineId();
             case JoystickBase::xIdPropertyKey:
@@ -1464,6 +1584,60 @@
                 return object->as<NodeBase>()->x();
             case NodeBase::yPropertyKey:
                 return object->as<NodeBase>()->y();
+            case LayoutComponentStyleBase::gapHorizontalPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->gapHorizontal();
+            case LayoutComponentStyleBase::gapVerticalPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->gapVertical();
+            case LayoutComponentStyleBase::maxWidthPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->maxWidth();
+            case LayoutComponentStyleBase::maxHeightPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->maxHeight();
+            case LayoutComponentStyleBase::minWidthPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->minWidth();
+            case LayoutComponentStyleBase::minHeightPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->minHeight();
+            case LayoutComponentStyleBase::borderLeftPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->borderLeft();
+            case LayoutComponentStyleBase::borderRightPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->borderRight();
+            case LayoutComponentStyleBase::borderTopPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->borderTop();
+            case LayoutComponentStyleBase::borderBottomPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->borderBottom();
+            case LayoutComponentStyleBase::marginLeftPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->marginLeft();
+            case LayoutComponentStyleBase::marginRightPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->marginRight();
+            case LayoutComponentStyleBase::marginTopPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->marginTop();
+            case LayoutComponentStyleBase::marginBottomPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->marginBottom();
+            case LayoutComponentStyleBase::paddingLeftPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->paddingLeft();
+            case LayoutComponentStyleBase::paddingRightPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->paddingRight();
+            case LayoutComponentStyleBase::paddingTopPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->paddingTop();
+            case LayoutComponentStyleBase::paddingBottomPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->paddingBottom();
+            case LayoutComponentStyleBase::positionLeftPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->positionLeft();
+            case LayoutComponentStyleBase::positionRightPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->positionRight();
+            case LayoutComponentStyleBase::positionTopPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->positionTop();
+            case LayoutComponentStyleBase::positionBottomPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->positionBottom();
+            case LayoutComponentStyleBase::flexPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->flex();
+            case LayoutComponentStyleBase::flexGrowPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->flexGrow();
+            case LayoutComponentStyleBase::flexShrinkPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->flexShrink();
+            case LayoutComponentStyleBase::flexBasisPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->flexBasis();
+            case LayoutComponentStyleBase::aspectRatioPropertyKey:
+                return object->as<LayoutComponentStyleBase>()->aspectRatio();
             case NestedLinearAnimationBase::mixPropertyKey:
                 return object->as<NestedLinearAnimationBase>()->mix();
             case NestedSimpleAnimationBase::speedPropertyKey:
@@ -1580,10 +1754,10 @@
                 return object->as<CubicDetachedVertexBase>()->outRotation();
             case CubicDetachedVertexBase::outDistancePropertyKey:
                 return object->as<CubicDetachedVertexBase>()->outDistance();
-            case ArtboardBase::widthPropertyKey:
-                return object->as<ArtboardBase>()->width();
-            case ArtboardBase::heightPropertyKey:
-                return object->as<ArtboardBase>()->height();
+            case LayoutComponentBase::widthPropertyKey:
+                return object->as<LayoutComponentBase>()->width();
+            case LayoutComponentBase::heightPropertyKey:
+                return object->as<LayoutComponentBase>()->height();
             case ArtboardBase::xPropertyKey:
                 return object->as<ArtboardBase>()->x();
             case ArtboardBase::yPropertyKey:
@@ -1745,8 +1919,8 @@
                 return object->as<ClippingShapeBase>()->isVisible();
             case CustomPropertyBooleanBase::propertyValuePropertyKey:
                 return object->as<CustomPropertyBooleanBase>()->propertyValue();
-            case ArtboardBase::clipPropertyKey:
-                return object->as<ArtboardBase>()->clip();
+            case LayoutComponentBase::clipPropertyKey:
+                return object->as<LayoutComponentBase>()->clip();
             case TextModifierRangeBase::clampPropertyKey:
                 return object->as<TextModifierRangeBase>()->clamp();
         }
@@ -1791,8 +1965,13 @@
             case DrawableBase::blendModeValuePropertyKey:
             case DrawableBase::drawableFlagsPropertyKey:
             case NestedArtboardBase::artboardIdPropertyKey:
+            case NestedArtboardBase::fitPropertyKey:
+            case NestedArtboardBase::alignmentPropertyKey:
             case NestedAnimationBase::animationIdPropertyKey:
             case SoloBase::activeComponentIdPropertyKey:
+            case LayoutComponentStyleBase::layoutFlags0PropertyKey:
+            case LayoutComponentStyleBase::layoutFlags1PropertyKey:
+            case LayoutComponentStyleBase::layoutFlags2PropertyKey:
             case ListenerFireEventBase::eventIdPropertyKey:
             case LayerStateBase::flagsPropertyKey:
             case ListenerInputChangeBase::inputIdPropertyKey:
@@ -1842,6 +2021,7 @@
             case PolygonBase::pointsPropertyKey:
             case ImageBase::assetIdPropertyKey:
             case DrawRulesBase::drawTargetIdPropertyKey:
+            case LayoutComponentBase::styleIdPropertyKey:
             case ArtboardBase::defaultStateMachineIdPropertyKey:
             case JoystickBase::xIdPropertyKey:
             case JoystickBase::yIdPropertyKey:
@@ -1891,6 +2071,33 @@
             case TransformComponentBase::scaleYPropertyKey:
             case NodeBase::xPropertyKey:
             case NodeBase::yPropertyKey:
+            case LayoutComponentStyleBase::gapHorizontalPropertyKey:
+            case LayoutComponentStyleBase::gapVerticalPropertyKey:
+            case LayoutComponentStyleBase::maxWidthPropertyKey:
+            case LayoutComponentStyleBase::maxHeightPropertyKey:
+            case LayoutComponentStyleBase::minWidthPropertyKey:
+            case LayoutComponentStyleBase::minHeightPropertyKey:
+            case LayoutComponentStyleBase::borderLeftPropertyKey:
+            case LayoutComponentStyleBase::borderRightPropertyKey:
+            case LayoutComponentStyleBase::borderTopPropertyKey:
+            case LayoutComponentStyleBase::borderBottomPropertyKey:
+            case LayoutComponentStyleBase::marginLeftPropertyKey:
+            case LayoutComponentStyleBase::marginRightPropertyKey:
+            case LayoutComponentStyleBase::marginTopPropertyKey:
+            case LayoutComponentStyleBase::marginBottomPropertyKey:
+            case LayoutComponentStyleBase::paddingLeftPropertyKey:
+            case LayoutComponentStyleBase::paddingRightPropertyKey:
+            case LayoutComponentStyleBase::paddingTopPropertyKey:
+            case LayoutComponentStyleBase::paddingBottomPropertyKey:
+            case LayoutComponentStyleBase::positionLeftPropertyKey:
+            case LayoutComponentStyleBase::positionRightPropertyKey:
+            case LayoutComponentStyleBase::positionTopPropertyKey:
+            case LayoutComponentStyleBase::positionBottomPropertyKey:
+            case LayoutComponentStyleBase::flexPropertyKey:
+            case LayoutComponentStyleBase::flexGrowPropertyKey:
+            case LayoutComponentStyleBase::flexShrinkPropertyKey:
+            case LayoutComponentStyleBase::flexBasisPropertyKey:
+            case LayoutComponentStyleBase::aspectRatioPropertyKey:
             case NestedLinearAnimationBase::mixPropertyKey:
             case NestedSimpleAnimationBase::speedPropertyKey:
             case AdvanceableStateBase::speedPropertyKey:
@@ -1949,8 +2156,8 @@
             case CubicDetachedVertexBase::inDistancePropertyKey:
             case CubicDetachedVertexBase::outRotationPropertyKey:
             case CubicDetachedVertexBase::outDistancePropertyKey:
-            case ArtboardBase::widthPropertyKey:
-            case ArtboardBase::heightPropertyKey:
+            case LayoutComponentBase::widthPropertyKey:
+            case LayoutComponentBase::heightPropertyKey:
             case ArtboardBase::xPropertyKey:
             case ArtboardBase::yPropertyKey:
             case ArtboardBase::originXPropertyKey:
@@ -2029,7 +2236,7 @@
             case RectangleBase::linkCornerRadiusPropertyKey:
             case ClippingShapeBase::isVisiblePropertyKey:
             case CustomPropertyBooleanBase::propertyValuePropertyKey:
-            case ArtboardBase::clipPropertyKey:
+            case LayoutComponentBase::clipPropertyKey:
             case TextModifierRangeBase::clampPropertyKey:
                 return CoreBoolType::id;
             case KeyFrameColorBase::valuePropertyKey:
@@ -2101,10 +2308,20 @@
                 return object->is<DrawableBase>();
             case NestedArtboardBase::artboardIdPropertyKey:
                 return object->is<NestedArtboardBase>();
+            case NestedArtboardBase::fitPropertyKey:
+                return object->is<NestedArtboardBase>();
+            case NestedArtboardBase::alignmentPropertyKey:
+                return object->is<NestedArtboardBase>();
             case NestedAnimationBase::animationIdPropertyKey:
                 return object->is<NestedAnimationBase>();
             case SoloBase::activeComponentIdPropertyKey:
                 return object->is<SoloBase>();
+            case LayoutComponentStyleBase::layoutFlags0PropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::layoutFlags1PropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::layoutFlags2PropertyKey:
+                return object->is<LayoutComponentStyleBase>();
             case ListenerFireEventBase::eventIdPropertyKey:
                 return object->is<ListenerFireEventBase>();
             case LayerStateBase::flagsPropertyKey:
@@ -2203,6 +2420,8 @@
                 return object->is<ImageBase>();
             case DrawRulesBase::drawTargetIdPropertyKey:
                 return object->is<DrawRulesBase>();
+            case LayoutComponentBase::styleIdPropertyKey:
+                return object->is<LayoutComponentBase>();
             case ArtboardBase::defaultStateMachineIdPropertyKey:
                 return object->is<ArtboardBase>();
             case JoystickBase::xIdPropertyKey:
@@ -2299,6 +2518,60 @@
                 return object->is<NodeBase>();
             case NodeBase::yPropertyKey:
                 return object->is<NodeBase>();
+            case LayoutComponentStyleBase::gapHorizontalPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::gapVerticalPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::maxWidthPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::maxHeightPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::minWidthPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::minHeightPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::borderLeftPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::borderRightPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::borderTopPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::borderBottomPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::marginLeftPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::marginRightPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::marginTopPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::marginBottomPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::paddingLeftPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::paddingRightPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::paddingTopPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::paddingBottomPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::positionLeftPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::positionRightPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::positionTopPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::positionBottomPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::flexPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::flexGrowPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::flexShrinkPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::flexBasisPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
+            case LayoutComponentStyleBase::aspectRatioPropertyKey:
+                return object->is<LayoutComponentStyleBase>();
             case NestedLinearAnimationBase::mixPropertyKey:
                 return object->is<NestedLinearAnimationBase>();
             case NestedSimpleAnimationBase::speedPropertyKey:
@@ -2415,10 +2688,10 @@
                 return object->is<CubicDetachedVertexBase>();
             case CubicDetachedVertexBase::outDistancePropertyKey:
                 return object->is<CubicDetachedVertexBase>();
-            case ArtboardBase::widthPropertyKey:
-                return object->is<ArtboardBase>();
-            case ArtboardBase::heightPropertyKey:
-                return object->is<ArtboardBase>();
+            case LayoutComponentBase::widthPropertyKey:
+                return object->is<LayoutComponentBase>();
+            case LayoutComponentBase::heightPropertyKey:
+                return object->is<LayoutComponentBase>();
             case ArtboardBase::xPropertyKey:
                 return object->is<ArtboardBase>();
             case ArtboardBase::yPropertyKey:
@@ -2573,8 +2846,8 @@
                 return object->is<ClippingShapeBase>();
             case CustomPropertyBooleanBase::propertyValuePropertyKey:
                 return object->is<CustomPropertyBooleanBase>();
-            case ArtboardBase::clipPropertyKey:
-                return object->is<ArtboardBase>();
+            case LayoutComponentBase::clipPropertyKey:
+                return object->is<LayoutComponentBase>();
             case TextModifierRangeBase::clampPropertyKey:
                 return object->is<TextModifierRangeBase>();
             case NestedTriggerBase::firePropertyKey:
diff --git a/include/rive/generated/layout/layout_component_style_base.hpp b/include/rive/generated/layout/layout_component_style_base.hpp
new file mode 100644
index 0000000..28dd01b
--- /dev/null
+++ b/include/rive/generated/layout/layout_component_style_base.hpp
@@ -0,0 +1,594 @@
+#ifndef _RIVE_LAYOUT_COMPONENT_STYLE_BASE_HPP_
+#define _RIVE_LAYOUT_COMPONENT_STYLE_BASE_HPP_
+#include "rive/component.hpp"
+#include "rive/core/field_types/core_double_type.hpp"
+#include "rive/core/field_types/core_uint_type.hpp"
+namespace rive
+{
+class LayoutComponentStyleBase : public Component
+{
+protected:
+    typedef Component Super;
+
+public:
+    static const uint16_t typeKey = 420;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case LayoutComponentStyleBase::typeKey:
+            case ComponentBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t layoutFlags0PropertyKey = 495;
+    static const uint16_t layoutFlags1PropertyKey = 496;
+    static const uint16_t layoutFlags2PropertyKey = 497;
+    static const uint16_t gapHorizontalPropertyKey = 498;
+    static const uint16_t gapVerticalPropertyKey = 499;
+    static const uint16_t maxWidthPropertyKey = 500;
+    static const uint16_t maxHeightPropertyKey = 501;
+    static const uint16_t minWidthPropertyKey = 502;
+    static const uint16_t minHeightPropertyKey = 503;
+    static const uint16_t borderLeftPropertyKey = 504;
+    static const uint16_t borderRightPropertyKey = 505;
+    static const uint16_t borderTopPropertyKey = 506;
+    static const uint16_t borderBottomPropertyKey = 507;
+    static const uint16_t marginLeftPropertyKey = 508;
+    static const uint16_t marginRightPropertyKey = 509;
+    static const uint16_t marginTopPropertyKey = 510;
+    static const uint16_t marginBottomPropertyKey = 511;
+    static const uint16_t paddingLeftPropertyKey = 512;
+    static const uint16_t paddingRightPropertyKey = 513;
+    static const uint16_t paddingTopPropertyKey = 514;
+    static const uint16_t paddingBottomPropertyKey = 515;
+    static const uint16_t positionLeftPropertyKey = 516;
+    static const uint16_t positionRightPropertyKey = 517;
+    static const uint16_t positionTopPropertyKey = 518;
+    static const uint16_t positionBottomPropertyKey = 519;
+    static const uint16_t flexPropertyKey = 520;
+    static const uint16_t flexGrowPropertyKey = 521;
+    static const uint16_t flexShrinkPropertyKey = 522;
+    static const uint16_t flexBasisPropertyKey = 523;
+    static const uint16_t aspectRatioPropertyKey = 524;
+
+private:
+    uint32_t m_LayoutFlags0 = 0x5000412;
+    uint32_t m_LayoutFlags1 = 0x00;
+    uint32_t m_LayoutFlags2 = 0x00;
+    float m_GapHorizontal = 0.0f;
+    float m_GapVertical = 0.0f;
+    float m_MaxWidth = 0.0f;
+    float m_MaxHeight = 0.0f;
+    float m_MinWidth = 0.0f;
+    float m_MinHeight = 0.0f;
+    float m_BorderLeft = 0.0f;
+    float m_BorderRight = 0.0f;
+    float m_BorderTop = 0.0f;
+    float m_BorderBottom = 0.0f;
+    float m_MarginLeft = 0.0f;
+    float m_MarginRight = 0.0f;
+    float m_MarginTop = 0.0f;
+    float m_MarginBottom = 0.0f;
+    float m_PaddingLeft = 0.0f;
+    float m_PaddingRight = 0.0f;
+    float m_PaddingTop = 0.0f;
+    float m_PaddingBottom = 0.0f;
+    float m_PositionLeft = 0.0f;
+    float m_PositionRight = 0.0f;
+    float m_PositionTop = 0.0f;
+    float m_PositionBottom = 0.0f;
+    float m_Flex = 0.0f;
+    float m_FlexGrow = 0.0f;
+    float m_FlexShrink = 1.0f;
+    float m_FlexBasis = 1.0f;
+    float m_AspectRatio = 0.0f;
+
+public:
+    inline uint32_t layoutFlags0() const { return m_LayoutFlags0; }
+    void layoutFlags0(uint32_t value)
+    {
+        if (m_LayoutFlags0 == value)
+        {
+            return;
+        }
+        m_LayoutFlags0 = value;
+        layoutFlags0Changed();
+    }
+
+    inline uint32_t layoutFlags1() const { return m_LayoutFlags1; }
+    void layoutFlags1(uint32_t value)
+    {
+        if (m_LayoutFlags1 == value)
+        {
+            return;
+        }
+        m_LayoutFlags1 = value;
+        layoutFlags1Changed();
+    }
+
+    inline uint32_t layoutFlags2() const { return m_LayoutFlags2; }
+    void layoutFlags2(uint32_t value)
+    {
+        if (m_LayoutFlags2 == value)
+        {
+            return;
+        }
+        m_LayoutFlags2 = value;
+        layoutFlags2Changed();
+    }
+
+    inline float gapHorizontal() const { return m_GapHorizontal; }
+    void gapHorizontal(float value)
+    {
+        if (m_GapHorizontal == value)
+        {
+            return;
+        }
+        m_GapHorizontal = value;
+        gapHorizontalChanged();
+    }
+
+    inline float gapVertical() const { return m_GapVertical; }
+    void gapVertical(float value)
+    {
+        if (m_GapVertical == value)
+        {
+            return;
+        }
+        m_GapVertical = value;
+        gapVerticalChanged();
+    }
+
+    inline float maxWidth() const { return m_MaxWidth; }
+    void maxWidth(float value)
+    {
+        if (m_MaxWidth == value)
+        {
+            return;
+        }
+        m_MaxWidth = value;
+        maxWidthChanged();
+    }
+
+    inline float maxHeight() const { return m_MaxHeight; }
+    void maxHeight(float value)
+    {
+        if (m_MaxHeight == value)
+        {
+            return;
+        }
+        m_MaxHeight = value;
+        maxHeightChanged();
+    }
+
+    inline float minWidth() const { return m_MinWidth; }
+    void minWidth(float value)
+    {
+        if (m_MinWidth == value)
+        {
+            return;
+        }
+        m_MinWidth = value;
+        minWidthChanged();
+    }
+
+    inline float minHeight() const { return m_MinHeight; }
+    void minHeight(float value)
+    {
+        if (m_MinHeight == value)
+        {
+            return;
+        }
+        m_MinHeight = value;
+        minHeightChanged();
+    }
+
+    inline float borderLeft() const { return m_BorderLeft; }
+    void borderLeft(float value)
+    {
+        if (m_BorderLeft == value)
+        {
+            return;
+        }
+        m_BorderLeft = value;
+        borderLeftChanged();
+    }
+
+    inline float borderRight() const { return m_BorderRight; }
+    void borderRight(float value)
+    {
+        if (m_BorderRight == value)
+        {
+            return;
+        }
+        m_BorderRight = value;
+        borderRightChanged();
+    }
+
+    inline float borderTop() const { return m_BorderTop; }
+    void borderTop(float value)
+    {
+        if (m_BorderTop == value)
+        {
+            return;
+        }
+        m_BorderTop = value;
+        borderTopChanged();
+    }
+
+    inline float borderBottom() const { return m_BorderBottom; }
+    void borderBottom(float value)
+    {
+        if (m_BorderBottom == value)
+        {
+            return;
+        }
+        m_BorderBottom = value;
+        borderBottomChanged();
+    }
+
+    inline float marginLeft() const { return m_MarginLeft; }
+    void marginLeft(float value)
+    {
+        if (m_MarginLeft == value)
+        {
+            return;
+        }
+        m_MarginLeft = value;
+        marginLeftChanged();
+    }
+
+    inline float marginRight() const { return m_MarginRight; }
+    void marginRight(float value)
+    {
+        if (m_MarginRight == value)
+        {
+            return;
+        }
+        m_MarginRight = value;
+        marginRightChanged();
+    }
+
+    inline float marginTop() const { return m_MarginTop; }
+    void marginTop(float value)
+    {
+        if (m_MarginTop == value)
+        {
+            return;
+        }
+        m_MarginTop = value;
+        marginTopChanged();
+    }
+
+    inline float marginBottom() const { return m_MarginBottom; }
+    void marginBottom(float value)
+    {
+        if (m_MarginBottom == value)
+        {
+            return;
+        }
+        m_MarginBottom = value;
+        marginBottomChanged();
+    }
+
+    inline float paddingLeft() const { return m_PaddingLeft; }
+    void paddingLeft(float value)
+    {
+        if (m_PaddingLeft == value)
+        {
+            return;
+        }
+        m_PaddingLeft = value;
+        paddingLeftChanged();
+    }
+
+    inline float paddingRight() const { return m_PaddingRight; }
+    void paddingRight(float value)
+    {
+        if (m_PaddingRight == value)
+        {
+            return;
+        }
+        m_PaddingRight = value;
+        paddingRightChanged();
+    }
+
+    inline float paddingTop() const { return m_PaddingTop; }
+    void paddingTop(float value)
+    {
+        if (m_PaddingTop == value)
+        {
+            return;
+        }
+        m_PaddingTop = value;
+        paddingTopChanged();
+    }
+
+    inline float paddingBottom() const { return m_PaddingBottom; }
+    void paddingBottom(float value)
+    {
+        if (m_PaddingBottom == value)
+        {
+            return;
+        }
+        m_PaddingBottom = value;
+        paddingBottomChanged();
+    }
+
+    inline float positionLeft() const { return m_PositionLeft; }
+    void positionLeft(float value)
+    {
+        if (m_PositionLeft == value)
+        {
+            return;
+        }
+        m_PositionLeft = value;
+        positionLeftChanged();
+    }
+
+    inline float positionRight() const { return m_PositionRight; }
+    void positionRight(float value)
+    {
+        if (m_PositionRight == value)
+        {
+            return;
+        }
+        m_PositionRight = value;
+        positionRightChanged();
+    }
+
+    inline float positionTop() const { return m_PositionTop; }
+    void positionTop(float value)
+    {
+        if (m_PositionTop == value)
+        {
+            return;
+        }
+        m_PositionTop = value;
+        positionTopChanged();
+    }
+
+    inline float positionBottom() const { return m_PositionBottom; }
+    void positionBottom(float value)
+    {
+        if (m_PositionBottom == value)
+        {
+            return;
+        }
+        m_PositionBottom = value;
+        positionBottomChanged();
+    }
+
+    inline float flex() const { return m_Flex; }
+    void flex(float value)
+    {
+        if (m_Flex == value)
+        {
+            return;
+        }
+        m_Flex = value;
+        flexChanged();
+    }
+
+    inline float flexGrow() const { return m_FlexGrow; }
+    void flexGrow(float value)
+    {
+        if (m_FlexGrow == value)
+        {
+            return;
+        }
+        m_FlexGrow = value;
+        flexGrowChanged();
+    }
+
+    inline float flexShrink() const { return m_FlexShrink; }
+    void flexShrink(float value)
+    {
+        if (m_FlexShrink == value)
+        {
+            return;
+        }
+        m_FlexShrink = value;
+        flexShrinkChanged();
+    }
+
+    inline float flexBasis() const { return m_FlexBasis; }
+    void flexBasis(float value)
+    {
+        if (m_FlexBasis == value)
+        {
+            return;
+        }
+        m_FlexBasis = value;
+        flexBasisChanged();
+    }
+
+    inline float aspectRatio() const { return m_AspectRatio; }
+    void aspectRatio(float value)
+    {
+        if (m_AspectRatio == value)
+        {
+            return;
+        }
+        m_AspectRatio = value;
+        aspectRatioChanged();
+    }
+
+    Core* clone() const override;
+    void copy(const LayoutComponentStyleBase& object)
+    {
+        m_LayoutFlags0 = object.m_LayoutFlags0;
+        m_LayoutFlags1 = object.m_LayoutFlags1;
+        m_LayoutFlags2 = object.m_LayoutFlags2;
+        m_GapHorizontal = object.m_GapHorizontal;
+        m_GapVertical = object.m_GapVertical;
+        m_MaxWidth = object.m_MaxWidth;
+        m_MaxHeight = object.m_MaxHeight;
+        m_MinWidth = object.m_MinWidth;
+        m_MinHeight = object.m_MinHeight;
+        m_BorderLeft = object.m_BorderLeft;
+        m_BorderRight = object.m_BorderRight;
+        m_BorderTop = object.m_BorderTop;
+        m_BorderBottom = object.m_BorderBottom;
+        m_MarginLeft = object.m_MarginLeft;
+        m_MarginRight = object.m_MarginRight;
+        m_MarginTop = object.m_MarginTop;
+        m_MarginBottom = object.m_MarginBottom;
+        m_PaddingLeft = object.m_PaddingLeft;
+        m_PaddingRight = object.m_PaddingRight;
+        m_PaddingTop = object.m_PaddingTop;
+        m_PaddingBottom = object.m_PaddingBottom;
+        m_PositionLeft = object.m_PositionLeft;
+        m_PositionRight = object.m_PositionRight;
+        m_PositionTop = object.m_PositionTop;
+        m_PositionBottom = object.m_PositionBottom;
+        m_Flex = object.m_Flex;
+        m_FlexGrow = object.m_FlexGrow;
+        m_FlexShrink = object.m_FlexShrink;
+        m_FlexBasis = object.m_FlexBasis;
+        m_AspectRatio = object.m_AspectRatio;
+        Component::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case layoutFlags0PropertyKey:
+                m_LayoutFlags0 = CoreUintType::deserialize(reader);
+                return true;
+            case layoutFlags1PropertyKey:
+                m_LayoutFlags1 = CoreUintType::deserialize(reader);
+                return true;
+            case layoutFlags2PropertyKey:
+                m_LayoutFlags2 = CoreUintType::deserialize(reader);
+                return true;
+            case gapHorizontalPropertyKey:
+                m_GapHorizontal = CoreDoubleType::deserialize(reader);
+                return true;
+            case gapVerticalPropertyKey:
+                m_GapVertical = CoreDoubleType::deserialize(reader);
+                return true;
+            case maxWidthPropertyKey:
+                m_MaxWidth = CoreDoubleType::deserialize(reader);
+                return true;
+            case maxHeightPropertyKey:
+                m_MaxHeight = CoreDoubleType::deserialize(reader);
+                return true;
+            case minWidthPropertyKey:
+                m_MinWidth = CoreDoubleType::deserialize(reader);
+                return true;
+            case minHeightPropertyKey:
+                m_MinHeight = CoreDoubleType::deserialize(reader);
+                return true;
+            case borderLeftPropertyKey:
+                m_BorderLeft = CoreDoubleType::deserialize(reader);
+                return true;
+            case borderRightPropertyKey:
+                m_BorderRight = CoreDoubleType::deserialize(reader);
+                return true;
+            case borderTopPropertyKey:
+                m_BorderTop = CoreDoubleType::deserialize(reader);
+                return true;
+            case borderBottomPropertyKey:
+                m_BorderBottom = CoreDoubleType::deserialize(reader);
+                return true;
+            case marginLeftPropertyKey:
+                m_MarginLeft = CoreDoubleType::deserialize(reader);
+                return true;
+            case marginRightPropertyKey:
+                m_MarginRight = CoreDoubleType::deserialize(reader);
+                return true;
+            case marginTopPropertyKey:
+                m_MarginTop = CoreDoubleType::deserialize(reader);
+                return true;
+            case marginBottomPropertyKey:
+                m_MarginBottom = CoreDoubleType::deserialize(reader);
+                return true;
+            case paddingLeftPropertyKey:
+                m_PaddingLeft = CoreDoubleType::deserialize(reader);
+                return true;
+            case paddingRightPropertyKey:
+                m_PaddingRight = CoreDoubleType::deserialize(reader);
+                return true;
+            case paddingTopPropertyKey:
+                m_PaddingTop = CoreDoubleType::deserialize(reader);
+                return true;
+            case paddingBottomPropertyKey:
+                m_PaddingBottom = CoreDoubleType::deserialize(reader);
+                return true;
+            case positionLeftPropertyKey:
+                m_PositionLeft = CoreDoubleType::deserialize(reader);
+                return true;
+            case positionRightPropertyKey:
+                m_PositionRight = CoreDoubleType::deserialize(reader);
+                return true;
+            case positionTopPropertyKey:
+                m_PositionTop = CoreDoubleType::deserialize(reader);
+                return true;
+            case positionBottomPropertyKey:
+                m_PositionBottom = CoreDoubleType::deserialize(reader);
+                return true;
+            case flexPropertyKey:
+                m_Flex = CoreDoubleType::deserialize(reader);
+                return true;
+            case flexGrowPropertyKey:
+                m_FlexGrow = CoreDoubleType::deserialize(reader);
+                return true;
+            case flexShrinkPropertyKey:
+                m_FlexShrink = CoreDoubleType::deserialize(reader);
+                return true;
+            case flexBasisPropertyKey:
+                m_FlexBasis = CoreDoubleType::deserialize(reader);
+                return true;
+            case aspectRatioPropertyKey:
+                m_AspectRatio = CoreDoubleType::deserialize(reader);
+                return true;
+        }
+        return Component::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void layoutFlags0Changed() {}
+    virtual void layoutFlags1Changed() {}
+    virtual void layoutFlags2Changed() {}
+    virtual void gapHorizontalChanged() {}
+    virtual void gapVerticalChanged() {}
+    virtual void maxWidthChanged() {}
+    virtual void maxHeightChanged() {}
+    virtual void minWidthChanged() {}
+    virtual void minHeightChanged() {}
+    virtual void borderLeftChanged() {}
+    virtual void borderRightChanged() {}
+    virtual void borderTopChanged() {}
+    virtual void borderBottomChanged() {}
+    virtual void marginLeftChanged() {}
+    virtual void marginRightChanged() {}
+    virtual void marginTopChanged() {}
+    virtual void marginBottomChanged() {}
+    virtual void paddingLeftChanged() {}
+    virtual void paddingRightChanged() {}
+    virtual void paddingTopChanged() {}
+    virtual void paddingBottomChanged() {}
+    virtual void positionLeftChanged() {}
+    virtual void positionRightChanged() {}
+    virtual void positionTopChanged() {}
+    virtual void positionBottomChanged() {}
+    virtual void flexChanged() {}
+    virtual void flexGrowChanged() {}
+    virtual void flexShrinkChanged() {}
+    virtual void flexBasisChanged() {}
+    virtual void aspectRatioChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/layout_component_absolute_base.hpp b/include/rive/generated/layout_component_absolute_base.hpp
new file mode 100644
index 0000000..b61486f
--- /dev/null
+++ b/include/rive/generated/layout_component_absolute_base.hpp
@@ -0,0 +1,39 @@
+#ifndef _RIVE_ABSOLUTE_LAYOUT_COMPONENT_BASE_HPP_
+#define _RIVE_ABSOLUTE_LAYOUT_COMPONENT_BASE_HPP_
+#include "rive/layout_component.hpp"
+namespace rive
+{
+class AbsoluteLayoutComponentBase : public LayoutComponent
+{
+protected:
+    typedef LayoutComponent Super;
+
+public:
+    static const uint16_t typeKey = 423;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case AbsoluteLayoutComponentBase::typeKey:
+            case LayoutComponentBase::typeKey:
+            case WorldTransformComponentBase::typeKey:
+            case ContainerComponentBase::typeKey:
+            case ComponentBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    Core* clone() const override;
+
+protected:
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/layout_component_base.hpp b/include/rive/generated/layout_component_base.hpp
new file mode 100644
index 0000000..e0c508a
--- /dev/null
+++ b/include/rive/generated/layout_component_base.hpp
@@ -0,0 +1,129 @@
+#ifndef _RIVE_LAYOUT_COMPONENT_BASE_HPP_
+#define _RIVE_LAYOUT_COMPONENT_BASE_HPP_
+#include "rive/core/field_types/core_bool_type.hpp"
+#include "rive/core/field_types/core_double_type.hpp"
+#include "rive/core/field_types/core_uint_type.hpp"
+#include "rive/world_transform_component.hpp"
+namespace rive
+{
+class LayoutComponentBase : public WorldTransformComponent
+{
+protected:
+    typedef WorldTransformComponent Super;
+
+public:
+    static const uint16_t typeKey = 409;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case LayoutComponentBase::typeKey:
+            case WorldTransformComponentBase::typeKey:
+            case ContainerComponentBase::typeKey:
+            case ComponentBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t clipPropertyKey = 196;
+    static const uint16_t widthPropertyKey = 7;
+    static const uint16_t heightPropertyKey = 8;
+    static const uint16_t styleIdPropertyKey = 494;
+
+private:
+    bool m_Clip = true;
+    float m_Width = 0.0f;
+    float m_Height = 0.0f;
+    uint32_t m_StyleId = -1;
+
+public:
+    inline bool clip() const { return m_Clip; }
+    void clip(bool value)
+    {
+        if (m_Clip == value)
+        {
+            return;
+        }
+        m_Clip = value;
+        clipChanged();
+    }
+
+    inline float width() const { return m_Width; }
+    void width(float value)
+    {
+        if (m_Width == value)
+        {
+            return;
+        }
+        m_Width = value;
+        widthChanged();
+    }
+
+    inline float height() const { return m_Height; }
+    void height(float value)
+    {
+        if (m_Height == value)
+        {
+            return;
+        }
+        m_Height = value;
+        heightChanged();
+    }
+
+    inline uint32_t styleId() const { return m_StyleId; }
+    void styleId(uint32_t value)
+    {
+        if (m_StyleId == value)
+        {
+            return;
+        }
+        m_StyleId = value;
+        styleIdChanged();
+    }
+
+    Core* clone() const override;
+    void copy(const LayoutComponentBase& object)
+    {
+        m_Clip = object.m_Clip;
+        m_Width = object.m_Width;
+        m_Height = object.m_Height;
+        m_StyleId = object.m_StyleId;
+        WorldTransformComponent::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case clipPropertyKey:
+                m_Clip = CoreBoolType::deserialize(reader);
+                return true;
+            case widthPropertyKey:
+                m_Width = CoreDoubleType::deserialize(reader);
+                return true;
+            case heightPropertyKey:
+                m_Height = CoreDoubleType::deserialize(reader);
+                return true;
+            case styleIdPropertyKey:
+                m_StyleId = CoreUintType::deserialize(reader);
+                return true;
+        }
+        return WorldTransformComponent::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void clipChanged() {}
+    virtual void widthChanged() {}
+    virtual void heightChanged() {}
+    virtual void styleIdChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/nested_artboard_base.hpp b/include/rive/generated/nested_artboard_base.hpp
index ce5d874..141aa60 100644
--- a/include/rive/generated/nested_artboard_base.hpp
+++ b/include/rive/generated/nested_artboard_base.hpp
@@ -34,9 +34,13 @@
     uint16_t coreType() const override { return typeKey; }
 
     static const uint16_t artboardIdPropertyKey = 197;
+    static const uint16_t fitPropertyKey = 538;
+    static const uint16_t alignmentPropertyKey = 539;
 
 private:
     uint32_t m_ArtboardId = -1;
+    uint32_t m_Fit = 0;
+    uint32_t m_Alignment = 0;
 
 public:
     inline uint32_t artboardId() const { return m_ArtboardId; }
@@ -50,10 +54,34 @@
         artboardIdChanged();
     }
 
+    inline uint32_t fit() const { return m_Fit; }
+    void fit(uint32_t value)
+    {
+        if (m_Fit == value)
+        {
+            return;
+        }
+        m_Fit = value;
+        fitChanged();
+    }
+
+    inline uint32_t alignment() const { return m_Alignment; }
+    void alignment(uint32_t value)
+    {
+        if (m_Alignment == value)
+        {
+            return;
+        }
+        m_Alignment = value;
+        alignmentChanged();
+    }
+
     Core* clone() const override;
     void copy(const NestedArtboardBase& object)
     {
         m_ArtboardId = object.m_ArtboardId;
+        m_Fit = object.m_Fit;
+        m_Alignment = object.m_Alignment;
         Drawable::copy(object);
     }
 
@@ -64,12 +92,20 @@
             case artboardIdPropertyKey:
                 m_ArtboardId = CoreUintType::deserialize(reader);
                 return true;
+            case fitPropertyKey:
+                m_Fit = CoreUintType::deserialize(reader);
+                return true;
+            case alignmentPropertyKey:
+                m_Alignment = CoreUintType::deserialize(reader);
+                return true;
         }
         return Drawable::deserialize(propertyKey, reader);
     }
 
 protected:
     virtual void artboardIdChanged() {}
+    virtual void fitChanged() {}
+    virtual void alignmentChanged() {}
 };
 } // namespace rive
 
diff --git a/include/rive/layout/layout_component_style.hpp b/include/rive/layout/layout_component_style.hpp
new file mode 100644
index 0000000..277373f
--- /dev/null
+++ b/include/rive/layout/layout_component_style.hpp
@@ -0,0 +1,134 @@
+#ifndef _RIVE_LAYOUT_COMPONENT_STYLE_HPP_
+#define _RIVE_LAYOUT_COMPONENT_STYLE_HPP_
+#include "rive/generated/layout/layout_component_style_base.hpp"
+#include "rive/math/bit_field_loc.hpp"
+#ifdef WITH_RIVE_LAYOUT
+#include "yoga/Yoga.h"
+#endif
+#include <stdio.h>
+namespace rive
+{
+// ---- Flags 0
+static BitFieldLoc DisplayBits = BitFieldLoc(0, 0);
+static BitFieldLoc PositionTypeBits = BitFieldLoc(1, 2);
+static BitFieldLoc FlexDirectionBits = BitFieldLoc(3, 4);
+static BitFieldLoc DirectionBits = BitFieldLoc(5, 6);
+static BitFieldLoc AlignContentBits = BitFieldLoc(7, 9);
+static BitFieldLoc AlignItemsBits = BitFieldLoc(10, 12);
+static BitFieldLoc AlignSelfBits = BitFieldLoc(13, 15);
+static BitFieldLoc JustifyContentBits = BitFieldLoc(16, 18);
+static BitFieldLoc FlexWrapBits = BitFieldLoc(19, 20);
+static BitFieldLoc OverflowBits = BitFieldLoc(21, 22);
+static BitFieldLoc IntrinsicallySizedBits = BitFieldLoc(23, 23);
+static BitFieldLoc WidthUnitsBits = BitFieldLoc(24, 25);
+static BitFieldLoc HeightUnitsBits = BitFieldLoc(26, 27);
+
+// ---- Flags 1
+static BitFieldLoc BorderLeftUnitsBits = BitFieldLoc(0, 1);
+static BitFieldLoc BorderRightUnitsBits = BitFieldLoc(2, 3);
+static BitFieldLoc BorderTopUnitsBits = BitFieldLoc(4, 5);
+static BitFieldLoc BorderBottomUnitsBits = BitFieldLoc(6, 7);
+static BitFieldLoc MarginLeftUnitsBits = BitFieldLoc(8, 9);
+static BitFieldLoc MarginRightUnitsBits = BitFieldLoc(10, 11);
+static BitFieldLoc MarginTopUnitsBits = BitFieldLoc(12, 13);
+static BitFieldLoc MarginBottomUnitsBits = BitFieldLoc(14, 15);
+static BitFieldLoc PaddingLeftUnitsBits = BitFieldLoc(16, 17);
+static BitFieldLoc PaddingRightUnitsBits = BitFieldLoc(18, 19);
+static BitFieldLoc PaddingTopUnitsBits = BitFieldLoc(20, 21);
+static BitFieldLoc PaddingBottomUnitsBits = BitFieldLoc(22, 23);
+static BitFieldLoc PositionLeftUnitsBits = BitFieldLoc(24, 25);
+static BitFieldLoc PositionRightUnitsBits = BitFieldLoc(26, 27);
+static BitFieldLoc PositionTopUnitsBits = BitFieldLoc(28, 29);
+static BitFieldLoc PositionBottomUnitsBits = BitFieldLoc(30, 31);
+
+// ---- Flags 2
+static BitFieldLoc GapHorizontalUnitsBits = BitFieldLoc(0, 1);
+static BitFieldLoc GapVerticalUnitsBits = BitFieldLoc(2, 3);
+static BitFieldLoc MinWidthUnitsBits = BitFieldLoc(4, 5);
+static BitFieldLoc MinHeightUnitsBits = BitFieldLoc(6, 7);
+static BitFieldLoc MaxWidthUnitsBits = BitFieldLoc(8, 9);
+static BitFieldLoc MaxHeightUnitsBits = BitFieldLoc(10, 11);
+
+class LayoutComponentStyle : public LayoutComponentStyleBase
+{
+public:
+    LayoutComponentStyle() {}
+
+#ifdef WITH_RIVE_LAYOUT
+    YGDisplay display();
+    YGPositionType positionType();
+
+    YGFlexDirection flexDirection();
+    YGDirection direction();
+    YGWrap flexWrap();
+    YGOverflow overflow();
+
+    YGAlign alignItems();
+    YGAlign alignSelf();
+    YGAlign alignContent();
+    YGJustify justifyContent();
+    bool intrinsicallySized();
+    YGUnit widthUnits();
+    YGUnit heightUnits();
+
+    YGUnit borderLeftUnits();
+    YGUnit borderRightUnits();
+    YGUnit borderTopUnits();
+    YGUnit borderBottomUnits();
+    YGUnit marginLeftUnits();
+    YGUnit marginRightUnits();
+    YGUnit marginTopUnits();
+    YGUnit marginBottomUnits();
+    YGUnit paddingLeftUnits();
+    YGUnit paddingRightUnits();
+    YGUnit paddingTopUnits();
+    YGUnit paddingBottomUnits();
+    YGUnit positionLeftUnits();
+    YGUnit positionRightUnits();
+    YGUnit positionTopUnits();
+    YGUnit positionBottomUnits();
+
+    YGUnit gapHorizontalUnits();
+    YGUnit gapVerticalUnits();
+    YGUnit maxWidthUnits();
+    YGUnit maxHeightUnits();
+    YGUnit minWidthUnits();
+    YGUnit minHeightUnits();
+#endif
+
+    void markLayoutNodeDirty();
+
+    void layoutFlags0Changed() override;
+    void layoutFlags1Changed() override;
+    void layoutFlags2Changed() override;
+    void flexChanged() override;
+    void flexGrowChanged() override;
+    void flexShrinkChanged() override;
+    void flexBasisChanged() override;
+    void aspectRatioChanged() override;
+    void gapHorizontalChanged() override;
+    void gapVerticalChanged() override;
+    void maxWidthChanged() override;
+    void maxHeightChanged() override;
+    void minWidthChanged() override;
+    void minHeightChanged() override;
+    void borderLeftChanged() override;
+    void borderRightChanged() override;
+    void borderTopChanged() override;
+    void borderBottomChanged() override;
+    void marginLeftChanged() override;
+    void marginRightChanged() override;
+    void marginTopChanged() override;
+    void marginBottomChanged() override;
+    void paddingLeftChanged() override;
+    void paddingRightChanged() override;
+    void paddingTopChanged() override;
+    void paddingBottomChanged() override;
+    void positionLeftChanged() override;
+    void positionRightChanged() override;
+    void positionTopChanged() override;
+    void positionBottomChanged() override;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/layout_component.hpp b/include/rive/layout_component.hpp
new file mode 100644
index 0000000..e7c5d55
--- /dev/null
+++ b/include/rive/layout_component.hpp
@@ -0,0 +1,94 @@
+#ifndef _RIVE_LAYOUT_COMPONENT_HPP_
+#define _RIVE_LAYOUT_COMPONENT_HPP_
+#include "rive/generated/layout_component_base.hpp"
+#include "rive/layout/layout_component_style.hpp"
+#ifdef WITH_RIVE_LAYOUT
+#include "yoga/YGNode.h"
+#include "yoga/YGStyle.h"
+#include "yoga/Yoga.h"
+#endif
+#include <stdio.h>
+namespace rive
+{
+#ifndef WITH_RIVE_LAYOUT
+class YGNodeRef
+{
+public:
+    YGNodeRef() {}
+};
+class YGStyle
+{
+public:
+    YGStyle() {}
+};
+#endif
+class LayoutComponent : public LayoutComponentBase
+{
+private:
+    LayoutComponentStyle* m_style = nullptr;
+    YGNodeRef m_layoutNode;
+    YGStyle* m_layoutStyle;
+    float m_layoutSizeWidth = 0;
+    float m_layoutSizeHeight = 0;
+    float m_layoutLocationX = 0;
+    float m_layoutLocationY = 0;
+
+#ifdef WITH_RIVE_LAYOUT
+private:
+    void syncLayoutChildren();
+    void propagateSizeToChildren(ContainerComponent* component);
+    AABB findMaxIntrinsicSize(ContainerComponent* component, AABB maxIntrinsicSize);
+
+protected:
+    void calculateLayout();
+#endif
+
+public:
+    LayoutComponentStyle* style() { return m_style; }
+    void style(LayoutComponentStyle* style) { m_style = style; }
+
+#ifdef WITH_RIVE_LAYOUT
+    YGNodeRef layoutNode() { return m_layoutNode; }
+
+    YGStyle* layoutStyle() { return m_layoutStyle; }
+
+    LayoutComponent()
+    {
+        m_layoutNode = new YGNode();
+        m_layoutStyle = new YGStyle();
+    }
+
+    ~LayoutComponent()
+    {
+        YGNodeFreeRecursive(m_layoutNode);
+        delete m_layoutStyle;
+    }
+    void syncStyle();
+    void propagateSize();
+    void updateLayoutBounds();
+    void update(ComponentDirt value) override;
+    StatusCode onAddedDirty(CoreContext* context) override;
+
+#else
+    LayoutComponent()
+    {
+        m_layoutNode = YGNodeRef();
+        auto s = new YGStyle();
+        m_layoutStyle = s;
+        m_layoutSizeWidth = 0;
+        m_layoutSizeHeight = 0;
+        m_layoutLocationX = 0;
+        m_layoutLocationY = 0;
+    }
+#endif
+    void buildDependencies() override;
+
+    void markLayoutNodeDirty();
+    void clipChanged() override;
+    void widthChanged() override;
+    void heightChanged() override;
+    void styleIdChanged() override;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/layout_component_absolute.hpp b/include/rive/layout_component_absolute.hpp
new file mode 100644
index 0000000..955f64c
--- /dev/null
+++ b/include/rive/layout_component_absolute.hpp
@@ -0,0 +1,13 @@
+#ifndef _RIVE_ABSOLUTE_LAYOUT_COMPONENT_HPP_
+#define _RIVE_ABSOLUTE_LAYOUT_COMPONENT_HPP_
+#include "rive/generated/layout_component_absolute_base.hpp"
+#include <stdio.h>
+namespace rive
+{
+class AbsoluteLayoutComponent : public AbsoluteLayoutComponentBase
+{
+public:
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/math/bit_field_loc.hpp b/include/rive/math/bit_field_loc.hpp
new file mode 100644
index 0000000..7c0c983
--- /dev/null
+++ b/include/rive/math/bit_field_loc.hpp
@@ -0,0 +1,28 @@
+#ifndef _RIVE_BIT_FIELD_LOC_HPP_
+#define _RIVE_BIT_FIELD_LOC_HPP_
+
+#include <cmath>
+#include <stdio.h>
+#include <cstdint>
+#include <tuple>
+#include <vector>
+
+namespace rive
+{
+
+class BitFieldLoc
+{
+public:
+    BitFieldLoc(uint32_t start, uint32_t end);
+
+    uint32_t read(uint32_t bits);
+    uint32_t write(uint32_t bits, uint32_t value);
+
+private:
+    uint32_t m_start;
+    uint32_t m_count;
+    uint32_t m_mask;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/nested_artboard.hpp b/include/rive/nested_artboard.hpp
index 14bd7c1..3eac7dc 100644
--- a/include/rive/nested_artboard.hpp
+++ b/include/rive/nested_artboard.hpp
@@ -8,6 +8,31 @@
 
 namespace rive
 {
+
+enum class NestedArtboardFitType : uint8_t
+{
+    fill, // Default value - scales to fill available view without maintaining aspect ratio
+    contain,
+    cover,
+    fitWidth,
+    fitHeight,
+    resizeArtboard,
+    none,
+};
+
+enum class NestedArtboardAlignmentType : uint8_t
+{
+    center, // Default value
+    topLeft,
+    topCenter,
+    topRight,
+    centerLeft,
+    centerRight,
+    bottomLeft,
+    bottomCenter,
+    bottomRight,
+};
+
 class ArtboardInstance;
 class NestedAnimation;
 class NestedInput;
@@ -20,6 +45,8 @@
     Artboard* m_Artboard = nullptr;               // might point to m_Instance, and might not
     std::unique_ptr<ArtboardInstance> m_Instance; // may be null
     std::vector<NestedAnimation*> m_NestedAnimations;
+    float m_layoutScaleX = NAN;
+    float m_layoutScaleY = NAN;
 
 public:
     NestedArtboard();
@@ -45,6 +72,17 @@
     NestedInput* input(std::string name) const;
     NestedInput* input(std::string name, std::string stateMachineName) const;
 
+    NestedArtboardAlignmentType alignmentType() const
+    {
+        return (NestedArtboardAlignmentType)alignment();
+    }
+    NestedArtboardFitType fitType() const { return (NestedArtboardFitType)fit(); }
+    float effectiveScaleX() { return std::isnan(m_layoutScaleX) ? scaleX() : m_layoutScaleX; }
+    float effectiveScaleY() { return std::isnan(m_layoutScaleY) ? scaleY() : m_layoutScaleY; }
+
+    AABB computeIntrinsicSize(AABB min, AABB max) override;
+    void controlSize(AABB size) override;
+
     /// Convert a world space (relative to the artboard that this
     /// NestedArtboard is a child of) to the local space of the Artboard
     /// nested within. Returns true when the conversion succeeds, and false
diff --git a/include/rive/shapes/image.hpp b/include/rive/shapes/image.hpp
index 554c25b..60a0c72 100644
--- a/include/rive/shapes/image.hpp
+++ b/include/rive/shapes/image.hpp
@@ -24,6 +24,8 @@
     void setAsset(FileAsset*) override;
     uint32_t assetId() override;
     Core* clone() const override;
+    AABB computeIntrinsicSize(AABB min, AABB max) override;
+    void controlSize(AABB size) override;
 };
 } // namespace rive
 
diff --git a/include/rive/shapes/parametric_path.hpp b/include/rive/shapes/parametric_path.hpp
index 03f872a..7bb3204 100644
--- a/include/rive/shapes/parametric_path.hpp
+++ b/include/rive/shapes/parametric_path.hpp
@@ -1,10 +1,15 @@
 #ifndef _RIVE_PARAMETRIC_PATH_HPP_
 #define _RIVE_PARAMETRIC_PATH_HPP_
+#include "rive/math/aabb.hpp"
 #include "rive/generated/shapes/parametric_path_base.hpp"
 namespace rive
 {
 class ParametricPath : public ParametricPathBase
 {
+public:
+    AABB computeIntrinsicSize(AABB min, AABB max) override;
+    void controlSize(AABB size) override;
+
 protected:
     void widthChanged() override;
     void heightChanged() override;
diff --git a/include/rive/text/text.hpp b/include/rive/text/text.hpp
index bed19f2..073af1d 100644
--- a/include/rive/text/text.hpp
+++ b/include/rive/text/text.hpp
@@ -1,6 +1,7 @@
 #ifndef _RIVE_TEXT_CORE_HPP_
 #define _RIVE_TEXT_CORE_HPP_
 #include "rive/generated/text/text_base.hpp"
+#include "rive/math/aabb.hpp"
 #include "rive/text/text_value_run.hpp"
 #include "rive/text_engine.hpp"
 #include "rive/simple_array.hpp"
@@ -170,12 +171,16 @@
     Core* hitTest(HitInfo*, const Mat2D&) override;
     void addRun(TextValueRun* run);
     void addModifierGroup(TextModifierGroup* group);
-    void markShapeDirty();
+    void markShapeDirty(bool sendToLayout = true);
     void modifierShapeDirty();
     void markPaintDirty();
     void update(ComponentDirt value) override;
 
     TextSizing sizing() const { return (TextSizing)sizingValue(); }
+    TextSizing effectiveSizing() const
+    {
+        return std::isnan(m_layoutHeight) ? sizing() : TextSizing::fixed;
+    }
     TextOverflow overflow() const { return (TextOverflow)overflowValue(); }
     TextOrigin textOrigin() const { return (TextOrigin)originValue(); }
     void overflow(TextOverflow value) { return overflowValue((uint32_t)value); }
@@ -185,6 +190,11 @@
     AABB localBounds() const override;
     void originXChanged() override;
     void originYChanged() override;
+
+    AABB computeIntrinsicSize(AABB min, AABB max) override;
+    void controlSize(AABB size) override;
+    float effectiveWidth() { return std::isnan(m_layoutWidth) ? width() : m_layoutWidth; }
+    float effectiveHeight() { return std::isnan(m_layoutHeight) ? height() : m_layoutHeight; }
 #ifdef WITH_RIVE_TEXT
     const std::vector<TextValueRun*>& runs() const { return m_runs; }
 #endif
@@ -235,6 +245,9 @@
 
     GlyphLookup m_glyphLookup;
 #endif
+    float m_layoutWidth = NAN;
+    float m_layoutHeight = NAN;
+    AABB measure(AABB maxSize);
 };
 } // namespace rive
 
diff --git a/include/rive/transform_component.hpp b/include/rive/transform_component.hpp
index 8b13bce..6b63a11 100644
--- a/include/rive/transform_component.hpp
+++ b/include/rive/transform_component.hpp
@@ -1,6 +1,7 @@
 #ifndef _RIVE_TRANSFORM_COMPONENT_HPP_
 #define _RIVE_TRANSFORM_COMPONENT_HPP_
 #include "rive/generated/transform_component_base.hpp"
+#include "rive/math/aabb.hpp"
 #include "rive/math/mat2d.hpp"
 
 namespace rive
@@ -47,6 +48,9 @@
     void addConstraint(Constraint* constraint);
     virtual AABB localBounds() const;
     void markDirtyIfConstrained();
+
+    virtual AABB computeIntrinsicSize(AABB min, AABB max) { return AABB(); }
+    virtual void controlSize(AABB size) {}
 };
 } // namespace rive
 
diff --git a/premake5_v2.lua b/premake5_v2.lua
index 5311fb7..44244a2 100644
--- a/premake5_v2.lua
+++ b/premake5_v2.lua
@@ -22,12 +22,17 @@
         'MA_NO_RESOURCE_MANAGER',
     })
 end
+filter({ 'options:with_rive_layout' })
+do
+    defines({ 'WITH_RIVE_LAYOUT' })
+end
 filter({})
 
 dependencies = path.getabsolute('dependencies/')
 dofile(path.join(dependencies, 'premake5_harfbuzz_v2.lua'))
 dofile(path.join(dependencies, 'premake5_sheenbidi_v2.lua'))
 dofile(path.join(dependencies, 'premake5_miniaudio_v2.lua'))
+dofile(path.join(dependencies, 'premake5_yoga_v2.lua'))
 
 project('rive')
 do
@@ -38,8 +43,11 @@
         harfbuzz .. '/src',
         sheenbidi .. '/Headers',
         miniaudio,
+        yoga,
     })
 
+    defines({ 'YOGA_EXPORT=' })
+
     files({ 'src/**.cpp' })
 
     flags({ 'FatalCompileWarnings' })
@@ -157,3 +165,8 @@
     description = 'The audio mode to use.',
     allowed = { { 'disabled' }, { 'system' }, { 'external' } },
 })
+
+newoption({
+    trigger = 'with_rive_layout',
+    description = 'Compiles in layout features.',
+})
\ No newline at end of file
diff --git a/src/artboard.cpp b/src/artboard.cpp
index 6fb08e7..9a9b741 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -509,6 +509,26 @@
 bool Artboard::advance(double elapsedSeconds)
 {
     m_HasChangedDrawOrderInLastUpdate = false;
+#ifdef WITH_RIVE_LAYOUT
+    if (!m_dirtyLayout.empty())
+    {
+        syncStyle();
+        for (auto layout : m_dirtyLayout)
+        {
+            layout->syncStyle();
+        }
+        m_dirtyLayout.clear();
+        calculateLayout();
+        for (auto dep : m_DependencyOrder)
+        {
+            if (dep->is<LayoutComponent>())
+            {
+                auto layout = dep->as<LayoutComponent>();
+                layout->updateLayoutBounds();
+            }
+        }
+    }
+#endif
     if (m_JoysticksApplyBeforeUpdate)
     {
         for (auto joystick : m_Joysticks)
diff --git a/src/generated/layout/layout_component_style_base.cpp b/src/generated/layout/layout_component_style_base.cpp
new file mode 100644
index 0000000..22c4da8
--- /dev/null
+++ b/src/generated/layout/layout_component_style_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/layout/layout_component_style_base.hpp"
+#include "rive/layout/layout_component_style.hpp"
+
+using namespace rive;
+
+Core* LayoutComponentStyleBase::clone() const
+{
+    auto cloned = new LayoutComponentStyle();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/generated/layout_component_absolute_base.cpp b/src/generated/layout_component_absolute_base.cpp
new file mode 100644
index 0000000..c35c4a8
--- /dev/null
+++ b/src/generated/layout_component_absolute_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/layout_component_absolute_base.hpp"
+#include "rive/layout_component_absolute.hpp"
+
+using namespace rive;
+
+Core* AbsoluteLayoutComponentBase::clone() const
+{
+    auto cloned = new AbsoluteLayoutComponent();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/generated/layout_component_base.cpp b/src/generated/layout_component_base.cpp
new file mode 100644
index 0000000..13e6c86
--- /dev/null
+++ b/src/generated/layout_component_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/layout_component_base.hpp"
+#include "rive/layout_component.hpp"
+
+using namespace rive;
+
+Core* LayoutComponentBase::clone() const
+{
+    auto cloned = new LayoutComponent();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/layout/layout_component_style.cpp b/src/layout/layout_component_style.cpp
new file mode 100644
index 0000000..882d752
--- /dev/null
+++ b/src/layout/layout_component_style.cpp
@@ -0,0 +1,203 @@
+#include "rive/layout_component.hpp"
+#include "rive/layout/layout_component_style.hpp"
+#include <vector>
+
+using namespace rive;
+
+#ifdef WITH_RIVE_LAYOUT
+YGDisplay LayoutComponentStyle::display() { return YGDisplay(DisplayBits.read(layoutFlags0())); }
+
+YGPositionType LayoutComponentStyle::positionType()
+{
+    return YGPositionType(PositionTypeBits.read(layoutFlags0()));
+}
+
+YGFlexDirection LayoutComponentStyle::flexDirection()
+{
+    return YGFlexDirection(FlexDirectionBits.read(layoutFlags0()));
+}
+
+YGDirection LayoutComponentStyle::direction()
+{
+    return YGDirection(DirectionBits.read(layoutFlags0()));
+}
+
+YGWrap LayoutComponentStyle::flexWrap() { return YGWrap(FlexWrapBits.read(layoutFlags0())); }
+
+YGAlign LayoutComponentStyle::alignItems() { return YGAlign(AlignItemsBits.read(layoutFlags0())); }
+
+YGAlign LayoutComponentStyle::alignSelf() { return YGAlign(AlignSelfBits.read(layoutFlags0())); }
+
+YGAlign LayoutComponentStyle::alignContent()
+{
+    return YGAlign(AlignContentBits.read(layoutFlags0()));
+}
+
+YGJustify LayoutComponentStyle::justifyContent()
+{
+    return YGJustify(JustifyContentBits.read(layoutFlags0()));
+}
+
+YGOverflow LayoutComponentStyle::overflow()
+{
+    return YGOverflow(OverflowBits.read(layoutFlags0()));
+}
+
+bool LayoutComponentStyle::intrinsicallySized()
+{
+    return IntrinsicallySizedBits.read(layoutFlags0()) == 1;
+}
+
+YGUnit LayoutComponentStyle::widthUnits() { return YGUnit(WidthUnitsBits.read(layoutFlags0())); }
+
+YGUnit LayoutComponentStyle::heightUnits() { return YGUnit(HeightUnitsBits.read(layoutFlags0())); }
+
+YGUnit LayoutComponentStyle::borderLeftUnits()
+{
+    return YGUnit(BorderLeftUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::borderRightUnits()
+{
+    return YGUnit(BorderRightUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::borderTopUnits()
+{
+    return YGUnit(BorderTopUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::borderBottomUnits()
+{
+    return YGUnit(BorderBottomUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::marginLeftUnits()
+{
+    return YGUnit(MarginLeftUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::marginRightUnits()
+{
+    return YGUnit(MarginRightUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::marginTopUnits()
+{
+    return YGUnit(MarginTopUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::marginBottomUnits()
+{
+    return YGUnit(MarginBottomUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::paddingLeftUnits()
+{
+    return YGUnit(PaddingLeftUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::paddingRightUnits()
+{
+    return YGUnit(PaddingRightUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::paddingTopUnits()
+{
+    return YGUnit(PaddingTopUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::paddingBottomUnits()
+{
+    return YGUnit(PaddingBottomUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::positionLeftUnits()
+{
+    return YGUnit(PositionLeftUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::positionRightUnits()
+{
+    return YGUnit(PositionRightUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::positionTopUnits()
+{
+    return YGUnit(PositionTopUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::positionBottomUnits()
+{
+    return YGUnit(PositionBottomUnitsBits.read(layoutFlags1()));
+}
+
+YGUnit LayoutComponentStyle::gapHorizontalUnits()
+{
+    return YGUnit(GapHorizontalUnitsBits.read(layoutFlags2()));
+}
+
+YGUnit LayoutComponentStyle::gapVerticalUnits()
+{
+    return YGUnit(GapVerticalUnitsBits.read(layoutFlags2()));
+}
+
+YGUnit LayoutComponentStyle::maxWidthUnits()
+{
+    return YGUnit(MaxWidthUnitsBits.read(layoutFlags2()));
+}
+
+YGUnit LayoutComponentStyle::maxHeightUnits()
+{
+    return YGUnit(MaxHeightUnitsBits.read(layoutFlags2()));
+}
+
+YGUnit LayoutComponentStyle::minWidthUnits()
+{
+    return YGUnit(MinWidthUnitsBits.read(layoutFlags2()));
+}
+YGUnit LayoutComponentStyle::minHeightUnits()
+{
+    return YGUnit(MinHeightUnitsBits.read(layoutFlags2()));
+}
+void LayoutComponentStyle::markLayoutNodeDirty()
+{
+    if (parent()->is<LayoutComponent>())
+    {
+        parent()->as<LayoutComponent>()->markLayoutNodeDirty();
+    }
+}
+#else
+void LayoutComponentStyle::markLayoutNodeDirty() {}
+#endif
+
+void LayoutComponentStyle::layoutFlags0Changed() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::layoutFlags1Changed() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::layoutFlags2Changed() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::flexChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::flexGrowChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::flexShrinkChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::flexBasisChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::aspectRatioChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::gapHorizontalChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::gapVerticalChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::maxWidthChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::maxHeightChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::minWidthChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::minHeightChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::borderLeftChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::borderRightChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::borderTopChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::borderBottomChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::marginLeftChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::marginRightChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::marginTopChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::marginBottomChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::paddingLeftChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::paddingRightChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::paddingTopChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::paddingBottomChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::positionLeftChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::positionRightChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::positionTopChanged() { markLayoutNodeDirty(); }
+void LayoutComponentStyle::positionBottomChanged() { markLayoutNodeDirty(); }
\ No newline at end of file
diff --git a/src/layout_component.cpp b/src/layout_component.cpp
new file mode 100644
index 0000000..ac908f6
--- /dev/null
+++ b/src/layout_component.cpp
@@ -0,0 +1,278 @@
+#include "rive/artboard.hpp"
+#include "rive/layout_component.hpp"
+#include "rive/node.hpp"
+#include "rive/math/aabb.hpp"
+#ifdef WITH_RIVE_LAYOUT
+#include "rive/transform_component.hpp"
+#include "yoga/YGEnums.h"
+#include "yoga/YGFloatOptional.h"
+#endif
+#include <vector>
+
+using namespace rive;
+
+void LayoutComponent::buildDependencies()
+{
+    Super::buildDependencies();
+    if (parent() != nullptr)
+    {
+        parent()->addDependent(this);
+    }
+}
+
+#ifdef WITH_RIVE_LAYOUT
+StatusCode LayoutComponent::onAddedDirty(CoreContext* context)
+{
+    auto code = Super::onAddedDirty(context);
+    if (code != StatusCode::Ok)
+    {
+        return code;
+    }
+
+    auto coreStyle = context->resolve(styleId());
+    if (coreStyle == nullptr || !coreStyle->is<LayoutComponentStyle>())
+    {
+        return StatusCode::MissingObject;
+    }
+    m_style = static_cast<LayoutComponentStyle*>(coreStyle);
+    addChild(m_style);
+    artboard()->markLayoutDirty(this);
+    if (parent() != nullptr && parent()->is<LayoutComponent>())
+    {
+        parent()->as<LayoutComponent>()->syncLayoutChildren();
+    }
+    return StatusCode::Ok;
+}
+
+void LayoutComponent::update(ComponentDirt value)
+{
+    if (hasDirt(value, ComponentDirt::WorldTransform))
+    {
+        Mat2D parentWorld = parent()->is<WorldTransformComponent>()
+                                ? (parent()->as<WorldTransformComponent>())->worldTransform()
+                                : Mat2D();
+        auto transform = Mat2D();
+        transform[4] = m_layoutLocationX;
+        transform[5] = m_layoutLocationY;
+
+        auto multipliedTransform = Mat2D::multiply(parentWorld, transform);
+        m_WorldTransform = multipliedTransform;
+    }
+}
+
+AABB LayoutComponent::findMaxIntrinsicSize(ContainerComponent* component, AABB maxIntrinsicSize)
+{
+    auto intrinsicSize = maxIntrinsicSize;
+    for (auto child : component->children())
+    {
+        if (child->is<LayoutComponent>())
+        {
+            continue;
+        }
+        if (child->is<TransformComponent>())
+        {
+            auto sizableChild = child->as<TransformComponent>();
+            auto minSize =
+                AABB::fromLTWH(0,
+                               0,
+                               style()->minWidthUnits() == YGUnitPoint ? style()->minWidth() : 0,
+                               style()->minHeightUnits() == YGUnitPoint ? style()->minHeight() : 0);
+            auto maxSize = AABB::fromLTWH(
+                0,
+                0,
+                style()->maxWidthUnits() == YGUnitPoint ? style()->maxWidth()
+                                                        : std::numeric_limits<float>::infinity(),
+                style()->maxHeightUnits() == YGUnitPoint ? style()->maxHeight()
+                                                         : std::numeric_limits<float>::infinity());
+            auto size = sizableChild->computeIntrinsicSize(minSize, maxSize);
+            intrinsicSize = AABB::fromLTWH(0,
+                                           0,
+                                           std::max(maxIntrinsicSize.width(), size.width()),
+                                           std::max(maxIntrinsicSize.height(), size.height()));
+        }
+        if (child->is<ContainerComponent>())
+        {
+            return findMaxIntrinsicSize(child->as<ContainerComponent>(), intrinsicSize);
+        }
+    }
+    return intrinsicSize;
+}
+
+void LayoutComponent::syncStyle()
+{
+    if (style() == nullptr || layoutStyle() == nullptr || layoutNode() == nullptr)
+    {
+        return;
+    }
+    bool setIntrinsicWidth = false;
+    bool setIntrinsicHeight = false;
+    if (style()->intrinsicallySized() &&
+        (style()->widthUnits() == YGUnitAuto || style()->heightUnits() == YGUnitAuto))
+    {
+        AABB intrinsicSize = findMaxIntrinsicSize(this, AABB());
+        bool foundIntrinsicSize = intrinsicSize.width() != 0 || intrinsicSize.height() != 0;
+
+        if (foundIntrinsicSize)
+        {
+            if (style()->widthUnits() == YGUnitAuto)
+            {
+                setIntrinsicWidth = true;
+                layoutStyle()->dimensions()[YGDimensionWidth] =
+                    YGValue{intrinsicSize.width(), YGUnitPoint};
+            }
+            if (style()->heightUnits() == YGUnitAuto)
+            {
+                setIntrinsicHeight = true;
+                layoutStyle()->dimensions()[YGDimensionHeight] =
+                    YGValue{intrinsicSize.height(), YGUnitPoint};
+            }
+        }
+    }
+    if (!setIntrinsicWidth)
+    {
+        layoutStyle()->dimensions()[YGDimensionWidth] = YGValue{width(), style()->widthUnits()};
+    }
+    if (!setIntrinsicHeight)
+    {
+        layoutStyle()->dimensions()[YGDimensionHeight] = YGValue{height(), style()->heightUnits()};
+    }
+    layoutStyle()->minDimensions()[YGDimensionWidth] =
+        YGValue{style()->minWidth(), style()->minWidthUnits()};
+    layoutStyle()->minDimensions()[YGDimensionHeight] =
+        YGValue{style()->minHeight(), style()->minHeightUnits()};
+    layoutStyle()->maxDimensions()[YGDimensionWidth] =
+        YGValue{style()->maxWidth(), style()->maxWidthUnits()};
+    layoutStyle()->maxDimensions()[YGDimensionHeight] =
+        YGValue{style()->maxHeight(), style()->maxHeightUnits()};
+
+    layoutStyle()->gap()[YGGutterColumn] =
+        YGValue{style()->gapHorizontal(), style()->gapHorizontalUnits()};
+    layoutStyle()->gap()[YGGutterRow] =
+        YGValue{style()->gapVertical(), style()->gapVerticalUnits()};
+    layoutStyle()->border()[YGEdgeLeft] =
+        YGValue{style()->borderLeft(), style()->borderLeftUnits()};
+    layoutStyle()->border()[YGEdgeRight] =
+        YGValue{style()->borderRight(), style()->borderRightUnits()};
+    layoutStyle()->border()[YGEdgeTop] = YGValue{style()->borderTop(), style()->borderTopUnits()};
+    layoutStyle()->border()[YGEdgeBottom] =
+        YGValue{style()->borderBottom(), style()->borderBottomUnits()};
+    layoutStyle()->margin()[YGEdgeLeft] =
+        YGValue{style()->marginLeft(), style()->marginLeftUnits()};
+    layoutStyle()->margin()[YGEdgeRight] =
+        YGValue{style()->marginRight(), style()->marginRightUnits()};
+    layoutStyle()->margin()[YGEdgeTop] = YGValue{style()->marginTop(), style()->marginTopUnits()};
+    layoutStyle()->margin()[YGEdgeBottom] =
+        YGValue{style()->marginBottom(), style()->marginBottomUnits()};
+    layoutStyle()->padding()[YGEdgeLeft] =
+        YGValue{style()->paddingLeft(), style()->paddingLeftUnits()};
+    layoutStyle()->padding()[YGEdgeRight] =
+        YGValue{style()->paddingRight(), style()->paddingRightUnits()};
+    layoutStyle()->padding()[YGEdgeTop] =
+        YGValue{style()->paddingTop(), style()->paddingTopUnits()};
+    layoutStyle()->padding()[YGEdgeBottom] =
+        YGValue{style()->paddingBottom(), style()->paddingBottomUnits()};
+    layoutStyle()->position()[YGEdgeLeft] =
+        YGValue{style()->positionLeft(), style()->positionLeftUnits()};
+    layoutStyle()->position()[YGEdgeRight] =
+        YGValue{style()->positionRight(), style()->positionRightUnits()};
+    layoutStyle()->position()[YGEdgeTop] =
+        YGValue{style()->positionTop(), style()->positionTopUnits()};
+    layoutStyle()->position()[YGEdgeBottom] =
+        YGValue{style()->positionBottom(), style()->positionBottomUnits()};
+
+    layoutStyle()->display() = style()->display();
+    layoutStyle()->positionType() = style()->positionType();
+    layoutStyle()->flex() = YGFloatOptional(style()->flex());
+    layoutStyle()->flexGrow() = YGFloatOptional(style()->flexGrow());
+    layoutStyle()->flexShrink() = YGFloatOptional(style()->flexShrink());
+    // layoutStyle()->flexBasis() = style()->flexBasis();
+    layoutStyle()->flexDirection() = style()->flexDirection();
+    layoutStyle()->flexWrap() = style()->flexWrap();
+    layoutStyle()->alignItems() = style()->alignItems();
+    layoutStyle()->alignContent() = style()->alignContent();
+    layoutStyle()->alignSelf() = style()->alignSelf();
+    layoutStyle()->justifyContent() = style()->justifyContent();
+
+    layoutNode()->setStyle(*layoutStyle());
+}
+
+void LayoutComponent::syncLayoutChildren()
+{
+    YGNodeRemoveAllChildren(layoutNode());
+    int index = 0;
+    for (size_t i = 0; i < children().size(); i++)
+    {
+        Component* child = children()[i];
+        if (child->is<LayoutComponent>())
+        {
+            YGNodeInsertChild(layoutNode(), child->as<LayoutComponent>()->layoutNode(), index);
+            index += 1;
+        }
+    }
+}
+
+void LayoutComponent::propagateSize()
+{
+    if (artboard() == this)
+    {
+        return;
+    }
+    propagateSizeToChildren(this);
+}
+
+void LayoutComponent::propagateSizeToChildren(ContainerComponent* component)
+{
+    for (auto child : component->children())
+    {
+        if (child->is<LayoutComponent>() || child->coreType() == NodeBase::typeKey)
+        {
+            continue;
+        }
+        if (child->is<TransformComponent>())
+        {
+            auto sizableChild = child->as<TransformComponent>();
+            sizableChild->controlSize(AABB::fromLTWH(0, 0, m_layoutSizeWidth, m_layoutSizeHeight));
+        }
+        if (child->is<ContainerComponent>())
+        {
+            propagateSizeToChildren(child->as<ContainerComponent>());
+        }
+    }
+}
+
+void LayoutComponent::calculateLayout()
+{
+    YGNodeCalculateLayout(layoutNode(), width(), height(), YGDirection::YGDirectionInherit);
+}
+
+void LayoutComponent::updateLayoutBounds()
+{
+    auto left = YGNodeLayoutGetLeft(layoutNode());
+    auto top = YGNodeLayoutGetTop(layoutNode());
+    auto width = YGNodeLayoutGetWidth(layoutNode());
+    auto height = YGNodeLayoutGetHeight(layoutNode());
+    if (left != m_layoutLocationX || top != m_layoutLocationY || width != m_layoutSizeWidth ||
+        height != m_layoutSizeHeight)
+    {
+        m_layoutLocationX = left;
+        m_layoutLocationY = top;
+        m_layoutSizeWidth = width;
+        m_layoutSizeHeight = height;
+        propagateSize();
+        markWorldTransformDirty();
+    }
+}
+
+void LayoutComponent::markLayoutNodeDirty()
+{
+    layoutNode()->markDirtyAndPropagate();
+    artboard()->markLayoutDirty(this);
+}
+#else
+void LayoutComponent::markLayoutNodeDirty() {}
+#endif
+
+void LayoutComponent::clipChanged() { markLayoutNodeDirty(); }
+void LayoutComponent::widthChanged() { markLayoutNodeDirty(); }
+void LayoutComponent::heightChanged() { markLayoutNodeDirty(); }
+void LayoutComponent::styleIdChanged() { markLayoutNodeDirty(); }
\ No newline at end of file
diff --git a/src/math/bit_field_loc.cpp b/src/math/bit_field_loc.cpp
new file mode 100644
index 0000000..377b10a
--- /dev/null
+++ b/src/math/bit_field_loc.cpp
@@ -0,0 +1,20 @@
+#include "rive/math/bit_field_loc.hpp"
+#include <cassert>
+
+using namespace rive;
+
+BitFieldLoc::BitFieldLoc(uint32_t start, uint32_t end) : m_start(start)
+{
+    assert(end >= start);
+    assert(end < 32);
+
+    m_count = end - start + 1;
+    m_mask = ((1 << (end - start + 1)) - 1) << start;
+}
+
+uint32_t BitFieldLoc::read(uint32_t bits) { return (bits & m_mask) >> m_start; }
+
+uint32_t BitFieldLoc::write(uint32_t bits, uint32_t value)
+{
+    return (bits & ~m_mask) | ((value << m_start) & m_mask);
+}
\ No newline at end of file
diff --git a/src/nested_artboard.cpp b/src/nested_artboard.cpp
index 1c58040..7bf7df2 100644
--- a/src/nested_artboard.cpp
+++ b/src/nested_artboard.cpp
@@ -230,4 +230,19 @@
     *local = toMountedArtboard * world;
 
     return true;
+}
+
+AABB NestedArtboard::computeIntrinsicSize(AABB min, AABB max) { return max; }
+
+void NestedArtboard::controlSize(AABB size)
+{
+    auto newScaleX = size.width() / m_Artboard->originalWidth();
+    auto newScaleY = size.height() / m_Artboard->originalHeight();
+    if (newScaleX != scaleX() || newScaleY != scaleY())
+    {
+        // TODO: Support nested artboard fit & alignment
+        scaleX(newScaleX);
+        scaleY(newScaleY);
+        addDirt(ComponentDirt::WorldTransform, false);
+    }
 }
\ No newline at end of file
diff --git a/src/shapes/image.cpp b/src/shapes/image.cpp
index 021f84b..c2f21cf 100644
--- a/src/shapes/image.cpp
+++ b/src/shapes/image.cpp
@@ -121,3 +121,18 @@
 
 void Image::setMesh(Mesh* mesh) { m_Mesh = mesh; }
 Mesh* Image::mesh() const { return m_Mesh; }
+
+AABB Image::computeIntrinsicSize(AABB min, AABB max) { return max; }
+
+void Image::controlSize(AABB size)
+{
+    auto renderImage = imageAsset()->renderImage();
+    auto newScaleX = size.width() / renderImage->width();
+    auto newScaleY = size.height() / renderImage->height();
+    if (newScaleX != scaleX() || newScaleY != scaleY())
+    {
+        scaleX(newScaleX);
+        scaleY(newScaleY);
+        addDirt(ComponentDirt::WorldTransform, false);
+    }
+}
\ No newline at end of file
diff --git a/src/shapes/parametric_path.cpp b/src/shapes/parametric_path.cpp
index 923d1d7..534dfc4 100644
--- a/src/shapes/parametric_path.cpp
+++ b/src/shapes/parametric_path.cpp
@@ -1,7 +1,20 @@
+#include "rive/math/aabb.hpp"
 #include "rive/shapes/parametric_path.hpp"
 
 using namespace rive;
 
+AABB ParametricPath::computeIntrinsicSize(AABB min, AABB max)
+{
+    return AABB::fromLTWH(0, 0, width(), height());
+}
+
+void ParametricPath::controlSize(AABB size)
+{
+    width(size.width());
+    height(size.height());
+    markWorldTransformDirty();
+}
+
 void ParametricPath::widthChanged() { markPathDirty(); }
 void ParametricPath::heightChanged() { markPathDirty(); }
 void ParametricPath::originXChanged() { markPathDirty(); }
diff --git a/src/text/text.cpp b/src/text/text.cpp
index 56bbbfd..8431140 100644
--- a/src/text/text.cpp
+++ b/src/text/text.cpp
@@ -241,6 +241,18 @@
     return true;
 }
 
+AABB Text::computeIntrinsicSize(AABB min, AABB max) { return measure(max); }
+
+void Text::controlSize(AABB size)
+{
+    if (m_layoutWidth != size.width() || m_layoutHeight != size.height())
+    {
+        m_layoutWidth = size.width();
+        m_layoutHeight = size.height();
+        markShapeDirty(false);
+    }
+}
+
 void Text::buildRenderStyles()
 {
     for (TextStyle* style : m_renderStyles)
@@ -270,7 +282,8 @@
     bool isEllipsisLineLast = false;
     // Find the line to put the ellipsis on (line before the one that
     // overflows).
-    bool wantEllipsis = overflow() == TextOverflow::ellipsis && sizing() == TextSizing::fixed;
+    bool wantEllipsis =
+        overflow() == TextOverflow::ellipsis && effectiveSizing() == TextSizing::fixed;
 
     int lastLineIndex = -1;
     for (const SimpleArray<GlyphLine>& paragraphLines : m_lines)
@@ -287,7 +300,7 @@
                 maxWidth = width;
             }
             lastLineIndex++;
-            if (wantEllipsis && y + line.bottom <= height())
+            if (wantEllipsis && y + line.bottom <= effectiveHeight())
             {
                 ellipsisLine++;
             }
@@ -308,16 +321,16 @@
 
     int lineIndex = 0;
     paragraphIndex = 0;
-    switch (sizing())
+    switch (effectiveSizing())
     {
         case TextSizing::autoWidth:
             m_bounds = AABB(0.0f, minY, maxWidth, std::max(minY, y - paragraphSpace));
             break;
         case TextSizing::autoHeight:
-            m_bounds = AABB(0.0f, minY, width(), std::max(minY, y - paragraphSpace));
+            m_bounds = AABB(0.0f, minY, effectiveWidth(), std::max(minY, y - paragraphSpace));
             break;
         case TextSizing::fixed:
-            m_bounds = AABB(0.0f, minY, width(), minY + height());
+            m_bounds = AABB(0.0f, minY, effectiveWidth(), minY + effectiveHeight());
             break;
     }
 
@@ -366,13 +379,14 @@
             switch (overflow())
             {
                 case TextOverflow::hidden:
-                    if (sizing() == TextSizing::fixed && y + line.bottom > height())
+                    if (effectiveSizing() == TextSizing::fixed &&
+                        y + line.bottom > effectiveHeight())
                     {
                         return;
                     }
                     break;
                 case TextOverflow::clipped:
-                    if (sizing() == TextSizing::fixed && y + line.top > height())
+                    if (effectiveSizing() == TextSizing::fixed && y + line.top > effectiveHeight())
                     {
                         return;
                     }
@@ -386,7 +400,7 @@
                 // We need to still compute this line's ordered runs.
                 m_orderedLines.emplace_back(OrderedLine(paragraph,
                                                         line,
-                                                        width(),
+                                                        effectiveWidth(),
                                                         ellipsisLine == lineIndex,
                                                         isEllipsisLineLast,
                                                         &m_ellipsisRun));
@@ -512,7 +526,7 @@
 
 void Text::addModifierGroup(TextModifierGroup* group) { m_modifierGroups.push_back(group); }
 
-void Text::markShapeDirty()
+void Text::markShapeDirty(bool sendToLayout)
 {
     addDirt(ComponentDirt::Path);
     for (TextModifierGroup* group : m_modifierGroups)
@@ -520,6 +534,18 @@
         group->clearRangeMaps();
     }
     markWorldTransformDirty();
+#ifdef WITH_RIVE_LAYOUT
+    if (sendToLayout)
+    {
+        for (ContainerComponent* p = parent(); p != nullptr; p = p->parent())
+        {
+            if (p->is<LayoutComponent>())
+            {
+                p->as<LayoutComponent>()->markLayoutNodeDirty();
+            }
+        }
+    }
+#endif
 }
 
 void Text::modifierShapeDirty() { addDirt(ComponentDirt::Path); }
@@ -532,7 +558,7 @@
 
 void Text::overflowValueChanged()
 {
-    if (sizing() != TextSizing::autoWidth)
+    if (effectiveSizing() != TextSizing::autoWidth)
     {
         markShapeDirty();
     }
@@ -540,7 +566,7 @@
 
 void Text::widthChanged()
 {
-    if (sizing() != TextSizing::autoWidth)
+    if (effectiveSizing() != TextSizing::autoWidth)
     {
         markShapeDirty();
     }
@@ -550,7 +576,7 @@
 
 void Text::heightChanged()
 {
-    if (sizing() == TextSizing::fixed)
+    if (effectiveSizing() == TextSizing::fixed)
     {
         markShapeDirty();
     }
@@ -670,9 +696,10 @@
             makeStyled(m_modifierStyledText, false);
             auto runs = m_modifierStyledText.runs();
             m_modifierShape = runs[0].font->shapeText(m_modifierStyledText.unichars(), runs);
-            m_modifierLines = breakLines(m_modifierShape,
-                                         sizing() == TextSizing::autoWidth ? -1.0f : width(),
-                                         (TextAlign)alignValue());
+            m_modifierLines =
+                breakLines(m_modifierShape,
+                           effectiveSizing() == TextSizing::autoWidth ? -1.0f : effectiveWidth(),
+                           (TextAlign)alignValue());
             m_glyphLookup.compute(m_modifierStyledText.unichars(), m_modifierShape);
             uint32_t textSize = (uint32_t)m_modifierStyledText.unichars().size();
             for (TextModifierGroup* group : m_modifierGroups)
@@ -688,9 +715,10 @@
         {
             auto runs = m_styledText.runs();
             m_shape = runs[0].font->shapeText(m_styledText.unichars(), runs);
-            m_lines = breakLines(m_shape,
-                                 sizing() == TextSizing::autoWidth ? -1.0f : width(),
-                                 (TextAlign)alignValue());
+            m_lines =
+                breakLines(m_shape,
+                           effectiveSizing() == TextSizing::autoWidth ? -1.0f : effectiveWidth(),
+                           (TextAlign)alignValue());
             if (!precomputeModifierCoverage && haveModifiers())
             {
                 m_glyphLookup.compute(m_styledText.unichars(), m_shape);
@@ -732,6 +760,64 @@
     }
 }
 
+AABB Text::measure(AABB maxSize)
+{
+    if (makeStyled(m_styledText))
+    {
+        const float paragraphSpace = paragraphSpacing();
+        auto runs = m_styledText.runs();
+        auto shape = runs[0].font->shapeText(m_styledText.unichars(), runs);
+        auto lines =
+            breakLines(shape,
+                       effectiveSizing() == TextSizing::autoWidth ? -1.0f : effectiveWidth(),
+                       (TextAlign)alignValue());
+        float y = 0;
+        float minY = 0;
+        int paragraphIndex = 0;
+        float maxWidth = 0;
+
+        if (textOrigin() == TextOrigin::baseline && !lines.empty() && !lines[0].empty())
+        {
+            y -= m_lines[0][0].baseline;
+            minY = y;
+        }
+        for (const SimpleArray<GlyphLine>& paragraphLines : lines)
+        {
+            const Paragraph& paragraph = shape[paragraphIndex++];
+            for (const GlyphLine& line : paragraphLines)
+            {
+                const GlyphRun& endRun = paragraph.runs[line.endRunIndex];
+                const GlyphRun& startRun = paragraph.runs[line.startRunIndex];
+                float width = endRun.xpos[line.endGlyphIndex] -
+                              startRun.xpos[line.startGlyphIndex] - endRun.letterSpacing;
+                if (width > maxWidth)
+                {
+                    maxWidth = width;
+                }
+            }
+            if (!paragraphLines.empty())
+            {
+                y += paragraphLines.back().bottom;
+            }
+            y += paragraphSpace;
+        }
+
+        switch (sizing())
+        {
+            case TextSizing::autoWidth:
+                return AABB::fromLTWH(0, 0, maxWidth, std::max(minY, y - paragraphSpace));
+                break;
+            case TextSizing::autoHeight:
+                return AABB::fromLTWH(0, 0, width(), std::max(minY, y - paragraphSpace));
+                break;
+            case TextSizing::fixed:
+                return AABB::fromLTWH(0, 0, width(), minY + height());
+                break;
+        }
+    }
+    return AABB();
+}
+
 AABB Text::localBounds() const
 {
     float width = m_bounds.width();
@@ -775,7 +861,7 @@
 Core* Text::hitTest(HitInfo*, const Mat2D&) { return nullptr; }
 void Text::addRun(TextValueRun* run) {}
 void Text::addModifierGroup(TextModifierGroup* group) {}
-void Text::markShapeDirty() {}
+void Text::markShapeDirty(bool sendToLayout) {}
 void Text::update(ComponentDirt value) {}
 void Text::alignValueChanged() {}
 void Text::sizingValueChanged() {}
@@ -791,4 +877,6 @@
 void Text::originValueChanged() {}
 void Text::originXChanged() {}
 void Text::originYChanged() {}
+AABB Text::computeIntrinsicSize(AABB min, AABB max) { return AABB(); }
+void Text::controlSize(AABB size) {}
 #endif
\ No newline at end of file
diff --git a/tess/build/premake5_tess.lua b/tess/build/premake5_tess.lua
index 7a07170..7f93b18 100644
--- a/tess/build/premake5_tess.lua
+++ b/tess/build/premake5_tess.lua
@@ -21,6 +21,7 @@
         dependencies .. '/sokol',
         dependencies .. '/earcut.hpp/include/mapbox',
         dependencies .. '/libtess2/Include',
+        yoga,
     })
     files({ '../src/**.cpp', dependencies .. '/libtess2/Source/**.c' })
     buildoptions({ '-Wall', '-fno-exceptions', '-fno-rtti', '-Werror=format' })
@@ -79,11 +80,12 @@
         rive .. '/include',
         dependencies .. '/sokol',
         dependencies .. '/earcut.hpp/include/mapbox',
+        yoga,
     })
     files({ '../test/**.cpp', rive .. 'utils/no_op_factory.cpp' })
-    links({ 'rive_tess_renderer', 'rive', 'rive_harfbuzz', 'rive_sheenbidi' })
+    links({ 'rive_tess_renderer', 'rive', 'rive_harfbuzz', 'rive_sheenbidi', 'rive_yoga' })
     buildoptions({ '-Wall', '-fno-exceptions', '-fno-rtti', '-Werror=format' })
-    defines({ 'TESTING' })
+    defines({ 'TESTING', 'YOGA_EXPORT=' })
 
     filter('configurations:debug')
     do
diff --git a/test/assets/layout/layout_center.riv b/test/assets/layout/layout_center.riv
new file mode 100644
index 0000000..f64c66c
--- /dev/null
+++ b/test/assets/layout/layout_center.riv
Binary files differ
diff --git a/test/assets/layout/layout_horizontal.riv b/test/assets/layout/layout_horizontal.riv
new file mode 100644
index 0000000..7813fa9
--- /dev/null
+++ b/test/assets/layout/layout_horizontal.riv
Binary files differ
diff --git a/test/assets/layout/layout_horizontal_gaps.riv b/test/assets/layout/layout_horizontal_gaps.riv
new file mode 100644
index 0000000..a10a936
--- /dev/null
+++ b/test/assets/layout/layout_horizontal_gaps.riv
Binary files differ
diff --git a/test/assets/layout/layout_horizontal_wrap.riv b/test/assets/layout/layout_horizontal_wrap.riv
new file mode 100644
index 0000000..f851c6b
--- /dev/null
+++ b/test/assets/layout/layout_horizontal_wrap.riv
Binary files differ
diff --git a/test/assets/layout/layout_vertical.riv b/test/assets/layout/layout_vertical.riv
new file mode 100644
index 0000000..35248ab
--- /dev/null
+++ b/test/assets/layout/layout_vertical.riv
Binary files differ
diff --git a/test/layout_test.cpp b/test/layout_test.cpp
new file mode 100644
index 0000000..08db250
--- /dev/null
+++ b/test/layout_test.cpp
@@ -0,0 +1,123 @@
+#include <rive/math/transform_components.hpp>
+#include <rive/shapes/rectangle.hpp>
+#include "utils/no_op_factory.hpp"
+#include "rive_file_reader.hpp"
+#include "rive_testing.hpp"
+#include <catch.hpp>
+#include <cstdio>
+
+TEST_CASE("LayoutComponent FlexDirection row", "[layout]")
+{
+    auto file = ReadRiveFile("../../test/assets/layout/layout_horizontal.riv");
+
+    auto artboard = file->artboard();
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent1") != nullptr);
+    auto target1 = artboard->find<rive::LayoutComponent>("LayoutComponent1");
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent2") != nullptr);
+    auto target2 = artboard->find<rive::LayoutComponent>("LayoutComponent2");
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent3") != nullptr);
+    auto target3 = artboard->find<rive::LayoutComponent>("LayoutComponent3");
+
+    artboard->advance(0.0f);
+    auto target1Components = target1->worldTransform().decompose();
+    auto target2Components = target2->worldTransform().decompose();
+    auto target3Components = target3->worldTransform().decompose();
+
+    REQUIRE(target1Components.x() == 0);
+    REQUIRE(target2Components.x() == 100);
+    REQUIRE(target3Components.x() == 200);
+    REQUIRE(target1Components.y() == 0);
+    REQUIRE(target2Components.y() == 0);
+    REQUIRE(target3Components.y() == 0);
+}
+
+TEST_CASE("LayoutComponent FlexDirection column", "[layout]")
+{
+    auto file = ReadRiveFile("../../test/assets/layout/layout_vertical.riv");
+
+    auto artboard = file->artboard();
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent1") != nullptr);
+    auto target1 = artboard->find<rive::LayoutComponent>("LayoutComponent1");
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent2") != nullptr);
+    auto target2 = artboard->find<rive::LayoutComponent>("LayoutComponent2");
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent3") != nullptr);
+    auto target3 = artboard->find<rive::LayoutComponent>("LayoutComponent3");
+
+    artboard->advance(0.0f);
+    auto target1Components = target1->worldTransform().decompose();
+    auto target2Components = target2->worldTransform().decompose();
+    auto target3Components = target3->worldTransform().decompose();
+
+    REQUIRE(target1Components.x() == 0);
+    REQUIRE(target2Components.x() == 0);
+    REQUIRE(target3Components.x() == 0);
+    REQUIRE(target1Components.y() == 0);
+    REQUIRE(target2Components.y() == 100);
+    REQUIRE(target3Components.y() == 200);
+}
+
+TEST_CASE("LayoutComponent FlexDirection row with gap", "[layout]")
+{
+    auto file = ReadRiveFile("../../test/assets/layout/layout_horizontal_gaps.riv");
+
+    auto artboard = file->artboard();
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent1") != nullptr);
+    auto target1 = artboard->find<rive::LayoutComponent>("LayoutComponent1");
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent2") != nullptr);
+    auto target2 = artboard->find<rive::LayoutComponent>("LayoutComponent2");
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent3") != nullptr);
+    auto target3 = artboard->find<rive::LayoutComponent>("LayoutComponent3");
+
+    artboard->advance(0.0f);
+    auto target1Components = target1->worldTransform().decompose();
+    auto target2Components = target2->worldTransform().decompose();
+    auto target3Components = target3->worldTransform().decompose();
+
+    REQUIRE(target1Components.x() == 0);
+    REQUIRE(target2Components.x() == 110);
+    REQUIRE(target3Components.x() == 220);
+    REQUIRE(target1Components.y() == 0);
+    REQUIRE(target2Components.y() == 0);
+    REQUIRE(target3Components.y() == 0);
+}
+
+TEST_CASE("LayoutComponent FlexDirection row with wrap", "[layout]")
+{
+    auto file = ReadRiveFile("../../test/assets/layout/layout_horizontal_wrap.riv");
+
+    auto artboard = file->artboard();
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent6") != nullptr);
+    auto target = artboard->find<rive::LayoutComponent>("LayoutComponent6");
+
+    artboard->advance(0.0f);
+    auto targetComponents = target->worldTransform().decompose();
+
+    REQUIRE(targetComponents.x() == 0);
+    REQUIRE(targetComponents.y() == 100);
+}
+
+TEST_CASE("LayoutComponent Center using alignItems and justifyContent", "[layout]")
+{
+    auto file = ReadRiveFile("../../test/assets/layout/layout_center.riv");
+
+    auto artboard = file->artboard();
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("LayoutComponent1") != nullptr);
+    auto target = artboard->find<rive::LayoutComponent>("LayoutComponent1");
+
+    artboard->advance(0.0f);
+    auto targetComponents = target->worldTransform().decompose();
+
+    REQUIRE(targetComponents.x() == 200);
+    REQUIRE(targetComponents.y() == 200);
+}
\ No newline at end of file
diff --git a/viewer/build/macosx/build_viewer.sh b/viewer/build/macosx/build_viewer.sh
index f41f0fe..8053947 100755
--- a/viewer/build/macosx/build_viewer.sh
+++ b/viewer/build/macosx/build_viewer.sh
@@ -57,7 +57,7 @@
 pushd ..
 
 OUT=out/$RENDERER/$GRAPHICS/$CONFIG
-$PREMAKE --scripts=../../build --file=./premake5_viewer.lua --config=$CONFIG --out=$OUT gmake2 --graphics=$GRAPHICS --renderer=$RENDERER --with_rive_tools --with_rive_text --with_rive_audio=system
+$PREMAKE --scripts=../../build --file=./premake5_viewer.lua --config=$CONFIG --out=$OUT gmake2 --graphics=$GRAPHICS --renderer=$RENDERER --with_rive_tools --with_rive_text --with_rive_audio=system --with_rive_layout
 
 for var in "$@"; do
     if [[ $var = "clean" ]]; then
diff --git a/viewer/build/premake5_viewer.lua b/viewer/build/premake5_viewer.lua
index 741c497..9418d8c 100644
--- a/viewer/build/premake5_viewer.lua
+++ b/viewer/build/premake5_viewer.lua
@@ -24,7 +24,7 @@
     end
     kind('ConsoleApp')
 
-    defines({ 'WITH_RIVE_TEXT', 'WITH_RIVE_AUDIO' })
+    defines({ 'WITH_RIVE_TEXT', 'WITH_RIVE_AUDIO', 'WITH_RIVE_LAYOUT', 'YOGA_EXPORT=' })
 
     includedirs({
         '../include',
@@ -34,9 +34,10 @@
         dependencies .. '/sokol',
         dependencies .. '/imgui',
         miniaudio,
+        yoga,
     })
 
-    links({ 'rive', 'rive_harfbuzz', 'rive_sheenbidi' })
+    links({ 'rive', 'rive_harfbuzz', 'rive_sheenbidi', 'rive_yoga' })
 
     libdirs({ rive .. '/build/%{cfg.system}/bin/%{cfg.buildcfg}' })