feature: add data bind font support (#13031) 25b5033ad8

Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head
index da6eb28..892e121 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-ced1506e5bddd30e8d3939578f024e7d9c8b8409
+25b5033ad80758aeb3ad9acdf160a72ac35ef96b
diff --git a/dev/defs/text/text_style.json b/dev/defs/text/text_style.json
index 2daad8d..a281bc4 100644
--- a/dev/defs/text/text_style.json
+++ b/dev/defs/text/text_style.json
@@ -44,7 +44,8 @@
       "key": {
         "int": 279,
         "string": "fontassetid"
-      }
+      },
+      "bindable": true
     },
     "familyName": {
       "type": "String",
diff --git a/dev/defs/viewmodel/viewmodel_instance_asset_font.json b/dev/defs/viewmodel/viewmodel_instance_asset_font.json
new file mode 100644
index 0000000..ec76e39
--- /dev/null
+++ b/dev/defs/viewmodel/viewmodel_instance_asset_font.json
@@ -0,0 +1,8 @@
+{
+  "name": "ViewModelInstanceAssetFont",
+  "key": {
+    "int": 1035,
+    "string": "viewmodelinstanceassetfont"
+  },
+  "extends": "viewmodel/viewmodel_instance_asset.json"
+}
diff --git a/dev/defs/viewmodel/viewmodel_property_asset_font.json b/dev/defs/viewmodel/viewmodel_property_asset_font.json
new file mode 100644
index 0000000..8e8895d
--- /dev/null
+++ b/dev/defs/viewmodel/viewmodel_property_asset_font.json
@@ -0,0 +1,8 @@
+{
+  "name": "ViewModelPropertyAssetFont",
+  "key": {
+    "int": 1034,
+    "string": "viewmodelpropertyassetfont"
+  },
+  "extends": "viewmodel/viewmodel_property_asset.json"
+}
diff --git a/include/rive/data_bind/bindable_property_asset.hpp b/include/rive/data_bind/bindable_property_asset.hpp
index d759425..4908c3b 100644
--- a/include/rive/data_bind/bindable_property_asset.hpp
+++ b/include/rive/data_bind/bindable_property_asset.hpp
@@ -2,13 +2,22 @@
 #define _RIVE_BINDABLE_PROPERTY_ASSET_HPP_
 #include "rive/generated/data_bind/bindable_property_asset_base.hpp"
 #include "rive/assets/image_asset.hpp"
+#include "rive/assets/font_asset.hpp"
+#include "rive/text_engine.hpp"
 #include <stdio.h>
 namespace rive
 {
+// A single bindable asset property that can carry either a live image or a live
+// font. Only one is populated for a given bind (determined by the bound asset
+// kind); the other stays empty. The asset id is carried by the
+// BindablePropertyId base regardless of kind.
 class BindablePropertyAsset : public BindablePropertyAssetBase
 {
 public:
-    BindablePropertyAsset() : m_fileAsset(rcp<ImageAsset>(new ImageAsset())) {}
+    BindablePropertyAsset() :
+        m_fileAsset(rcp<ImageAsset>(new ImageAsset())),
+        m_fontAsset(rcp<FontAsset>(new FontAsset()))
+    {}
     constexpr static uint32_t defaultValue = -1;
     rcp<ImageAsset> fileAsset() { return m_fileAsset; }
     void imageValue(RenderImage* image)
@@ -16,9 +25,13 @@
         m_fileAsset->renderImage(ref_rcp(image));
     }
     RenderImage* imageValue() { return m_fileAsset->renderImage(); }
+    rcp<FontAsset> fontFileAsset() { return m_fontAsset; }
+    void fontValue(Font* font) { m_fontAsset->font(ref_rcp(font)); }
+    Font* fontValue() { return m_fontAsset->font().get(); }
 
 private:
     rcp<ImageAsset> m_fileAsset = nullptr;
+    rcp<FontAsset> m_fontAsset = nullptr;
 };
 } // namespace rive
 
diff --git a/include/rive/data_bind/context/context_value_asset_font.hpp b/include/rive/data_bind/context/context_value_asset_font.hpp
new file mode 100644
index 0000000..de276e6
--- /dev/null
+++ b/include/rive/data_bind/context/context_value_asset_font.hpp
@@ -0,0 +1,21 @@
+#ifndef _RIVE_DATA_BIND_CONTEXT_VALUE_ASSET_FONT_HPP_
+#define _RIVE_DATA_BIND_CONTEXT_VALUE_ASSET_FONT_HPP_
+#include "rive/data_bind/context/context_value.hpp"
+#include "rive/data_bind/data_values/data_value_asset_font.hpp"
+namespace rive
+{
+class FontAsset;
+class DataBindContextValueAssetFont : public DataBindContextValue
+{
+
+public:
+    DataBindContextValueAssetFont(DataBind* m_dataBind);
+    void apply(Core* component,
+               uint32_t propertyKey,
+               bool isMainDirection,
+               DataBind* dataBind) override;
+    rcp<FontAsset> fileAsset(DataBind* dataBind);
+};
+} // namespace rive
+
+#endif
diff --git a/include/rive/data_bind/data_values/data_type.hpp b/include/rive/data_bind/data_values/data_type.hpp
index 2e7debe..e8bf667 100644
--- a/include/rive/data_bind/data_values/data_type.hpp
+++ b/include/rive/data_bind/data_values/data_type.hpp
@@ -44,6 +44,9 @@
     /// Artboard.
     artboard = 12,
 
