Text fix variable and opacity

Fixes issues caught by @JcToon

Diffs=
44ef23f7e Text fix variable and opacity (#5017)
diff --git a/.rive_head b/.rive_head
index be1933e..a911f6c 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-3c396d3b8d94b6c628493797c4f2b83938879284
+44ef23f7eba8156a8d1129a18bc948e74c920a6a
diff --git a/include/rive/shapes/shape_paint_container.hpp b/include/rive/shapes/shape_paint_container.hpp
index 6771f6b..f5200fa 100644
--- a/include/rive/shapes/shape_paint_container.hpp
+++ b/include/rive/shapes/shape_paint_container.hpp
@@ -36,6 +36,8 @@
     void invalidateStrokeEffects();
 
     std::unique_ptr<CommandPath> makeCommandPath(PathSpace space);
+
+    void propagateOpacity(float opacity);
 };
 } // namespace rive
 
diff --git a/include/rive/text/text_style.hpp b/include/rive/text/text_style.hpp
index b8825b4..5a61435 100644
--- a/include/rive/text/text_style.hpp
+++ b/include/rive/text/text_style.hpp
@@ -14,6 +14,7 @@
 class Renderer;
 class RenderPath;
 
+class TextVariationHelper;
 class TextStyleAxis;
 class TextStyle : public TextStyleBase, public ShapePaintContainer, public FileAssetReferencer
 {
@@ -23,7 +24,7 @@
 public:
     TextStyle();
     void buildDependencies() override;
-    const rcp<Font> font() const { return m_font; }
+    const rcp<Font> font() const;
     void assets(const std::vector<FileAsset*>& assets) override;
     StatusCode import(ImportStack& importStack) override;
 
@@ -32,14 +33,17 @@
     void draw(Renderer* renderer);
     Core* clone() const override;
     void addVariation(TextStyleAxis* axis);
+    void updateVariableFont();
+    StatusCode onAddedClean(CoreContext* context) override;
     void onDirty(ComponentDirt dirt) override;
-    void update(ComponentDirt value) override;
+    // void update(ComponentDirt value) override;
 
 protected:
     void fontSizeChanged() override;
 
 private:
-    rcp<Font> m_font;
+    std::unique_ptr<TextVariationHelper> m_variationHelper;
+    rcp<Font> m_variableFont;
     FontAsset* m_fontAsset = nullptr;
     std::unique_ptr<RenderPath> m_path;
     bool m_hasContents = false;
diff --git a/src/shapes/shape.cpp b/src/shapes/shape.cpp
index cdcc59b..c7270fa 100644
--- a/src/shapes/shape.cpp
+++ b/src/shapes/shape.cpp
@@ -24,10 +24,7 @@
 
     if (hasDirt(value, ComponentDirt::RenderOpacity))
     {
-        for (auto shapePaint : m_ShapePaints)
-        {
-            shapePaint->renderOpacity(renderOpacity());
-        }
+        propagateOpacity(renderOpacity());
     }
 }
 
diff --git a/src/shapes/shape_paint_container.cpp b/src/shapes/shape_paint_container.cpp
index 458038c..283568a 100644
--- a/src/shapes/shape_paint_container.cpp
+++ b/src/shapes/shape_paint_container.cpp
@@ -88,3 +88,11 @@
         return std::unique_ptr<CommandPath>(factory->makeEmptyRenderPath());
     }
 }
+
+void ShapePaintContainer::propagateOpacity(float opacity)
+{
+    for (auto shapePaint : m_ShapePaints)
+    {
+        shapePaint->renderOpacity(opacity);
+    }
+}
\ No newline at end of file
diff --git a/src/text/text.cpp b/src/text/text.cpp
index af87a78..98406e0 100644
--- a/src/text/text.cpp
+++ b/src/text/text.cpp
@@ -117,6 +117,7 @@
                     // This was the first path added to the style, so let's mark
                     // it in our draw list.
                     m_renderStyles.push_back(style);
+                    style->propagateOpacity(renderOpacity());
                 }
             }
             lineIndex++;
@@ -249,6 +250,15 @@
         // with shaping.
         buildRenderStyles();
     }
