import { isNotArrayBuffer, isNotBoolean, isNotNumber, isNull, isUndefined } from 'itee-validators' import { Byte, Endianness } from './TBinaryReader' /** * @author [Tristan Valcke]{@link https://github.com/Itee} * @license [BSD-3-Clause]{@link https://opensource.org/licenses/BSD-3-Clause} */ class TBinaryWriter { /** * * @returns {*} */ get buffer () { return this._buffer } set buffer ( value ) { const memberName = 'Buffer' const expect = 'Expect an instance of ArrayBuffer.' if ( isNull( value ) ) { throw new TypeError( `${ memberName } cannot be null ! ${ expect }` ) } if ( isUndefined( value ) ) { throw new TypeError( `${ memberName } cannot be undefined ! ${ expect }` ) } if ( isNotArrayBuffer( value ) ) { throw new TypeError( `${ memberName } cannot be an instance of ${ value.constructor.name } ! ${ expect }` ) } this._buffer = value this._offset = 0 this._length = value.byteLength this._updateDataView() } /** * * @returns {*} */ get offset () { return this._offset } set offset ( value ) { const memberName = 'Offset' const expect = 'Expect a number.' if ( isNull( value ) ) { throw new TypeError( `${ memberName } cannot be null ! ${ expect }` ) } if ( isUndefined( value ) ) { throw new TypeError( `${ memberName } cannot be undefined ! ${ expect }` ) } if ( isNotNumber( value ) ) { throw new TypeError( `${ memberName } cannot be an instance of ${ value.constructor.name } ! ${ expect }` ) } this._offset = value this._updateDataView() } /** * * @param value */ get length () { return this._length } set length ( value ) { const memberName = 'Length' const expect = 'Expect a number.' if ( isNull( value ) ) { throw new TypeError( `${ memberName } cannot be null ! ${ expect }` ) } if ( isUndefined( value ) ) { throw new TypeError( `${ memberName } cannot be undefined ! ${ expect }` ) } if ( isNotNumber( value ) ) { throw new TypeError( `${ memberName } cannot be an instance of ${ value.constructor.name } ! ${ expect }` ) } this._length = value this._updateDataView() } /** * * @returns {*} */ get endianness () { return this._endianness } set endianness ( value ) { const memberName = 'Endianness' const expect = 'Expect a boolean.' if ( isNull( value ) ) { throw new TypeError( `${ memberName } cannot be null ! ${ expect }` ) } if ( isUndefined( value ) ) { throw new TypeError( `${ memberName } cannot be undefined ! ${ expect }` ) } if ( isNotBoolean( value ) ) { throw new TypeError( `${ memberName } cannot be an instance of ${ value.constructor.name } ! ${ expect }` ) } this._endianness = value } constructor ( { buffer = new ArrayBuffer( 1024 ), endianness = Endianness.Little } = {} ) { this.buffer = buffer this.endianness = endianness // Todo: For bit writing use same approche than byte // this._bits = { // buffer: null, // offset: 0, // length: 0 // } this._updateDataView() } /** * * @param buffer * @param offset * @param length * @returns {TBinaryReader} */ setBuffer ( buffer, offset, length ) { this.buffer = buffer this.offset = offset || 0 this.length = length || buffer.byteLength return this } /** * * @param value * @returns {TBinaryReader} */ setOffset ( value ) { this.offset = value return this } /** * * @param value * @returns {TBinaryReader} */ setLength ( value ) { this.length = value return this } /** * * @param endianess * @returns {TBinaryReader} */ setEndianess ( endianess ) { this.endianness = endianess return this } /** * * @param increment * @returns {*} * @private */ _getAndUpdateOffsetBy ( increment ) { const currentOffset = this._offset this._offset += increment // Manage buffer overflow, and auto-extend if needed const bufferByteLength = this._buffer.byteLength if ( this._offset >= bufferByteLength ) { // Create new buffer and view with extended size const newBuffer = new ArrayBuffer( bufferByteLength + 1024 ) const newDataView = new DataView( newBuffer, 0, newBuffer.byteLength ) // Copy data from current to new for ( let i = 0 ; i < bufferByteLength ; i++ ) { newDataView.setUint8( i, this._dataView.getUint8( i ) ) } // Update local this._buffer = newBuffer this._dataView = newDataView } return currentOffset } /** * * @private */ _updateDataView () { this._dataView = new DataView( this._buffer, this._offset, this._length ) } /** * * @returns {boolean} */ isEndOfFile () { return ( this._offset === this._length ) } _iterArray ( values, func, moveNext ) { const currentOffset = this._offset for ( let value of values ) { func( value ) } if ( !moveNext ) { this._offset = currentOffset } } // Bytes /** * * @param offset */ skipOffsetTo ( offset ) { this._offset = offset } /** * * @param nBytes */ skipOffsetOf ( nBytes ) { this._offset += nBytes } setBoolean ( value, moveNext = true ) { this.setUint8( ( ( value & 1 ) === 1 ), moveNext ) } setBooleanArray ( values, moveNext = true ) { this._iterArray( values, this.setBoolean.bind( this ), moveNext ) } setInt8 ( value, moveNext = true ) { const offset = ( moveNext ) ? this._getAndUpdateOffsetBy( Byte.One ) : this._offset this._dataView.setInt8( offset, value ) } setInt8Array ( values, moveNext = true ) { this._iterArray( values, this.setInt8.bind( this ), moveNext ) } setUint8 ( value, moveNext = true ) { const offset = ( moveNext ) ? this._getAndUpdateOffsetBy( Byte.One ) : this._offset this._dataView.setUint8( offset, value ) } setUint8Array ( values, moveNext = true ) { this._iterArray( values, this.setUint8.bind( this ), moveNext ) } setInt16 ( value, moveNext = true ) { const offset = ( moveNext ) ? this._getAndUpdateOffsetBy( Byte.Two ) : this._offset this._dataView.setInt16( offset, value, this._endianness ) } setInt16Array ( values, moveNext = true ) { this._iterArray( values, this.setInt16.bind( this ), moveNext ) } setUint16 ( value, moveNext = true ) { const offset = ( moveNext ) ? this._getAndUpdateOffsetBy( Byte.Two ) : this._offset this._dataView.setUint16( offset, value, this._endianness ) } setUint16Array ( values, moveNext = true ) { this._iterArray( values, this.setUint16.bind( this ), moveNext ) } setInt32 ( value, moveNext = true ) { const offset = ( moveNext ) ? this._getAndUpdateOffsetBy( Byte.Four ) : this._offset this._dataView.setInt32( offset, value, this._endianness ) } setInt32Array ( values, moveNext = true ) { this._iterArray( values, this.setInt32.bind( this ), moveNext ) } setUint32 ( value, moveNext = true ) { const offset = ( moveNext ) ? this._getAndUpdateOffsetBy( Byte.Four ) : this._offset this._dataView.setUint32( offset, value, this._endianness ) } setUint32Array ( values, moveNext = true ) { this._iterArray( values, this.setUint32.bind( this ), moveNext ) } setInt64 ( /*value, moveNext = true*/ ) { // From THREE.FBXLoader // JavaScript doesn't support 64-bit integer so attempting to calculate by ourselves. // 1 << 32 will return 1 so using multiply operation instead here. // There'd be a possibility that this method returns wrong value if the value // is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER. // TODO: safely handle 64-bit integer using bigint ? throw new Error( 'Not implemented, sorry... any help is welcome !' ) } setInt64Array ( values, moveNext = true ) { this._iterArray( values, this.setInt64.bind( this ), moveNext ) } setUint64 ( /*value, moveNext = true*/ ) { // Note: see setInt64() comment throw new Error( 'Not implemented, sorry... any help is welcome !' ) } setUint64Array ( values, moveNext = true ) { this._iterArray( values, this.setUint64.bind( this ), moveNext ) } setFloat32 ( value, moveNext = true ) { const offset = ( moveNext ) ? this._getAndUpdateOffsetBy( Byte.Four ) : this._offset this._dataView.setFloat32( offset, value, this._endianness ) } setFloat32Array ( values, moveNext = true ) { this._iterArray( values, this.setFloat32.bind( this ), moveNext ) } setFloat64 ( value, moveNext = true ) { const offset = ( moveNext ) ? this._getAndUpdateOffsetBy( Byte.Eight ) : this._offset this._dataView.setFloat64( offset, value, this._endianness ) } setFloat64Array ( values, moveNext = true ) { this._iterArray( values, this.setFloat64.bind( this ), moveNext ) } setChar ( value, moveNext = true ) { this.setUint8( value.charCodeAt( 0 ), moveNext ) } setString ( values, moveNext = true ) { this._iterArray( values, this.setChar.bind( this ), moveNext ) } // setArrayBuffer ( value, moveNext = true ) { // // const offset = this._getAndUpdateOffsetBy( size ) // this._dataView.buffer.slice( offset, offset + size ) // // } } export { TBinaryWriter }