add arithmetic operation and group converters

two new converters:
- arithmetic operation, that applies a basic arithmetic operation to an input
- group converter: this is a special converter that is used to apply a sequence of converters to a data bound property.

https://github.com/user-attachments/assets/50a0226c-343f-4252-923b-4f0c51ca6c8b

Diffs=
ad34dd4da add arithmetic operation and group converters (#7801)

Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head
index 7dcbdef..41d3f18 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-f99c93181b2073db07b0c54f766648ce56c89df9
+ad34dd4dae54aa071ca80c457e375c015b9497a8
diff --git a/dev/defs/data_bind/converters/data_converter_group.json b/dev/defs/data_bind/converters/data_converter_group.json
new file mode 100644
index 0000000..acc05f8
--- /dev/null
+++ b/dev/defs/data_bind/converters/data_converter_group.json
@@ -0,0 +1,8 @@
+{
+  "name": "DataConverterGroup",
+  "key": {
+    "int": 499,
+    "string": "dataconvertergroup"
+  },
+  "extends": "data_bind/converters/data_converter.json"
+}
\ No newline at end of file
diff --git a/dev/defs/data_bind/converters/data_converter_group_item.json b/dev/defs/data_bind/converters/data_converter_group_item.json
new file mode 100644
index 0000000..60e95ec
--- /dev/null
+++ b/dev/defs/data_bind/converters/data_converter_group_item.json
@@ -0,0 +1,39 @@
+{
+  "name": "DataConverterGroupItem",
+  "key": {
+    "int": 498,
+    "string": "dataconvertergroupitem"
+  },
+  "properties": {
+    "order": {
+      "type": "FractionalIndex",
+      "initialValue": "FractionalIndex.invalid",
+      "key": {
+        "int": 678,
+        "string": "order"
+      },
+      "runtime": false
+    },
+    "converterId": {
+      "type": "Id",
+      "typeRuntime": "uint",
+      "initialValue": "Core.missingId",
+      "initialValueRuntime": "-1",
+      "key": {
+        "int": 679,
+        "string": "converterid"
+      },
+      "description": "Id of the converter"
+    },
+    "groupId": {
+      "type": "Id",
+      "initialValue": "Core.missingId",
+      "key": {
+        "int": 680,
+        "string": "groupid"
+      },
+      "description": "Id of the group this item belongs to",
+      "runtime": false
+    }
+  }
+}
\ No newline at end of file
diff --git a/dev/defs/data_bind/converters/data_converter_operation.json b/dev/defs/data_bind/converters/data_converter_operation.json
new file mode 100644
index 0000000..1a92179
--- /dev/null
+++ b/dev/defs/data_bind/converters/data_converter_operation.json
@@ -0,0 +1,28 @@
+{
+  "name": "DataConverterOperation",
+  "key": {
+    "int": 500,
+    "string": "dataconverteroperation"
+  },
+  "extends": "data_bind/converters/data_converter.json",
+  "properties": {
+    "value": {
+      "type": "double",
+      "initialValue": "1",
+      "key": {
+        "int": 681,
+        "string": "value"
+      },
+      "description": "Number to multiply the input to"
+    },
+    "operationType": {
+      "type": "uint",
+      "initialValue": "0",
+      "key": {
+        "int": 682,
+        "string": "operationtype"
+      },
+      "description": "The operation tu use for the input and value"
+    }
+  }
+}
\ No newline at end of file
diff --git a/include/rive/animation/arithmetic_operation.hpp b/include/rive/animation/arithmetic_operation.hpp
new file mode 100644
index 0000000..1870b8d
--- /dev/null
+++ b/include/rive/animation/arithmetic_operation.hpp
@@ -0,0 +1,16 @@
+#ifndef _RIVE_ARITHMETIC_OPERATION_HPP_
+#define _RIVE_ARITHMETIC_OPERATION_HPP_
+
+namespace rive
+{
+enum class ArithmeticOperation : int
+{
+    add = 0,
+    subtract = 1,
+    multiply = 2,
+    divide = 3,
+    modulo = 4,
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/converters/data_converter_group.hpp b/include/rive/data_bind/converters/data_converter_group.hpp
new file mode 100644
index 0000000..4056085
--- /dev/null
+++ b/include/rive/data_bind/converters/data_converter_group.hpp
@@ -0,0 +1,28 @@
+#ifndef _RIVE_DATA_CONVERTER_GROUP_HPP_
+#define _RIVE_DATA_CONVERTER_GROUP_HPP_
+#include "rive/generated/data_bind/converters/data_converter_group_base.hpp"
+#include "rive/data_bind/converters/data_converter_group_item.hpp"
+#include <stdio.h>
+namespace rive
+{
+class DataConverterGroup : public DataConverterGroupBase
+{
+public:
+    DataValue* convert(DataValue* value) override;
+    DataValue* reverseConvert(DataValue* value) override;
+    void addItem(DataConverterGroupItem* item);
+    DataType outputType() override
+    {
+        if (m_items.size() > 0)
+        {
+            return m_items.back()->converter()->outputType();
+        };
+        return Super::outputType();
+    }
+
+private:
+    std::vector<DataConverterGroupItem*> m_items;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/converters/data_converter_group_item.hpp b/include/rive/data_bind/converters/data_converter_group_item.hpp
new file mode 100644
index 0000000..0a6202d
--- /dev/null
+++ b/include/rive/data_bind/converters/data_converter_group_item.hpp
@@ -0,0 +1,20 @@
+#ifndef _RIVE_DATA_CONVERTER_GROUP_ITEM_HPP_
+#define _RIVE_DATA_CONVERTER_GROUP_ITEM_HPP_
+#include "rive/generated/data_bind/converters/data_converter_group_item_base.hpp"
+#include "rive/data_bind/converters/data_converter.hpp"
+#include <stdio.h>
+namespace rive
+{
+class DataConverterGroupItem : public DataConverterGroupItemBase
+{
+public:
+    StatusCode import(ImportStack& importStack) override;
+    DataConverter* converter() const { return m_dataConverter; };
+    void converter(DataConverter* value) { m_dataConverter = value; };
+
+protected:
+    DataConverter* m_dataConverter;
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/data_bind/converters/data_converter_operation.hpp b/include/rive/data_bind/converters/data_converter_operation.hpp
new file mode 100644
index 0000000..9bbb2b4
--- /dev/null
+++ b/include/rive/data_bind/converters/data_converter_operation.hpp
@@ -0,0 +1,18 @@
+#ifndef _RIVE_DATA_CONVERTER_OPERATION_HPP_
+#define _RIVE_DATA_CONVERTER_OPERATION_HPP_
+#include "rive/generated/data_bind/converters/data_converter_operation_base.hpp"
+#include "rive/animation/arithmetic_operation.hpp"
+#include <stdio.h>
+namespace rive
+{
+class DataConverterOperation : public DataConverterOperationBase
+{
+public:
+    DataValue* convert(DataValue* value) override;
+    DataValue* reverseConvert(DataValue* value) override;
+    DataType outputType() override { return DataType::number; };
+    ArithmeticOperation op() const { return (ArithmeticOperation)operationType(); }
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp
index 77e4251..bb2d399 100644
--- a/include/rive/generated/core_registry.hpp
+++ b/include/rive/generated/core_registry.hpp
@@ -121,6 +121,9 @@
 #include "rive/data_bind/bindable_property_number.hpp"
 #include "rive/data_bind/bindable_property_string.hpp"
 #include "rive/data_bind/converters/data_converter.hpp"
+#include "rive/data_bind/converters/data_converter_group.hpp"
+#include "rive/data_bind/converters/data_converter_group_item.hpp"
+#include "rive/data_bind/converters/data_converter_operation.hpp"
 #include "rive/data_bind/converters/data_converter_rounder.hpp"
 #include "rive/data_bind/converters/data_converter_to_string.hpp"
 #include "rive/data_bind/data_bind.hpp"
@@ -472,8 +475,14 @@
                 return new BindablePropertyBoolean();
             case DataBindBase::typeKey:
                 return new DataBind();
+            case DataConverterGroupItemBase::typeKey:
+                return new DataConverterGroupItem();
+            case DataConverterGroupBase::typeKey:
+                return new DataConverterGroup();
             case DataConverterRounderBase::typeKey:
                 return new DataConverterRounder();
+            case DataConverterOperationBase::typeKey:
+                return new DataConverterOperation();
             case DataConverterToStringBase::typeKey:
                 return new DataConverterToString();
             case DataBindContextBase::typeKey:
@@ -1047,9 +1056,15 @@
             case DataBindBase::converterIdPropertyKey:
                 object->as<DataBindBase>()->converterId(value);
                 break;
+            case DataConverterGroupItemBase::converterIdPropertyKey:
+                object->as<DataConverterGroupItemBase>()->converterId(value);
+                break;
             case DataConverterRounderBase::decimalsPropertyKey:
                 object->as<DataConverterRounderBase>()->decimals(value);
                 break;
+            case DataConverterOperationBase::operationTypePropertyKey:
+                object->as<DataConverterOperationBase>()->operationType(value);
+                break;
             case BindablePropertyEnumBase::propertyValuePropertyKey:
                 object->as<BindablePropertyEnumBase>()->propertyValue(value);
                 break;
@@ -1589,6 +1604,9 @@
             case JoystickBase::heightPropertyKey:
                 object->as<JoystickBase>()->height(value);
                 break;
+            case DataConverterOperationBase::valuePropertyKey:
+                object->as<DataConverterOperationBase>()->value(value);
+                break;
             case BindablePropertyNumberBase::propertyValuePropertyKey:
                 object->as<BindablePropertyNumberBase>()->propertyValue(value);
                 break;
@@ -2085,8 +2103,12 @@
                 return object->as<DataBindBase>()->flags();
             case DataBindBase::converterIdPropertyKey:
                 return object->as<DataBindBase>()->converterId();
+            case DataConverterGroupItemBase::converterIdPropertyKey:
+                return object->as<DataConverterGroupItemBase>()->converterId();
             case DataConverterRounderBase::decimalsPropertyKey:
                 return object->as<DataConverterRounderBase>()->decimals();
+            case DataConverterOperationBase::operationTypePropertyKey:
+                return object->as<DataConverterOperationBase>()->operationType();
             case BindablePropertyEnumBase::propertyValuePropertyKey:
                 return object->as<BindablePropertyEnumBase>()->propertyValue();
             case NestedArtboardLeafBase::fitPropertyKey:
@@ -2456,6 +2478,8 @@
                 return object->as<JoystickBase>()->width();
             case JoystickBase::heightPropertyKey:
                 return object->as<JoystickBase>()->height();
+            case DataConverterOperationBase::valuePropertyKey:
+                return object->as<DataConverterOperationBase>()->value();
             case BindablePropertyNumberBase::propertyValuePropertyKey:
                 return object->as<BindablePropertyNumberBase>()->propertyValue();
             case NestedArtboardLeafBase::alignmentXPropertyKey:
@@ -2722,7 +2746,9 @@
             case DataBindBase::propertyKeyPropertyKey:
             case DataBindBase::flagsPropertyKey:
             case DataBindBase::converterIdPropertyKey:
+            case DataConverterGroupItemBase::converterIdPropertyKey:
             case DataConverterRounderBase::decimalsPropertyKey:
+            case DataConverterOperationBase::operationTypePropertyKey:
             case BindablePropertyEnumBase::propertyValuePropertyKey:
             case NestedArtboardLeafBase::fitPropertyKey:
             case WeightBase::valuesPropertyKey:
@@ -2901,6 +2927,7 @@
             case JoystickBase::originYPropertyKey:
             case JoystickBase::widthPropertyKey:
             case JoystickBase::heightPropertyKey:
+            case DataConverterOperationBase::valuePropertyKey:
             case BindablePropertyNumberBase::propertyValuePropertyKey:
             case NestedArtboardLeafBase::alignmentXPropertyKey:
             case NestedArtboardLeafBase::alignmentYPropertyKey:
@@ -3308,8 +3335,12 @@
                 return object->is<DataBindBase>();
             case DataBindBase::converterIdPropertyKey:
                 return object->is<DataBindBase>();
+            case DataConverterGroupItemBase::converterIdPropertyKey:
+                return object->is<DataConverterGroupItemBase>();
             case DataConverterRounderBase::decimalsPropertyKey:
                 return object->is<DataConverterRounderBase>();
+            case DataConverterOperationBase::operationTypePropertyKey:
+                return object->is<DataConverterOperationBase>();
             case BindablePropertyEnumBase::propertyValuePropertyKey:
                 return object->is<BindablePropertyEnumBase>();
             case NestedArtboardLeafBase::fitPropertyKey:
@@ -3658,6 +3689,8 @@
                 return object->is<JoystickBase>();
             case JoystickBase::heightPropertyKey:
                 return object->is<JoystickBase>();
+            case DataConverterOperationBase::valuePropertyKey:
+                return object->is<DataConverterOperationBase>();
             case BindablePropertyNumberBase::propertyValuePropertyKey:
                 return object->is<BindablePropertyNumberBase>();
             case NestedArtboardLeafBase::alignmentXPropertyKey:
diff --git a/include/rive/generated/data_bind/converters/data_converter_group_base.hpp b/include/rive/generated/data_bind/converters/data_converter_group_base.hpp
new file mode 100644
index 0000000..32377ba
--- /dev/null
+++ b/include/rive/generated/data_bind/converters/data_converter_group_base.hpp
@@ -0,0 +1,36 @@
+#ifndef _RIVE_DATA_CONVERTER_GROUP_BASE_HPP_
+#define _RIVE_DATA_CONVERTER_GROUP_BASE_HPP_
+#include "rive/data_bind/converters/data_converter.hpp"
+namespace rive
+{
+class DataConverterGroupBase : public DataConverter
+{
+protected:
+    typedef DataConverter Super;
+
+public:
+    static const uint16_t typeKey = 499;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case DataConverterGroupBase::typeKey:
+            case DataConverterBase::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/data_bind/converters/data_converter_group_item_base.hpp b/include/rive/generated/data_bind/converters/data_converter_group_item_base.hpp
new file mode 100644
index 0000000..2f51281
--- /dev/null
+++ b/include/rive/generated/data_bind/converters/data_converter_group_item_base.hpp
@@ -0,0 +1,66 @@
+#ifndef _RIVE_DATA_CONVERTER_GROUP_ITEM_BASE_HPP_
+#define _RIVE_DATA_CONVERTER_GROUP_ITEM_BASE_HPP_
+#include "rive/core.hpp"
+#include "rive/core/field_types/core_uint_type.hpp"
+namespace rive
+{
+class DataConverterGroupItemBase : public Core
+{
+protected:
+    typedef Core Super;
+
+public:
+    static const uint16_t typeKey = 498;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case DataConverterGroupItemBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t converterIdPropertyKey = 679;
+
+private:
+    uint32_t m_ConverterId = -1;
+
+public:
+    inline uint32_t converterId() const { return m_ConverterId; }
+    void converterId(uint32_t value)
+    {
+        if (m_ConverterId == value)
+        {
+            return;
+        }
+        m_ConverterId = value;
+        converterIdChanged();
+    }
+
+    Core* clone() const override;
+    void copy(const DataConverterGroupItemBase& object) { m_ConverterId = object.m_ConverterId; }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case converterIdPropertyKey:
+                m_ConverterId = CoreUintType::deserialize(reader);
+                return true;
+        }
+        return false;
+    }
+
+protected:
+    virtual void converterIdChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/generated/data_bind/converters/data_converter_operation_base.hpp b/include/rive/generated/data_bind/converters/data_converter_operation_base.hpp
new file mode 100644
index 0000000..1291318
--- /dev/null
+++ b/include/rive/generated/data_bind/converters/data_converter_operation_base.hpp
@@ -0,0 +1,90 @@
+#ifndef _RIVE_DATA_CONVERTER_OPERATION_BASE_HPP_
+#define _RIVE_DATA_CONVERTER_OPERATION_BASE_HPP_
+#include "rive/core/field_types/core_double_type.hpp"
+#include "rive/core/field_types/core_uint_type.hpp"
+#include "rive/data_bind/converters/data_converter.hpp"
+namespace rive
+{
+class DataConverterOperationBase : public DataConverter
+{
+protected:
+    typedef DataConverter Super;
+
+public:
+    static const uint16_t typeKey = 500;
+
+    /// Helper to quickly determine if a core object extends another without RTTI
+    /// at runtime.
+    bool isTypeOf(uint16_t typeKey) const override
+    {
+        switch (typeKey)
+        {
+            case DataConverterOperationBase::typeKey:
+            case DataConverterBase::typeKey:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    uint16_t coreType() const override { return typeKey; }
+
+    static const uint16_t valuePropertyKey = 681;
+    static const uint16_t operationTypePropertyKey = 682;
+
+private:
+    float m_Value = 1.0f;
+    uint32_t m_OperationType = 0;
+
+public:
+    inline float value() const { return m_Value; }
+    void value(float value)
+    {
+        if (m_Value == value)
+        {
+            return;
+        }
+        m_Value = value;
+        valueChanged();
+    }
+
+    inline uint32_t operationType() const { return m_OperationType; }
+    void operationType(uint32_t value)
+    {
+        if (m_OperationType == value)
+        {
+            return;
+        }
+        m_OperationType = value;
+        operationTypeChanged();
+    }
+
+    Core* clone() const override;
+    void copy(const DataConverterOperationBase& object)
+    {
+        m_Value = object.m_Value;
+        m_OperationType = object.m_OperationType;
+        DataConverter::copy(object);
+    }
+
+    bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
+    {
+        switch (propertyKey)
+        {
+            case valuePropertyKey:
+                m_Value = CoreDoubleType::deserialize(reader);
+                return true;
+            case operationTypePropertyKey:
+                m_OperationType = CoreUintType::deserialize(reader);
+                return true;
+        }
+        return DataConverter::deserialize(propertyKey, reader);
+    }
+
+protected:
+    virtual void valueChanged() {}
+    virtual void operationTypeChanged() {}
+};
+} // namespace rive
+
+#endif
\ No newline at end of file
diff --git a/include/rive/importers/backboard_importer.hpp b/include/rive/importers/backboard_importer.hpp
index c92454f..cb2f971 100644
--- a/include/rive/importers/backboard_importer.hpp
+++ b/include/rive/importers/backboard_importer.hpp
@@ -14,6 +14,7 @@
 class FileAssetReferencer;
 class DataConverter;
 class DataBind;
+class DataConverterGroupItem;
 class BackboardImporter : public ImportStackObject
 {
 private:
@@ -24,6 +25,7 @@
     std::vector<FileAssetReferencer*> m_FileAssetReferencers;
     std::vector<DataConverter*> m_DataConverters;
     std::vector<DataBind*> m_DataConverterReferencers;
+    std::vector<DataConverterGroupItem*> m_DataConverterGroupItemReferencers;
     int m_NextArtboardId;
 
 public:
@@ -35,6 +37,7 @@
     void addFileAssetReferencer(FileAssetReferencer* referencer);
     void addDataConverterReferencer(DataBind* referencer);
     void addDataConverter(DataConverter* converter);
+    void addDataConverterGroupItemReferencer(DataConverterGroupItem* referencer);
 
     StatusCode resolve() override;
     const Backboard* backboard() const { return m_Backboard; }
diff --git a/include/rive/importers/data_converter_group_importer.hpp b/include/rive/importers/data_converter_group_importer.hpp
new file mode 100644
index 0000000..61ba8ab
--- /dev/null
+++ b/include/rive/importers/data_converter_group_importer.hpp
@@ -0,0 +1,20 @@
+#ifndef _RIVE_DATA_CONVERTER_GROUP_IMPORTER_HPP_
+#define _RIVE_DATA_CONVERTER_GROUP_IMPORTER_HPP_
+
+#include "rive/importers/import_stack.hpp"
+
+namespace rive
+{
+class Core;
+class DataConverterGroup;
+class DataConverterGroupImporter : public ImportStackObject
+{
+private:
+    DataConverterGroup* m_dataConverterGroup;
+
+public:
+    DataConverterGroupImporter(DataConverterGroup* group);
+    DataConverterGroup* group() { return m_dataConverterGroup; }
+};
+} // namespace rive
+#endif
\ No newline at end of file
diff --git a/src/data_bind/converters/data_converter_group.cpp b/src/data_bind/converters/data_converter_group.cpp
new file mode 100644
index 0000000..aab150f
--- /dev/null
+++ b/src/data_bind/converters/data_converter_group.cpp
@@ -0,0 +1,28 @@
+#include "rive/math/math_types.hpp"
+#include "rive/data_bind/converters/data_converter_group.hpp"
+#include "rive/data_bind/data_values/data_value_number.hpp"
+#include "rive/data_bind/data_values/data_value_string.hpp"
+
+using namespace rive;
+
+void DataConverterGroup::addItem(DataConverterGroupItem* item) { m_items.push_back(item); }
+
+DataValue* DataConverterGroup::convert(DataValue* input)
+{
+    DataValue* value = input;
+    for (auto item : m_items)
+    {
+        value = item->converter()->convert(value);
+    }
+    return value;
+}
+
+DataValue* DataConverterGroup::reverseConvert(DataValue* input)
+{
+    DataValue* value = input;
+    for (auto it = m_items.rbegin(); it != m_items.rend(); ++it)
+    {
+        value = (*it)->converter()->reverseConvert(value);
+    }
+    return value;
+}
\ No newline at end of file
diff --git a/src/data_bind/converters/data_converter_group_item.cpp b/src/data_bind/converters/data_converter_group_item.cpp
new file mode 100644
index 0000000..3a6fa65
--- /dev/null
+++ b/src/data_bind/converters/data_converter_group_item.cpp
@@ -0,0 +1,26 @@
+#include "rive/backboard.hpp"
+#include "rive/math/math_types.hpp"
+#include "rive/data_bind/converters/data_converter_group_item.hpp"
+#include "rive/data_bind/converters/data_converter_group.hpp"
+#include "rive/importers/data_converter_group_importer.hpp"
+#include "rive/importers/backboard_importer.hpp"
+
+using namespace rive;
+
+StatusCode DataConverterGroupItem::import(ImportStack& importStack)
+{
+    auto backboardImporter = importStack.latest<BackboardImporter>(Backboard::typeKey);
+    if (backboardImporter == nullptr)
+    {
+        return StatusCode::MissingObject;
+    }
+    backboardImporter->addDataConverterGroupItemReferencer(this);
+    auto dataConveterGroupImporter =
+        importStack.latest<DataConverterGroupImporter>(DataConverterGroupBase::typeKey);
+    if (dataConveterGroupImporter == nullptr)
+    {
+        return StatusCode::MissingObject;
+    }
+    dataConveterGroupImporter->group()->addItem(this);
+    return Super::import(importStack);
+}
\ No newline at end of file
diff --git a/src/data_bind/converters/data_converter_operation.cpp b/src/data_bind/converters/data_converter_operation.cpp
new file mode 100644
index 0000000..dfb43d8
--- /dev/null
+++ b/src/data_bind/converters/data_converter_operation.cpp
@@ -0,0 +1,65 @@
+#include "rive/math/math_types.hpp"
+#include "rive/data_bind/converters/data_converter_operation.hpp"
+#include "rive/data_bind/data_values/data_value_number.hpp"
+
+using namespace rive;
+
+DataValue* DataConverterOperation::convert(DataValue* input)
+{
+    auto output = new DataValueNumber();
+    if (input->is<DataValueNumber>())
+    {
+        float inputValue = input->as<DataValueNumber>()->value();
+        float resultValue = value();
+        switch (op())
+        {
+            case ArithmeticOperation::add:
+                resultValue = inputValue + resultValue;
+                break;
+            case ArithmeticOperation::subtract:
+                resultValue = inputValue - resultValue;
+                break;
+            case ArithmeticOperation::multiply:
+                resultValue = inputValue * resultValue;
+                break;
+            case ArithmeticOperation::divide:
+                resultValue = inputValue / resultValue;
+                break;
+            case ArithmeticOperation::modulo:
+                resultValue = fmodf(inputValue, resultValue);
+                break;
+        }
+        output->value(resultValue);
+    }
+    return output;
+}
+
+DataValue* DataConverterOperation::reverseConvert(DataValue* input)
+{
+    auto output = new DataValueNumber();
+    if (input->is<DataValueNumber>())
+    {
+        float inputValue = input->as<DataValueNumber>()->value();
+        float resultValue = value();
+        switch (op())
+        {
+            case ArithmeticOperation::add:
+                resultValue = inputValue - resultValue;
+                break;
+            case ArithmeticOperation::subtract:
+                resultValue = inputValue + resultValue;
+                break;
+            case ArithmeticOperation::multiply:
+                resultValue = inputValue / resultValue;
+                break;
+            case ArithmeticOperation::divide:
+                resultValue = inputValue * resultValue;
+                break;
+                // No reverse operation for modulo
+            case ArithmeticOperation::modulo:
+                break;
+        }
+        output->value(resultValue);
+    }
+    return output;
+}
\ No newline at end of file
diff --git a/src/file.cpp b/src/file.cpp
index 100969e..db3e98f 100644
--- a/src/file.cpp
+++ b/src/file.cpp
@@ -9,6 +9,7 @@
 #include "rive/importers/artboard_importer.hpp"
 #include "rive/importers/backboard_importer.hpp"
 #include "rive/importers/bindable_property_importer.hpp"
+#include "rive/importers/data_converter_group_importer.hpp"
 #include "rive/importers/enum_importer.hpp"
 #include "rive/importers/file_asset_importer.hpp"
 #include "rive/importers/import_stack.hpp"
@@ -39,6 +40,7 @@
 #include "rive/data_bind/bindable_property_color.hpp"
 #include "rive/data_bind/bindable_property_enum.hpp"
 #include "rive/data_bind/bindable_property_boolean.hpp"
+#include "rive/data_bind/converters/data_converter_group.hpp"
 #include "rive/assets/file_asset.hpp"
 #include "rive/assets/audio_asset.hpp"
 #include "rive/assets/file_asset_contents.hpp"
@@ -389,6 +391,11 @@
                     rivestd::make_unique<BindablePropertyImporter>(object->as<BindableProperty>());
                 stackType = BindablePropertyBase::typeKey;
                 break;
+            case DataConverterGroupBase::typeKey:
+                stackObject = rivestd::make_unique<DataConverterGroupImporter>(
+                    object->as<DataConverterGroup>());
+                stackType = DataConverterGroupBase::typeKey;
+                break;
         }
         if (importStack.makeLatest(stackType, std::move(stackObject)) != StatusCode::Ok)
         {
diff --git a/src/generated/data_bind/converters/data_converter_group_base.cpp b/src/generated/data_bind/converters/data_converter_group_base.cpp
new file mode 100644
index 0000000..23ee770
--- /dev/null
+++ b/src/generated/data_bind/converters/data_converter_group_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/data_bind/converters/data_converter_group_base.hpp"
+#include "rive/data_bind/converters/data_converter_group.hpp"
+
+using namespace rive;
+
+Core* DataConverterGroupBase::clone() const
+{
+    auto cloned = new DataConverterGroup();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/generated/data_bind/converters/data_converter_group_item_base.cpp b/src/generated/data_bind/converters/data_converter_group_item_base.cpp
new file mode 100644
index 0000000..8f1e17b
--- /dev/null
+++ b/src/generated/data_bind/converters/data_converter_group_item_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/data_bind/converters/data_converter_group_item_base.hpp"
+#include "rive/data_bind/converters/data_converter_group_item.hpp"
+
+using namespace rive;
+
+Core* DataConverterGroupItemBase::clone() const
+{
+    auto cloned = new DataConverterGroupItem();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/generated/data_bind/converters/data_converter_operation_base.cpp b/src/generated/data_bind/converters/data_converter_operation_base.cpp
new file mode 100644
index 0000000..77ee84b
--- /dev/null
+++ b/src/generated/data_bind/converters/data_converter_operation_base.cpp
@@ -0,0 +1,11 @@
+#include "rive/generated/data_bind/converters/data_converter_operation_base.hpp"
+#include "rive/data_bind/converters/data_converter_operation.hpp"
+
+using namespace rive;
+
+Core* DataConverterOperationBase::clone() const
+{
+    auto cloned = new DataConverterOperation();
+    cloned->copy(*this);
+    return cloned;
+}
diff --git a/src/importers/backboard_importer.cpp b/src/importers/backboard_importer.cpp
index 845c536..97a6c55 100644
--- a/src/importers/backboard_importer.cpp
+++ b/src/importers/backboard_importer.cpp
@@ -8,6 +8,7 @@
 #include "rive/viewmodel/viewmodel.hpp"
 #include "rive/viewmodel/viewmodel_instance.hpp"
 #include "rive/data_bind/converters/data_converter.hpp"
+#include "rive/data_bind/converters/data_converter_group_item.hpp"
 #include "rive/data_bind/data_bind.hpp"
 #include <unordered_set>
 
@@ -96,7 +97,15 @@
         }
         referencer->converter(m_DataConverters[index]);
     }
-
+    for (auto referencer : m_DataConverterGroupItemReferencers)
+    {
+        auto index = (size_t)referencer->converterId();
+        if (index >= m_DataConverters.size() || index < 0)
+        {
+            continue;
+        }
+        referencer->converter(m_DataConverters[index]);
+    }
     return StatusCode::Ok;
 }
 
@@ -108,4 +117,9 @@
 void BackboardImporter::addDataConverterReferencer(DataBind* dataBind)
 {
     m_DataConverterReferencers.push_back(dataBind);
+}
+
+void BackboardImporter::addDataConverterGroupItemReferencer(DataConverterGroupItem* dataBind)
+{
+    m_DataConverterGroupItemReferencers.push_back(dataBind);
 }
\ No newline at end of file
diff --git a/src/importers/data_converter_group_importer.cpp b/src/importers/data_converter_group_importer.cpp
new file mode 100644
index 0000000..457a0e2
--- /dev/null
+++ b/src/importers/data_converter_group_importer.cpp
@@ -0,0 +1,9 @@
+#include "rive/artboard.hpp"
+#include "rive/importers/data_converter_group_importer.hpp"
+#include "rive/data_bind/converters/data_converter.hpp"
+
+using namespace rive;
+
+DataConverterGroupImporter::DataConverterGroupImporter(DataConverterGroup* group) :
+    m_dataConverterGroup(group)
+{}
\ No newline at end of file