feat: add nested text run getters and setters in Unity

This PR introduces new methods to get and set text run values in nested artboards in Unity

### Unity
- Added to `Artboard` class in Unity:
  - `GetTextRunValue(string runName)`
  - `SetTextRunValueAtPath(string runName, string path, string value)`
  - `GetTextRunValueAtPath(string runName, string path)`

### rive-cpp
- Exposed in `Artboard` class:
  - `TextValueRun* getTextRun(const std::string& name) const;`

Diffs=
55de8286c feat: add nested text run getters and setters in Unity (#7808)

Co-authored-by: Adam <67035612+damzobridge@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head
index c15f5b7..27941d2 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-2c0927fc5e9a5c420305a81446ac41eb5b896b2c
+55de8286c5ba680de950a3a0aac26e13bb890d6b
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp
index 6c9df53..b054943 100644
--- a/include/rive/artboard.hpp
+++ b/include/rive/artboard.hpp
@@ -408,6 +408,7 @@
     SMIBool* getBool(const std::string& name, const std::string& path);
     SMINumber* getNumber(const std::string& name, const std::string& path);
     SMITrigger* getTrigger(const std::string& name, const std::string& path);
+    TextValueRun* getTextRun(const std::string& name, const std::string& path);
 };
 } // namespace rive
 
diff --git a/include/rive/nested_artboard.hpp b/include/rive/nested_artboard.hpp
index 6457727..6b97708 100644
--- a/include/rive/nested_artboard.hpp
+++ b/include/rive/nested_artboard.hpp
@@ -69,4 +69,4 @@
 };
 } // namespace rive
 
-#endif
+#endif
\ No newline at end of file
diff --git a/src/artboard.cpp b/src/artboard.cpp
index 67a5a1b..b0576d9 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -1359,6 +1359,28 @@
     return getNamedInput<SMITrigger>(name, path);
 }
 
+TextValueRun* ArtboardInstance::getTextRun(const std::string& name, const std::string& path)
+{
+    if (path.empty())
+    {
+        return nullptr;
+    }
+
+    auto nestedArtboard = nestedArtboardAtPath(path);
+    if (nestedArtboard == nullptr)
+    {
+        return nullptr;
+    }
+
+    auto artboardInstance = nestedArtboard->artboardInstance();
+    if (artboardInstance == nullptr)
+    {
+        return nullptr;
+    }
+
+    return artboardInstance->find<TextValueRun>(name);
+}
+
 #ifdef EXTERNAL_RIVE_AUDIO_ENGINE
 rcp<AudioEngine> Artboard::audioEngine() const { return m_audioEngine; }
 void Artboard::audioEngine(rcp<AudioEngine> audioEngine)
diff --git a/test/assets/runtime_nested_text_runs.riv b/test/assets/runtime_nested_text_runs.riv
new file mode 100644
index 0000000..b7535a0
--- /dev/null
+++ b/test/assets/runtime_nested_text_runs.riv
Binary files differ
diff --git a/test/nested_text_run_test.cpp b/test/nested_text_run_test.cpp
new file mode 100644
index 0000000..f79a6e6
--- /dev/null
+++ b/test/nested_text_run_test.cpp
@@ -0,0 +1,61 @@
+#include "rive/core/binary_reader.hpp"
+#include "rive/file.hpp"
+#include "rive/nested_artboard.hpp"
+#include "rive/text/text_value_run.hpp"
+#include "rive/animation/state_machine_instance.hpp"
+#include "rive/animation/state_machine_input_instance.hpp"
+#include "catch.hpp"
+#include "rive_file_reader.hpp"
+#include <cstdio>
+
+TEST_CASE("validate nested text get/set", "[nestedText]")
+{
+    auto file = ReadRiveFile("../../test/assets/runtime_nested_text_runs.riv");
+
+    auto artboard = file->artboard("ArtboardA")->instance();
+    REQUIRE(artboard != nullptr);
+    REQUIRE(artboard->stateMachineCount() == 1);
+
+    // Test getting/setting TextValueRun view nested artboard path one level deep
+
+    auto textRunB1 = artboard->getTextRun("ArtboardBRun", "ArtboardB-1");
+    REQUIRE(textRunB1->is<rive::TextValueRun>());
+    REQUIRE(textRunB1->text() == "Artboard B Run");
+
+    auto textRunB2 = artboard->getTextRun("ArtboardBRun", "ArtboardB-2");
+    REQUIRE(textRunB2->is<rive::TextValueRun>());
+    REQUIRE(textRunB2->text() == "Artboard B Run");
+
+    // Test getting/setting TextValueRun view nested artboard path two level deep
+
+    auto textRunB1C1 = artboard->getTextRun("ArtboardCRun", "ArtboardB-1/ArtboardC-1");
+    REQUIRE(textRunB1C1->is<rive::TextValueRun>());
+    REQUIRE(textRunB1C1->text() == "Artboard C Run");
+
+    auto textRunB1C2 = artboard->getTextRun("ArtboardCRun", "ArtboardB-1/ArtboardC-2");
+    REQUIRE(textRunB1C2->is<rive::TextValueRun>());
+    REQUIRE(textRunB1C2->text() == "Artboard C Run");
+
+    auto textRunB2C1 = artboard->getTextRun("ArtboardCRun", "ArtboardB-2/ArtboardC-1");
+    REQUIRE(textRunB2C1->is<rive::TextValueRun>());
+    REQUIRE(textRunB2C1->text() == "Artboard C Run");
+
+    auto textRunB2C2 = artboard->getTextRun("ArtboardCRun", "ArtboardB-2/ArtboardC-2");
+    REQUIRE(textRunB2C2->is<rive::TextValueRun>());
+    REQUIRE(textRunB2C2->text() == "Artboard C Run");
+
+    // Validate that text run values can be set
+
+    textRunB1->text("Artboard B1 Run Updated");
+    textRunB2->text("Artboard B2 Run Updated");
+    textRunB1C1->text("Artboard B1C1 Run Updated");
+    textRunB1C2->text("Artboard B1C2 Run Updated");
+    textRunB2C1->text("Artboard B2C1 Run Updated");
+    textRunB2C2->text("Artboard B2C2 Run Updated");
+    REQUIRE(textRunB1->text() == "Artboard B1 Run Updated");
+    REQUIRE(textRunB2->text() == "Artboard B2 Run Updated");
+    REQUIRE(textRunB1C1->text() == "Artboard B1C1 Run Updated");
+    REQUIRE(textRunB1C2->text() == "Artboard B1C2 Run Updated");
+    REQUIRE(textRunB2C1->text() == "Artboard B2C1 Run Updated");
+    REQUIRE(textRunB2C2->text() == "Artboard B2C2 Run Updated");
+}
\ No newline at end of file