Text style variation at runtime.

Diffs=
3c396d3b8 Text style variation at runtime. (#5014)
diff --git a/.rive_head b/.rive_head
index 2e9b29e..be1933e 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-c0c8a45ae1cda84a64d0791a52f9c7ebbfceab7e
+3c396d3b8d94b6c628493797c4f2b83938879284
diff --git a/include/rive/component_dirt.hpp b/include/rive/component_dirt.hpp
index f0097e6..e1f5c1b 100644
--- a/include/rive/component_dirt.hpp
+++ b/include/rive/component_dirt.hpp
@@ -20,6 +20,9 @@
     /// Path is dirty and needs to be rebuilt.
     Path = 1 << 3,
 
+    /// TextShape is dirty and needs to be rebuilt.
+    TextShape = 1 << 3,
+
     /// Skin needs to recompute bone transformations.
     Skin = 1 << 3,
 
diff --git a/include/rive/text/text_style.hpp b/include/rive/text/text_style.hpp
index 05f950b..b8825b4 100644
--- a/include/rive/text/text_style.hpp
+++ b/include/rive/text/text_style.hpp
@@ -14,6 +14,7 @@
 class Renderer;
 class RenderPath;
 
+class TextStyleAxis;
 class TextStyle : public TextStyleBase, public ShapePaintContainer, public FileAssetReferencer
 {
 private:
@@ -22,7 +23,7 @@
 public:
     TextStyle();
     void buildDependencies() override;
-    const rcp<Font> font() const;
+    const rcp<Font> font() const { return m_font; }
     void assets(const std::vector<FileAsset*>& assets) override;
     StatusCode import(ImportStack& importStack) override;
 
@@ -30,14 +31,20 @@
     void rewindPath();
     void draw(Renderer* renderer);
     Core* clone() const override;
+    void addVariation(TextStyleAxis* axis);
+    void onDirty(ComponentDirt dirt) override;
+    void update(ComponentDirt value) override;
 
 protected:
     void fontSizeChanged() override;
 
 private:
+    rcp<Font> m_font;
     FontAsset* m_fontAsset = nullptr;
     std::unique_ptr<RenderPath> m_path;
     bool m_hasContents = false;
+    std::vector<Font::Coord> m_coords;
+    std::vector<TextStyleAxis*> m_variations;
 };
 } // namespace rive
 
diff --git a/include/rive/text/text_style_axis.hpp b/include/rive/text/text_style_axis.hpp
index 3a9084a..695df58 100644
--- a/include/rive/text/text_style_axis.hpp
+++ b/include/rive/text/text_style_axis.hpp
@@ -7,6 +7,9 @@
 class TextStyleAxis : public TextStyleAxisBase
 {
 public:
+    StatusCode onAddedDirty(CoreContext* context) override;
+    void tagChanged() override;
+    void axisValueChanged() override;
 };
 } // namespace rive
 
diff --git a/src/text/text_style.cpp b/src/text/text_style.cpp
index bb0b90b..d3af95a 100644
--- a/src/text/text_style.cpp
+++ b/src/text/text_style.cpp
@@ -1,4 +1,5 @@
 #include "rive/text/text_style.hpp"
+#include "rive/text/text_style_axis.hpp"
 #include "rive/renderer.hpp"
 #include "rive/shapes/paint/shape_paint.hpp"
 #include "rive/backboard.hpp"
@@ -12,8 +13,41 @@
 // satisfy unique_ptr
 TextStyle::TextStyle() {}
 
+void TextStyle::addVariation(TextStyleAxis* axis) { m_variations.push_back(axis); }
+
+void TextStyle::onDirty(ComponentDirt dirt)
+{
+    if ((dirt & ComponentDirt::TextShape) == ComponentDirt::TextShape)
+    {
+        parent()->as<Text>()->markShapeDirty();
+    }
+}
+
+void TextStyle::update(ComponentDirt value)
+{
+    if ((value & ComponentDirt::TextShape) == ComponentDirt::TextShape)
+    {
+        rcp<Font> baseFont = m_fontAsset == nullptr ? nullptr : m_fontAsset->font();
+        if (m_variations.empty())
+        {
+            m_font = baseFont;
+        }
+        else
+        {
+            m_coords.clear();
+            for (TextStyleAxis* axis : m_variations)
+            {
+                m_coords.push_back({axis->tag(), axis->axisValue()});
+            }
+            m_font = baseFont->makeAtCoords(m_coords);
+        }
+    }
+}
+
 void TextStyle::buildDependencies()
 {
+    addDependent(parent());
+    artboard()->addDependent(this);
     Super::buildDependencies();
     auto factory = getArtboard()->factory();
     m_path = factory->makeEmptyRenderPath();
@@ -48,7 +82,6 @@
 
 void TextStyle::assets(const std::vector<FileAsset*>& assets)
 {
-
     if ((size_t)fontAssetId() >= assets.size())
     {
         return;
@@ -60,11 +93,6 @@
     }
 }
 
-const rcp<Font> TextStyle::font() const
-{
-    return m_fontAsset == nullptr ? nullptr : m_fontAsset->font();
-}
-
 StatusCode TextStyle::import(ImportStack& importStack)
 {
     auto result = registerReferencer(importStack);
diff --git a/src/text/text_style_axis.cpp b/src/text/text_style_axis.cpp
new file mode 100644
index 0000000..4064578
--- /dev/null
+++ b/src/text/text_style_axis.cpp
@@ -0,0 +1,24 @@
+#include "rive/text/text_style_axis.hpp"
+#include "rive/text/text_style.hpp"
+#include "rive/container_component.hpp"
+
+using namespace rive;
+
+StatusCode TextStyleAxis::onAddedDirty(CoreContext* context)
+{
+    StatusCode code = Super::onAddedDirty(context);
+    if (code == StatusCode::Ok)
+    {
+        if (!parent()->is<TextStyle>())
+        {
+            return StatusCode::InvalidObject;
+        }
+        auto style = parent()->as<TextStyle>();
+        style->addVariation(this);
+    }
+    return code;
+}
+
+void TextStyleAxis::tagChanged() { parent()->addDirt(ComponentDirt::TextShape); }
+
+void TextStyleAxis::axisValueChanged() { parent()->addDirt(ComponentDirt::TextShape); }
\ No newline at end of file