Source: src/loader/LoaderParser.js

/**
* @author       Richard Davey <rich@photonstorm.com>
* @copyright    2016 Photon Storm Ltd.
* @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/

/**
* Phaser.LoaderParser parses data objects from Phaser.Loader that need more preparation before they can be inserted into the Cache.
*
* @class Phaser.LoaderParser
*/
Phaser.LoaderParser = {

    /**
    * Alias for xmlBitmapFont, for backwards compatibility.
    *
    * @method Phaser.LoaderParser.bitmapFont
    * @param {object} xml - XML data you want to parse.
    * @param {PIXI.BaseTexture} baseTexture - The BaseTexture this font uses.
    * @param {number} [xSpacing=0] - Additional horizontal spacing between the characters.
    * @param {number} [ySpacing=0] - Additional vertical spacing between the characters.
    * @param {Phaser.Frame} [frame] - Optional Frame, if this font is embedded in a texture atlas.
    * @param {number} [resolution=1] - Optional game resolution to apply to the kerning data.
    * @return {object} The parsed Bitmap Font data.
    */
    bitmapFont: function (xml, baseTexture, xSpacing, ySpacing, frame, resolution) {

        return this.xmlBitmapFont(xml, baseTexture, xSpacing, ySpacing, frame, resolution);

    },

    /**
    * Parse a Bitmap Font from an XML file.
    *
    * @method Phaser.LoaderParser.xmlBitmapFont
    * @param {object} xml - XML data you want to parse.
    * @param {PIXI.BaseTexture} baseTexture - The BaseTexture this font uses.
    * @param {number} [xSpacing=0] - Additional horizontal spacing between the characters.
    * @param {number} [ySpacing=0] - Additional vertical spacing between the characters.
    * @param {Phaser.Frame} [frame] - Optional Frame, if this font is embedded in a texture atlas.
    * @param {number} [resolution=1] - Optional game resolution to apply to the kerning data.
    * @return {object} The parsed Bitmap Font data.
    */
    xmlBitmapFont: function (xml, baseTexture, xSpacing, ySpacing, frame, resolution) {

        if (resolution == null)
        {
            resolution = 1;
        }

        var data = {};
        var info = xml.getElementsByTagName('info')[0];
        var common = xml.getElementsByTagName('common')[0];

        data.font = info.getAttribute('face');
        data.size = parseInt(info.getAttribute('size'), 10);
        data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) + ySpacing;
        data.chars = {};

        var letters = xml.getElementsByTagName('char');

        var x = (frame) ? frame.x : 0;
        var y = (frame) ? frame.y : 0;

        for (var i = 0; i < letters.length; i++)        {
            var charCode = parseInt(letters[i].getAttribute('id'), 10);

            data.chars[charCode] = {
                x: x + parseInt(letters[i].getAttribute('x'), 10),
                y: y + parseInt(letters[i].getAttribute('y'), 10),
                width: parseInt(letters[i].getAttribute('width'), 10),
                height: parseInt(letters[i].getAttribute('height'), 10),
                xOffset: parseInt(letters[i].getAttribute('xoffset'), 10) / resolution,
                yOffset: parseInt(letters[i].getAttribute('yoffset'), 10) / resolution,
                xAdvance: (parseInt(letters[i].getAttribute('xadvance'), 10) + xSpacing) / resolution,
                kerning: {}
            };
        }

        var kernings = xml.getElementsByTagName('kerning');

        for (i = 0; i < kernings.length; i++)        {
            var first = parseInt(kernings[i].getAttribute('first'), 10);
            var second = parseInt(kernings[i].getAttribute('second'), 10);
            var amount = parseInt(kernings[i].getAttribute('amount'), 10) / resolution;

            data.chars[second].kerning[first] = amount;
        }

        return this.finalizeBitmapFont(baseTexture, data);

    },

    /**
    * Parse a Bitmap Font from a JSON file.
    *
    * @method Phaser.LoaderParser.jsonBitmapFont
    * @param {object} json - JSON data you want to parse.
    * @param {PIXI.BaseTexture} baseTexture - The BaseTexture this font uses.
    * @param {number} [xSpacing=0] - Additional horizontal spacing between the characters.
    * @param {number} [ySpacing=0] - Additional vertical spacing between the characters.
    * @param {Phaser.Frame} [frame] - Optional Frame, if this font is embedded in a texture atlas.
    * @param {number} [resolution=1] - Optional game resolution to apply to the kerning data.
    * @return {object} The parsed Bitmap Font data.
    */
    jsonBitmapFont: function (json, baseTexture, xSpacing, ySpacing, frame, resolution) {

        if (resolution == null)
        {
            resolution = 1;
        }

        var data = {
            font: json.font.info._face,
            size: parseInt(json.font.info._size, 10),
            lineHeight: parseInt(json.font.common._lineHeight, 10) + ySpacing,
            chars: {}
        };

        var x = (frame) ? frame.x : 0;
        var y = (frame) ? frame.y : 0;

        json.font.chars["char"].forEach(
            function parseChar(letter) {

                var charCode = parseInt(letter._id, 10);

                data.chars[charCode] = {
                    x: x + parseInt(letter._x, 10),
                    y: y + parseInt(letter._y, 10),
                    width: parseInt(letter._width, 10),
                    height: parseInt(letter._height, 10),
                    xOffset: parseInt(letter._xoffset, 10) / resolution,
                    yOffset: parseInt(letter._yoffset, 10) / resolution,
                    xAdvance: (parseInt(letter._xadvance, 10) + xSpacing) / resolution,
                    kerning: {}
                };
            }

        );

        if (json.font.kernings && json.font.kernings.kerning)
        {
            json.font.kernings.kerning.forEach(
                function parseKerning(kerning) {

                    data.chars[kerning._second].kerning[kerning._first] = parseInt(kerning._amount, 10) / resolution;

                }

            );
        }

        return this.finalizeBitmapFont(baseTexture, data);

    },

    /**
    * Finalize Bitmap Font parsing.
    *
    * @method Phaser.LoaderParser.finalizeBitmapFont
    * @private
    * @param {PIXI.BaseTexture} baseTexture - The BaseTexture this font uses.
    * @param {object} bitmapFontData - Pre-parsed bitmap font data.
    * @return {object} The parsed Bitmap Font data.
    */
    finalizeBitmapFont: function (baseTexture, bitmapFontData) {

        Object.keys(bitmapFontData.chars).forEach(
            function addTexture(charCode) {

                var letter = bitmapFontData.chars[charCode];

                letter.texture = new PIXI.Texture(baseTexture, new Phaser.Rectangle(letter.x, letter.y, letter.width, letter.height));

            }

        );

        return bitmapFontData;

    },

    /**
    * Extract PVR header from loaded binary
    *
    * @method Phaser.LoaderParser.pvr
    * @param {ArrayBuffer} arrayBuffer
    * @return {object} The parsed PVR file including texture data.
    */
    pvr: function (arrayBuffer) {

        // Reference: http://cdn.imgtec.com/sdk-documentation/PVR+File+Format.Specification.pdf
        // PVR 3 header structure
        // ---------------------------------------
        // address: 0, size: 4 bytes version
        // address: 4, size: 4 bytes flags
        // address: 8, size: 8 bytes pixel format
        // address: 16, size: 4 bytes color space
        // address: 20, size: 4 bytes channel type
        // address: 24, size: 4 bytes height
        // address: 28, size: 4 bytes width
        // address: 32, size: 4 bytes depth
        // address: 36, size: 4 bytes number of surfaces
        // address: 40, size: 4 bytes number of faces
        // address: 44, size: 4 bytes number of mipmaps
        // address: 48, size: 4 bytes meta data size
        // ---------------------------------------
        var uintArray = new Uint32Array(arrayBuffer.slice(0, 52)),
            byteArray = new Uint8Array(arrayBuffer),
            pvrHeader = null,
            pixelFormat = (uintArray[3] << 32 | uintArray[2]),            compressionAlgorithm,
            glExtensionFormat = 0;

        if (uintArray[0] === 0x03525650 &&            [ // Validate WebGL Pixel Format
                0, 1, 2, 3,
                6, 7, 9, 11
            ].indexOf(pixelFormat) >= 0
        ) {
            if (pixelFormat >= 0 && pixelFormat <= 3) {                compressionAlgorithm = 'PVRTC';
            } else if (pixelFormat >= 7 && pixelFormat <= 11) {                compressionAlgorithm = 'S3TC';
            } else if (pixelFormat === 6) {
                compressionAlgorithm = 'ETC1';
            }

            switch (pixelFormat) {
                case 0:
                    glExtensionFormat = 0x8C01;
                    break;
                case 1:
                    glExtensionFormat = 0x8C03;
                    break;
                case 2:
                    glExtensionFormat = 0x8C00;
                    break;
                case 3:
                    glExtensionFormat = 0x8C02;
                    break;
                case 6:
                    glExtensionFormat = 0x8D64;
                    break;
                case 7:
                    glExtensionFormat = 0x83F1;
                    break;
                case 9:
                    glExtensionFormat = 0x83F2;
                    break;
                case 11:
                    glExtensionFormat = 0x83F3;
                    break;
                default:
                    glExtensionFormat = -1;
            }

            pvrHeader = {
                complete: true,
                fileFormat: 'PVR',
                compressionAlgorithm: compressionAlgorithm,
                flags: uintArray[1],
                pixelFormat: pixelFormat,
                colorSpace: uintArray[4],
                channelType: uintArray[5],
                height: uintArray[6],
                width: uintArray[7],
                depth: uintArray[8],
                numberOfSurfaces: uintArray[9],
                numberOfFaces: uintArray[10],
                numberOfMipmaps: uintArray[11],
                metaDataSize: uintArray[12],
                textureData: byteArray.subarray(52 + uintArray[12], byteArray.byteLength),
                glExtensionFormat: glExtensionFormat
            };
        }

        return pvrHeader;

    },

    /**
    * Extract DDS header from loaded binary
    *
    * @method Phaser.LoaderParser.dds
    * @param {ArrayBuffer} arrayBuffer
    * @return {object} The parsed DDS file including texture data.
    */
    dds: function (arrayBuffer) {

        // Reference at: https://msdn.microsoft.com/en-us/library/windows/desktop/bb943982(v=vs.85).aspx
        // DDS header structure
        // ---------------------------------------
        // address: 0, size: 4 bytes Identifier 'DDS '
        // address: 4, size: 4 bytes size
        // address: 8, size: 4 bytes flags
        // address: 12, size: 4 bytes height
        // address: 16, size: 4 bytes width
        // address: 20, size: 4 bytes pitch or linear size
        // address: 24, size: 4 bytes depth
        // address: 28, size: 4 bytes mipmap count
        // address: 32, size: 44 bytes reserved space 1
        // address: 76, size: 4 pixel format size
        // address: 80, size: 4 pixel format flag
        // address: 84, size: 4 pixel format four CC
        // address: 88, size: 4 pixel format bit count
        // address: 92, size: 4 pixel format R bit mask
        // address: 96, size: 4 pixel format G bit mask
        // address: 100, size: 4 pixel format B bit mask
        // address: 104, size: 4 pixel format A bit mask
        // address: 108, size: 4 caps 1
        // address: 112, size: 4 caps 2
        // address: 116, size: 4 caps 3
        // address: 120, size: 4 caps 4
        // address: 124, size: 4 reserved 2
        // -- DXT10 extension
        // address: 130, size: 4 DXGI Format
        // address: 134, size: 4 resource dimension
        // address: 138, size: 4 misc flag
        // address: 142, size: 4 array size
        // address: 146, size: 4 misc flag 2
        // ---------------------------------------
        var byteArray = new Uint8Array(arrayBuffer),
            uintArray = new Uint32Array(arrayBuffer),
            ddsHeader = null;

        if (byteArray[0] === 0x44 &&            byteArray[1] === 0x44 &&
            byteArray[2] === 0x53 &&
            byteArray[3] === 0x20) {
            ddsHeader = {
                complete: true,
                fileFormat: 'DDS',
                compressionAlgorithm: 'S3TC',
                size: uintArray[1],
                flags: uintArray[2],
                height: uintArray[3],
                width: uintArray[4],
                pitch: uintArray[5],
                depth: uintArray[6],
                mipmapCount: uintArray[7],
                formatSize: uintArray[19],
                formatFlag: uintArray[19],
                formatFourCC: [
                    String.fromCharCode(byteArray[84]),
                    String.fromCharCode(byteArray[85]),
                    String.fromCharCode(byteArray[86]),
                    String.fromCharCode(byteArray[87])
                ].join(''),
                formatBitCount: uintArray[21],
                formatRBitMask: uintArray[22],
                formatGBitMask: uintArray[23],
                formatBBitMask: uintArray[24],
                formatABitMask: uintArray[25],
                caps1: uintArray[26],
                caps2: uintArray[27],
                caps3: uintArray[28],
                caps4: uintArray[29],
                reserved2: uintArray[30],
                DXGIFormat: null,
                resourceDimension: null,
                miscFlag: null,
                arraySize: null,
                textureData: byteArray.subarray(uintArray[1] + 4, byteArray.byteLength)
            };
            if (ddsHeader.formatFourCC === 'DX10') {
                ddsHeader.DXGIFormat = uintArray[31];
                ddsHeader.resourceDimension = uintArray[32];
                ddsHeader.miscFlag = uintArray[33];
                ddsHeader.arraySize = uintArray[34];
                ddsHeader.miscFlag = uintArray[35];
            }
        }

        return ddsHeader;

    },

    /**
    * Extract KTX header from loaded binary
    *
    * @method Phaser.LoaderParser.ktx
    * @param {ArrayBuffer} arrayBuffer
    * @return {object} The parsed KTX file including texture data.
    */
    ktx: function (arrayBuffer) {

        // Reference: https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
        // KTX header structure
        // ---------------------------------------
        // address: 0, size 12 bytes: Identifier '«KTX 11»\r\n\x1A\n'
        // address: 12, size 4 bytes: endianness
        // address: 16, size 4 bytes: GL type
        // address: 20, size 4 bytes: GL type size
        // address: 24, size 4 bytes: GL format
        // address: 28, size 4 bytes: GL internal format
        // address: 32, size 4 bytes: GL base internal format
        // address: 36, size 4 bytes: pixel width
        // address: 40, size 4 bytes: pixel height
        // address: 44, size 4 bytes: pixel depth
        // address: 48, size 4 bytes: number of array elements
        // address: 52, size 4 bytes: number of faces
        // address: 56, size 4 bytes: number of mipmap levels
        // address: 60, size 4 bytes: bytes of key value data
        // address: 64, size 4 bytes: key and value bytes size
        // address: X, size 1 byte : key and value
        // address: X, size 1 byte : value padding
        // address: X, size 4 byte : image size
        // ---------------------------------------
        var byteArray = new Uint8Array(arrayBuffer),
            uintArray = new Uint32Array(arrayBuffer),
            ktxHeader = null,
            imageSizeOffset = 16 + (uintArray[15] / 4) | 0,
            imageSize = uintArray[imageSizeOffset],
            glInternalFormat = uintArray[7],
            compressionAlgorithm = 0;

        if (byteArray[0] === 0xAB && byteArray[1] === 0x4B &&            byteArray[2] === 0x54 && byteArray[3] === 0x58 &&
            byteArray[4] === 0x20 && byteArray[5] === 0x31 &&
            byteArray[6] === 0x31 && byteArray[7] === 0xBB &&
            byteArray[8] === 0x0D && byteArray[9] === 0x0A &&
            byteArray[10] === 0x1A && byteArray[11] === 0x0A &&
            //Check if internal GL format is supported by WebGL
            [
                // ETC1
                0x8D64,
                // PVRTC
                0x8C00, 0x8C01, 0x8C02, 0x8C03,
                // DXTC | S3TC
                0x83F0, 0x83F1, 0x83F2, 0x83F3
            ].indexOf(glInternalFormat) >= 0) {
            switch (glInternalFormat) {
                case 0x8D64:
                    compressionAlgorithm = 'ETC1';
                    break;
                case 0x8C00:
                case 0x8C01:
                case 0x8C02:
                case 0x8C03:
                    compressionAlgorithm = 'PVRTC';
                    break;
                case 0x83F0:
                case 0x83F1:
                case 0x83F2:
                case 0x83F3:
                    compressionAlgorithm = 'S3TC';
                    break;
            }

            ktxHeader = {
                complete: true,
                fileFormat: 'KTX',
                compressionAlgorithm: compressionAlgorithm,
                endianness: uintArray[3],
                glType: uintArray[4],
                glTypeSize: uintArray[5],
                glFormat: uintArray[6],
                glInternalFormat: uintArray[7],
                glBaseInternalFormat: uintArray[8],
                width: uintArray[9],
                height: uintArray[10],
                pixelDepth: uintArray[11],
                numberOfArrayElements: uintArray[12],
                numberOfFaces: uintArray[13],
                numberOfMipmapLevels: uintArray[14],
                bytesOfKeyValueData: uintArray[15],
                keyAndValueByteSize: uintArray[16],
                imageSize: imageSize,
                textureData: byteArray.subarray((imageSizeOffset + 1) * 4, imageSize + 100)
            };
        }

        return ktxHeader;

    },

    /**
    * Extract PKM header from loaded binary
    *
    * @method Phaser.LoaderParser.pkm
    * @param {ArrayBuffer} arrayBuffer
    * @return {object} The parsed PKM file including texture data.
    */
    pkm: function (arrayBuffer) {

        // PKM header structure
        // ---------------------------------------
        // address: 0, size 4 bytes: for 'PKM '
        // address: 4, size 2 bytes: for version
        // address: 6, size 2 bytes: for type
        // address: 8, size 2 bytes: for extended width
        // address: 10, size 2 bytes: for extended height
        // address: 12, size 2 bytes: for original width
        // address: 14, size 2 bytes: for original height
        // address: 16, texture data
        // ---------------------------------------
        var byteArray = new Uint8Array(arrayBuffer),
            pkmHeader = null;

        if (byteArray[0] === 0x50 &&            byteArray[1] === 0x4B &&
            byteArray[2] === 0x4D &&
            byteArray[3] === 0x20) {

            pkmHeader = {
                complete: true,
                fileFormat: 'PKM',
                compressionAlgorithm: 'ETC1',
                format: ((byteArray[6] << 8 | byteArray[7])) & 0xFFFF,                width: ((byteArray[8] << 8 | byteArray[9])) & 0xFFFF,                height: ((byteArray[10] << 8 | byteArray[11])) & 0xFFFF,                originalWidth: ((byteArray[12] << 8 | byteArray[13])) & 0xFFFF,                originalHeight: ((byteArray[14] << 8 | byteArray[15])) & 0xFFFF,                textureData: byteArray.subarray(16, byteArray.length)
            };
        }

        return pkmHeader;

    }

};