UNPKG

molstar

Version:

A comprehensive macromolecular library.

400 lines (399 loc) 11.5 kB
/** * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * Adapted and converted to TypeScript from https://github.com/image-js/iobuffer * MIT License, Copyright (c) 2015 Michaël Zasso * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ const defaultByteLength = 1024 * 8; const charArray = []; /** * Class for writing and reading binary data */ export class IOBuffer { /** * If it's a number, it will initialize the buffer with the number as * the buffer's length. If it's undefined, it will initialize the buffer * with a default length of 8 Kb. If its an ArrayBuffer, a TypedArray, * it will create a view over the underlying ArrayBuffer. */ constructor(data, params = {}) { this._mark = 0; this._marks = []; this.offset = 0; // The current offset of the buffer's pointer this.littleEndian = true; let dataIsGiven = false; if (data === undefined) { data = defaultByteLength; } if (typeof data === 'number') { data = new ArrayBuffer(data); } else { dataIsGiven = true; } const offset = params.offset ? params.offset >>> 0 : 0; const byteLength = data.byteLength - offset; let dvOffset = offset; if (!(data instanceof ArrayBuffer)) { if (data.byteLength !== data.buffer.byteLength) { dvOffset = data.byteOffset + offset; } data = data.buffer; } if (dataIsGiven) { this._lastWrittenByte = byteLength; } else { this._lastWrittenByte = 0; } this.buffer = data; this.length = byteLength; this.byteLength = byteLength; this.byteOffset = dvOffset; this._data = new DataView(this.buffer, dvOffset, byteLength); } /** * Checks if the memory allocated to the buffer is sufficient to store more bytes after the offset * @param byteLength The needed memory in bytes */ available(byteLength = 1) { return (this.offset + byteLength) <= this.length; } /** * Check if little-endian mode is used for reading and writing multi-byte values * Returns true if little-endian mode is used, false otherwise */ isLittleEndian() { return this.littleEndian; } /** * Set little-endian mode for reading and writing multi-byte values */ setLittleEndian() { this.littleEndian = true; return this; } /** * Check if big-endian mode is used for reading and writing multi-byte values * Returns true if big-endian mode is used, false otherwise */ isBigEndian() { return !this.littleEndian; } /** * Switches to big-endian mode for reading and writing multi-byte values */ setBigEndian() { this.littleEndian = false; return this; } /** * Move the pointer n bytes forward */ skip(n) { if (n === undefined) n = 1; this.offset += n; return this; } /** * Move the pointer to the given offset */ seek(offset) { this.offset = offset; return this; } /** * Store the current pointer offset. */ mark() { this._mark = this.offset; return this; } /** * Move the pointer back to the last pointer offset set by mark */ reset() { this.offset = this._mark; return this; } /** * Push the current pointer offset to the mark stack */ pushMark() { this._marks.push(this.offset); return this; } /** * Pop the last pointer offset from the mark stack, and set the current pointer offset to the popped value */ popMark() { const offset = this._marks.pop(); if (offset === undefined) throw new Error('Mark stack empty'); this.seek(offset); return this; } /** * Move the pointer offset back to 0 */ rewind() { this.offset = 0; return this; } /** * Make sure the buffer has sufficient memory to write a given byteLength at the current pointer offset * If the buffer's memory is insufficient, this method will create a new buffer (a copy) with a length * that is twice (byteLength + current offset) */ ensureAvailable(byteLength) { if (byteLength === undefined) byteLength = 1; if (!this.available(byteLength)) { const lengthNeeded = this.offset + byteLength; const newLength = lengthNeeded * 2; const newArray = new Uint8Array(newLength); newArray.set(new Uint8Array(this.buffer)); this.buffer = newArray.buffer; this.length = this.byteLength = newLength; this._data = new DataView(this.buffer); } return this; } /** * Read a byte and return false if the byte's value is 0, or true otherwise * Moves pointer forward */ readBoolean() { return this.readUint8() !== 0; } /** * Read a signed 8-bit integer and move pointer forward */ readInt8() { return this._data.getInt8(this.offset++); } /** * Read an unsigned 8-bit integer and move pointer forward */ readUint8() { return this._data.getUint8(this.offset++); } /** * Alias for {@link IOBuffer#readUint8} */ readByte() { return this.readUint8(); } /** * Read n bytes and move pointer forward. */ readBytes(n) { if (n === undefined) n = 1; const bytes = new Uint8Array(n); for (let i = 0; i < n; i++) { bytes[i] = this.readByte(); } return bytes; } /** * Read a 16-bit signed integer and move pointer forward */ readInt16() { const value = this._data.getInt16(this.offset, this.littleEndian); this.offset += 2; return value; } /** * Read a 16-bit unsigned integer and move pointer forward */ readUint16() { const value = this._data.getUint16(this.offset, this.littleEndian); this.offset += 2; return value; } /** * Read a 32-bit signed integer and move pointer forward */ readInt32() { const value = this._data.getInt32(this.offset, this.littleEndian); this.offset += 4; return value; } /** * Read a 32-bit unsigned integer and move pointer forward */ readUint32() { const value = this._data.getUint32(this.offset, this.littleEndian); this.offset += 4; return value; } /** * Read a 32-bit floating number and move pointer forward */ readFloat32() { const value = this._data.getFloat32(this.offset, this.littleEndian); this.offset += 4; return value; } /** * Read a 64-bit floating number and move pointer forward */ readFloat64() { const value = this._data.getFloat64(this.offset, this.littleEndian); this.offset += 8; return value; } /** * Read 1-byte ascii character and move pointer forward */ readChar() { return String.fromCharCode(this.readInt8()); } /** * Read n 1-byte ascii characters and move pointer forward */ readChars(n = 1) { charArray.length = n; for (let i = 0; i < n; i++) { charArray[i] = this.readChar(); } return charArray.join(''); } /** * Write 0xff if the passed value is truthy, 0x00 otherwise */ writeBoolean(value = false) { this.writeUint8(value ? 0xff : 0x00); return this; } /** * Write value as an 8-bit signed integer */ writeInt8(value) { this.ensureAvailable(1); this._data.setInt8(this.offset++, value); this._updateLastWrittenByte(); return this; } /** * Write value as a 8-bit unsigned integer */ writeUint8(value) { this.ensureAvailable(1); this._data.setUint8(this.offset++, value); this._updateLastWrittenByte(); return this; } /** * An alias for IOBuffer#writeUint8 */ writeByte(value) { return this.writeUint8(value); } /** * Write bytes */ writeBytes(bytes) { this.ensureAvailable(bytes.length); for (let i = 0; i < bytes.length; i++) { this._data.setUint8(this.offset++, bytes[i]); } this._updateLastWrittenByte(); return this; } /** * Write value as an 16-bit signed integer */ writeInt16(value) { this.ensureAvailable(2); this._data.setInt16(this.offset, value, this.littleEndian); this.offset += 2; this._updateLastWrittenByte(); return this; } /** * Write value as a 16-bit unsigned integer */ writeUint16(value) { this.ensureAvailable(2); this._data.setUint16(this.offset, value, this.littleEndian); this.offset += 2; this._updateLastWrittenByte(); return this; } /** * Write a 32-bit signed integer at the current pointer offset */ writeInt32(value) { this.ensureAvailable(4); this._data.setInt32(this.offset, value, this.littleEndian); this.offset += 4; this._updateLastWrittenByte(); return this; } /** * Write a 32-bit unsigned integer at the current pointer offset */ writeUint32(value) { this.ensureAvailable(4); this._data.setUint32(this.offset, value, this.littleEndian); this.offset += 4; this._updateLastWrittenByte(); return this; } /** * Write a 32-bit floating number at the current pointer offset */ writeFloat32(value) { this.ensureAvailable(4); this._data.setFloat32(this.offset, value, this.littleEndian); this.offset += 4; this._updateLastWrittenByte(); return this; } /** * Write a 64-bit floating number at the current pointer offset */ writeFloat64(value) { this.ensureAvailable(8); this._data.setFloat64(this.offset, value, this.littleEndian); this.offset += 8; this._updateLastWrittenByte(); return this; } /** * Write the charCode of the passed string's first character to the current pointer offset */ writeChar(str) { return this.writeUint8(str.charCodeAt(0)); } /** * Write the charCodes of the passed string's characters to the current pointer offset */ writeChars(str) { for (let i = 0; i < str.length; i++) { this.writeUint8(str.charCodeAt(i)); } return this; } /** * Export a Uint8Array view of the internal buffer. * The view starts at the byte offset and its length * is calculated to stop at the last written byte or the original length. */ toArray() { return new Uint8Array(this.buffer, this.byteOffset, this._lastWrittenByte); } /** * Update the last written byte offset */ _updateLastWrittenByte() { if (this.offset > this._lastWrittenByte) { this._lastWrittenByte = this.offset; } } }