Add rivinfo tool
diff --git a/.gitignore b/.gitignore
index 563e7e5..72dd777 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,9 +31,6 @@
*.out
*.app
-# test builds
-dev/test/build/bin/*
-
# aot snapshots
dev/bin/*
@@ -57,6 +54,8 @@
# Build directories
build/bin
+dev/test/build/bin
+rivinfo/build/macosx
# Skia dependencies
skia/dependencies/skia
diff --git a/rivinfo/build.sh b/rivinfo/build.sh
new file mode 100755
index 0000000..e0d0eb5
--- /dev/null
+++ b/rivinfo/build.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# dir=$(pwd)
+
+# cd ../renderer
+# ./build.sh $@
+
+# cd $dir
+
+cd build
+
+OPTION=$1
+
+if [ "$OPTION" = 'help' ]; then
+ echo build.sh - build debug library
+ echo build.sh clean - clean the build
+ echo build.sh release - build release library
+elif [ "$OPTION" = "clean" ]; then
+ echo Cleaning project ...
+ # TODO: fix premake5 clean to bubble the clean command to dependent projects
+ premake5 gmake && make clean
+elif [ "$OPTION" = "release" ]; then
+ premake5 gmake && make config=release -j7
+else
+ premake5 gmake && make -j7
+fi
diff --git a/rivinfo/build/premake5.lua b/rivinfo/build/premake5.lua
new file mode 100644
index 0000000..0c69b56
--- /dev/null
+++ b/rivinfo/build/premake5.lua
@@ -0,0 +1,73 @@
+workspace "rive"
+configurations {"debug", "release"}
+
+project "rivinfo"
+ kind "ConsoleApp"
+ language "C++"
+ cppdialect "C++17"
+ targetdir "%{cfg.system}/bin/%{cfg.buildcfg}"
+ objdir "%{cfg.system}/obj/%{cfg.buildcfg}"
+ includedirs {
+ "../../include",
+ "../../test",
+ "/usr/local/include",
+ "/usr/include",
+ }
+
+ if os.host() == 'macosx' then
+ links {
+ "Cocoa.framework",
+ "CoreFoundation.framework",
+ "IOKit.framework",
+ "Security.framework",
+ "bz2",
+ "iconv",
+ "lzma",
+ "rive",
+ "z", -- lib av format
+ }
+ else
+ links {
+ "m",
+ "rive",
+ "z",
+ "dl",
+ }
+ end
+
+ libdirs {
+ "../../build/%{cfg.system}/bin/%{cfg.buildcfg}",
+ "/usr/local/lib",
+ "/usr/lib",
+ }
+
+ files {
+ "../**.cpp",
+ "../../test/no_op_factory.cpp",
+ }
+
+ buildoptions {"-Wall", "-fno-rtti", "-g"}
+
+ filter "configurations:debug"
+ defines {"DEBUG"}
+ symbols "On"
+
+ filter "configurations:release"
+ defines {"RELEASE"}
+ defines {"NDEBUG"}
+ optimize "On"
+
+-- Clean Function --
+newaction {
+ trigger = "clean",
+ description = "clean the build",
+ execute = function()
+ print("clean the build...")
+ os.rmdir("./bin")
+ os.rmdir("./obj")
+ os.remove("Makefile")
+ -- no wildcards in os.remove, so use shell
+ os.execute("rm *.make")
+ print("build cleaned")
+ end
+}
diff --git a/rivinfo/main.cpp b/rivinfo/main.cpp
new file mode 100644
index 0000000..17b70e5
--- /dev/null
+++ b/rivinfo/main.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#include "rive/artboard.hpp"
+#include "rive/file.hpp"
+#include "rive/animation/linear_animation_instance.hpp"
+#include "rive/animation/state_machine_instance.hpp"
+#include "rive/animation/state_machine_input_instance.hpp"
+#include "no_op_factory.hpp"
+
+class JSoner {
+ std::vector<bool> m_IsArray;
+
+ void tab() {
+ for (int i = 0; i < m_IsArray.size(); ++i) {
+ printf("\t");
+ }
+ }
+ void add_c(const char key[], char c) {
+ this->tab();
+ if (key) {
+ printf("\"%s\": %c\n", key, c);
+ } else {
+ printf("%c\n", c);
+ }
+ }
+
+public:
+ JSoner() {}
+ ~JSoner() {
+ while (!m_IsArray.empty()) {
+ this->pop();
+ }
+ }
+
+ void add(const char key[], const char value[]) {
+ this->tab();
+ printf("\"%s\": \"%s\"\n", key, value);
+ }
+ void pushArray(const char key[] = nullptr) {
+ this->add_c(key, '[');
+ m_IsArray.push_back(true);
+ }
+ void pushStruct(const char key[] = nullptr) {
+ this->add_c(key, '{');
+ m_IsArray.push_back(false);
+ }
+ void pop() {
+ assert(!m_IsArray.empty());
+ char c = m_IsArray.front() ? ']' : '}';
+ m_IsArray.pop_back();
+
+ this->tab();
+ printf("%c\n", c);
+ }
+
+ void add(const char key[], int value) {
+ this->add(key, std::to_string(value).c_str());
+ }
+};
+
+//////////////////////////////////////////////////
+
+static void dump(JSoner& js, rive::LinearAnimationInstance* anim) {
+ js.pushStruct();
+ js.add("name", anim->name().c_str());
+ js.add("duration", std::to_string(anim->durationSeconds()).c_str());
+ js.add("loop", std::to_string(anim->loopValue()).c_str());
+ js.pop();
+}
+
+static void dump(JSoner& js, rive::StateMachineInstance* smi) {
+ js.pushStruct();
+ js.add("name", smi->name().c_str());
+ if (auto count = smi->inputCount()) {
+ js.pushArray("inputs");
+ for (auto i = 0; i < count; ++i) {
+ auto inp = smi->input(i);
+ js.add("name", inp->name().c_str());
+ }
+ js.pop();
+ }
+ js.pop();
+}
+
+static void dump(JSoner& js, rive::ArtboardInstance* abi) {
+ js.pushStruct();
+ js.add("name", abi->name().c_str());
+ if (auto count = abi->animationCount()) {
+ js.pushArray("animations");
+ for (size_t i = 0; i < count; ++i) {
+ dump(js, abi->animationAt(i).get());
+ }
+ js.pop();
+ }
+ if (auto count = abi->stateMachineCount()) {
+ js.pushArray("machines");
+ for (size_t i = 0; i < count; ++i) {
+ dump(js, abi->stateMachineAt(i).get());
+ }
+ js.pop();
+ }
+ js.pop();
+}
+
+static void dump(JSoner& js, rive::File* file) {
+ auto count = file->artboardCount();
+ js.pushArray("artboards");
+ for (size_t i = 0; i < count; ++i) {
+ dump(js, file->artboardAt(i).get());
+ }
+ js.pop();
+}
+
+static std::unique_ptr<rive::File> open_file(const char name[]) {
+ FILE* f = fopen(name, "rb");
+ if (!f) {
+ return nullptr;
+ }
+
+ fseek(f, 0, SEEK_END);
+ auto length = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ std::vector<uint8_t> bytes(length);
+
+ if (fread(bytes.data(), 1, length, f) != length) {
+ printf("Failed to read file into bytes array\n");
+ return nullptr;
+ }
+
+ static rive::NoOpFactory gFactory;
+ return rive::File::import(rive::toSpan(bytes), &gFactory);
+}
+
+static bool is_arg(const char arg[], const char target[], const char alt[] = nullptr) {
+ return !strcmp(arg, target) || (arg && !strcmp(arg, alt));
+}
+
+int main(int argc, const char* argv[]) {
+ const char* filename = nullptr;
+
+ for (int i = 1; i < argc; ++i) {
+ if (is_arg(argv[i], "--file", "-f")) {
+ filename = argv[++i];
+ continue;
+ }
+ printf("Unrecognized argument %s\n", argv[i]);
+ return 1;
+ }
+
+ if (!filename) {
+ printf("Need --file filename\n");
+ return 1;
+ }
+
+ auto file = open_file(filename);
+ if (!file) {
+ printf("Can't open %s\n", filename);
+ return 1;
+ }
+
+ JSoner js;
+ js.pushStruct();
+ dump(js, file.get());
+ return 0;
+}