Skip to content

Commit a7a7455

Browse files
committed
Merge pull request phaserjs#1837 from Feenposhleen/dev
JSON support for BitmapFont atlases
2 parents 7d5fade + 70428fd commit a7a7455

4 files changed

Lines changed: 157 additions & 48 deletions

File tree

src/gameobjects/BitmapText.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
*/
66

77
/**
8-
* BitmapText objects work by taking a texture file and an XML file that describes the font structure.
8+
* BitmapText objects work by taking a texture file and an XML or JSON file that describes the font structure.
99
* It then generates a new Sprite object for each letter of the text, proportionally spaced out and aligned to
1010
* match the font structure.
1111
*
1212
* BitmapText objects are less flexible than Text objects, in that they have less features such as shadows, fills and the ability
13-
* to use Web Fonts. However you trade this flexibility for pure rendering speed. You can also create visually compelling BitmapTexts by
14-
* processing the font texture in an image editor first, applying fills and any other effects required.
13+
* to use Web Fonts, however you trade this flexibility for rendering speed. You can also create visually compelling BitmapTexts by
14+
* processing the font texture in an image editor, applying fills and any other effects required.
1515
*
1616
* To create multi-line text insert \r, \n or \r\n escape codes into the text string.
1717
*
@@ -24,6 +24,8 @@
2424
* Glyph Designer (OS X, commercial): http://www.71squared.com/en/glyphdesigner
2525
* Littera (Web-based, free): http://kvazars.com/littera/
2626
*
27+
* For most use cases it is recommended to use XML. If you wish to use JSON, the formatting should be equal to the result of running a valid XML file through the popular X2JS library.
28+
*
2729
* @class Phaser.BitmapText
2830
* @constructor
2931
* @extends PIXI.DisplayObjectContainer

src/loader/Cache.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -385,23 +385,29 @@ Phaser.Cache.prototype = {
385385
* @param {string} key - The unique key by which you will reference this object.
386386
* @param {string} url - URL of this font xml file.
387387
* @param {object} data - Extra font data.
388-
* @param {object} xmlData - Texture atlas frames data.
388+
* @param {object} atlasData - Texture atlas frames data.
389389
* @param {number} [xSpacing=0] - If you'd like to add additional horizontal spacing between the characters then set the pixel value here.
390390
* @param {number} [ySpacing=0] - If you'd like to add additional vertical spacing between the lines then set the pixel value here.
391391
*/
392-
addBitmapFont: function (key, url, data, xmlData, xSpacing, ySpacing) {
392+
addBitmapFont: function (key, url, data, atlasData, atlasType, xSpacing, ySpacing) {
393393

394394
this._images[key] = { url: url, data: data };
395395

396396
PIXI.BaseTextureCache[key] = new PIXI.BaseTexture(data);
397397
// PIXI.TextureCache[key] = new PIXI.Texture(PIXI.BaseTextureCache[key]);
398398

399-
Phaser.LoaderParser.bitmapFont(this.game, xmlData, key, xSpacing, ySpacing);
399+
if (atlasType === 'json')
400+
{
401+
Phaser.LoaderParser.jsonBitmapFont(this.game, atlasData, key, xSpacing, ySpacing);
402+
}
403+
else
404+
{
405+
Phaser.LoaderParser.xmlBitmapFont(this.game, atlasData, key, xSpacing, ySpacing);
406+
}
400407

401408
this._bitmapFont[key] = PIXI.BitmapText.fonts[key];
402409

403410
this._resolveURL(url, this._bitmapFont[key]);
404-
405411
},
406412

407413
/**

src/loader/Loader.js

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ Phaser.Loader = function (game) {
112112
this.onFileStart = new Phaser.Signal();
113113

114114
/**
115-
* This event is dispatched when a file has either loaded or failed to load. *
115+
* This event is dispatched when a file has either loaded or failed to load.
116116
*
117117
* Any function bound to this will receive the following parameters:
118118
*
@@ -1176,53 +1176,56 @@ Phaser.Loader.prototype = {
11761176
*
11771177
* @method Phaser.Loader#bitmapFont
11781178
* @param {string} key - Unique asset key of the bitmap font.
1179-
* @param {string} [textureURL] - URL of the Bitmap Font texture file. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "megaFont" then the URL will be "megaFont.png".
1180-
* @param {string} [xmlURL] - URL of the Bitmap Font data file. If undefined or `null` and no data is given the url will be set to `<key>.xml`, i.e. if `key` was "megaFont" then the URL will be "megaFont.xml".
1181-
* @param {object} [xmlData] - An optional XML data object.
1179+
* @param {string} textureURL - URL of the Bitmap Font texture file. If undefined or `null` the url will be set to `<key>.png`, i.e. if `key` was "megaFont" then the URL will be "megaFont.png".
1180+
* @param {string} atlasURL - URL of the Bitmap Font atlas file (xml/json).
1181+
* @param {object} atlasData - An optional Bitmap Font atlas in string form (stringified xml/json).
11821182
* @param {number} [xSpacing=0] - If you'd like to add additional horizontal spacing between the characters then set the pixel value here.
11831183
* @param {number} [ySpacing=0] - If you'd like to add additional vertical spacing between the lines then set the pixel value here.
11841184
* @return {Phaser.Loader} This Loader instance.
11851185
*/
1186-
bitmapFont: function (key, textureURL, xmlURL, xmlData, xSpacing, ySpacing) {
1187-
1186+
bitmapFont: function (key, textureURL, atlasURL, atlasData, xSpacing, ySpacing) {
11881187
if (typeof textureURL === 'undefined' || textureURL === null)
11891188
{
11901189
textureURL = key + '.png';
11911190
}
11921191

1193-
if (typeof xmlURL === 'undefined') { xmlURL = null; }
1194-
if (typeof xmlData === 'undefined') { xmlData = null; }
1192+
if (typeof atlasURL === 'undefined') { atlasURL = null; }
1193+
if (typeof atlasData === 'undefined') { atlasData = null; }
11951194
if (typeof xSpacing === 'undefined') { xSpacing = 0; }
11961195
if (typeof ySpacing === 'undefined') { ySpacing = 0; }
11971196

1198-
if (!xmlURL && !xmlData)
1199-
{
1200-
xmlURL = key + '.xml';
1201-
}
1202-
1203-
// A URL to a json/xml file has been given
1204-
if (xmlURL)
1197+
// A URL to a json/xml atlas has been given
1198+
if (atlasURL)
12051199
{
1206-
this.addToFileList('bitmapfont', key, textureURL, { xmlURL: xmlURL, xSpacing: xSpacing, ySpacing: ySpacing });
1200+
this.addToFileList('bitmapfont', key, textureURL, { atlasURL: atlasURL, xSpacing: xSpacing, ySpacing: ySpacing });
12071201
}
12081202
else
12091203
{
1210-
// An xml string or object has been given
1211-
if (typeof xmlData === 'string')
1204+
// A stringified xml/json atlas has been given
1205+
if (typeof atlasData === 'string')
12121206
{
1213-
var xml = this.parseXml(xmlData);
1207+
var json, xml;
1208+
1209+
try
1210+
{
1211+
json = JSON.parse(atlasData);
1212+
}
1213+
catch ( e )
1214+
{
1215+
xml = this.parseXml(atlasData);
1216+
}
12141217

1215-
if (!xml)
1218+
if (!xml && !json)
12161219
{
1217-
throw new Error("Phaser.Loader. Invalid Bitmap Font XML given");
1220+
throw new Error("Phaser.Loader. Invalid Bitmap Font atlas given");
12181221
}
12191222

1220-
this.addToFileList('bitmapfont', key, textureURL, { xmlURL: null, xmlData: xml, xSpacing: xSpacing, ySpacing: ySpacing });
1223+
this.addToFileList('bitmapfont', key, textureURL, { atlasURL: null, atlasData: json || xml,
1224+
atlasType: (!!json ? 'json' : 'xml'), xSpacing: xSpacing, ySpacing: ySpacing });
12211225
}
12221226
}
12231227

12241228
return this;
1225-
12261229
},
12271230

12281231
/**
@@ -1863,7 +1866,7 @@ Phaser.Loader.prototype = {
18631866
break;
18641867

18651868
case "bitmapFont":
1866-
this.bitmapFont(file.key, file.textureURL, file.xmlURL, file.xmlData, file.xSpacing, file.ySpacing);
1869+
this.bitmapFont(file.key, file.textureURL, file.atlasURL, file.atlasData, file.xSpacing, file.ySpacing);
18671870
break;
18681871

18691872
case "atlasJSONArray":
@@ -2494,15 +2497,35 @@ Phaser.Loader.prototype = {
24942497

24952498
case 'bitmapfont':
24962499

2497-
if (!file.xmlURL)
2500+
if (!file.atlasURL)
24982501
{
2499-
this.game.cache.addBitmapFont(file.key, file.url, file.data, file.xmlData, file.xSpacing, file.ySpacing);
2502+
this.game.cache.addBitmapFont(file.key, file.url, file.data, file.atlasData, file.atlasType, file.xSpacing, file.ySpacing);
25002503
}
25012504
else
25022505
{
25032506
// Load the XML before carrying on with the next file
25042507
loadNext = false;
2505-
this.xhrLoad(file, this.transformUrl(file.xmlURL, file), 'text', this.xmlLoadComplete);
2508+
this.xhrLoad(file, this.transformUrl(file.atlasURL, file), 'text', function (file, xhr) {
2509+
var json;
2510+
2511+
try
2512+
{
2513+
// Try to parse as JSON, if it fails, then it's hopefully XML
2514+
json = JSON.parse(xhr.responseText);
2515+
}
2516+
catch (e) {}
2517+
2518+
if (!!json)
2519+
{
2520+
file.atlasType = 'json';
2521+
this.jsonLoadComplete(file, xhr);
2522+
}
2523+
else
2524+
{
2525+
file.atlasType = 'xml';
2526+
this.xmlLoadComplete(file, xhr);
2527+
}
2528+
});
25062529
}
25072530
break;
25082531

@@ -2603,6 +2626,10 @@ Phaser.Loader.prototype = {
26032626
{
26042627
this.game.cache.addTilemap(file.key, file.url, data, file.format);
26052628
}
2629+
else if (file.type === 'bitmapfont')
2630+
{
2631+
this.game.cache.addBitmapFont(file.key, file.url, file.data, data, file.atlasType, file.xSpacing, file.ySpacing);
2632+
}
26062633
else if (file.type === 'json')
26072634
{
26082635
this.game.cache.addJSON(file.key, file.url, data);
@@ -2613,7 +2640,6 @@ Phaser.Loader.prototype = {
26132640
}
26142641

26152642
this.asyncComplete(file);
2616-
26172643
},
26182644

26192645
/**
@@ -2658,7 +2684,7 @@ Phaser.Loader.prototype = {
26582684

26592685
if (file.type === 'bitmapfont')
26602686
{
2661-
this.game.cache.addBitmapFont(file.key, file.url, file.data, xml, file.xSpacing, file.ySpacing);
2687+
this.game.cache.addBitmapFont(file.key, file.url, file.data, xml, file.atlasType, file.xSpacing, file.ySpacing);
26622688
}
26632689
else if (file.type === 'textureatlas')
26642690
{

src/loader/LoaderParser.js

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
Phaser.LoaderParser = {
1313

1414
/**
15-
* Parse a Bitmap Font from an XML file.
15+
* Alias for xmlBitmapFont, for backwards compatiblity.
1616
*
1717
* @method Phaser.LoaderParser.bitmapFont
1818
* @param {Phaser.Game} game - A reference to the current game.
@@ -22,7 +22,20 @@ Phaser.LoaderParser = {
2222
* @param {number} [ySpacing=0] - Additional vertical spacing between the characters.
2323
*/
2424
bitmapFont: function (game, xml, cacheKey, xSpacing, ySpacing) {
25+
this.xmlBitmapFont(game, xml, cacheKey, xSpacing, ySpacing);
26+
},
2527

28+
/**
29+
* Parse a Bitmap Font from an XML file.
30+
*
31+
* @method Phaser.LoaderParser.xmlBitmapFont
32+
* @param {Phaser.Game} game - A reference to the current game.
33+
* @param {object} xml - XML data you want to parse.
34+
* @param {string} cacheKey - The key of the texture this font uses in the cache.
35+
* @param {number} [xSpacing=0] - Additional horizontal spacing between the characters.
36+
* @param {number} [ySpacing=0] - Additional vertical spacing between the characters.
37+
*/
38+
xmlBitmapFont: function (game, xml, cacheKey, xSpacing, ySpacing) {
2639
var data = {};
2740
var info = xml.getElementsByTagName('info')[0];
2841
var common = xml.getElementsByTagName('common')[0];
@@ -38,19 +51,15 @@ Phaser.LoaderParser = {
3851
{
3952
var charCode = parseInt(letters[i].getAttribute('id'), 10);
4053

41-
var textureRect = new PIXI.Rectangle(
42-
parseInt(letters[i].getAttribute('x'), 10),
43-
parseInt(letters[i].getAttribute('y'), 10),
44-
parseInt(letters[i].getAttribute('width'), 10),
45-
parseInt(letters[i].getAttribute('height'), 10)
46-
);
47-
4854
data.chars[charCode] = {
55+
x: parseInt(letters[i].getAttribute('x'), 10),
56+
y: parseInt(letters[i].getAttribute('y'), 10),
57+
width: parseInt(letters[i].getAttribute('width'), 10),
58+
height: parseInt(letters[i].getAttribute('height'), 10),
4959
xOffset: parseInt(letters[i].getAttribute('xoffset'), 10),
5060
yOffset: parseInt(letters[i].getAttribute('yoffset'), 10),
5161
xAdvance: parseInt(letters[i].getAttribute('xadvance'), 10) + xSpacing,
52-
kerning: {},
53-
texture: new PIXI.Texture(PIXI.BaseTextureCache[cacheKey], textureRect)
62+
kerning: {}
5463
};
5564
}
5665

@@ -65,8 +74,74 @@ Phaser.LoaderParser = {
6574
data.chars[second].kerning[first] = amount;
6675
}
6776

68-
PIXI.BitmapText.fonts[cacheKey] = data;
77+
this.finalizeBitmapFont(cacheKey, data);
78+
},
6979

70-
}
80+
/**
81+
* Parse a Bitmap Font from a JSON file.
82+
*
83+
* @method Phaser.LoaderParser.jsonBitmapFont
84+
* @param {Phaser.Game} game - A reference to the current game.
85+
* @param {object} json - JSON data you want to parse.
86+
* @param {string} cacheKey - The key of the texture this font uses in the cache.
87+
* @param {number} [xSpacing=0] - Additional horizontal spacing between the characters.
88+
* @param {number} [ySpacing=0] - Additional vertical spacing between the characters.
89+
*/
90+
jsonBitmapFont: function (game, json, cacheKey, xSpacing, ySpacing) {
91+
var data = {
92+
font: json.font.info._font,
93+
size: parseInt(json.font.info._size, 10),
94+
lineHeight: parseInt(json.font.common._lineHeight, 10) + ySpacing,
95+
chars: {}
96+
};
97+
98+
json.font.chars["char"].forEach(
99+
function parseChar(letter) {
100+
var charCode = parseInt(letter._id, 10);
101+
102+
data.chars[charCode] = {
103+
x: parseInt(letter._x, 10),
104+
y: parseInt(letter._y, 10),
105+
width: parseInt(letter._width, 10),
106+
height: parseInt(letter._height, 10),
107+
xOffset: parseInt(letter._xoffset, 10),
108+
yOffset: parseInt(letter._yoffset, 10),
109+
xAdvance: parseInt(letter._xadvance, 10) + xSpacing,
110+
kerning: {}
111+
};
112+
}
113+
);
114+
115+
json.font.kernings.kerning.forEach(
116+
function parseKerning(kerning) {
117+
data.chars[kerning._second].kerning[kerning._first] = parseInt(kerning._amount, 10);
118+
}
119+
);
71120

121+
this.finalizeBitmapFont(cacheKey, data);
122+
},
123+
124+
/**
125+
* Finalize Bitmap Font parsing.
126+
*
127+
* @method Phaser.LoaderParser.finalizeBitmapFont
128+
* @private
129+
* @param {string} cacheKey - The key of the texture this font uses in the cache.
130+
* @param {object} bitmapFontData - Pre-parsed bitmap font data.
131+
*/
132+
finalizeBitmapFont: function (cacheKey, bitmapFontData) {
133+
Object.keys(bitmapFontData.chars).forEach(
134+
function addTexture(charCode) {
135+
var letter = bitmapFontData.chars[charCode];
136+
var textureRect = new PIXI.Rectangle(
137+
letter.x, letter.y,
138+
letter.width, letter.height
139+
);
140+
141+
letter.texture = new PIXI.Texture(PIXI.BaseTextureCache[cacheKey], textureRect);
142+
}
143+
);
144+
145+
PIXI.BitmapText.fonts[cacheKey] = bitmapFontData;
146+
}
72147
};

0 commit comments

Comments
 (0)