Replace computeIntrinsicSize with measureLayout

Replaces computeIntrinsicSize with measureLayout which lets the layout engine call the measure function as necessary to allow the objects that can respond to changes in size to fit as best as they can given the constraints. This allow for better handling of line wrapping, ellipsis, and pushing content under text to the exact boundary of the text when using max width/height options in the layout engine.

I also tied in a few other fixes for how we allocate layout nodes and styles.

Diffs=
da0b71559 Replace computeIntrinsicSize with measureLayout (#7410)

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head
index 499eaf1..162ea2c 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-6c76b425f887a8adb3beb8ba8263200b9cdbb2c9
+da0b7155934e5e6b94d3d7e3d8b5eef76ae6607d
diff --git a/dev/test.sh b/dev/test.sh
index 0167ca2..213e09a 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  --with_rive_layout --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 00db702..78c2296 100644
--- a/dev/test/premake5.lua
+++ b/dev/test/premake5.lua
@@ -8,7 +8,7 @@
     'WITH_RIVE_AUDIO',
     'WITH_RIVE_AUDIO_TOOLS',
     'WITH_RIVE_LAYOUT',
-    'YOGA_EXPORT='
+    'YOGA_EXPORT=',
 })
 
 dofile(path.join(path.getabsolute('../../'), 'premake5_v2.lua'))
diff --git a/include/rive/animation/state_machine_instance.hpp b/include/rive/animation/state_machine_instance.hpp
index 13e3740..786366f 100644
--- a/include/rive/animation/state_machine_instance.hpp
+++ b/include/rive/animation/state_machine_instance.hpp
@@ -29,8 +29,8 @@
 class NestedEventListener;
 class NestedEventNotifier;
 class Event;
-class EventReport;
 class KeyedProperty;
+class EventReport;
 
 class StateMachineInstance : public Scene, public NestedEventNotifier, public NestedEventListener
 {
diff --git a/include/rive/layout/layout_measure_mode.hpp b/include/rive/layout/layout_measure_mode.hpp
new file mode 100644
index 0000000..50f6eb8
--- /dev/null
+++ b/include/rive/layout/layout_measure_mode.hpp
@@ -0,0 +1,12 @@
+#ifndef _RIVE_LAYOUT_MEASURE_MODE_HPP_
+#define _RIVE_LAYOUT_MEASURE_MODE_HPP_
+namespace rive
+{
+enum class LayoutMeasureMode : uint8_t
+{
+    undefined = 0,
+    exactly = 1,
+    atMost = 2
+};
+}
+#endif
\ No newline at end of file
diff --git a/include/rive/layout_component.hpp b/include/rive/layout_component.hpp
index e7c5d55..1049e3b 100644
--- a/include/rive/layout_component.hpp
+++ b/include/rive/layout_component.hpp
@@ -2,6 +2,7 @@
 #define _RIVE_LAYOUT_COMPONENT_HPP_
 #include "rive/generated/layout_component_base.hpp"
 #include "rive/layout/layout_component_style.hpp"
+#include "rive/layout/layout_measure_mode.hpp"
 #ifdef WITH_RIVE_LAYOUT
 #include "yoga/YGNode.h"
 #include "yoga/YGStyle.h"
@@ -10,24 +11,20 @@
 #include <stdio.h>
 namespace rive
 {
-#ifndef WITH_RIVE_LAYOUT
-class YGNodeRef
+struct LayoutData
 {
-public:
-    YGNodeRef() {}
-};
-class YGStyle
-{
-public:
-    YGStyle() {}
-};
+#ifdef WITH_RIVE_LAYOUT
+    YGNode node;
+    YGStyle style;
 #endif
+};
+
 class LayoutComponent : public LayoutComponentBase
 {
 private:
     LayoutComponentStyle* m_style = nullptr;
-    YGNodeRef m_layoutNode;
-    YGStyle* m_layoutStyle;
+    std::unique_ptr<LayoutData> m_layoutData;
+
     float m_layoutSizeWidth = 0;
     float m_layoutSizeHeight = 0;
     float m_layoutLocationX = 0;
@@ -35,6 +32,8 @@
 
 #ifdef WITH_RIVE_LAYOUT
 private:
+    YGNode& layoutNode() { return m_layoutData->node; }
+    YGStyle& layoutStyle() { return m_layoutData->style; }
     void syncLayoutChildren();
     void propagateSizeToChildren(ContainerComponent* component);
     AABB findMaxIntrinsicSize(ContainerComponent* component, AABB maxIntrinsicSize);
@@ -48,38 +47,13 @@
     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;
-    }
+    LayoutComponent();
     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;
 
@@ -88,6 +62,11 @@
     void widthChanged() override;
     void heightChanged() override;
     void styleIdChanged() override;
+
+    Vec2D measureLayout(float width,
+                        LayoutMeasureMode widthMode,
+                        float height,
+                        LayoutMeasureMode heightMode);
 };
 } // namespace rive
 