+    else if (hasDirt(value, ComponentDirt::RenderOpacity))
+    {
+        // Note that buildRenderStyles does this too, which is why we can get
+        // away doing this in the else.
+        for (TextStyle* style : m_renderStyles)
+        {
+            style->propagateOpacity(renderOpacity());
+        }
+    }
 }
 
 Core* Text::hitTest(HitInfo*, const Mat2D&)
diff --git a/src/text/text_style.cpp b/src/text/text_style.cpp
index d3af95a..6ea4d37 100644
--- a/src/text/text_style.cpp
+++ b/src/text/text_style.cpp
@@ -10,6 +10,27 @@
 
 using namespace rive;
 
+namespace rive
+{
+class TextVariationHelper : public Component
+{
+public:
+    TextVariationHelper(TextStyle* style) : m_textStyle(style) {}
+    TextStyle* style() const { return m_textStyle; }
+    void buildDependencies() override
+    {
+        auto text = m_textStyle->parent();
+        text->artboard()->addDependent(this);
+        addDependent(text);
+    }
+
+    void update(ComponentDirt value) override { m_textStyle->updateVariableFont(); }
+
+private:
+    TextStyle* m_textStyle;
+};
+} // namespace rive
+
 // satisfy unique_ptr
 TextStyle::TextStyle() {}
 
@@ -20,34 +41,78 @@
     if ((dirt & ComponentDirt::TextShape) == ComponentDirt::TextShape)
     {
         parent()->as<Text>()->markShapeDirty();
+        if (m_variationHelper != nullptr)
+        {
+            m_variationHelper->addDirt(ComponentDirt::TextShape);
+        }
     }
 }
 
-void TextStyle::update(ComponentDirt value)
+StatusCode TextStyle::onAddedClean(CoreContext* context)
 {
-    if ((value & ComponentDirt::TextShape) == ComponentDirt::TextShape)
+    auto code = Super::onAddedClean(context);
+    if (code != StatusCode::Ok)
     {
-        rcp<Font> baseFont = m_fontAsset == nullptr ? nullptr : m_fontAsset->font();
-        if (m_variations.empty())
+        return code;
+    }
+    // This ensures context propagates to variation helper too.
+    if (!m_variations.empty())
+    {
+        m_variationHelper = rivestd::make_unique<TextVariationHelper>(this);
+    }
+    if (m_variationHelper != nullptr)
+    {
+        if ((code = m_variationHelper->onAddedDirty(context)) != StatusCode::Ok)
         {
-            m_font = baseFont;
+            return code;
         }
-        else
+        if ((code = m_variationHelper->onAddedClean(context)) != StatusCode::Ok)
         {
-            m_coords.clear();
-            for (TextStyleAxis* axis : m_variations)
-            {
-                m_coords.push_back({axis->tag(), axis->axisValue()});
-            }
-            m_font = baseFont->makeAtCoords(m_coords);
+            return code;
         }
     }
+    return StatusCode::Ok;
+}
+
+const rcp<Font> TextStyle::font() const
+{
+    if (m_variableFont != nullptr)
+    {
+        return m_variableFont;
+    }
+    return m_fontAsset == nullptr ? nullptr : m_fontAsset->font();
+}
+
+void TextStyle::updateVariableFont()
+{
+    rcp<Font> baseFont = m_fontAsset == nullptr ? nullptr : m_fontAsset->font();
+    if (baseFont == nullptr)
+    {
+        // Not ready yet.
+        return;
+    }
+    if (!m_variations.empty())
+    {
+        m_coords.clear();
+        for (TextStyleAxis* axis : m_variations)
+        {
+            m_coords.push_back({axis->tag(), axis->axisValue()});
+        }
+        m_variableFont = baseFont->makeAtCoords(m_coords);
+    }
+    else
+    {
+        m_variableFont = nullptr;
+    }
 }
 
 void TextStyle::buildDependencies()
 {
-    addDependent(parent());
-    artboard()->addDependent(this);
+    if (m_variationHelper != nullptr)
+    {
+        m_variationHelper->buildDependencies();
+    }
+    parent()->addDependent(this);
     Super::buildDependencies();
     auto factory = getArtboard()->factory();
     m_path = factory->makeEmptyRenderPath();