yahoo/pngjs-image

View on GitHub
lib/png/utils/bufferedStream.js

Summary

Maintainability
A
1 hr
Test Coverage
// Copyright 2015 Yahoo! Inc.
// Copyrights licensed under the Mit License. See the accompanying LICENSE file for terms.

/**
 * @class BufferedStream
 * @module PNG
 * @submodule PNGCore
 * @param {Buffer} [data] Data buffer
 * @param {int|boolean} [offset=0] Offset within the data (With offset = false, they data will be taken as-is)
 * @param {int} [length=data.length-offset] Length of the data
 * @constructor
 */
var BufferedStream = function (data, offset, length) {

    this.readOffset = 0;
    this.writeOffset = 0;

    this.readCounter = 0;
    this.writeCounter = 0;

    if (data) {
        if (offset === false) {
            this._data = data;
            this.writeOffset = data.length;
        } else {
            this._data = new Buffer((length || (data.length - (offset || 0))) * 2);
            this.writeBuffer(data, offset, length);
        }
    } else {
        if (length === 0) {
            this._data = new Buffer();
        } else {
            this._data = new Buffer(length || 1024 * 500);
        }
    }
};

Object.defineProperty(BufferedStream.prototype, 'length', {
    enumerable: false,
    configurable: false,
    get: function () {
        return this.writeOffset - this.readOffset;
    },
    set: function (length) {
        if (this.readOffset + length > this.writeOffset) {
            throw new Error('Length beyond the write pointer is not allowed.');
        }
        this.writeOffset = this.readOffset + length;
    }
});

/**
 * Returns the number of bytes left before resizing
 *
 * @method getSpaceLeft
 * @return {int}
 */
BufferedStream.prototype.getSpaceLeft = function () {
    return this._data.length - this.writeOffset;
};


/**
 * Checks if a read goes beyond the write pointer, reaching out of bounds
 *
 * @method _readCheck
 * @param {int} size Size of the data that is pending to be read from the stream
 * @private
 */
BufferedStream.prototype._readCheck = function (size) {
    if (this.readOffset + size > this.writeOffset) {
        throw new Error('Reading out of bounds.');
    }
};

/**
 * Skips a number of bytes
 *
 * @param {int} count Number of bytes to skip
 */
BufferedStream.prototype.skip = function (count) {
    this.readOffset += count;
    this.readCounter += count;
};

/**
 * @method readUInt8
 * @param {boolean} [noAssert=false]
 * @return {int}
 */
/**
 * @method peekUInt8
 * @param {boolean} [noAssert=false]
 * @return {int}
 */
[
    ['UInt16LE', 2],
    ['UInt16BE', 2],
    ['UInt32LE', 4],
    ['UInt32BE', 4],
    ['Int8', 1],
    ['Int16LE', 2],
    ['Int16BE', 2],
    ['Int32LE', 4],
    ['Int32BE', 4],
    ['FloatLE', 4],
    ['FloatBE', 4],
    ['DoubleLE', 8],
    ['DoubleBE', 8]
].forEach(function (fnInfo) {

        ['read', 'peek'].forEach(function (prefix) {
            var relayedMethodName = 'read' + fnInfo[0],
                methodName = prefix + fnInfo[0],
                peeking = (prefix === 'peek'),
                size = fnInfo[1];

            BufferedStream.prototype[methodName] = function (noAssert) {
                var value;

                this._readCheck(size);

                value = this._data[relayedMethodName](this.readOffset, noAssert);
                if (!peeking) {
                    this.readOffset += size;
                    this.readCounter += size;
                }

                return value;
            }
        });
    });


BufferedStream.prototype.peekUInt8 = function () {
    return this._data[this.readOffset];
};

BufferedStream.prototype.readUInt8 = function () {
    var result = this._data[this.readOffset];
    this.readOffset++;
    this.readCounter++;
    return result;
};

BufferedStream.prototype.writeUInt8 = function (value) {
    this._writeCheck(1);
    this._data[this.writeOffset] = value & 0xff;
    this.writeOffset++;
    this.writeCounter++;
};


BufferedStream.prototype.peekUInt16BE = function () {
    return (this._data[this.readOffset] << 8) | this._data[this.readOffset + 1]
};