+    /// Asset Font.
+    assetFont = 13,
+
     /// Special case, this type is used to indicate it uses the input type.
     input = 99,
 
diff --git a/include/rive/data_bind/data_values/data_value_asset_font.hpp b/include/rive/data_bind/data_values/data_value_asset_font.hpp
new file mode 100644
index 0000000..74068df
--- /dev/null
+++ b/include/rive/data_bind/data_values/data_value_asset_font.hpp
@@ -0,0 +1,32 @@
+#ifndef _RIVE_DATA_VALUE_ASSET_FONT_HPP_
+#define _RIVE_DATA_VALUE_ASSET_FONT_HPP_
+#include "rive/data_bind/data_values/data_value_integer.hpp"
+#include "rive/assets/font_asset.hpp"
+#include "rive/text_engine.hpp"
+
+#include <iostream>
+namespace rive
+{
+class DataValueAssetFont : public DataValueInteger
+{
+public:
+    DataValueAssetFont(uint32_t value) :
+        DataValueInteger(value),
+        m_fileAsset(rcp<FontAsset>(new FontAsset())) {};
+    DataValueAssetFont() :
+        DataValueInteger(-1), m_fileAsset(rcp<FontAsset>(new FontAsset())) {};
+    static const DataType typeKey = DataType::assetFont;
+    bool isTypeOf(DataType typeKey) const override
+    {
+        return typeKey == DataType::assetFont || typeKey == DataType::integer;
+    };
+    constexpr static uint32_t defaultValue = -1;
+    rcp<FontAsset> fileAsset() { return m_fileAsset; }
+    void fontValue(Font* font) { m_fileAsset->font(ref_rcp(font)); }
+    Font* fontValue() { return m_fileAsset->font().get(); }
+
+private:
+    rcp<FontAsset> m_fileAsset = nullptr;
+};
+} // namespace rive
+#endif
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index 0a6b546..36e1fc9 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -306,6 +306,7 @@
 #include "rive/viewmodel/viewmodel_instance.hpp"
 #include "rive/viewmodel/viewmodel_instance_artboard.hpp"
 #include "rive/viewmodel/viewmodel_instance_asset.hpp"
+#include "rive/viewmodel/viewmodel_instance_asset_font.hpp"
 #include "rive/viewmodel/viewmodel_instance_asset_image.hpp"
 #include "rive/viewmodel/viewmodel_instance_boolean.hpp"
 #include "rive/viewmodel/viewmodel_instance_color.hpp"
@@ -322,6 +323,7 @@
 #include "rive/viewmodel/viewmodel_property.hpp"
 #include "rive/viewmodel/viewmodel_property_artboard.hpp"
 #include "rive/viewmodel/viewmodel_property_asset.hpp"
+#include "rive/viewmodel/viewmodel_property_asset_font.hpp"
 #include "rive/viewmodel/viewmodel_property_asset_image.hpp"
 #include "rive/viewmodel/viewmodel_property_boolean.hpp"
 #include "rive/viewmodel/viewmodel_property_color.hpp"
@@ -395,6 +397,8 @@
                 return new ViewModelPropertyColor();
             case ViewModelPropertyAssetImageBase::typeKey:
                 return new ViewModelPropertyAssetImage();
+            case ViewModelPropertyAssetFontBase::typeKey:
+                return new ViewModelPropertyAssetFont();
             case ViewModelInstanceBooleanBase::typeKey:
                 return new ViewModelInstanceBoolean();
             case ViewModelInstanceListBase::typeKey:
@@ -415,6 +419,8 @@
                 return new ViewModelInstanceAsset();
             case ViewModelInstanceAssetImageBase::typeKey:
                 return new ViewModelInstanceAssetImage();
+            case ViewModelInstanceAssetFontBase::typeKey:
+                return new ViewModelInstanceAssetFont();
             case DataEnumValueBase::typeKey:
                 return new DataEnumValue();
             case CustomPropertyTriggerBase::typeKey:
