diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js index 4eb829c..14b986d 100644 --- a/packages/text-bitmap/src/BitmapFontLoader.js +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -112,47 +112,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) { BitmapFontLoader.parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: LoaderResource.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: LoaderResource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js index 4eb829c..14b986d 100644 --- a/packages/text-bitmap/src/BitmapFontLoader.js +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -112,47 +112,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) { BitmapFontLoader.parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: LoaderResource.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: LoaderResource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } } diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js index 14e7f2b..143d8ad 100644 --- a/packages/text-bitmap/src/BitmapText.js +++ b/packages/text-bitmap/src/BitmapText.js @@ -549,7 +549,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) @@ -557,50 +558,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 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 Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new 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 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 Texture(textures[page].baseTexture, textureRect), + texture: new Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js index 4eb829c..14b986d 100644 --- a/packages/text-bitmap/src/BitmapFontLoader.js +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -112,47 +112,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) { BitmapFontLoader.parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: LoaderResource.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: LoaderResource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } } diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js index 14e7f2b..143d8ad 100644 --- a/packages/text-bitmap/src/BitmapText.js +++ b/packages/text-bitmap/src/BitmapText.js @@ -549,7 +549,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) @@ -557,50 +558,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 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 Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new 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 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 Texture(textures[page].baseTexture, textureRect), + texture: new Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js index aa2177c..845e136 100644 --- a/packages/text-bitmap/test/BitmapFontLoader.js +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -28,6 +28,8 @@ { const resolveURL = (url) => path.resolve(this.resources, url); + BitmapFontLoader.add(); + this.resources = path.join(__dirname, 'resources'); this.fontXML = null; this.fontScaledXML = null; @@ -124,13 +126,13 @@ it('should properly register bitmap font', function (done) { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + const texture = Texture.from(this.fontImage); const font = BitmapText.registerFont(this.fontXML, texture); expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.fontImage); @@ -138,7 +140,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.resource.source).to.equal(this.fontImage); @@ -146,7 +148,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.resource.source).to.equal(this.fontImage); @@ -154,7 +156,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.resource.source).to.equal(this.fontImage); @@ -162,7 +164,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(); @@ -180,7 +182,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.fontScaledImage); @@ -188,7 +190,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.resource.source).to.equal(this.fontScaledImage); @@ -196,7 +198,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.resource.source).to.equal(this.fontScaledImage); @@ -204,7 +206,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.resource.source).to.equal(this.fontScaledImage); @@ -212,7 +214,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(); @@ -220,7 +222,7 @@ it('should properly register bitmap font NESTED into spritesheet', function (done) { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const baseTexture = new BaseTexture(this.atlasImage); const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); spritesheet.parse(() => @@ -233,7 +235,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.atlasImage); @@ -241,7 +243,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.resource.source).to.equal(this.atlasImage); @@ -249,7 +251,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.resource.source).to.equal(this.atlasImage); @@ -257,7 +259,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.resource.source).to.equal(this.atlasImage); @@ -265,7 +267,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(); @@ -274,7 +276,7 @@ it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const baseTexture = new BaseTexture(this.atlasScaledImage); const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); spritesheet.resolution = 1; @@ -289,7 +291,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.atlasScaledImage); @@ -297,7 +299,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.resource.source).to.equal(this.atlasScaledImage); @@ -305,7 +307,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.resource.source).to.equal(this.atlasScaledImage); @@ -313,7 +315,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.resource.source).to.equal(this.atlasScaledImage); @@ -321,7 +323,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(); @@ -332,6 +334,7 @@ { const loader = new Loader(); + loader.use(BitmapFontLoader.use); loader.add(path.join(this.resources, 'split_font.fnt')); loader.load(() => { @@ -340,7 +343,7 @@ expect(font).to.be.an.object; expect(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.resource.url; @@ -351,7 +354,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.resource.url; @@ -362,7 +365,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.resource.url; @@ -373,7 +376,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.resource.url; @@ -384,13 +387,58 @@ 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 Loader(); + + loader.use(BitmapFontLoader.use); + 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 = 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.resource.url).to.equal(page0); + expect(charC.texture.baseTexture.resource.url).to.equal(page1); + + done(); + }); + }); + + it('should register bitmap font with side-loaded image', function (done) + { + const loader = new 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(); + }); + }); + it('should parse exist', function () { expect(BitmapFontLoader.parse).to.be.a('function'); diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js index 4eb829c..14b986d 100644 --- a/packages/text-bitmap/src/BitmapFontLoader.js +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -112,47 +112,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) { BitmapFontLoader.parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: LoaderResource.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: LoaderResource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } } diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js index 14e7f2b..143d8ad 100644 --- a/packages/text-bitmap/src/BitmapText.js +++ b/packages/text-bitmap/src/BitmapText.js @@ -549,7 +549,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) @@ -557,50 +558,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 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 Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new 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 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 Texture(textures[page].baseTexture, textureRect), + texture: new Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js index aa2177c..845e136 100644 --- a/packages/text-bitmap/test/BitmapFontLoader.js +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -28,6 +28,8 @@ { const resolveURL = (url) => path.resolve(this.resources, url); + BitmapFontLoader.add(); + this.resources = path.join(__dirname, 'resources'); this.fontXML = null; this.fontScaledXML = null; @@ -124,13 +126,13 @@ it('should properly register bitmap font', function (done) { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + const texture = Texture.from(this.fontImage); const font = BitmapText.registerFont(this.fontXML, texture); expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.fontImage); @@ -138,7 +140,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.resource.source).to.equal(this.fontImage); @@ -146,7 +148,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.resource.source).to.equal(this.fontImage); @@ -154,7 +156,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.resource.source).to.equal(this.fontImage); @@ -162,7 +164,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(); @@ -180,7 +182,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.fontScaledImage); @@ -188,7 +190,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.resource.source).to.equal(this.fontScaledImage); @@ -196,7 +198,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.resource.source).to.equal(this.fontScaledImage); @@ -204,7 +206,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.resource.source).to.equal(this.fontScaledImage); @@ -212,7 +214,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(); @@ -220,7 +222,7 @@ it('should properly register bitmap font NESTED into spritesheet', function (done) { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const baseTexture = new BaseTexture(this.atlasImage); const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); spritesheet.parse(() => @@ -233,7 +235,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.atlasImage); @@ -241,7 +243,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.resource.source).to.equal(this.atlasImage); @@ -249,7 +251,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.resource.source).to.equal(this.atlasImage); @@ -257,7 +259,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.resource.source).to.equal(this.atlasImage); @@ -265,7 +267,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(); @@ -274,7 +276,7 @@ it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const baseTexture = new BaseTexture(this.atlasScaledImage); const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); spritesheet.resolution = 1; @@ -289,7 +291,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.atlasScaledImage); @@ -297,7 +299,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.resource.source).to.equal(this.atlasScaledImage); @@ -305,7 +307,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.resource.source).to.equal(this.atlasScaledImage); @@ -313,7 +315,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.resource.source).to.equal(this.atlasScaledImage); @@ -321,7 +323,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(); @@ -332,6 +334,7 @@ { const loader = new Loader(); + loader.use(BitmapFontLoader.use); loader.add(path.join(this.resources, 'split_font.fnt')); loader.load(() => { @@ -340,7 +343,7 @@ expect(font).to.be.an.object; expect(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.resource.url; @@ -351,7 +354,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.resource.url; @@ -362,7 +365,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.resource.url; @@ -373,7 +376,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.resource.url; @@ -384,13 +387,58 @@ 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 Loader(); + + loader.use(BitmapFontLoader.use); + 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 = 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.resource.url).to.equal(page0); + expect(charC.texture.baseTexture.resource.url).to.equal(page1); + + done(); + }); + }); + + it('should register bitmap font with side-loaded image', function (done) + { + const loader = new 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(); + }); + }); + it('should parse exist', function () { expect(BitmapFontLoader.parse).to.be.a('function'); diff --git a/packages/text-bitmap/test/BitmapText.js b/packages/text-bitmap/test/BitmapText.js index ff7a82c..084df40 100644 --- a/packages/text-bitmap/test/BitmapText.js +++ b/packages/text-bitmap/test/BitmapText.js @@ -1,7 +1,7 @@ const path = require('path'); const fs = require('fs'); const { BitmapText } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); +const { Texture } = require('@pixi/core'); describe('PIXI.BitmapText', function () { @@ -29,76 +29,96 @@ this.resources = path.join(__dirname, 'resources'); Promise.all([ - loadXML('font1.fnt'), - loadImage('font1.png'), + 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 Texture(new BaseTexture(this.fontImage, null, 1)); - - this.font = 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 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 register fonts from preloaded images', function () + { + this.texture = Texture.from(this.fontImage); + this.font = BitmapText.registerFont(this.fontXML, this.texture); + this.font2 = BitmapText.registerFont(this.font2XML, this.texture); + }); + it('should render text even if there are unsupported characters', function () + { + const text = new BitmapText('ABCDEFG', { + font: this.font.font, }); - it('should break line on space', function () - { - const bmpText = new 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 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 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 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 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/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js index 4eb829c..14b986d 100644 --- a/packages/text-bitmap/src/BitmapFontLoader.js +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -112,47 +112,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) { BitmapFontLoader.parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: LoaderResource.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: LoaderResource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } } diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js index 14e7f2b..143d8ad 100644 --- a/packages/text-bitmap/src/BitmapText.js +++ b/packages/text-bitmap/src/BitmapText.js @@ -549,7 +549,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) @@ -557,50 +558,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 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 Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new 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 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 Texture(textures[page].baseTexture, textureRect), + texture: new Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js index aa2177c..845e136 100644 --- a/packages/text-bitmap/test/BitmapFontLoader.js +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -28,6 +28,8 @@ { const resolveURL = (url) => path.resolve(this.resources, url); + BitmapFontLoader.add(); + this.resources = path.join(__dirname, 'resources'); this.fontXML = null; this.fontScaledXML = null; @@ -124,13 +126,13 @@ it('should properly register bitmap font', function (done) { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + const texture = Texture.from(this.fontImage); const font = BitmapText.registerFont(this.fontXML, texture); expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.fontImage); @@ -138,7 +140,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.resource.source).to.equal(this.fontImage); @@ -146,7 +148,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.resource.source).to.equal(this.fontImage); @@ -154,7 +156,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.resource.source).to.equal(this.fontImage); @@ -162,7 +164,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(); @@ -180,7 +182,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.fontScaledImage); @@ -188,7 +190,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.resource.source).to.equal(this.fontScaledImage); @@ -196,7 +198,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.resource.source).to.equal(this.fontScaledImage); @@ -204,7 +206,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.resource.source).to.equal(this.fontScaledImage); @@ -212,7 +214,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(); @@ -220,7 +222,7 @@ it('should properly register bitmap font NESTED into spritesheet', function (done) { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const baseTexture = new BaseTexture(this.atlasImage); const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); spritesheet.parse(() => @@ -233,7 +235,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.atlasImage); @@ -241,7 +243,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.resource.source).to.equal(this.atlasImage); @@ -249,7 +251,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.resource.source).to.equal(this.atlasImage); @@ -257,7 +259,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.resource.source).to.equal(this.atlasImage); @@ -265,7 +267,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(); @@ -274,7 +276,7 @@ it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const baseTexture = new BaseTexture(this.atlasScaledImage); const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); spritesheet.resolution = 1; @@ -289,7 +291,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.atlasScaledImage); @@ -297,7 +299,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.resource.source).to.equal(this.atlasScaledImage); @@ -305,7 +307,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.resource.source).to.equal(this.atlasScaledImage); @@ -313,7 +315,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.resource.source).to.equal(this.atlasScaledImage); @@ -321,7 +323,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(); @@ -332,6 +334,7 @@ { const loader = new Loader(); + loader.use(BitmapFontLoader.use); loader.add(path.join(this.resources, 'split_font.fnt')); loader.load(() => { @@ -340,7 +343,7 @@ expect(font).to.be.an.object; expect(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.resource.url; @@ -351,7 +354,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.resource.url; @@ -362,7 +365,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.resource.url; @@ -373,7 +376,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.resource.url; @@ -384,13 +387,58 @@ 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 Loader(); + + loader.use(BitmapFontLoader.use); + 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 = 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.resource.url).to.equal(page0); + expect(charC.texture.baseTexture.resource.url).to.equal(page1); + + done(); + }); + }); + + it('should register bitmap font with side-loaded image', function (done) + { + const loader = new 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(); + }); + }); + it('should parse exist', function () { expect(BitmapFontLoader.parse).to.be.a('function'); diff --git a/packages/text-bitmap/test/BitmapText.js b/packages/text-bitmap/test/BitmapText.js index ff7a82c..084df40 100644 --- a/packages/text-bitmap/test/BitmapText.js +++ b/packages/text-bitmap/test/BitmapText.js @@ -1,7 +1,7 @@ const path = require('path'); const fs = require('fs'); const { BitmapText } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); +const { Texture } = require('@pixi/core'); describe('PIXI.BitmapText', function () { @@ -29,76 +29,96 @@ this.resources = path.join(__dirname, 'resources'); Promise.all([ - loadXML('font1.fnt'), - loadImage('font1.png'), + 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 Texture(new BaseTexture(this.fontImage, null, 1)); - - this.font = 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 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 register fonts from preloaded images', function () + { + this.texture = Texture.from(this.fontImage); + this.font = BitmapText.registerFont(this.fontXML, this.texture); + this.font2 = BitmapText.registerFont(this.font2XML, this.texture); + }); + it('should render text even if there are unsupported characters', function () + { + const text = new BitmapText('ABCDEFG', { + font: this.font.font, }); - it('should break line on space', function () - { - const bmpText = new 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 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 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 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 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/packages/text-bitmap/test/resources/font-no-page.fnt b/packages/text-bitmap/test/resources/font-no-page.fnt new file mode 100644 index 0000000..fe3de77 --- /dev/null +++ b/packages/text-bitmap/test/resources/font-no-page.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/text-bitmap/src/BitmapFontLoader.js b/packages/text-bitmap/src/BitmapFontLoader.js index 4eb829c..14b986d 100644 --- a/packages/text-bitmap/src/BitmapFontLoader.js +++ b/packages/text-bitmap/src/BitmapFontLoader.js @@ -112,47 +112,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) { BitmapFontLoader.parse(resource, textures); next(); } }; - // Standard loading options for images - const loadOptions = { - crossOrigin: resource.crossOrigin, - loadType: LoaderResource.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: LoaderResource.LOAD_TYPE.IMAGE, + metadata: Object.assign( + { pageFile }, + resource.metadata.imageMetadata + ), + parentResource: resource, + }; + + this.add(url, options, completed); } } } diff --git a/packages/text-bitmap/src/BitmapText.js b/packages/text-bitmap/src/BitmapText.js index 14e7f2b..143d8ad 100644 --- a/packages/text-bitmap/src/BitmapText.js +++ b/packages/text-bitmap/src/BitmapText.js @@ -549,7 +549,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) @@ -557,50 +558,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 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 Rectangle(0, 0, 0, 0); - page = 0; - } - else - { - textureRect = new 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 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 Texture(textures[page].baseTexture, textureRect), + texture: new Texture(pagesTextures[page].baseTexture, textureRect), page, }; } diff --git a/packages/text-bitmap/test/BitmapFontLoader.js b/packages/text-bitmap/test/BitmapFontLoader.js index aa2177c..845e136 100644 --- a/packages/text-bitmap/test/BitmapFontLoader.js +++ b/packages/text-bitmap/test/BitmapFontLoader.js @@ -28,6 +28,8 @@ { const resolveURL = (url) => path.resolve(this.resources, url); + BitmapFontLoader.add(); + this.resources = path.join(__dirname, 'resources'); this.fontXML = null; this.fontScaledXML = null; @@ -124,13 +126,13 @@ it('should properly register bitmap font', function (done) { - const texture = new Texture(new BaseTexture(this.fontImage, null, 1)); + const texture = Texture.from(this.fontImage); const font = BitmapText.registerFont(this.fontXML, texture); expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.fontImage); @@ -138,7 +140,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.resource.source).to.equal(this.fontImage); @@ -146,7 +148,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.resource.source).to.equal(this.fontImage); @@ -154,7 +156,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.resource.source).to.equal(this.fontImage); @@ -162,7 +164,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(); @@ -180,7 +182,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.fontScaledImage); @@ -188,7 +190,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.resource.source).to.equal(this.fontScaledImage); @@ -196,7 +198,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.resource.source).to.equal(this.fontScaledImage); @@ -204,7 +206,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.resource.source).to.equal(this.fontScaledImage); @@ -212,7 +214,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(); @@ -220,7 +222,7 @@ it('should properly register bitmap font NESTED into spritesheet', function (done) { - const baseTexture = new BaseTexture(this.atlasImage, null, 1); + const baseTexture = new BaseTexture(this.atlasImage); const spritesheet = new Spritesheet(baseTexture, this.atlasJSON); spritesheet.parse(() => @@ -233,7 +235,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.atlasImage); @@ -241,7 +243,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.resource.source).to.equal(this.atlasImage); @@ -249,7 +251,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.resource.source).to.equal(this.atlasImage); @@ -257,7 +259,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.resource.source).to.equal(this.atlasImage); @@ -265,7 +267,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(); @@ -274,7 +276,7 @@ it('should properly register bitmap font NESTED into SCALED spritesheet', function (done) { - const baseTexture = new BaseTexture(this.atlasScaledImage, null, 1); + const baseTexture = new BaseTexture(this.atlasScaledImage); const spritesheet = new Spritesheet(baseTexture, this.atlasScaledJSON); spritesheet.resolution = 1; @@ -289,7 +291,7 @@ expect(font).to.be.an.object; expect(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.resource.source).to.equal(this.atlasScaledImage); @@ -297,7 +299,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.resource.source).to.equal(this.atlasScaledImage); @@ -305,7 +307,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.resource.source).to.equal(this.atlasScaledImage); @@ -313,7 +315,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.resource.source).to.equal(this.atlasScaledImage); @@ -321,7 +323,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(); @@ -332,6 +334,7 @@ { const loader = new Loader(); + loader.use(BitmapFontLoader.use); loader.add(path.join(this.resources, 'split_font.fnt')); loader.load(() => { @@ -340,7 +343,7 @@ expect(font).to.be.an.object; expect(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.resource.url; @@ -351,7 +354,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.resource.url; @@ -362,7 +365,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.resource.url; @@ -373,7 +376,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.resource.url; @@ -384,13 +387,58 @@ 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 Loader(); + + loader.use(BitmapFontLoader.use); + 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 = 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.resource.url).to.equal(page0); + expect(charC.texture.baseTexture.resource.url).to.equal(page1); + + done(); + }); + }); + + it('should register bitmap font with side-loaded image', function (done) + { + const loader = new 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(); + }); + }); + it('should parse exist', function () { expect(BitmapFontLoader.parse).to.be.a('function'); diff --git a/packages/text-bitmap/test/BitmapText.js b/packages/text-bitmap/test/BitmapText.js index ff7a82c..084df40 100644 --- a/packages/text-bitmap/test/BitmapText.js +++ b/packages/text-bitmap/test/BitmapText.js @@ -1,7 +1,7 @@ const path = require('path'); const fs = require('fs'); const { BitmapText } = require('../'); -const { Texture, BaseTexture } = require('@pixi/core'); +const { Texture } = require('@pixi/core'); describe('PIXI.BitmapText', function () { @@ -29,76 +29,96 @@ this.resources = path.join(__dirname, 'resources'); Promise.all([ - loadXML('font1.fnt'), - loadImage('font1.png'), + 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 Texture(new BaseTexture(this.fontImage, null, 1)); - - this.font = 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 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 register fonts from preloaded images', function () + { + this.texture = Texture.from(this.fontImage); + this.font = BitmapText.registerFont(this.fontXML, this.texture); + this.font2 = BitmapText.registerFont(this.font2XML, this.texture); + }); + it('should render text even if there are unsupported characters', function () + { + const text = new BitmapText('ABCDEFG', { + font: this.font.font, }); - it('should break line on space', function () - { - const bmpText = new 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 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 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 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 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/packages/text-bitmap/test/resources/font-no-page.fnt b/packages/text-bitmap/test/resources/font-no-page.fnt new file mode 100644 index 0000000..fe3de77 --- /dev/null +++ b/packages/text-bitmap/test/resources/font-no-page.fnt @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/text-bitmap/test/resources/split_font2.fnt b/packages/text-bitmap/test/resources/split_font2.fnt new file mode 100644 index 0000000..a2ae840 --- /dev/null +++ b/packages/text-bitmap/test/resources/split_font2.fnt @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file