BufferedStream.prototype.readUInt16BE = function () {
    var result = (this._data[this.readOffset] << 8) | this._data[this.readOffset + 1];
    this.readOffset += 2;
    this.readCounter += 2;
    return result;
};

BufferedStream.prototype.writeUInt16BE = function (value) {
    this._writeCheck(2);
    this._data[this.writeOffset] = (value >>> 8) & 0xff;
    this._data[this.writeOffset + 1] = value & 0xff;
    this.writeOffset += 2;
    this.writeCounter += 2;
};


//BufferedStream.prototype.peekUInt32BE = function () {
//    return (this._data[this.readOffset] * 0x1000000) +
//        ((this._data[this.readOffset + 1] << 16) |
//        (this._data[this.readOffset + 2] << 8) |
//        this._data[this.readOffset + 3]);
//};
//
//BufferedStream.prototype.readUInt32BE = function () {
//    var result = this.peekUInt32BE();
//    this.readOffset += 4;
//    this.readCounter += 4;
//    return result;
//};

BufferedStream.prototype.writeUInt32BE = function (value) {
    this._writeCheck(4);
    this._data[this.writeOffset] = (value >>> 24) & 0xff;
    this._data[this.writeOffset + 1] = (value >>> 16) & 0xff;
    this._data[this.writeOffset + 2] = (value >>> 8) & 0xff;
    this._data[this.writeOffset + 3] = value & 0xff;
    this.writeOffset += 4;
    this.writeCounter += 4;
};


/**
 * Reads a string from the stream without moving the read pointer
 *
 * @method peekString
 * @param {int} size Number of bytes to read from stream
 * @param {string} [encoding='utf8'] Encoding of string
 * @return {string}
 */
BufferedStream.prototype.peekString = function (size, encoding) {
    this._readCheck(size);
    return this._data.toString(encoding || 'utf8', this.readOffset, this.readOffset + size);
};

/**
 * Reads a string from the stream
 *
 * @method readString
 * @param {int} size Number of bytes to read from stream
 * @param {string} [encoding='utf8'] Encoding of string
 * @return {string}
 */
BufferedStream.prototype.readString = function (size, encoding) {
    var result = this.peekString(size, encoding);
    this.readOffset += size;
    this.readCounter += size;
    return result;
};


/**
 * Reads data from the stream into a buffer without moving the read pointer
 *
 * @method peekBuffer
 * @param {int} size Number of bytes that should be read from the stream
 * @returns {Buffer}
 */
BufferedStream.prototype.peekBuffer = function (size) {
    var buffer;

    this._readCheck(size);

    buffer = new Buffer(size);
    this._data.copy(buffer, 0, this.readOffset, this.readOffset + size);

    return buffer;
};

/**
 * Reads data from the stream into a buffer
 *
 * @method readBuffer
 * @param {int} size Number of bytes that should be read from the stream
 * @return {Buffer}
 */
BufferedStream.prototype.readBuffer = function (size) {
    var buffer = this.peekBuffer(size);
    this.readOffset += size;
    this.readCounter += size;
    return buffer;
};


/**
 * Reads data from the stream into a buffer
 *
 * @method peekBufferedStream
 * @param {BufferedStream} stream Stream to write to
 * @param {int} size Number of bytes that should be read
 */
BufferedStream.prototype.peekBufferedStream = function (stream, size) {
    stream.writeBufferedStream(this, size);
};

/**
 * Reads data from the stream into a buffer
 *
 * @method readBufferedStream
 * @param {BufferedStream} stream Stream to write to
 * @param {int} size Number of bytes that should be read
 */
BufferedStream.prototype.readBufferedStream = function (stream, size) {
    this.peekBufferedStream(stream, size);
    this.readOffset += size;
    this.readCounter += size;
};


/**
 * Checks if a write needs a re-size
 *
 * @method _writeCheck
 * @param {int} size Size that is pending to be written to the stream
 * @private
 */
BufferedStream.prototype._writeCheck = function (size) {
    if (this._data.length < this.writeOffset + size) {
        this._resize();
    }
};

/**
 * Re-sizes the internal buffer, copying all existing data into it
 *
 * @method _resize
 * @private
 */
