diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index a8b680e..631e11b 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -543,7 +543,8 @@ * * @static * @param {XMLDocument} xml - The XML document data. - * @param {PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * @param {Object.|PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * If providing an object, the key is the `` element's `file` attribute in the FNT file. * @return {Object} Result font object with font, size, lineHeight and char fields. */ static registerFont(xml, textures) @@ -551,50 +552,52 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); - const res = getResolutionOfUrl(fileName, settings.RESOLUTION); + const pages = xml.getElementsByTagName('page'); + const res = getResolutionOfUrl(pages[0].getAttribute('file'), settings.RESOLUTION); + const pagesTextures = {}; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; - if (!(textures instanceof Array)) + + // Single texture, convert to list + if (textures instanceof core.Texture) { textures = [textures]; } + // Convert the input Texture, Textures or object + // into a page Texture lookup by "id" + for (let i = 0; i < pages.length; i++) + { + const id = pages[i].getAttribute('id'); + const file = pages[i].getAttribute('file'); + + pagesTextures[id] = textures instanceof Array ? textures[i] : textures[file]; + } + // parse letters const letters = xml.getElementsByTagName('char'); - let page; for (let i = 0; i < letters.length; i++) { const letter = letters[i]; const charCode = parseInt(letter.getAttribute('id'), 10); - let textureRect; - - page = parseInt(letter.getAttribute('page'), 10); - if (isNaN(page)) - { - textureRect = new core.Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new core.Rectangle( - (parseInt(letter.getAttribute('x'), 10) / res) + (textures[page].frame.x / res), - (parseInt(letter.getAttribute('y'), 10) / res) + (textures[page].frame.y / res), - parseInt(letter.getAttribute('width'), 10) / res, - parseInt(letter.getAttribute('height'), 10) / res - ); - } + const page = letter.getAttribute('page') || 0; + const textureRect = new core.Rectangle( + (parseInt(letter.getAttribute('x'), 10) / res) + (pagesTextures[page].frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (pagesTextures[page].frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res + ); data.chars[charCode] = { xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, - texture: new core.Texture(textures[page].baseTexture, textureRect), + texture: new core.Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index a8b680e..631e11b 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -543,7 +543,8 @@ * * @static * @param {XMLDocument} xml - The XML document data. - * @param {PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * @param {Object.|PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * If providing an object, the key is the `` element's `file` attribute in the FNT file. * @return {Object} Result font object with font, size, lineHeight and char fields. */ static registerFont(xml, textures) @@ -551,50 +552,52 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); - const res = getResolutionOfUrl(fileName, settings.RESOLUTION); + const pages = xml.getElementsByTagName('page'); + const res = getResolutionOfUrl(pages[0].getAttribute('file'), settings.RESOLUTION); + const pagesTextures = {}; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; - if (!(textures instanceof Array)) + + // Single texture, convert to list + if (textures instanceof core.Texture) { textures = [textures]; } + // Convert the input Texture, Textures or object + // into a page Texture lookup by "id" + for (let i = 0; i < pages.length; i++) + { + const id = pages[i].getAttribute('id'); + const file = pages[i].getAttribute('file'); + + pagesTextures[id] = textures instanceof Array ? textures[i] : textures[file]; + } + // parse letters const letters = xml.getElementsByTagName('char'); - let page; for (let i = 0; i < letters.length; i++) { const letter = letters[i]; const charCode = parseInt(letter.getAttribute('id'), 10); - let textureRect; - - page = parseInt(letter.getAttribute('page'), 10); - if (isNaN(page)) - { - textureRect = new core.Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new core.Rectangle( - (parseInt(letter.getAttribute('x'), 10) / res) + (textures[page].frame.x / res), - (parseInt(letter.getAttribute('y'), 10) / res) + (textures[page].frame.y / res), - parseInt(letter.getAttribute('width'), 10) / res, - parseInt(letter.getAttribute('height'), 10) / res - ); - } + const page = letter.getAttribute('page') || 0; + const textureRect = new core.Rectangle( + (parseInt(letter.getAttribute('x'), 10) / res) + (pagesTextures[page].frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (pagesTextures[page].frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res + ); data.chars[charCode] = { xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, - texture: new core.Texture(textures[page].baseTexture, textureRect), + texture: new core.Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index ce90e44..c5f17dd 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -67,47 +67,56 @@ } const pages = resource.data.getElementsByTagName('page'); - const textures = []; + const textures = {}; // Handle completed, when the number of textures // load is the same number as references in the fnt file - const completed = () => + const completed = (page) => { - if (textures.length === pages.length) + textures[page.metadata.pageFile] = page.texture; + + if (Object.keys(textures).length === pages.length) { parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - for (let i = 0; i < pages.length; ++i) { - const url = xmlUrl + pages[i].getAttribute('file'); + const pageFile = pages[i].getAttribute('file'); + const url = xmlUrl + pageFile; + let exists = false; + + // incase the image is loaded outside + // using the same loader, resource will be available + for (const name in this.resources) + { + if (this.resources[name].url === url) + { + this.resources[name].metadata.pageFile = pageFile; + completed(this.resources[name]); + exists = true; + break; + } + } // texture is not loaded, we'll attempt to add // it to the load and add the texture to the list - if (!this.resources[url]) + if (!exists) { - this.add(url, loadOptions, (resource) => - { - textures.push(resource.texture); - completed(); - }); - } - else - { - // incase the image is loaded outside - // using the same loader, texture will be available - textures.push(this.resources[url].texture); - completed(); + // Standard loading options for images + const options = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } }; diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index a8b680e..631e11b 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -543,7 +543,8 @@ * * @static * @param {XMLDocument} xml - The XML document data. - * @param {PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * @param {Object.|PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * If providing an object, the key is the `` element's `file` attribute in the FNT file. * @return {Object} Result font object with font, size, lineHeight and char fields. */ static registerFont(xml, textures) @@ -551,50 +552,52 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); - const res = getResolutionOfUrl(fileName, settings.RESOLUTION); + const pages = xml.getElementsByTagName('page'); + const res = getResolutionOfUrl(pages[0].getAttribute('file'), settings.RESOLUTION); + const pagesTextures = {}; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; - if (!(textures instanceof Array)) + + // Single texture, convert to list + if (textures instanceof core.Texture) { textures = [textures]; } + // Convert the input Texture, Textures or object + // into a page Texture lookup by "id" + for (let i = 0; i < pages.length; i++) + { + const id = pages[i].getAttribute('id'); + const file = pages[i].getAttribute('file'); + + pagesTextures[id] = textures instanceof Array ? textures[i] : textures[file]; + } + // parse letters const letters = xml.getElementsByTagName('char'); - let page; for (let i = 0; i < letters.length; i++) { const letter = letters[i]; const charCode = parseInt(letter.getAttribute('id'), 10); - let textureRect; - - page = parseInt(letter.getAttribute('page'), 10); - if (isNaN(page)) - { - textureRect = new core.Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new core.Rectangle( - (parseInt(letter.getAttribute('x'), 10) / res) + (textures[page].frame.x / res), - (parseInt(letter.getAttribute('y'), 10) / res) + (textures[page].frame.y / res), - parseInt(letter.getAttribute('width'), 10) / res, - parseInt(letter.getAttribute('height'), 10) / res - ); - } + const page = letter.getAttribute('page') || 0; + const textureRect = new core.Rectangle( + (parseInt(letter.getAttribute('x'), 10) / res) + (pagesTextures[page].frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (pagesTextures[page].frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res + ); data.chars[charCode] = { xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, - texture: new core.Texture(textures[page].baseTexture, textureRect), + texture: new core.Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index ce90e44..c5f17dd 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -67,47 +67,56 @@ } const pages = resource.data.getElementsByTagName('page'); - const textures = []; + const textures = {}; // Handle completed, when the number of textures // load is the same number as references in the fnt file - const completed = () => + const completed = (page) => { - if (textures.length === pages.length) + textures[page.metadata.pageFile] = page.texture; + + if (Object.keys(textures).length === pages.length) { parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - for (let i = 0; i < pages.length; ++i) { - const url = xmlUrl + pages[i].getAttribute('file'); + const pageFile = pages[i].getAttribute('file'); + const url = xmlUrl + pageFile; + let exists = false; + + // incase the image is loaded outside + // using the same loader, resource will be available + for (const name in this.resources) + { + if (this.resources[name].url === url) + { + this.resources[name].metadata.pageFile = pageFile; + completed(this.resources[name]); + exists = true; + break; + } + } // texture is not loaded, we'll attempt to add // it to the load and add the texture to the list - if (!this.resources[url]) + if (!exists) { - this.add(url, loadOptions, (resource) => - { - textures.push(resource.texture); - completed(); - }); - } - else - { - // incase the image is loaded outside - // using the same loader, texture will be available - textures.push(this.resources[url].texture); - completed(); + // Standard loading options for images + const options = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } }; diff --git a/test/extras/BitmapText.js b/test/extras/BitmapText.js index 9d8d2b8..1ca5704 100644 --- a/test/extras/BitmapText.js +++ b/test/extras/BitmapText.js @@ -30,75 +30,95 @@ this.resources = path.join(__dirname, 'resources'); Promise.all([ loadXML('font.fnt'), + loadXML('font-no-page.fnt'), loadImage('font.png'), ]).then(([ fontXML, + font2XML, fontImage, ]) => { this.fontXML = fontXML; + this.font2XML = font2XML; this.fontImage = fontImage; - const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); - - this.font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); done(); }); }); - describe('text', function () + after(function () { - it('should render text even if there are unsupported characters', function () - { - const text = new PIXI.extras.BitmapText('ABCDEFG', { - font: this.font.font, - }); + this.texture.destroy(true); + this.texture = null; + this.font = null; + this.font2 = null; + }); - expect(text.children.length).to.equal(4); + it('should regster fonts from preloaded images', function () + { + this.texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + this.font = PIXI.extras.BitmapText.registerFont(this.fontXML, this.texture); + this.font2 = PIXI.extras.BitmapText.registerFont(this.font2XML, this.texture); + }); + it('should render text even if there are unsupported characters', function () + { + const text = new PIXI.extras.BitmapText('ABCDEFG', { + font: this.font.font, }); - it('should break line on space', function () - { - const bmpText = new PIXI.extras.BitmapText('', { - font: this.font.font, - size: 24, - }); - bmpText.maxWidth = 40; - bmpText.text = 'A A A A A A A '; + expect(text.children.length).to.equal(4); + }); + it('should support font without page reference', function () + { + const text = new PIXI.extras.BitmapText('A', { + font: this.font2.font, + }); + + expect(text.children[0].width).to.equal(19); + expect(text.children[0].height).to.equal(20); + }); + it('should break line on space', function () + { + const bmpText = new PIXI.extras.BitmapText('', { + font: this.font.font, + size: 24, + }); + + bmpText.maxWidth = 40; + bmpText.text = 'A A A A A A A '; + bmpText.updateText(); + + expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); + + bmpText.maxWidth = 40; + bmpText.text = 'A A A A A A A'; + bmpText.updateText(); + + expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); + }); + it('letterSpacing should add extra space between characters', function () + { + const text = 'ABCD zz DCBA'; + const bmpText = new PIXI.extras.BitmapText(text, { + font: this.font.font, + }); + const positions = []; + const renderedChars = bmpText.children.length; + + for (let x = 0; x < renderedChars; ++x) + { + positions.push(bmpText.children[x].x); + } + for (let space = 1; space < 20; ++space) + { + bmpText.letterSpacing = space; bmpText.updateText(); + let prevPos = bmpText.children[0].x; - expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); - - bmpText.maxWidth = 40; - bmpText.text = 'A A A A A A A'; - bmpText.updateText(); - - expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); - }); - it('letterSpacing should add extra space between characters', function () - { - const text = 'ABCD zz DCBA'; - const bmpText = new PIXI.extras.BitmapText(text, { - font: this.font.font, - }); - const positions = []; - const renderedChars = bmpText.children.length; - - for (let x = 0; x < renderedChars; ++x) + for (let char = 1; char < renderedChars; ++char) { - positions.push(bmpText.children[x].x); + expect(bmpText.children[char].x).to.equal(prevPos + space + positions[char] - positions[char - 1]); + prevPos = bmpText.children[char].x; } - for (let space = 1; space < 20; ++space) - { - bmpText.letterSpacing = space; - bmpText.updateText(); - let prevPos = bmpText.children[0].x; - - for (let char = 1; char < renderedChars; ++char) - { - expect(bmpText.children[char].x).to.equal(prevPos + space + positions[char] - positions[char - 1]); - prevPos = bmpText.children[char].x; - } - } - }); + } }); }); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index a8b680e..631e11b 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -543,7 +543,8 @@ * * @static * @param {XMLDocument} xml - The XML document data. - * @param {PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * @param {Object.|PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * If providing an object, the key is the `` element's `file` attribute in the FNT file. * @return {Object} Result font object with font, size, lineHeight and char fields. */ static registerFont(xml, textures) @@ -551,50 +552,52 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); - const res = getResolutionOfUrl(fileName, settings.RESOLUTION); + const pages = xml.getElementsByTagName('page'); + const res = getResolutionOfUrl(pages[0].getAttribute('file'), settings.RESOLUTION); + const pagesTextures = {}; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; - if (!(textures instanceof Array)) + + // Single texture, convert to list + if (textures instanceof core.Texture) { textures = [textures]; } + // Convert the input Texture, Textures or object + // into a page Texture lookup by "id" + for (let i = 0; i < pages.length; i++) + { + const id = pages[i].getAttribute('id'); + const file = pages[i].getAttribute('file'); + + pagesTextures[id] = textures instanceof Array ? textures[i] : textures[file]; + } + // parse letters const letters = xml.getElementsByTagName('char'); - let page; for (let i = 0; i < letters.length; i++) { const letter = letters[i]; const charCode = parseInt(letter.getAttribute('id'), 10); - let textureRect; - - page = parseInt(letter.getAttribute('page'), 10); - if (isNaN(page)) - { - textureRect = new core.Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new core.Rectangle( - (parseInt(letter.getAttribute('x'), 10) / res) + (textures[page].frame.x / res), - (parseInt(letter.getAttribute('y'), 10) / res) + (textures[page].frame.y / res), - parseInt(letter.getAttribute('width'), 10) / res, - parseInt(letter.getAttribute('height'), 10) / res - ); - } + const page = letter.getAttribute('page') || 0; + const textureRect = new core.Rectangle( + (parseInt(letter.getAttribute('x'), 10) / res) + (pagesTextures[page].frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (pagesTextures[page].frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res + ); data.chars[charCode] = { xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, - texture: new core.Texture(textures[page].baseTexture, textureRect), + texture: new core.Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index ce90e44..c5f17dd 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -67,47 +67,56 @@ } const pages = resource.data.getElementsByTagName('page'); - const textures = []; + const textures = {}; // Handle completed, when the number of textures // load is the same number as references in the fnt file - const completed = () => + const completed = (page) => { - if (textures.length === pages.length) + textures[page.metadata.pageFile] = page.texture; + + if (Object.keys(textures).length === pages.length) { parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - for (let i = 0; i < pages.length; ++i) { - const url = xmlUrl + pages[i].getAttribute('file'); + const pageFile = pages[i].getAttribute('file'); + const url = xmlUrl + pageFile; + let exists = false; + + // incase the image is loaded outside + // using the same loader, resource will be available + for (const name in this.resources) + { + if (this.resources[name].url === url) + { + this.resources[name].metadata.pageFile = pageFile; + completed(this.resources[name]); + exists = true; + break; + } + } // texture is not loaded, we'll attempt to add // it to the load and add the texture to the list - if (!this.resources[url]) + if (!exists) { - this.add(url, loadOptions, (resource) => - { - textures.push(resource.texture); - completed(); - }); - } - else - { - // incase the image is loaded outside - // using the same loader, texture will be available - textures.push(this.resources[url].texture); - completed(); + // Standard loading options for images + const options = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } }; diff --git a/test/extras/BitmapText.js b/test/extras/BitmapText.js index 9d8d2b8..1ca5704 100644 --- a/test/extras/BitmapText.js +++ b/test/extras/BitmapText.js @@ -30,75 +30,95 @@ this.resources = path.join(__dirname, 'resources'); Promise.all([ loadXML('font.fnt'), + loadXML('font-no-page.fnt'), loadImage('font.png'), ]).then(([ fontXML, + font2XML, fontImage, ]) => { this.fontXML = fontXML; + this.font2XML = font2XML; this.fontImage = fontImage; - const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); - - this.font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); done(); }); }); - describe('text', function () + after(function () { - it('should render text even if there are unsupported characters', function () - { - const text = new PIXI.extras.BitmapText('ABCDEFG', { - font: this.font.font, - }); + this.texture.destroy(true); + this.texture = null; + this.font = null; + this.font2 = null; + }); - expect(text.children.length).to.equal(4); + it('should regster fonts from preloaded images', function () + { + this.texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + this.font = PIXI.extras.BitmapText.registerFont(this.fontXML, this.texture); + this.font2 = PIXI.extras.BitmapText.registerFont(this.font2XML, this.texture); + }); + it('should render text even if there are unsupported characters', function () + { + const text = new PIXI.extras.BitmapText('ABCDEFG', { + font: this.font.font, }); - it('should break line on space', function () - { - const bmpText = new PIXI.extras.BitmapText('', { - font: this.font.font, - size: 24, - }); - bmpText.maxWidth = 40; - bmpText.text = 'A A A A A A A '; + expect(text.children.length).to.equal(4); + }); + it('should support font without page reference', function () + { + const text = new PIXI.extras.BitmapText('A', { + font: this.font2.font, + }); + + expect(text.children[0].width).to.equal(19); + expect(text.children[0].height).to.equal(20); + }); + it('should break line on space', function () + { + const bmpText = new PIXI.extras.BitmapText('', { + font: this.font.font, + size: 24, + }); + + bmpText.maxWidth = 40; + bmpText.text = 'A A A A A A A '; + bmpText.updateText(); + + expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); + + bmpText.maxWidth = 40; + bmpText.text = 'A A A A A A A'; + bmpText.updateText(); + + expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); + }); + it('letterSpacing should add extra space between characters', function () + { + const text = 'ABCD zz DCBA'; + const bmpText = new PIXI.extras.BitmapText(text, { + font: this.font.font, + }); + const positions = []; + const renderedChars = bmpText.children.length; + + for (let x = 0; x < renderedChars; ++x) + { + positions.push(bmpText.children[x].x); + } + for (let space = 1; space < 20; ++space) + { + bmpText.letterSpacing = space; bmpText.updateText(); + let prevPos = bmpText.children[0].x; - expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); - - bmpText.maxWidth = 40; - bmpText.text = 'A A A A A A A'; - bmpText.updateText(); - - expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); - }); - it('letterSpacing should add extra space between characters', function () - { - const text = 'ABCD zz DCBA'; - const bmpText = new PIXI.extras.BitmapText(text, { - font: this.font.font, - }); - const positions = []; - const renderedChars = bmpText.children.length; - - for (let x = 0; x < renderedChars; ++x) + for (let char = 1; char < renderedChars; ++char) { - positions.push(bmpText.children[x].x); + expect(bmpText.children[char].x).to.equal(prevPos + space + positions[char] - positions[char - 1]); + prevPos = bmpText.children[char].x; } - for (let space = 1; space < 20; ++space) - { - bmpText.letterSpacing = space; - bmpText.updateText(); - let prevPos = bmpText.children[0].x; - - for (let char = 1; char < renderedChars; ++char) - { - expect(bmpText.children[char].x).to.equal(prevPos + space + positions[char] - positions[char - 1]); - prevPos = bmpText.children[char].x; - } - } - }); + } }); }); diff --git a/test/extras/resources/font-no-page.fnt b/test/extras/resources/font-no-page.fnt new file mode 100644 index 0000000..4bbe874 --- /dev/null +++ b/test/extras/resources/font-no-page.fnt @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index a8b680e..631e11b 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -543,7 +543,8 @@ * * @static * @param {XMLDocument} xml - The XML document data. - * @param {PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * @param {Object.|PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * If providing an object, the key is the `` element's `file` attribute in the FNT file. * @return {Object} Result font object with font, size, lineHeight and char fields. */ static registerFont(xml, textures) @@ -551,50 +552,52 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); - const res = getResolutionOfUrl(fileName, settings.RESOLUTION); + const pages = xml.getElementsByTagName('page'); + const res = getResolutionOfUrl(pages[0].getAttribute('file'), settings.RESOLUTION); + const pagesTextures = {}; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; - if (!(textures instanceof Array)) + + // Single texture, convert to list + if (textures instanceof core.Texture) { textures = [textures]; } + // Convert the input Texture, Textures or object + // into a page Texture lookup by "id" + for (let i = 0; i < pages.length; i++) + { + const id = pages[i].getAttribute('id'); + const file = pages[i].getAttribute('file'); + + pagesTextures[id] = textures instanceof Array ? textures[i] : textures[file]; + } + // parse letters const letters = xml.getElementsByTagName('char'); - let page; for (let i = 0; i < letters.length; i++) { const letter = letters[i]; const charCode = parseInt(letter.getAttribute('id'), 10); - let textureRect; - - page = parseInt(letter.getAttribute('page'), 10); - if (isNaN(page)) - { - textureRect = new core.Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new core.Rectangle( - (parseInt(letter.getAttribute('x'), 10) / res) + (textures[page].frame.x / res), - (parseInt(letter.getAttribute('y'), 10) / res) + (textures[page].frame.y / res), - parseInt(letter.getAttribute('width'), 10) / res, - parseInt(letter.getAttribute('height'), 10) / res - ); - } + const page = letter.getAttribute('page') || 0; + const textureRect = new core.Rectangle( + (parseInt(letter.getAttribute('x'), 10) / res) + (pagesTextures[page].frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (pagesTextures[page].frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res + ); data.chars[charCode] = { xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, - texture: new core.Texture(textures[page].baseTexture, textureRect), + texture: new core.Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index ce90e44..c5f17dd 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -67,47 +67,56 @@ } const pages = resource.data.getElementsByTagName('page'); - const textures = []; + const textures = {}; // Handle completed, when the number of textures // load is the same number as references in the fnt file - const completed = () => + const completed = (page) => { - if (textures.length === pages.length) + textures[page.metadata.pageFile] = page.texture; + + if (Object.keys(textures).length === pages.length) { parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - for (let i = 0; i < pages.length; ++i) { - const url = xmlUrl + pages[i].getAttribute('file'); + const pageFile = pages[i].getAttribute('file'); + const url = xmlUrl + pageFile; + let exists = false; + + // incase the image is loaded outside + // using the same loader, resource will be available + for (const name in this.resources) + { + if (this.resources[name].url === url) + { + this.resources[name].metadata.pageFile = pageFile; + completed(this.resources[name]); + exists = true; + break; + } + } // texture is not loaded, we'll attempt to add // it to the load and add the texture to the list - if (!this.resources[url]) + if (!exists) { - this.add(url, loadOptions, (resource) => - { - textures.push(resource.texture); - completed(); - }); - } - else - { - // incase the image is loaded outside - // using the same loader, texture will be available - textures.push(this.resources[url].texture); - completed(); + // Standard loading options for images + const options = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } }; diff --git a/test/extras/BitmapText.js b/test/extras/BitmapText.js index 9d8d2b8..1ca5704 100644 --- a/test/extras/BitmapText.js +++ b/test/extras/BitmapText.js @@ -30,75 +30,95 @@ this.resources = path.join(__dirname, 'resources'); Promise.all([ loadXML('font.fnt'), + loadXML('font-no-page.fnt'), loadImage('font.png'), ]).then(([ fontXML, + font2XML, fontImage, ]) => { this.fontXML = fontXML; + this.font2XML = font2XML; this.fontImage = fontImage; - const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); - - this.font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); done(); }); }); - describe('text', function () + after(function () { - it('should render text even if there are unsupported characters', function () - { - const text = new PIXI.extras.BitmapText('ABCDEFG', { - font: this.font.font, - }); + this.texture.destroy(true); + this.texture = null; + this.font = null; + this.font2 = null; + }); - expect(text.children.length).to.equal(4); + it('should regster fonts from preloaded images', function () + { + this.texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + this.font = PIXI.extras.BitmapText.registerFont(this.fontXML, this.texture); + this.font2 = PIXI.extras.BitmapText.registerFont(this.font2XML, this.texture); + }); + it('should render text even if there are unsupported characters', function () + { + const text = new PIXI.extras.BitmapText('ABCDEFG', { + font: this.font.font, }); - it('should break line on space', function () - { - const bmpText = new PIXI.extras.BitmapText('', { - font: this.font.font, - size: 24, - }); - bmpText.maxWidth = 40; - bmpText.text = 'A A A A A A A '; + expect(text.children.length).to.equal(4); + }); + it('should support font without page reference', function () + { + const text = new PIXI.extras.BitmapText('A', { + font: this.font2.font, + }); + + expect(text.children[0].width).to.equal(19); + expect(text.children[0].height).to.equal(20); + }); + it('should break line on space', function () + { + const bmpText = new PIXI.extras.BitmapText('', { + font: this.font.font, + size: 24, + }); + + bmpText.maxWidth = 40; + bmpText.text = 'A A A A A A A '; + bmpText.updateText(); + + expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); + + bmpText.maxWidth = 40; + bmpText.text = 'A A A A A A A'; + bmpText.updateText(); + + expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); + }); + it('letterSpacing should add extra space between characters', function () + { + const text = 'ABCD zz DCBA'; + const bmpText = new PIXI.extras.BitmapText(text, { + font: this.font.font, + }); + const positions = []; + const renderedChars = bmpText.children.length; + + for (let x = 0; x < renderedChars; ++x) + { + positions.push(bmpText.children[x].x); + } + for (let space = 1; space < 20; ++space) + { + bmpText.letterSpacing = space; bmpText.updateText(); + let prevPos = bmpText.children[0].x; - expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); - - bmpText.maxWidth = 40; - bmpText.text = 'A A A A A A A'; - bmpText.updateText(); - - expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); - }); - it('letterSpacing should add extra space between characters', function () - { - const text = 'ABCD zz DCBA'; - const bmpText = new PIXI.extras.BitmapText(text, { - font: this.font.font, - }); - const positions = []; - const renderedChars = bmpText.children.length; - - for (let x = 0; x < renderedChars; ++x) + for (let char = 1; char < renderedChars; ++char) { - positions.push(bmpText.children[x].x); + expect(bmpText.children[char].x).to.equal(prevPos + space + positions[char] - positions[char - 1]); + prevPos = bmpText.children[char].x; } - for (let space = 1; space < 20; ++space) - { - bmpText.letterSpacing = space; - bmpText.updateText(); - let prevPos = bmpText.children[0].x; - - for (let char = 1; char < renderedChars; ++char) - { - expect(bmpText.children[char].x).to.equal(prevPos + space + positions[char] - positions[char - 1]); - prevPos = bmpText.children[char].x; - } - } - }); + } }); }); diff --git a/test/extras/resources/font-no-page.fnt b/test/extras/resources/font-no-page.fnt new file mode 100644 index 0000000..4bbe874 --- /dev/null +++ b/test/extras/resources/font-no-page.fnt @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 79bcc50..35f08fb 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -116,7 +116,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; expect(charA.texture.baseTexture.source).to.equal(this.fontImage); @@ -124,7 +124,7 @@ expect(charA.texture.frame.y).to.equal(2); expect(charA.texture.frame.width).to.equal(19); expect(charA.texture.frame.height).to.equal(20); - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; expect(charB.texture.baseTexture.source).to.equal(this.fontImage); @@ -132,7 +132,7 @@ expect(charB.texture.frame.y).to.equal(24); expect(charB.texture.frame.width).to.equal(15); expect(charB.texture.frame.height).to.equal(20); - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; expect(charC.texture.baseTexture.source).to.equal(this.fontImage); @@ -140,7 +140,7 @@ expect(charC.texture.frame.y).to.equal(2); expect(charC.texture.frame.width).to.equal(18); expect(charC.texture.frame.height).to.equal(20); - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; expect(charD.texture.baseTexture.source).to.equal(this.fontImage); @@ -148,7 +148,7 @@ expect(charD.texture.frame.y).to.equal(24); expect(charD.texture.frame.width).to.equal(17); expect(charD.texture.frame.height).to.equal(20); - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); @@ -162,7 +162,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); @@ -170,7 +170,7 @@ expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); @@ -178,7 +178,7 @@ expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); @@ -186,7 +186,7 @@ expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); @@ -194,7 +194,7 @@ expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); @@ -215,7 +215,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); @@ -223,7 +223,7 @@ expect(charA.texture.frame.y).to.equal(fontY + 2); expect(charA.texture.frame.width).to.equal(19); expect(charA.texture.frame.height).to.equal(20); - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); @@ -231,7 +231,7 @@ expect(charB.texture.frame.y).to.equal(fontY + 24); expect(charB.texture.frame.width).to.equal(15); expect(charB.texture.frame.height).to.equal(20); - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); @@ -239,7 +239,7 @@ expect(charC.texture.frame.y).to.equal(fontY + 2); expect(charC.texture.frame.width).to.equal(18); expect(charC.texture.frame.height).to.equal(20); - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); @@ -247,7 +247,7 @@ expect(charD.texture.frame.y).to.equal(fontY + 24); expect(charD.texture.frame.width).to.equal(17); expect(charD.texture.frame.height).to.equal(20); - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); @@ -269,7 +269,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); @@ -277,7 +277,7 @@ expect(charA.texture.frame.y).to.equal(fontY + 2); expect(charA.texture.frame.width).to.equal(19); expect(charA.texture.frame.height).to.equal(20); - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); @@ -285,7 +285,7 @@ expect(charB.texture.frame.y).to.equal(fontY + 24); expect(charB.texture.frame.width).to.equal(15); expect(charB.texture.frame.height).to.equal(20); - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); @@ -293,7 +293,7 @@ expect(charC.texture.frame.y).to.equal(fontY + 2); expect(charC.texture.frame.width).to.equal(18); expect(charC.texture.frame.height).to.equal(20); - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); @@ -301,7 +301,7 @@ expect(charD.texture.frame.y).to.equal(fontY + 24); expect(charD.texture.frame.width).to.equal(17); expect(charD.texture.frame.height).to.equal(20); - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); @@ -320,7 +320,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.split_font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; let src = charA.texture.baseTexture.source.src; @@ -331,7 +331,7 @@ expect(charA.texture.frame.y).to.equal(2); expect(charA.texture.frame.width).to.equal(19); expect(charA.texture.frame.height).to.equal(20); - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; src = charB.texture.baseTexture.source.src; @@ -342,7 +342,7 @@ expect(charB.texture.frame.y).to.equal(24); expect(charB.texture.frame.width).to.equal(15); expect(charB.texture.frame.height).to.equal(20); - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; src = charC.texture.baseTexture.source.src; @@ -353,7 +353,7 @@ expect(charC.texture.frame.y).to.equal(2); expect(charC.texture.frame.width).to.equal(18); expect(charC.texture.frame.height).to.equal(20); - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; src = charD.texture.baseTexture.source.src; @@ -364,12 +364,56 @@ expect(charD.texture.frame.y).to.equal(24); expect(charD.texture.frame.width).to.equal(17); expect(charD.texture.frame.height).to.equal(20); - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); }); }); + + it('should split fonts if page IDs are in chronological order', function (done) + { + const loader = new PIXI.loaders.Loader(); + + loader.add(path.join(this.resources, 'split_font2.fnt')); + loader.load(() => + { + const page0 = path.join(this.resources, 'split_font_ab.png'); + const page1 = path.join(this.resources, 'split_font_cd.png'); + + expect(loader.resources[page0].metadata.pageFile).to.equal('split_font_ab.png'); + expect(loader.resources[page1].metadata.pageFile).to.equal('split_font_cd.png'); + + const font = PIXI.extras.BitmapText.fonts.split_font2; + const charA = font.chars['A'.charCodeAt(0)]; + const charC = font.chars['C'.charCodeAt(0)]; + + expect(charA.page).to.equal('0'); + expect(charC.page).to.equal('1'); + expect(charA.texture.baseTexture.imageUrl).to.equal(page0); + expect(charC.texture.baseTexture.imageUrl).to.equal(page1); + + done(); + }); + }); + + it('should register bitmap font with side-loaded image', function (done) + { + const loader = new PIXI.loaders.Loader(); + const imagePath = path.join(this.resources, 'font.png'); + const fontPath = path.join(this.resources, 'font.fnt'); + + loader.add('image', imagePath); + loader.add('font', fontPath); + loader.load(() => + { + expect(Object.values(loader.resources).length).to.equal(2); + expect(loader.resources.image.url).to.equal(imagePath); + expect(loader.resources.font.url).to.equal(fontPath); + + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index a8b680e..631e11b 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -543,7 +543,8 @@ * * @static * @param {XMLDocument} xml - The XML document data. - * @param {PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * @param {Object.|PIXI.Texture|PIXI.Texture[]} textures - List of textures for each page. + * If providing an object, the key is the `` element's `file` attribute in the FNT file. * @return {Object} Result font object with font, size, lineHeight and char fields. */ static registerFont(xml, textures) @@ -551,50 +552,52 @@ const data = {}; const info = xml.getElementsByTagName('info')[0]; const common = xml.getElementsByTagName('common')[0]; - const fileName = xml.getElementsByTagName('page')[0].getAttribute('file'); - const res = getResolutionOfUrl(fileName, settings.RESOLUTION); + const pages = xml.getElementsByTagName('page'); + const res = getResolutionOfUrl(pages[0].getAttribute('file'), settings.RESOLUTION); + const pagesTextures = {}; data.font = info.getAttribute('face'); data.size = parseInt(info.getAttribute('size'), 10); data.lineHeight = parseInt(common.getAttribute('lineHeight'), 10) / res; data.chars = {}; - if (!(textures instanceof Array)) + + // Single texture, convert to list + if (textures instanceof core.Texture) { textures = [textures]; } + // Convert the input Texture, Textures or object + // into a page Texture lookup by "id" + for (let i = 0; i < pages.length; i++) + { + const id = pages[i].getAttribute('id'); + const file = pages[i].getAttribute('file'); + + pagesTextures[id] = textures instanceof Array ? textures[i] : textures[file]; + } + // parse letters const letters = xml.getElementsByTagName('char'); - let page; for (let i = 0; i < letters.length; i++) { const letter = letters[i]; const charCode = parseInt(letter.getAttribute('id'), 10); - let textureRect; - - page = parseInt(letter.getAttribute('page'), 10); - if (isNaN(page)) - { - textureRect = new core.Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new core.Rectangle( - (parseInt(letter.getAttribute('x'), 10) / res) + (textures[page].frame.x / res), - (parseInt(letter.getAttribute('y'), 10) / res) + (textures[page].frame.y / res), - parseInt(letter.getAttribute('width'), 10) / res, - parseInt(letter.getAttribute('height'), 10) / res - ); - } + const page = letter.getAttribute('page') || 0; + const textureRect = new core.Rectangle( + (parseInt(letter.getAttribute('x'), 10) / res) + (pagesTextures[page].frame.x / res), + (parseInt(letter.getAttribute('y'), 10) / res) + (pagesTextures[page].frame.y / res), + parseInt(letter.getAttribute('width'), 10) / res, + parseInt(letter.getAttribute('height'), 10) / res + ); data.chars[charCode] = { xOffset: parseInt(letter.getAttribute('xoffset'), 10) / res, yOffset: parseInt(letter.getAttribute('yoffset'), 10) / res, xAdvance: parseInt(letter.getAttribute('xadvance'), 10) / res, kerning: {}, - texture: new core.Texture(textures[page].baseTexture, textureRect), + texture: new core.Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/src/loaders/bitmapFontParser.js b/src/loaders/bitmapFontParser.js index ce90e44..c5f17dd 100644 --- a/src/loaders/bitmapFontParser.js +++ b/src/loaders/bitmapFontParser.js @@ -67,47 +67,56 @@ } const pages = resource.data.getElementsByTagName('page'); - const textures = []; + const textures = {}; // Handle completed, when the number of textures // load is the same number as references in the fnt file - const completed = () => + const completed = (page) => { - if (textures.length === pages.length) + textures[page.metadata.pageFile] = page.texture; + + if (Object.keys(textures).length === pages.length) { parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: Resource.LOAD_TYPE.IMAGE, - metadata: resource.metadata.imageMetadata, - parentResource: resource, - }; - for (let i = 0; i < pages.length; ++i) { - const url = xmlUrl + pages[i].getAttribute('file'); + const pageFile = pages[i].getAttribute('file'); + const url = xmlUrl + pageFile; + let exists = false; + + // incase the image is loaded outside + // using the same loader, resource will be available + for (const name in this.resources) + { + if (this.resources[name].url === url) + { + this.resources[name].metadata.pageFile = pageFile; + completed(this.resources[name]); + exists = true; + break; + } + } // texture is not loaded, we'll attempt to add // it to the load and add the texture to the list - if (!this.resources[url]) + if (!exists) { - this.add(url, loadOptions, (resource) => - { - textures.push(resource.texture); - completed(); - }); - } - else - { - // incase the image is loaded outside - // using the same loader, texture will be available - textures.push(this.resources[url].texture); - completed(); + // Standard loading options for images + const options = { + crossOrigin: resource.crossOrigin, + loadType: Resource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } }; diff --git a/test/extras/BitmapText.js b/test/extras/BitmapText.js index 9d8d2b8..1ca5704 100644 --- a/test/extras/BitmapText.js +++ b/test/extras/BitmapText.js @@ -30,75 +30,95 @@ this.resources = path.join(__dirname, 'resources'); Promise.all([ loadXML('font.fnt'), + loadXML('font-no-page.fnt'), loadImage('font.png'), ]).then(([ fontXML, + font2XML, fontImage, ]) => { this.fontXML = fontXML; + this.font2XML = font2XML; this.fontImage = fontImage; - const texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); - - this.font = PIXI.extras.BitmapText.registerFont(this.fontXML, texture); done(); }); }); - describe('text', function () + after(function () { - it('should render text even if there are unsupported characters', function () - { - const text = new PIXI.extras.BitmapText('ABCDEFG', { - font: this.font.font, - }); + this.texture.destroy(true); + this.texture = null; + this.font = null; + this.font2 = null; + }); - expect(text.children.length).to.equal(4); + it('should regster fonts from preloaded images', function () + { + this.texture = new PIXI.Texture(new PIXI.BaseTexture(this.fontImage, null, 1)); + this.font = PIXI.extras.BitmapText.registerFont(this.fontXML, this.texture); + this.font2 = PIXI.extras.BitmapText.registerFont(this.font2XML, this.texture); + }); + it('should render text even if there are unsupported characters', function () + { + const text = new PIXI.extras.BitmapText('ABCDEFG', { + font: this.font.font, }); - it('should break line on space', function () - { - const bmpText = new PIXI.extras.BitmapText('', { - font: this.font.font, - size: 24, - }); - bmpText.maxWidth = 40; - bmpText.text = 'A A A A A A A '; + expect(text.children.length).to.equal(4); + }); + it('should support font without page reference', function () + { + const text = new PIXI.extras.BitmapText('A', { + font: this.font2.font, + }); + + expect(text.children[0].width).to.equal(19); + expect(text.children[0].height).to.equal(20); + }); + it('should break line on space', function () + { + const bmpText = new PIXI.extras.BitmapText('', { + font: this.font.font, + size: 24, + }); + + bmpText.maxWidth = 40; + bmpText.text = 'A A A A A A A '; + bmpText.updateText(); + + expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); + + bmpText.maxWidth = 40; + bmpText.text = 'A A A A A A A'; + bmpText.updateText(); + + expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); + }); + it('letterSpacing should add extra space between characters', function () + { + const text = 'ABCD zz DCBA'; + const bmpText = new PIXI.extras.BitmapText(text, { + font: this.font.font, + }); + const positions = []; + const renderedChars = bmpText.children.length; + + for (let x = 0; x < renderedChars; ++x) + { + positions.push(bmpText.children[x].x); + } + for (let space = 1; space < 20; ++space) + { + bmpText.letterSpacing = space; bmpText.updateText(); + let prevPos = bmpText.children[0].x; - expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); - - bmpText.maxWidth = 40; - bmpText.text = 'A A A A A A A'; - bmpText.updateText(); - - expect(bmpText.textWidth).to.lessThan(bmpText.maxWidth); - }); - it('letterSpacing should add extra space between characters', function () - { - const text = 'ABCD zz DCBA'; - const bmpText = new PIXI.extras.BitmapText(text, { - font: this.font.font, - }); - const positions = []; - const renderedChars = bmpText.children.length; - - for (let x = 0; x < renderedChars; ++x) + for (let char = 1; char < renderedChars; ++char) { - positions.push(bmpText.children[x].x); + expect(bmpText.children[char].x).to.equal(prevPos + space + positions[char] - positions[char - 1]); + prevPos = bmpText.children[char].x; } - for (let space = 1; space < 20; ++space) - { - bmpText.letterSpacing = space; - bmpText.updateText(); - let prevPos = bmpText.children[0].x; - - for (let char = 1; char < renderedChars; ++char) - { - expect(bmpText.children[char].x).to.equal(prevPos + space + positions[char] - positions[char - 1]); - prevPos = bmpText.children[char].x; - } - } - }); + } }); }); diff --git a/test/extras/resources/font-no-page.fnt b/test/extras/resources/font-no-page.fnt new file mode 100644 index 0000000..4bbe874 --- /dev/null +++ b/test/extras/resources/font-no-page.fnt @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/loaders/bitmapFontParser.js b/test/loaders/bitmapFontParser.js index 79bcc50..35f08fb 100644 --- a/test/loaders/bitmapFontParser.js +++ b/test/loaders/bitmapFontParser.js @@ -116,7 +116,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; expect(charA.texture.baseTexture.source).to.equal(this.fontImage); @@ -124,7 +124,7 @@ expect(charA.texture.frame.y).to.equal(2); expect(charA.texture.frame.width).to.equal(19); expect(charA.texture.frame.height).to.equal(20); - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; expect(charB.texture.baseTexture.source).to.equal(this.fontImage); @@ -132,7 +132,7 @@ expect(charB.texture.frame.y).to.equal(24); expect(charB.texture.frame.width).to.equal(15); expect(charB.texture.frame.height).to.equal(20); - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; expect(charC.texture.baseTexture.source).to.equal(this.fontImage); @@ -140,7 +140,7 @@ expect(charC.texture.frame.y).to.equal(2); expect(charC.texture.frame.width).to.equal(18); expect(charC.texture.frame.height).to.equal(20); - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; expect(charD.texture.baseTexture.source).to.equal(this.fontImage); @@ -148,7 +148,7 @@ expect(charD.texture.frame.y).to.equal(24); expect(charD.texture.frame.width).to.equal(17); expect(charD.texture.frame.height).to.equal(20); - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); @@ -162,7 +162,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; expect(charA.texture.baseTexture.source).to.equal(this.fontScaledImage); @@ -170,7 +170,7 @@ expect(charA.texture.frame.y).to.equal(4); // 2 / 0.5 expect(charA.texture.frame.width).to.equal(38); // 19 / 0.5 expect(charA.texture.frame.height).to.equal(40); // 20 / 0.5 - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; expect(charB.texture.baseTexture.source).to.equal(this.fontScaledImage); @@ -178,7 +178,7 @@ expect(charB.texture.frame.y).to.equal(48); // 24 / 0.5 expect(charB.texture.frame.width).to.equal(30); // 15 / 0.5 expect(charB.texture.frame.height).to.equal(40); // 20 / 0.5 - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; expect(charC.texture.baseTexture.source).to.equal(this.fontScaledImage); @@ -186,7 +186,7 @@ expect(charC.texture.frame.y).to.equal(4); // 2 / 0.5 expect(charC.texture.frame.width).to.equal(36); // 18 / 0.5 expect(charC.texture.frame.height).to.equal(40); // 20 / 0.5 - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; expect(charD.texture.baseTexture.source).to.equal(this.fontScaledImage); @@ -194,7 +194,7 @@ expect(charD.texture.frame.y).to.equal(48); // 24 / 0.5 expect(charD.texture.frame.width).to.equal(34); // 17 / 0.5 expect(charD.texture.frame.height).to.equal(40); // 20 / 0.5 - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); @@ -215,7 +215,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; expect(charA.texture.baseTexture.source).to.equal(this.atlasImage); @@ -223,7 +223,7 @@ expect(charA.texture.frame.y).to.equal(fontY + 2); expect(charA.texture.frame.width).to.equal(19); expect(charA.texture.frame.height).to.equal(20); - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; expect(charB.texture.baseTexture.source).to.equal(this.atlasImage); @@ -231,7 +231,7 @@ expect(charB.texture.frame.y).to.equal(fontY + 24); expect(charB.texture.frame.width).to.equal(15); expect(charB.texture.frame.height).to.equal(20); - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; expect(charC.texture.baseTexture.source).to.equal(this.atlasImage); @@ -239,7 +239,7 @@ expect(charC.texture.frame.y).to.equal(fontY + 2); expect(charC.texture.frame.width).to.equal(18); expect(charC.texture.frame.height).to.equal(20); - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; expect(charD.texture.baseTexture.source).to.equal(this.atlasImage); @@ -247,7 +247,7 @@ expect(charD.texture.frame.y).to.equal(fontY + 24); expect(charD.texture.frame.width).to.equal(17); expect(charD.texture.frame.height).to.equal(20); - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); @@ -269,7 +269,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; expect(charA.texture.baseTexture.source).to.equal(this.atlasScaledImage); @@ -277,7 +277,7 @@ expect(charA.texture.frame.y).to.equal(fontY + 2); expect(charA.texture.frame.width).to.equal(19); expect(charA.texture.frame.height).to.equal(20); - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; expect(charB.texture.baseTexture.source).to.equal(this.atlasScaledImage); @@ -285,7 +285,7 @@ expect(charB.texture.frame.y).to.equal(fontY + 24); expect(charB.texture.frame.width).to.equal(15); expect(charB.texture.frame.height).to.equal(20); - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; expect(charC.texture.baseTexture.source).to.equal(this.atlasScaledImage); @@ -293,7 +293,7 @@ expect(charC.texture.frame.y).to.equal(fontY + 2); expect(charC.texture.frame.width).to.equal(18); expect(charC.texture.frame.height).to.equal(20); - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; expect(charD.texture.baseTexture.source).to.equal(this.atlasScaledImage); @@ -301,7 +301,7 @@ expect(charD.texture.frame.y).to.equal(fontY + 24); expect(charD.texture.frame.width).to.equal(17); expect(charD.texture.frame.height).to.equal(20); - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); @@ -320,7 +320,7 @@ expect(font).to.be.an.object; expect(PIXI.extras.BitmapText.fonts.split_font).to.equal(font); expect(font).to.have.property('chars'); - const charA = font.chars['A'.charCodeAt(0) || 65]; + const charA = font.chars['A'.charCodeAt(0)]; expect(charA).to.exist; let src = charA.texture.baseTexture.source.src; @@ -331,7 +331,7 @@ expect(charA.texture.frame.y).to.equal(2); expect(charA.texture.frame.width).to.equal(19); expect(charA.texture.frame.height).to.equal(20); - const charB = font.chars['B'.charCodeAt(0) || 66]; + const charB = font.chars['B'.charCodeAt(0)]; expect(charB).to.exist; src = charB.texture.baseTexture.source.src; @@ -342,7 +342,7 @@ expect(charB.texture.frame.y).to.equal(24); expect(charB.texture.frame.width).to.equal(15); expect(charB.texture.frame.height).to.equal(20); - const charC = font.chars['C'.charCodeAt(0) || 67]; + const charC = font.chars['C'.charCodeAt(0)]; expect(charC).to.exist; src = charC.texture.baseTexture.source.src; @@ -353,7 +353,7 @@ expect(charC.texture.frame.y).to.equal(2); expect(charC.texture.frame.width).to.equal(18); expect(charC.texture.frame.height).to.equal(20); - const charD = font.chars['D'.charCodeAt(0) || 68]; + const charD = font.chars['D'.charCodeAt(0)]; expect(charD).to.exist; src = charD.texture.baseTexture.source.src; @@ -364,12 +364,56 @@ expect(charD.texture.frame.y).to.equal(24); expect(charD.texture.frame.width).to.equal(17); expect(charD.texture.frame.height).to.equal(20); - const charE = font.chars['E'.charCodeAt(0) || 69]; + const charE = font.chars['E'.charCodeAt(0)]; expect(charE).to.be.undefined; done(); }); }); + + it('should split fonts if page IDs are in chronological order', function (done) + { + const loader = new PIXI.loaders.Loader(); + + loader.add(path.join(this.resources, 'split_font2.fnt')); + loader.load(() => + { + const page0 = path.join(this.resources, 'split_font_ab.png'); + const page1 = path.join(this.resources, 'split_font_cd.png'); + + expect(loader.resources[page0].metadata.pageFile).to.equal('split_font_ab.png'); + expect(loader.resources[page1].metadata.pageFile).to.equal('split_font_cd.png'); + + const font = PIXI.extras.BitmapText.fonts.split_font2; + const charA = font.chars['A'.charCodeAt(0)]; + const charC = font.chars['C'.charCodeAt(0)]; + + expect(charA.page).to.equal('0'); + expect(charC.page).to.equal('1'); + expect(charA.texture.baseTexture.imageUrl).to.equal(page0); + expect(charC.texture.baseTexture.imageUrl).to.equal(page1); + + done(); + }); + }); + + it('should register bitmap font with side-loaded image', function (done) + { + const loader = new PIXI.loaders.Loader(); + const imagePath = path.join(this.resources, 'font.png'); + const fontPath = path.join(this.resources, 'font.fnt'); + + loader.add('image', imagePath); + loader.add('font', fontPath); + loader.load(() => + { + expect(Object.values(loader.resources).length).to.equal(2); + expect(loader.resources.image.url).to.equal(imagePath); + expect(loader.resources.font.url).to.equal(fontPath); + + done(); + }); + }); }); describe('PIXI.loaders.parseBitmapFontData', function () diff --git a/test/loaders/resources/split_font2.fnt b/test/loaders/resources/split_font2.fnt new file mode 100644 index 0000000..a2ae840 --- /dev/null +++ b/test/loaders/resources/split_font2.fnt @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file