diff --git a/include/rive/nested_artboard.hpp b/include/rive/nested_artboard.hpp
index 3eac7dc..0c2ecd7 100644
--- a/include/rive/nested_artboard.hpp
+++ b/include/rive/nested_artboard.hpp
@@ -80,8 +80,11 @@
     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;
+    Vec2D measureLayout(float width,
+                        LayoutMeasureMode widthMode,
+                        float height,
+                        LayoutMeasureMode heightMode) override;
+    void controlSize(Vec2D size) override;
 
     /// Convert a world space (relative to the artboard that this
     /// NestedArtboard is a child of) to the local space of the Artboard
diff --git a/include/rive/shapes/image.hpp b/include/rive/shapes/image.hpp
index 60a0c72..7d9510b 100644
--- a/include/rive/shapes/image.hpp
+++ b/include/rive/shapes/image.hpp
@@ -24,8 +24,13 @@
     void setAsset(FileAsset*) override;
     uint32_t assetId() override;
     Core* clone() const override;
-    AABB computeIntrinsicSize(AABB min, AABB max) override;
-    void controlSize(AABB size) override;
+    Vec2D measureLayout(float width,
+                        LayoutMeasureMode widthMode,
+                        float height,
+                        LayoutMeasureMode heightMode) override;
+    void controlSize(Vec2D size) override;
+    float width() const;
+    float height() const;
 };
 } // namespace rive
 
diff --git a/include/rive/shapes/parametric_path.hpp b/include/rive/shapes/parametric_path.hpp
index 7bb3204..477b9e2 100644
--- a/include/rive/shapes/parametric_path.hpp
+++ b/include/rive/shapes/parametric_path.hpp
@@ -7,8 +7,11 @@
 class ParametricPath : public ParametricPathBase
 {
 public:
-    AABB computeIntrinsicSize(AABB min, AABB max) override;
-    void controlSize(AABB size) override;
+    Vec2D measureLayout(float width,
+                        LayoutMeasureMode widthMode,
+                        float height,
+                        LayoutMeasureMode heightMode) override;
+    void controlSize(Vec2D size) override;
 
 protected:
     void widthChanged() override;
diff --git a/include/rive/text/text.hpp b/include/rive/text/text.hpp
index 073af1d..7d0ae9a 100644
--- a/include/rive/text/text.hpp
+++ b/include/rive/text/text.hpp
@@ -191,8 +191,11 @@
     void originXChanged() override;
     void originYChanged() override;
 
-    AABB computeIntrinsicSize(AABB min, AABB max) override;
-    void controlSize(AABB size) override;
+    Vec2D measureLayout(float width,
+                        LayoutMeasureMode widthMode,
+                        float height,
+                        LayoutMeasureMode heightMode) override;
+    void controlSize(Vec2D 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
@@ -247,7 +250,7 @@
 #endif
     float m_layoutWidth = NAN;
     float m_layoutHeight = NAN;
-    AABB measure(AABB maxSize);
+    Vec2D measure(Vec2D maxSize);
 };
 } // namespace rive
 
