phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
428 lines (381 loc) • 14.7 kB
JavaScript
var ParseXMLBitmapFont = require('../../../src/gameobjects/bitmaptext/ParseXMLBitmapFont');
function createMockNode (attrs)
{
return {
getAttribute: function (name)
{
return attrs[name] !== undefined ? String(attrs[name]) : null;
}
};
}
function createMockXML (infoAttrs, commonAttrs, charNodes, kerningNodes)
{
var info = createMockNode(infoAttrs);
var common = createMockNode(commonAttrs);
return {
getElementsByTagName: function (tag)
{
if (tag === 'info') { return [ info ]; }
if (tag === 'common') { return [ common ]; }
if (tag === 'char') { return charNodes || []; }
if (tag === 'kerning') { return kerningNodes || []; }
return [];
}
};
}
function createMockFrame (options)
{
options = options || {};
return {
cutX: options.cutX !== undefined ? options.cutX : 0,
cutY: options.cutY !== undefined ? options.cutY : 0,
source: {
width: options.sourceWidth !== undefined ? options.sourceWidth : 512,
height: options.sourceHeight !== undefined ? options.sourceHeight : 512
},
sourceIndex: options.sourceIndex !== undefined ? options.sourceIndex : 0,
trimmed: options.trimmed || false,
data: {
spriteSourceSize: {
x: options.trimX !== undefined ? options.trimX : 0,
y: options.trimY !== undefined ? options.trimY : 0
},
cut: {
x: options.cutDataX !== undefined ? options.cutDataX : 0,
y: options.cutDataY !== undefined ? options.cutDataY : 0
}
}
};
}
function createCharNode (id, x, y, w, h, xoffset, yoffset, xadvance)
{
return createMockNode({
id: id,
x: x,
y: y,
width: w,
height: h,
xoffset: xoffset !== undefined ? xoffset : 0,
yoffset: yoffset !== undefined ? yoffset : 0,
xadvance: xadvance !== undefined ? xadvance : w
});
}
describe('ParseXMLBitmapFont', function ()
{
var xml;
var frame;
beforeEach(function ()
{
xml = createMockXML(
{ face: 'Arial', size: 32 },
{ lineHeight: 40 },
[
createCharNode(65, 10, 20, 16, 18, 1, 2, 17) // 'A'
],
[]
);
frame = createMockFrame();
});
describe('return value structure', function ()
{
it('should return an object with font, size, lineHeight, and chars properties', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result).toHaveProperty('font');
expect(result).toHaveProperty('size');
expect(result).toHaveProperty('lineHeight');
expect(result).toHaveProperty('chars');
});
it('should parse font face name from info element', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.font).toBe('Arial');
});
it('should parse font size as integer from info element', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.size).toBe(32);
});
it('should parse lineHeight as integer from common element', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.lineHeight).toBe(40);
});
it('should return chars as an object', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(typeof result.chars).toBe('object');
});
});
describe('ySpacing parameter', function ()
{
it('should default ySpacing to 0', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.lineHeight).toBe(40);
});
it('should add ySpacing to lineHeight', function ()
{
var result = ParseXMLBitmapFont(xml, frame, 0, 10);
expect(result.lineHeight).toBe(50);
});
it('should subtract ySpacing from lineHeight when negative', function ()
{
var result = ParseXMLBitmapFont(xml, frame, 0, -5);
expect(result.lineHeight).toBe(35);
});
});
describe('character parsing', function ()
{
it('should key chars by char code', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65]).toBeDefined();
});
it('should parse character x and y positions', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].x).toBe(10);
expect(result.chars[65].y).toBe(20);
});
it('should parse character width and height', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].width).toBe(16);
expect(result.chars[65].height).toBe(18);
});
it('should compute centerX as floor of width / 2', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].centerX).toBe(8);
});
it('should compute centerY as floor of height / 2', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].centerY).toBe(9);
});
it('should floor centerX for odd widths', function ()
{
xml = createMockXML(
{ face: 'Test', size: 16 },
{ lineHeight: 20 },
[ createCharNode(65, 0, 0, 17, 19) ],
[]
);
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].centerX).toBe(8);
expect(result.chars[65].centerY).toBe(9);
});
it('should parse xOffset and yOffset', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].xOffset).toBe(1);
expect(result.chars[65].yOffset).toBe(2);
});
it('should parse xAdvance', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].xAdvance).toBe(17);
});
it('should initialise char data and kerning as empty objects', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(typeof result.chars[65].data).toBe('object');
expect(typeof result.chars[65].kerning).toBe('object');
});
});
describe('xSpacing parameter', function ()
{
it('should default xSpacing to 0', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].xAdvance).toBe(17);
});
it('should add xSpacing to each character xAdvance', function ()
{
var result = ParseXMLBitmapFont(xml, frame, 5);
expect(result.chars[65].xAdvance).toBe(22);
});
it('should subtract xSpacing from each character xAdvance when negative', function ()
{
var result = ParseXMLBitmapFont(xml, frame, -3);
expect(result.chars[65].xAdvance).toBe(14);
});
});
describe('UV coordinate calculation', function ()
{
it('should compute u0 as (textureX + gx) / textureWidth', function ()
{
// frame cutX=0, gx=10, textureWidth=512
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].u0).toBeCloseTo(10 / 512);
});
it('should compute u1 as (textureX + gx + gw) / textureWidth', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].u1).toBeCloseTo((10 + 16) / 512);
});
it('should compute v0 as 1 - (textureY + gy) / textureHeight', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].v0).toBeCloseTo(1 - 20 / 512);
});
it('should compute v1 as 1 - (textureY + gy + gh) / textureHeight', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].v1).toBeCloseTo(1 - (20 + 18) / 512);
});
it('should account for frame cutX and cutY in UV calculation', function ()
{
frame = createMockFrame({ cutX: 64, cutY: 32 });
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].u0).toBeCloseTo((64 + 10) / 512);
expect(result.chars[65].v0).toBeCloseTo(1 - (32 + 20) / 512);
});
});
describe('multiple characters', function ()
{
it('should parse all characters in the xml', function ()
{
xml = createMockXML(
{ face: 'Font', size: 16 },
{ lineHeight: 20 },
[
createCharNode(65, 0, 0, 8, 10),
createCharNode(66, 10, 0, 8, 10),
createCharNode(67, 20, 0, 8, 10)
],
[]
);
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65]).toBeDefined();
expect(result.chars[66]).toBeDefined();
expect(result.chars[67]).toBeDefined();
});
it('should return an empty chars object when there are no char nodes', function ()
{
xml = createMockXML(
{ face: 'Empty', size: 16 },
{ lineHeight: 20 },
[],
[]
);
var result = ParseXMLBitmapFont(xml, frame);
expect(Object.keys(result.chars).length).toBe(0);
});
});
describe('kerning', function ()
{
it('should store kerning amounts on the second character keyed by first', function ()
{
xml = createMockXML(
{ face: 'Font', size: 16 },
{ lineHeight: 20 },
[
createCharNode(65, 0, 0, 8, 10),
createCharNode(86, 10, 0, 8, 10)
],
[
createMockNode({ first: 65, second: 86, amount: -2 })
]
);
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[86].kerning[65]).toBe(-2);
});
it('should handle multiple kerning pairs', function ()
{
xml = createMockXML(
{ face: 'Font', size: 16 },
{ lineHeight: 20 },
[
createCharNode(65, 0, 0, 8, 10),
createCharNode(84, 10, 0, 8, 10),
createCharNode(86, 20, 0, 8, 10)
],
[
createMockNode({ first: 65, second: 84, amount: -1 }),
createMockNode({ first: 65, second: 86, amount: -3 })
]
);
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[84].kerning[65]).toBe(-1);
expect(result.chars[86].kerning[65]).toBe(-3);
});
it('should leave kerning empty when no kerning nodes exist', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(Object.keys(result.chars[65].kerning).length).toBe(0);
});
});
describe('frame trim adjustment', function ()
{
it('should not adjust gx/gy when frame is not trimmed', function ()
{
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].x).toBe(10);
expect(result.chars[65].y).toBe(20);
});
it('should subtract trimX and trimY from gx and gy when frame is trimmed', function ()
{
frame = createMockFrame({ trimmed: true, trimX: 4, trimY: 6 });
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].x).toBe(6);
expect(result.chars[65].y).toBe(14);
});
it('should handle zero trim offset on a trimmed frame', function ()
{
frame = createMockFrame({ trimmed: true, trimX: 0, trimY: 0 });
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65].x).toBe(10);
expect(result.chars[65].y).toBe(20);
});
});
describe('texture parameter', function ()
{
it('should call texture.add for each character with non-zero dimensions', function ()
{
var mockTexture = { add: vi.fn() };
var result = ParseXMLBitmapFont(xml, frame, 0, 0, mockTexture);
expect(mockTexture.add).toHaveBeenCalledTimes(1);
expect(result.chars[65]).toBeDefined();
});
it('should pass correct arguments to texture.add', function ()
{
frame = createMockFrame({ cutDataX: 5, cutDataY: 3, sourceIndex: 2 });
var mockTexture = { add: vi.fn() };
ParseXMLBitmapFont(xml, frame, 0, 0, mockTexture);
// letter 'A', sourceIndex 2, gx + cut.x, gy + cut.y, gw, gh
expect(mockTexture.add).toHaveBeenCalledWith('A', 2, 10 + 5, 20 + 3, 16, 18);
});
it('should not call texture.add when glyph width is zero', function ()
{
xml = createMockXML(
{ face: 'Font', size: 16 },
{ lineHeight: 20 },
[ createCharNode(32, 0, 0, 0, 10) ], // space char
[]
);
var mockTexture = { add: vi.fn() };
ParseXMLBitmapFont(xml, frame, 0, 0, mockTexture);
expect(mockTexture.add).not.toHaveBeenCalled();
});
it('should not call texture.add when glyph height is zero', function ()
{
xml = createMockXML(
{ face: 'Font', size: 16 },
{ lineHeight: 20 },
[ createCharNode(32, 0, 0, 10, 0) ],
[]
);
var mockTexture = { add: vi.fn() };
ParseXMLBitmapFont(xml, frame, 0, 0, mockTexture);
expect(mockTexture.add).not.toHaveBeenCalled();
});
it('should not call texture.add when texture is not provided', function ()
{
// Should not throw even without texture
var result = ParseXMLBitmapFont(xml, frame);
expect(result.chars[65]).toBeDefined();
});
});
});