Source: jPsdReader.js

((function(global){
    /***************************************
     * jEndianConverter
     ***************************************/
    /**
     * Toggle between little-endian and big-endian
     * @class jEndianConverter
     * @classdesc Help to convert endian.
     */
    global.jEndianConverter = {};
     /**
      * Toggle Int16 between little-endian and big-endian
      * @param {Int16} number - The number
      * @returns {Int16} number which converted
      */
    jEndianConverter.convertInt16 = function(n) {
        return (0xFF00 & (n << 8)) | (0x00FF & (n >> 8));
    };

    /**
    * Toggle Int32 between little-endian and big-endian
    * @param {Int32} number - The number
    * @returns {Int32} number which converted
    */
    jEndianConverter.convertInt32 = function(n) {
        return 0x00000000 |
        (0xFF000000 & (n << 24)) |
        (0x00FF0000 & (n << 8)) |
        (0x0000FF00 & (n >> 8)) |
        (0x000000FF & (n >> 24));
    };


    /***************************************
     * jBitConverter
     ***************************************/
     /**
     * Bit converter
     * @class jBitConverter
     * @classdesc Convert integer to byte array.
     */
    global.jBitConverter = {};
    /**
     * Convert Int16 to byte array
     * @param {Int16} number
     * @returns {byteArray} bytes array
     */
    jBitConverter.int16ToByteArray = function(int16) {
        return [0xFF & (int16 >> 8), 0xFF & int16];
    };

    /**
     * Convert Int32 to byte array
     * @param {Int32} number
     * @returns {byteArray} bytes array
     */
    jBitConverter.int32ToByteArray = function (int32) {
        return [0xFF & (int32 >> 24), 0xFF & (int32 >> 16), 0xFF & (int32 >> 8), 0xFF & int32];
    };


    /***************************************
     * jPsdReader
     ***************************************/
     /**
      * psd file reader
      * @class jPsdReader
      * @classdesc Create {@linkcode jPsdDocument} from file object.
      */
    global.jPsdReader = {};

    /**
     * Enum for compression
     * @memberof jPsdReader
     * @readonly
     * @enum {Number}
     */
    jPsdReader.Compression = {
        RAW: 0,
        RLE: 1,
        ZIP: 2,
        ZIPPRED: 3
    };



    /**
     * Create {@linkcode jPsdDocument} from psd file.
     * @param {Object} options - Options to receive callback.
     * @param {Object} options.file - The *.psd file object.
     * @param {function} options.success - This function will be called if the {@linkcode jPsdDocument} has been created successfully.
     * @param {function} options.error - This function will be called if the request has been failed.
     * @returns {@linkcode jPsdDocument} psd document object
	 * @example
	 *  var psdFile = document.getElementById("file").files[0];
	 *  jPsdReader.load({
     *      file: psdFile,
     *      success: function(psd) {
     *          alert(psd.width + " x " + psd.height);
     *          psd.dispose();
     *      },
     *      error: function(e) {
     *          alert(e.message);
     *      }
     *  });
     */
    jPsdReader.load = function(options) {
        options = options || {};

        // check params
        if(jPsdReader._checkParams(options) == false) { return; }

        var reader = new FileReader();
        reader.onload = function (e) {
            var bytes = null;
            try {
                bytes = new Uint8Array(reader.result);
                options.success(jPsdReader._load(bytes));
            } catch (e) {
                if (e.name != "jUserException") {
                    console.error(e.stack);
                }
                options.error(e);
            } finally {
                // release
                bytes = null;
            }
        };
        reader.onerror = function (evt) {
            options.error(new jUserException(evt, evt.target.error.code, 0x10000020));
        };
        reader.readAsArrayBuffer(options.file);
    };


    // check mandatory parameters
    jPsdReader._checkParams = function(options) {
        var file = options.file;
        var onsuccess = options.success;
        var onerror = options.error;

        if(file == null || onsuccess == null || onerror == null) {
            var message = "Mandatory parameter is missing.";
            if(onerror != null) { onerror(message); }
            throw new jUserException(null, message, 0x00000000);
            return false;
        }

        return true;
    }


    // deserialize psd file
    jPsdReader._load = function(bytes) {
        var reader = new jBinaryReader(bytes);
        try {
            // read file header section
            var fileHeader = jPsdReader._readFileHeader(reader);
            jPsdReader._validateFileHeader(fileHeader);
            //alert(JSON.stringify(fileHeader));

            // read color mode data section
            var colorModeDataSection = jPsdReader._readColorModeDataSection(reader);

            // read image resource section
            var imageResourceSection = jPsdReader._readImageResourceSection(reader);
            for(var i = 0; i < imageResourceSection.resources.length; i++) {
                imageResourceSection.resources[i];
            }

            // read layer and mask information section
            var layerAndMaskInfoSection = jPsdReader._readLayerAndMaskInformationSection(reader, fileHeader.version);

            // read image data section
            var imageDataSection = jPsdReader._readImageDataSection(reader);

            return new jPsdDocument({ 
                header:fileHeader,
                colorModeDataSection:colorModeDataSection,
                imageResourceSection:imageResourceSection,
                layerAndMaskInfoSection:layerAndMaskInfoSection,
                imageDataSection:imageDataSection
            });
        } finally {
            reader.dispose();
        }

    };


    //=========================
    // File Header Section
    //=========================
    jPsdReader._readFileHeader = function(jReader) {
        var FILE_HEADER_SECTION_SIZE = 26;
        if(jReader.getLength() < FILE_HEADER_SECTION_SIZE) {
            throw new jUserException(null, "This file is not psd format.", 0x30000010);
        }

        var header = jReader.readBytes(26);

        var reader = new jBinaryReader(header);
        try	{
            var signature = jEncoding.Ascii.getString(reader.readBytes(4));
            if(signature != "8BPS") {
                throw new jUserException(null, "This file is not psd format", 0x30000011);
            }
            var version = reader.readInt16("big");
            var reserved = reader.readBytes(6);
            var channelCount = reader.readInt16("big");
            var height = reader.readInt32("big");
            var width = reader.readInt32("big");
            var depth = reader.readInt16("big");
            var colorMode = reader.readInt16("big");
            
            return { signature: signature,
                version : version,
                reserved : reserved,
                channelCount : channelCount,
                height : height,
                width : width,
                depth : depth,
                colorMode : colorMode
            };
        } finally {
            reader.dispose();
        }
    };


    jPsdReader._validateFileHeader = function(fileHeader) {
        if(fileHeader.signature != "8BPS") {
            throw new jUserException(null, "This file is not psd format", 0x30000012);
        }
        
        if(fileHeader.version != 1 && fileHeader.version != 2) {
            throw new jUserException(null, "This version is not supported.", 0x30000020);
        }

        if((1 <= fileHeader.channelCount && fileHeader.channelCount <= 56) == false) {
            throw new jUserException(null, "The number of channel is too less or too many.", 0x30000030);
        }

        if((1 <= fileHeader.height && fileHeader.height <= (fileHeader.version == 1 ? 30000 : 300000)) == false) {
            throw new jUserException(null, "The height of image is too small or too big.", 0x30000040);
        }

        if((1 <= fileHeader.width && fileHeader.width <= (fileHeader.version == 1 ? 30000 : 300000)) == false) {
            throw new jUserException(null, "The width of image is too small or too big.", 0x30000050);
        }

        if((fileHeader.depth == 1 || fileHeader.depth == 8 || fileHeader.depth == 16 || fileHeader.depth == 32) == false) {
            throw new jUserException(null, "This depth is not supported.", 0x30000060);
        }

        if(((0 <= fileHeader.colorMode && fileHeader.colorMode <= 4) || 
            (7 <= fileHeader.colorMode && fileHeader.colorMode <= 9)) == false) {
            throw new jUserException(null, "This color mode is not supported", 0x30000070);
        }

        return true;
    };



    //=========================
    // Color Mode Data Section
    //=========================
    jPsdReader._readColorModeDataSection = function(jReader) {
        var length = jReader.readInt32("big");
        var data = [];
        if(length > 0) {
            data = jReader.readBytes(length);
        }

        return { length : length, data : data };
    };



    //=========================
    // Read Image Resource Section
    //=========================
    jPsdReader._readImageResourceSection = function(jReader) {
        var length = jReader.readInt32("big");
        var resources = [];
        if(length > 0) {
            var buffer = jReader.readBytes(length);
            var reader = new jBinaryReader(buffer);
            try {
                while(reader.getPosition() < reader.getLength()) {
                    resources.push(jPsdReader._readImageResourceBlock(reader));
                }				
            } finally {
                reader.dispose();
            }
        }

        return {length:length, resources:resources};
    };


    //--------------------------
    // Read Image Resource Block
    //--------------------------
    jPsdReader._readImageResourceBlock = function(jReader) {
        var signature = jEncoding.Ascii.getString(jReader.readBytes(4));
        if (signature != "8BIM") { throw new jUserException(null, "This file format is not psd.", 0x30000013); }

        var id = jReader.readInt16("big");
        var nameLength = jReader.readInt16("big");
        var name = jEncoding.BigEndianUnicode.getString(jReader.readBytes(nameLength));
        jReader.readBytes(nameLength % 2); // skip padding
        var dataLength = jReader.readInt32("big");
        var data = jReader.readBytes(dataLength);
        jReader.readBytes(dataLength % 2); // skip padding

        return { id:id, name:name, data:data };
    };



    //=========================
    // read layer and mask information section
    //=========================
    jPsdReader._readLayerAndMaskInformationSection = function (jReader, version) {
        var length = 0;
        if(version == 1) {	// psd
            length = jReader.readInt32("big");
        } else if(version == 2) { // psb
            length = jReader.readInt64("big");
        }

        var buffer = jReader.readBytes(length);
        var reader = new jBinaryReader(buffer);
        try {
            var layerInfo = jPsdReader._readLayerInfo(reader, version);
            var maskInfo = null;
            var additionalInfos = new Array();
            if (reader.getLength() > reader.getPosition) {
                maskInfo = jPsdReader._readGlobalLayerMaskInfo(reader);
                additionalInfos = jPsdReader._readAdditionalLayerInfos(reader, version);
            }
            
            return { layerInfo:layerInfo, maskInfo:maskInfo, additionalInfos:additionalInfos };
        } finally {
            reader.dispose();
        }
    };



    //----------------------------
    // read layer info
    //----------------------------
    jPsdReader._readLayerInfo = function(jReader, version) {
        var length = 0;
        if(version == 1) {	// psd
            length = jReader.readInt32("big");
        } else if(version == 2) {	// psb
            length = jReader.readInt64("big");
        }

        if(length <= 0) {
            return { length: length, layerCount:0, records: [], imageDatas: [], alphaContainsTransparency: false };
        }

        // read layer info section
        var buffer = jReader.readBytes(length);
        var reader = new jBinaryReader(buffer);
        try {
            // read layer count
            var layerCount = reader.readInt16("big");
            var alphaContainsTransparency = (layerCount < 0);
            var layerCount = Math.abs(layerCount);

            // read layer record
            var records = [];
            for(var i = 0; i < layerCount; i++) {
                records.push(jPsdReader._readLayerRecord(reader, version));
            }

            // read channel image data
            var imageDatas = [];
            for(var i = 0; i < layerCount; i++) {
                imageDatas.push(jPsdReader._readChannelImageData(reader, records[i], version));
            }

            return { length: length, layerCount: layerCount, records: records, imageDatas: imageDatas, alphaContainsTransparency: alphaContainsTransparency };
        } finally {
            reader.dispose();
        }
    };


    //----------------------------
    // read layer record
    //----------------------------
    jPsdReader._readLayerRecord = function(jReader, version) {
        // read dimention
        var top = jReader.readInt32("big");
        var left = jReader.readInt32("big");
        var bottom = jReader.readInt32("big");
        var right = jReader.readInt32("big");

        // number of channel in layer
        var numOfChannel = jReader.readInt16("big");

        // read channel info
        var channelList = [];
        for(var i = 0; i < numOfChannel; i++) {
            var id = jReader.readInt16("big");
            var dataLength = 0;
            if(version == 1) {	// psd
                dataLength = jReader.readInt32("big");
            } else if(version == 2) {	// psb
                dataLength = jReader.readInt32("big");
            }
            channelList.push({ id:id, dataLength:dataLength });
        }

        // read signature of blend
        var signature = jEncoding.Ascii.getString(jReader.readBytes(4));
        if(signature != "8BIM") {
            throw new jUserException(null, "This file is not psd format.", 0x30000014);
        }

        // read blend mode and etc.
        var blendModeKey = jReader.readBytes(4);
        var opacity = jReader.readByte();
        var clipping = jReader.readByte();
        var flags = jReader.readByte();
        var filter = jReader.readByte();

        // read length of extra data
        var lengthOfExtra = jReader.readInt32("big");

        // create reader
        var buffer = jReader.readBytes(lengthOfExtra);
        var reader = new jBinaryReader(buffer);
        try {
            var maskData = jPsdReader._readLayerMaskAdjustmentLayerData(reader);
            var blendingRanges = jPsdReader._readLayerBlendingRangesData(reader, numOfChannel);
            var lengthOfName = reader.readByte();
            var nameArray = reader.readBytes(lengthOfName);
            var name = jEncoding.Ascii.getString(nameArray);

            // skip padding
            if((lengthOfName + 1) % 4 != 0) {
                reader.readBytes(4 - ((lengthOfName + 1) % 4));
            }

            var additionoalInfos = jPsdReader._readAdditionalLayerInfos(reader, version);
            return { top:top, left:left, bottom:bottom, right:right, numOfChannel:numOfChannel,
                channelInfos: channelList, signature:signature, blendModeKey:blendModeKey,
                opacity:opacity, clipping:clipping, flags:flags, filter:filter,
                lengthOfExtra:lengthOfExtra, maskData:maskData, blendingRanges:blendingRanges,
                name:name, additionalInfos: additionoalInfos };
        } finally {
            reader.dispose();
        }
    };


    //----------------------------
    // read layer mask adjustment layer data
    //----------------------------
    /**
     * @typedef jLayerMaskAdjustmentLayerData
     * @type {Object}
     * @property {Number} length
     * @property {Number} bitmapTop
     * @property {Number} bitmapLeft
     * @property {Number} bitmapBottom
     * @property {Number} bitmapRight
     * @property {Byte} defaultColor
     * @property {Byte} flags
     * @property {Int16} padding
     * @property {Byte} realFlags
     * @property {Number} realUserMaskBackground
     * @property {Number} vectorTop
     * @property {Number} vectorLeft
     * @property {Number} vectorBottom
     * @property {Number} vectorRight
     */
    jPsdReader._readLayerMaskAdjustmentLayerData = function(jReader) {
        // read size of layer data
        var length = jReader.readInt32("big");
        if(length <= 0) {
            return { length:length, bitmapTop:0, bitmapLeft:0, bitmapBottom:0, bitmapRight:0,
                defaultColor:0x00, flags:0x00, padding:0x0000, realFlags:0x00, realUserMaskBackground:0x00,
                vectorTop:0, vectorLeft:0, vectorBottom:0, vectorRight:0 };
        }

        // create reader
        var buffer = jReader.readBytes(length);
        var reader = new jBinaryReader(buffer);
        try {
            var top = reader.readInt32("big");
            var left = reader.readInt32("big");
            var bottom = reader.readInt32("big");
            var right = reader.readInt32("big");
            var defaultColor = reader.readByte();
            var flags = reader.readByte();
            var padding = 0x0000;
            var realFlags = 0x00;
            var realUserMaskBackground = 0x00;
            var vectorTop = 0;
            var vectorLeft = 0;
            var vectorBottom = 0;
            var vectorRight = 0;
            if(length == 20) {
                padding = reader.readInt16("big");
            } else {
                realFlags = reader.readByte();
                realUserMaskBackground = reader.readByte();
                vectorLeft = reader.readInt32("big");
                vectorBottom = reader.readInt32("big");
                vectorTop = reader.readInt32("big");
                vectorRight = reader.readInt32("big");
            }

            return { length:length, bitmapTop:top, bitmapLeft:left, bitmapBottom:bottom, bitmapRight:right,
                defaultColor:defaultColor, flags:flags, padding:padding, realFlags:realFlags, realUserMaskBackground:realUserMaskBackground,
                vectorTop:vectorTop, vectorLeft:vectorLeft, vectorBottom:vectorBottom, vectorRight:vectorRight };
        } finally {
            reader.dispose();
        }
    };


    //----------------------------
    // read layer blending ranges data
    //----------------------------

    /**
     * @typedef jLayerBlendingRangesData
     * @type {Object}
     * @property {Number} length
     * @property {Number} compositeGrayBlendSource
     * @property {Number} compositeGrayBlendDestinationRange
     * @property {Array} {@linkcode jChannelRange} Array
     */

    /**
     * @typedef jChannelRange
     * @type {Object}
     * @property {Number} sourceRange
     * @property {Number} destRange
     */
    jPsdReader._readLayerBlendingRangesData = function(jReader, numOfChannel) {
        var length = jReader.readInt32("big");
        if(length <= 0) { 
            return { length:length,
                    compositeGrayBlendSource:0, compositeGrayBlendDestinationRange:0,
                    channelRanges:[]
                };
        }

        // read whole range data
        var buffer = jReader.readBytes(length);

        // read range data
        var reader = new jBinaryReader(buffer);
        try {
            var compositeGrayBlendSource = reader.readInt32("big");
            var compositeGrayBlendDestinationRange = reader.readInt32("big");

            var ranges = [];
            while(reader.getPosition() < reader.getLength()) {
                var sourceRange = reader.readInt32("big");
                var destRange = reader.readInt32("big");
                ranges.push({sourceRange:sourceRange, destRange:destRange });
            }

            return { length:length,
                    compositeGrayBlendSource:compositeGrayBlendSource, compositeGrayBlendDestinationRange:compositeGrayBlendDestinationRange,
                    channelRanges:ranges
                };
        } finally {
            reader.dispose();
        }
    };




    //----------------------------
    // read channel image data
    //----------------------------
    /**
     * @typedef jImageData
     * @type {Object}
     * @property {jPsdDocument.Compression} compression
     * @property {Number} layerSize
     * @property {Array} imageData - Byte array
     */
    jPsdReader._readChannelImageData = function(jReader, layer, version) {
        var channels = layer.numOfChannel;

        var imageDataList = [];
        for(var i = 0; i < channels; i++) {
            var compression = jReader.readInt16("big");
            var layerSize = this._getLayerSize(layer, layer.channelInfos[i].id);
            var imageData = [];
            switch(compression) {
                case jPsdReader.Compression.RAW:	// raw
                    imageData = jImageDataDeserializer.fromRawData(jReader, layerSize);
                    break;
                case jPsdReader.Compression.RLE: // rle
                    imageData = jImageDataDeserializer.fromRle(jReader, layerSize, version);
                    break;
                case jPsdReader.Compression.ZIP: // zip without predication
                    imageData = jImageDataDeserializer.fromZip(jReader, layerSize);
                    break;
                case jPsdReader.Compression.ZIPPRED: // zip with predication
                    imageData = jImageDataDeserializer.fromZipPredication(jReader, layerSize);
                    break;
                default:
                    throw new jUserException(null, "This is a compression that is not supported.", 0x40000010);
            }
            
            imageDataList.push({ compression:compression, layerSize:layerSize, imageData:imageData });
        }
        return { numOfChannel:channels, channelImageDatas: imageDataList };
    };



    //----------------------------
    // get layer size
    //----------------------------
    jPsdReader._getLayerSize = function(layer, id) {
        var width = layer.right - layer.left;
        var height = layer.bottom - layer.top;

        switch(id) {
            case -2: // user layer mask
                width = layer.maskData.bitmapRight - layer.maskData.bitmapLeft;
                height = layer.maskData.bitmapBottom - layer.maskData.bitmapTop;
                break;
            case -3: // real user layer mask
                width = layer.maskData.vectorRight - layer.maskData.vectorLeft;
                height = layer.maskData.vectorBottom - layer.maskData.vectorTop;
                break;
        }

        return { width:width, height:height };
    };



    //----------------------------
    // read global layer mask info
    //----------------------------
    jPsdReader._readGlobalLayerMaskInfo = function(jReader) {
        var length = jReader.readInt32("big");
        if(length <= 0) { 
            return {length:length, overlayColorSpace:0x0000, colorComponents:[],
                opacity:0x0000, kind:0x00, filter:[]};
        }

        var buffer = jReader.readBytes(length);
        var reader = new jBinaryReader(buffer);
        try {
            var overlayColorSpace = reader.readInt16("big");
            var colorComponents = reader.readBytes(8);
            var opacity = reader.readInt16("big");
            var kind = reader.readByte();
            var filter = [];	// Filter:zeros

            return {length:length, overlayColorSpace:overlayColorSpace, colorComponents:colorComponents,
                opacity:opacity, kind:kind, filter:filter};
        } finally {
            reader.dispose();
        }
    };


    //----------------------------
    // read additional layer info
    //----------------------------
    jPsdReader._readAdditionalLayerInfos = function(jReader, version) {
        var layerInfos = [];

        while(jReader.getPosition() < jReader.getLength()) {
            var signature = jEncoding.Ascii.getString(jReader.readBytes(4));
            if(signature != "8BIM" && signature != "8B64") {
                throw new jUserException(null, "This file is not psd format.", 0x30000015);
            }

            var key = jEncoding.Ascii.getString(jReader.readBytes(4));
            var dataLength = 0;
            if(version == 2 && (key == "LMsk" || key == "Lr16" || key == "Lr32" || key == "Layr" || key == "Mt16" || key == "Mt32" || key == "Mtrn" ||
                   key == "Alph" || key == "FMsk" || key == "lnk2" || key == "FEid" || key == "FXid" || key == "PxSD")) {
                dataLength = jReader.readInt64("big");
            } else {
                dataLength = jReader.readInt32("big");
            }

            var data = jReader.readBytes(dataLength);

            // skip padding
            if(dataLength % 4 > 0) {
                var padSize = (4 - (dataLength % 4));
                var padding = jReader.readBytes(padSize);
                if((padding.length > 0) && (padding[0] != 0x00)) {
                    jReader.seek(-1, "Current");
                }
            }

            layerInfos.push({signature:signature, key:key, dataLength:dataLength, data:data});
        }

        return layerInfos;
    };



    //=============================
    // read image data section
    //=============================
    jPsdReader._readImageDataSection = function(jReader) {
        var compression = jReader.readInt16("big");
        var data = jReader.readBytes(jReader.getLength() - jReader.getPosition());

        return { compression:compression, data:data };
    };





    /***************************************
     * jBinaryReader
     ***************************************/
     /**
      * Binary Reader
      * @class jBinaryReader
      * @classdesc Reads primitive data types as binary values in a specific encoding.
      * @param {Array} - Byte Array
      */
    global.jBinaryReader = function (bytes) {
        this._bytes = bytes;
        this._length = bytes.length;
        this._position = 0;
    };

    /**
     * Get current position of binary array
     * @memberof jBinaryReader
     * @returns {Int32} - Current position of binary array
     */
    jBinaryReader.prototype.getPosition = function() {
        return this._position;
    };

    /**
     * Get length of binary array
     * @memberof jBinaryReader
     * @returns {Int32} - Current length of binary array
     */
    jBinaryReader.prototype.getLength = function() {
        return this._length;
    };

    /**
     * Release all resource
     * @memberof jBinaryReader
     */
    jBinaryReader.prototype.close = function() {
        this._bytes = null;
        this._length = 0;
        this._position = 0;
    };

    /**
     * Release all resource
     * This function call {@linkcode close} internally.
     * @memberof jBinaryReader
     */
    jBinaryReader.prototype.dispose = function() {
        this.close();
    };

    /**
     * Reads a boolean value from the current stream and advances the current position of the stream by one byte.
     * @memberof jBinaryReader
     * @returns {Boolean}
     */
    jBinaryReader.prototype.readBoolean = function() {
        return (this.readByte() != 0 ? true : false);
    };

    /**
     * Reads a byte value from the current stream and advances the current position of the stream by one byte.
     * @memberof jBinaryReader
     * @returns {Byte}
     */
    jBinaryReader.prototype.readByte = function() {
        if(this._length <= this._position) {
            throw new jUserException(null, "out of range.", 0x50000010);
        }
        return this._bytes[this._position++];
    };

    /**
     * Reads the specified number of bytes value from the current stream into a byte array and advances the current position by the number of bytes.
     * @memberof jBinaryReader
     * @returns {Array}
     * @param {Int32} - The number of bytes that to read.
     */
    jBinaryReader.prototype.readBytes = function (count) {
        if (this._length < this._position + count) {
            throw new jUserException(null, "out of range.", 0x50000011);
        }

        // for performance.
        if (this._bytes.slice != null) {
            var pos = this._position;
            this._position += count;
            return this._bytes.slice(pos, this._position)[0];
        } else if (this._bytes.subarray != null) {
            var pos = this._position;
            this._position += count;
            return this._bytes.subarray(pos, this._position);
        } else {
            var bytes = new Array();
            for (var i = 0; i < count; i++) {
                bytes.push(this.readByte());
            }
            return bytes;
        }
    };

    /**
     * Reads the next character from the current stream and advances the current position of the stream in accordance with the unicode used and the specific character being read from the stream.
     * @memberof jBinaryReader
     * @returns {char}
     */
    jBinaryReader.prototype.readChar = function() {
        return String.fromCharCode(this.readByte());
    };

    /**
     * Reads the specified number of characters from the current stream, returns the data in a character array, and advances the current position in accordance with the unicode used and the specific character being read from the stream.
     * @memberof jBinaryReader
     * @returns {Array}
     */
    jBinaryReader.prototype.readChars = function(count) {
        var chars = new Array();
        for(var i = 0; i < count; i++) {
            chars.push(this.readChar());
        }
        return chars;
    };

    
    /**
     * Reads a 2-byte unsigned integer from the current stream and advances the current position of the stream by two bytes.
     * @memberof jBinaryReader
     * @returns {UInt16}
     * @param {String} - Endian option. This value have to be "little" or "big". Default value is "little".
     */
    jBinaryReader.prototype.readUInt16 = function(endian) {
        if (typeof (endian) === "undifined") { endian = "little"; }

        var buffer = this.readBytes(2);
        switch (endian) {
            case "little":
                return (0x0000 | (buffer[1] << 8)) | (buffer[0]);
            case "big":
            default:
                return (0x0000 | (buffer[0] << 8)) | (buffer[1]);
        }
    };


    /**
     * Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes.
     * @memberof jBinaryReader
     * @returns {Int16}
     * @param {String} - Endian option. This value have to be "little" or "big". Default value is "little".
     */
    jBinaryReader.prototype.readInt16 = function(endian) {
        if(typeof(endian) === "undifined") { endian = "little"; }

        var num = this.readUInt16(endian);
        if ((num & 0x8000) > 0) {
            return (num - 0x10000);
        }
        return num;
    };


    /**
     * Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
     * @memberof jBinaryReader
     * @returns {Int32}
     * @param {String} - Endian option. This value have to be "little" or "big". Default value is "little".
     */
    jBinaryReader.prototype.readInt32 = function(endian) {
        if(typeof(endian) === "undifined") { endian = "little"; }

        var buffer = this.readBytes(4);
        switch(endian) {
            case "little":
                return (0x00000000 | ((buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | (buffer[0])));
            case "big":
            default:
                return (0x00000000 | ((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3])));
        }
    };


    /**
     * Reads a 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.
     * @memberof jBinaryReader
     * @returns {Int64}
     * @param {String} - Endian option. This value have to be "little" or "big". Default value is "little".
     */
    jBinaryReader.prototype.readInt64 = function(endian) {
        console.log("readInt64() is called.");
        if(typeof(endian) === "undifined") { endian = "little"; }

        var buffer = this.readBytes(8);
        switch(endian) {
            case "little":
                return jInt64.parse([buffer[7], buffer[6], buffer[5], buffer[4], buffer[3], buffer[2], buffer[1], buffer[0]]);
            case "big":
            default:
                return jInt64.parse(buffer);
        }
    };


    /**
     * Sets the current position of this stream to the given value
     * @memberof jBinaryReader
     * @param {Number} offset - The point relative to origin from which to begin seeking.
     * @param {String} origin - Specifies the beginning, the end, or the current position as a reference point for offset, using "Begin", "Current" or "End".
     */
    jBinaryReader.prototype.seek = function(offset, origin) {
        switch(origin) {
            case "Begin":
                this._position = offset; break;
            case "Current":
                this._position += offset; break;
            case "End":
                this._position = this._length-offset -1; break;
        }

        if(this._position < 0 || this._position >= this._length) {
            throw new jUserException(null, "out of range", 0x50000012);
        }
    };

    /**
     * Read all bytes that are remained.
     * @memberof jBinaryReader
     * @returns {Array} - Byte array.
     */
    jBinaryReader.prototype.readAllBytes = function () {
        // for performance.
        if (this._bytes.slice != null) {
            var pos = this._position;
            this._position = this.getLength() - 1;
            return this._bytes.slice(pos, this.getLength())[0];
        } else if (this._bytes.subarray != null) {
            var pos = this._position;
            this._position = this.getLength();
            return this._bytes.subarray(pos, this._position);
        } else {
            return this.readBytes(this.getLength() - this.getPosition());
        }
    };




    /***************************************
     * jImageDecoder
     ***************************************/
    var jImageDecoder = {};
    jImageDecoder.fromChannels = function (channels, mask, width, height) {
        var red = channels.red;
        var green = channels.green;
        var blue = channels.blue;
        var alpha = channels.alpha;

        var pngData = "";
        for (var y = 0; y < height; y++) {
            for (var x = 0; x < width; x++) {
                var i = y * width + x;
                var alphaValue = ((mask.length > 0 && mask[i] == 0) ? 0 : (alpha.length <= 0 ? 0xFF : alpha[i]));
                pngData += String.fromCharCode(red[i], green[i], blue[i], alphaValue);

            }
        }

        var pngFile = generatePng(width, height, pngData);
        return jEncoding.Base64.encode(pngFile);
    };

    jImageDecoder.fromArray = function(bytes, width, height) {
        var pngData = "";
        var size = width * height;
        for (var i = 0; i < size; i++) {
            pngData += String.fromCharCode(bytes[i]);
        }
        var pngFile = generatePng(width, height, pngData);
        return jEncoding.Base64.encode(pngFile);
    };




    /***************************************
     * jImageDataDeserializer
     ***************************************/
    var jImageDataDeserializer = {};
    //---------------------
    //	plain data
    //---------------------
    jImageDataDeserializer.fromRawData = function(jReader, layerSize) {
        var size = layerSize.width * layerSize.height;
        return jReader.readBytes(size);
    };



    //---------------------
    //	Rle compression
    //---------------------
    jImageDataDeserializer.fromRle = function(jReader, layerSize, version) {
        var totalLength = 0;
        var data = [];
        for(var r = 0; r < layerSize.height; r++) {
            var byteCountOfRow = 0;
            if(version == 1) {	// psd
                byteCountOfRow = jReader.readInt16("big");
                var little = jEndianConverter.convertInt16(byteCountOfRow);
                var bLittle = jBitConverter.int16ToByteArray(little);
                data = data.concat(bLittle);
            } else if(version == 2) {	// psb
                byteCountOfRow = jReader.readInt32("big");
                var little = jEndianConverter.convertInt32(byteCountOfRow);
                var bLittle = jBitConverter.int32ToByteArray(little);
                data = data.concat(bLittle);
            }
            totalLength += byteCountOfRow;
        }

        return data.concat(jReader.readBytes(totalLength));
    };


    //---------------------
    //	zip compression
    //---------------------
    jImageDataDeserializer.fromZip = function(jReader, layerSize) {
        throw new jUserException(null, "This method is Not implemented yet.", 0x40000020);
    };


    //---------------------
    //	zip with predication compression
    //---------------------
    jImageDataDeserializer.fromZipPredication = function(jReader, layerSize) {
        throw new jUserException(null, "This method is Not implemented yet.", 0x40000021);
    };




    /***************************************
     * jInt64
     ***************************************/
    var jInt64 = {};
    jInt64.parse = function(bytes) {
        // Running sum of octets, doing a 2's complement
        var negate = bytes[0] & 0x80, m = 0, x = 0, carry = 1;
        for (var i = 7, m = 1; i >= 0; i--, m *= 256) {
            var v = bytes[i];

            if (negate) {
                v = (v ^ 0xff) + carry;
                carry = v >> 8;
                v = v & 0xff;
            }

            x += v * m;
        }

        // Return Infinity if we've lost integer precision
        var MAX_INT64 = Math.pow(2, 53);
        if (x >= MAX_INT64) {
            throw new jUserException(null, "This number(int64) is too bigger than what can be handled.", 0x40000030);
        }

        return negate ? -x : x;
    };




    /***************************************
     * jPsdDocument
     ***************************************/
      /**
      * psd document.
      * For detail information, see {@link http://www.adobe.com/devnet-apps/photoshop/fileformatashtml}.
      * @class jPsdDocument
      * @classdesc Provide many informations of psd document.
      */
    var jPsdDocument = function(psd) {
        var _psd = psd;

        /**
         * Always equal to 1.
         * @name jPsdDocument#version
         * @type Number
         * @readonly
         */
        Object.defineProperty(this, 'version', {
             get: function() { return _psd.header.version; }
        });
        
        /**
         * The number of channels in the image, including any alpha channels.
         * @name jPsdDocument#channelCount
         * @type Number
         * @readonly
         */
        Object.defineProperty(this, 'channelCount', {
             get: function() { return _psd.header.channelCount; }
        });
        /**
         * The height of the image in pixels.
         * @name jPsdDocument#height
         * @type Number
         * @readonly
         */
        Object.defineProperty(this, 'height', {
             get: function() { return _psd.header.height; }
        });
        /**
         * The width of the image in pixels.
         * @name jPsdDocument#width
         * @type Number
         * @readonly
         */
        Object.defineProperty(this, 'width', {
             get: function() { return _psd.header.width; }
        });
        /**
         * The number of bits per channel.
         * @name jPsdDocument#depth
         * @type Number
         * @readonly
         */
        Object.defineProperty(this, 'depth', {
             get: function() { return _psd.header.depth; }
        });
        /**
         * The color mode of the file.
         * Bitmap = 0, Grayscale = 1, Indexed = 2, RGB = 3, CMYK = 4, Multichannel = 7, Duotone = 8, Lab = 9.
         * @name jPsdDocument#colorMode
         * @type Number
         * @readonly
         */
        Object.defineProperty(this, 'colorMode', {
             get: function() { return _psd.header.colorMode; }
        });
        /**
         * If this value is true, document contains alpha channel.
         * If this value is false, document doesn't contains alpha channel.
         * @name jPsdDocument#transparencyDataMerged
         * @type Boolean
         * @readonly
         */
        Object.defineProperty(this, 'transparencyDataMerged', {
             get: function() { return _psd.layerAndMaskInfoSection.layerInfo.alphaContainsTransparency; }
        });

        // about detail information
        /**
         * Image Resource Block list of the file.
         * Each element has id, name and data.
         * For more information, see {@link http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_46269}
         * @name jPsdDocument#imageResources
         * @type Array
         * @readonly
         */
        Object.defineProperty(this, 'imageResources', {
             get: function() { return _psd.imageResourceSection.resources; }
        });


        var _additionalInfoDic = null;
        /**
         * Additional information of the file.
         * For more information, see {@link http://www.adobe.com/devnet-apps/photoshop/fileformatashtml}
         * @memberof jPsdDocument
         */
        jPsdDocument.prototype.getAdditionalInfoDic = function () {
            if (_additionalInfoDic == null) {
                _additionalInfoDic = {};
                for (var i = 0; i < _psd.layerAndMaskInfoSection.additionalInfos.length; i++) {
                    var info = _psd.layerAndMaskInfoSection.additionalInfos[i];
                    _additionalInfoDic[info.key] = info;
                }
            }
            return _additionalInfoDic;
        };

        
        /**
         * Global layer mask information of the file.
         * This value contains length, overlayColorSpace, colorComponents, opacity, kind and filter.
         * length : length of global layer mask info section
         * overlayColorSpace : undocumented
         * colorComponents : 4 * 2 byte color components
         * opacity : 0 = transparent, 100 = opaque.
         * kind: 0 = Color selected--i.e. inverted; 1 = Color protected;128 = use value stored per layer. This value is preferred. The others are for backward compatibility with beta versions.
         * For more information, see {@link http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_17115}
         * @name jPsdDocument#globalMask
         * @readonly
         */
        Object.defineProperty(this, 'globalMask', {
             get: function() { return _psd.layerAndMaskInfoSection.maskInfo; }
        });

        /**
         * Global layer mask information of the file.
         * This value contains compression and data.
         * compression : 
         *  0 = Raw image data
         *  1 = RLE compressed the image data starts with the byte counts for all the scan lines (rows * channels), with each count stored as a two-byte value. The RLE compressed data follows, with each scan line compressed separately. The RLE compression is the same compression algorithm used by the Macintosh ROM routine PackBits , and the TIFF standard.
         *  2 = ZIP without prediction
         *  3 = ZIP with prediction.
         * data : The image data. Planar order = RRR GGG BBB, etc.
         * For more information, see {@link http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_89817}
         * @name jPsdDocument#imageData
         * @readonly
         */
        Object.defineProperty(this, 'imageData', {
             get: function() { return _psd.imageDataSection; }
        });

        // produced data
        /**
         * Get resolution of the file.
         * @memberof jPsdDocument
         * @returns {Number}
         */
        jPsdDocument.prototype.getResolution = function () {
            var resource = null;
            var resources = this.imageResources;
            for (var i in resources) {
                if (resources[i].id == jPsdDocument.ResourceID.ResolutionInfo) {
                    resource = resources[i]; break;
                }
            }

            if (resource == null) { return -1; }
            var reader = new jBinaryReader(resource.data);
            try {
                return reader.readInt16("big");
            } finally {
                reader.dispose();
            }
        };


        var _layers = null;
        var _allLayers = null;
        /**
         * Get all direct child layers of the document.
         * For get all layer of the document, use the {@linkcode jPsdDocument.getAllLayers}
         * @memberof jPsdDocument
         * @returns Array - {@linkcode jLayer} array.
         */
        jPsdDocument.prototype.getLayers = function () {
            // for cache
            if (_layers != null) { return _layers; }

            var layers = [];
            for (var i = 0; i < this.layerCount ; i++) {
                var record = _psd.layerAndMaskInfoSection.layerInfo.records[i];
                var imageData = _psd.layerAndMaskInfoSection.layerInfo.imageDatas[i];

                layers.push(new jLayer(record, imageData));
            }
            _allLayers = layers;
            _layers = buildLayerHierarchy(layers);
            return _layers;
        };

        /**
         * Get all layers of the document.
         * @memberof jPsdDocument
         * @returns Array - {@linkcode jLayer} array.
         */
        jPsdDocument.prototype.getAllLayers = function () {
            if (_allLayers == null) { this.getLayers(); }
            return _allLayers;
        };


        /**
         * Number of the layer.
         * @name jPsdDocument#layerCount
         * @type Number
         * @readonly
         */
        Object.defineProperty(this, 'layerCount', {
             get: function() { return _psd.layerAndMaskInfoSection.layerInfo.records.length; }
        });

        /**
         * Get image of the file.
         * Return value is encoded by base64.
         * Need to append prefix 'data:image/png;base64,' to display image on web browser.
         * @memberof jPsdDocument
         * @returns String - Image data that is encoded by base64.
      	 * @example
         *  <head>
         *      <script src="./jPsdReader.js" type="text/javascript"></script>
         *      <script>
         *          function loadImage() {
         *             var psdFile = document.getElementById("file").files[0];
         *             jPsdReader.load({
         *                  file: psdFile,
         *                  success: function(psd) {
         *                      document.getElementById("img").src = "data:image/png;base64," + psd.getImage();
         *                      psd.dispose();
         *                  },
         *                  error: function(e) {
         *                     alert(e.message);
         *                  }
         *             });
         *          }
         *      </script>
         *  </head>
         *  <body>
         *      <input id="file" type="file"/>
         *      <img id="img"/>
         *      <input type="button" onclick="loadImage()" value="load"/> 
         *  </body>
         *
         */
        jPsdDocument.prototype.getImage = function () {
            var width = this.width;
            var height = this.height;
            var imageData = this.imageData.data;


            switch (this.imageData.compression) {
                case jPsdDocument.Compression.RAW: // RAW
                    return jImageDecoder.fromArray(imageData, width, height);
                    break;
                case jPsdDocument.Compression.RLE: // RLE
                    var pixelsPerChannel = width * height;
                    var headerSize = height * this.channelCount * 2;
                    var channels = [];
                    var reader = new jBinaryReader(imageData);
                    try {
                        reader.seek(headerSize, "Begin");
                        for (var i = 0; i < this.channelCount; i++) {
                            channels.push(jDecompressor.decompRleByLimit(reader, pixelsPerChannel));
                        }
                        return jImageDecoder.fromChannels({ red: channels[0], green: channels[1], blue: channels[2], alpha: (channels.length >= 4 ? channels[3] : []) }, [], width, height);
                    } finally { reader.dispose(); }
                    break;
                case jPsdDocument.Compression.ZIP:
                case jPsdDocument.Compression.ZIPPRED:
                default:
                    throw new jUserException(null, "Not implemented yet.", 0x40000040);
            }
        };


        /**
         * Release all resource.
         * @memberof jPsdDocument
         */
        jPsdDocument.prototype.dispose = function() {
            _psd = null;
            _layers = null;
            _allLayers = null;
            _additionalInfoDic = null;
        };


        var buildLayerHierarchy = function (layers) {
            var buildedLayers = [];
            var root = null;
            for (var i = layers.length - 1; i >= 0; i--) {
                var layer = layers[i];
                switch (layer.type) {
                    case 1: // open group
                    case 2: // close group
                        if (root == null) {
                            buildedLayers.push(layer);
                        } else {
                            layer._parentLayer = root;
                            root._childLayers.push(layer);
                        }
                        root = layer;
                        break;
                    case 3: // boundingSection
                        root = root._parentLayer;
                        break;
                    default:
                        if (root != null) {
                            layer._parentLayer = root;
                            root._childLayers.push(layer);
                        } else {
                            buildedLayers.push(layer);
                        }
                }
            }

            return buildedLayers;
        };
    };

    /**
     * Enum for jLayer type
     * @memberof jPsdDocument
     * @readonly
     * @enum {Number}
     */
    jPsdDocument.LayerType = {
        Normal: -1,
        AnyOther: 0,
        OpenFolder: 1,
        ClosedFolder: 2,
        BoundingSection: 3
    };

    /**
     * Enum for compression
     * @memberof jPsdDocument
     * @readonly
     * @enum {Number}
     */
    jPsdDocument.Compression = {
        RAW: 0,
        RLE: 1,
        ZIP: 2,
        ZIPPRED: 3
    };

    /**
     * Enum for resource id.
     * @memberof jPsdDocument
     * @readonly
     * @enum {Number}
     */
    jPsdDocument.ResourceID = {
        Unknown: 0x0000,
        BasicInfo: 0x03E8,
        PrintInfoRecordOfMacintosh: 0x03E9,
        IndexedColorTable: 0x03EB,
        ResolutionInfo: 0x03ED,
        NameOfAlphaChannels: 0x03EE,
        DisplayInfo: 0x03EF,
        Caption: 0x03F0,
        BorderInfo: 0x03F1,
        BackgroundColor: 0x03F2,
        PrintFlag: 0x03F3,
        GrayscaleInfo: 0x03F4,
        ColorHalftonInfo: 0x03F5,
        DuotoneHalftonInfo: 0x03F6,
        GraysacleTransferInfo: 0x03F7,
        ColorTransferFunctions: 0x03F8,
        DuotoneTransferFunctions: 0x03F9,
        DuotoneImageInfo: 0x03FA,
        DotRange: 0x03FB,
        Obsolete1: 0x03FC,
        EPSOptions: 0x03FD,
        QuickMaskInfo: 0x03FE,
        Obsolete2: 0x03FF,
        LayerStateInfo: 0x0400,
        WorkingPath: 0x0401,
        LayersGroupInfo: 0x0402,
        Obsolete3: 0x0403,
        IPTC_NAA_Record: 0x0404,
        ImageModeForRawFormatFile: 0x0405,
        JPEG_Quality: 0x0406,
        GridGuidesInfo: 0x0408,
        ThumbnailResource4: 0x0409,
        CopyrightFlag: 0x040A,
        URL: 0x040B,
        ThumbnailResource5: 0x040C,
        GlobalAngle: 0x040D,
        ColorSamplers: 0x040E,
        ICCProfile: 0x040F,
        WaterMark: 0x0410,
        ICCUntaggedProfile: 0x0411,
        EffectsVisible: 0x0412,
        SpotHalftone: 0x0413,
        IDSeedNumber: 0x0414,
        UnicodeAlphaName: 0x0415,
        IndexedColorTableCount: 0x0416,
        TransparencyIndex: 0x0417,
        GlobalAltitude: 0x0419,
        Slices: 0x041A,
        WorkflowURL: 0x041B,
        JumpToXPEP: 0x041C,
        AlphaID: 0x041D,
        URLList: 0x041E,
        VersionInfo: 0x0421,
        EXIF1: 0x0422,
        EXIF2: 0x0423,
        XMP: 0x0424,
        CaptionDigest: 0x0425,
        PrintScale: 0x0426,
        PixelAspectRatio: 0x0428,
        LayerComps: 0x0429,
        AlternateDuotoneColors: 0x042A,
        AlternateSpotColors: 0x042B,
        LayerSelectionID: 0x042D,
        HDRToningInfo: 0x042E,
        PrintInfoOnCS2: 0x042F,
        LayerGroupEnabledID: 0x0430,
        ColorSamplersResource: 0x0431,
        MeasurementScale: 0x0432,
        TimelineInfo: 0x0433,
        SheetDisclosure: 0x0434,
        DisplayInfoStructure: 0x0435,
        OnionSkins: 0x0436,
        CountInfo: 0x0438,
        PrintInfoOnCS5: 0x043A,
        PrintStyle: 0x043B,
        MacintoshNSPrintInfo: 0x043C,
        WindowsDevMode: 0x043D,
        AutoSaveFilePath: 0x043E,
        AutoSaveFormat: 0x043F,
        //0x07D0-0x0BB6
        //2000-2997
        //Path Information (saved paths)
        //See See Path resource format.
        ClipPathName: 0x0BB7,
        //0x0FA0-0x1387
        //4000-4999
        //Plug-In resource(s). Resources added by a plug-in. See the plug-in API found in the SDK documentation
        ImageReadyVariables: 0x1B58,
        ImageReadyDataSets: 0x1B59,
        LightroomWorkflow: 0x1F40,
        PrintFlagsInfo: 0x2710
    };



    /***************************************
     * jLayer
     ***************************************/
    /**
     * Layer
     * @class jLayer
     * @classdesc Provide many informations of a layer.
     */
    var jLayer = function (record, imageData) {
        /**
         * @typedef jRangeData
         * @type {Object} 
         * @property {Number} CompositeGrayBlendSource
         * @property {Number} CompositeGrayBlendDestinationRange
         */

        /**
         * Data of range
         * @name jLayer#rangeData
         * @readonly
         * @type {jRangeData}
         */
         Object.defineProperty(this, 'rangeData', {
             get: function() { return { CompositeGrayBlendSource: record.blendingRanges.compositeGrayBlendSource, CompositeGrayBlendDestinationRange: record.blendingRanges.compositeGrayBlendDestinationRange }; }
         });

        /**
         * Layer mask and adjustment layer data
         * @name jLayer#maskData
         * @readonly
         * @type {jLayerMaskAdjustmentLayerData}
         */
        Object.defineProperty(this, 'maskData', {
             get: function() { return record.maskData; }
        });
    
        
        /**
         * @typedef jChannel
         * @type {Object} 
         * @property {Number} id
         * @property {jImageData} image
         */
        this._channels = [];
        for (var i = 0; i < record.channelInfos.length; i++) {
            this._channels.push({ id: record.channelInfos[i].id, image: imageData.channelImageDatas[i] });
        }

        /**
         * Channels of this layer.
         * This type is array of {@link jChannel}
         * @name jLayer#channels
         * @readonly
         * @type {Array}
         */
        Object.defineProperty(this, 'channels', {
            get: function() { return this._channels; }
        });


        /**
         * Ranges of channel
         * This type is array of {@link jChannelRange}
         * @name jLayer#channelRanges
         * @readonly
         * @type {Array}
         */
         Object.defineProperty(this, 'channelRanges', {
            get: function() { return record.blendingRanges.channelRanges; }
        });


        this._additionalInfoDic = {};
        for (var i = 0; i < record.additionalInfos.length; i++) {
            var info = record.additionalInfos[i];
            this._additionalInfoDic[info.key] = info;
        }

        /**
         * Additional layer information
         * For more information, see {@link http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1049436}
         * This type is key-value dictionary.
         * @name jLayer#additionalInfoDic
         * @readonly
         * @type {Array}
         */
        Object.defineProperty(this, 'additionalInfoDic', {
            get: function() { return this._additionalInfoDic; }
        });


        this._name = record.name;
        if (this.additionalInfoDic["luni"] != null) {
            var reader = new jBinaryReader(this.additionalInfoDic["luni"].data);
            try {
                var length = reader.readInt32("big");
                var nameData = reader.readBytes(length * 2);
                this._name = jEncoding.BigEndianUnicode.getString(nameData);
            } finally {reader.dispose();}
        }

        /**
         * The name of layer.
         * @name jLayer#name
         * @readonly
         * @type {String}
         */
        Object.defineProperty(this, 'name', {
            get: function() { return this._name; }
        });

        this._isGroupLayer = false;
        if (this.additionalInfoDic["lsct"] != null) {
            var reader = new jBinaryReader(this.additionalInfoDic["lsct"].data);
            try {
                var type = reader.readInt32("big");
                this._isGroupLayer = (type == jPsdDocument.LayerType.BoundingSection || type == jPsdDocument.LayerType.ClosedFolder || type == jPsdDocument.LayerType.OpenFolder);
            } finally { reader.dispose(); }
        }
        
        /**
         * If this value is true, layer is group folder.
         * @name jLayer#isGroupLayer
         * @readonly
         * @type {Boolean}
         */
        Object.defineProperty(this, 'isGroupLayer', {
            get: function() { return this._isGroupLayer; }
        });


        this._isTextLayer = false;
        if (this.isGroupLayer == true) {
            this.isTextLayer = false;
        } else {
            this._isTextLayer = (this.additionalInfoDic["TySh"] != null);
        }


        /**
         * If this value is true, layer is text layer.
         * @name jLayer#isTextLayer
         * @readonly
         * @type {Boolean}
         */
        Object.defineProperty(this, 'isTextLayer', {
            get: function() { return this._isTextLayer; }
        });


        this._id = -1;
        if (this.additionalInfoDic["lyid"] != null) {
            var reader = new jBinaryReader(this.additionalInfoDic["lyid"].data);
            try {
                this._id = reader.readInt32("big");
            } finally { reader.dispose();}
        }


        /**
         * ID of layer.
         * @name jLayer#id
         * @readonly
         * @type {Int32}
         */
        Object.defineProperty(this, 'id', {
            get: function() { return this._id; }
        });


        /**
         * Location of layer.
         * Top
         * @type {Int32}
         * @readonly
         * @name jLayer#top
         */
        Object.defineProperty(this, 'top', {
            get: function() { return record.top; }
        });
        
        /**
         * Location of layer.
         * Left
         * @type {Int32}
         * @readonly
         * @name jLayer#left
         */
        Object.defineProperty(this, 'left', {
            get: function() { return record.left; }
        });
        
        /**
         * Location of layer.
         * bottom
         * @type {Int32}
         * @readonly
         * @name jLayer#bottom
         */
        Object.defineProperty(this, 'bottom', {
            get: function() { return record.bottom; }
        });
        
        /**
         * Location of layer.
         * Right
         * @type {Int32}
         * @readonly
         * @name jLayer#right
         */
        Object.defineProperty(this, 'right', {
            get: function() { return record.right; }
        });
        
        /**
         * Blend mode key
         * For more information, see {@link http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_13084}
         * @type {String}
         * @readonly
         * @name jLayer#blendModeKey
         */
        Object.defineProperty(this, 'blendModeKey', {
            get: function() { return jEncoding.Ascii.getString(record.blendModeKey); }
        });
        
        /**
         * Opacity of layer
         * @type {Byte}
         * @readonly
         * @name jLayer#opacity
         */
        Object.defineProperty(this, 'opacity', {
            get: function() { return record.opacity; }
        });
        
        /**
         * Clipping
         * 0 = base, 1 = non-base
         * @type {Byte}
         * @readonly
         * @name jLayer#clipping
         */
        Object.defineProperty(this, 'clipping', {
            get: function() { return record.clipping; }
        });
        
        /**
         * Flags of layer
         * bit 0 = transparency protected;
         * bit 1 = visible;
         * bit 2 = obsolete;
         * bit 3 = 1 for Photoshop 5.0 and later, tells if bit 4 has useful information;
         * bit 4 = pixel data irrelevant to appearance of document
         * @type {Byte}
         * @readonly
         * @name jLayer#flags
         */
        Object.defineProperty(this, 'flags', {
            get: function() { return record.flags; }
        });
        
        /**
         * Allways this value is zero.
         * @type {Byte}
         * @readonly
         * @name jLayer#filter
         */
        Object.defineProperty(this, 'filter', {
            get: function() { return record.filter; }
        });

        /**
         * Width of layer
         * @type {Int32}
         * @readonly
         * @name jLayer#width
         */
        Object.defineProperty(this, 'width', {
            get: function() { return this.right - this.left; }
        });

        /**
         * Height of layer
         * @type {Int32}
         * @readonly
         * @name jLayer#height
         */
        Object.defineProperty(this, 'height', {
            get: function() { return this.bottom - this.top; }
        });

        /**
         * If the value is true, this layer has image data.
         * If the value is false, this layer doen't have image data.
         * @type {Boolean}
         * @readonly
         * @name jLayer#hasImage
         */
        Object.defineProperty(this, 'hasImage', {
            get: function() { return (this.width != 0 && this.height != 0); }
        });
        
        /**
         * The visibility of layer.
         * Even if this value is true, if its parent is false, this layer will not be visible.
         * @type {Boolean}
         * @readonly
         * @name jLayer#visible
         */
        Object.defineProperty(this, 'visible', {
            get: function() { return ((this.flags & 0x02) == 0); }
        });





        this._parentLayer = null;
        /**
         * Parent layer of this layer.
         * @type {jLayer}
         * @readonly
         * @name jLayer#parentLayer
         */
        Object.defineProperty(this, 'parentLayer', {
            get: function() { return this._parentLayer; }
        });

        this._childLayers = [];
        /**
         * All direct child layers.
         * This type is array of {@linkcode jLayer}
         * @type {Array}
         * @readonly
         * @name jLayer#childLayers
         */
        Object.defineProperty(this, 'childLayers', {
            get: function() { return this._childLayers; }
        });


        this._type = -1;
        if (this.additionalInfoDic["lsct"] != null) {
            var info = this.additionalInfoDic["lsct"];
            var reader = new jBinaryReader(info.data);
            try {
                this._type = reader.readInt32("big");
                /*
                var enableBlendMode = (info.length == 12);
                if (enableBlendMode == true) {
                    reader.readBytes(4); // skip
                    var blendMode = jEncoding.Ascii.toString(reader.readBytes(4));
                }
                */
            } finally { reader.dispose();}
        }

        /**
         * Type of layer.
         * 0 = any other type of layer, 1 = open "folder", 2 = closed "folder", 3 = bounding section divider, hidden in the UI
         * @type {Int32}
         * @readonly
         * @name jLayer#type
         */
        Object.defineProperty(this, 'type', {
            get: function() { return this._type; }
        });

        /**
         * If the value is true, this layer is visible.
         * If the value is false, this layer is invisible.
         * @returns {Boolean}
         * @memberof jLayer
         */
        jLayer.prototype.getActualVisibility = function() {
            var layer = this;
            while (layer != null) {
                if (layer.visible == false) { return false; }
                layer = layer.parentLayer;
            }
            return true;
        };

        /**
         * Enum for compression
         * @memberof jLayer
         * @readonly
         * @enum {Number}
         */
        jLayer.Compression = {
            RAW: 0,
            RLE: 1,
            ZIP: 2,
            ZIPPRED: 3
        };


        /**
         * Get channel data of layer.
         * The value contains alpha, red, green, blue and mask.
         * @returns {associativeArray}
         * @memberof jLayer
         */
        jLayer.prototype.getChannelDatas = function () {
            var aData = [];
            var rData = [];
            var gData = [];
            var bData = [];
            var maskData = [];
            var realMaskData = [];

            if (this.hasImage == false) { return { alpha: aData, red: rData, green: gData, blue: bData, mask: maskData }; }

            //if (this.maskData.bitmapTop != 0 && this.maskData.bitmapLeft != 0 && this.maskData.bitmapBottom != 0 && this.bitmapRight != 0 && this.defaultColor != 0 && this.flags != 0
            //    && this.realFlags != 0 && this.realUserMaskBackground != 0 && this.vectorTop != 0 && this.vectorBottom != 0 && this.vectorLeft != 0 && this.vectorRight != 0) {
            //}

            var decompChannel = function (compression, bytes, width, height) {
                switch (compression) {
                    case jLayer.Compression.RAW: // raw
                        return bytes;
                        break;
                    case jLayer.Compression.RLE: // rle
                        var offset = height * 2;
                        var reader = new jBinaryReader(bytes);
                        try {
                            reader.seek(offset, "Begin");
                            var buffer = reader.readAllBytes();
                            return jDecompressor.decompRle(buffer);
                        } finally { reader.dispose(); }
                        break;
                    case jLayer.Compression.ZIP: // zip
                        break;
                    case jLayer.Compression.ZIPPRED: // zip with predication
                        break;
                    default:
                        throw new jUserException(null, "This compression is not supproted.", 0x40000050);
                }
            };


            for (var i = 0; i < this.channels.length; i++) {
                switch (this.channels[i].id) {
                    case 0: // red
                        rData = decompChannel(this.channels[i].image.compression, this.channels[i].image.imageData, this.width, this.height);
                        break;
                    case 1: // green
                        gData = decompChannel(this.channels[i].image.compression, this.channels[i].image.imageData, this.width, this.height);
                        break;
                    case 2: // blue
                        bData = decompChannel(this.channels[i].image.compression, this.channels[i].image.imageData, this.width, this.height);
                        break;
                    case -1: // transparency mask
                        aData = decompChannel(this.channels[i].image.compression, this.channels[i].image.imageData, this.width, this.height);
                        break;
                    case -2: // user layer mask
                        maskData = decompChannel(this.channels[i].image.compression, this.channels[i].image.imageData, this.width, this.height);
                        break;
                    case -3: // real user layer mask
                        throw new jUserException(null, "'real user layer mask' is not supported.", 0x40000060);
                        break;
                    default:
                }
            }

            return { alpha: aData, red: rData, green: gData, blue: bData, mask: maskData };
        }


        /**
         * Get image of layer.
         * Return value is encoded by base64.
         * Need to append prefix 'data:image/png;base64,' to display image on web browser.
         * @returns {string} base64
         * @memberof jLayer
         * @example
         *  <head>
         *      <script src="./jPsdReader.js" type="text/javascript"></script>
         *      <script>
         *          function loadImage() {
         *             var psdFile = document.getElementById("file").files[0];
         *             jPsdReader.load({
         *                  file: psdFile,
         *                  success: function(psd) {
         *                      document.getElementById("img").src = "data:image/png;base64," + psd.getLayers()[0].getImage();
         *                      psd.dispose();
         *                  },
         *                  error: function(e) {
         *                     alert(e.message);
         *                  }
         *             });
         *          }
         *      </script>
         *  </head>
         *  <body>
         *      <input id="file" type="file"/>
         *      <img id="img"/>
         *      <input type="button" onclick="loadImage()" value="load"/> 
         *  </body>
         *
         */
        jLayer.prototype.getImage = function () {
            var channels = this.getChannelDatas();
            return jImageDecoder.fromChannels(channels, channels.mask, this.width, this.height);
        };
    };




    /***************************************
     * jDecompressor
     ***************************************/
    var jDecompressor = {};
    jDecompressor.decompRle = function(bytes) {
        var decompData = [];
        var reader = new jBinaryReader(bytes);
        try {
            while (reader.getPosition() < reader.getLength()) {
                var length = reader.readByte();
                if (length >= 128) {
                    var repeatValue = reader.readByte();
                    for (var repeat = 0; repeat < ((length ^ 0xFF) + 2); repeat++) {
                        decompData.push(repeatValue);
                    }
                } else if (length < 128) {
                    for (var repeat = 0; repeat < (length + 1); repeat++) {
                        decompData.push(reader.readByte());
                    }
                }
            }
        } finally { reader.dispose(); }

        return decompData;
    };


    jDecompressor.decompRleByLimit = function (jReader, limit) {
        var decompData = [];
        var reader = jReader;
        var count = 0;

        while (count < limit) {
            var length = reader.readByte();
            if (length >= 128) {
                var repeatValue = reader.readByte();
                for (var repeat = 0; repeat < ((length ^ 0xFF) + 2) ; repeat++) {
                    decompData.push(repeatValue);
                    count++;
                }
            } else if (length < 128) {
                for (var repeat = 0; repeat < (length + 1) ; repeat++) {
                    decompData.push(reader.readByte());
                    count++;
                }
            }
        }

        return decompData;
    };




    /***************************************
     * jEncoding
     ***************************************/
    var jEncoding = {};
    jEncoding.Base64 = {};
    jEncoding.Base64.encode = function (string) {
        if (btoa != null) {
            return btoa(string);
        } else {
            throw new jUserException(null, "This browser dose not support 'atob()'", 0x10000030);
        }
        
    };

    jEncoding.Base64.decode = function (base64) {
        if (atob != null) {
            return atob(base64);
        } else {
            throw new jUserException(null, "This browser dose not support 'atob()'", 0x10000040);
        }
    };


    jEncoding.BigEndianUnicode = {};
    jEncoding.BigEndianUnicode.getString = function (bytes) {
        var wcharBytes = [];
        for (var i = 0; i < (bytes.length - 1) ; i += 2) {
            wcharBytes.push(0xFFFF & ((bytes[i] << 8) | bytes[i + 1]));
        }
        if (bytes.length % 2 != 0) {
            wcharBytes.push(0xFF & bytes[bytes.length - 1]);
        }

        return jEncoding.BigEndianUnicode._toUtf16(wcharBytes);
    };

    jEncoding.BigEndianUnicode._toUtf16 = function(wbytes) {
        var i = 0;
        var len = wbytes.length;
        var w1, w2;
        var charCodes = [];
        while (i < len) {
            var w1 = wbytes[i++];
            if ((w1 & 0xF800) !== 0xD800) { // w1 < 0xD800 || w1 > 0xDFFF
                charCodes.push(w1);
                continue;
            }
            if ((w1 & 0xFC00) === 0xD800) { // w1 >= 0xD800 && w1 <= 0xDBFF
                throw new RangeError('Invalid octet 0x' + w1.toString(16) + ' at offset ' + (i - 1));
            }
            if (i === len) {
                throw new RangeError('Expected additional octet');
            }
            w2 = wbytes[i++];
            if ((w2 & 0xFC00) !== 0xDC00) { // w2 < 0xDC00 || w2 > 0xDFFF)
                throw new RangeError('Invalid octet 0x' + w2.toString(16) + ' at offset ' + (i - 1));
            }
            charCodes.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000);
        }
        return String.fromCharCode.apply(String, charCodes);
    };



    jEncoding.Ascii = {};
    jEncoding.Ascii.getString = function(bytes) {
        var string = "";
        for (var i = 0; i < bytes.length; i++) {
            string += String.fromCharCode(bytes[i]);
        }
        return string;
    };






    /***************************************
     * jUserException
     ***************************************/
    function jUserException(e, message, code) {
        if (code == null) {
            console.error(new Error().stack);
        }
        
        this.obj = e;
        this.message = message;
        this.code = (typeof(code) === 'undefined' || code == null) ? 0 : code;
        this.name = "jUserException";
    };
})(this));