diff --git a/include/rive/transform_component.hpp b/include/rive/transform_component.hpp
index 6b63a11..534165c 100644
--- a/include/rive/transform_component.hpp
+++ b/include/rive/transform_component.hpp
@@ -3,6 +3,7 @@
 #include "rive/generated/transform_component_base.hpp"
 #include "rive/math/aabb.hpp"
 #include "rive/math/mat2d.hpp"
+#include "rive/layout/layout_measure_mode.hpp"
 
 namespace rive
 {
@@ -49,8 +50,15 @@
     virtual AABB localBounds() const;
     void markDirtyIfConstrained();
 
-    virtual AABB computeIntrinsicSize(AABB min, AABB max) { return AABB(); }
-    virtual void controlSize(AABB size) {}
+    virtual Vec2D measureLayout(float width,
+                                LayoutMeasureMode widthMode,
+                                float height,
+                                LayoutMeasureMode heightMode)
+    {
+        return Vec2D();
+    }
+
+    virtual void controlSize(Vec2D size) {}
 };
 } // namespace rive
 
diff --git a/src/artboard.cpp b/src/artboard.cpp
index 9a9b741..6b8e83d 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -84,6 +84,9 @@
     m_BackgroundPath = factory()->makeEmptyRenderPath();
     m_ClipPath = factory()->makeEmptyRenderPath();
 
+#ifdef WITH_RIVE_LAYOUT
+    markLayoutDirty(this);
+#endif
     // onAddedDirty guarantees that all objects are now available so they can be
     // looked up by index/id. This is where nodes find their parents, but they
     // can't assume that their parent's parent will have resolved yet.
diff --git a/src/layout_component.cpp b/src/layout_component.cpp
index ac908f6..c9b600f 100644
--- a/src/layout_component.cpp
+++ b/src/layout_component.cpp
@@ -21,6 +21,8 @@
 }
 
 #ifdef WITH_RIVE_LAYOUT
+LayoutComponent::LayoutComponent() : m_layoutData(std::unique_ptr<LayoutData>(new LayoutData())) {}
+
 StatusCode LayoutComponent::onAddedDirty(CoreContext* context)
 {
     auto code = Super::onAddedDirty(context);
@@ -60,10 +62,28 @@
     }
 }
 
-AABB LayoutComponent::findMaxIntrinsicSize(ContainerComponent* component, AABB maxIntrinsicSize)
+static YGSize measureFunc(YGNode* node,
+                          float width,
+                          YGMeasureMode widthMode,
+                          float height,
+                          YGMeasureMode heightMode)
 {
-    auto intrinsicSize = maxIntrinsicSize;
-    for (auto child : component->children())
+    Vec2D size = ((LayoutComponent*)node->getContext())
+                     ->measureLayout(width,
+                                     (LayoutMeasureMode)widthMode,
+                                     height,
+                                     (LayoutMeasureMode)heightMode);
+
+    return YGSize{size.x, size.y};
+}
+
+Vec2D LayoutComponent::measureLayout(float width,
+                                     LayoutMeasureMode widthMode,
+                                     float height,
+                                     LayoutMeasureMode heightMode)
+{
+    Vec2D size = Vec2D();
+    for (auto child : children())
     {
         if (child->is<LayoutComponent>())
         {
@@ -71,141 +91,106 @@
         }
         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);
+            auto transformComponent = child->as<TransformComponent>();
+            Vec2D measured =
+                transformComponent->measureLayout(width, widthMode, height, heightMode);
+            size = Vec2D(std::max(size.x, measured.x), std::max(size.y, measured.y));
         }
     }
