Created
April 10, 2024 00:30
-
-
Save iamtekeste/9705fc8c030188c7c73872dfdf6ef03c to your computer and use it in GitHub Desktop.
Rive file writer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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