//#############################################################################
// Third party libraries
//#############################################################################



 /*jslint bitwise:true, plusplus: true */
 /*global Base64: false*/
 (function (globalObj) {
     'use strict';

     // 0x78, 7 = 2^(7+8) = 32768 window size (CMF)
     //       8 = deflate compression
     // 0x01, (00 0 00001) (FLG)
     // bits 0 to 4  FCHECK  (check bits for CMF and FLG)
     // bit  5       FDICT   (preset dictionary)
     // bits 6 to 7  FLEVEL  (compression level)

     var DEFLATE_METHOD = String.fromCharCode(0x78, 0x01),
         CRC_TABLE = [],
         SIGNATURE = String.fromCharCode(137, 80, 78, 71, 13, 10, 26, 10),
         NO_FILTER = String.fromCharCode(0),

         make_crc_table = function () {
             var n, c, k;

             for (n = 0; n < 256; n++) {
                 c = n;
                 for (k = 0; k < 8; k++) {
                     if (c & 1) {
                         c = 0xedb88320 ^ (c >>> 1);
                     } else {
                         c = c >>> 1;
                     }
                 }
                 CRC_TABLE[n] = c;
             }
         },

         inflateStore = function (data) {
             var MAX_STORE_LENGTH = 65535,
                 storeBuffer = '',
                 i,
                 remaining,
                 blockType;

             for (i = 0; i < data.length; i += MAX_STORE_LENGTH) {
                 remaining = data.length - i;
                 blockType = '';

                 if (remaining <= MAX_STORE_LENGTH) {
                     blockType = String.fromCharCode(0x01);
                 } else {
                     remaining = MAX_STORE_LENGTH;
                     blockType = String.fromCharCode(0x00);
                 }
                 // little-endian
                 storeBuffer += blockType + String.fromCharCode((remaining & 0xFF), (remaining & 0xFF00) >>> 8);
                 storeBuffer += String.fromCharCode(((~remaining) & 0xFF), ((~remaining) & 0xFF00) >>> 8);

                 storeBuffer += data.substring(i, i + remaining);
             }

             return storeBuffer;
         },

         adler32 = function (data) {
             var MOD_ADLER = 65521,
                 a = 1,
                 b = 0,
                 i;

             for (i = 0; i < data.length; i++) {
                 a = (a + data.charCodeAt(i)) % MOD_ADLER;
                 b = (b + a) % MOD_ADLER;
             }

             return (b << 16) | a;
         },

         update_crc = function (crc, buf) {
             var c = crc, n, b;

             for (n = 0; n < buf.length; n++) {
                 b = buf.charCodeAt(n);
                 c = CRC_TABLE[(c ^ b) & 0xff] ^ (c >>> 8);
             }
             return c;
         },

         crc = function crc(buf) {
             return update_crc(0xffffffff, buf) ^ 0xffffffff;
         },

         dwordAsString = function (dword) {
             return String.fromCharCode((dword & 0xFF000000) >>> 24, (dword & 0x00FF0000) >>> 16, (dword & 0x0000FF00) >>> 8, (dword & 0x000000FF));
         },

         createChunk = function (length, type, data) {
             var CRC = crc(type + data);

             return dwordAsString(length) +
                 type +
                 data +
                 dwordAsString(CRC);
         },

         IEND,

         createIHDR = function (width, height) {
             var IHDRdata;

             IHDRdata = dwordAsString(width);
             IHDRdata += dwordAsString(height);

             // bit depth
             IHDRdata += String.fromCharCode(8);
             // color type: 6=truecolor with alpha
             IHDRdata += String.fromCharCode(6);
             // compression method: 0=deflate, only allowed value
             IHDRdata += String.fromCharCode(0);
             // filtering: 0=adaptive, only allowed value
             IHDRdata += String.fromCharCode(0);
             // interlacing: 0=none
             IHDRdata += String.fromCharCode(0);

             return createChunk(13, 'IHDR', IHDRdata);
         },

         png = function (width, height, rgba) {
             var IHDR = createIHDR(width, height),
                 IDAT,
                 scanlines = '',
                 scanline,
                 y,
                 x,
                 compressedScanlines;

             for (y = 0; y < rgba.length; y += width * 4) {
                 scanline = NO_FILTER;
                 if (Array.isArray(rgba)) {
                     for (x = 0; x < width * 4; x++) {
                         scanline += String.fromCharCode(rgba[y + x] & 0xff);
                     }
                 } else {
                     // rgba=string
                     scanline += rgba.substr(y, width * 4);
                 }
                 scanlines += scanline;
             }

             compressedScanlines = DEFLATE_METHOD + inflateStore(scanlines) + dwordAsString(adler32(scanlines));

             IDAT = createChunk(compressedScanlines.length, 'IDAT', compressedScanlines);

             return SIGNATURE + IHDR + IDAT + IEND;
         };

     make_crc_table();
     IEND = createChunk(0, 'IEND', '');

     globalObj.generatePng = png;
 }(this));





 


 /*
 # MIT LICENSE
 # Copyright (c) 2011 Devon Govett
 # 
 # Permission is hereby granted, free of charge, to any person obtaining a copy of this 
 # software and associated documentation files (the "Software"), to deal in the Software 
 # without restriction, including without limitation the rights to use, copy, modify, merge, 
 # publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 
 # to whom the Software is furnished to do so, subject to the following conditions:
 # 
 # The above copyright notice and this permission notice shall be included in all copies or 
 # substantial portions of the Software.
 # 
 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
 # BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


 (function () {
     var PNG;

     PNG = (function () {
         var APNG_BLEND_OP_OVER, APNG_BLEND_OP_SOURCE, APNG_DISPOSE_OP_BACKGROUND, APNG_DISPOSE_OP_NONE, APNG_DISPOSE_OP_PREVIOUS, makeImage, scratchCanvas, scratchCtx;
         
         APNG_DISPOSE_OP_NONE = 0;

         APNG_DISPOSE_OP_BACKGROUND = 1;

         APNG_DISPOSE_OP_PREVIOUS = 2;

         APNG_BLEND_OP_SOURCE = 0;

         APNG_BLEND_OP_OVER = 1;

         function PNG(data) {
             var chunkSize, colors, delayDen, delayNum, frame, i, index, key, section, _short, text, _i, _j, _ref;
             this.data = data;
             this.pos = 8;
             this.palette = [];
             this.imgData = [];
             this.transparency = {};
             this.animation = null;
             this.text = {};
             frame = null;
             while (true) {
                 chunkSize = this.readUInt32();
                 section = ((function () {
                     var _i, _results;
                     _results = [];
                     for (i = _i = 0; _i < 4; i = ++_i) {
                         _results.push(String.fromCharCode(this.data[this.pos++]));
                     }
                     return _results;
                 }).call(this)).join('');
                 switch (section) {
                     case 'IHDR':
                         this.width = this.readUInt32();
                         this.height = this.readUInt32();
                         this.bits = this.data[this.pos++];
                         this.colorType = this.data[this.pos++];
                         this.compressionMethod = this.data[this.pos++];
                         this.filterMethod = this.data[this.pos++];
                         this.interlaceMethod = this.data[this.pos++];
                         break;
                     case 'acTL':
                         this.animation = {
                             numFrames: this.readUInt32(),
                             numPlays: this.readUInt32() || Infinity,
                             frames: []
                         };
                         break;
                     case 'PLTE':
                         this.palette = this.read(chunkSize);
                         break;
                     case 'fcTL':
                         if (frame) {
                             this.animation.frames.push(frame);
                         }
                         this.pos += 4;
                         frame = {
                             width: this.readUInt32(),
                             height: this.readUInt32(),
                             xOffset: this.readUInt32(),
                             yOffset: this.readUInt32()
                         };
                         delayNum = this.readUInt16();
                         delayDen = this.readUInt16() || 100;
                         frame.delay = 1000 * delayNum / delayDen;
                         frame.disposeOp = this.data[this.pos++];
                         frame.blendOp = this.data[this.pos++];
                         frame.data = [];
                         break;
                     case 'IDAT':
                     case 'fdAT':
                         if (section === 'fdAT') {
                             this.pos += 4;
                             chunkSize -= 4;
                         }
                         data = (frame != null ? frame.data : void 0) || this.imgData;
                         for (i = _i = 0; 0 <= chunkSize ? _i < chunkSize : _i > chunkSize; i = 0 <= chunkSize ? ++_i : --_i) {
                             data.push(this.data[this.pos++]);
                         }
                         break;
                     case 'tRNS':
                         this.transparency = {};
                         switch (this.colorType) {
                             case 3:
                                 this.transparency.indexed = this.read(chunkSize);
                                 _short = 255 - this.transparency.indexed.length;
                                 if (_short > 0) {
                                     for (i = _j = 0; 0 <= _short ? _j < _short : _j > _short; i = 0 <= _short ? ++_j : --_j) {
                                         this.transparency.indexed.push(255);
                                     }
                                 }
                                 break;
                             case 0:
                                 this.transparency.grayscale = this.read(chunkSize)[0];
                                 break;
                             case 2:
                                 this.transparency.rgb = this.read(chunkSize);
                         }
                         break;
                     case 'tEXt':
                         text = this.read(chunkSize);
                         index = text.indexOf(0);
                         key = String.fromCharCode.apply(String, text.slice(0, index));
                         this.text[key] = String.fromCharCode.apply(String, text.slice(index + 1));
                         break;
                     case 'IEND':
                         if (frame) {
                             this.animation.frames.push(frame);
                         }
                         this.colors = (function () {
                             switch (this.colorType) {
                                 case 0:
                                 case 3:
                                 case 4:
                                     return 1;
                                 case 2:
                                 case 6:
                                     return 3;
                             }
                         }).call(this);
                         this.hasAlphaChannel = (_ref = this.colorType) === 4 || _ref === 6;
                         colors = this.colors + (this.hasAlphaChannel ? 1 : 0);
                         this.pixelBitlength = this.bits * colors;
                         this.colorSpace = (function () {
                             switch (this.colors) {
                                 case 1:
                                     return 'DeviceGray';
                                 case 3:
                                     return 'DeviceRGB';
                             }
                         }).call(this);
                         this.imgData = new Uint8Array(this.imgData);
                         return;
                     default:
                         this.pos += chunkSize;
                 }
                 this.pos += 4;
                 if (this.pos > this.data.length) {
                     throw new Error("Incomplete or corrupt PNG file");
                 }
             }
             return;
         }

         PNG.prototype.read = function (bytes) {
             var i, _i, _results;
             _results = [];
             for (i = _i = 0; 0 <= bytes ? _i < bytes : _i > bytes; i = 0 <= bytes ? ++_i : --_i) {
                 _results.push(this.data[this.pos++]);
             }
             return _results;
         };

         PNG.prototype.readUInt32 = function () {
             var b1, b2, b3, b4;
             b1 = this.data[this.pos++] << 24;
             b2 = this.data[this.pos++] << 16;
             b3 = this.data[this.pos++] << 8;
             b4 = this.data[this.pos++];
             return b1 | b2 | b3 | b4;
         };

         PNG.prototype.readUInt16 = function () {
             var b1, b2;
             b1 = this.data[this.pos++] << 8;
             b2 = this.data[this.pos++];
             return b1 | b2;
         };

         PNG.prototype.decodePixels = function (data) {
             var _byte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft, _i, _j, _k, _l, _m;
             if (data == null) {
                 data = this.imgData;
             }
             if (data.length === 0) {
                 return new Uint8Array(0);
             }
             data = new FlateStream(data);
             data = data.getBytes();
             pixelBytes = this.pixelBitlength / 8;
             scanlineLength = pixelBytes * this.width;
             pixels = new Uint8Array(scanlineLength * this.height);
             length = data.length;
             row = 0;
             pos = 0;
             c = 0;
             while (pos < length) {
                 switch (data[pos++]) {
                     case 0:
                         for (i = _i = 0; _i < scanlineLength; i = _i += 1) {
                             pixels[c++] = data[pos++];
                         }
                         break;
                     case 1:
                         for (i = _j = 0; _j < scanlineLength; i = _j += 1) {
                             _byte = data[pos++];
                             left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                             pixels[c++] = (_byte + left) % 256;
                         }
                         break;
                     case 2:
                         for (i = _k = 0; _k < scanlineLength; i = _k += 1) {
                             _byte = data[pos++];
                             col = (i - (i % pixelBytes)) / pixelBytes;
                             upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                             pixels[c++] = (upper + _byte) % 256;
                         }
                         break;
                     case 3:
                         for (i = _l = 0; _l < scanlineLength; i = _l += 1) {
                             _byte = data[pos++];
                             col = (i - (i % pixelBytes)) / pixelBytes;
                             left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                             upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                             pixels[c++] = (_byte + Math.floor((left + upper) / 2)) % 256;
                         }
                         break;
                     case 4:
                         for (i = _m = 0; _m < scanlineLength; i = _m += 1) {
                             _byte = data[pos++];
                             col = (i - (i % pixelBytes)) / pixelBytes;
                             left = i < pixelBytes ? 0 : pixels[c - pixelBytes];
                             if (row === 0) {
                                 upper = upperLeft = 0;
                             } else {
                                 upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)];
                                 upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)];
                             }
                             p = left + upper - upperLeft;
                             pa = Math.abs(p - left);
                             pb = Math.abs(p - upper);
                             pc = Math.abs(p - upperLeft);
                             if (pa <= pb && pa <= pc) {
                                 paeth = left;
                             } else if (pb <= pc) {
                                 paeth = upper;
                             } else {
                                 paeth = upperLeft;
                             }
                             pixels[c++] = (_byte + paeth) % 256;
                         }
                         break;
                     default:
                         throw new Error("Invalid filter algorithm: " + data[pos - 1]);
                 }
                 row++;
             }
             return pixels;
         };

         PNG.prototype.decodePalette = function () {
             var c, i, length, palette, pos, ret, transparency, _i, _ref, _ref1;
             palette = this.palette;
             transparency = this.transparency.indexed || [];
             ret = new Uint8Array((transparency.length || 0) + palette.length);
             pos = 0;
             length = palette.length;
             c = 0;
             for (i = _i = 0, _ref = palette.length; _i < _ref; i = _i += 3) {
                 ret[pos++] = palette[i];
                 ret[pos++] = palette[i + 1];
                 ret[pos++] = palette[i + 2];
                 ret[pos++] = (_ref1 = transparency[c++]) != null ? _ref1 : 255;
             }
             return ret;
         };

         PNG.prototype.copyToImageData = function (imageData, pixels) {
             var alpha, colors, data, i, input, j, k, length, palette, v, _ref;
             colors = this.colors;
             palette = null;
             alpha = this.hasAlphaChannel;
             if (this.palette.length) {
                 palette = (_ref = this._decodedPalette) != null ? _ref : this._decodedPalette = this.decodePalette();
                 colors = 4;
                 alpha = true;
             }
             data = imageData.data || imageData;
             length = data.length;
             input = palette || pixels;
             i = j = 0;
             if (colors === 1) {
                 while (i < length) {
                     k = palette ? pixels[i / 4] * 4 : j;
                     v = input[k++];
                     data[i++] = v;
                     data[i++] = v;
                     data[i++] = v;
                     data[i++] = alpha ? input[k++] : 255;
                     j = k;
                 }
             } else {
                 while (i < length) {
                     k = palette ? pixels[i / 4] * 4 : j;
                     data[i++] = input[k++];
                     data[i++] = input[k++];
                     data[i++] = input[k++];
                     data[i++] = alpha ? input[k++] : 255;
                     j = k;
                 }
             }
         };

         PNG.prototype.decode = function () {
             var ret;
             ret = new Uint8Array(this.width * this.height * 4);
             this.copyToImageData(ret, this.decodePixels());
             return ret;
         };

         scratchCanvas = window.document.createElement('canvas');

         scratchCtx = scratchCanvas.getContext('2d');

         makeImage = function (imageData) {
             var img;
             scratchCtx.width = imageData.width;
             scratchCtx.height = imageData.height;
             scratchCtx.clearRect(0, 0, imageData.width, imageData.height);
             scratchCtx.putImageData(imageData, 0, 0);
             img = new Image;
             img.src = scratchCanvas.toDataURL();
             return img;
         };

         PNG.prototype.decodeFrames = function (ctx) {
             var frame, i, imageData, pixels, _i, _len, _ref, _results;
             if (!this.animation) {
                 return;
             }
             _ref = this.animation.frames;
             _results = [];
             for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
                 frame = _ref[i];
                 imageData = ctx.createImageData(frame.width, frame.height);
                 pixels = this.decodePixels(new Uint8Array(frame.data));
                 this.copyToImageData(imageData, pixels);
                 frame.imageData = imageData;
                 _results.push(frame.image = makeImage(imageData));
             }
             return _results;
         };

         return PNG;

     })();

     window.PNG = PNG;

 }).call(this);



 /*
  * Extracted from pdf.js
  * https://github.com/andreasgal/pdf.js
  *
  * Copyright (c) 2011 Mozilla Foundation
  *
  * Contributors: Andreas Gal <gal@mozilla.com>
  *               Chris G Jones <cjones@mozilla.com>
  *               Shaon Barman <shaon.barman@gmail.com>
  *               Vivien Nicolas <21@vingtetun.org>
  *               Justin D'Arcangelo <justindarc@gmail.com>
  *               Yury Delendik
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
  * to deal in the Software without restriction, including without limitation
  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  * and/or sell copies of the Software, and to permit persons to whom the
  * Software is furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  * DEALINGS IN THE SOFTWARE.
  */

 var DecodeStream = (function () {
     function constructor() {
         this.pos = 0;
         this.bufferLength = 0;
         this.eof = false;
         this.buffer = null;
     }

     constructor.prototype = {
         ensureBuffer: function decodestream_ensureBuffer(requested) {
             var buffer = this.buffer;
             var current = buffer ? buffer.byteLength : 0;
             if (requested < current)
                 return buffer;
             var size = 512;
             while (size < requested)
                 size <<= 1;
             var buffer2 = new Uint8Array(size);
             for (var i = 0; i < current; ++i)
                 buffer2[i] = buffer[i];
             return this.buffer = buffer2;
         },
         getByte: function decodestream_getByte() {
             var pos = this.pos;
             while (this.bufferLength <= pos) {
                 if (this.eof)
                     return null;
                 this.readBlock();
             }
             return this.buffer[this.pos++];
         },
         getBytes: function decodestream_getBytes(length) {
             var pos = this.pos;

             if (length) {
                 this.ensureBuffer(pos + length);
                 var end = pos + length;

                 while (!this.eof && this.bufferLength < end)
                     this.readBlock();

                 var bufEnd = this.bufferLength;
                 if (end > bufEnd)
                     end = bufEnd;
             } else {
                 while (!this.eof)
                     this.readBlock();

                 var end = this.bufferLength;
             }

             this.pos = end;
             return this.buffer.subarray(pos, end);
         },
         lookChar: function decodestream_lookChar() {
             var pos = this.pos;
             while (this.bufferLength <= pos) {
                 if (this.eof)
                     return null;
                 this.readBlock();
             }
             return String.fromCharCode(this.buffer[this.pos]);
         },
         getChar: function decodestream_getChar() {
             var pos = this.pos;
             while (this.bufferLength <= pos) {
                 if (this.eof)
                     return null;
                 this.readBlock();
             }
             return String.fromCharCode(this.buffer[this.pos++]);
         },
         makeSubStream: function decodestream_makeSubstream(start, length, dict) {
             var end = start + length;
             while (this.bufferLength <= end && !this.eof)
                 this.readBlock();
             return new Stream(this.buffer, start, length, dict);
         },
         skip: function decodestream_skip(n) {
             if (!n)
                 n = 1;
             this.pos += n;
         },
         reset: function decodestream_reset() {
             this.pos = 0;
         }
     };

     return constructor;
 })();

 var FlateStream = (function () {
     var codeLenCodeMap = new Uint32Array([
       16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
     ]);

     var lengthDecode = new Uint32Array([
       0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a,
       0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f,
       0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073,
       0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102
     ]);

     var distDecode = new Uint32Array([
       0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d,
       0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1,
       0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01,
       0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001
     ]);

     var fixedLitCodeTab = [new Uint32Array([
       0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0,
       0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0,
       0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0,
       0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0,
       0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8,
       0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8,
       0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8,
       0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8,
       0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4,
       0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4,
       0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4,
       0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4,
       0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc,
       0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec,
       0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc,
       0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc,
       0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2,
       0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2,
       0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2,
       0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2,
       0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca,
       0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea,
       0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da,
       0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa,
       0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6,
       0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6,
       0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6,
       0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6,
       0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce,
       0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee,
       0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de,
       0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe,
       0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1,
       0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1,
       0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1,
       0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1,
       0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9,
       0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9,
       0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9,
       0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9,
       0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5,
       0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5,
       0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5,
       0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5,
       0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd,
       0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed,
       0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd,
       0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd,
       0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3,
       0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3,
       0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3,
       0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3,
       0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb,
       0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb,
       0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db,
       0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb,
       0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7,
       0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7,
       0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7,
       0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7,
       0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf,
       0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef,
       0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df,
       0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff
     ]), 9];

     var fixedDistCodeTab = [new Uint32Array([
       0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c,
       0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000,
       0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d,
       0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000
     ]), 5];

     function error(e) {
         throw new Error(e)
     }

     function constructor(bytes) {
         //var bytes = stream.getBytes();
         var bytesPos = 0;

         var cmf = bytes[bytesPos++];
         var flg = bytes[bytesPos++];
         if (cmf == -1 || flg == -1)
             error('Invalid header in flate stream');
         if ((cmf & 0x0f) != 0x08)
             error('Unknown compression method in flate stream');
         if ((((cmf << 8) + flg) % 31) != 0)
             error('Bad FCHECK in flate stream');
         if (flg & 0x20)
             error('FDICT bit set in flate stream');

         this.bytes = bytes;
         this.bytesPos = bytesPos;

         this.codeSize = 0;
         this.codeBuf = 0;

         DecodeStream.call(this);
     }

     constructor.prototype = Object.create(DecodeStream.prototype);

     constructor.prototype.getBits = function (bits) {
         var codeSize = this.codeSize;
         var codeBuf = this.codeBuf;
         var bytes = this.bytes;
         var bytesPos = this.bytesPos;

         var b;
         while (codeSize < bits) {
             if (typeof (b = bytes[bytesPos++]) == 'undefined')
                 error('Bad encoding in flate stream');
             codeBuf |= b << codeSize;
             codeSize += 8;
         }
         b = codeBuf & ((1 << bits) - 1);
         this.codeBuf = codeBuf >> bits;
         this.codeSize = codeSize -= bits;
         this.bytesPos = bytesPos;
         return b;
     };

     constructor.prototype.getCode = function (table) {
         var codes = table[0];
         var maxLen = table[1];
         var codeSize = this.codeSize;
         var codeBuf = this.codeBuf;
         var bytes = this.bytes;
         var bytesPos = this.bytesPos;

         while (codeSize < maxLen) {
             var b;
             if (typeof (b = bytes[bytesPos++]) == 'undefined')
                 error('Bad encoding in flate stream');
             codeBuf |= (b << codeSize);
             codeSize += 8;
         }
         var code = codes[codeBuf & ((1 << maxLen) - 1)];
         var codeLen = code >> 16;
         var codeVal = code & 0xffff;
         if (codeSize == 0 || codeSize < codeLen || codeLen == 0)
             error('Bad encoding in flate stream');
         this.codeBuf = (codeBuf >> codeLen);
         this.codeSize = (codeSize - codeLen);
         this.bytesPos = bytesPos;
         return codeVal;
     };

     constructor.prototype.generateHuffmanTable = function (lengths) {
         var n = lengths.length;

         // find max code length
         var maxLen = 0;
         for (var i = 0; i < n; ++i) {
             if (lengths[i] > maxLen)
                 maxLen = lengths[i];
         }

         // build the table
         var size = 1 << maxLen;
         var codes = new Uint32Array(size);
         for (var len = 1, code = 0, skip = 2;
              len <= maxLen;
              ++len, code <<= 1, skip <<= 1) {
             for (var val = 0; val < n; ++val) {
                 if (lengths[val] == len) {
                     // bit-reverse the code
                     var code2 = 0;
                     var t = code;
                     for (var i = 0; i < len; ++i) {
                         code2 = (code2 << 1) | (t & 1);
                         t >>= 1;
                     }

                     // fill the table entries
                     for (var i = code2; i < size; i += skip)
                         codes[i] = (len << 16) | val;

                     ++code;
                 }
             }
         }

         return [codes, maxLen];
     };

     constructor.prototype.readBlock = function () {
         function repeat(stream, array, len, offset, what) {
             var repeat = stream.getBits(len) + offset;
             while (repeat-- > 0)
                 array[i++] = what;
         }

         // read block header
         var hdr = this.getBits(3);
         if (hdr & 1)
             this.eof = true;
         hdr >>= 1;

         if (hdr == 0) { // uncompressed block
             var bytes = this.bytes;
             var bytesPos = this.bytesPos;
             var b;

             if (typeof (b = bytes[bytesPos++]) == 'undefined')
                 error('Bad block header in flate stream');
             var blockLen = b;
             if (typeof (b = bytes[bytesPos++]) == 'undefined')
                 error('Bad block header in flate stream');
             blockLen |= (b << 8);
             if (typeof (b = bytes[bytesPos++]) == 'undefined')
                 error('Bad block header in flate stream');
             var check = b;
             if (typeof (b = bytes[bytesPos++]) == 'undefined')
                 error('Bad block header in flate stream');
             check |= (b << 8);
             if (check != (~blockLen & 0xffff))
                 error('Bad uncompressed block length in flate stream');

             this.codeBuf = 0;
             this.codeSize = 0;

             var bufferLength = this.bufferLength;
             var buffer = this.ensureBuffer(bufferLength + blockLen);
             var end = bufferLength + blockLen;
             this.bufferLength = end;
             for (var n = bufferLength; n < end; ++n) {
                 if (typeof (b = bytes[bytesPos++]) == 'undefined') {
                     this.eof = true;
                     break;
                 }
                 buffer[n] = b;
             }
             this.bytesPos = bytesPos;
             return;
         }

         var litCodeTable;
         var distCodeTable;
         if (hdr == 1) { // compressed block, fixed codes
             litCodeTable = fixedLitCodeTab;
             distCodeTable = fixedDistCodeTab;
         } else if (hdr == 2) { // compressed block, dynamic codes
             var numLitCodes = this.getBits(5) + 257;
             var numDistCodes = this.getBits(5) + 1;
             var numCodeLenCodes = this.getBits(4) + 4;

             // build the code lengths code table
             var codeLenCodeLengths = Array(codeLenCodeMap.length);
             var i = 0;
             while (i < numCodeLenCodes)
                 codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3);
             var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths);

             // build the literal and distance code tables
             var len = 0;
             var i = 0;
             var codes = numLitCodes + numDistCodes;
             var codeLengths = new Array(codes);
             while (i < codes) {
                 var code = this.getCode(codeLenCodeTab);
                 if (code == 16) {
                     repeat(this, codeLengths, 2, 3, len);
                 } else if (code == 17) {
                     repeat(this, codeLengths, 3, 3, len = 0);
                 } else if (code == 18) {
                     repeat(this, codeLengths, 7, 11, len = 0);
                 } else {
                     codeLengths[i++] = len = code;
                 }
             }

             litCodeTable =
               this.generateHuffmanTable(codeLengths.slice(0, numLitCodes));
             distCodeTable =
               this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes));
         } else {
             error('Unknown block type in flate stream');
         }

         var buffer = this.buffer;
         var limit = buffer ? buffer.length : 0;
         var pos = this.bufferLength;
         while (true) {
             var code1 = this.getCode(litCodeTable);
             if (code1 < 256) {
                 if (pos + 1 >= limit) {
                     buffer = this.ensureBuffer(pos + 1);
                     limit = buffer.length;
                 }
                 buffer[pos++] = code1;
                 continue;
             }
             if (code1 == 256) {
                 this.bufferLength = pos;
                 return;
             }
             code1 -= 257;
             code1 = lengthDecode[code1];
             var code2 = code1 >> 16;
             if (code2 > 0)
                 code2 = this.getBits(code2);
             var len = (code1 & 0xffff) + code2;
             code1 = this.getCode(distCodeTable);
             code1 = distDecode[code1];
             code2 = code1 >> 16;
             if (code2 > 0)
                 code2 = this.getBits(code2);
             var dist = (code1 & 0xffff) + code2;
             if (pos + len >= limit) {
                 buffer = this.ensureBuffer(pos + len);
                 limit = buffer.length;
             }
             for (var k = 0; k < len; ++k, ++pos)
                 buffer[pos] = buffer[pos - dist];
         }
     };

     return constructor;
 })();