-    return intrinsicSize;
+    return size;
 }
 
 void LayoutComponent::syncStyle()
 {
-    if (style() == nullptr || layoutStyle() == nullptr || layoutNode() == nullptr)
+    if (m_style == nullptr)
     {
         return;
     }
-    bool setIntrinsicWidth = false;
-    bool setIntrinsicHeight = false;
-    if (style()->intrinsicallySized() &&
-        (style()->widthUnits() == YGUnitAuto || style()->heightUnits() == YGUnitAuto))
+    YGNode& ygNode = layoutNode();
+    YGStyle& ygStyle = layoutStyle();
+    if (m_style->intrinsicallySized())
     {
-        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};
-            }
-        }
+        ygNode.setContext(this);
+        ygNode.setMeasureFunc(measureFunc);
     }
-    if (!setIntrinsicWidth)
+    else
     {
-        layoutStyle()->dimensions()[YGDimensionWidth] = YGValue{width(), style()->widthUnits()};
+        ygNode.setMeasureFunc(nullptr);
     }
-    if (!setIntrinsicHeight)
+    if (m_style->widthUnits() != YGUnitAuto)
     {
-        layoutStyle()->dimensions()[YGDimensionHeight] = YGValue{height(), style()->heightUnits()};
+        ygStyle.dimensions()[YGDimensionWidth] = YGValue{width(), m_style->widthUnits()};
     }
-    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()};
+    else
+    {
+        ygStyle.dimensions()[YGDimensionWidth] = YGValueAuto;
+    }
+    if (m_style->heightUnits() != YGUnitAuto)
+    {
+        ygStyle.dimensions()[YGDimensionHeight] = YGValue{height(), m_style->heightUnits()};
+    }
+    else
+    {
+        ygStyle.dimensions()[YGDimensionHeight] = YGValueAuto;
+    }
+    ygStyle.minDimensions()[YGDimensionWidth] =
+        YGValue{m_style->minWidth(), m_style->minWidthUnits()};
+    ygStyle.minDimensions()[YGDimensionHeight] =
+        YGValue{m_style->minHeight(), m_style->minHeightUnits()};
+    ygStyle.maxDimensions()[YGDimensionWidth] =
+        YGValue{m_style->maxWidth(), m_style->maxWidthUnits()};
+    ygStyle.maxDimensions()[YGDimensionHeight] =
+        YGValue{m_style->maxHeight(), m_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()};
+    ygStyle.gap()[YGGutterColumn] =
+        YGValue{m_style->gapHorizontal(), m_style->gapHorizontalUnits()};
+    ygStyle.gap()[YGGutterRow] = YGValue{m_style->gapVertical(), m_style->gapVerticalUnits()};
+    ygStyle.border()[YGEdgeLeft] = YGValue{m_style->borderLeft(), m_style->borderLeftUnits()};
+    ygStyle.border()[YGEdgeRight] = YGValue{m_style->borderRight(), m_style->borderRightUnits()};
+    ygStyle.border()[YGEdgeTop] = YGValue{m_style->borderTop(), m_style->borderTopUnits()};
+    ygStyle.border()[YGEdgeBottom] = YGValue{m_style->borderBottom(), m_style->borderBottomUnits()};
+    ygStyle.margin()[YGEdgeLeft] = YGValue{m_style->marginLeft(), m_style->marginLeftUnits()};
+    ygStyle.margin()[YGEdgeRight] = YGValue{m_style->marginRight(), m_style->marginRightUnits()};
+    ygStyle.margin()[YGEdgeTop] = YGValue{m_style->marginTop(), m_style->marginTopUnits()};
+    ygStyle.margin()[YGEdgeBottom] = YGValue{m_style->marginBottom(), m_style->marginBottomUnits()};
+    ygStyle.padding()[YGEdgeLeft] = YGValue{m_style->paddingLeft(), m_style->paddingLeftUnits()};
+    ygStyle.padding()[YGEdgeRight] = YGValue{m_style->paddingRight(), m_style->paddingRightUnits()};
+    ygStyle.padding()[YGEdgeTop] = YGValue{m_style->paddingTop(), m_style->paddingTopUnits()};
+    ygStyle.padding()[YGEdgeBottom] =
+        YGValue{m_style->paddingBottom(), m_style->paddingBottomUnits()};
+    ygStyle.position()[YGEdgeLeft] = YGValue{m_style->positionLeft(), m_style->positionLeftUnits()};
+    ygStyle.position()[YGEdgeRight] =
+        YGValue{m_style->positionRight(), m_style->positionRightUnits()};
+    ygStyle.position()[YGEdgeTop] = YGValue{m_style->positionTop(), m_style->positionTopUnits()};
+    ygStyle.position()[YGEdgeBottom] =
+        YGValue{m_style->positionBottom(), m_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();
+    ygStyle.display() = m_style->display();
+    ygStyle.positionType() = m_style->positionType();
+    ygStyle.flex() = YGFloatOptional(m_style->flex());
+    ygStyle.flexGrow() = YGFloatOptional(m_style->flexGrow());
+    ygStyle.flexShrink() = YGFloatOptional(m_style->flexShrink());
+    // ygStyle.flexBasis() = m_style->flexBasis();
+    ygStyle.flexDirection() = m_style->flexDirection();
+    ygStyle.flexWrap() = m_style->flexWrap();
+    ygStyle.alignItems() = m_style->alignItems();
+    ygStyle.alignContent() = m_style->alignContent();
+    ygStyle.alignSelf() = m_style->alignSelf();
+    ygStyle.justifyContent() = m_style->justifyContent();
 
-    layoutNode()->setStyle(*layoutStyle());
+    ygNode.setStyle(ygStyle);
 }
 
 void LayoutComponent::syncLayoutChildren()
 {
-    YGNodeRemoveAllChildren(layoutNode());
+    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);
+            YGNodeInsertChild(&layoutNode(), &child->as<LayoutComponent>()->layoutNode(), index);
             index += 1;
         }
     }
