Skip to content

Instantly share code, notes, and snippets.

@iamtekeste
Created April 10, 2024 00:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iamtekeste/9705fc8c030188c7c73872dfdf6ef03c to your computer and use it in GitHub Desktop.
Save iamtekeste/9705fc8c030188c7c73872dfdf6ef03c to your computer and use it in GitHub Desktop.
Rive file writer
import "./styles.css";
import { useRive, Layout, Fit, Alignment } from "@rive-app/react-canvas";
// @refresh reset
var Endian;
(function (Endian) {
Endian["Little"] = "little";
Endian["Big"] = "big";
})(Endian || (Endian = {}));
var BackingType;
(function (BackingType) {
BackingType[(BackingType["UintBool"] = 0)] = "UintBool";
BackingType[(BackingType["String"] = 1)] = "String";
BackingType[(BackingType["Float"] = 2)] = "Float";
BackingType[(BackingType["Color"] = 3)] = "Color";
})(BackingType || (BackingType = {}));
class BinaryWriter {
variableEncodeList = new Uint8Array(8);
writeIndex = 0;
constructor(alignment = 1024, endian = Endian.Little) {
this.alignment = Math.max(1, alignment);
this.endian = endian;
this.buffer = new Uint8Array(this.alignment);
}
get size() {
return this.writeIndex;
}
nextAlignment(length) {
return Math.ceil(length / this.alignment) * this.alignment;
}
ensureAvailable(byteLength) {
if (this.writeIndex + byteLength > this.buffer.length) {
let newLength = this.buffer.length + this.nextAlignment(byteLength);
const newBuffer = new Uint8Array(newLength);
newBuffer.set(this.buffer);
this.buffer = newBuffer;
}
}
get uint8Buffer() {
return new Uint8Array(
this.buffer.buffer,
this.buffer.byteOffset,
this.size
);
}
getBuffer() {
return this.buffer;
}
writeFloat32(value) {
this.ensureAvailable(4);
const view = new DataView(this.buffer.buffer, this.buffer.byteOffset);
view.setFloat32(this.writeIndex, value, this.endian === Endian.Little);
this.writeIndex += 4;
}
writeFloat64(value) {
this.ensureAvailable(8);
const view = new DataView(this.buffer.buffer, this.buffer.byteOffset);
view.setFloat64(this.writeIndex, value, this.endian === Endian.Little);
this.writeIndex += 8;
}
writeInt8(value) {
this.ensureAvailable(1);
this.buffer[this.writeIndex] = value;
this.writeIndex += 1;
}
writeUint8(value) {
this.ensureAvailable(1);
this.buffer[this.writeIndex] = value;
this.writeIndex += 1;
}
writeInt16(value) {
this.ensureAvailable(2);
const view = new DataView(this.buffer.buffer, this.buffer.byteOffset);
view.setInt16(this.writeIndex, value, this.endian === Endian.Little);
this.writeIndex += 2;
}
writeUint16(value) {
this.ensureAvailable(2);
const view = new DataView(this.buffer.buffer, this.buffer.byteOffset);
view.setUint16(this.writeIndex, value, this.endian === Endian.Little);
this.writeIndex += 2;
}
writeInt32(value) {
this.ensureAvailable(4);
const view = new DataView(this.buffer.buffer, this.buffer.byteOffset);
view.setInt32(this.writeIndex, value, this.endian === Endian.Little);
this.writeIndex += 4;
}
writeUint32(value) {
this.ensureAvailable(4);
const view = new DataView(this.buffer.buffer, this.buffer.byteOffset);
view.setUint32(this.writeIndex, value, this.endian === Endian.Little);
this.writeIndex += 4;
}
writeInt64(value) {
this.ensureAvailable(8);
const view = new DataView(this.buffer.buffer, this.buffer.byteOffset);
view.setBigInt64(this.writeIndex, value, this.endian === Endian.Little);
this.writeIndex += 8;
}
writeUint64(value) {
this.ensureAvailable(8);
const view = new DataView(this.buffer.buffer, this.buffer.byteOffset);
view.setBigUint64(this.writeIndex, value, this.endian === Endian.Little);
this.writeIndex += 8;
}
write(bytes, length) {
length = length ? length : bytes.length;
this.ensureAvailable(length);
this.buffer.set(bytes, this.writeIndex);
this.writeIndex += length;
}
writeVarUint(value) {
let size = Math.ceil(value.toString(2).length / 7);
let index = 0;
let i = 0;
while (i < size) {
let part = value & 0x7f;
value >>= 7;
this.variableEncodeList[index++] = part;
i += 1;
}
for (let i = 0; i < index - 1; i++) {
this.variableEncodeList[i] |= 0x80;
}
this.write(this.variableEncodeList.subarray(0, index));
}
writeString(value, explicitLength = true) {
const encoder = new TextEncoder();
const bytes = encoder.encode(value);
if (explicitLength) {
this.writeVarUint(bytes.length);
}
this.write(bytes);
}
}
function createRiveFile() {
const writer = new BinaryWriter();
createHeader(writer);
createArtboard(writer); // 0
createShape(writer); // 1
createRectangle(writer); // 2
createSolidColor(writer, 5); // 3
createSolidColor(writer, 6); // 4
createFill(writer, 1); // 5
createFill(writer, 0); // 6
createAnimation(writer); // 7
createKeyedObject(writer, 2); // 8
createKeyedProperty(writer, 8);
createKeyFrameDouble(writer, 20, true);
createKeyFrameDouble(writer, 320);
return writer.uint8Buffer;
}
function createKeyedObject(writer, parentId) {
writer.writeVarUint(25);
writer.writeVarUint(51);
writer.writeVarUint(parentId);
writer.writeVarUint(0); //
}
function createKeyedProperty(writer, parentId) {
writer.writeVarUint(26);
writer.writeVarUint(53);
writer.writeVarUint(13); // animating the x property
writer.writeVarUint(0); //
}
function createKeyFrameDouble(writer, doubleValue, isFirst = false) {
// value of the property being animated is represented by its data type
// in this case since x is a double we create a KeyFrameDouble node
writer.writeVarUint(30);
if (isFirst) {
writer.writeVarUint(67); // frame number
writer.writeVarUint(0);
} else {
writer.writeVarUint(67); // frame number
writer.writeVarUint(60);
}
writer.writeVarUint(68);
writer.writeVarUint(1); // interpolation type
writer.writeVarUint(70);
writer.writeFloat32(doubleValue);
writer.writeVarUint(0); //
}
function createFill(writer, parentId) {
writer.writeVarUint(20); // fill type key
writer.writeVarUint(5); // parentId core key
writer.writeVarUint(parentId); // parent of this fill is the artboard
writer.writeVarUint(0); // done writing backboard
}
function createSolidColor(writer, parentId) {
writer.writeVarUint(18); // SolidColor type key
writer.writeVarUint(5); // parentId core key
writer.writeVarUint(parentId); // parent of this SolidColor is the fill
writer.writeVarUint(37); // colorValue core key
if (parentId === 5) writer.writeUint32(0xffffffff);
else writer.writeUint32(0xff313131);
writer.writeVarUint(0); // done writing backboard
}
function generateBitArray(backingFieldTypes, writer) {
const numProperties = backingFieldTypes.length;
const numElements = Math.ceil(numProperties / 16); // 16 backing types fit in each Uint32 element
const bitArray = new Uint32Array(numElements);
let currentElement = 0;
let backingTypesEncoded = 0;
let elementIndex = 0;
for (let i = 0; i < numProperties; i++) {
const typeCode = backingFieldTypes[i];
if (typeCode === undefined) {
throw new Error(`Invalid backing field type: ${type}`);
}
currentElement = (currentElement << 2) | typeCode;
backingTypesEncoded++;
if (backingTypesEncoded === 16) {
writer.writeUint32(currentElement);
bitArray[elementIndex] = currentElement;
currentElement = 0;
backingTypesEncoded = 0;
elementIndex++;
}
}
if (backingTypesEncoded > 0) {
currentElement = currentElement << ((16 - backingTypesEncoded) * 2);
bitArray[elementIndex] = currentElement;
writer.writeUint32(currentElement);
}
return bitArray;
}
function createHeader(writer) {
const fingerprint = new Uint8Array([0x52, 0x49, 0x56, 0x45]);
writer.write(fingerprint);
// major v, minor v, fileId
[7, 0, 731418].forEach((x) => writer.writeVarUint(x));
writer.writeVarUint(0);
}
function createArtboard(writer) {
writer.writeVarUint(23); // backboard
writer.writeVarUint(0); // done writing backboard
writer.writeVarUint(1); // Artboard type key
writer.writeVarUint(4); // name type key
writer.writeString("a");
writer.writeVarUint(7); // Width type key
writer.writeFloat32(400); // Width
writer.writeVarUint(8); // height type key
writer.writeFloat32(100); // height
writer.writeVarUint(0);
}
function createRectangle(writer) {
writer.writeVarUint(7);
writer.writeVarUint(5); // parent type key
writer.writeVarUint(1); //
writer.writeVarUint(13); // x
writer.writeFloat32(2333); // x value
writer.writeVarUint(14); // y
writer.writeFloat32(25); // y value
writer.writeVarUint(20); // Width type key
writer.writeFloat32(50); // Width
writer.writeVarUint(21); // height type key
writer.writeFloat32(50); // height
writer.writeVarUint(31); // border radius
writer.writeFloat32(50);
writer.writeVarUint(123); // x origin type key
writer.writeFloat32(0); // x
writer.writeVarUint(124); // y origin type key
writer.writeFloat32(0); // y
writer.writeVarUint(0);
}
function createShape(writer) {
writer.writeVarUint(3);
writer.writeVarUint(4);
writer.writeString("r");
writer.writeVarUint(5);
writer.writeVarUint(0);
writer.writeVarUint(0);
}
function createAnimation(writer) {
writer.writeVarUint(31);
writer.writeVarUint(55);
writer.writeString("xzz");
writer.writeVarUint(59);
writer.writeVarUint(2);
writer.writeVarUint(0);
}
function downloadRivFile(uint8Array) {
const blob = new Blob([uint8Array], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `temp.riv`;
a.click();
URL.revokeObjectURL(url);
}
function HomeAnimation() {
let fileBytes = createRiveFile();
// downloadRivFile(fileBytes);
const { RiveComponent } = useRive({
buffer: fileBytes,
artboard: "a",
autoplay: true,
});
return <RiveComponent />;
}
export const RiveDemo = () => {
return <HomeAnimation />;
};
export default function App() {
return (
<div className="RiveContainer">
<RiveDemo />
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment