Simplify File API
diff --git a/include/rive/core/binary_reader.hpp b/include/rive/core/binary_reader.hpp
index 4894bad..d6e2570 100644
--- a/include/rive/core/binary_reader.hpp
+++ b/include/rive/core/binary_reader.hpp
@@ -9,16 +9,14 @@
 namespace rive {
     class BinaryReader {
     private:
+        Span<const uint8_t> m_Bytes;
         const uint8_t* m_Position;
-        const uint8_t* m_End;
         bool m_Overflowed;
-        size_t m_Length;
 
         void overflow();
 
     public:
-        BinaryReader(const uint8_t* bytes, size_t length);
-        BinaryReader(Span<const uint8_t> span);
+        explicit BinaryReader(Span<const uint8_t>);
         bool didOverflow() const;
         bool reachedEnd() const;
 
diff --git a/include/rive/file.hpp b/include/rive/file.hpp
index ab85e86..419634e 100644
--- a/include/rive/file.hpp
+++ b/include/rive/file.hpp
@@ -3,8 +3,6 @@
 
 #include "rive/artboard.hpp"
 #include "rive/backboard.hpp"
-#include "rive/core/binary_reader.hpp"
-#include "rive/runtime_header.hpp"
 #include "rive/file_asset_resolver.hpp"
 #include <vector>
 
@@ -12,6 +10,9 @@
 /// Default namespace for Rive Cpp runtime code.
 ///
 namespace rive {
+    class BinaryReader;
+    class RuntimeHeader;
+
     ///
     /// Tracks the success/failure result when importing a Rive file.
     ///
@@ -54,12 +55,12 @@
 
         ///
         /// Imports a Rive file from a binary buffer.
-        /// @param reader a pointer to a binary reader attached to the file.
+        /// @param data the raw date of the file.
         /// @param result is an optional status result.
         /// @param assetResolver is an optional helper to resolve assets which
         /// cannot be found in-band.
         /// @returns a pointer to the file, or null on failure.
-        static std::unique_ptr<File> import(BinaryReader& reader,
+        static std::unique_ptr<File> import(Span<const uint8_t> data,
                                    ImportResult* result  = nullptr,
                                    FileAssetResolver* assetResolver = nullptr);
 
diff --git a/skia/thumbnail_generator/src/main.cpp b/skia/thumbnail_generator/src/main.cpp
index e17b9f1..75200fe 100644
--- a/skia/thumbnail_generator/src/main.cpp
+++ b/skia/thumbnail_generator/src/main.cpp
@@ -2,7 +2,6 @@
 #include "SkImage.h"
 #include "SkStream.h"
 #include "SkSurface.h"
-#include "rive/core/binary_reader.hpp"
 #include "rive/file.hpp"
 #include "rive/math/aabb.hpp"
 #include "skia_renderer.hpp"
@@ -43,14 +42,15 @@
     fseek(fp, 0, SEEK_END);
     auto length = ftell(fp);
     fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    if (fread(bytes, 1, length, fp) != length) {
+    std::vector<uint8_t> bytes(length);
+    if (fread(bytes.data(), 1, length, fp) != length) {
         fprintf(stderr, "Failed to read rive file.\n");
+        fclose(fp);
         return 1;
     }
+    fclose(fp);
 
-    auto reader = rive::BinaryReader(bytes, length);
-    auto file = rive::File::import(reader);
+    auto file = rive::File::import(rive::toSpan(bytes));
     if (!file) {
         fprintf(stderr, "Failed to read rive file.\n");
         return 1;
@@ -58,8 +58,6 @@
     auto artboard = file->artboardDefault();
     artboard->advance(0.0f);
 
-    delete[] bytes;
-
     int width = 256, height = 256;
 
     sk_sp<SkSurface> rasterSurface = SkSurface::MakeRasterN32Premul(width, height);
diff --git a/skia/viewer/src/main.cpp b/skia/viewer/src/main.cpp
index d7cf24c..5f3b22d 100644
--- a/skia/viewer/src/main.cpp
+++ b/skia/viewer/src/main.cpp
@@ -68,8 +68,7 @@
     stateMachineIndex = index;
     animationIndex = -1;
     assert(fileBytes.size() != 0);
-    rive::BinaryReader reader(rive::toSpan(fileBytes));
-    auto file = rive::File::import(reader);
+    auto file = rive::File::import(rive::toSpan(fileBytes));
     if (!file) {
         fileBytes.clear();
         fprintf(stderr, "failed to import file\n");
@@ -93,8 +92,7 @@
     animationIndex = index;
     stateMachineIndex = -1;
     assert(fileBytes.size() != 0);
-    rive::BinaryReader reader(rive::toSpan(fileBytes));
-    auto file = rive::File::import(reader);
+    auto file = rive::File::import(rive::toSpan(fileBytes));
     if (!file) {
         fileBytes.clear();
         fprintf(stderr, "failed to import file\n");
diff --git a/src/core/binary_reader.cpp b/src/core/binary_reader.cpp
index 5e2c578..4ea5189 100644
--- a/src/core/binary_reader.cpp
+++ b/src/core/binary_reader.cpp
@@ -5,25 +5,23 @@
 
 using namespace rive;
 
-BinaryReader::BinaryReader(Span<const uint8_t> span) : BinaryReader(span.begin(), span.size()) {}
+BinaryReader::BinaryReader(Span<const uint8_t> bytes) :
+    m_Bytes(bytes), m_Position(bytes.begin()), m_Overflowed(false) {}
 
-BinaryReader::BinaryReader(const uint8_t* bytes, size_t length) :
-    m_Position(bytes), m_End(bytes + length), m_Overflowed(false), m_Length(length) {}
+bool BinaryReader::reachedEnd() const { return m_Position == m_Bytes.end() || didOverflow(); }
 
-bool BinaryReader::reachedEnd() const { return m_Position == m_End || didOverflow(); }
-
-size_t BinaryReader::lengthInBytes() const { return m_Length; }
+size_t BinaryReader::lengthInBytes() const { return m_Bytes.size(); }
 
 bool BinaryReader::didOverflow() const { return m_Overflowed; }
 
 void BinaryReader::overflow() {
     m_Overflowed = true;
-    m_Position = m_End;
+    m_Position = m_Bytes.end();
 }
 
 uint64_t BinaryReader::readVarUint64() {
     uint64_t value;
-    auto readBytes = decode_uint_leb(m_Position, m_End, &value);
+    auto readBytes = decode_uint_leb(m_Position, m_Bytes.end(), &value);
     if (readBytes == 0) {
         overflow();
         return 0;
@@ -39,7 +37,7 @@
     }
 
     std::vector<char> rawValue(length + 1);
-    auto readBytes = decode_string(length, m_Position, m_End, &rawValue[0]);
+    auto readBytes = decode_string(length, m_Position, m_Bytes.end(), &rawValue[0]);
     if (readBytes != length) {
         overflow();
         return std::string();
@@ -61,7 +59,7 @@
 
 double BinaryReader::readFloat64() {
     double value;
-    auto readBytes = decode_double(m_Position, m_End, &value);
+    auto readBytes = decode_double(m_Position, m_Bytes.end(), &value);
     if (readBytes == 0) {
         overflow();
         return 0.0;
@@ -72,7 +70,7 @@
 
 float BinaryReader::readFloat32() {
     float value;
-    auto readBytes = decode_float(m_Position, m_End, &value);
+    auto readBytes = decode_float(m_Position, m_Bytes.end(), &value);
     if (readBytes == 0) {
         overflow();
         return 0.0f;
@@ -82,7 +80,7 @@
 }
 
 uint8_t BinaryReader::readByte() {
-    if (m_End - m_Position < 1) {
+    if (m_Bytes.end() - m_Position < 1) {
         overflow();
         return 0;
     }
@@ -91,7 +89,7 @@
 
 uint32_t BinaryReader::readUint32() {
     uint32_t value;
-    auto readBytes = decode_uint_32(m_Position, m_End, &value);
+    auto readBytes = decode_uint_32(m_Position, m_Bytes.end(), &value);
     if (readBytes == 0) {
         overflow();
         return 0;
diff --git a/src/file.cpp b/src/file.cpp
index bf5bd04..a08c5d9 100644
--- a/src/file.cpp
+++ b/src/file.cpp
@@ -1,4 +1,5 @@
 #include "rive/file.hpp"
+#include "rive/runtime_header.hpp"
 #include "rive/animation/animation.hpp"
 #include "rive/core/field_types/core_color_type.hpp"
 #include "rive/core/field_types/core_double_type.hpp"
@@ -110,9 +111,9 @@
 
 File::~File() {}
 
-// Import a Rive file from a file handle
 std::unique_ptr<File>
-File::import(BinaryReader& reader, ImportResult* result, FileAssetResolver* assetResolver) {
+File::import(Span<const uint8_t> bytes, ImportResult* result, FileAssetResolver* assetResolver) {
+    BinaryReader reader(bytes);
     RuntimeHeader header;
     if (!RuntimeHeader::read(reader, header)) {
         fprintf(stderr, "Bad header\n");
diff --git a/test/bound_bones_test.cpp b/test/bound_bones_test.cpp
index cb647c0..4a92b48 100644
--- a/test/bound_bones_test.cpp
+++ b/test/bound_bones_test.cpp
@@ -1,6 +1,5 @@
 #include <rive/bones/skin.hpp>
 #include <rive/bones/tendon.hpp>
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/shapes/clipping_shape.hpp>
@@ -14,9 +13,9 @@
 #include <cstdio>
 
 TEST_CASE("bound bones load correctly", "[bones]") {
-    RiveFileReader reader("../../test/assets/off_road_car.riv");
+    auto file = ReadRiveFile("../../test/assets/off_road_car.riv");
 
-    auto node = reader.file()->artboard()->find("transmission_front_testing");
+    auto node = file->artboard()->find("transmission_front_testing");
     REQUIRE(node != nullptr);
     REQUIRE(node->is<rive::Shape>());
     REQUIRE(node->as<rive::Shape>()->paths().size() == 1);
diff --git a/test/clip_test.cpp b/test/clip_test.cpp
index 8a0cdf3..5fd4c76 100644
--- a/test/clip_test.cpp
+++ b/test/clip_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/shapes/clipping_shape.hpp>
@@ -10,9 +9,8 @@
 #include <cstdio>
 
 TEST_CASE("clipping loads correctly", "[clipping]") {
-    RiveFileReader reader("../../test/assets/circle_clips.riv");
+    auto file = ReadRiveFile("../../test/assets/circle_clips.riv");
 
-    auto file = reader.file();
     auto node = file->artboard()->find("TopEllipse");
     REQUIRE(node != nullptr);
     REQUIRE(node->is<rive::Shape>());
diff --git a/test/distance_constraint_test.cpp b/test/distance_constraint_test.cpp
index df59f70..dfb7b26 100644
--- a/test/distance_constraint_test.cpp
+++ b/test/distance_constraint_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/constraints/distance_constraint.hpp>
 #include <rive/node.hpp>
@@ -9,9 +8,9 @@
 #include <cstdio>
 
 TEST_CASE("distance constraints moves items as expected", "[file]") {
-    RiveFileReader reader("../../test/assets/distance_constraint.riv");
+    auto file = ReadRiveFile("../../test/assets/distance_constraint.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
 
     REQUIRE(artboard->find<rive::Shape>("A") != nullptr);
     auto a = artboard->find<rive::Shape>("A");
diff --git a/test/draw_order_test.cpp b/test/draw_order_test.cpp
index 32e0b9d..02bf3a8 100644
--- a/test/draw_order_test.cpp
+++ b/test/draw_order_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/shapes/clipping_shape.hpp>
@@ -10,7 +9,7 @@
 #include <cstdio>
 
 TEST_CASE("draw rules load and sort correctly", "[draw rules]") {
-    RiveFileReader reader("../../test/assets/draw_rule_cycle.riv");
+    auto file = ReadRiveFile("../../test/assets/draw_rule_cycle.riv");
 
     // auto file = reader.file();
     // auto node = file->artboard()->node("TopEllipse");
diff --git a/test/file_test.cpp b/test/file_test.cpp
index d81d67a..4a0a99f 100644
--- a/test/file_test.cpp
+++ b/test/file_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/shapes/rectangle.hpp>
@@ -9,19 +8,19 @@
 #include <cstdio>
 
 TEST_CASE("file can be read", "[file]") {
-    RiveFileReader reader("../../test/assets/two_artboards.riv");
+    auto file = ReadRiveFile("../../test/assets/two_artboards.riv");
 
     // Default artboard should be named Two.
-    REQUIRE(reader.file()->artboard()->name() == "Two");
+    REQUIRE(file->artboard()->name() == "Two");
 
     // There should be a second artboard named One.
-    REQUIRE(reader.file()->artboard("One") != nullptr);
+    REQUIRE(file->artboard("One") != nullptr);
 }
 
 TEST_CASE("file with animation can be read", "[file]") {
-    RiveFileReader reader("../../test/assets/juice.riv");
+    auto file = ReadRiveFile("../../test/assets/juice.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
     REQUIRE(artboard->name() == "New Artboard");
 
     auto shin = artboard->find("shin_right");
@@ -43,8 +42,7 @@
 }
 
 TEST_CASE("artboards can be counted and accessed via index or name", "[file]") {
-    RiveFileReader reader("../../test/assets/dependency_test.riv");
-    auto file = reader.file();
+    auto file = ReadRiveFile("../../test/assets/dependency_test.riv");
 
     // The artboards caqn be counted
     REQUIRE(file->artboardCount() == 1);
@@ -75,9 +73,9 @@
     //                   │ ┌──────────────┐
     //                   └▶│Rectangle Path│
     //                     └──────────────┘
-    RiveFileReader reader("../../test/assets/dependency_test.riv");
+    auto file = ReadRiveFile("../../test/assets/dependency_test.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
     REQUIRE(artboard->name() == "Blue");
 
     auto nodeA = artboard->find<rive::Node>("A");
diff --git a/test/ik_constraint_test.cpp b/test/ik_constraint_test.cpp
index 1481171..b373818 100644
--- a/test/ik_constraint_test.cpp
+++ b/test/ik_constraint_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/constraints/ik_constraint.hpp>
 #include <rive/node.hpp>
@@ -11,9 +10,9 @@
 #include <cstdio>
 
 TEST_CASE("ik with skinned bones orders correctly", "[file]") {
-    RiveFileReader reader("../../test/assets/complex_ik_dependency.riv");
+    auto file = ReadRiveFile("../../test/assets/complex_ik_dependency.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
 
     REQUIRE(artboard->find<rive::Bone>("One") != nullptr);
     auto one = artboard->find<rive::Bone>("One");
diff --git a/test/ik_test.cpp b/test/ik_test.cpp
index f0c7b2b..ac87240 100644
--- a/test/ik_test.cpp
+++ b/test/ik_test.cpp
@@ -7,8 +7,8 @@
 #include <cstdio>
 
 TEST_CASE("two bone ik places bones correctly", "[file]") {
-    RiveFileReader reader("../../test/assets/two_bone_ik.riv");
-    auto artboard = reader.file()->artboard();
+    auto file = ReadRiveFile("../../test/assets/two_bone_ik.riv");
+    auto artboard = file->artboard();
 
     REQUIRE(artboard->find<rive::Shape>("circle a") != nullptr);
     auto circleA = artboard->find<rive::Shape>("circle a");
@@ -79,8 +79,8 @@
 }
 
 TEST_CASE("ik keeps working after a lot of iterations", "[file]") {
-    RiveFileReader reader("../../test/assets/two_bone_ik.riv");
-    auto artboard = reader.file()->artboard();
+    auto file = ReadRiveFile("../../test/assets/two_bone_ik.riv");
+    auto artboard = file->artboard();
 
     REQUIRE(artboard->find<rive::Shape>("circle a") != nullptr);
     auto circleA = artboard->find<rive::Shape>("circle a");
diff --git a/test/image_asset_test.cpp b/test/image_asset_test.cpp
index d5252b7..a681d9d 100644
--- a/test/image_asset_test.cpp
+++ b/test/image_asset_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/shapes/clipping_shape.hpp>
@@ -12,8 +11,7 @@
 #include <cstdio>
 
 TEST_CASE("image assets loads correctly", "[assets]") {
-    RiveFileReader reader("../../test/assets/walle.riv");
-    auto file = reader.file();
+    auto file = ReadRiveFile("../../test/assets/walle.riv");
 
     auto node = file->artboard()->find("walle");
     REQUIRE(node != nullptr);
@@ -46,19 +44,7 @@
     std::string filename = "../../test/assets/out_of_band/walle.riv";
     rive::RelativeLocalAssetResolver resolver(filename);
 
-    FILE* fp = fopen(filename.c_str(), "rb");
-    REQUIRE(fp != nullptr);
-
-    fseek(fp, 0, SEEK_END);
-    const size_t length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    auto file = rive::File::import(reader, nullptr, &resolver);
-
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
+    auto file = ReadRiveFile(filename.c_str(), &resolver);
 
     auto node = file->artboard()->find("walle");
     REQUIRE(node != nullptr);
@@ -85,6 +71,4 @@
 
     rive::NoOpRenderer renderer;
     file->artboard()->draw(&renderer);
-
-    delete[] bytes;
 }
diff --git a/test/image_mesh_test.cpp b/test/image_mesh_test.cpp
index 6b817ed..aa7c3b8 100644
--- a/test/image_mesh_test.cpp
+++ b/test/image_mesh_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/shapes/clipping_shape.hpp>
@@ -13,8 +12,7 @@
 #include <cstdio>
 
 TEST_CASE("image with mesh loads correctly", "[mesh]") {
-    RiveFileReader reader("../../test/assets/tape.riv");
-    auto file = reader.file();
+    auto file = ReadRiveFile("../../test/assets/tape.riv");
 
     auto node = file->artboard()->find("Tape body.png");
     REQUIRE(node != nullptr);
@@ -28,8 +26,7 @@
 }
 
 TEST_CASE("duplicating a mesh shares the indices", "[mesh]") {
-    RiveFileReader reader("../../test/assets/tape.riv");
-    auto file = reader.file();
+    auto file = ReadRiveFile("../../test/assets/tape.riv");
 
     auto instance1 = file->artboardDefault();
     auto instance2 = file->artboardDefault();
diff --git a/test/instancing_test.cpp b/test/instancing_test.cpp
index 2d8672a..85d2e4f 100644
--- a/test/instancing_test.cpp
+++ b/test/instancing_test.cpp
@@ -1,27 +1,15 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/shapes/clipping_shape.hpp>
 #include <rive/shapes/rectangle.hpp>
 #include <rive/shapes/shape.hpp>
 #include "no_op_renderer.hpp"
+#include "rive_file_reader.hpp"
 #include <catch.hpp>
 #include <cstdio>
 
 TEST_CASE("cloning an ellipse works", "[instancing]") {
-    FILE* fp = fopen("../../test/assets/circle_clips.riv", "rb");
-    REQUIRE(fp != nullptr);
-
-    fseek(fp, 0, SEEK_END);
-    const size_t length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    auto file = rive::File::import(reader);
-
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
+    auto file = ReadRiveFile("../../test/assets/circle_clips.riv");
 
     auto node = file->artboard()->find<rive::Shape>("TopEllipse");
     REQUIRE(node != nullptr);
@@ -31,23 +19,11 @@
     REQUIRE(node->y() == clonedNode->y());
 
     delete clonedNode;
-    delete[] bytes;
 }
 
 TEST_CASE("instancing artboard clones clipped properties", "[instancing]") {
-    FILE* fp = fopen("../../test/assets/circle_clips.riv", "rb");
-    REQUIRE(fp != nullptr);
+    auto file = ReadRiveFile("../../test/assets/circle_clips.riv");
 
-    fseek(fp, 0, SEEK_END);
-    const size_t length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    auto file = rive::File::import(reader);
-
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
     REQUIRE(!file->artboard()->isInstance());
 
     auto artboard = file->artboardDefault();
@@ -67,24 +43,10 @@
 
     rive::NoOpRenderer renderer;
     artboard->draw(&renderer);
-
-    delete[] bytes;
 }
 
 TEST_CASE("instancing artboard doesn't clone animations", "[instancing]") {
-    FILE* fp = fopen("../../test/assets/juice.riv", "rb");
-    REQUIRE(fp != nullptr);
-
-    fseek(fp, 0, SEEK_END);
-    const size_t length = ftell(fp);
-    fseek(fp, 0, SEEK_SET);
-    uint8_t* bytes = new uint8_t[length];
-    REQUIRE(fread(bytes, 1, length, fp) == length);
-    auto reader = rive::BinaryReader(bytes, length);
-    auto file = rive::File::import(reader);
-
-    REQUIRE(file != nullptr);
-    REQUIRE(file->artboard() != nullptr);
+    auto file = ReadRiveFile("../../test/assets/juice.riv");
 
     auto artboard = file->artboardDefault();
 
@@ -99,6 +61,4 @@
     file.reset(nullptr);
     // Now the animations should've been deleted.
     REQUIRE(rive::LinearAnimation::deleteCount == numberOfAnimations);
-
-    delete[] bytes;
 }
diff --git a/test/path_test.cpp b/test/path_test.cpp
index 7440840..15f9218 100644
--- a/test/path_test.cpp
+++ b/test/path_test.cpp
@@ -1,5 +1,4 @@
 #include <rive/artboard.hpp>
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/math/circle_constant.hpp>
 #include <rive/node.hpp>
diff --git a/test/rive_file_reader.hpp b/test/rive_file_reader.hpp
index f62e1e8..cec7a41 100644
--- a/test/rive_file_reader.hpp
+++ b/test/rive_file_reader.hpp
@@ -1,39 +1,28 @@
 #ifndef _RIVE_FILE_READER_HPP_
 #define _RIVE_FILE_READER_HPP_
 
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include "rive_testing.hpp"
 
-class RiveFileReader {
-    std::unique_ptr<rive::File> m_File;
-    uint8_t* m_Bytes = nullptr;
-    rive::BinaryReader* m_Reader;
+static inline std::unique_ptr<rive::File>
+ReadRiveFile(const char path[], rive::FileAssetResolver* resolver = nullptr) {
+    FILE* fp = fopen(path, "rb");
+    REQUIRE(fp != nullptr);
 
-public:
-    RiveFileReader(const char path[]) {
-        FILE* fp = fopen(path, "r");
-        REQUIRE(fp != nullptr);
+    fseek(fp, 0, SEEK_END);
+    const size_t length = ftell(fp);
+    fseek(fp, 0, SEEK_SET);
+    std::vector<uint8_t> bytes(length);
+    REQUIRE(fread(bytes.data(), 1, length, fp) == length);
+    fclose(fp);
 
-        fseek(fp, 0, SEEK_END);
-        const size_t length = ftell(fp);
-        fseek(fp, 0, SEEK_SET);
-        m_Bytes = new uint8_t[length];
-        REQUIRE(fread(m_Bytes, 1, length, fp) == length);
-        m_Reader = new rive::BinaryReader(m_Bytes, length);
-        rive::ImportResult result;
-        m_File = rive::File::import(*m_Reader, &result);
+    rive::ImportResult result;
+    auto file = rive::File::import(rive::toSpan(bytes), &result, resolver);
+    REQUIRE(result == rive::ImportResult::success);
+    REQUIRE(file.get() != nullptr);
+    REQUIRE(file->artboard() != nullptr);
 
-        REQUIRE(result == rive::ImportResult::success);
-        REQUIRE(m_File != nullptr);
-        REQUIRE(m_File->artboard() != nullptr);
-    }
-    ~RiveFileReader() {
-        delete m_Reader;
-        delete[] m_Bytes;
-    }
-
-    rive::File* file() const { return m_File.get(); }
-};
+    return file;
+}
 
 #endif
diff --git a/test/rotation_constraint_test.cpp b/test/rotation_constraint_test.cpp
index 30a62e6..007f6f9 100644
--- a/test/rotation_constraint_test.cpp
+++ b/test/rotation_constraint_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/bones/bone.hpp>
@@ -10,9 +9,9 @@
 #include <cstdio>
 
 TEST_CASE("rotation constraint updates world transform", "[file]") {
-    RiveFileReader reader("../../test/assets/rotation_constraint.riv");
+    auto file = ReadRiveFile("../../test/assets/rotation_constraint.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
 
     REQUIRE(artboard->find<rive::TransformComponent>("target") != nullptr);
     auto target = artboard->find<rive::TransformComponent>("target");
diff --git a/test/scale_constraint_test.cpp b/test/scale_constraint_test.cpp
index 9770fc0..bd9827b 100644
--- a/test/scale_constraint_test.cpp
+++ b/test/scale_constraint_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/bones/bone.hpp>
@@ -10,9 +9,9 @@
 #include <cstdio>
 
 TEST_CASE("scale constraint updates world transform", "[file]") {
-    RiveFileReader reader("../../test/assets/scale_constraint.riv");
+    auto file = ReadRiveFile("../../test/assets/scale_constraint.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
 
     REQUIRE(artboard->find<rive::TransformComponent>("target") != nullptr);
     auto target = artboard->find<rive::TransformComponent>("target");
diff --git a/test/state_machine_test.cpp b/test/state_machine_test.cpp
index bd6196c..b4b0f97 100644
--- a/test/state_machine_test.cpp
+++ b/test/state_machine_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/animation/state_machine_bool.hpp>
 #include <rive/animation/state_machine_layer.hpp>
@@ -16,9 +15,9 @@
 #include <cstdio>
 
 TEST_CASE("file with state machine be read", "[file]") {
-    RiveFileReader reader("../../test/assets/rocket.riv");
+    auto file = ReadRiveFile("../../test/assets/rocket.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
     REQUIRE(artboard != nullptr);
     REQUIRE(artboard->animationCount() == 3);
     REQUIRE(artboard->stateMachineCount() == 1);
@@ -83,9 +82,9 @@
 }
 
 TEST_CASE("file with blend states loads correctly", "[file]") {
-    RiveFileReader reader("../../test/assets/blend_test.riv");
+    auto file = ReadRiveFile("../../test/assets/blend_test.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
     REQUIRE(artboard != nullptr);
     REQUIRE(artboard->animationCount() == 4);
     REQUIRE(artboard->stateMachineCount() == 2);
@@ -138,9 +137,9 @@
 }
 
 TEST_CASE("animation state with no animation doesn't crash", "[file]") {
-    RiveFileReader reader("../../test/assets/multiple_state_machines.riv");
+    auto file = ReadRiveFile("../../test/assets/multiple_state_machines.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
     REQUIRE(artboard != nullptr);
     REQUIRE(artboard->animationCount() == 1);
     REQUIRE(artboard->stateMachineCount() == 4);
diff --git a/test/stroke_test.cpp b/test/stroke_test.cpp
index a342dd7..3af2791 100644
--- a/test/stroke_test.cpp
+++ b/test/stroke_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/shapes/rectangle.hpp>
@@ -12,9 +11,9 @@
 #include <cstdio>
 
 TEST_CASE("stroke can be looked up at runtime", "[file]") {
-    RiveFileReader reader("../../test/assets/stroke_name_test.riv");
+    auto file = ReadRiveFile("../../test/assets/stroke_name_test.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
     REQUIRE(artboard->find<rive::Stroke>("white_stroke") != nullptr);
     auto stroke = artboard->find<rive::Stroke>("white_stroke");
     REQUIRE(stroke->paint()->is<rive::SolidColor>());
diff --git a/test/transform_constraint_test.cpp b/test/transform_constraint_test.cpp
index b32173b..12707ba 100644
--- a/test/transform_constraint_test.cpp
+++ b/test/transform_constraint_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/bones/bone.hpp>
@@ -9,9 +8,9 @@
 #include <cstdio>
 
 TEST_CASE("transform constraint updates world transform", "[file]") {
-    RiveFileReader reader("../../test/assets/transform_constraint.riv");
+    auto file = ReadRiveFile("../../test/assets/transform_constraint.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
 
     REQUIRE(artboard->find<rive::TransformComponent>("Target") != nullptr);
     auto target = artboard->find<rive::TransformComponent>("Target");
diff --git a/test/translation_constraint_test.cpp b/test/translation_constraint_test.cpp
index 5e06bb5..d01f15b 100644
--- a/test/translation_constraint_test.cpp
+++ b/test/translation_constraint_test.cpp
@@ -1,4 +1,3 @@
-#include <rive/core/binary_reader.hpp>
 #include <rive/file.hpp>
 #include <rive/node.hpp>
 #include <rive/bones/bone.hpp>
@@ -10,9 +9,9 @@
 #include <cstdio>
 
 TEST_CASE("translation constraint updates world transform", "[file]") {
-    RiveFileReader reader("../../test/assets/translation_constraint.riv");
+    auto file = ReadRiveFile("../../test/assets/translation_constraint.riv");
 
-    auto artboard = reader.file()->artboard();
+    auto artboard = file->artboard();
 
     REQUIRE(artboard->find<rive::TransformComponent>("target") != nullptr);
     auto target = artboard->find<rive::TransformComponent>("target");