@@ -231,7 +216,7 @@
         if (child->is<TransformComponent>())
         {
             auto sizableChild = child->as<TransformComponent>();
-            sizableChild->controlSize(AABB::fromLTWH(0, 0, m_layoutSizeWidth, m_layoutSizeHeight));
+            sizableChild->controlSize(Vec2D(m_layoutSizeWidth, m_layoutSizeHeight));
         }
         if (child->is<ContainerComponent>())
         {
@@ -242,15 +227,16 @@
 
 void LayoutComponent::calculateLayout()
 {
-    YGNodeCalculateLayout(layoutNode(), width(), height(), YGDirection::YGDirectionInherit);
+    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());
+    auto node = &layoutNode();
+    auto left = YGNodeLayoutGetLeft(node);
+    auto top = YGNodeLayoutGetTop(node);
+    auto width = YGNodeLayoutGetWidth(node);
+    auto height = YGNodeLayoutGetHeight(node);
     if (left != m_layoutLocationX || top != m_layoutLocationY || width != m_layoutSizeWidth ||
         height != m_layoutSizeHeight)
     {
@@ -265,7 +251,7 @@
 
 void LayoutComponent::markLayoutNodeDirty()
 {
-    layoutNode()->markDirtyAndPropagate();
+    layoutNode().markDirtyAndPropagate();
     artboard()->markLayoutDirty(this);
 }
 #else
diff --git a/src/nested_artboard.cpp b/src/nested_artboard.cpp
index 7bf7df2..e59b9e9 100644
--- a/src/nested_artboard.cpp
+++ b/src/nested_artboard.cpp
@@ -6,6 +6,7 @@
 #include "rive/nested_animation.hpp"
 #include "rive/animation/nested_state_machine.hpp"
 #include "rive/clip_result.hpp"
+#include <limits>
 #include <cassert>
 
 using namespace rive;
@@ -232,12 +233,24 @@
     return true;
 }
 
-AABB NestedArtboard::computeIntrinsicSize(AABB min, AABB max) { return max; }
-
-void NestedArtboard::controlSize(AABB size)
+Vec2D NestedArtboard::measureLayout(float width,
+                                    LayoutMeasureMode widthMode,
+                                    float height,
+                                    LayoutMeasureMode heightMode)
 {
-    auto newScaleX = size.width() / m_Artboard->originalWidth();
-    auto newScaleY = size.height() / m_Artboard->originalHeight();
+    return Vec2D(
+        std::min(widthMode == LayoutMeasureMode::undefined ? std::numeric_limits<float>::max()
+                                                           : width,
+                 m_Instance ? m_Instance->width() : 0.0f),
+        std::min(heightMode == LayoutMeasureMode::undefined ? std::numeric_limits<float>::max()
+                                                            : height,
+                 m_Instance ? m_Instance->height() : 0.0f));
+}
+
+void NestedArtboard::controlSize(Vec2D size)
+{
+    auto newScaleX = size.x / m_Artboard->originalWidth();
+    auto newScaleY = size.y / m_Artboard->originalHeight();
     if (newScaleX != scaleX() || newScaleY != scaleY())
     {
         // TODO: Support nested artboard fit & alignment
diff --git a/src/shapes/image.cpp b/src/shapes/image.cpp
index c2f21cf..e761230 100644
--- a/src/shapes/image.cpp
+++ b/src/shapes/image.cpp
@@ -122,13 +122,76 @@
 void Image::setMesh(Mesh* mesh) { m_Mesh = mesh; }
 Mesh* Image::mesh() const { return m_Mesh; }
 
-AABB Image::computeIntrinsicSize(AABB min, AABB max) { return max; }
+float Image::width() const
+{
+    rive::ImageAsset* asset = this->imageAsset();
+    if (asset == nullptr)
+    {
+        return 0.0f;
+    }
 
-void Image::controlSize(AABB size)
+    rive::RenderImage* renderImage = asset->renderImage();
+    if (renderImage == nullptr)
+    {
+        return 0.0f;
+    }
+    return renderImage->width();
+}
+
+float Image::height() const
+{
+    rive::ImageAsset* asset = this->imageAsset();
+    if (asset == nullptr)
+    {
+        return 0.0f;
+    }
+
+    rive::RenderImage* renderImage = asset->renderImage();
+    if (renderImage == nullptr)
+    {
+        return 0.0f;
+    }
+    return renderImage->height();
+}
+
+Vec2D Image::measureLayout(float width,
+                           LayoutMeasureMode widthMode,
+                           float height,
+                           LayoutMeasureMode heightMode)
+{
+    float measuredWidth, measuredHeight;
+    switch (widthMode)
+    {
+        case LayoutMeasureMode::atMost:
+            measuredWidth = std::max(Image::width(), width);
+            break;
+        case LayoutMeasureMode::exactly:
+            measuredWidth = width;
+            break;
+        case LayoutMeasureMode::undefined:
+            measuredWidth = Image::width();
+            break;
+    }
+    switch (heightMode)
+    {
+        case LayoutMeasureMode::atMost:
+            measuredHeight = std::max(Image::height(), height);
+            break;
+        case LayoutMeasureMode::exactly:
+            measuredHeight = height;
+            break;
+        case LayoutMeasureMode::undefined:
+            measuredHeight = Image::height();
+            break;
+    }
+    return Vec2D(measuredWidth, measuredHeight);
+}
+
+void Image::controlSize(Vec2D size)
 {
     auto renderImage = imageAsset()->renderImage();
-    auto newScaleX = size.width() / renderImage->width();
-    auto newScaleY = size.height() / renderImage->height();
+    auto newScaleX = size.x / renderImage->width();
+    auto newScaleY = size.y / renderImage->height();
     if (newScaleX != scaleX() || newScaleY != scaleY())
     {
         scaleX(newScaleX);
diff --git a/src/shapes/parametric_path.cpp b/src/shapes/parametric_path.cpp
index 534dfc4..b854cdf 100644
--- a/src/shapes/parametric_path.cpp
+++ b/src/shapes/parametric_path.cpp
@@ -3,15 +3,43 @@
 
 using namespace rive;
 
-AABB ParametricPath::computeIntrinsicSize(AABB min, AABB max)
+Vec2D ParametricPath::measureLayout(float width,
+                                    LayoutMeasureMode widthMode,
+                                    float height,
+                                    LayoutMeasureMode heightMode)
 {
-    return AABB::fromLTWH(0, 0, width(), height());
+    float measuredWidth, measuredHeight;
+    switch (widthMode)
+    {
+        case LayoutMeasureMode::atMost:
+            measuredWidth = std::max(ParametricPath::width(), width);
+            break;
+        case LayoutMeasureMode::exactly:
+            measuredWidth = width;
+            break;
+        case LayoutMeasureMode::undefined:
+            measuredWidth = ParametricPath::width();
+            break;
+    }
+    switch (heightMode)
+    {
+        case LayoutMeasureMode::atMost:
+            measuredHeight = std::max(ParametricPath::height(), height);
+            break;
+        case LayoutMeasureMode::exactly:
+            measuredHeight = height;
+            break;
+        case LayoutMeasureMode::undefined:
+            measuredHeight = ParametricPath::height();
+            break;
+    }
+    return Vec2D(measuredWidth, measuredHeight);
 }
 
-void ParametricPath::controlSize(AABB size)
+void ParametricPath::controlSize(Vec2D size)
 {
-    width(size.width());
-    height(size.height());
+    width(size.x);
+    height(size.y);
     markWorldTransformDirty();
 }
 
diff --git a/src/text/text.cpp b/src/text/text.cpp
index 8431140..48624a5 100644
--- a/src/text/text.cpp
+++ b/src/text/text.cpp
@@ -11,6 +11,7 @@
 #include "rive/artboard.hpp"
 #include "rive/factory.hpp"
 #include "rive/clip_result.hpp"
+#include <limits>
 
 void GlyphItr::tryAdvanceRun()
 {
@@ -241,14 +242,22 @@
     return true;
 }
 
-AABB Text::computeIntrinsicSize(AABB min, AABB max) { return measure(max); }
-
-void Text::controlSize(AABB size)
+Vec2D Text::measureLayout(float width,
+                          LayoutMeasureMode widthMode,
+                          float height,
+                          LayoutMeasureMode heightMode)
 {
-    if (m_layoutWidth != size.width() || m_layoutHeight != size.height())
+    return measure(Vec2D(
+        widthMode == LayoutMeasureMode::undefined ? std::numeric_limits<float>::max() : width,
+        heightMode == LayoutMeasureMode::undefined ? std::numeric_limits<float>::max() : height));
+}
+
+void Text::controlSize(Vec2D size)
+{
+    if (m_layoutWidth != size.x || m_layoutHeight != size.y)
     {
-        m_layoutWidth = size.width();
-        m_layoutHeight = size.height();
+        m_layoutWidth = size.x;
+        m_layoutHeight = size.y;
         markShapeDirty(false);
     }
 }
@@ -760,18 +769,21 @@
     }
 }
 
-AABB Text::measure(AABB maxSize)
+Vec2D Text::measure(Vec2D 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());
+        auto lines = breakLines(shape,
+                                std::min(maxSize.x,
+                                         sizing() == TextSizing::autoWidth
+                                             ? std::numeric_limits<float>::max()
+                                             : width()),
+                                (TextAlign)alignValue());
         float y = 0;
+        float computedHeight = 0.0f;
         float minY = 0;
         int paragraphIndex = 0;
         float maxWidth = 0;
@@ -781,6 +793,9 @@
             y -= m_lines[0][0].baseline;
             minY = y;
         }
+        int ellipsisLine = -1;
+        bool wantEllipsis = overflow() == TextOverflow::ellipsis;
+
         for (const SimpleArray<GlyphLine>& paragraphLines : lines)
         {
             const Paragraph& paragraph = shape[paragraphIndex++];
@@ -794,6 +809,17 @@
                 {
                     maxWidth = width;
                 }
+                if (wantEllipsis && y + line.bottom > maxSize.y)
+                {
+                    if (ellipsisLine == -1)
+                    {
+                        // Nothing fits, just show the first line and ellipse it.
+                        computedHeight = y + line.bottom;
+                    }
+                    goto doneMeasuring;
+                }
+                ellipsisLine++;
+                computedHeight = y + line.bottom;
             }
             if (!paragraphLines.empty())
             {
@@ -801,21 +827,22 @@
             }
             y += paragraphSpace;
         }
+    doneMeasuring:
 
         switch (sizing())
         {
             case TextSizing::autoWidth:
-                return AABB::fromLTWH(0, 0, maxWidth, std::max(minY, y - paragraphSpace));
+                return Vec2D(maxWidth, std::max(minY, computedHeight));
                 break;
             case TextSizing::autoHeight:
-                return AABB::fromLTWH(0, 0, width(), std::max(minY, y - paragraphSpace));
+                return Vec2D(width(), std::max(minY, computedHeight));
                 break;
             case TextSizing::fixed:
-                return AABB::fromLTWH(0, 0, width(), minY + height());
+                return Vec2D(width(), minY + height());
                 break;
         }
     }