diff --git a/include/rive/generated/viewmodel/viewmodel_instance_asset_font_base.hpp b/include/rive/generated/viewmodel/viewmodel_instance_asset_font_base.hpp
new file mode 100644
index 0000000..34041ee
--- /dev/null
+++ b/include/rive/generated/viewmodel/viewmodel_instance_asset_font_base.hpp
@@ -0,0 +1,38 @@
+#ifndef _RIVE_VIEW_MODEL_INSTANCE_ASSET_FONT_BASE_HPP_
+#define _RIVE_VIEW_MODEL_INSTANCE_ASSET_FONT_BASE_HPP_
+#include "rive/viewmodel/viewmodel_instance_asset.hpp"
+namespace rive
+{
+class ViewModelInstanceAssetFontBase : public ViewModelInstanceAsset
+{
+protected:
+    typedef ViewModelInstanceAsset Super;
+
+public:
+    static const uint16_t typeKey = 1035;
+
+    /// Helper to quickly determine if a core object extends another without
+    /// RTTI at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case ViewModelInstanceAssetFontBase::typeKey:
+            case ViewModelInstanceAssetBase::typeKey:
+            case ViewModelInstanceValueBase::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/viewmodel/viewmodel_property_asset_font_base.hpp b/include/rive/generated/viewmodel/viewmodel_property_asset_font_base.hpp
new file mode 100644
index 0000000..76c2511
--- /dev/null
+++ b/include/rive/generated/viewmodel/viewmodel_property_asset_font_base.hpp
@@ -0,0 +1,38 @@
+#ifndef _RIVE_VIEW_MODEL_PROPERTY_ASSET_FONT_BASE_HPP_
+#define _RIVE_VIEW_MODEL_PROPERTY_ASSET_FONT_BASE_HPP_
+#include "rive/viewmodel/viewmodel_property_asset.hpp"
+namespace rive
+{
+class ViewModelPropertyAssetFontBase : public ViewModelPropertyAsset
+{
+protected:
+    typedef ViewModelPropertyAsset Super;
+
+public:
+    static const uint16_t typeKey = 1034;
+
+    /// Helper to quickly determine if a core object extends another without
+    /// RTTI at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case ViewModelPropertyAssetFontBase::typeKey:
+            case ViewModelPropertyAssetBase::typeKey:
+            case ViewModelPropertyBase::typeKey:
+            case ViewModelComponentBase::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/viewmodel/runtime/viewmodel_instance_asset_font_runtime.hpp b/include/rive/viewmodel/runtime/viewmodel_instance_asset_font_runtime.hpp
new file mode 100644
index 0000000..962d0a7
--- /dev/null
+++ b/include/rive/viewmodel/runtime/viewmodel_instance_asset_font_runtime.hpp
@@ -0,0 +1,28 @@
+#ifndef _RIVE_VIEW_MODEL_INSTANCE_ASSET_FONT_RUNTIME_HPP_
+#define _RIVE_VIEW_MODEL_INSTANCE_ASSET_FONT_RUNTIME_HPP_
+
+#include <string>
+#include <stdint.h>
+#include "rive/viewmodel/runtime/viewmodel_instance_value_runtime.hpp"
+#include "rive/viewmodel/viewmodel_instance_asset_font.hpp"
+
+namespace rive
+{
+
+class ViewModelInstanceAssetFontRuntime : public ViewModelInstanceValueRuntime
+{
+
+public:
+    ViewModelInstanceAssetFontRuntime(
+        ViewModelInstanceAssetFont* viewModelInstance) :
+        ViewModelInstanceValueRuntime(viewModelInstance)
+    {}
+    void value(Font* font);
+    const DataType dataType() override { return DataType::assetFont; }
+
+#ifdef TESTING
+    Font* testing_value();
+#endif
+};
+} // namespace rive
+#endif
diff --git a/include/rive/viewmodel/runtime/viewmodel_instance_runtime.hpp b/include/rive/viewmodel/runtime/viewmodel_instance_runtime.hpp
index 65ced02..3fe49c6 100644
--- a/include/rive/viewmodel/runtime/viewmodel_instance_runtime.hpp
+++ b/include/rive/viewmodel/runtime/viewmodel_instance_runtime.hpp
@@ -15,6 +15,7 @@
 #include "rive/viewmodel/runtime/viewmodel_instance_trigger_runtime.hpp"
 #include "rive/viewmodel/runtime/viewmodel_instance_list_runtime.hpp"
 #include "rive/viewmodel/runtime/viewmodel_instance_asset_image_runtime.hpp"
+#include "rive/viewmodel/runtime/viewmodel_instance_asset_font_runtime.hpp"
 #include "rive/viewmodel/runtime/viewmodel_instance_artboard_runtime.hpp"
 #include "rive/viewmodel/runtime/viewmodel_instance_list_index_runtime.hpp"
 #include "rive/refcnt.hpp"
@@ -51,6 +52,8 @@
         const std::string& path) const;
     ViewModelInstanceAssetImageRuntime* propertyImage(
         const std::string& path) const;
+    ViewModelInstanceAssetFontRuntime* propertyFont(
+        const std::string& path) const;
     ViewModelInstanceArtboardRuntime* propertyArtboard(
         const std::string& path) const;
     bool replaceViewModel(const std::string& path,
diff --git a/include/rive/viewmodel/viewmodel_instance_asset_font.hpp b/include/rive/viewmodel/viewmodel_instance_asset_font.hpp
new file mode 100644
index 0000000..78b5888
--- /dev/null
+++ b/include/rive/viewmodel/viewmodel_instance_asset_font.hpp
@@ -0,0 +1,28 @@
+#ifndef _RIVE_VIEW_MODEL_INSTANCE_ASSET_FONT_HPP_
+#define _RIVE_VIEW_MODEL_INSTANCE_ASSET_FONT_HPP_
+#include "rive/generated/viewmodel/viewmodel_instance_asset_font_base.hpp"
+#include "rive/text_engine.hpp"
+#include "rive/refcnt.hpp"
+#include "rive/data_bind/data_values/data_value_integer.hpp"
+#include "rive/assets/font_asset.hpp"
+#include <stdio.h>
+namespace rive
+{
+class ViewModelInstanceAssetFont : public ViewModelInstanceAssetFontBase
+{
+protected:
+    void propertyValueChanged() override;
+
+public:
+    ViewModelInstanceAssetFont();
+    void value(Font* font);
+    rcp<FontAsset> asset() { return m_fontAsset; }
+    Core* clone() const override;
+    void applyValue(DataValueInteger*);
+
+private:
+    rcp<FontAsset> m_fontAsset = nullptr;
+};
+} // namespace rive
+
+#endif
diff --git a/include/rive/viewmodel/viewmodel_property_asset_font.hpp b/include/rive/viewmodel/viewmodel_property_asset_font.hpp
new file mode 100644
index 0000000..18f9840
--- /dev/null
+++ b/include/rive/viewmodel/viewmodel_property_asset_font.hpp
@@ -0,0 +1,13 @@
+#ifndef _RIVE_VIEW_MODEL_PROPERTY_ASSET_FONT_HPP_
+#define _RIVE_VIEW_MODEL_PROPERTY_ASSET_FONT_HPP_
+#include "rive/generated/viewmodel/viewmodel_property_asset_font_base.hpp"
+#include <stdio.h>
+namespace rive
+{
+class ViewModelPropertyAssetFont : public ViewModelPropertyAssetFontBase
+{
+public:
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/src/data_bind/context/context_target_value.cpp b/src/data_bind/context/context_target_value.cpp
index 1230a97..17d092c 100644
--- a/src/data_bind/context/context_target_value.cpp
+++ b/src/data_bind/context/context_target_value.cpp
@@ -2,6 +2,8 @@
 #include "rive/data_bind/data_bind.hpp"
 #include "rive/data_bind/data_values/data_value.hpp"
 #include "rive/data_bind/data_values/data_value_asset_image.hpp"
+#include "rive/data_bind/data_values/data_value_asset_font.hpp"
+#include "rive/data_bind/bindable_property_asset.hpp"
 #include "rive/data_bind/data_values/data_value_boolean.hpp"
 #include "rive/data_bind/data_values/data_value_color.hpp"
 #include "rive/data_bind/data_values/data_value_integer.hpp"
@@ -62,6 +64,12 @@
             }
             else if (dataBind->source() != nullptr &&
                      dataBind->source()->coreType() ==
+                         ViewModelInstanceAssetFontBase::typeKey)
+            {
+                m_targetValue = new DataValueAssetFont();
+            }
+            else if (dataBind->source() != nullptr &&
+                     dataBind->source()->coreType() ==
                          ViewModelInstanceViewModelBase::typeKey)
             {
                 m_targetValue = new DataValueViewModel();
@@ -155,22 +163,39 @@
             else if (dataBind->target()->coreType() ==
                      BindablePropertyAssetBase::typeKey)
             {
+                auto bindableAsset =
+                    dataBind->target()->as<BindablePropertyAsset>();
                 auto value = CoreRegistry::getUint(dataBind->target(),
                                                    dataBind->propertyKey());
-                auto fileAsset = dataBind->target()
-                                     ->as<BindablePropertyAsset>()
-                                     ->fileAsset();
                 bool didChange = false;
                 if (updateValue<DataValueInteger, int>(value))
                 {
                     didChange = true;
                 }
-                if (fileAsset->renderImage() !=
-                    m_targetValue->as<DataValueAssetImage>()->imageValue())
+                // BindablePropertyAsset carries either a live image or a live
+                // font; sync whichever matches the target value's kind (the id
+                // above is always synced).
+                if (m_targetValue->is<DataValueAssetImage>())
                 {
-                    m_targetValue->as<DataValueAssetImage>()->imageValue(
-                        fileAsset->renderImage());
-                    didChange = true;
+                    auto image = bindableAsset->fileAsset()->renderImage();
+                    if (image !=
+                        m_targetValue->as<DataValueAssetImage>()->imageValue())
+                    {
+                        m_targetValue->as<DataValueAssetImage>()->imageValue(
+                            image);
+                        didChange = true;
+                    }
+                }
+                else if (m_targetValue->is<DataValueAssetFont>())
+                {
+                    auto font = bindableAsset->fontValue();
+                    if (font !=
+                        m_targetValue->as<DataValueAssetFont>()->fontValue())
+                    {
+                        m_targetValue->as<DataValueAssetFont>()->fontValue(
+                            font);
+                        didChange = true;
+                    }
                 }
                 return didChange;
             }
diff --git a/src/data_bind/context/context_value.cpp b/src/data_bind/context/context_value.cpp
index 3221065..6ec2b16 100644
--- a/src/data_bind/context/context_value.cpp
+++ b/src/data_bind/context/context_value.cpp
@@ -11,6 +11,7 @@
 #include "rive/data_bind/data_values/data_value_list.hpp"
 #include "rive/data_bind/data_values/data_value_symbol_list_index.hpp"
 #include "rive/data_bind/data_values/data_value_asset_image.hpp"
+#include "rive/data_bind/data_values/data_value_asset_font.hpp"
 #include "rive/data_bind/data_values/data_value_artboard.hpp"
 #include "rive/data_bind/data_values/data_value_viewmodel.hpp"
 #include "rive/generated/core_registry.hpp"
@@ -81,6 +82,10 @@
                 m_dataValue = new DataValueAssetImage(
                     source->as<ViewModelInstanceAssetImage>()->propertyValue());
                 break;
+            case ViewModelInstanceAssetFontBase::typeKey:
+                m_dataValue = new DataValueAssetFont(
+                    source->as<ViewModelInstanceAssetFont>()->propertyValue());
+                break;
             case ViewModelInstanceArtboardBase::typeKey:
                 m_dataValue = new DataValueArtboard(
                     source->as<ViewModelInstanceArtboard>()->propertyValue());
@@ -145,6 +150,10 @@
                 m_dataValue->as<DataValueAssetImage>()->value(
                     source->as<ViewModelInstanceAssetImage>()->propertyValue());
                 break;
+            case ViewModelInstanceAssetFontBase::typeKey:
+                m_dataValue->as<DataValueAssetFont>()->value(
+                    source->as<ViewModelInstanceAssetFont>()->propertyValue());
+                break;
             case ViewModelInstanceArtboardBase::typeKey:
                 m_dataValue->as<DataValueArtboard>()->value(
                     source->as<ViewModelInstanceArtboard>()->propertyValue());
@@ -232,6 +241,14 @@
                                                                 dataBind);
         }
         break;
+        case ViewModelInstanceAssetFontBase::typeKey:
+        {
+            calculateValueAndApply<DataValueInteger,
+                                   uint32_t,
+                                   ViewModelInstanceAssetFont>(isMainDirection,
+                                                               dataBind);
+        }
+        break;
         case ViewModelInstanceArtboardBase::typeKey:
         {
             calculateValueAndApply<DataValueInteger,
diff --git a/src/data_bind/context/context_value_asset_font.cpp b/src/data_bind/context/context_value_asset_font.cpp
new file mode 100644
index 0000000..830ea09
--- /dev/null
+++ b/src/data_bind/context/context_value_asset_font.cpp
@@ -0,0 +1,70 @@
+#include "rive/data_bind/context/context_value_asset_font.hpp"
+#include "rive/data_bind/data_values/data_value_asset_font.hpp"
+#include "rive/data_bind/bindable_property_asset.hpp"
+#include "rive/generated/core_registry.hpp"
+#include "rive/text/text_style.hpp"
+#include "rive/file.hpp"
+
+using namespace rive;
+
+DataBindContextValueAssetFont::DataBindContextValueAssetFont(
+    DataBind* dataBind) :
+    DataBindContextValue(dataBind)
+{}
+
+rcp<FontAsset> DataBindContextValueAssetFont::fileAsset(DataBind* dataBind)
+{
+    auto file = dataBind->file();
+    auto source = dataBind->source();
+    if (file != nullptr && source != nullptr &&
+        source->is<ViewModelInstanceAssetFont>())
+    {
+
+        auto asset = file->asset(
+            source->as<ViewModelInstanceAssetFont>()->propertyValue());
+        if (asset != nullptr && asset->is<FontAsset>())
+        {
+            return static_rcp_cast<FontAsset>(asset);
+        }
+    }
+    return nullptr;
+}
+
+void DataBindContextValueAssetFont::apply(Core* target,
+                                          uint32_t propertyKey,
+                                          bool isMainDirection,
+                                          DataBind* dataBind)
+{
+    if (target->is<TextStyle>())
+    {
+        auto asset = fileAsset(dataBind);
+        if (asset != nullptr)
+        {
+            target->as<TextStyle>()->setAsset(asset);
+        }
+        else
+        {
+            auto source = dataBind->source();
+            target->as<TextStyle>()->setAsset(
+                source->as<ViewModelInstanceAssetFont>()->asset());
+        }
+    }
+    else if (target->is<BindablePropertyAsset>())
+    {
+        auto source = dataBind->source();
+        target->as<BindablePropertyAsset>()->fontValue(
+            source->as<ViewModelInstanceAssetFont>()->asset()->font().get());
+        CoreRegistry::setUint(
+            target,
+            propertyKey,
+            source->as<ViewModelInstanceAssetFont>()->propertyValue());
+    }
+    else
+    {
+        auto source = dataBind->source();
+        CoreRegistry::setUint(
+            target,
+            propertyKey,
+            source->as<ViewModelInstanceAssetFont>()->propertyValue());
+    }
+}
diff --git a/src/data_bind/data_bind.cpp b/src/data_bind/data_bind.cpp
index 9f8a32c..0e1a76a 100644
--- a/src/data_bind/data_bind.cpp
+++ b/src/data_bind/data_bind.cpp
@@ -17,6 +17,7 @@
 #include "rive/data_bind/context/context_value.hpp"
 #include "rive/data_bind/context/context_value_any.hpp"
 #include "rive/data_bind/context/context_value_asset_image.hpp"
+#include "rive/data_bind/context/context_value_asset_font.hpp"
 #include "rive/data_bind/context/context_value_artboard.hpp"
 #include "rive/data_bind/context/context_value_boolean.hpp"
 #include "rive/data_bind/context/context_value_number.hpp"
@@ -195,6 +196,8 @@
                 return DataType::symbolListIndex;
             case ViewModelInstanceAssetImageBase::typeKey:
                 return DataType::assetImage;
+            case ViewModelInstanceAssetFontBase::typeKey:
+                return DataType::assetFont;
             case ViewModelInstanceArtboardBase::typeKey:
                 return DataType::artboard;
             case ViewModelInstanceViewModelBase::typeKey:
@@ -278,6 +281,9 @@
         case DataType::assetImage:
             m_ContextValue = new DataBindContextValueAssetImage(this);
             break;
+        case DataType::assetFont:
+            m_ContextValue = new DataBindContextValueAssetFont(this);
+            break;
         case DataType::artboard:
             m_ContextValue = new DataBindContextValueArtboard(this);
             break;
diff --git a/src/file.cpp b/src/file.cpp
index 9daec55..3d66d88 100644
--- a/src/file.cpp
+++ b/src/file.cpp
@@ -1177,6 +1177,9 @@
                 case ViewModelPropertyAssetImageBase::typeKey:
                     viewModelInstanceValue = new ViewModelInstanceAssetImage();
                     break;
+                case ViewModelPropertyAssetFontBase::typeKey:
+                    viewModelInstanceValue = new ViewModelInstanceAssetFont();
+                    break;
                 case ViewModelPropertySymbolListIndexBase::typeKey:
                     viewModelInstanceValue =
                         new ViewModelInstanceSymbolListIndex();
diff --git a/src/generated/viewmodel/viewmodel_instance_asset_font_base.cpp b/src/generated/viewmodel/viewmodel_instance_asset_font_base.cpp
new file mode 100644
index 0000000..1d19535
--- /dev/null
+++ b/src/generated/viewmodel/viewmodel_instance_asset_font_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/viewmodel/viewmodel_instance_asset_font_base.hpp"
+#include "rive/viewmodel/viewmodel_instance_asset_font.hpp"
+
+using namespace rive;
+
+Core* ViewModelInstanceAssetFontBase::clone() const
+{
+    auto cloned = new ViewModelInstanceAssetFont();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/generated/viewmodel/viewmodel_property_asset_font_base.cpp b/src/generated/viewmodel/viewmodel_property_asset_font_base.cpp
new file mode 100644
index 0000000..e12b2cf
--- /dev/null
+++ b/src/generated/viewmodel/viewmodel_property_asset_font_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/viewmodel/viewmodel_property_asset_font_base.hpp"
+#include "rive/viewmodel/viewmodel_property_asset_font.hpp"
+
+using namespace rive;
+
+Core* ViewModelPropertyAssetFontBase::clone() const
+{
+    auto cloned = new ViewModelPropertyAssetFont();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/text/text_style.cpp b/src/text/text_style.cpp
index 4d6f4e4..9f07621 100644
--- a/src/text/text_style.cpp
+++ b/src/text/text_style.cpp
@@ -139,9 +139,17 @@
 
 void TextStyle::setAsset(rcp<FileAsset> asset)
 {
-    if (asset->is<FontAsset>())
+    if (asset != nullptr && asset->is<FontAsset>())
     {
         FileAssetReferencer::setAsset(asset);
+        // Changing the referenced font at runtime (e.g. via data binding) must
+        // reshape the text. Mirror FontAsset::font()'s dirtying so onDirty
+        // marks the text shape dirty and rebuilds any variable font. Guard for
+        // the clone/import case where the style isn't wired to its Text yet.
+        if (m_text != nullptr)
+        {
+            addDirt(ComponentDirt::TextShape);
+        }
     }
 }
 
diff --git a/src/viewmodel/runtime/viewmodel_instance_asset_font_runtime.cpp b/src/viewmodel/runtime/viewmodel_instance_asset_font_runtime.cpp
new file mode 100644
index 0000000..8c85b68
--- /dev/null
+++ b/src/viewmodel/runtime/viewmodel_instance_asset_font_runtime.cpp
@@ -0,0 +1,20 @@
+
+#include "rive/viewmodel/runtime/viewmodel_instance_asset_font_runtime.hpp"
+
+// Default namespace for Rive Cpp code
+using namespace rive;
+
+void ViewModelInstanceAssetFontRuntime::value(Font* font)
+{
+    m_viewModelInstanceValue->as<ViewModelInstanceAssetFont>()->value(font);
+}
+
+#ifdef TESTING
+Font* ViewModelInstanceAssetFontRuntime::testing_value()
+{
+    return m_viewModelInstanceValue->as<ViewModelInstanceAssetFont>()
+        ->asset()
+        ->font()
+        .get();
+}
+#endif
diff --git a/src/viewmodel/runtime/viewmodel_instance_runtime.cpp b/src/viewmodel/runtime/viewmodel_instance_runtime.cpp
index 62c01d6..01e58b7 100644
--- a/src/viewmodel/runtime/viewmodel_instance_runtime.cpp
+++ b/src/viewmodel/runtime/viewmodel_instance_runtime.cpp
@@ -117,6 +117,9 @@
                     case DataType::assetImage:
                         return viewModelInstanceRuntime->propertyImage(
                             propertyName);
+                    case DataType::assetFont:
+                        return viewModelInstanceRuntime->propertyFont(
+                            propertyName);
                     case DataType::artboard:
                         return viewModelInstanceRuntime->propertyArtboard(
                             propertyName);
@@ -350,6 +353,22 @@
     return nullptr;
 }
 
+ViewModelInstanceAssetFontRuntime* ViewModelInstanceRuntime::propertyFont(
+    const std::string& path) const
+{
+    const auto propertyName = getPropertyNameFromPath(path);
+    auto viewModelInstance = viewModelInstanceFromFullPath(path);
+    if (viewModelInstance != nullptr)
+    {
+
+        return viewModelInstance
+            ->getPropertyInstance<ViewModelInstanceAssetFont,
+                                  ViewModelInstanceAssetFontRuntime>(
+                propertyName);
+    }
+    return nullptr;
+}
+
 ViewModelInstanceArtboardRuntime* ViewModelInstanceRuntime::propertyArtboard(
     const std::string& path) const
 {
diff --git a/src/viewmodel/runtime/viewmodel_runtime.cpp b/src/viewmodel/runtime/viewmodel_runtime.cpp
index d92500f..f111ae0 100644
--- a/src/viewmodel/runtime/viewmodel_runtime.cpp
+++ b/src/viewmodel/runtime/viewmodel_runtime.cpp
@@ -2,6 +2,7 @@
 #include "rive/viewmodel/runtime/viewmodel_runtime.hpp"
 #include "rive/viewmodel/viewmodel.hpp"
 #include "rive/viewmodel/viewmodel_property_asset_image.hpp"
+#include "rive/viewmodel/viewmodel_property_asset_font.hpp"
 #include "rive/viewmodel/viewmodel_property_string.hpp"
 #include "rive/viewmodel/viewmodel_property_number.hpp"
 #include "rive/viewmodel/viewmodel_property_boolean.hpp"
@@ -89,6 +90,9 @@
             case ViewModelPropertyAssetImage::typeKey:
                 type = DataType::assetImage;
                 break;
+            case ViewModelPropertyAssetFont::typeKey:
+                type = DataType::assetFont;
+                break;
             case ViewModelPropertyArtboard::typeKey:
                 type = DataType::artboard;
                 break;
diff --git a/src/viewmodel/viewmodel_instance_asset_font.cpp b/src/viewmodel/viewmodel_instance_asset_font.cpp
new file mode 100644
index 0000000..d53f9c5
--- /dev/null
+++ b/src/viewmodel/viewmodel_instance_asset_font.cpp
@@ -0,0 +1,87 @@
+#include <sstream>
+#include <iomanip>
+#include <array>
+
+#include "rive/viewmodel/viewmodel_instance_asset_font.hpp"
+#include "rive/component_dirt.hpp"
+#include "rive/refcnt.hpp"
+#include "rive/data_bind/data_values/data_value.hpp"
+#include "rive/data_bind/data_values/data_value_asset_font.hpp"
+
+using namespace rive;
+
+ViewModelInstanceAssetFont::ViewModelInstanceAssetFont() :
+    m_fontAsset(rcp<FontAsset>(new FontAsset()))
+{}
+
+void ViewModelInstanceAssetFont::propertyValueChanged()
+{
+    addDirt(ComponentDirt::Bindings);
+#ifdef WITH_RIVE_TOOLS
+    if (m_changedCallback != nullptr)
+    {
+        m_changedCallback(this, propertyValue());
+    }
+#endif
+    onValueChanged();
+}
+
+void ViewModelInstanceAssetFont::value(Font* font)
+{
+    if (m_fontAsset->font().get() == font)
+    {
+        propertyValue(-1);
+        return;
+    }
+#ifdef WITH_RIVE_TOOLS
+    const bool alreadySentinel = (propertyValue() == static_cast<uint32_t>(-1));
+#endif
+    if (font == nullptr)
+    {
+        m_fontAsset->font(nullptr);
+    }
+    else
+    {
+        font->ref();
+        m_fontAsset->font(rcp<Font>(font));
+    }
+#ifdef WITH_RIVE_TOOLS
+    if (!alreadySentinel)
+    {
+        propertyValue(-1);
+    }
+    else if (m_changedCallback != nullptr)
+    {
+        m_changedCallback(this, propertyValue());
+    }
+#else
+    propertyValue(-1);
+#endif
+    addDirt(ComponentDirt::Bindings);
+    onValueChanged();
+}
+
+void ViewModelInstanceAssetFont::applyValue(DataValueInteger* dataValue)
+{
+    if (dataValue && dataValue->is<DataValueAssetFont>())
+    {
+        auto font = dataValue->as<DataValueAssetFont>()->fontValue();
+        value(font);
+        if (font)
+        {
+            return;
+        }
+    }
+    propertyValue(dataValue->value());
+}
+
+Core* ViewModelInstanceAssetFont::clone() const
+{
+    auto cloned = new ViewModelInstanceAssetFont();
+    cloned->copy(*this);
+    for (const auto& asset : assets())
+    {
+        cloned->addAsset(asset);
+    }
+    return cloned;
+}
diff --git a/tests/unit_tests/assets/data_bind_font_test.riv b/tests/unit_tests/assets/data_bind_font_test.riv
new file mode 100644
index 0000000..60f8c19
--- /dev/null
+++ b/tests/unit_tests/assets/data_bind_font_test.riv
Binary files differ
diff --git a/tests/unit_tests/assets/kablammo.ttf b/tests/unit_tests/assets/kablammo.ttf
new file mode 100644
index 0000000..4f6604e
--- /dev/null
+++ b/tests/unit_tests/assets/kablammo.ttf
Binary files differ
diff --git a/tests/unit_tests/assets/nabla.ttf b/tests/unit_tests/assets/nabla.ttf
new file mode 100644
index 0000000..9bdc205
--- /dev/null
+++ b/tests/unit_tests/assets/nabla.ttf
Binary files differ
diff --git a/tests/unit_tests/runtime/data_binding_fonts_test.cpp b/tests/unit_tests/runtime/data_binding_fonts_test.cpp
new file mode 100644
index 0000000..6821592
--- /dev/null
+++ b/tests/unit_tests/runtime/data_binding_fonts_test.cpp
@@ -0,0 +1,119 @@
+#include <rive/file.hpp>
+#include <rive/node.hpp>
+#include <utils/no_op_renderer.hpp>
+#include <rive/viewmodel/viewmodel_instance_asset_font.hpp>
+#include <rive/viewmodel/viewmodel_instance_viewmodel.hpp>
+#include "rive/animation/state_machine_instance.hpp"
+#include "rive/assets/font_asset.hpp"
+#include "rive/nested_artboard.hpp"
+#include "rive/text/font_hb.hpp"
+#include "rive_file_reader.hpp"
+#include "utils/serializing_factory.hpp"
+#include <catch.hpp>
+#include <cstdio>
+#include <cstring>
+
+using namespace rive;
+
+TEST_CASE("Data bind font", "[silver]")
+{
+    SerializingFactory silver;
+    auto file = ReadRiveFile("assets/data_bind_font_test.riv", &silver);
+
+    auto artboard = file->artboardDefault();
+    REQUIRE(artboard != nullptr);
+
+    silver.frameSize(artboard->width(), artboard->height());
+
+    auto stateMachine = artboard->stateMachineAt(0);
+    REQUIRE(stateMachine != nullptr);
+
+    auto vmi = file->createDefaultViewModelInstance(artboard.get());
+    REQUIRE(vmi != nullptr);
+    auto renderer = silver.makeRenderer();
+    stateMachine->bindViewModelInstance(vmi);
+    stateMachine->advanceAndApply(0.0f);
+    artboard->draw(renderer.get());
+    silver.addFrame();
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+
+    silver.addFrame();
+
+    // Load the kablammo ttf through the file's factory and set it as the value
+    // of the bound ViewModelInstanceAssetFont. This should reshape the text
+    // with the new font on the next advance/draw.
+    auto fontBytes = ReadFile("assets/kablammo.ttf");
+    auto font = silver.decodeFont(fontBytes);
+    REQUIRE(font != nullptr);
+
+    auto fontProperty = vmi->propertyValue("fontProperty");
+    REQUIRE(fontProperty != nullptr);
+    REQUIRE(fontProperty->is<rive::ViewModelInstanceAssetFont>());
+    fontProperty->as<rive::ViewModelInstanceAssetFont>()->value(font.get());
+
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+    silver.addFrame();
+
+    // Click in a square that fires a listener
+    // that sets the font to another font value.
+    stateMachine->pointerDown(rive::Vec2D(490, 490));
+    stateMachine->pointerUp(rive::Vec2D(490, 490));
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+
+    silver.addFrame();
+    // Click in a square that fires a listener
+    // that sets the font to another font property.
+    stateMachine->pointerDown(rive::Vec2D(490, 20));
+    stateMachine->pointerUp(rive::Vec2D(490, 20));
+    stateMachine->advanceAndApply(0.016f);
+    artboard->draw(renderer.get());
+    CHECK(silver.matches("data_bind_font_test"));
+}
+
+// Deterministic (no golden) coverage of the ViewModelInstanceAssetFont value
+// API: assigning a decoded font stores it on the property's backing FontAsset,
+// and passing null clears it.
+TEST_CASE("Font data bind stores and clears the font on the property",
+          "[data binding]")
+{
+    auto file = ReadRiveFile("assets/data_bind_font_test.riv");
+
+    auto artboard = file->artboardDefault();
+    REQUIRE(artboard != nullptr);
+    auto stateMachine = artboard->stateMachineAt(0);
+    REQUIRE(stateMachine != nullptr);
+    auto vmi = file->createDefaultViewModelInstance(artboard.get());
+    REQUIRE(vmi != nullptr);
+    stateMachine->bindViewModelInstance(vmi);
+    stateMachine->advanceAndApply(0.0f);
+
+    auto property = vmi->propertyValue("fontProperty");
+    REQUIRE(property != nullptr);
+    REQUIRE(property->is<rive::ViewModelInstanceAssetFont>());
+    auto fontProperty = property->as<rive::ViewModelInstanceAssetFont>();
+    REQUIRE(fontProperty->asset() != nullptr);
+
+    // Assigning a decoded font stores it on the property's backing FontAsset.
+    auto fontBytes = ReadFile("assets/kablammo.ttf");
+    auto font = HBFont::Decode(fontBytes);
+    REQUIRE(font != nullptr);
+    fontProperty->value(font.get());
+    stateMachine->advanceAndApply(0.0f);
+    CHECK(fontProperty->asset()->font().get() == font.get());
+
+    // Swapping to a different font updates the backing FontAsset.
+    auto font2Bytes = ReadFile("assets/nabla.ttf");
+    auto font2 = HBFont::Decode(font2Bytes);
+    REQUIRE(font2 != nullptr);
+    fontProperty->value(font2.get());
+    stateMachine->advanceAndApply(0.0f);
+    CHECK(fontProperty->asset()->font().get() == font2.get());
+
+    // Passing null clears the backing font.
+    fontProperty->value(nullptr);
+    stateMachine->advanceAndApply(0.0f);
+    CHECK(fontProperty->asset()->font() == nullptr);
+}
\ No newline at end of file
diff --git a/tests/unit_tests/silvers/data_bind_font_test.sriv b/tests/unit_tests/silvers/data_bind_font_test.sriv
new file mode 100644
index 0000000..e4c1d58
--- /dev/null
+++ b/tests/unit_tests/silvers/data_bind_font_test.sriv
Binary files differ