BufferedStream.prototype._resize = function () {
    var buffer = new Buffer(this._data.length * 2);
    this._data.copy(buffer, 0, 0, this.writeOffset);
    this._data = buffer;
};

/**
 * @method writeUInt8
 * @param {boolean} [noAssert=false]
 */
[
    ['writeUInt16LE', 2],
    ['writeUInt32LE', 4],
    ['writeInt8', 1],
    ['writeInt16LE', 2],
    ['writeInt16BE', 2],
    ['writeInt32LE', 4],
    ['writeInt32BE', 4],
    ['writeFloatLE', 4],
    ['writeFloatBE', 4],
    ['writeDoubleLE', 8],
    ['writeDoubleBE', 8]
].forEach(function (fnInfo) {
        BufferedStream.prototype[fnInfo[0]] = function (value, noAssert) {
            this._writeCheck(fnInfo[1]);
            this._data[fnInfo[0]](value, this.writeOffset, noAssert);
            this.writeOffset += fnInfo[1];
            this.writeCounter += fnInfo[1];
        }
    });

/**
 * Writes a string to the stream
 *
 * @method writeASCIIString
 * @param {string} text Text to write to the stream
 */
BufferedStream.prototype.writeASCIIString = function (text) {

    this._writeCheck(text.length);

    for(var i = 0, len = text.length; i < len; i++) {
        this._data.writeUInt8(text.charCodeAt(i) & 0xff, this.writeOffset + i);
    }
    this.writeOffset += text.length;
    this.writeCounter += text.length;
};

/**
 * Writes a string to the stream
 *
 * @method writeString
 * @param {string} text Text to write to the stream
 * @param {string} [encoding='utf8'] Encoding of string
 */
BufferedStream.prototype.writeString = function (text, encoding) {
    this.writeBuffer(new Buffer(text, encoding || 'utf8'));
};

/**
 * Writes a buffer to the stream
 *
 * @method writeBuffer
 * @param {Buffer} buffer Data buffer
 * @param {int} [offset=0] Offset within buffer
 * @param {int} [length=buffer.length-offset] length Length of buffer
 */
BufferedStream.prototype.writeBuffer = function (buffer, offset, length) {
    var localOffset = offset || 0,
          localLength = length || (buffer.length - localOffset);

    this._writeCheck(localLength);

    buffer.copy(this._data, this.writeOffset, localOffset, localOffset + localLength);
    this.writeOffset += localLength;
    this.writeCounter += localLength;
};

/**
 * Writes a buffered stream into the stream
 *
 * @param {BufferedStream} stream
 * @param {int} [length=stream.length] Length of buffered data
 */
BufferedStream.prototype.writeBufferedStream = function (stream, length) {
    var len = (length || stream.length);

    this._writeCheck(len);

    stream._data.copy(this._data, this.writeOffset, stream.readOffset, stream.readOffset + len);
    this.writeOffset += len;
    this.writeCounter += len;
};


/**
 * Clones the stream with the current internal state
 *
 * @method clone
 * @return {BufferedStream} Cloned stream
 */
BufferedStream.prototype.clone = function () {
    return this.slice();
};

/**
 * Slices the stream
 *
 * @method slice
 * @param {int} [start=0] Start of stream
 * @param {int} [end=this.length] End of stream
 * @return {BufferedStream} Sliced stream
 */
BufferedStream.prototype.slice = function (start, end) {

    var stream,
        localStart = start || 0,
        localEnd = end || this._data.length;

    if (localStart > localEnd) {
        throw new Error('End cannot be smaller than start.');
    }

    stream = new BufferedStream(this._data, false);

    stream.writeOffset = this.readOffset + localEnd;
    stream.readOffset = this.readOffset + localStart;

    stream.readCounter = this.readCounter;
    stream.writeCounter = this.writeCounter;

    return stream;
};

/**
 * Converts contents to a buffer without moving the read pointer
 *
 * @method toBuffer
 * @param {boolean} [noCopy=false]
 * @return {Buffer}
 */
BufferedStream.prototype.toBuffer = function (noCopy) {
    if (noCopy) {
        return this._data;
    } else {
        return this.peekBuffer(this.length);
    }
};


module.exports = BufferedStream;