-    return AABB();
+    return Vec2D();
 }
 
 AABB Text::localBounds() const
@@ -877,6 +904,12 @@
 void Text::originValueChanged() {}
 void Text::originXChanged() {}
 void Text::originYChanged() {}
-AABB Text::computeIntrinsicSize(AABB min, AABB max) { return AABB(); }
-void Text::controlSize(AABB size) {}
+Vec2D Text::measureLayout(float width,
+                          LayoutMeasureMode widthMode,
+                          float height,
+                          LayoutMeasureMode heightMode)
+{
+    return Vec2D();
+}
+void Text::controlSize(Vec2D size) {}
 #endif
\ No newline at end of file
diff --git a/test/assets/layout/measure_tests.riv b/test/assets/layout/measure_tests.riv
new file mode 100644
index 0000000..1f4e5cc
--- /dev/null
+++ b/test/assets/layout/measure_tests.riv
Binary files differ
diff --git a/test/layout_test.cpp b/test/layout_test.cpp
index 08db250..c5c07ec 100644
--- a/test/layout_test.cpp
+++ b/test/layout_test.cpp
@@ -1,5 +1,6 @@
-#include <rive/math/transform_components.hpp>
-#include <rive/shapes/rectangle.hpp>
+#include "rive/math/transform_components.hpp"
+#include "rive/shapes/rectangle.hpp"
+#include "rive/text/text.hpp"
 #include "utils/no_op_factory.hpp"
 #include "rive_file_reader.hpp"
 #include "rive_testing.hpp"
@@ -120,4 +121,23 @@
 
     REQUIRE(targetComponents.x() == 200);
     REQUIRE(targetComponents.y() == 200);
-}
\ No newline at end of file
+}
+
+TEST_CASE("LayoutComponent with intrinsic size gets measured correctly", "[layout]")
+{
+    auto file = ReadRiveFile("../../test/assets/layout/measure_tests.riv");
+
+    auto artboard = file->artboard("hi");
+
+    REQUIRE(artboard->find<rive::LayoutComponent>("TextLayout") != nullptr);
+    REQUIRE(artboard->find<rive::Text>("HiText") != nullptr);
+
+    artboard->advance(0.0f);
+
+    auto text = artboard->find<rive::Text>("HiText");
+    auto bounds = text->localBounds();
+    REQUIRE(bounds.left() == 0);
+    REQUIRE(bounds.top() == 0);
+    REQUIRE(bounds.width() == 63.0f);
+    REQUIRE(bounds.height() == 73.0f);
+}